Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@georapbox/capture-photo-element

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@georapbox/capture-photo-element - npm Package Compare versions

Comparing version 2.0.0 to 3.0.0

src/utils/clamp.js

6

dist/capture-photo-defined.js

@@ -1,2 +0,2 @@

var t,e,i,s;function o(t,e,i){if(!e.has(t))throw new TypeError("attempted to "+i+" private field on non-instance");return e.get(t)}function a(t,e){return e.get?e.get.call(t):e.value}function n(t,e){return a(t,o(t,e,"get"))}function h(t,e){if(e.has(t))throw new TypeError("Cannot initialize the same private elements twice on an object")}function r(t,e,i){h(t,e),e.set(t,i)}function l(t,e,i){if(e.set)e.set.call(t,i);else{if(!e.writable)throw new TypeError("attempted to set read only private field");e.value=i}}function c(t,e,i){return l(t,o(t,e,"set"),i),i}function u(t,e,i){if(!e.has(t))throw new TypeError("attempted to get private field on non-instance");return i}function d(t,e){h(t,e),e.add(t)}t={},e="CapturePhoto",i=function(){return D},Object.defineProperty(t,e,{get:i,set:s,enumerable:!0,configurable:!0});const p=document.createElement("template"),m=String.raw;p.innerHTML=m`
var t,e,i,s;function a(t,e,i){if(!e.has(t))throw new TypeError("attempted to "+i+" private field on non-instance");return e.get(t)}function n(t,e){return e.get?e.get.call(t):e.value}function o(t,e){return n(t,a(t,e,"get"))}function r(t,e){if(e.has(t))throw new TypeError("Cannot initialize the same private elements twice on an object")}function h(t,e,i){r(t,e),e.set(t,i)}function l(t,e,i){if(e.set)e.set.call(t,i);else{if(!e.writable)throw new TypeError("attempted to set read only private field");e.value=i}}function c(t,e,i){return l(t,a(t,e,"set"),i),i}function u(t,e,i){if(!e.has(t))throw new TypeError("attempted to get private field on non-instance");return i}function d(t,e){r(t,e),e.add(t)}t={},e="CapturePhoto",i=function(){return B},Object.defineProperty(t,e,{get:i,set:s,enumerable:!0,configurable:!0});const p=document.createElement("template"),m=String.raw;p.innerHTML=m`
<style>

@@ -29,6 +29,6 @@ :host {

</slot>
<slot name="facing-mode-button"><button part="facing-mode-button" type="button"><slot name="facing-mode-button-content">Toggle facing mode</slot></button></slot>
<slot name="facing-mode-button" hidden><button part="facing-mode-button" type="button"><slot name="facing-mode-button-content">Toggle facing mode</slot></button></slot>
</div>
<div part="output-container" id="output"></div>
`;var b=new WeakMap,g=new WeakMap,v=new WeakMap,f=new WeakMap,w=new WeakMap,E=new WeakMap,k=new WeakMap,y=new WeakMap,M=new WeakMap,C=new WeakMap,W=new WeakSet,A=new WeakSet,N=new WeakMap,S=new WeakMap,z=new WeakMap,L=new WeakSet,T=new WeakSet,R=new WeakMap,x=new WeakMap,O=new WeakSet,I=new WeakSet,U=new WeakSet;class D extends HTMLElement{connectedCallback(){if(u(this,U,F).call(this,"noImage"),u(this,U,F).call(this,"facingMode"),u(this,U,F).call(this,"cameraResolution"),u(this,U,F).call(this,"zoom"),c(this,b,!0),c(this,f,this.shadowRoot.querySelector("canvas")),c(this,w,this.shadowRoot.getElementById("output")),c(this,E,this.shadowRoot.querySelector("video")),n(this,E)&&n(this,E).addEventListener("loadedmetadata",n(this,z)),c(this,k,this.shadowRoot.querySelector('slot[name="capture-button"]')),n(this,k)&&n(this,k).addEventListener("slotchange",n(this,R)),c(this,y,u(this,I,V).call(this)),n(this,y)&&n(this,y).addEventListener("click",n(this,S)),c(this,M,this.shadowRoot.querySelector('slot[name="facing-mode-button"]')),n(this,M)&&n(this,M).addEventListener("slotchange",n(this,x)),c(this,C,u(this,O,H).call(this)),n(this,C)&&(n(this,g).facingMode?n(this,C).addEventListener("click",n(this,N)):n(this,C).hidden=!0),!D.isSupported())return this.dispatchEvent(new CustomEvent("capture-photo:error",{bubbles:!0,composed:!0,detail:{error:{name:"NotSupportedError",message:"Not supported"}}}));u(this,A,j).call(this)}disconnectedCallback(){u(this,W,B).call(this),n(this,C)&&n(this,C).removeEventListener("click",n(this,N)),n(this,y)&&n(this,y).removeEventListener("click",n(this,S)),n(this,E)&&n(this,E).removeEventListener("canplay",n(this,z)),n(this,k)&&n(this,k).removeEventListener("slotchange",n(this,R)),n(this,M)&&n(this,M).removeEventListener("slotchange",n(this,x))}attributeChangedCallback(t,e,i){n(this,b)&&("no-image"===t&&u(this,L,q).call(this),"facing-mode"===t&&n(this,g).facingMode&&e!==i&&(u(this,W,B).call(this),u(this,A,j).call(this),this.dispatchEvent(new CustomEvent("capture-photo:facing-mode-change",{bubbles:!0,composed:!0,detail:{facingMode:i}}))),"camera-resolution"===t&&e!==i&&(u(this,W,B).call(this),u(this,A,j).call(this),this.dispatchEvent(new CustomEvent("capture-photo:camera-resolution-change",{bubbles:!0,composed:!0,detail:{cameraResolution:i}}))),"zoom"===t&&e!==i&&(u(this,T,P).call(this,this.zoom),this.dispatchEvent(new CustomEvent("capture-photo:zoom-change",{bubbles:!0,composed:!0,detail:{zoom:this.zoom}}))))}static get observedAttributes(){return["no-image","facing-mode","camera-resolution","zoom"]}get noImage(){return this.hasAttribute("no-image")}set noImage(t){t?this.setAttribute("no-image",""):this.removeAttribute("no-image")}get facingMode(){return this.getAttribute("facing-mode")}set facingMode(t){this.setAttribute("facing-mode",t)}get cameraResolution(){return this.getAttribute("camera-resolution")}set cameraResolution(t){this.setAttribute("camera-resolution",t)}get zoom(){return Number(this.getAttribute("zoom"))||null}set zoom(t){const e=Number(t)||0;this.setAttribute("zoom",e>0?Math.floor(e):0)}get loading(){return this.hasAttribute("loading")}capture(){if(!this.loading)try{const t=n(this,f).getContext("2d"),e=n(this,E).videoWidth,i=n(this,E).videoHeight;n(this,f).width=e,n(this,f).height=i,t.drawImage(n(this,E),0,0,e,i);const s=n(this,f).toDataURL("image/png");if("string"==typeof s&&s.includes("data:image")){if(!this.noImage){const t=new Image;t.src=s,t.width=e,t.height=i,t.part="output-image",u(this,L,q).call(this),n(this,w)&&n(this,w).appendChild(t)}this.dispatchEvent(new CustomEvent("capture-photo:success",{bubbles:!0,composed:!0,detail:{dataURI:s,width:e,height:i}}))}}catch(t){this.dispatchEvent(new CustomEvent("capture-photo:error",{bubbles:!0,composed:!0,detail:{error:t}}))}}static isSupported(){return Boolean(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia)}static defineCustomElement(t="capture-photo"){"undefined"==typeof window||window.customElements.get(t)||window.customElements.define(t,D)}constructor(){super(),d(this,W),d(this,A),d(this,L),d(this,T),d(this,O),d(this,I),d(this,U),r(this,b,{writable:!0,value:void 0}),r(this,g,{writable:!0,value:void 0}),r(this,v,{writable:!0,value:void 0}),r(this,f,{writable:!0,value:void 0}),r(this,w,{writable:!0,value:void 0}),r(this,E,{writable:!0,value:void 0}),r(this,k,{writable:!0,value:void 0}),r(this,y,{writable:!0,value:void 0}),r(this,M,{writable:!0,value:void 0}),r(this,C,{writable:!0,value:void 0}),r(this,N,{writable:!0,value:t=>{t.preventDefault(),this.loading||(this.facingMode="user"!==this.facingMode&&this.facingMode?"user":"environment")}}),r(this,S,{writable:!0,value:t=>{t.preventDefault(),this.capture()}}),r(this,z,{writable:!0,value:t=>{const e=t.target;e.play().then((()=>{this.dispatchEvent(new CustomEvent("capture-photo:video-play",{bubbles:!0,composed:!0,detail:{video:e}}))})).catch((t=>{this.dispatchEvent(new CustomEvent("capture-photo:error",{bubbles:!0,composed:!0,detail:{error:t}}))})).finally((()=>{this.removeAttribute("loading")}))}}),r(this,R,{writable:!0,value:t=>{t.target&&"capture-button"===t.target.name&&(n(this,y)&&n(this,y).removeEventListener("click",n(this,S)),c(this,y,u(this,I,V).call(this)),n(this,y)&&(n(this,y).addEventListener("click",n(this,S)),"BUTTON"===n(this,y).nodeName||n(this,y).hasAttribute("role")||n(this,y).setAttribute("role","button")))}}),r(this,x,{writable:!0,value:t=>{t.target&&"facing-mode-button"===t.target.name&&(n(this,C)&&n(this,C).removeEventListener("click",n(this,N)),c(this,C,u(this,O,H).call(this)),n(this,C)&&(n(this,C).addEventListener("click",n(this,N)),"BUTTON"===n(this,C).nodeName||n(this,C).hasAttribute("role")||n(this,C).setAttribute("role","button")))}}),c(this,b,!1),c(this,g,D.isSupported()?navigator.mediaDevices.getSupportedConstraints():{}),this.shadowRoot||(this.attachShadow({mode:"open"}),this.shadowRoot.appendChild(p.content.cloneNode(!0)))}}function B(){if(!n(this,E)||!n(this,v))return;const[t]=n(this,v).getVideoTracks();t&&t.stop(),n(this,E).srcObject=null,c(this,v,null)}function j(){if(!D.isSupported())return;this.setAttribute("loading","");const t={video:{facingMode:{ideal:this.facingMode||"user"}},audio:!1};if("string"==typeof this.cameraResolution){const[e,i]=this.cameraResolution.split("x");t.video.width=e,t.video.height=i}navigator.mediaDevices.getUserMedia(t).then((t=>{n(this,E).srcObject=t,c(this,v,t),u(this,T,P).call(this,this.zoom)})).catch((t=>{this.dispatchEvent(new CustomEvent("capture-photo:error",{bubbles:!0,composed:!0,detail:{error:t}}))})).finally((()=>{this.removeAttribute("loading")}))}function q(){n(this,w)&&Array.from(n(this,w).childNodes).forEach((t=>t.remove()))}function P(t){if(!n(this,v)||!t)return;const[e]=n(this,v).getVideoTracks();if("function"!=typeof e.getCapabilities||"function"!=typeof e.getSettings)return;const i=e.getCapabilities();var s,o,a;"zoom"in e.getSettings()&&e.applyConstraints({advanced:[{zoom:(s=Number(t),o=i.zoom.min,a=i.zoom.max,Number.isNaN(o)&&(o=0),Number.isNaN(a)&&(a=0),Math.min(Math.max(s,Math.min(o,a)),Math.max(o,a)))}]})}function H(){return n(this,M)?n(this,M).assignedElements({flatten:!0}).find((t=>"BUTTON"===t.nodeName||"facing-mode-button"===t.getAttribute("slot"))):null}function V(){return n(this,k)?n(this,k).assignedElements({flatten:!0}).find((t=>"BUTTON"===t.nodeName||"capture-button"===t.getAttribute("slot"))):null}function F(t){if(Object.prototype.hasOwnProperty.call(this,t)){const e=this[t];delete this[t],this[t]=e}}D.defineCustomElement();export{D as CapturePhoto};
`;var g=new WeakMap,b=new WeakMap,v=new WeakMap,f=new WeakMap,w=new WeakMap,k=new WeakMap,E=new WeakMap,y=new WeakMap,M=new WeakMap,C=new WeakMap,N=new WeakSet,S=new WeakSet,T=new WeakMap,A=new WeakMap,W=new WeakMap,z=new WeakSet,L=new WeakSet,x=new WeakMap,R=new WeakMap,O=new WeakSet,I=new WeakSet,U=new WeakSet;class B extends HTMLElement{connectedCallback(){if(u(this,U,F).call(this,"noImage"),u(this,U,F).call(this,"facingMode"),u(this,U,F).call(this,"cameraResolution"),u(this,U,F).call(this,"pan"),u(this,U,F).call(this,"tilt"),u(this,U,F).call(this,"zoom"),c(this,g,!0),c(this,f,this.shadowRoot.querySelector("canvas")),c(this,w,this.shadowRoot.getElementById("output")),c(this,k,this.shadowRoot.querySelector("video")),c(this,E,this.shadowRoot.querySelector('slot[name="capture-button"]')),c(this,y,u(this,I,H).call(this)),c(this,M,this.shadowRoot.querySelector('slot[name="facing-mode-button"]')),c(this,C,u(this,O,V).call(this)),o(this,k)?.addEventListener("loadedmetadata",o(this,W)),o(this,E)?.addEventListener("slotchange",o(this,x)),o(this,y)?.addEventListener("click",o(this,A)),o(this,M)?.addEventListener("slotchange",o(this,R)),o(this,C)?.addEventListener("click",o(this,T)),!B.isSupported())return this.dispatchEvent(new CustomEvent("capture-photo:error",{bubbles:!0,composed:!0,detail:{error:{name:"NotSupportedError",message:"Not supported"}}}));u(this,S,j).call(this)}disconnectedCallback(){u(this,N,D).call(this),o(this,C)?.removeEventListener("click",o(this,T)),o(this,y)?.removeEventListener("click",o(this,A)),o(this,k)?.removeEventListener("canplay",o(this,W)),o(this,E)?.removeEventListener("slotchange",o(this,x)),o(this,M)?.removeEventListener("slotchange",o(this,R))}static get observedAttributes(){return["no-image","facing-mode","camera-resolution","pan","tilt","zoom"]}attributeChangedCallback(t,e,i){if(!o(this,g))return;const s=this.getTrackCapabilities(),a=this.getTrackSettings();if("no-image"===t&&e!==i&&u(this,z,q).call(this),"facing-mode"===t&&e!==i&&o(this,b)?.facingMode){const t=["user","environment"].includes(this.facingMode);a?.facingMode&&t&&(u(this,N,D).call(this),u(this,S,j).call(this))}if("camera-resolution"===t&&e!==i){const[t,e]=this.cameraResolution.split("x").map((t=>Number(t))),i=t>=s?.width?.min&&t<=s?.width?.max,n=e>=s?.height?.min&&e<=s?.height?.max;a?.width&&a?.height&&i&&n&&(u(this,N,D).call(this),u(this,S,j).call(this))}if("pan"===t&&e!==i&&o(this,b)?.pan){const t=this.pan>=s?.pan?.min&&this.pan<=s?.pan?.max;a?.pan&&t&&u(this,L,P).call(this,"pan",this.pan)}if("tilt"===t&&e!==i&&o(this,b)?.tilt){const t=this.tilt>=s?.tilt?.min&&this.tilt<=s?.tilt?.max;a?.tilt&&t&&u(this,L,P).call(this,"tilt",this.tilt)}if("zoom"===t&&e!==i&&o(this,b)?.zoom){const t=this.zoom>=s?.zoom?.min&&this.zoom<=s?.zoom?.max;a?.zoom&&t&&u(this,L,P).call(this,"zoom",this.zoom)}}get noImage(){return this.hasAttribute("no-image")}set noImage(t){t?this.setAttribute("no-image",""):this.removeAttribute("no-image")}get facingMode(){return this.getAttribute("facing-mode")}set facingMode(t){this.setAttribute("facing-mode",t)}get cameraResolution(){return this.getAttribute("camera-resolution")}set cameraResolution(t){this.setAttribute("camera-resolution",t)}get pan(){return Number(this.getAttribute("pan"))||null}set pan(t){this.setAttribute("pan",Number(t)||null)}get tilt(){return Number(this.getAttribute("tilt"))||null}set tilt(t){this.setAttribute("tilt",Number(t)||null)}get zoom(){return Number(this.getAttribute("zoom"))||null}set zoom(t){this.setAttribute("zoom",Number(t)||null)}get loading(){return this.hasAttribute("loading")}capture(){if(!this.loading)try{const t=o(this,f).getContext("2d"),e=o(this,k).videoWidth,i=o(this,k).videoHeight;o(this,f).width=e,o(this,f).height=i,t.drawImage(o(this,k),0,0,e,i);const s=o(this,f).toDataURL("image/png");if("string"==typeof s&&s.includes("data:image")){if(!this.noImage){const t=new Image;t.src=s,t.width=e,t.height=i,t.part="output-image",u(this,z,q).call(this),o(this,w)?.appendChild(t)}this.dispatchEvent(new CustomEvent("capture-photo:success",{bubbles:!0,composed:!0,detail:{dataURI:s,width:e,height:i}}))}}catch(t){this.dispatchEvent(new CustomEvent("capture-photo:error",{bubbles:!0,composed:!0,detail:{error:t}}))}}getSupportedConstraints(){return B.isSupported()&&navigator.mediaDevices.getSupportedConstraints()||{}}getTrackCapabilities(){if(!o(this,v))return{};const[t]=o(this,v).getVideoTracks();return t&&"function"==typeof t.getCapabilities&&t.getCapabilities()||{}}getTrackSettings(){if(!o(this,v))return{};const[t]=o(this,v).getVideoTracks();return t&&"function"==typeof t.getSettings&&t.getSettings()||{}}static isSupported(){return Boolean(navigator.mediaDevices?.getUserMedia)}static defineCustomElement(t="capture-photo"){"undefined"==typeof window||window.customElements.get(t)||window.customElements.define(t,B)}constructor(){super(),d(this,N),d(this,S),d(this,z),d(this,L),d(this,O),d(this,I),d(this,U),h(this,g,{writable:!0,value:void 0}),h(this,b,{writable:!0,value:void 0}),h(this,v,{writable:!0,value:void 0}),h(this,f,{writable:!0,value:void 0}),h(this,w,{writable:!0,value:void 0}),h(this,k,{writable:!0,value:void 0}),h(this,E,{writable:!0,value:void 0}),h(this,y,{writable:!0,value:void 0}),h(this,M,{writable:!0,value:void 0}),h(this,C,{writable:!0,value:void 0}),h(this,T,{writable:!0,value:t=>{t.preventDefault(),this.loading||(this.facingMode="user"!==this.facingMode&&this.facingMode?"user":"environment")}}),h(this,A,{writable:!0,value:t=>{t.preventDefault(),this.capture()}}),h(this,W,{writable:!0,value:t=>{const e=t.target;e.play().then((()=>{this.dispatchEvent(new CustomEvent("capture-photo:video-play",{bubbles:!0,composed:!0,detail:{video:e}}))})).catch((t=>{this.dispatchEvent(new CustomEvent("capture-photo:error",{bubbles:!0,composed:!0,detail:{error:t}}))})).finally((()=>{this.removeAttribute("loading")}))}}),h(this,x,{writable:!0,value:t=>{"capture-button"===t.target?.name&&(o(this,y)?.removeEventListener("click",o(this,A)),c(this,y,u(this,I,H).call(this)),o(this,y)&&(o(this,y).addEventListener("click",o(this,A)),"BUTTON"===o(this,y).nodeName||o(this,y).hasAttribute("role")||o(this,y).setAttribute("role","button")))}}),h(this,R,{writable:!0,value:t=>{"facing-mode-button"===t.target?.name&&(o(this,C)?.removeEventListener("click",o(this,T)),c(this,C,u(this,O,V).call(this)),o(this,C)&&(o(this,C).addEventListener("click",o(this,T)),"BUTTON"===o(this,C).nodeName||o(this,C).hasAttribute("role")||o(this,C).setAttribute("role","button")))}}),c(this,g,!1),c(this,b,this.getSupportedConstraints()),this.shadowRoot||(this.attachShadow({mode:"open"}),this.shadowRoot.appendChild(p.content.cloneNode(!0)))}}function D(){if(!o(this,k)||!o(this,v))return;const[t]=o(this,v).getVideoTracks();t?.stop(),o(this,k).srcObject=null,c(this,v,null)}async function j(){if(!B.isSupported())return;this.setAttribute("loading","");const t={video:{facingMode:{ideal:this.facingMode||"user"},pan:!0,tilt:!0,zoom:!0},audio:!1};if("string"==typeof this.cameraResolution){const[e,i]=this.cameraResolution.split("x").map((t=>Number(t)));t.video.width=e,t.video.height=i}try{c(this,v,await navigator.mediaDevices.getUserMedia(t)),o(this,k).srcObject=o(this,v),u(this,L,P).call(this,"pan",this.pan),u(this,L,P).call(this,"tilt",this.tilt),u(this,L,P).call(this,"zoom",this.zoom);this.getTrackSettings()?.facingMode&&(o(this,M).hidden=!1)}catch(t){this.dispatchEvent(new CustomEvent("capture-photo:error",{bubbles:!0,composed:!0,detail:{error:t}}))}finally{this.removeAttribute("loading")}}function q(){o(this,w)&&Array.from(o(this,w).childNodes).forEach((t=>t.remove()))}function P(t,e){if(!o(this,v)||!t||!e)return;const[i]=o(this,v).getVideoTracks(),s=this.getTrackCapabilities();var a,n,r;this.getTrackSettings()?.[t]&&i.applyConstraints({advanced:[{[t]:(a=Number(e),n=s?.[t]?.min,r=s?.[t]?.max,Number.isNaN(n)&&(n=0),Number.isNaN(r)&&(r=0),Math.min(Math.max(a,Math.min(n,r)),Math.max(n,r)))}]})}function V(){return o(this,M)?o(this,M).assignedElements({flatten:!0}).find((t=>"BUTTON"===t.nodeName||"facing-mode-button"===t.getAttribute("slot"))):null}function H(){return o(this,E)?o(this,E).assignedElements({flatten:!0}).find((t=>"BUTTON"===t.nodeName||"capture-button"===t.getAttribute("slot"))):null}function F(t){if(Object.prototype.hasOwnProperty.call(this,t)){const e=this[t];delete this[t],this[t]=e}}B.defineCustomElement();export{B as CapturePhoto};
//# sourceMappingURL=capture-photo-defined.js.map

@@ -1,2 +0,2 @@

function t(t,e,i){if(!e.has(t))throw new TypeError("attempted to "+i+" private field on non-instance");return e.get(t)}function e(t,e){return e.get?e.get.call(t):e.value}function i(i,s){return e(i,t(i,s,"get"))}function s(t,e){if(e.has(t))throw new TypeError("Cannot initialize the same private elements twice on an object")}function o(t,e,i){s(t,e),e.set(t,i)}function a(t,e,i){if(e.set)e.set.call(t,i);else{if(!e.writable)throw new TypeError("attempted to set read only private field");e.value=i}}function n(e,i,s){return a(e,t(e,i,"set"),s),s}function h(t,e,i){if(!e.has(t))throw new TypeError("attempted to get private field on non-instance");return i}function r(t,e){s(t,e),e.add(t)}const l=document.createElement("template"),c=String.raw;l.innerHTML=c`
function t(t,e,i){if(!e.has(t))throw new TypeError("attempted to "+i+" private field on non-instance");return e.get(t)}function e(t,e){return e.get?e.get.call(t):e.value}function i(i,s){return e(i,t(i,s,"get"))}function s(t,e){if(e.has(t))throw new TypeError("Cannot initialize the same private elements twice on an object")}function a(t,e,i){s(t,e),e.set(t,i)}function n(t,e,i){if(e.set)e.set.call(t,i);else{if(!e.writable)throw new TypeError("attempted to set read only private field");e.value=i}}function o(e,i,s){return n(e,t(e,i,"set"),s),s}function h(t,e,i){if(!e.has(t))throw new TypeError("attempted to get private field on non-instance");return i}function r(t,e){s(t,e),e.add(t)}const l=document.createElement("template"),c=String.raw;l.innerHTML=c`
<style>

@@ -29,6 +29,6 @@ :host {

</slot>
<slot name="facing-mode-button"><button part="facing-mode-button" type="button"><slot name="facing-mode-button-content">Toggle facing mode</slot></button></slot>
<slot name="facing-mode-button" hidden><button part="facing-mode-button" type="button"><slot name="facing-mode-button-content">Toggle facing mode</slot></button></slot>
</div>
<div part="output-container" id="output"></div>
`;var d=new WeakMap,u=new WeakMap,p=new WeakMap,m=new WeakMap,v=new WeakMap,g=new WeakMap,b=new WeakMap,f=new WeakMap,w=new WeakMap,E=new WeakMap,k=new WeakSet,y=new WeakSet,M=new WeakMap,C=new WeakMap,W=new WeakMap,A=new WeakSet,N=new WeakSet,S=new WeakMap,z=new WeakMap,L=new WeakSet,T=new WeakSet,R=new WeakSet;class x extends HTMLElement{connectedCallback(){if(h(this,R,q).call(this,"noImage"),h(this,R,q).call(this,"facingMode"),h(this,R,q).call(this,"cameraResolution"),h(this,R,q).call(this,"zoom"),n(this,d,!0),n(this,m,this.shadowRoot.querySelector("canvas")),n(this,v,this.shadowRoot.getElementById("output")),n(this,g,this.shadowRoot.querySelector("video")),i(this,g)&&i(this,g).addEventListener("loadedmetadata",i(this,W)),n(this,b,this.shadowRoot.querySelector('slot[name="capture-button"]')),i(this,b)&&i(this,b).addEventListener("slotchange",i(this,S)),n(this,f,h(this,T,j).call(this)),i(this,f)&&i(this,f).addEventListener("click",i(this,C)),n(this,w,this.shadowRoot.querySelector('slot[name="facing-mode-button"]')),i(this,w)&&i(this,w).addEventListener("slotchange",i(this,z)),n(this,E,h(this,L,B).call(this)),i(this,E)&&(i(this,u).facingMode?i(this,E).addEventListener("click",i(this,M)):i(this,E).hidden=!0),!x.isSupported())return this.dispatchEvent(new CustomEvent("capture-photo:error",{bubbles:!0,composed:!0,detail:{error:{name:"NotSupportedError",message:"Not supported"}}}));h(this,y,O).call(this)}disconnectedCallback(){h(this,k,I).call(this),i(this,E)&&i(this,E).removeEventListener("click",i(this,M)),i(this,f)&&i(this,f).removeEventListener("click",i(this,C)),i(this,g)&&i(this,g).removeEventListener("canplay",i(this,W)),i(this,b)&&i(this,b).removeEventListener("slotchange",i(this,S)),i(this,w)&&i(this,w).removeEventListener("slotchange",i(this,z))}attributeChangedCallback(t,e,s){i(this,d)&&("no-image"===t&&h(this,A,U).call(this),"facing-mode"===t&&i(this,u).facingMode&&e!==s&&(h(this,k,I).call(this),h(this,y,O).call(this),this.dispatchEvent(new CustomEvent("capture-photo:facing-mode-change",{bubbles:!0,composed:!0,detail:{facingMode:s}}))),"camera-resolution"===t&&e!==s&&(h(this,k,I).call(this),h(this,y,O).call(this),this.dispatchEvent(new CustomEvent("capture-photo:camera-resolution-change",{bubbles:!0,composed:!0,detail:{cameraResolution:s}}))),"zoom"===t&&e!==s&&(h(this,N,D).call(this,this.zoom),this.dispatchEvent(new CustomEvent("capture-photo:zoom-change",{bubbles:!0,composed:!0,detail:{zoom:this.zoom}}))))}static get observedAttributes(){return["no-image","facing-mode","camera-resolution","zoom"]}get noImage(){return this.hasAttribute("no-image")}set noImage(t){t?this.setAttribute("no-image",""):this.removeAttribute("no-image")}get facingMode(){return this.getAttribute("facing-mode")}set facingMode(t){this.setAttribute("facing-mode",t)}get cameraResolution(){return this.getAttribute("camera-resolution")}set cameraResolution(t){this.setAttribute("camera-resolution",t)}get zoom(){return Number(this.getAttribute("zoom"))||null}set zoom(t){const e=Number(t)||0;this.setAttribute("zoom",e>0?Math.floor(e):0)}get loading(){return this.hasAttribute("loading")}capture(){if(!this.loading)try{const t=i(this,m).getContext("2d"),e=i(this,g).videoWidth,s=i(this,g).videoHeight;i(this,m).width=e,i(this,m).height=s,t.drawImage(i(this,g),0,0,e,s);const o=i(this,m).toDataURL("image/png");if("string"==typeof o&&o.includes("data:image")){if(!this.noImage){const t=new Image;t.src=o,t.width=e,t.height=s,t.part="output-image",h(this,A,U).call(this),i(this,v)&&i(this,v).appendChild(t)}this.dispatchEvent(new CustomEvent("capture-photo:success",{bubbles:!0,composed:!0,detail:{dataURI:o,width:e,height:s}}))}}catch(t){this.dispatchEvent(new CustomEvent("capture-photo:error",{bubbles:!0,composed:!0,detail:{error:t}}))}}static isSupported(){return Boolean(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia)}static defineCustomElement(t="capture-photo"){"undefined"==typeof window||window.customElements.get(t)||window.customElements.define(t,x)}constructor(){super(),r(this,k),r(this,y),r(this,A),r(this,N),r(this,L),r(this,T),r(this,R),o(this,d,{writable:!0,value:void 0}),o(this,u,{writable:!0,value:void 0}),o(this,p,{writable:!0,value:void 0}),o(this,m,{writable:!0,value:void 0}),o(this,v,{writable:!0,value:void 0}),o(this,g,{writable:!0,value:void 0}),o(this,b,{writable:!0,value:void 0}),o(this,f,{writable:!0,value:void 0}),o(this,w,{writable:!0,value:void 0}),o(this,E,{writable:!0,value:void 0}),o(this,M,{writable:!0,value:t=>{t.preventDefault(),this.loading||(this.facingMode="user"!==this.facingMode&&this.facingMode?"user":"environment")}}),o(this,C,{writable:!0,value:t=>{t.preventDefault(),this.capture()}}),o(this,W,{writable:!0,value:t=>{const e=t.target;e.play().then((()=>{this.dispatchEvent(new CustomEvent("capture-photo:video-play",{bubbles:!0,composed:!0,detail:{video:e}}))})).catch((t=>{this.dispatchEvent(new CustomEvent("capture-photo:error",{bubbles:!0,composed:!0,detail:{error:t}}))})).finally((()=>{this.removeAttribute("loading")}))}}),o(this,S,{writable:!0,value:t=>{t.target&&"capture-button"===t.target.name&&(i(this,f)&&i(this,f).removeEventListener("click",i(this,C)),n(this,f,h(this,T,j).call(this)),i(this,f)&&(i(this,f).addEventListener("click",i(this,C)),"BUTTON"===i(this,f).nodeName||i(this,f).hasAttribute("role")||i(this,f).setAttribute("role","button")))}}),o(this,z,{writable:!0,value:t=>{t.target&&"facing-mode-button"===t.target.name&&(i(this,E)&&i(this,E).removeEventListener("click",i(this,M)),n(this,E,h(this,L,B).call(this)),i(this,E)&&(i(this,E).addEventListener("click",i(this,M)),"BUTTON"===i(this,E).nodeName||i(this,E).hasAttribute("role")||i(this,E).setAttribute("role","button")))}}),n(this,d,!1),n(this,u,x.isSupported()?navigator.mediaDevices.getSupportedConstraints():{}),this.shadowRoot||(this.attachShadow({mode:"open"}),this.shadowRoot.appendChild(l.content.cloneNode(!0)))}}function I(){if(!i(this,g)||!i(this,p))return;const[t]=i(this,p).getVideoTracks();t&&t.stop(),i(this,g).srcObject=null,n(this,p,null)}function O(){if(!x.isSupported())return;this.setAttribute("loading","");const t={video:{facingMode:{ideal:this.facingMode||"user"}},audio:!1};if("string"==typeof this.cameraResolution){const[e,i]=this.cameraResolution.split("x");t.video.width=e,t.video.height=i}navigator.mediaDevices.getUserMedia(t).then((t=>{i(this,g).srcObject=t,n(this,p,t),h(this,N,D).call(this,this.zoom)})).catch((t=>{this.dispatchEvent(new CustomEvent("capture-photo:error",{bubbles:!0,composed:!0,detail:{error:t}}))})).finally((()=>{this.removeAttribute("loading")}))}function U(){i(this,v)&&Array.from(i(this,v).childNodes).forEach((t=>t.remove()))}function D(t){if(!i(this,p)||!t)return;const[e]=i(this,p).getVideoTracks();if("function"!=typeof e.getCapabilities||"function"!=typeof e.getSettings)return;const s=e.getCapabilities();var o,a,n;"zoom"in e.getSettings()&&e.applyConstraints({advanced:[{zoom:(o=Number(t),a=s.zoom.min,n=s.zoom.max,Number.isNaN(a)&&(a=0),Number.isNaN(n)&&(n=0),Math.min(Math.max(o,Math.min(a,n)),Math.max(a,n)))}]})}function B(){return i(this,w)?i(this,w).assignedElements({flatten:!0}).find((t=>"BUTTON"===t.nodeName||"facing-mode-button"===t.getAttribute("slot"))):null}function j(){return i(this,b)?i(this,b).assignedElements({flatten:!0}).find((t=>"BUTTON"===t.nodeName||"capture-button"===t.getAttribute("slot"))):null}function q(t){if(Object.prototype.hasOwnProperty.call(this,t)){const e=this[t];delete this[t],this[t]=e}}export{x as CapturePhoto};
`;var u=new WeakMap,d=new WeakMap,p=new WeakMap,m=new WeakMap,g=new WeakMap,b=new WeakMap,v=new WeakMap,f=new WeakMap,w=new WeakMap,k=new WeakMap,E=new WeakSet,y=new WeakSet,M=new WeakMap,N=new WeakMap,S=new WeakMap,C=new WeakSet,T=new WeakSet,A=new WeakMap,W=new WeakMap,z=new WeakSet,L=new WeakSet,x=new WeakSet;class R extends HTMLElement{connectedCallback(){if(h(this,x,q).call(this,"noImage"),h(this,x,q).call(this,"facingMode"),h(this,x,q).call(this,"cameraResolution"),h(this,x,q).call(this,"pan"),h(this,x,q).call(this,"tilt"),h(this,x,q).call(this,"zoom"),o(this,u,!0),o(this,m,this.shadowRoot.querySelector("canvas")),o(this,g,this.shadowRoot.getElementById("output")),o(this,b,this.shadowRoot.querySelector("video")),o(this,v,this.shadowRoot.querySelector('slot[name="capture-button"]')),o(this,f,h(this,L,j).call(this)),o(this,w,this.shadowRoot.querySelector('slot[name="facing-mode-button"]')),o(this,k,h(this,z,D).call(this)),i(this,b)?.addEventListener("loadedmetadata",i(this,S)),i(this,v)?.addEventListener("slotchange",i(this,A)),i(this,f)?.addEventListener("click",i(this,N)),i(this,w)?.addEventListener("slotchange",i(this,W)),i(this,k)?.addEventListener("click",i(this,M)),!R.isSupported())return this.dispatchEvent(new CustomEvent("capture-photo:error",{bubbles:!0,composed:!0,detail:{error:{name:"NotSupportedError",message:"Not supported"}}}));h(this,y,O).call(this)}disconnectedCallback(){h(this,E,I).call(this),i(this,k)?.removeEventListener("click",i(this,M)),i(this,f)?.removeEventListener("click",i(this,N)),i(this,b)?.removeEventListener("canplay",i(this,S)),i(this,v)?.removeEventListener("slotchange",i(this,A)),i(this,w)?.removeEventListener("slotchange",i(this,W))}static get observedAttributes(){return["no-image","facing-mode","camera-resolution","pan","tilt","zoom"]}attributeChangedCallback(t,e,s){if(!i(this,u))return;const a=this.getTrackCapabilities(),n=this.getTrackSettings();if("no-image"===t&&e!==s&&h(this,C,U).call(this),"facing-mode"===t&&e!==s&&i(this,d)?.facingMode){const t=["user","environment"].includes(this.facingMode);n?.facingMode&&t&&(h(this,E,I).call(this),h(this,y,O).call(this))}if("camera-resolution"===t&&e!==s){const[t,e]=this.cameraResolution.split("x").map((t=>Number(t))),i=t>=a?.width?.min&&t<=a?.width?.max,s=e>=a?.height?.min&&e<=a?.height?.max;n?.width&&n?.height&&i&&s&&(h(this,E,I).call(this),h(this,y,O).call(this))}if("pan"===t&&e!==s&&i(this,d)?.pan){const t=this.pan>=a?.pan?.min&&this.pan<=a?.pan?.max;n?.pan&&t&&h(this,T,B).call(this,"pan",this.pan)}if("tilt"===t&&e!==s&&i(this,d)?.tilt){const t=this.tilt>=a?.tilt?.min&&this.tilt<=a?.tilt?.max;n?.tilt&&t&&h(this,T,B).call(this,"tilt",this.tilt)}if("zoom"===t&&e!==s&&i(this,d)?.zoom){const t=this.zoom>=a?.zoom?.min&&this.zoom<=a?.zoom?.max;n?.zoom&&t&&h(this,T,B).call(this,"zoom",this.zoom)}}get noImage(){return this.hasAttribute("no-image")}set noImage(t){t?this.setAttribute("no-image",""):this.removeAttribute("no-image")}get facingMode(){return this.getAttribute("facing-mode")}set facingMode(t){this.setAttribute("facing-mode",t)}get cameraResolution(){return this.getAttribute("camera-resolution")}set cameraResolution(t){this.setAttribute("camera-resolution",t)}get pan(){return Number(this.getAttribute("pan"))||null}set pan(t){this.setAttribute("pan",Number(t)||null)}get tilt(){return Number(this.getAttribute("tilt"))||null}set tilt(t){this.setAttribute("tilt",Number(t)||null)}get zoom(){return Number(this.getAttribute("zoom"))||null}set zoom(t){this.setAttribute("zoom",Number(t)||null)}get loading(){return this.hasAttribute("loading")}capture(){if(!this.loading)try{const t=i(this,m).getContext("2d"),e=i(this,b).videoWidth,s=i(this,b).videoHeight;i(this,m).width=e,i(this,m).height=s,t.drawImage(i(this,b),0,0,e,s);const a=i(this,m).toDataURL("image/png");if("string"==typeof a&&a.includes("data:image")){if(!this.noImage){const t=new Image;t.src=a,t.width=e,t.height=s,t.part="output-image",h(this,C,U).call(this),i(this,g)?.appendChild(t)}this.dispatchEvent(new CustomEvent("capture-photo:success",{bubbles:!0,composed:!0,detail:{dataURI:a,width:e,height:s}}))}}catch(t){this.dispatchEvent(new CustomEvent("capture-photo:error",{bubbles:!0,composed:!0,detail:{error:t}}))}}getSupportedConstraints(){return R.isSupported()&&navigator.mediaDevices.getSupportedConstraints()||{}}getTrackCapabilities(){if(!i(this,p))return{};const[t]=i(this,p).getVideoTracks();return t&&"function"==typeof t.getCapabilities&&t.getCapabilities()||{}}getTrackSettings(){if(!i(this,p))return{};const[t]=i(this,p).getVideoTracks();return t&&"function"==typeof t.getSettings&&t.getSettings()||{}}static isSupported(){return Boolean(navigator.mediaDevices?.getUserMedia)}static defineCustomElement(t="capture-photo"){"undefined"==typeof window||window.customElements.get(t)||window.customElements.define(t,R)}constructor(){super(),r(this,E),r(this,y),r(this,C),r(this,T),r(this,z),r(this,L),r(this,x),a(this,u,{writable:!0,value:void 0}),a(this,d,{writable:!0,value:void 0}),a(this,p,{writable:!0,value:void 0}),a(this,m,{writable:!0,value:void 0}),a(this,g,{writable:!0,value:void 0}),a(this,b,{writable:!0,value:void 0}),a(this,v,{writable:!0,value:void 0}),a(this,f,{writable:!0,value:void 0}),a(this,w,{writable:!0,value:void 0}),a(this,k,{writable:!0,value:void 0}),a(this,M,{writable:!0,value:t=>{t.preventDefault(),this.loading||(this.facingMode="user"!==this.facingMode&&this.facingMode?"user":"environment")}}),a(this,N,{writable:!0,value:t=>{t.preventDefault(),this.capture()}}),a(this,S,{writable:!0,value:t=>{const e=t.target;e.play().then((()=>{this.dispatchEvent(new CustomEvent("capture-photo:video-play",{bubbles:!0,composed:!0,detail:{video:e}}))})).catch((t=>{this.dispatchEvent(new CustomEvent("capture-photo:error",{bubbles:!0,composed:!0,detail:{error:t}}))})).finally((()=>{this.removeAttribute("loading")}))}}),a(this,A,{writable:!0,value:t=>{"capture-button"===t.target?.name&&(i(this,f)?.removeEventListener("click",i(this,N)),o(this,f,h(this,L,j).call(this)),i(this,f)&&(i(this,f).addEventListener("click",i(this,N)),"BUTTON"===i(this,f).nodeName||i(this,f).hasAttribute("role")||i(this,f).setAttribute("role","button")))}}),a(this,W,{writable:!0,value:t=>{"facing-mode-button"===t.target?.name&&(i(this,k)?.removeEventListener("click",i(this,M)),o(this,k,h(this,z,D).call(this)),i(this,k)&&(i(this,k).addEventListener("click",i(this,M)),"BUTTON"===i(this,k).nodeName||i(this,k).hasAttribute("role")||i(this,k).setAttribute("role","button")))}}),o(this,u,!1),o(this,d,this.getSupportedConstraints()),this.shadowRoot||(this.attachShadow({mode:"open"}),this.shadowRoot.appendChild(l.content.cloneNode(!0)))}}function I(){if(!i(this,b)||!i(this,p))return;const[t]=i(this,p).getVideoTracks();t?.stop(),i(this,b).srcObject=null,o(this,p,null)}async function O(){if(!R.isSupported())return;this.setAttribute("loading","");const t={video:{facingMode:{ideal:this.facingMode||"user"},pan:!0,tilt:!0,zoom:!0},audio:!1};if("string"==typeof this.cameraResolution){const[e,i]=this.cameraResolution.split("x").map((t=>Number(t)));t.video.width=e,t.video.height=i}try{o(this,p,await navigator.mediaDevices.getUserMedia(t)),i(this,b).srcObject=i(this,p),h(this,T,B).call(this,"pan",this.pan),h(this,T,B).call(this,"tilt",this.tilt),h(this,T,B).call(this,"zoom",this.zoom);this.getTrackSettings()?.facingMode&&(i(this,w).hidden=!1)}catch(t){this.dispatchEvent(new CustomEvent("capture-photo:error",{bubbles:!0,composed:!0,detail:{error:t}}))}finally{this.removeAttribute("loading")}}function U(){i(this,g)&&Array.from(i(this,g).childNodes).forEach((t=>t.remove()))}function B(t,e){if(!i(this,p)||!t||!e)return;const[s]=i(this,p).getVideoTracks(),a=this.getTrackCapabilities();var n,o,h;this.getTrackSettings()?.[t]&&s.applyConstraints({advanced:[{[t]:(n=Number(e),o=a?.[t]?.min,h=a?.[t]?.max,Number.isNaN(o)&&(o=0),Number.isNaN(h)&&(h=0),Math.min(Math.max(n,Math.min(o,h)),Math.max(o,h)))}]})}function D(){return i(this,w)?i(this,w).assignedElements({flatten:!0}).find((t=>"BUTTON"===t.nodeName||"facing-mode-button"===t.getAttribute("slot"))):null}function j(){return i(this,v)?i(this,v).assignedElements({flatten:!0}).find((t=>"BUTTON"===t.nodeName||"capture-button"===t.getAttribute("slot"))):null}function q(t){if(Object.prototype.hasOwnProperty.call(this,t)){const e=this[t];delete this[t],this[t]=e}}export{R as CapturePhoto};
//# sourceMappingURL=capture-photo.js.map
{
"name": "@georapbox/capture-photo-element",
"version": "2.0.0",
"version": "3.0.0",
"description": "A custom element that implements the MediaDevices.getUserMedia() method of the MediaDevices interface to capture a photo in the browser.",

@@ -5,0 +5,0 @@ "main": "dist/capture-photo.js",

@@ -8,4 +8,2 @@ [![npm version](https://img.shields.io/npm/v/@georapbox/capture-photo-element.svg)](https://www.npmjs.com/package/@georapbox/capture-photo-element)

[constraints]: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#parameters
[support]: https://caniuse.com/#feat=custom-elementsv1
[polyfill]: https://github.com/webcomponents/polyfills/tree/master/packages/custom-elements
[license]: https://georapbox.mit-license.org/@2022

@@ -82,10 +80,14 @@ [changelog]: https://github.com/georapbox/capture-photo-element/blob/main/CHANGELOG.md

### Properties
| Name | Reflects | Type | Default | Description |
| ---- | -------- | ---- | ------- | ----------- |
| `noImage`<br>*`no-image`* | ✓ | Boolean | `false` | Optional. Defines if the generated image is added in DOM. Use it if you don't need to display the generated image or if you need to display it somewhere else in DOM. |
| `facingMode`<br>*`facing-mode`* | ✓ | String | `null` | Optional. The preferred camera to be used if the device supports more than one (mostly for mobile devices). Available values: "user" and "environment" for the front and the rear camera accordingly. |
| `cameraResolution`<br>*`camera-resolution`* | ✓ | String | `null` | Optional. Defines the ideal camera resolution constraint. It must be of the format `[width]x[height]`, eg `640x480`. The browser will try to honour this, but may return other resolutions if an exact match is not available. Please refer to [constraints documentation][constraints] for more details of how constraints work. |
| `zoom` | ✓ | Number | `null` | Optional. Defines the camera's zoom level if supported by the device. |
| `loading` | ✓ | Boolean | `false` | **Readonly**. Indicates if the component is ready for interaction. It is used internally but is also exposed as a readonly property for purposes such as styling, etc. |
| Name | Reflects | Type | Required | Default | Description |
| ---- | -------- | ---- | -------- | ------- | ----------- |
| `noImage`<br>*`no-image`* | ✓ | Boolean | - | `false` | Defines if the generated image is added in DOM. Use it if you don't need to display the generated image or if you need to display it somewhere else in DOM. |
| `facingMode`<br>*`facing-mode`*<sup>1</sup> | ✓ | String | - | `null` | The preferred camera to be used if the camera hardware supports more than one (mostly for mobile devices). Available values: "user" and "environment" for the front and the rear camera respectively. |
| `cameraResolution`<br>*`camera-resolution`*<sup>1</sup> | ✓ | String | - | `null` | Defines the ideal camera resolution constraint. It must be of the format `[width]x[height]`, eg `640x480`. The browser will try to honour this, but may return other resolutions if an exact match is not available. You can access the min & max supported values for width and height, using `getTrackCapabilities().width` and `getTrackCapabilities().height` respectively. |
| `pan`<sup>1</sup> | ✓ | Number | - | `null` | Defines the camera's pan level if supported by the camera hardware. You can access the min & max supported values for pan level, using `getTrackCapabilities().pan`. |
| `tilt`<sup>1</sup> | ✓ | Number | - | `null` | Defines the camera's tilt level if supported by the camera hardware. You can access the min & max supported values for tilt level, using `getTrackCapabilities().tilt`. |
| `zoom`<sup>1</sup> | ✓ | Number | - | `null` | Defines the camera's zoom level if supported by the camera hardware. You can access the min & max supported values for zoom level, using `getTrackCapabilities().zoom`. |
| `loading` | ✓ | Boolean | - | `false` | **Readonly**. Indicates if the component is ready for interaction. It is used internally but is also exposed as a readonly property for purposes such as styling, etc. |
<sup>1</sup> Changing any of these properties/attributes may not always guarantee the desired result, because they all depend on the camera hardware support. For example, `zoom=3` might not result to the camera to zoom if the camera hardware does not support zooming. Using `getTrackCapabilities()` and `getTrackSettings()` can prove helpful to check the campera hardware support.
### Slots

@@ -97,3 +99,3 @@

| `capture-button-content` | Override the default content of the capture photo button with your own content. |
| `facing-mode-button` | Override the default facing mode button with your own. |
| `facing-mode-button` | Override the default facing mode button with your own. If `facingMode` is not supported in constrainable properties for the current `MediaStreamTrack`, the slot is hidden. |
| `facing-mode-button-content` | Override the default content of the facing mode button with your own content. |

@@ -145,2 +147,5 @@

| `capture` | Prototype | Captures a photo using the element's properties. | - |
| `getSupportedConstraints` | Prototype | Returns an object based on the `MediaTrackSupportedConstraints` dictionary, whose member fields each specify one of the constrainable properties the user agent understands. [Read more...](https://developer.mozilla.org/docs/Web/API/MediaDevices/getSupportedConstraints) | - |
| `getTrackCapabilities` | Prototype | Returns a `MediaTrackCapabilities` object which specifies the values or range of values which each constrainable property, based upon the platform and user agent. [Read more...](https://developer.mozilla.org/docs/Web/API/MediaStreamTrack/getCapabilities) | - |
| `getTrackSettings` | Prototype | Returns a `MediaTrackSettings` object containing the current values of each of the constrainable properties for the current MediaStreamTrack. [Read more...](https://developer.mozilla.org/docs/Web/API/MediaStreamTrack/getSettings) | - |

@@ -152,115 +157,5 @@ ### Events

| `capture-photo:video-play` | Emitted when camera's video stream starts playing. It is triggered during initial load, or when facing mode or camera resolution mode change are requested. | `{ video: HTMLVideoElement }` |
| `capture-photo:facing-mode-change` | Emitted when the camera's facing mode changes. | `{ facingMode: 'user' \| 'environment' }` |
| `capture-photo:camera-resolution-change` | Emitted when the camera's resolution changes. | `{ cameraResolution: String }`|
| `capture-photo:zoom-change` | Emitted when the camera's zoom level changes. | `{ zoom: Number }` |
| `capture-photo:success` | Emitted when a photo is captured successfully. | `{ dataURI: String, width: Number, height: Number }` |
| `capture-photo:error` | Emitted when an error occurs. An error might occur because camera permission is denied, a photo cannot be captured for any reason, the video stream cannot start for any reason, etc. | `{ error: DOMException }` |
## Example
Below is a full usage example, with custom configuration and styling. Check the [demo page][demo] for a demonstration.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>capture-photo-element demo</title>
<style>
capture-photo:not(:defined) {
display: none;
}
capture-photo {
overflow: hidden;
}
capture-photo::part(video) {
width: 100%;
padding: 1rem 1rem 0 1rem;
background-color: #000000;
}
capture-photo::part(actions-container) {
display: flex;
justify-content: center;
align-items: center;
gap: 2rem;
padding: 1rem 0;
margin-bottom: 1rem;
background-color: #000000;
}
capture-photo::part(capture-button) {
min-width: 60px;
width: 60px;
height: 60px;
background-color: #cccccc;
border: 5px solid #383838;
color: #000000;
border-radius: 50%;
font-size: 1rem;
cursor: pointer;
text-indent: -9999px;
overflow: hidden;
-webkit-appearance: none;
-moz-appearance: none;
}
capture-photo::part(facing-mode-button) {
margin-right: calc(-40px - 2rem); /* facing mode button width + actions buttons gap */
min-width: 40px;
width: 40px;
height: 40px;
background-color: #ffffff;
border: 0;
line-height: 1;
border-radius: 50%;
cursor: pointer;
-webkit-appearance: none;
-moz-appearance: none;
}
capture-photo::part(output-container) {
overflow-x: auto;
}
capture-photo::part(output-image) {
max-width: 300px;
height: auto;
border: 5px solid #000;
}
capture-photo[loading]::part(video) {
background-image: url(assets/icons/spinner.svg);
background-size: 60px;
background-position: center center;
background-repeat: no-repeat;
}
capture-photo[loading]::part(capture-button),
capture-photo[loading]::part(facing-mode-button) {
opacity: 0.7;
cursor: not-allowed;
}
</style>
</head>
<body>
<capture-photo>
<span slot="facing-mode-button-content">
<img src="assets/icons/reverse.svg" width="26" height="26" alt="Change facing mode">
</span>
</capture-photo>
<script type="module">
import { CapturePhoto } from './capture-photo.js';
CapturePhoto.defineCustomElement();
</script>
</body>
</html>
```
## Changelog

@@ -270,13 +165,4 @@

## Browser support
Browsers without native [custom element support][support] require a [polyfill][polyfill].
- Firefox
- Chrome
- Microsoft Edge
- Safari
## License
[The MIT License (MIT)][license]

@@ -0,3 +1,4 @@

import { clamp } from './utils/clamp.js';
const template = document.createElement('template');
const html = String.raw;

@@ -33,3 +34,3 @@

</slot>
<slot name="facing-mode-button"><button part="facing-mode-button" type="button"><slot name="facing-mode-button-content">Toggle facing mode</slot></button></slot>
<slot name="facing-mode-button" hidden><button part="facing-mode-button" type="button"><slot name="facing-mode-button-content">Toggle facing mode</slot></button></slot>
</div>

@@ -39,14 +40,2 @@ <div part="output-container" id="output"></div>

const clamp = (value, lower, upper) => {
if (Number.isNaN(lower)) {
lower = 0;
}
if (Number.isNaN(upper)) {
upper = 0;
}
return Math.min(Math.max(value, Math.min(lower, upper)), Math.max(lower, upper));
};
class CapturePhoto extends HTMLElement {

@@ -68,3 +57,3 @@ #connected;

this.#connected = false;
this.#supportedConstraints = CapturePhoto.isSupported() ? navigator.mediaDevices.getSupportedConstraints() : {};
this.#supportedConstraints = this.getSupportedConstraints();

@@ -81,2 +70,4 @@ if (!this.shadowRoot) {

this.#upgradeProperty('cameraResolution');
this.#upgradeProperty('pan');
this.#upgradeProperty('tilt');
this.#upgradeProperty('zoom');

@@ -88,18 +79,12 @@

this.#videoElement = this.shadowRoot.querySelector('video');
this.#videoElement && this.#videoElement.addEventListener('loadedmetadata', this.#onVideoLoadedMetaData);
this.#captureButtonSlot = this.shadowRoot.querySelector('slot[name="capture-button"]');
this.#captureButtonSlot && this.#captureButtonSlot.addEventListener('slotchange', this.#onCaptureButtonSlotChange);
this.#captureButton = this.#getCaptureButton();
this.#captureButton && this.#captureButton.addEventListener('click', this.#onCapturePhotoButtonClick);
this.#facingModeButtonSlot = this.shadowRoot.querySelector('slot[name="facing-mode-button"]');
this.#facingModeButtonSlot && this.#facingModeButtonSlot.addEventListener('slotchange', this.#onFacingModeButtonSlotChange);
this.#facingModeButton = this.#getFacingModeButton();
if (this.#facingModeButton) {
if (this.#supportedConstraints.facingMode) {
this.#facingModeButton.addEventListener('click', this.#onFacingModeButtonClick);
} else {
this.#facingModeButton.hidden = true;
}
}
this.#videoElement?.addEventListener('loadedmetadata', this.#onVideoLoadedMetaData);
this.#captureButtonSlot?.addEventListener('slotchange', this.#onCaptureButtonSlotChange);
this.#captureButton?.addEventListener('click', this.#onCapturePhotoButtonClick);
this.#facingModeButtonSlot?.addEventListener('slotchange', this.#onFacingModeButtonSlotChange);
this.#facingModeButton?.addEventListener('click', this.#onFacingModeButtonClick);

@@ -124,9 +109,13 @@ if (!CapturePhoto.isSupported()) {

this.#stopVideoStreaming();
this.#facingModeButton && this.#facingModeButton.removeEventListener('click', this.#onFacingModeButtonClick);
this.#captureButton && this.#captureButton.removeEventListener('click', this.#onCapturePhotoButtonClick);
this.#videoElement && this.#videoElement.removeEventListener('canplay', this.#onVideoLoadedMetaData);
this.#captureButtonSlot && this.#captureButtonSlot.removeEventListener('slotchange', this.#onCaptureButtonSlotChange);
this.#facingModeButtonSlot && this.#facingModeButtonSlot.removeEventListener('slotchange', this.#onFacingModeButtonSlotChange);
this.#facingModeButton?.removeEventListener('click', this.#onFacingModeButtonClick);
this.#captureButton?.removeEventListener('click', this.#onCapturePhotoButtonClick);
this.#videoElement?.removeEventListener('canplay', this.#onVideoLoadedMetaData);
this.#captureButtonSlot?.removeEventListener('slotchange', this.#onCaptureButtonSlotChange);
this.#facingModeButtonSlot?.removeEventListener('slotchange', this.#onFacingModeButtonSlotChange);
}
static get observedAttributes() {
return ['no-image', 'facing-mode', 'camera-resolution', 'pan', 'tilt', 'zoom'];
}
attributeChangedCallback(name, oldValue, newValue) {

@@ -137,38 +126,52 @@ if (!this.#connected) {

if (name === 'no-image') {
const trackCapabilities = this.getTrackCapabilities();
const trackSettings = this.getTrackSettings();
if (name === 'no-image' && oldValue !== newValue) {
this.#emptyOutputElement();
}
if (name === 'facing-mode' && this.#supportedConstraints.facingMode && oldValue !== newValue) {
this.#stopVideoStreaming();
this.#requestGetUserMedia();
this.dispatchEvent(new CustomEvent('capture-photo:facing-mode-change', {
bubbles: true,
composed: true,
detail: { facingMode: newValue }
}));
if (name === 'facing-mode' && oldValue !== newValue && this.#supportedConstraints?.facingMode) {
const isValidFacingMode = ['user', 'environment'].includes(this.facingMode);
if (trackSettings?.facingMode && isValidFacingMode) {
this.#stopVideoStreaming();
this.#requestGetUserMedia();
}
}
if (name === 'camera-resolution' && oldValue !== newValue) {
this.#stopVideoStreaming();
this.#requestGetUserMedia();
this.dispatchEvent(new CustomEvent('capture-photo:camera-resolution-change', {
bubbles: true,
composed: true,
detail: { cameraResolution: newValue }
}));
const [width, height] = this.cameraResolution.split('x').map(x => Number(x));
const widthInAllowedRange = width >= trackCapabilities?.width?.min && width <= trackCapabilities?.width?.max;
const heightInAllowedRange = height >= trackCapabilities?.height?.min && height <= trackCapabilities?.height?.max;
if (trackSettings?.width && trackSettings?.height && widthInAllowedRange && heightInAllowedRange) {
this.#stopVideoStreaming();
this.#requestGetUserMedia();
}
}
if (name === 'zoom' && oldValue !== newValue) {
this.#applyZoom(this.zoom);
this.dispatchEvent(new CustomEvent('capture-photo:zoom-change', {
bubbles: true,
composed: true,
detail: { zoom: this.zoom }
}));
if (name === 'pan' && oldValue !== newValue && this.#supportedConstraints?.pan) {
const panInAllowedRange = this.pan >= trackCapabilities?.pan?.min && this.pan <= trackCapabilities?.pan?.max;
if (trackSettings?.pan && panInAllowedRange) {
this.#applyPTZ('pan', this.pan);
}
}
}
static get observedAttributes() {
return ['no-image', 'facing-mode', 'camera-resolution', 'zoom'];
if (name === 'tilt' && oldValue !== newValue && this.#supportedConstraints?.tilt) {
const tiltInAllowedRange = this.tilt >= trackCapabilities?.tilt?.min && this.tilt <= trackCapabilities?.tilt?.max;
if (trackSettings?.tilt && tiltInAllowedRange) {
this.#applyPTZ('tilt', this.tilt);
}
}
if (name === 'zoom' && oldValue !== newValue && this.#supportedConstraints?.zoom) {
const zoomInAllowedRange = this.zoom >= trackCapabilities?.zoom?.min && this.zoom <= trackCapabilities?.zoom?.max;
if (trackSettings?.zoom && zoomInAllowedRange) {
this.#applyPTZ('zoom', this.zoom);
}
}
}

@@ -204,2 +207,18 @@

get pan() {
return Number(this.getAttribute('pan')) || null;
}
set pan(value) {
this.setAttribute('pan', Number(value) || null);
}
get tilt() {
return Number(this.getAttribute('tilt')) || null;
}
set tilt(value) {
this.setAttribute('tilt', Number(value) || null);
}
get zoom() {

@@ -210,4 +229,3 @@ return Number(this.getAttribute('zoom')) || null;

set zoom(value) {
const numValue = Number(value) || 0;
this.setAttribute('zoom', numValue > 0 ? Math.floor(numValue) : 0);
this.setAttribute('zoom', Number(value) || null);
}

@@ -225,3 +243,4 @@

const [track] = this.#stream.getVideoTracks();
track && track.stop();
track?.stop();
this.#videoElement.srcObject = null;

@@ -231,3 +250,3 @@ this.#stream = null;

#requestGetUserMedia() {
async #requestGetUserMedia() {
if (!CapturePhoto.isSupported()) {

@@ -243,3 +262,6 @@ return;

ideal: this.facingMode || 'user'
}
},
pan: true,
tilt: true,
zoom: true
},

@@ -250,3 +272,4 @@ audio: false

if (typeof this.cameraResolution === 'string') {
const [width, height] = this.cameraResolution.split('x');
const [width, height] = this.cameraResolution.split('x').map(x => Number(x));
constraints.video.width = width;

@@ -256,7 +279,15 @@ constraints.video.height = height;

navigator.mediaDevices.getUserMedia(constraints).then(stream => {
this.#videoElement.srcObject = stream;
this.#stream = stream;
this.#applyZoom(this.zoom);
}).catch(error => {
try {
this.#stream = await navigator.mediaDevices.getUserMedia(constraints);
this.#videoElement.srcObject = this.#stream;
this.#applyPTZ('pan', this.pan);
this.#applyPTZ('tilt', this.tilt);
this.#applyPTZ('zoom', this.zoom);
const trackSettings = this.getTrackSettings();
if (trackSettings?.facingMode) {
this.#facingModeButtonSlot.hidden = false;
}
} catch (error) {
this.dispatchEvent(new CustomEvent('capture-photo:error', {

@@ -267,5 +298,5 @@ bubbles: true,

}));
}).finally(() => {
} finally {
this.removeAttribute('loading');
});
}
}

@@ -295,3 +326,3 @@

this.#emptyOutputElement();
this.#outputElement && this.#outputElement.appendChild(image);
this.#outputElement?.appendChild(image);
}

@@ -314,2 +345,38 @@

getSupportedConstraints() {
if (!CapturePhoto.isSupported()) {
return {};
}
return navigator.mediaDevices.getSupportedConstraints() || {};
}
getTrackCapabilities() {
if (!this.#stream) {
return {};
}
const [track] = this.#stream.getVideoTracks();
if (track && typeof track.getCapabilities === 'function') {
return track.getCapabilities() || {};
}
return {};
}
getTrackSettings() {
if (!this.#stream) {
return {};
}
const [track] = this.#stream.getVideoTracks();
if (track && typeof track.getSettings === 'function') {
return track.getSettings() || {};
}
return {};
}
#onFacingModeButtonClick = evt => {

@@ -358,4 +425,4 @@ evt.preventDefault();

#applyZoom(zoom) {
if (!this.#stream || !zoom) {
#applyPTZ(constraintName, constraintValue) {
if (!this.#stream || !constraintName || !constraintValue) {
return;

@@ -365,14 +432,9 @@ }

const [track] = this.#stream.getVideoTracks();
const trackCapabilities = this.getTrackCapabilities();
const trackSettings = this.getTrackSettings();
if (typeof track.getCapabilities !== 'function' || typeof track.getSettings !== 'function') {
return;
}
const capabilities = track.getCapabilities();
const settings = track.getSettings();
if ('zoom' in settings) {
if (trackSettings?.[constraintName]) {
track.applyConstraints({
advanced: [{
zoom: clamp(Number(zoom), capabilities.zoom.min, capabilities.zoom.max)
[constraintName]: clamp(Number(constraintValue), trackCapabilities?.[constraintName]?.min, trackCapabilities?.[constraintName]?.max)
}]

@@ -384,4 +446,4 @@ });

#onCaptureButtonSlotChange = evt => {
if (evt.target && evt.target.name === 'capture-button') {
this.#captureButton && this.#captureButton.removeEventListener('click', this.#onCapturePhotoButtonClick);
if (evt.target?.name === 'capture-button') {
this.#captureButton?.removeEventListener('click', this.#onCapturePhotoButtonClick);
this.#captureButton = this.#getCaptureButton();

@@ -400,4 +462,4 @@

#onFacingModeButtonSlotChange = evt => {
if (evt.target && evt.target.name === 'facing-mode-button') {
this.#facingModeButton && this.#facingModeButton.removeEventListener('click', this.#onFacingModeButtonClick);
if (evt.target?.name === 'facing-mode-button') {
this.#facingModeButton?.removeEventListener('click', this.#onFacingModeButtonClick);
this.#facingModeButton = this.#getFacingModeButton();

@@ -450,3 +512,3 @@

static isSupported() {
return Boolean(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
return Boolean(navigator.mediaDevices?.getUserMedia);
}

@@ -453,0 +515,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc