@thednp/color-picker 0.0.1-alpha1 → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +63 -26
  3. package/dist/css/color-picker.css +504 -337
  4. package/dist/css/color-picker.min.css +2 -0
  5. package/dist/css/color-picker.rtl.css +529 -0
  6. package/dist/css/color-picker.rtl.min.css +2 -0
  7. package/dist/js/color-picker-element-esm.js +3851 -2
  8. package/dist/js/color-picker-element-esm.min.js +2 -0
  9. package/dist/js/color-picker-element.js +2086 -1278
  10. package/dist/js/color-picker-element.min.js +2 -2
  11. package/dist/js/color-picker-esm.js +3742 -0
  12. package/dist/js/color-picker-esm.min.js +2 -0
  13. package/dist/js/color-picker.js +2030 -1286
  14. package/dist/js/color-picker.min.js +2 -2
  15. package/package.json +18 -9
  16. package/src/js/color-palette.js +71 -0
  17. package/src/js/color-picker-element.js +62 -16
  18. package/src/js/color-picker.js +734 -618
  19. package/src/js/color.js +621 -358
  20. package/src/js/index.js +0 -9
  21. package/src/js/util/colorNames.js +2 -152
  22. package/src/js/util/colorPickerLabels.js +22 -0
  23. package/src/js/util/getColorControls.js +103 -0
  24. package/src/js/util/getColorForm.js +26 -19
  25. package/src/js/util/getColorMenu.js +88 -0
  26. package/src/js/util/isValidJSON.js +13 -0
  27. package/src/js/util/nonColors.js +5 -0
  28. package/src/js/util/roundPart.js +9 -0
  29. package/src/js/util/setCSSProperties.js +12 -0
  30. package/src/js/util/tabindex.js +3 -0
  31. package/src/js/util/templates.js +1 -0
  32. package/src/scss/color-picker.rtl.scss +23 -0
  33. package/src/scss/color-picker.scss +449 -0
  34. package/types/cp.d.ts +263 -162
  35. package/types/index.d.ts +9 -2
  36. package/types/source/source.ts +2 -1
  37. package/types/source/types.d.ts +28 -5
  38. package/dist/js/color-picker.esm.js +0 -2998
  39. package/dist/js/color-picker.esm.min.js +0 -2
  40. package/src/js/util/getColorControl.js +0 -49
  41. package/src/js/util/init.js +0 -14
@@ -1,2 +1,3851 @@
1
- // ColorPickerElement v0.0.1alpha1 | thednp © 2022 | MIT-License
2
- function e(e){return e instanceof HTMLElement?e.ownerDocument:e instanceof Window?e.document:window.document}const t=[Document,Element,HTMLElement],o=[Element,HTMLElement];function n(n,r){const a=t.some(e=>r instanceof e)?r:e();return o.some(e=>n instanceof e)?n:a.querySelector(n)}const r=(e,t)=>Object.assign(e,t);function a(t){if("string"==typeof t)return e().createElement(t);const{tagName:o}=t,n={...t},s=a(o);return delete n.tagName,r(s,n),s}const s=(e,t)=>{r(e.style,t)},i=["aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","goldenrod","gold","gray","green","greenyellow","grey","honeydew","hotpink","indianred","indigo","ivory","khaki","lavenderblush","lavender","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","turquoise","violet","wheat","white","whitesmoke","yellow","yellowgreen"],l="(?:[-\\+]?\\d*\\.\\d+%?)|(?:[-\\+]?\\d+%?)",c=`[\\s|\\(]+(${l})[,|\\s]+(${l})[,|\\s]+(${l})\\s*\\)?`,h=`[\\s|\\(]+(${l})[,|\\s]+(${l})[,|\\s]+(${l})[,|\\s]+(${l})\\s*\\)?`,u={CSS_UNIT:new RegExp(l),rgb:new RegExp("rgb"+c),rgba:new RegExp("rgba"+h),hsl:new RegExp("hsl"+c),hsla:new RegExp("hsla"+h),hsv:new RegExp("hsv"+c),hsva:new RegExp("hsva"+h),hex3:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex6:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,hex4:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex8:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/};function d(e){return"string"==typeof e&&e.includes(".")&&1===parseFloat(e)}function g(e){return"string"==typeof e&&e.includes("%")}function p(e){return Boolean(u.CSS_UNIT.exec(String(e)))}function m(e,t){let o=e;return d(e)&&(o="100%"),o=360===t?o:Math.min(t,Math.max(0,parseFloat(o))),g(o)&&(o=parseInt(String(o*t),10)/100),Math.abs(o-t)<1e-6?1:(o=360===t?(o<0?o%t+t:o%t)/parseFloat(String(t)):o%t/parseFloat(String(t)),o)}function f(e){let t=parseFloat(e);return(Number.isNaN(t)||t<0||t>1)&&(t=1),t}function b(e){return Math.min(1,Math.max(0,e))}function w(t){const o=e(n).head;var n;s(o,{color:t});const r=function(e,t){const o=getComputedStyle(e);return t in o?o[t]:""}(o,"color");return s(o,{color:""}),r}function v(e){return e<=1?100*Number(e)+"%":e}function k(e){return 1===e.length?"0"+e:String(e)}function x(e,t,o){return{r:255*m(e,255),g:255*m(t,255),b:255*m(o,255)}}function y(e,t,o){const n=m(e,255),r=m(t,255),a=m(o,255),s=Math.max(n,r,a),i=Math.min(n,r,a);let l=0,c=0;const h=(s+i)/2;if(s===i)c=0,l=0;else{const e=s-i;switch(c=h>.5?e/(2-s-i):e/(s+i),s){case n:l=(r-a)/e+(r<a?6:0);break;case r:l=(a-n)/e+2;break;case a:l=(n-r)/e+4}l/=6}return{h:l,s:c,l:h}}function S(e,t,o){let n=o;return n<0&&(n+=1),n>1&&(n-=1),n<1/6?e+6*n*(t-e):n<.5?t:n<2/3?e+(t-e)*(2/3-n)*6:e}function M(e,t,o){let n=0,r=0,a=0;const s=m(e,360),i=m(t,100),l=m(o,100);if(0===i)r=l,a=l,n=l;else{const e=l<.5?l*(1+i):l+i-l*i,t=2*l-e;n=S(t,e,s+1/3),r=S(t,e,s),a=S(t,e,s-1/3)}return{r:255*n,g:255*r,b:255*a}}function C(e,t,o){const n=m(e,255),r=m(t,255),a=m(o,255),s=Math.max(n,r,a),i=Math.min(n,r,a);let l=0;const c=s,h=s-i,u=0===s?0:h/s;if(s===i)l=0;else{switch(s){case n:l=(r-a)/h+(r<a?6:0);break;case r:l=(a-n)/h+2;break;case a:l=(n-r)/h+4}l/=6}return{h:l,s:u,v:c}}function $(e,t,o){const n=6*m(e,360),r=m(t,100),a=m(o,100),s=Math.floor(n),i=n-s,l=a*(1-r),c=a*(1-i*r),h=a*(1-(1-i)*r),u=s%6;return{r:255*[a,c,l,l,h,a][u],g:255*[h,a,a,c,l,l][u],b:255*[l,l,h,a,a,c][u]}}function L(e,t,o){return[k(Math.round(e).toString(16)),k(Math.round(t).toString(16)),k(Math.round(o).toString(16))].join("")}function N(e){return P(e)/255}function P(e){return parseInt(e,16)}function H(e){return{r:e>>16,g:(65280&e)>>8,b:255&e}}function E(e){let t=e.trim().toLowerCase();if(0===t.length)return{r:0,g:0,b:0,a:0};let o=!1;if(i.includes(t))t=w(t),o=!0;else if("transparent"===t)return{r:0,g:0,b:0,a:0,format:"name"};let n=u.rgb.exec(t);return n?{r:n[1],g:n[2],b:n[3]}:(n=u.rgba.exec(t),n?{r:n[1],g:n[2],b:n[3],a:n[4]}:(n=u.hsl.exec(t),n?{h:n[1],s:n[2],l:n[3]}:(n=u.hsla.exec(t),n?{h:n[1],s:n[2],l:n[3],a:n[4]}:(n=u.hsv.exec(t),n?{h:n[1],s:n[2],v:n[3]}:(n=u.hsva.exec(t),n?{h:n[1],s:n[2],v:n[3],a:n[4]}:(n=u.hex8.exec(t),n?{r:P(n[1]),g:P(n[2]),b:P(n[3]),a:N(n[4]),format:o?"name":"hex8"}:(n=u.hex6.exec(t),n?{r:P(n[1]),g:P(n[2]),b:P(n[3]),format:o?"name":"hex"}:(n=u.hex4.exec(t),n?{r:P(n[1]+n[1]),g:P(n[2]+n[2]),b:P(n[3]+n[3]),a:N(n[4]+n[4]),format:o?"name":"hex8"}:(n=u.hex3.exec(t),!!n&&{r:P(n[1]+n[1]),g:P(n[2]+n[2]),b:P(n[3]+n[3]),format:o?"name":"hex"})))))))))}function A(e){let t,o={r:0,g:0,b:0},n=e,r=null,a=null,s=null,i=!1,l="hex";return"string"==typeof e&&(n=E(e),n&&(i=!0)),"object"==typeof n&&(p(n.r)&&p(n.g)&&p(n.b)?(o=x(n.r,n.g,n.b),i=!0,l="%"===(""+n.r).slice(-1)?"prgb":"rgb"):p(n.h)&&p(n.s)&&p(n.v)?(r=v(n.s),a=v(n.v),o=$(n.h,r,a),i=!0,l="hsv"):p(n.h)&&p(n.s)&&p(n.l)&&(r=v(n.s),s=v(n.l),o=M(n.h,r,s),i=!0,l="hsl"),"a"in n&&(t=n.a)),{ok:i,format:n.format||l,r:Math.min(255,Math.max(o.r,0)),g:Math.min(255,Math.max(o.g,0)),b:Math.min(255,Math.max(o.b,0)),a:f(t)}}const T={format:"hex"};class R{constructor(e,t){let o=e;const n="object"==typeof t?r(T,t):r({},T);o instanceof R&&(o=A(o)),"number"==typeof o&&(o=H(o));const{r:a,g:s,b:i,a:l,ok:c,format:h}=A(o);this.originalInput=o,this.r=a,this.g=s,this.b=i,this.a=l,this.ok=c,this.roundA=Math.round(100*this.a)/100,this.format=n.format||h,this.r<1&&(this.r=Math.round(this.r)),this.g<1&&(this.g=Math.round(this.g)),this.b<1&&(this.b=Math.round(this.b))}get isValid(){return this.ok}get isDark(){return this.brightness<128}get luminance(){const{r:e,g:t,b:o}=this;let n=0,r=0,a=0;const s=e/255,i=t/255,l=o/255;return n=s<=.03928?s/12.92:((s+.055)/1.055)**2.4,r=i<=.03928?i/12.92:((i+.055)/1.055)**2.4,a=l<=.03928?l/12.92:((l+.055)/1.055)**2.4,.2126*n+.7152*r+.0722*a}get brightness(){const{r:e,g:t,b:o}=this;return(299*e+587*t+114*o)/1e3}toRgb(){return{r:Math.round(this.r),g:Math.round(this.g),b:Math.round(this.b),a:this.a}}toRgbString(){const e=Math.round(this.r),t=Math.round(this.g),o=Math.round(this.b);return 1===this.a?`rgb(${e},${t},${o})`:`rgba(${e},${t},${o},${this.roundA})`}toHex(){return L(this.r,this.g,this.b)}toHexString(){return"#"+this.toHex()}toHsv(){const{h:e,s:t,v:o}=C(this.r,this.g,this.b);return{h:360*e,s:t,v:o,a:this.a}}toHsl(){const{h:e,s:t,l:o}=y(this.r,this.g,this.b);return{h:360*e,s:t,l:o,a:this.a}}toHslString(){const e=this.toHsl(),t=Math.round(e.h),o=Math.round(100*e.s),n=Math.round(100*e.l);return 1===this.a?`hsl(${t},${o}%,${n}%)`:`hsla(${t},${o}%,${n}%,${this.roundA})`}setAlpha(e){return this.a=f(e),this.roundA=Math.round(100*this.a)/100,this}saturate(e){if(!e)return this;const t=this.toHsl();return t.s+=e/100,t.s=b(t.s),new R(t)}desaturate(e){return e?this.saturate(-e):this}greyscale(){return this.desaturate(100)}clone(){return new R(this)}toString(){const{format:e}=this;return"rgb"===e?this.toRgbString():"hsl"===e?this.toHslString():this.toHexString()}}r(R,{colorNames:i,CSS_INTEGER:"[-\\+]?\\d+%?",CSS_NUMBER:"[-\\+]?\\d*\\.\\d+%?",CSS_UNIT:l,PERMISSIVE_MATCH3:c,PERMISSIVE_MATCH4:h,matchers:u,isOnePointZero:d,isPercentage:g,isValidCSSUnit:p,bound01:m,boundAlpha:f,clamp01:b,getHexFromColorName:w,convertToPercentage:v,convertHexToDecimal:N,pad2:k,rgbToRgb:x,rgbToHsl:y,rgbToHex:L,rgbToHsv:C,hslToRgb:M,hsvToRgb:$,hue2rgb:S,parseIntFromHex:P,numberInputToObject:H,stringInputToObject:E,inputToRGB:A});const F={};function D(e){const t=this,{type:o}=e;(F[o]?[...F[o]]:[]).forEach(n=>{const[r,a]=n;[...a].forEach(n=>{if(r===t){const[t,a]=n;t.apply(r,[e]),a&&a.once&&O(r,o,t,a)}})})}const I=(e,t,o,n)=>{F[t]||(F[t]=new Map);const r=F[t];r.has(e)||r.set(e,new Map);const a=r.get(e),{size:s}=a;a&&a.set(o,n),s||e.addEventListener(t,D,n)},O=(e,t,o,n)=>{const r=F[t],a=r&&r.get(e),s=a&&a.get(o),{options:i}=void 0!==s?s:{options:n};a&&a.has(o)&&a.delete(o),!r||a&&a.size||r.delete(e),r&&r.size||delete F[t],a&&a.size||e.removeEventListener(t,D,i)},q="ArrowDown",Y="ArrowUp",_="ArrowLeft",V="ArrowRight",K="Enter",G="Space",{userAgentData:U}=navigator,X=U,{userAgent:j}=navigator,B=j,z=/iPhone|iPad|iPod|Android/i;let W=!1;W=X?X.brands.some(e=>z.test(e.brand)):z.test(B);const J=W;let Z=1;const Q=new Map;function ee(e,t){const{width:o,height:n,top:r,right:a,bottom:s,left:i}=e.getBoundingClientRect();let l=1,c=1;if(t&&e instanceof HTMLElement){const{offsetWidth:t,offsetHeight:r}=e;l=t>0&&Math.round(o)/t||1,c=r>0&&Math.round(n)/r||1}return{width:o/l,height:n/c,top:r/c,right:a/l,bottom:s/c,left:i/l,x:i/l,y:r/c}}function te(o,n){return(n&&t.some(e=>n instanceof e)?n:e()).querySelectorAll(o)}function oe(t,o){if("string"==typeof o)return e().createElementNS(t,o);const{tagName:n}=o,a={...o},s=oe(t,n);return delete a.tagName,r(s,a),s}const ne=new Map,re={set:(e,t,o)=>{const r=n(e);if(!r)return;ne.has(t)||ne.set(t,new Map);ne.get(t).set(r,o)},getAllFor:e=>ne.get(e)||null,get:(e,t)=>{const o=n(e),r=re.getAllFor(t);return o&&r&&r.get(o)||null},remove:(e,t)=>{const o=n(e),r=ne.get(t);r&&o&&(r.delete(o),0===r.size&&ne.delete(t))}};function ae(e,t){return e.classList.contains(t)}function se(e,t){e.classList.add(t)}function ie(e,t){e.classList.remove(t)}const le=(e,t,o)=>e.setAttribute(t,o),ce=(e,t)=>e.getAttribute(t),he="v-hidden";function ue(e,t,o,n,r){const s=`appearance${e}_${t}`,i=1===e?"color-pointer":"color-slider",l=a({tagName:"div",className:"color-control"});le(l,"role","presentation"),l.append(a({id:s,tagName:"label",className:"color-label v-hidden",ariaLive:"polite"}),a({tagName:"canvas",className:"visual-control"+e,ariaHidden:"true",width:""+o,height:""+n}));const c=a({tagName:"div",className:i+" knob"});return le(c,"aria-labelledby",r||s),le(c,"tabindex","0"),l.append(c),l}const de=["transparent","currentColor","inherit","initial"],ge=["white","black","grey","red","orange","brown","gold","olive","yellow","lime","green","teal","cyan","blue","violet","magenta","pink"],pe={pickerLabel:"Colour Picker",toggleLabel:"Select colour",menuLabel:"Select colour preset",requiredLabel:"Required",formatLabel:"Colour Format",formatHEX:"Hexadecimal Format",formatRGB:"RGB Format",formatHSL:"HSL Format",alphaLabel:"Alpha",appearanceLabel:"Colour Appearance",hexLabel:"Hexadecimal",hueLabel:"Hue",saturationLabel:"Saturation",lightnessLabel:"Lightness",redLabel:"Red",greenLabel:"Green",blueLabel:"Blue"};function me(e,t){const o=t?I:O,{input:n,pickerToggle:r,menuToggle:a}=e;o(n,"focusin",e.showPicker),o(r,"click",e.togglePicker),o(n,"keydown",e.keyHandler),a&&o(a,"click",e.toggleMenu)}function fe(e){const{input:t,parent:o,format:n,id:s,componentLabels:i,keywords:l}=e,c=ce(t,"value")||"#fff",{toggleLabel:h,menuLabel:u,formatLabel:d,pickerLabel:g,appearanceLabel:p}=i,m=de.includes(c)?"#fff":c;e.color=new R(m,{format:n});const f=J?150:230,b=J?150:230,w=J?" mobile":"",v="hsl"===n?`appearance_${s} appearance1_${s}`:"appearance1_"+s,k="hsl"===n?"appearance2_"+s:`appearance_${s} appearance2_${s}`,x=a({tagName:"button",className:"picker-toggle button-appearance",ariaExpanded:"false",ariaHasPopup:"true",ariaLive:"polite"});le(x,"tabindex","-1"),x.append(a({tagName:"span",className:he,innerText:"Open Color Picker"}));const y=a({tagName:"div",className:"color-dropdown picker"+w});le(y,"aria-labelledby",`picker-label-${s} format-label-${s}`),le(y,"role","group"),y.append(a({tagName:"label",className:he,ariaHidden:"true",id:"picker-label-"+s,innerText:""+g}),a({tagName:"label",className:he,ariaHidden:"true",id:"format-label-"+s,innerText:""+d}),a({tagName:"label",className:"color-appearance v-hidden",ariaHidden:"true",ariaLive:"polite",id:"appearance_"+s,innerText:""+p}));const S=a({tagName:"div",className:"color-controls "+n});S.append(ue(1,s,f,b,v),ue(2,s,21,b,k)),"hex"!==n&&S.append(ue(3,s,21,b));const M=function(e){const{format:t,id:o}=e,n=a({tagName:"div",className:"color-form "+t});let s=["hex"];return"rgb"===t?s=["red","green","blue","alpha"]:"hsl"===t&&(s=["hue","saturation","lightness","alpha"]),s.forEach(e=>{const[s]="hex"===t?["#"]:(i=e,i.toUpperCase()).split("");var i;const l=`color_${t}_${e}_${o}`,c=a({tagName:"label"});le(c,"for",l),c.append(a({tagName:"span",ariaHidden:"true",innerText:s+":"}),a({tagName:"span",className:he,innerText:""+e}));const h=a({tagName:"input",id:l,type:"hex"===t?"text":"number",value:"alpha"===e?"1":"0",className:"color-input "+e,autocomplete:"off",spellcheck:"false"});if("hex"!==t){let o="1",n="0.01";"alpha"!==e&&("rgb"===t?(o="255",n="1"):"hue"===e?(o="360",n="1"):(o="100",n="1")),r(h,{min:"0",max:o,step:n})}n.append(c,h)}),n}(e);if(y.append(S,M),o.append(x,y),l){const e=l,r=a({tagName:"div",className:"color-dropdown menu"+w}),s=a({tagName:"ul",ariaLabel:""+u,className:"color-menu"});le(s,"role","listbox"),r.append(s),e.forEach(e=>{const o=e.trim(),r=new R(o,{format:n}).toString()===ce(t,"value"),i=a({tagName:"li",className:"color-option"+(r?" active":""),ariaSelected:r?"true":"false",innerText:""+e});le(i,"role","option"),le(i,"tabindex","0"),le(i,"data-value",""+o),s.append(i)});const i=a({tagName:"button",className:"menu-toggle button-appearance",ariaExpanded:"false",ariaHasPopup:"true"}),c=encodeURI("http://www.w3.org/2000/svg"),d=oe(c,{tagName:"svg"});le(d,"xmlns",c),le(d,"aria-hidden","true"),le(d,"viewBox","0 0 512 512");const g=oe(c,{tagName:"path"});le(g,"d","M98,158l157,156L411,158l27,27L255,368L71,185L98,158z"),le(g,"fill","#fff"),d.append(g),i.append(a({tagName:"span",className:he,innerText:""+h}),d),o.append(i,r)}l&&de.includes(c)&&(e.value=c)}function be(e,t){const o=t?I:O,n="ontouchstart"in document?{down:"touchstart",move:"touchmove",up:"touchend"}:{down:"mousedown",move:"mousemove",up:"mouseup"};o(e.controls,n.down,e.pointerDown),e.controlKnobs.forEach(t=>o(t,"keydown",e.handleKnobs)),o(window,"scroll",e.handleScroll),[e.input,...e.inputs].forEach(t=>o(t,"change",e.changeHandler)),e.colorMenu&&(o(e.colorMenu,"click",e.menuClickHandler),o(e.colorMenu,"keydown",e.menuKeyHandler)),o(document,n.move,e.pointerMove),o(document,n.up,e.pointerUp),o(window,"keyup",e.handleDismiss),o(e.parent,"focusout",e.handleFocusOut)}function we(e){var t,o;t=e.input,o=new CustomEvent("colorpicker.change"),t.dispatchEvent(o)}function ve(e,t){const o=t?ae:ie;return!!e&&["show","show-top"][t?"some":"forEach"](t=>o(e,t))}class ke{constructor(e){const t=this;if(t.input=n(e),!t.input)throw new TypeError(`ColorPicker target ${e} cannot be found.`);const{input:o}=t;if(t.parent=function e(t,o){return t?t.closest(o)||e(t.getRootNode().host,o):null}(o,".color-picker,color-picker"),!t.parent)throw new TypeError("ColorPicker requires a specific markup to work.");t.id=function(e,t){Z+=1;let o=Q.get(e),n=Z;if(t&&t.length)if(o){const e=o.get(t);Number.isNaN(e)?o.set(t,n):n=e}else Q.set(e,new Map),o=Q.get(e),o.set(t,n);else Number.isNaN(o)?Q.set(e,n):n=o;return n}(o,"color-picker"),t.dragElement=null,t.isOpen=!1,t.controlPositions={c1x:0,c1y:0,c2y:0,c3y:0},t.colorLabels={},t.keywords=!1,t.color=new R("white",{format:t.format}),t.componentLabels=r({},pe);const{componentLabels:a,colorLabels:s,keywords:i}=o.dataset,l=a?JSON.parse(a):{};t.componentLabels=r(t.componentLabels,l);const c=s&&17===s.split(",").length?s.split(","):ge;ge.forEach((e,o)=>{t.colorLabels[e]=c[o]}),"false"!==i&&(t.keywords=i?i.split(","):de),t.showPicker=t.showPicker.bind(t),t.togglePicker=t.togglePicker.bind(t),t.toggleMenu=t.toggleMenu.bind(t),t.menuClickHandler=t.menuClickHandler.bind(t),t.menuKeyHandler=t.menuKeyHandler.bind(t),t.pointerDown=t.pointerDown.bind(t),t.pointerMove=t.pointerMove.bind(t),t.pointerUp=t.pointerUp.bind(t),t.handleScroll=t.handleScroll.bind(t),t.handleFocusOut=t.handleFocusOut.bind(t),t.changeHandler=t.changeHandler.bind(t),t.handleDismiss=t.handleDismiss.bind(t),t.keyHandler=t.keyHandler.bind(t),t.handleKnobs=t.handleKnobs.bind(t),fe(t);const{parent:h}=t;t.pickerToggle=n(".picker-toggle",h),t.menuToggle=n(".menu-toggle",h),t.colorMenu=n(".color-dropdown.menu",h),t.colorPicker=n(".color-dropdown.picker",h),t.controls=n(".color-controls",h),t.inputs=[...te(".color-input",h)],t.controlKnobs=[...te(".knob",h)],t.visuals=[...te("canvas",t.controls)],t.knobLabels=[...te(".color-label",h)],t.appearance=n(".color-appearance",h);const[u,d,g]=t.visuals;t.width1=u.width,t.height1=u.height,t.width2=d.width,t.height2=d.height,t.ctx1=u.getContext("2d"),t.ctx2=d.getContext("2d"),t.ctx1.rect(0,0,t.width1,t.height1),t.ctx2.rect(0,0,t.width2,t.height2),t.width3=0,t.height3=0,"hex"!==t.format&&(t.width3=g.width,t.height3=g.height,this.ctx3=g.getContext("2d"),t.ctx3.rect(0,0,t.width3,t.height3)),this.setControlPositions(),this.setColorAppearence(),this.updateInputs(!0),this.updateControls(),this.updateVisuals(),me(t,!0),re.set(o,"color-picker",t)}get value(){return this.input.value}set value(e){this.input.value=e}get required(){return e=this.input,t="required",e.hasAttribute(t);var e,t}get format(){return ce(this.input,"format")||"hex"}get name(){return ce(this.input,"name")}get label(){return n(`[for="${this.input.id}"]`)}get includeNonColor(){return this.keywords instanceof Array&&this.keywords.some(e=>de.includes(e))}get hex(){return this.color.toHex()}get hsv(){return this.color.toHsv()}get hsl(){return this.color.toHsl()}get rgb(){return this.color.toRgb()}get brightness(){return this.color.brightness}get luminance(){return this.color.luminance}get isDark(){const{rgb:e,brightness:t}=this;return t<120&&e.a>.33}get isValid(){const e=this.input.value;return""!==e&&new R(e).isValid}updateVisuals(){const{color:e,format:t,controlPositions:o,width1:n,width2:r,width3:a,height1:s,height2:i,height3:l,ctx1:c,ctx2:h,ctx3:u}=this,{r:d,g:g,b:p}=e;if("hsl"!==t){const e=Math.round(o.c2y/i*360);c.fillStyle=new R(`hsl(${e},100%,50%})`).toRgbString(),c.fillRect(0,0,n,s);const t=h.createLinearGradient(0,0,n,0);t.addColorStop(0,"rgba(255,255,255,1)"),t.addColorStop(1,"rgba(255,255,255,0)"),c.fillStyle=t,c.fillRect(0,0,n,s);const a=h.createLinearGradient(0,0,0,s);a.addColorStop(0,"rgba(0,0,0,0)"),a.addColorStop(1,"rgba(0,0,0,1)"),c.fillStyle=a,c.fillRect(0,0,n,s);const l=h.createLinearGradient(0,0,0,s);l.addColorStop(0,"rgba(255,0,0,1)"),l.addColorStop(.17,"rgba(255,255,0,1)"),l.addColorStop(.34,"rgba(0,255,0,1)"),l.addColorStop(.51,"rgba(0,255,255,1)"),l.addColorStop(.68,"rgba(0,0,255,1)"),l.addColorStop(.85,"rgba(255,0,255,1)"),l.addColorStop(1,"rgba(255,0,0,1)"),h.fillStyle=l,h.fillRect(0,0,r,i)}else{const t=c.createLinearGradient(0,0,n,0),r=Math.round(100*(1-o.c2y/i));t.addColorStop(0,new R("rgba(255,0,0,1)").desaturate(100-r).toRgbString()),t.addColorStop(.17,new R("rgba(255,255,0,1)").desaturate(100-r).toRgbString()),t.addColorStop(.34,new R("rgba(0,255,0,1)").desaturate(100-r).toRgbString()),t.addColorStop(.51,new R("rgba(0,255,255,1)").desaturate(100-r).toRgbString()),t.addColorStop(.68,new R("rgba(0,0,255,1)").desaturate(100-r).toRgbString()),t.addColorStop(.85,new R("rgba(255,0,255,1)").desaturate(100-r).toRgbString()),t.addColorStop(1,new R("rgba(255,0,0,1)").desaturate(100-r).toRgbString()),c.fillStyle=t,c.fillRect(0,0,n,s);const u=c.createLinearGradient(0,0,0,s);u.addColorStop(0,"rgba(255,255,255,1)"),u.addColorStop(.5,"rgba(255,255,255,0)"),c.fillStyle=u,c.fillRect(0,0,n,s);const m=c.createLinearGradient(0,0,0,s);m.addColorStop(.5,"rgba(0,0,0,0)"),m.addColorStop(1,"rgba(0,0,0,1)"),c.fillStyle=m,c.fillRect(0,0,n,s);const f=h.createLinearGradient(0,0,0,i),b=e.clone().greyscale().toRgb();f.addColorStop(0,`rgba(${d},${g},${p},1)`),f.addColorStop(1,`rgba(${b.r},${b.g},${b.b},1)`),h.fillStyle=f,h.fillRect(0,0,a,l)}if("hex"!==t){u.clearRect(0,0,a,l);const e=u.createLinearGradient(0,0,0,l);e.addColorStop(0,`rgba(${d},${g},${p},1)`),e.addColorStop(1,`rgba(${d},${g},${p},0)`),u.fillStyle=e,u.fillRect(0,0,a,l)}}handleFocusOut({relatedTarget:e}){e&&!this.parent.contains(e)&&this.hide(!0)}handleDismiss({code:e}){const t=this;t.isOpen&&"Escape"===e&&t.hide()}handleScroll(e){const{activeElement:t}=document;(J&&this.dragElement||t&&this.controlKnobs.includes(t))&&(e.stopPropagation(),e.preventDefault()),this.updateDropdownPosition()}menuKeyHandler(e){const{target:t,code:o}=e;[q,Y].includes(o)?e.preventDefault():[K,G].includes(o)&&this.menuClickHandler({target:t})}menuClickHandler(e){const t=this,{target:o}=e,{format:n}=t,r=(ce(o,"data-value")||"").trim(),a=t.colorMenu.querySelector("li.active"),s=de.includes(r)?"white":r;var i;t.color=new R(s,{format:n}),t.setControlPositions(),t.setColorAppearence(),t.updateInputs(!0),t.updateControls(),t.updateVisuals(),a&&(ie(a,"active"),i="aria-selected",a.removeAttribute(i)),a!==o&&(se(o,"active"),le(o,"aria-selected","true"),de.includes(r)&&(t.value=r,we(t)))}pointerDown(e){const t=this,{type:o,target:r,touches:a,pageX:s,pageY:i}=e,{visuals:l,controlKnobs:c,format:h}=t,[u,d,g]=l,[p,m,f]=c,b="canvas"===r.tagName?r:n("canvas",r.parentElement),w=ee(b),v="touchstart"===o?a[0].pageX:s,k="touchstart"===o?a[0].pageY:i,x=v-window.pageXOffset-w.left,y=k-window.pageYOffset-w.top;r===u||r===p?(t.dragElement=b,t.changeControl1({offsetX:x,offsetY:y})):r===d||r===m?(t.dragElement=b,t.changeControl2({offsetY:y})):"hex"===h||r!==g&&r!==f||(t.dragElement=b,t.changeAlpha({offsetY:y})),e.preventDefault()}pointerUp({target:e}){const t=this,o=document.getSelection();t.dragElement||o.toString().length||t.parent.contains(e)||t.hide(),t.dragElement=null}pointerMove(e){const t=this,{dragElement:o,visuals:n,format:r}=t,[a,s,i]=n,{type:l,touches:c,pageX:h,pageY:u}=e;if(!o)return;const d=ee(o),g="touchmove"===l?c[0].pageX:h,p="touchmove"===l?c[0].pageY:u,m=g-window.pageXOffset-d.left,f=p-window.pageYOffset-d.top;o===a&&t.changeControl1({offsetX:m,offsetY:f}),o===s&&t.changeControl2({offsetY:f}),o===i&&"hex"!==r&&t.changeAlpha({offsetY:f})}handleKnobs(e){const{target:t,code:o}=e,n=this;if(![Y,q,_,V].includes(o))return;e.preventDefault();const{activeElement:r}=document,{controlKnobs:a}=n,s=a.find(e=>e===r),[i,l,c]=a;if(s){let r=0,a=0;t===i?([_,V].includes(o)?n.controlPositions.c1x+=o===V?1:-1:[Y,q].includes(o)&&(n.controlPositions.c1y+=o===q?1:-1),r=n.controlPositions.c1x,a=n.controlPositions.c1y,n.changeControl1({offsetX:r,offsetY:a})):t===l?(n.controlPositions.c2y+=[q,V].includes(o)?1:-1,a=n.controlPositions.c2y,n.changeControl2({offsetY:a})):t===c&&(n.controlPositions.c3y+=[q,V].includes(o)?1:-1,a=n.controlPositions.c3y,n.changeAlpha({offsetY:a})),n.setColorAppearence(),n.updateInputs(),n.updateControls(),n.updateVisuals(),n.handleScroll(e)}}changeHandler(){const e=this;let t;const{activeElement:o}=document,{inputs:n,format:r,value:a,input:s}=e,[i,l,c,h]=n,u=e.includeNonColor&&de.includes(a);(o===s||o&&n.includes(o))&&(t=o===s?u?"white":a:"hex"===r?i.value:"hsl"===r?`hsla(${i.value},${l.value}%,${c.value}%,${h.value})`:`rgba(${n.map(e=>e.value).join(",")})`,e.color=new R(t,{format:r}),e.setControlPositions(),e.setColorAppearence(),e.updateInputs(),e.updateControls(),e.updateVisuals(),o===s&&u&&(e.value=a))}changeControl1(e){let[t,o]=[0,0];const{offsetX:n,offsetY:r}=e,{format:a,controlPositions:s,height1:i,height2:l,height3:c,width1:h}=this;n>h?t=h:n>=0&&(t=n),r>i?o=i:r>=0&&(o=r);const u="hsl"!==a?Math.round(s.c2y/l*360):Math.round(t/h*360),d="hsl"!==a?Math.round(t/h*100):Math.round(100*(1-s.c2y/l)),g=Math.round(100*(1-o/i)),p="hex"!==a?Math.round(100*(1-s.c3y/c))/100:1,m="hsl"!==a?"hsva":"hsla";this.color=new R(`${m}(${u},${d}%,${g}%,${p})`,{format:a}),this.controlPositions.c1x=t,this.controlPositions.c1y=o,this.setColorAppearence(),this.updateInputs(),this.updateControls(),this.updateVisuals()}changeControl2(e){const{offsetY:t}=e,{format:o,width1:n,height1:r,height2:a,height3:s,controlPositions:i}=this;let l=0;t>a?l=a:t>=0&&(l=t);const c="hsl"!==o?Math.round(l/a*360):Math.round(i.c1x/n*360),h="hsl"!==o?Math.round(i.c1x/n*100):Math.round(100*(1-l/a)),u=Math.round(100*(1-i.c1y/r)),d="hex"!==o?Math.round(100*(1-i.c3y/s))/100:1,g="hsl"!==o?"hsva":"hsla";this.color=new R(`${g}(${c},${h}%,${u}%,${d})`,{format:o}),this.controlPositions.c2y=l,this.setColorAppearence(),this.updateInputs(),this.updateControls(),this.updateVisuals()}changeAlpha(e){const{height3:t}=this,{offsetY:o}=e;let n=0;o>t?n=t:o>=0&&(n=o);const r=Math.round(100*(1-n/t));this.color.setAlpha(r/100),this.controlPositions.c3y=n,this.updateInputs(),this.updateControls(),this.updateVisuals()}updateDropdownPosition(){const{input:e,colorPicker:t,colorMenu:o}=this,n=ee(e),{offsetHeight:r}=e,a=document.documentElement.clientHeight,s=ve(t,!0)?t:o,{offsetHeight:i}=s,l=a-n.bottom,c=n.top,h=n.top+i+r>a,u=n.top-i<0;ae(s,"show")&&l<c&&h&&(ie(s,"show"),se(s,"show-top")),ae(s,"show-top")&&l>c&&u&&(ie(s,"show-top"),se(s,"show"))}setControlPositions(){const e=this,{hsv:t,hsl:o,format:n,height1:r,height2:a,height3:s,width1:i}=e,l=o.h,c="hsl"!==n?t.s:o.s,h="hsl"!==n?t.v:o.l,u=t.a;e.controlPositions.c1x="hsl"!==n?c*i:l/360*i,e.controlPositions.c1y=(1-h)*r,e.controlPositions.c2y="hsl"!==n?l/360*a:(1-c)*a,"hex"!==n&&(e.controlPositions.c3y=(1-u)*s)}setColorAppearence(){const e=this,{componentLabels:t,colorLabels:o,hsl:n,hsv:r,hex:a,format:s,knobLabels:i}=e,{lightnessLabel:l,saturationLabel:c,hueLabel:h,alphaLabel:u,appearanceLabel:d,hexLabel:g}=t;let{requiredLabel:p}=t;const[m,f,b]=i,w=Math.round(n.h),v=r.a,k="hsl"===s?n.s:r.s,x=Math.round(100*k),y=Math.round(100*n.l),S=100*r.v;let M;if(100===y&&0===x)M=o.white;else if(0===y)M=o.black;else if(0===x)M=o.grey;else if(w<15||w>=345)M=o.red;else if(w>=15&&w<45)M=S>80&&x>80?o.orange:o.brown;else if(w>=45&&w<75){const e=w>=54&&w<75&&S<80;M=w>46&&w<54&&S<80&&x>90?o.gold:o.yellow,M=e?o.olive:M}else w>=75&&w<155?M=S<68?o.green:o.lime:w>=155&&w<175?M=o.teal:w>=175&&w<195?M=o.cyan:w>=195&&w<255?M=o.blue:w>=255&&w<270?M=o.violet:w>=270&&w<295?M=o.magenta:w>=295&&w<345&&(M=o.pink);if("hsl"===s?(m.innerText=`${h}: ${w}°. ${l}: ${y}%`,f.innerText=`${c}: ${x}%`):(m.innerText=`${l}: ${y}%. ${c}: ${x}%`,f.innerText=`${h}: ${w}°`),"hex"!==s){const e=Math.round(100*v);b.innerText=`${u}: ${e}%`}e.appearance.innerText=`${d}: ${M}.`;const C="hex"===s?`${g} ${a.split("").join(" ")}.`:e.value.toUpperCase();if(e.label){const t=e.label.innerText.replace("*","").trim(),[o]=e.pickerToggle.children;p=e.required?" "+p:"",o.innerText=`${t}: ${C}${p}`}}updateControls(){const{format:e,controlKnobs:t,controlPositions:o}=this,[n,r,a]=t;n.style.transform=`translate3d(${o.c1x-3}px,${o.c1y-3}px,0)`,r.style.transform=`translate3d(0,${o.c2y-3}px,0)`,"hex"!==e&&(a.style.transform=`translate3d(0,${o.c3y-3}px,0)`)}updateInputs(e){const t=this,{value:o,rgb:n,hsl:a,hsv:s,format:i,parent:l,input:c,inputs:h}=t,[u,d,g,p]=h,m=a.a,f=Math.round(a.h),b=Math.round(100*a.s),w="hsl"===i?a.l:s.v,v=Math.round(100*w);let k;"hex"===i?(k=t.color.toHexString(),u.value=t.hex):"hsl"===i?(k=t.color.toHslString(),u.value=""+f,d.value=""+b,g.value=""+v,p.value=""+m):"rgb"===i&&(k=t.color.toRgbString(),u.value=""+n.r,d.value=""+n.g,g.value=""+n.b,p.value=""+m),t.value=""+k,r(c.style,{backgroundColor:k}),t.isDark?(ae(l,"light")&&ie(l,"light"),ae(l,"dark")||se(l,"dark")):(ae(l,"dark")&&ie(l,"dark"),ae(l,"light")||se(l,"light")),e||k===o||we(t)}keyHandler(e){const t=this,{menuToggle:o}=t,{activeElement:n}=document,{code:r}=e;[K,G].includes(r)&&(o&&n===o||!n)&&(e.preventDefault(),n?t.toggleMenu():t.togglePicker(e))}togglePicker(e){e.preventDefault();const t=this,o=ve(t.colorPicker,!0);t.isOpen&&o?t.hide(!0):t.showPicker()}showPicker(){ve(this.colorMenu),se(this.colorPicker,"show"),this.input.focus(),this.show(),le(this.pickerToggle,"aria-expanded","true")}toggleMenu(){const e=this,t=ve(e.colorMenu,!0);e.isOpen&&t?e.hide(!0):function(e){ve(e.colorPicker),se(e.colorMenu,"show"),e.show(),le(e.menuToggle,"aria-expanded","true")}(e)}show(){const e=this;e.isOpen||(se(e.parent,"open"),be(e,!0),e.updateDropdownPosition(),e.isOpen=!0)}hide(e){const t=this;if(t.isOpen){const{pickerToggle:o,colorMenu:n}=t;be(t),ie(t.parent,"open"),ve(t.colorPicker),le(o,"aria-expanded","false"),n&&(ve(n),le(t.menuToggle,"aria-expanded","false")),t.isValid||(t.value=t.color.toString()),t.isOpen=!1,e||o.focus()}}dispose(){const{input:e,parent:t}=this;this.hide(!0),me(this),[...t.children].forEach(t=>{t!==e&&t.remove()}),re.remove(e,"color-picker")}}r(ke,{Color:R,getInstance:e=>{return t=e,o="color-picker",re.get(t,o);var t,o},init:e=>new ke(e),selector:'[data-function="color-picker"]'});class xe extends HTMLElement{constructor(){super(),this.colorPicker=null,this.input=n("input",this),this.isDisconnected=!0,this.attachShadow({mode:"open"})}get value(){return this.input.value}get color(){return this.colorPicker&&this.colorPicker.color}connectedCallback(){this.colorPicker?this.isDisconnected&&(this.isDisconnected=!1):(this.colorPicker=new ke(this.input),this.isDisconnected=!1,this.shadowRoot&&this.shadowRoot.append(a("slot")))}disconnectedCallback(){this.colorPicker&&this.colorPicker.dispose(),this.isDisconnected=!0}}r(xe,{Color:R,ColorPicker:ke}),customElements.define("color-picker",xe);export default xe;
1
+ /*!
2
+ * ColorPickerElement v0.0.1 (http://thednp.github.io/color-picker)
3
+ * Copyright 2022 © thednp
4
+ * Licensed under MIT (https://github.com/thednp/color-picker/blob/master/LICENSE)
5
+ */
6
+ /**
7
+ * Returns the `document` or the `#document` element.
8
+ * @see https://github.com/floating-ui/floating-ui
9
+ * @param {(Node | HTMLElement | Element | globalThis)=} node
10
+ * @returns {Document}
11
+ */
12
+ function getDocument(node) {
13
+ if (node instanceof HTMLElement) return node.ownerDocument;
14
+ if (node instanceof Window) return node.document;
15
+ return window.document;
16
+ }
17
+
18
+ /**
19
+ * A global array of possible `ParentNode`.
20
+ */
21
+ const parentNodes = [Document, Element, HTMLElement];
22
+
23
+ /**
24
+ * Shortcut for `HTMLElement.getElementsByTagName` method. Some `Node` elements
25
+ * like `ShadowRoot` do not support `getElementsByTagName`.
26
+ *
27
+ * @param {string} selector the tag name
28
+ * @param {(HTMLElement | Element | Document)=} parent optional Element to look into
29
+ * @return {HTMLCollectionOf<HTMLElement | Element>} the 'HTMLCollection'
30
+ */
31
+ function getElementsByTagName(selector, parent) {
32
+ const lookUp = parent && parentNodes
33
+ .some((x) => parent instanceof x) ? parent : getDocument();
34
+ return lookUp.getElementsByTagName(selector);
35
+ }
36
+
37
+ /**
38
+ * Shortcut for `Object.assign()` static method.
39
+ * @param {Record<string, any>} obj a target object
40
+ * @param {Record<string, any>} source a source object
41
+ */
42
+ const ObjectAssign = (obj, source) => Object.assign(obj, source);
43
+
44
+ /**
45
+ * This is a shortie for `document.createElement` method
46
+ * which allows you to create a new `HTMLElement` for a given `tagName`
47
+ * or based on an object with specific non-readonly attributes:
48
+ * `id`, `className`, `textContent`, `style`, etc.
49
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement
50
+ *
51
+ * @param {Record<string, string> | string} param `tagName` or object
52
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
53
+ */
54
+ function createElement(param) {
55
+ if (typeof param === 'string') {
56
+ return getDocument().createElement(param);
57
+ }
58
+
59
+ const { tagName } = param;
60
+ const attr = { ...param };
61
+ const newElement = createElement(tagName);
62
+ delete attr.tagName;
63
+ ObjectAssign(newElement, attr);
64
+ return newElement;
65
+ }
66
+
67
+ /**
68
+ * Shortcut for `HTMLElement.setAttribute()` method.
69
+ * @param {HTMLElement | Element} element target element
70
+ * @param {string} attribute attribute name
71
+ * @param {string} value attribute value
72
+ * @returns {void}
73
+ */
74
+ const setAttribute = (element, attribute, value) => element.setAttribute(attribute, value);
75
+
76
+ /**
77
+ * Shortcut for `HTMLElement.getAttribute()` method.
78
+ * @param {HTMLElement | Element} element target element
79
+ * @param {string} attribute attribute name
80
+ * @returns {string?} attribute value
81
+ */
82
+ const getAttribute = (element, attribute) => element.getAttribute(attribute);
83
+
84
+ /**
85
+ * Returns the `document.head` or the `<head>` element.
86
+ *
87
+ * @param {(Node | HTMLElement | Element | globalThis)=} node
88
+ * @returns {HTMLElement | HTMLHeadElement}
89
+ */
90
+ function getDocumentHead(node) {
91
+ return getDocument(node).head;
92
+ }
93
+
94
+ /**
95
+ * Shortcut for `window.getComputedStyle(element).propertyName`
96
+ * static method.
97
+ *
98
+ * * If `element` parameter is not an `HTMLElement`, `getComputedStyle`
99
+ * throws a `ReferenceError`.
100
+ *
101
+ * @param {HTMLElement | Element} element target
102
+ * @param {string} property the css property
103
+ * @return {string} the css property value
104
+ */
105
+ function getElementStyle(element, property) {
106
+ const computedStyle = getComputedStyle(element);
107
+
108
+ // @ts-ignore -- must use camelcase strings,
109
+ // or non-camelcase strings with `getPropertyValue`
110
+ return property in computedStyle ? computedStyle[property] : '';
111
+ }
112
+
113
+ /**
114
+ * Shortcut for `Object.keys()` static method.
115
+ * @param {Record<string, any>} obj a target object
116
+ * @returns {string[]}
117
+ */
118
+ const ObjectKeys = (obj) => Object.keys(obj);
119
+
120
+ /**
121
+ * Shortcut for multiple uses of `HTMLElement.style.propertyName` method.
122
+ * @param {HTMLElement | Element} element target element
123
+ * @param {Partial<CSSStyleDeclaration>} styles attribute value
124
+ */
125
+ // @ts-ignore
126
+ const setElementStyle = (element, styles) => ObjectAssign(element.style, styles);
127
+
128
+ /**
129
+ * A list of explicit default non-color values.
130
+ */
131
+ const nonColors = ['transparent', 'currentColor', 'inherit', 'revert', 'initial'];
132
+
133
+ /**
134
+ * Round colour components, for all formats except HEX.
135
+ * @param {number} v one of the colour components
136
+ * @returns {number} the rounded number
137
+ */
138
+ function roundPart(v) {
139
+ const floor = Math.floor(v);
140
+ return v - floor < 0.5 ? floor : Math.round(v);
141
+ }
142
+
143
+ // Color supported formats
144
+ const COLOR_FORMAT = ['rgb', 'hex', 'hsl', 'hsb', 'hwb'];
145
+
146
+ // Hue angles
147
+ const ANGLES = 'deg|rad|grad|turn';
148
+
149
+ // <http://www.w3.org/TR/css3-values/#integers>
150
+ const CSS_INTEGER = '[-\\+]?\\d+%?';
151
+
152
+ // Include CSS3 Module
153
+ // <http://www.w3.org/TR/css3-values/#number-value>
154
+ const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
155
+
156
+ // Include CSS4 Module Hue degrees unit
157
+ // <https://www.w3.org/TR/css3-values/#angle-value>
158
+ const CSS_ANGLE = `[-\\+]?\\d*\\.?\\d+(?:${ANGLES})?`;
159
+
160
+ // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
161
+ const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
162
+
163
+ // Add angles to the mix
164
+ const CSS_UNIT2 = `(?:${CSS_UNIT})|(?:${CSS_ANGLE})`;
165
+
166
+ // Actual matching.
167
+ // Parentheses and commas are optional, but not required.
168
+ // Whitespace can take the place of commas or opening paren
169
+ const PERMISSIVE_MATCH = `[\\s|\\(]+(${CSS_UNIT2})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s|\\/\\s]*(${CSS_UNIT})?\\s*\\)?`;
170
+
171
+ const matchers = {
172
+ CSS_UNIT: new RegExp(CSS_UNIT2),
173
+ hwb: new RegExp(`hwb${PERMISSIVE_MATCH}`),
174
+ rgb: new RegExp(`rgb(?:a)?${PERMISSIVE_MATCH}`),
175
+ hsl: new RegExp(`hsl(?:a)?${PERMISSIVE_MATCH}`),
176
+ hsv: new RegExp(`hsv(?:a)?${PERMISSIVE_MATCH}`),
177
+ hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
178
+ hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
179
+ hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
180
+ hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
181
+ };
182
+
183
+ /**
184
+ * Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
185
+ * <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
186
+ * @param {string} n testing number
187
+ * @returns {boolean} the query result
188
+ */
189
+ function isOnePointZero(n) {
190
+ return `${n}`.includes('.') && parseFloat(n) === 1;
191
+ }
192
+
193
+ /**
194
+ * Check to see if string passed in is a percentage
195
+ * @param {string} n testing number
196
+ * @returns {boolean} the query result
197
+ */
198
+ function isPercentage(n) {
199
+ return `${n}`.includes('%');
200
+ }
201
+
202
+ /**
203
+ * Check to see if string passed in is an angle
204
+ * @param {string} n testing string
205
+ * @returns {boolean} the query result
206
+ */
207
+ function isAngle(n) {
208
+ return ANGLES.split('|').some((a) => `${n}`.includes(a));
209
+ }
210
+
211
+ /**
212
+ * Check to see if string passed is a web safe colour.
213
+ * @param {string} color a colour name, EG: *red*
214
+ * @returns {boolean} the query result
215
+ */
216
+ function isColorName(color) {
217
+ return !['#', ...COLOR_FORMAT].some((s) => color.includes(s))
218
+ && !/[0-9]/.test(color);
219
+ }
220
+
221
+ /**
222
+ * Check to see if it looks like a CSS unit
223
+ * (see `matchers` above for definition).
224
+ * @param {string | number} color testing value
225
+ * @returns {boolean} the query result
226
+ */
227
+ function isValidCSSUnit(color) {
228
+ return Boolean(matchers.CSS_UNIT.exec(String(color)));
229
+ }
230
+
231
+ /**
232
+ * Take input from [0, n] and return it as [0, 1]
233
+ * @param {*} N the input number
234
+ * @param {number} max the number maximum value
235
+ * @returns {number} the number in [0, 1] value range
236
+ */
237
+ function bound01(N, max) {
238
+ let n = N;
239
+ if (isOnePointZero(n)) n = '100%';
240
+
241
+ n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n)));
242
+
243
+ // Handle hue angles
244
+ if (isAngle(N)) n = N.replace(new RegExp(ANGLES), '');
245
+
246
+ // Automatically convert percentage into number
247
+ if (isPercentage(n)) n = parseInt(String(n * max), 10) / 100;
248
+
249
+ // Handle floating point rounding errors
250
+ if (Math.abs(n - max) < 0.000001) {
251
+ return 1;
252
+ }
253
+ // Convert into [0, 1] range if it isn't already
254
+ if (max === 360) {
255
+ // If n is a hue given in degrees,
256
+ // wrap around out-of-range values into [0, 360] range
257
+ // then convert into [0, 1].
258
+ n = (n < 0 ? (n % max) + max : n % max) / parseFloat(String(max));
259
+ } else {
260
+ // If n not a hue given in degrees
261
+ // Convert into [0, 1] range if it isn't already.
262
+ n = (n % max) / parseFloat(String(max));
263
+ }
264
+ return n;
265
+ }
266
+
267
+ /**
268
+ * Return a valid alpha value [0,1] with all invalid values being set to 1.
269
+ * @param {string | number} a transparency value
270
+ * @returns {number} a transparency value in the [0, 1] range
271
+ */
272
+ function boundAlpha(a) {
273
+ let na = parseFloat(`${a}`);
274
+
275
+ if (Number.isNaN(na) || na < 0 || na > 1) {
276
+ na = 1;
277
+ }
278
+
279
+ return na;
280
+ }
281
+
282
+ /**
283
+ * Force a number between 0 and 1.
284
+ * @param {number} v the float number
285
+ * @returns {number} - the resulting number
286
+ */
287
+ function clamp01(v) {
288
+ return Math.min(1, Math.max(0, v));
289
+ }
290
+
291
+ /**
292
+ * Returns the hexadecimal value of a web safe colour.
293
+ * @param {string} name
294
+ * @returns {string}
295
+ */
296
+ function getRGBFromName(name) {
297
+ const documentHead = getDocumentHead();
298
+ setElementStyle(documentHead, { color: name });
299
+ const colorName = getElementStyle(documentHead, 'color');
300
+ setElementStyle(documentHead, { color: '' });
301
+ return colorName;
302
+ }
303
+
304
+ /**
305
+ * Converts a decimal value to hexadecimal.
306
+ * @param {number} d the input number
307
+ * @returns {string} - the hexadecimal value
308
+ */
309
+ function convertDecimalToHex(d) {
310
+ return roundPart(d * 255).toString(16);
311
+ }
312
+
313
+ /**
314
+ * Converts a hexadecimal value to decimal.
315
+ * @param {string} h hexadecimal value
316
+ * @returns {number} number in decimal format
317
+ */
318
+ function convertHexToDecimal(h) {
319
+ return parseIntFromHex(h) / 255;
320
+ }
321
+
322
+ /**
323
+ * Converts a base-16 hexadecimal value into a base-10 integer.
324
+ * @param {string} val
325
+ * @returns {number}
326
+ */
327
+ function parseIntFromHex(val) {
328
+ return parseInt(val, 16);
329
+ }
330
+
331
+ /**
332
+ * Force a hexadecimal value to have 2 characters.
333
+ * @param {string} c string with [0-9A-F] ranged values
334
+ * @returns {string} 0 => 00, a => 0a
335
+ */
336
+ function pad2(c) {
337
+ return c.length === 1 ? `0${c}` : String(c);
338
+ }
339
+
340
+ /**
341
+ * Converts an RGB colour value to HSL.
342
+ *
343
+ * @param {number} R Red component [0, 255]
344
+ * @param {number} G Green component [0, 255]
345
+ * @param {number} B Blue component [0, 255]
346
+ * @returns {CP.HSL} {h,s,l} object with [0, 1] ranged values
347
+ */
348
+ function rgbToHsl(R, G, B) {
349
+ const r = R / 255;
350
+ const g = G / 255;
351
+ const b = B / 255;
352
+ const max = Math.max(r, g, b);
353
+ const min = Math.min(r, g, b);
354
+ let h = 0;
355
+ let s = 0;
356
+ const l = (max + min) / 2;
357
+ if (max === min) {
358
+ s = 0;
359
+ h = 0; // achromatic
360
+ } else {
361
+ const d = max - min;
362
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
363
+ switch (max) {
364
+ case r:
365
+ h = (g - b) / d + (g < b ? 6 : 0);
366
+ break;
367
+ case g:
368
+ h = (b - r) / d + 2;
369
+ break;
370
+ case b:
371
+ h = (r - g) / d + 4;
372
+ break;
373
+ }
374
+ h /= 6;
375
+ }
376
+ return { h, s, l };
377
+ }
378
+
379
+ /**
380
+ * Returns a normalized RGB component value.
381
+ * @param {number} p
382
+ * @param {number} q
383
+ * @param {number} t
384
+ * @returns {number}
385
+ */
386
+ function hueToRgb(p, q, t) {
387
+ let T = t;
388
+ if (T < 0) T += 1;
389
+ if (T > 1) T -= 1;
390
+ if (T < 1 / 6) return p + (q - p) * (6 * T);
391
+ if (T < 1 / 2) return q;
392
+ if (T < 2 / 3) return p + (q - p) * (2 / 3 - T) * 6;
393
+ return p;
394
+ }
395
+
396
+ /**
397
+ * Returns an HWB colour object from an RGB colour object.
398
+ * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
399
+ * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
400
+ *
401
+ * @param {number} R Red component [0, 255]
402
+ * @param {number} G Green [0, 255]
403
+ * @param {number} B Blue [0, 255]
404
+ * @return {CP.HWB} {h,w,b} object with [0, 1] ranged values
405
+ */
406
+ function rgbToHwb(R, G, B) {
407
+ const r = R / 255;
408
+ const g = G / 255;
409
+ const b = B / 255;
410
+
411
+ let f = 0;
412
+ let i = 0;
413
+ const whiteness = Math.min(r, g, b);
414
+ const max = Math.max(r, g, b);
415
+ const black = 1 - max;
416
+
417
+ if (max === whiteness) return { h: 0, w: whiteness, b: black };
418
+ if (r === whiteness) {
419
+ f = g - b;
420
+ i = 3;
421
+ } else {
422
+ f = g === whiteness ? b - r : r - g;
423
+ i = g === whiteness ? 5 : 1;
424
+ }
425
+
426
+ const h = (i - f / (max - whiteness)) / 6;
427
+ return {
428
+ h: h === 1 ? 0 : h,
429
+ w: whiteness,
430
+ b: black,
431
+ };
432
+ }
433
+
434
+ /**
435
+ * Returns an RGB colour object from an HWB colour.
436
+ *
437
+ * @param {number} H Hue Angle [0, 1]
438
+ * @param {number} W Whiteness [0, 1]
439
+ * @param {number} B Blackness [0, 1]
440
+ * @return {CP.RGB} {r,g,b} object with [0, 255] ranged values
441
+ *
442
+ * @link https://www.w3.org/TR/css-color-4/#hwb-to-rgb
443
+ * @link http://alvyray.com/Papers/CG/hwb2rgb.htm
444
+ */
445
+ function hwbToRgb(H, W, B) {
446
+ if (W + B >= 1) {
447
+ const gray = (W / (W + B)) * 255;
448
+ return { r: gray, g: gray, b: gray };
449
+ }
450
+ let { r, g, b } = hslToRgb(H, 1, 0.5);
451
+ [r, g, b] = [r, g, b]
452
+ .map((v) => (v / 255) * (1 - W - B) + W)
453
+ .map((v) => v * 255);
454
+
455
+ return { r, g, b };
456
+ }
457
+
458
+ /**
459
+ * Converts an HSL colour value to RGB.
460
+ *
461
+ * @param {number} h Hue Angle [0, 1]
462
+ * @param {number} s Saturation [0, 1]
463
+ * @param {number} l Lightness Angle [0, 1]
464
+ * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
465
+ */
466
+ function hslToRgb(h, s, l) {
467
+ let r = 0;
468
+ let g = 0;
469
+ let b = 0;
470
+
471
+ if (s === 0) {
472
+ // achromatic
473
+ g = l;
474
+ b = l;
475
+ r = l;
476
+ } else {
477
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
478
+ const p = 2 * l - q;
479
+ r = hueToRgb(p, q, h + 1 / 3);
480
+ g = hueToRgb(p, q, h);
481
+ b = hueToRgb(p, q, h - 1 / 3);
482
+ }
483
+ [r, g, b] = [r, g, b].map((x) => x * 255);
484
+
485
+ return { r, g, b };
486
+ }
487
+
488
+ /**
489
+ * Converts an RGB colour value to HSV.
490
+ *
491
+ * @param {number} R Red component [0, 255]
492
+ * @param {number} G Green [0, 255]
493
+ * @param {number} B Blue [0, 255]
494
+ * @returns {CP.HSV} {h,s,v} object with [0, 1] ranged values
495
+ */
496
+ function rgbToHsv(R, G, B) {
497
+ const r = R / 255;
498
+ const g = G / 255;
499
+ const b = B / 255;
500
+ const max = Math.max(r, g, b);
501
+ const min = Math.min(r, g, b);
502
+ let h = 0;
503
+ const v = max;
504
+ const d = max - min;
505
+ const s = max === 0 ? 0 : d / max;
506
+ if (max === min) {
507
+ h = 0; // achromatic
508
+ } else {
509
+ switch (max) {
510
+ case r:
511
+ h = (g - b) / d + (g < b ? 6 : 0);
512
+ break;
513
+ case g:
514
+ h = (b - r) / d + 2;
515
+ break;
516
+ case b:
517
+ h = (r - g) / d + 4;
518
+ break;
519
+ }
520
+ h /= 6;
521
+ }
522
+ return { h, s, v };
523
+ }
524
+
525
+ /**
526
+ * Converts an HSV colour value to RGB.
527
+ *
528
+ * @param {number} H Hue Angle [0, 1]
529
+ * @param {number} S Saturation [0, 1]
530
+ * @param {number} V Brightness Angle [0, 1]
531
+ * @returns {CP.RGB} {r,g,b} object with [0, 1] ranged values
532
+ */
533
+ function hsvToRgb(H, S, V) {
534
+ const h = H * 6;
535
+ const s = S;
536
+ const v = V;
537
+ const i = Math.floor(h);
538
+ const f = h - i;
539
+ const p = v * (1 - s);
540
+ const q = v * (1 - f * s);
541
+ const t = v * (1 - (1 - f) * s);
542
+ const mod = i % 6;
543
+ const r = [v, q, p, p, t, v][mod];
544
+ const g = [t, v, v, q, p, p][mod];
545
+ const b = [p, p, t, v, v, q][mod];
546
+ return { r: r * 255, g: g * 255, b: b * 255 };
547
+ }
548
+
549
+ /**
550
+ * Converts an RGB colour to hex
551
+ *
552
+ * Assumes r, g, and b are contained in the set [0, 255]
553
+ * Returns a 3 or 6 character hex
554
+ * @param {number} r Red component [0, 255]
555
+ * @param {number} g Green [0, 255]
556
+ * @param {number} b Blue [0, 255]
557
+ * @param {boolean=} allow3Char
558
+ * @returns {string}
559
+ */
560
+ function rgbToHex(r, g, b, allow3Char) {
561
+ const hex = [
562
+ pad2(roundPart(r).toString(16)),
563
+ pad2(roundPart(g).toString(16)),
564
+ pad2(roundPart(b).toString(16)),
565
+ ];
566
+
567
+ // Return a 3 character hex if possible
568
+ if (allow3Char && hex[0].charAt(0) === hex[0].charAt(1)
569
+ && hex[1].charAt(0) === hex[1].charAt(1)
570
+ && hex[2].charAt(0) === hex[2].charAt(1)) {
571
+ return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
572
+ }
573
+
574
+ return hex.join('');
575
+ }
576
+
577
+ /**
578
+ * Converts an RGBA color plus alpha transparency to hex8.
579
+ *
580
+ * @param {number} r Red component [0, 255]
581
+ * @param {number} g Green [0, 255]
582
+ * @param {number} b Blue [0, 255]
583
+ * @param {number} a Alpha transparency [0, 1]
584
+ * @param {boolean=} allow4Char when *true* it will also find hex shorthand
585
+ * @returns {string} a hexadecimal value with alpha transparency
586
+ */
587
+ function rgbaToHex(r, g, b, a, allow4Char) {
588
+ const hex = [
589
+ pad2(roundPart(r).toString(16)),
590
+ pad2(roundPart(g).toString(16)),
591
+ pad2(roundPart(b).toString(16)),
592
+ pad2(convertDecimalToHex(a)),
593
+ ];
594
+
595
+ // Return a 4 character hex if possible
596
+ if (allow4Char && hex[0].charAt(0) === hex[0].charAt(1)
597
+ && hex[1].charAt(0) === hex[1].charAt(1)
598
+ && hex[2].charAt(0) === hex[2].charAt(1)
599
+ && hex[3].charAt(0) === hex[3].charAt(1)) {
600
+ return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
601
+ }
602
+ return hex.join('');
603
+ }
604
+
605
+ /**
606
+ * Returns a colour object corresponding to a given number.
607
+ * @param {number} color input number
608
+ * @returns {CP.RGB} {r,g,b} object with [0, 255] ranged values
609
+ */
610
+ function numberInputToObject(color) {
611
+ /* eslint-disable no-bitwise */
612
+ return {
613
+ r: color >> 16,
614
+ g: (color & 0xff00) >> 8,
615
+ b: color & 0xff,
616
+ };
617
+ /* eslint-enable no-bitwise */
618
+ }
619
+
620
+ /**
621
+ * Permissive string parsing. Take in a number of formats, and output an object
622
+ * based on detected format. Returns {r,g,b} or {h,s,l} or {h,s,v}
623
+ * @param {string} input colour value in any format
624
+ * @returns {Record<string, (number | string)> | false} an object matching the RegExp
625
+ */
626
+ function stringInputToObject(input) {
627
+ let color = input.trim().toLowerCase();
628
+ if (color.length === 0) {
629
+ return {
630
+ r: 0, g: 0, b: 0, a: 0,
631
+ };
632
+ }
633
+ let named = false;
634
+ if (isColorName(color)) {
635
+ color = getRGBFromName(color);
636
+ named = true;
637
+ } else if (nonColors.includes(color)) {
638
+ const isTransparent = color === 'transparent';
639
+ const rgb = isTransparent ? 0 : 255;
640
+ const a = isTransparent ? 0 : 1;
641
+ return {
642
+ r: rgb, g: rgb, b: rgb, a, format: 'rgb',
643
+ };
644
+ }
645
+
646
+ // Try to match string input using regular expressions.
647
+ // Keep most of the number bounding out of this function,
648
+ // don't worry about [0,1] or [0,100] or [0,360]
649
+ // Just return an object and let the conversion functions handle that.
650
+ // This way the result will be the same whether Color is initialized with string or object.
651
+ let [, m1, m2, m3, m4] = matchers.rgb.exec(color) || [];
652
+ if (m1 && m2 && m3/* && m4 */) {
653
+ return {
654
+ r: m1, g: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'rgb',
655
+ };
656
+ }
657
+ [, m1, m2, m3, m4] = matchers.hsl.exec(color) || [];
658
+ if (m1 && m2 && m3/* && m4 */) {
659
+ return {
660
+ h: m1, s: m2, l: m3, a: m4 !== undefined ? m4 : 1, format: 'hsl',
661
+ };
662
+ }
663
+ [, m1, m2, m3, m4] = matchers.hsv.exec(color) || [];
664
+ if (m1 && m2 && m3/* && m4 */) {
665
+ return {
666
+ h: m1, s: m2, v: m3, a: m4 !== undefined ? m4 : 1, format: 'hsv',
667
+ };
668
+ }
669
+ [, m1, m2, m3, m4] = matchers.hwb.exec(color) || [];
670
+ if (m1 && m2 && m3) {
671
+ return {
672
+ h: m1, w: m2, b: m3, a: m4 !== undefined ? m4 : 1, format: 'hwb',
673
+ };
674
+ }
675
+ [, m1, m2, m3, m4] = matchers.hex8.exec(color) || [];
676
+ if (m1 && m2 && m3 && m4) {
677
+ return {
678
+ r: parseIntFromHex(m1),
679
+ g: parseIntFromHex(m2),
680
+ b: parseIntFromHex(m3),
681
+ a: convertHexToDecimal(m4),
682
+ // format: named ? 'rgb' : 'hex8',
683
+ format: named ? 'rgb' : 'hex',
684
+ };
685
+ }
686
+ [, m1, m2, m3] = matchers.hex6.exec(color) || [];
687
+ if (m1 && m2 && m3) {
688
+ return {
689
+ r: parseIntFromHex(m1),
690
+ g: parseIntFromHex(m2),
691
+ b: parseIntFromHex(m3),
692
+ format: named ? 'rgb' : 'hex',
693
+ };
694
+ }
695
+ [, m1, m2, m3, m4] = matchers.hex4.exec(color) || [];
696
+ if (m1 && m2 && m3 && m4) {
697
+ return {
698
+ r: parseIntFromHex(m1 + m1),
699
+ g: parseIntFromHex(m2 + m2),
700
+ b: parseIntFromHex(m3 + m3),
701
+ a: convertHexToDecimal(m4 + m4),
702
+ // format: named ? 'rgb' : 'hex8',
703
+ format: named ? 'rgb' : 'hex',
704
+ };
705
+ }
706
+ [, m1, m2, m3] = matchers.hex3.exec(color) || [];
707
+ if (m1 && m2 && m3) {
708
+ return {
709
+ r: parseIntFromHex(m1 + m1),
710
+ g: parseIntFromHex(m2 + m2),
711
+ b: parseIntFromHex(m3 + m3),
712
+ format: named ? 'rgb' : 'hex',
713
+ };
714
+ }
715
+ return false;
716
+ }
717
+
718
+ /**
719
+ * Given a string or object, convert that input to RGB
720
+ *
721
+ * Possible string inputs:
722
+ * ```
723
+ * "red"
724
+ * "#f00" or "f00"
725
+ * "#ff0000" or "ff0000"
726
+ * "#ff000000" or "ff000000" // CSS4 Module
727
+ * "rgb 255 0 0" or "rgb (255, 0, 0)"
728
+ * "rgb 1.0 0 0" or "rgb (1, 0, 0)"
729
+ * "rgba(255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
730
+ * "rgba(1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
731
+ * "rgb(255 0 0 / 10%)" or "rgb 255 0 0 0.1" // CSS4 Module
732
+ * "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
733
+ * "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
734
+ * "hsl(0deg 100% 50% / 50%)" or "hsl 0 100 50 50" // CSS4 Module
735
+ * "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
736
+ * "hsva(0, 100%, 100%, 0.1)" or "hsva 0 100% 100% 0.1"
737
+ * "hsv(0deg 100% 100% / 10%)" or "hsv 0 100 100 0.1" // CSS4 Module
738
+ * "hwb(0deg, 100%, 100%, 100%)" or "hwb 0 100% 100% 0.1" // CSS4 Module
739
+ * ```
740
+ * @param {string | Record<string, any>} input
741
+ * @returns {CP.ColorObject}
742
+ */
743
+ function inputToRGB(input) {
744
+ let rgb = { r: 0, g: 0, b: 0 };
745
+ let color = input;
746
+ let a = 1;
747
+ let s = null;
748
+ let v = null;
749
+ let l = null;
750
+ let w = null;
751
+ let b = null;
752
+ let h = null;
753
+ let r = null;
754
+ let g = null;
755
+ let ok = false;
756
+ let format = 'hex';
757
+
758
+ if (typeof input === 'string') {
759
+ // @ts-ignore -- this now is converted to object
760
+ color = stringInputToObject(input);
761
+ if (color) ok = true;
762
+ }
763
+ if (typeof color === 'object') {
764
+ if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
765
+ ({ r, g, b } = color);
766
+ // RGB values now are all in [0, 255] range
767
+ [r, g, b] = [r, g, b].map((n) => bound01(n, isPercentage(n) ? 100 : 255) * 255);
768
+ rgb = { r, g, b };
769
+ ok = true;
770
+ format = 'rgb';
771
+ } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
772
+ ({ h, s, v } = color);
773
+ h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
774
+ s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
775
+ v = typeof v === 'number' ? v : bound01(v, 100); // brightness can be `5%` or a [0, 1] value
776
+ rgb = hsvToRgb(h, s, v);
777
+ ok = true;
778
+ format = 'hsv';
779
+ } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
780
+ ({ h, s, l } = color);
781
+ h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
782
+ s = typeof s === 'number' ? s : bound01(s, 100); // saturation can be `5%` or a [0, 1] value
783
+ l = typeof l === 'number' ? l : bound01(l, 100); // lightness can be `5%` or a [0, 1] value
784
+ rgb = hslToRgb(h, s, l);
785
+ ok = true;
786
+ format = 'hsl';
787
+ } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.w) && isValidCSSUnit(color.b)) {
788
+ ({ h, w, b } = color);
789
+ h = typeof h === 'number' ? h : bound01(h, 360); // hue can be `5deg` or a [0, 1] value
790
+ w = typeof w === 'number' ? w : bound01(w, 100); // whiteness can be `5%` or a [0, 1] value
791
+ b = typeof b === 'number' ? b : bound01(b, 100); // blackness can be `5%` or a [0, 1] value
792
+ rgb = hwbToRgb(h, w, b);
793
+ ok = true;
794
+ format = 'hwb';
795
+ }
796
+ if (isValidCSSUnit(color.a)) {
797
+ a = color.a;
798
+ a = isPercentage(`${a}`) ? bound01(a, 100) : a;
799
+ }
800
+ }
801
+
802
+ return {
803
+ ok, // @ts-ignore
804
+ format: color.format || format,
805
+ r: Math.min(255, Math.max(rgb.r, 0)),
806
+ g: Math.min(255, Math.max(rgb.g, 0)),
807
+ b: Math.min(255, Math.max(rgb.b, 0)),
808
+ a: boundAlpha(a),
809
+ };
810
+ }
811
+
812
+ /**
813
+ * @class
814
+ * Returns a new `Color` instance.
815
+ * @see https://github.com/bgrins/TinyColor
816
+ */
817
+ class Color {
818
+ /**
819
+ * @constructor
820
+ * @param {CP.ColorInput} input the given colour value
821
+ * @param {CP.ColorFormats=} config the given format
822
+ */
823
+ constructor(input, config) {
824
+ let color = input;
825
+ const configFormat = config && COLOR_FORMAT.includes(config)
826
+ ? config : 'rgb';
827
+
828
+ // If input is already a `Color`, return itself
829
+ if (color instanceof Color) {
830
+ color = inputToRGB(color);
831
+ }
832
+ if (typeof color === 'number') {
833
+ color = numberInputToObject(color);
834
+ }
835
+ const {
836
+ r, g, b, a, ok, format,
837
+ } = inputToRGB(color);
838
+
839
+ // bind
840
+ const self = this;
841
+
842
+ /** @type {CP.ColorInput} */
843
+ self.originalInput = color;
844
+ /** @type {number} */
845
+ self.r = r;
846
+ /** @type {number} */
847
+ self.g = g;
848
+ /** @type {number} */
849
+ self.b = b;
850
+ /** @type {number} */
851
+ self.a = a;
852
+ /** @type {boolean} */
853
+ self.ok = ok;
854
+ /** @type {CP.ColorFormats} */
855
+ self.format = configFormat || format;
856
+ }
857
+
858
+ /**
859
+ * Checks if the current input value is a valid colour.
860
+ * @returns {boolean} the query result
861
+ */
862
+ get isValid() {
863
+ return this.ok;
864
+ }
865
+
866
+ /**
867
+ * Checks if the current colour requires a light text colour.
868
+ * @returns {boolean} the query result
869
+ */
870
+ get isDark() {
871
+ return this.brightness < 120;
872
+ }
873
+
874
+ /**
875
+ * Returns the perceived luminance of a colour.
876
+ * @see http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
877
+ * @returns {number} a number in the [0, 1] range
878
+ */
879
+ get luminance() {
880
+ const { r, g, b } = this;
881
+ let R = 0;
882
+ let G = 0;
883
+ let B = 0;
884
+ const rp = r / 255;
885
+ const rg = g / 255;
886
+ const rb = b / 255;
887
+
888
+ if (rp <= 0.03928) {
889
+ R = rp / 12.92;
890
+ } else {
891
+ R = ((rp + 0.055) / 1.055) ** 2.4;
892
+ }
893
+ if (rg <= 0.03928) {
894
+ G = rg / 12.92;
895
+ } else {
896
+ G = ((rg + 0.055) / 1.055) ** 2.4;
897
+ }
898
+ if (rb <= 0.03928) {
899
+ B = rb / 12.92;
900
+ } else {
901
+ B = ((rb + 0.055) / 1.055) ** 2.4;
902
+ }
903
+ return 0.2126 * R + 0.7152 * G + 0.0722 * B;
904
+ }
905
+
906
+ /**
907
+ * Returns the perceived brightness of the colour.
908
+ * @returns {number} a number in the [0, 255] range
909
+ */
910
+ get brightness() {
911
+ const { r, g, b } = this;
912
+ return (r * 299 + g * 587 + b * 114) / 1000;
913
+ }
914
+
915
+ /**
916
+ * Returns the colour as an RGBA object.
917
+ * @returns {CP.RGBA} an {r,g,b,a} object with [0, 255] ranged values
918
+ */
919
+ toRgb() {
920
+ const {
921
+ r, g, b, a,
922
+ } = this;
923
+
924
+ return {
925
+ r, g, b, a: roundPart(a * 100) / 100,
926
+ };
927
+ }
928
+
929
+ /**
930
+ * Returns the RGBA values concatenated into a CSS3 Module string format.
931
+ * * rgb(255,255,255)
932
+ * * rgba(255,255,255,0.5)
933
+ * @returns {string} the CSS valid colour in RGB/RGBA format
934
+ */
935
+ toRgbString() {
936
+ const {
937
+ r, g, b, a,
938
+ } = this.toRgb();
939
+ const [R, G, B] = [r, g, b].map(roundPart);
940
+
941
+ return a === 1
942
+ ? `rgb(${R}, ${G}, ${B})`
943
+ : `rgba(${R}, ${G}, ${B}, ${a})`;
944
+ }
945
+
946
+ /**
947
+ * Returns the RGBA values concatenated into a CSS4 Module string format.
948
+ * * rgb(255 255 255)
949
+ * * rgb(255 255 255 / 50%)
950
+ * @returns {string} the CSS valid colour in CSS4 RGB format
951
+ */
952
+ toRgbCSS4String() {
953
+ const {
954
+ r, g, b, a,
955
+ } = this.toRgb();
956
+ const [R, G, B] = [r, g, b].map(roundPart);
957
+ const A = a === 1 ? '' : ` / ${roundPart(a * 100)}%`;
958
+
959
+ return `rgb(${R} ${G} ${B}${A})`;
960
+ }
961
+
962
+ /**
963
+ * Returns the hexadecimal value of the colour. When the parameter is *true*
964
+ * it will find a 3 characters shorthand of the decimal value.
965
+ *
966
+ * @param {boolean=} allow3Char when `true` returns shorthand HEX
967
+ * @returns {string} the hexadecimal colour format
968
+ */
969
+ toHex(allow3Char) {
970
+ const {
971
+ r, g, b, a,
972
+ } = this.toRgb();
973
+
974
+ return a === 1
975
+ ? rgbToHex(r, g, b, allow3Char)
976
+ : rgbaToHex(r, g, b, a, allow3Char);
977
+ }
978
+
979
+ /**
980
+ * Returns the CSS valid hexadecimal vaue of the colour. When the parameter is *true*
981
+ * it will find a 3 characters shorthand of the value.
982
+ *
983
+ * @param {boolean=} allow3Char when `true` returns shorthand HEX
984
+ * @returns {string} the CSS valid colour in hexadecimal format
985
+ */
986
+ toHexString(allow3Char) {
987
+ return `#${this.toHex(allow3Char)}`;
988
+ }
989
+
990
+ /**
991
+ * Returns the HEX8 value of the colour.
992
+ * @param {boolean=} allow4Char when `true` returns shorthand HEX
993
+ * @returns {string} the CSS valid colour in hexadecimal format
994
+ */
995
+ toHex8(allow4Char) {
996
+ const {
997
+ r, g, b, a,
998
+ } = this.toRgb();
999
+
1000
+ return rgbaToHex(r, g, b, a, allow4Char);
1001
+ }
1002
+
1003
+ /**
1004
+ * Returns the HEX8 value of the colour.
1005
+ * @param {boolean=} allow4Char when `true` returns shorthand HEX
1006
+ * @returns {string} the CSS valid colour in hexadecimal format
1007
+ */
1008
+ toHex8String(allow4Char) {
1009
+ return `#${this.toHex8(allow4Char)}`;
1010
+ }
1011
+
1012
+ /**
1013
+ * Returns the colour as a HSVA object.
1014
+ * @returns {CP.HSVA} the `{h,s,v,a}` object with [0, 1] ranged values
1015
+ */
1016
+ toHsv() {
1017
+ const {
1018
+ r, g, b, a,
1019
+ } = this.toRgb();
1020
+ const { h, s, v } = rgbToHsv(r, g, b);
1021
+
1022
+ return {
1023
+ h, s, v, a,
1024
+ };
1025
+ }
1026
+
1027
+ /**
1028
+ * Returns the colour as an HSLA object.
1029
+ * @returns {CP.HSLA} the `{h,s,l,a}` object with [0, 1] ranged values
1030
+ */
1031
+ toHsl() {
1032
+ const {
1033
+ r, g, b, a,
1034
+ } = this.toRgb();
1035
+ const { h, s, l } = rgbToHsl(r, g, b);
1036
+
1037
+ return {
1038
+ h, s, l, a,
1039
+ };
1040
+ }
1041
+
1042
+ /**
1043
+ * Returns the HSLA values concatenated into a CSS3 Module format string.
1044
+ * * `hsl(150, 100%, 50%)`
1045
+ * * `hsla(150, 100%, 50%, 0.5)`
1046
+ * @returns {string} the CSS valid colour in HSL/HSLA format
1047
+ */
1048
+ toHslString() {
1049
+ let {
1050
+ h, s, l, a,
1051
+ } = this.toHsl();
1052
+ h = roundPart(h * 360);
1053
+ s = roundPart(s * 100);
1054
+ l = roundPart(l * 100);
1055
+ a = roundPart(a * 100) / 100;
1056
+
1057
+ return a === 1
1058
+ ? `hsl(${h}, ${s}%, ${l}%)`
1059
+ : `hsla(${h}, ${s}%, ${l}%, ${a})`;
1060
+ }
1061
+
1062
+ /**
1063
+ * Returns the HSLA values concatenated into a CSS4 Module format string.
1064
+ * * `hsl(150deg 100% 50%)`
1065
+ * * `hsl(150deg 100% 50% / 50%)`
1066
+ * @returns {string} the CSS valid colour in CSS4 HSL format
1067
+ */
1068
+ toHslCSS4String() {
1069
+ let {
1070
+ h, s, l, a,
1071
+ } = this.toHsl();
1072
+ h = roundPart(h * 360);
1073
+ s = roundPart(s * 100);
1074
+ l = roundPart(l * 100);
1075
+ a = roundPart(a * 100);
1076
+ const A = a < 100 ? ` / ${roundPart(a)}%` : '';
1077
+
1078
+ return `hsl(${h}deg ${s}% ${l}%${A})`;
1079
+ }
1080
+
1081
+ /**
1082
+ * Returns the colour as an HWBA object.
1083
+ * @returns {CP.HWBA} the `{h,w,b,a}` object with [0, 1] ranged values
1084
+ */
1085
+ toHwb() {
1086
+ const {
1087
+ r, g, b, a,
1088
+ } = this;
1089
+ const { h, w, b: bl } = rgbToHwb(r, g, b);
1090
+ return {
1091
+ h, w, b: bl, a,
1092
+ };
1093
+ }
1094
+
1095
+ /**
1096
+ * Returns the HWBA values concatenated into a string.
1097
+ * @returns {string} the CSS valid colour in HWB format
1098
+ */
1099
+ toHwbString() {
1100
+ let {
1101
+ h, w, b, a,
1102
+ } = this.toHwb();
1103
+ h = roundPart(h * 360);
1104
+ w = roundPart(w * 100);
1105
+ b = roundPart(b * 100);
1106
+ a = roundPart(a * 100);
1107
+ const A = a < 100 ? ` / ${roundPart(a)}%` : '';
1108
+
1109
+ return `hwb(${h}deg ${w}% ${b}%${A})`;
1110
+ }
1111
+
1112
+ /**
1113
+ * Sets the alpha value of the current colour.
1114
+ * @param {number} alpha a new alpha value in the [0, 1] range.
1115
+ * @returns {Color} the `Color` instance
1116
+ */
1117
+ setAlpha(alpha) {
1118
+ const self = this;
1119
+ self.a = boundAlpha(alpha);
1120
+ return self;
1121
+ }
1122
+
1123
+ /**
1124
+ * Saturate the colour with a given amount.
1125
+ * @param {number=} amount a value in the [0, 100] range
1126
+ * @returns {Color} the `Color` instance
1127
+ */
1128
+ saturate(amount) {
1129
+ const self = this;
1130
+ if (typeof amount !== 'number') return self;
1131
+ const { h, s, l } = self.toHsl();
1132
+ const { r, g, b } = hslToRgb(h, clamp01(s + amount / 100), l);
1133
+
1134
+ ObjectAssign(self, { r, g, b });
1135
+ return self;
1136
+ }
1137
+
1138
+ /**
1139
+ * Desaturate the colour with a given amount.
1140
+ * @param {number=} amount a value in the [0, 100] range
1141
+ * @returns {Color} the `Color` instance
1142
+ */
1143
+ desaturate(amount) {
1144
+ return typeof amount === 'number' ? this.saturate(-amount) : this;
1145
+ }
1146
+
1147
+ /**
1148
+ * Completely desaturates a colour into greyscale.
1149
+ * Same as calling `desaturate(100)`
1150
+ * @returns {Color} the `Color` instance
1151
+ */
1152
+ greyscale() {
1153
+ return this.saturate(-100);
1154
+ }
1155
+
1156
+ /**
1157
+ * Increase the colour lightness with a given amount.
1158
+ * @param {number=} amount a value in the [0, 100] range
1159
+ * @returns {Color} the `Color` instance
1160
+ */
1161
+ lighten(amount) {
1162
+ const self = this;
1163
+ if (typeof amount !== 'number') return self;
1164
+
1165
+ const { h, s, l } = self.toHsl();
1166
+ const { r, g, b } = hslToRgb(h, s, clamp01(l + amount / 100));
1167
+
1168
+ ObjectAssign(self, { r, g, b });
1169
+ return self;
1170
+ }
1171
+
1172
+ /**
1173
+ * Decrease the colour lightness with a given amount.
1174
+ * @param {number=} amount a value in the [0, 100] range
1175
+ * @returns {Color} the `Color` instance
1176
+ */
1177
+ darken(amount) {
1178
+ return typeof amount === 'number' ? this.lighten(-amount) : this;
1179
+ }
1180
+
1181
+ /**
1182
+ * Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
1183
+ * Values outside of this range will be wrapped into this range.
1184
+ *
1185
+ * @param {number=} amount a value in the [0, 100] range
1186
+ * @returns {Color} the `Color` instance
1187
+ */
1188
+ spin(amount) {
1189
+ const self = this;
1190
+ if (typeof amount !== 'number') return self;
1191
+
1192
+ const { h, s, l } = self.toHsl();
1193
+ const { r, g, b } = hslToRgb(clamp01(((h * 360 + amount) % 360) / 360), s, l);
1194
+
1195
+ ObjectAssign(self, { r, g, b });
1196
+ return self;
1197
+ }
1198
+
1199
+ /** Returns a clone of the current `Color` instance. */
1200
+ clone() {
1201
+ return new Color(this);
1202
+ }
1203
+
1204
+ /**
1205
+ * Returns the colour value in CSS valid string format.
1206
+ * @param {boolean=} allowShort when *true*, HEX values can be shorthand
1207
+ * @returns {string} the CSS valid colour in the configured format
1208
+ */
1209
+ toString(allowShort) {
1210
+ const self = this;
1211
+ const { format } = self;
1212
+
1213
+ if (format === 'hex') return self.toHexString(allowShort);
1214
+ if (format === 'hsl') return self.toHslString();
1215
+ if (format === 'hwb') return self.toHwbString();
1216
+
1217
+ return self.toRgbString();
1218
+ }
1219
+ }
1220
+
1221
+ ObjectAssign(Color, {
1222
+ ANGLES,
1223
+ CSS_ANGLE,
1224
+ CSS_INTEGER,
1225
+ CSS_NUMBER,
1226
+ CSS_UNIT,
1227
+ CSS_UNIT2,
1228
+ PERMISSIVE_MATCH,
1229
+ matchers,
1230
+ isOnePointZero,
1231
+ isPercentage,
1232
+ isValidCSSUnit,
1233
+ pad2,
1234
+ clamp01,
1235
+ bound01,
1236
+ boundAlpha,
1237
+ getRGBFromName,
1238
+ convertHexToDecimal,
1239
+ convertDecimalToHex,
1240
+ rgbToHsl,
1241
+ rgbToHex,
1242
+ rgbToHsv,
1243
+ rgbToHwb,
1244
+ rgbaToHex,
1245
+ hslToRgb,
1246
+ hsvToRgb,
1247
+ hueToRgb,
1248
+ hwbToRgb,
1249
+ parseIntFromHex,
1250
+ numberInputToObject,
1251
+ stringInputToObject,
1252
+ inputToRGB,
1253
+ roundPart,
1254
+ ObjectAssign,
1255
+ });
1256
+
1257
+ /** @type {Record<string, any>} */
1258
+ const EventRegistry = {};
1259
+
1260
+ /**
1261
+ * The global event listener.
1262
+ *
1263
+ * @this {Element | HTMLElement | Window | Document}
1264
+ * @param {Event} e
1265
+ * @returns {void}
1266
+ */
1267
+ function globalListener(e) {
1268
+ const that = this;
1269
+ const { type } = e;
1270
+ const oneEvMap = EventRegistry[type] ? [...EventRegistry[type]] : [];
1271
+
1272
+ oneEvMap.forEach((elementsMap) => {
1273
+ const [element, listenersMap] = elementsMap;
1274
+ [...listenersMap].forEach((listenerMap) => {
1275
+ if (element === that) {
1276
+ const [listener, options] = listenerMap;
1277
+ listener.apply(element, [e]);
1278
+
1279
+ if (options && options.once) {
1280
+ removeListener(element, type, listener, options);
1281
+ }
1282
+ }
1283
+ });
1284
+ });
1285
+ }
1286
+
1287
+ /**
1288
+ * Register a new listener with its options and attach the `globalListener`
1289
+ * to the target if this is the first listener.
1290
+ *
1291
+ * @param {Element | HTMLElement | Window | Document} element
1292
+ * @param {string} eventType
1293
+ * @param {EventListenerObject['handleEvent']} listener
1294
+ * @param {AddEventListenerOptions=} options
1295
+ */
1296
+ const addListener = (element, eventType, listener, options) => {
1297
+ // get element listeners first
1298
+ if (!EventRegistry[eventType]) {
1299
+ EventRegistry[eventType] = new Map();
1300
+ }
1301
+ const oneEventMap = EventRegistry[eventType];
1302
+
1303
+ if (!oneEventMap.has(element)) {
1304
+ oneEventMap.set(element, new Map());
1305
+ }
1306
+ const oneElementMap = oneEventMap.get(element);
1307
+
1308
+ // get listeners size
1309
+ const { size } = oneElementMap;
1310
+
1311
+ // register listener with its options
1312
+ if (oneElementMap) {
1313
+ oneElementMap.set(listener, options);
1314
+ }
1315
+
1316
+ // add listener last
1317
+ if (!size) {
1318
+ element.addEventListener(eventType, globalListener, options);
1319
+ }
1320
+ };
1321
+
1322
+ /**
1323
+ * Remove a listener from registry and detach the `globalListener`
1324
+ * if no listeners are found in the registry.
1325
+ *
1326
+ * @param {Element | HTMLElement | Window | Document} element
1327
+ * @param {string} eventType
1328
+ * @param {EventListenerObject['handleEvent']} listener
1329
+ * @param {AddEventListenerOptions=} options
1330
+ */
1331
+ const removeListener = (element, eventType, listener, options) => {
1332
+ // get listener first
1333
+ const oneEventMap = EventRegistry[eventType];
1334
+ const oneElementMap = oneEventMap && oneEventMap.get(element);
1335
+ const savedOptions = oneElementMap && oneElementMap.get(listener);
1336
+
1337
+ // also recover initial options
1338
+ const { options: eventOptions } = savedOptions !== undefined
1339
+ ? savedOptions
1340
+ : { options };
1341
+
1342
+ // unsubscribe second, remove from registry
1343
+ if (oneElementMap && oneElementMap.has(listener)) oneElementMap.delete(listener);
1344
+ if (oneEventMap && (!oneElementMap || !oneElementMap.size)) oneEventMap.delete(element);
1345
+ if (!oneEventMap || !oneEventMap.size) delete EventRegistry[eventType];
1346
+
1347
+ // remove listener last
1348
+ if (!oneElementMap || !oneElementMap.size) {
1349
+ element.removeEventListener(eventType, globalListener, eventOptions);
1350
+ }
1351
+ };
1352
+
1353
+ /**
1354
+ * A global namespace for aria-description.
1355
+ * @type {string}
1356
+ */
1357
+ const ariaDescription = 'aria-description';
1358
+
1359
+ /**
1360
+ * A global namespace for aria-selected.
1361
+ * @type {string}
1362
+ */
1363
+ const ariaSelected = 'aria-selected';
1364
+
1365
+ /**
1366
+ * A global namespace for aria-expanded.
1367
+ * @type {string}
1368
+ */
1369
+ const ariaExpanded = 'aria-expanded';
1370
+
1371
+ /**
1372
+ * A global namespace for aria-valuetext.
1373
+ * @type {string}
1374
+ */
1375
+ const ariaValueText = 'aria-valuetext';
1376
+
1377
+ /**
1378
+ * A global namespace for aria-valuenow.
1379
+ * @type {string}
1380
+ */
1381
+ const ariaValueNow = 'aria-valuenow';
1382
+
1383
+ /**
1384
+ * A global namespace for aria-haspopup.
1385
+ * @type {string}
1386
+ */
1387
+ const ariaHasPopup = 'aria-haspopup';
1388
+
1389
+ /**
1390
+ * A global namespace for aria-hidden.
1391
+ * @type {string}
1392
+ */
1393
+ const ariaHidden = 'aria-hidden';
1394
+
1395
+ /**
1396
+ * A global namespace for aria-labelledby.
1397
+ * @type {string}
1398
+ */
1399
+ const ariaLabelledBy = 'aria-labelledby';
1400
+
1401
+ /**
1402
+ * A global namespace for `ArrowDown` key.
1403
+ * @type {string} e.which = 40 equivalent
1404
+ */
1405
+ const keyArrowDown = 'ArrowDown';
1406
+
1407
+ /**
1408
+ * A global namespace for `ArrowUp` key.
1409
+ * @type {string} e.which = 38 equivalent
1410
+ */
1411
+ const keyArrowUp = 'ArrowUp';
1412
+
1413
+ /**
1414
+ * A global namespace for `ArrowLeft` key.
1415
+ * @type {string} e.which = 37 equivalent
1416
+ */
1417
+ const keyArrowLeft = 'ArrowLeft';
1418
+
1419
+ /**
1420
+ * A global namespace for `ArrowRight` key.
1421
+ * @type {string} e.which = 39 equivalent
1422
+ */
1423
+ const keyArrowRight = 'ArrowRight';
1424
+
1425
+ /**
1426
+ * A global namespace for `Enter` key.
1427
+ * @type {string} e.which = 13 equivalent
1428
+ */
1429
+ const keyEnter = 'Enter';
1430
+
1431
+ /**
1432
+ * A global namespace for `Space` key.
1433
+ * @type {string} e.which = 32 equivalent
1434
+ */
1435
+ const keySpace = 'Space';
1436
+
1437
+ /**
1438
+ * A global namespace for `Escape` key.
1439
+ * @type {string} e.which = 27 equivalent
1440
+ */
1441
+ const keyEscape = 'Escape';
1442
+
1443
+ /**
1444
+ * A global namespace for `focusin` event.
1445
+ * @type {string}
1446
+ */
1447
+ const focusinEvent = 'focusin';
1448
+
1449
+ /**
1450
+ * A global namespace for `click` event.
1451
+ * @type {string}
1452
+ */
1453
+ const mouseclickEvent = 'click';
1454
+
1455
+ /**
1456
+ * A global namespace for `keydown` event.
1457
+ * @type {string}
1458
+ */
1459
+ const keydownEvent = 'keydown';
1460
+
1461
+ /**
1462
+ * A global namespace for `change` event.
1463
+ * @type {string}
1464
+ */
1465
+ const changeEvent = 'change';
1466
+
1467
+ /**
1468
+ * A global namespace for `touchstart` event.
1469
+ * @type {string}
1470
+ */
1471
+ const touchstartEvent = 'touchstart';
1472
+
1473
+ /**
1474
+ * A global namespace for `touchmove` event.
1475
+ * @type {string}
1476
+ */
1477
+ const touchmoveEvent = 'touchmove';
1478
+
1479
+ /**
1480
+ * A global namespace for `touchend` event.
1481
+ * @type {string}
1482
+ */
1483
+ const touchendEvent = 'touchend';
1484
+
1485
+ /**
1486
+ * A global namespace for `mousedown` event.
1487
+ * @type {string}
1488
+ */
1489
+ const mousedownEvent = 'mousedown';
1490
+
1491
+ /**
1492
+ * A global namespace for `mousemove` event.
1493
+ * @type {string}
1494
+ */
1495
+ const mousemoveEvent = 'mousemove';
1496
+
1497
+ /**
1498
+ * A global namespace for `mouseup` event.
1499
+ * @type {string}
1500
+ */
1501
+ const mouseupEvent = 'mouseup';
1502
+
1503
+ /**
1504
+ * A global namespace for `scroll` event.
1505
+ * @type {string}
1506
+ */
1507
+ const scrollEvent = 'scroll';
1508
+
1509
+ /**
1510
+ * A global namespace for `keyup` event.
1511
+ * @type {string}
1512
+ */
1513
+ const keyupEvent = 'keyup';
1514
+
1515
+ /**
1516
+ * A global namespace for `resize` event.
1517
+ * @type {string}
1518
+ */
1519
+ const resizeEvent = 'resize';
1520
+
1521
+ /**
1522
+ * A global namespace for `focusout` event.
1523
+ * @type {string}
1524
+ */
1525
+ const focusoutEvent = 'focusout';
1526
+
1527
+ // @ts-ignore
1528
+ const { userAgentData: uaDATA } = navigator;
1529
+
1530
+ /**
1531
+ * A global namespace for `userAgentData` object.
1532
+ */
1533
+ const userAgentData = uaDATA;
1534
+
1535
+ const { userAgent: userAgentString } = navigator;
1536
+
1537
+ /**
1538
+ * A global namespace for `navigator.userAgent` string.
1539
+ */
1540
+ const userAgent = userAgentString;
1541
+
1542
+ const mobileBrands = /iPhone|iPad|iPod|Android/i;
1543
+ let isMobileCheck = false;
1544
+
1545
+ if (userAgentData) {
1546
+ isMobileCheck = userAgentData.brands
1547
+ .some((/** @type {Record<String, any>} */x) => mobileBrands.test(x.brand));
1548
+ } else {
1549
+ isMobileCheck = mobileBrands.test(userAgent);
1550
+ }
1551
+
1552
+ /**
1553
+ * A global `boolean` for mobile detection.
1554
+ * @type {boolean}
1555
+ */
1556
+ const isMobile = isMobileCheck;
1557
+
1558
+ /**
1559
+ * Returns the `document.documentElement` or the `<html>` element.
1560
+ *
1561
+ * @param {(Node | HTMLElement | Element | globalThis)=} node
1562
+ * @returns {HTMLElement | HTMLHtmlElement}
1563
+ */
1564
+ function getDocumentElement(node) {
1565
+ return getDocument(node).documentElement;
1566
+ }
1567
+
1568
+ /**
1569
+ * Returns the `Window` object of a target node.
1570
+ * @see https://github.com/floating-ui/floating-ui
1571
+ *
1572
+ * @param {(Node | HTMLElement | Element | Window)=} node target node
1573
+ * @returns {globalThis}
1574
+ */
1575
+ function getWindow(node) {
1576
+ if (node == null) {
1577
+ return window;
1578
+ }
1579
+
1580
+ if (!(node instanceof Window)) {
1581
+ const { ownerDocument } = node;
1582
+ return ownerDocument ? ownerDocument.defaultView || window : window;
1583
+ }
1584
+
1585
+ // @ts-ignore
1586
+ return node;
1587
+ }
1588
+
1589
+ let elementUID = 0;
1590
+ let elementMapUID = 0;
1591
+ const elementIDMap = new Map();
1592
+
1593
+ /**
1594
+ * Returns a unique identifier for popover, tooltip, scrollspy.
1595
+ *
1596
+ * @param {HTMLElement | Element} element target element
1597
+ * @param {string=} key predefined key
1598
+ * @returns {number} an existing or new unique ID
1599
+ */
1600
+ function getUID(element, key) {
1601
+ let result = key ? elementUID : elementMapUID;
1602
+
1603
+ if (key) {
1604
+ const elID = getUID(element);
1605
+ const elMap = elementIDMap.get(elID) || new Map();
1606
+ if (!elementIDMap.has(elID)) {
1607
+ elementIDMap.set(elID, elMap);
1608
+ }
1609
+ if (!elMap.has(key)) {
1610
+ elMap.set(key, result);
1611
+ elementUID += 1;
1612
+ } else result = elMap.get(key);
1613
+ } else {
1614
+ const elkey = element.id || element;
1615
+
1616
+ if (!elementIDMap.has(elkey)) {
1617
+ elementIDMap.set(elkey, result);
1618
+ elementMapUID += 1;
1619
+ } else result = elementIDMap.get(elkey);
1620
+ }
1621
+ return result;
1622
+ }
1623
+
1624
+ /**
1625
+ * Returns the bounding client rect of a target `HTMLElement`.
1626
+ *
1627
+ * @see https://github.com/floating-ui/floating-ui
1628
+ *
1629
+ * @param {HTMLElement | Element} element event.target
1630
+ * @param {boolean=} includeScale when *true*, the target scale is also computed
1631
+ * @returns {SHORTER.BoundingClientRect} the bounding client rect object
1632
+ */
1633
+ function getBoundingClientRect(element, includeScale) {
1634
+ const {
1635
+ width, height, top, right, bottom, left,
1636
+ } = element.getBoundingClientRect();
1637
+ let scaleX = 1;
1638
+ let scaleY = 1;
1639
+
1640
+ if (includeScale && element instanceof HTMLElement) {
1641
+ const { offsetWidth, offsetHeight } = element;
1642
+ scaleX = offsetWidth > 0 ? Math.round(width) / offsetWidth || 1 : 1;
1643
+ scaleY = offsetHeight > 0 ? Math.round(height) / offsetHeight || 1 : 1;
1644
+ }
1645
+
1646
+ return {
1647
+ width: width / scaleX,
1648
+ height: height / scaleY,
1649
+ top: top / scaleY,
1650
+ right: right / scaleX,
1651
+ bottom: bottom / scaleY,
1652
+ left: left / scaleX,
1653
+ x: left / scaleX,
1654
+ y: top / scaleY,
1655
+ };
1656
+ }
1657
+
1658
+ /**
1659
+ * A global namespace for 'transitionDuration' string.
1660
+ * @type {string}
1661
+ */
1662
+ const transitionDuration = 'transitionDuration';
1663
+
1664
+ /**
1665
+ * A global namespace for `transitionProperty` string for modern browsers.
1666
+ *
1667
+ * @type {string}
1668
+ */
1669
+ const transitionProperty = 'transitionProperty';
1670
+
1671
+ /**
1672
+ * Utility to get the computed `transitionDuration`
1673
+ * from Element in miliseconds.
1674
+ *
1675
+ * @param {HTMLElement | Element} element target
1676
+ * @return {number} the value in miliseconds
1677
+ */
1678
+ function getElementTransitionDuration(element) {
1679
+ const propertyValue = getElementStyle(element, transitionProperty);
1680
+ const durationValue = getElementStyle(element, transitionDuration);
1681
+ const durationScale = durationValue.includes('ms') ? 1 : 1000;
1682
+ const duration = propertyValue && propertyValue !== 'none'
1683
+ ? parseFloat(durationValue) * durationScale : 0;
1684
+
1685
+ return !Number.isNaN(duration) ? duration : 0;
1686
+ }
1687
+
1688
+ /**
1689
+ * A global array with `Element` | `HTMLElement`.
1690
+ */
1691
+ const elementNodes = [Element, HTMLElement];
1692
+
1693
+ /**
1694
+ * Utility to check if target is typeof `HTMLElement`, `Element`, `Node`
1695
+ * or find one that matches a selector.
1696
+ *
1697
+ * @param {HTMLElement | Element | string} selector the input selector or target element
1698
+ * @param {(HTMLElement | Element | Document)=} parent optional node to look into
1699
+ * @return {(HTMLElement | Element)?} the `HTMLElement` or `querySelector` result
1700
+ */
1701
+ function querySelector(selector, parent) {
1702
+ const lookUp = parentNodes.some((x) => parent instanceof x)
1703
+ ? parent : getDocument();
1704
+
1705
+ // @ts-ignore
1706
+ return elementNodes.some((x) => selector instanceof x)
1707
+ // @ts-ignore
1708
+ ? selector : lookUp.querySelector(selector);
1709
+ }
1710
+
1711
+ /**
1712
+ * Shortcut for `HTMLElement.closest` method which also works
1713
+ * with children of `ShadowRoot`. The order of the parameters
1714
+ * is intentional since they're both required.
1715
+ *
1716
+ * @see https://stackoverflow.com/q/54520554/803358
1717
+ *
1718
+ * @param {HTMLElement | Element} element Element to look into
1719
+ * @param {string} selector the selector name
1720
+ * @return {(HTMLElement | Element)?} the query result
1721
+ */
1722
+ function closest(element, selector) {
1723
+ return element ? (element.closest(selector)
1724
+ // @ts-ignore -- break out of `ShadowRoot`
1725
+ || closest(element.getRootNode().host, selector)) : null;
1726
+ }
1727
+
1728
+ /**
1729
+ * Shortcut for `HTMLElement.getElementsByClassName` method. Some `Node` elements
1730
+ * like `ShadowRoot` do not support `getElementsByClassName`.
1731
+ *
1732
+ * @param {string} selector the class name
1733
+ * @param {(HTMLElement | Element | Document)=} parent optional Element to look into
1734
+ * @return {HTMLCollectionOf<HTMLElement | Element>} the 'HTMLCollection'
1735
+ */
1736
+ function getElementsByClassName(selector, parent) {
1737
+ const lookUp = parent && parentNodes.some((x) => parent instanceof x)
1738
+ ? parent : getDocument();
1739
+ return lookUp.getElementsByClassName(selector);
1740
+ }
1741
+
1742
+ /**
1743
+ * This is a shortie for `document.createElementNS` method
1744
+ * which allows you to create a new `HTMLElement` for a given `tagName`
1745
+ * or based on an object with specific non-readonly attributes:
1746
+ * `id`, `className`, `textContent`, `style`, etc.
1747
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
1748
+ *
1749
+ * @param {string} namespace `namespaceURI` to associate with the new `HTMLElement`
1750
+ * @param {Record<string, string> | string} param `tagName` or object
1751
+ * @return {HTMLElement | Element} a new `HTMLElement` or `Element`
1752
+ */
1753
+ function createElementNS(namespace, param) {
1754
+ if (typeof param === 'string') {
1755
+ return getDocument().createElementNS(namespace, param);
1756
+ }
1757
+
1758
+ const { tagName } = param;
1759
+ const attr = { ...param };
1760
+ const newElement = createElementNS(namespace, tagName);
1761
+ delete attr.tagName;
1762
+ ObjectAssign(newElement, attr);
1763
+ return newElement;
1764
+ }
1765
+
1766
+ /**
1767
+ * Shortcut for the `Element.dispatchEvent(Event)` method.
1768
+ *
1769
+ * @param {HTMLElement | Element} element is the target
1770
+ * @param {Event} event is the `Event` object
1771
+ */
1772
+ const dispatchEvent = (element, event) => element.dispatchEvent(event);
1773
+
1774
+ /** @type {Map<string, Map<HTMLElement | Element, Record<string, any>>>} */
1775
+ const componentData = new Map();
1776
+ /**
1777
+ * An interface for web components background data.
1778
+ * @see https://github.com/thednp/bootstrap.native/blob/master/src/components/base-component.js
1779
+ */
1780
+ const Data = {
1781
+ /**
1782
+ * Sets web components data.
1783
+ * @param {HTMLElement | Element | string} target target element
1784
+ * @param {string} component the component's name or a unique key
1785
+ * @param {Record<string, any>} instance the component instance
1786
+ */
1787
+ set: (target, component, instance) => {
1788
+ const element = querySelector(target);
1789
+ if (!element) return;
1790
+
1791
+ if (!componentData.has(component)) {
1792
+ componentData.set(component, new Map());
1793
+ }
1794
+
1795
+ const instanceMap = componentData.get(component);
1796
+ // @ts-ignore - not undefined, but defined right above
1797
+ instanceMap.set(element, instance);
1798
+ },
1799
+
1800
+ /**
1801
+ * Returns all instances for specified component.
1802
+ * @param {string} component the component's name or a unique key
1803
+ * @returns {Map<HTMLElement | Element, Record<string, any>>?} all the component instances
1804
+ */
1805
+ getAllFor: (component) => {
1806
+ const instanceMap = componentData.get(component);
1807
+
1808
+ return instanceMap || null;
1809
+ },
1810
+
1811
+ /**
1812
+ * Returns the instance associated with the target.
1813
+ * @param {HTMLElement | Element | string} target target element
1814
+ * @param {string} component the component's name or a unique key
1815
+ * @returns {Record<string, any>?} the instance
1816
+ */
1817
+ get: (target, component) => {
1818
+ const element = querySelector(target);
1819
+ const allForC = Data.getAllFor(component);
1820
+ const instance = element && allForC && allForC.get(element);
1821
+
1822
+ return instance || null;
1823
+ },
1824
+
1825
+ /**
1826
+ * Removes web components data.
1827
+ * @param {HTMLElement | Element | string} target target element
1828
+ * @param {string} component the component's name or a unique key
1829
+ */
1830
+ remove: (target, component) => {
1831
+ const element = querySelector(target);
1832
+ const instanceMap = componentData.get(component);
1833
+ if (!instanceMap || !element) return;
1834
+
1835
+ instanceMap.delete(element);
1836
+
1837
+ if (instanceMap.size === 0) {
1838
+ componentData.delete(component);
1839
+ }
1840
+ },
1841
+ };
1842
+
1843
+ /**
1844
+ * An alias for `Data.get()`.
1845
+ * @type {SHORTER.getInstance<any>}
1846
+ */
1847
+ const getInstance = (target, component) => Data.get(target, component);
1848
+
1849
+ /**
1850
+ * The raw value or a given component option.
1851
+ *
1852
+ * @typedef {string | HTMLElement | Function | number | boolean | null} niceValue
1853
+ */
1854
+
1855
+ /**
1856
+ * Utility to normalize component options
1857
+ *
1858
+ * @param {any} value the input value
1859
+ * @return {niceValue} the normalized value
1860
+ */
1861
+ function normalizeValue(value) {
1862
+ if (value === 'true') { // boolean
1863
+ return true;
1864
+ }
1865
+
1866
+ if (value === 'false') { // boolean
1867
+ return false;
1868
+ }
1869
+
1870
+ if (!Number.isNaN(+value)) { // number
1871
+ return +value;
1872
+ }
1873
+
1874
+ if (value === '' || value === 'null') { // null
1875
+ return null;
1876
+ }
1877
+
1878
+ // string / function / HTMLElement / object
1879
+ return value;
1880
+ }
1881
+
1882
+ /**
1883
+ * Shortcut for `String.toLowerCase()`.
1884
+ *
1885
+ * @param {string} source input string
1886
+ * @returns {string} lowercase output string
1887
+ */
1888
+ const toLowerCase = (source) => source.toLowerCase();
1889
+
1890
+ /**
1891
+ * Utility to normalize component options.
1892
+ *
1893
+ * @param {HTMLElement | Element} element target
1894
+ * @param {Record<string, any>} defaultOps component default options
1895
+ * @param {Record<string, any>} inputOps component instance options
1896
+ * @param {string=} ns component namespace
1897
+ * @return {Record<string, any>} normalized component options object
1898
+ */
1899
+ function normalizeOptions(element, defaultOps, inputOps, ns) {
1900
+ // @ts-ignore -- our targets are always `HTMLElement`
1901
+ const data = { ...element.dataset };
1902
+ /** @type {Record<string, any>} */
1903
+ const normalOps = {};
1904
+ /** @type {Record<string, any>} */
1905
+ const dataOps = {};
1906
+ const title = 'title';
1907
+
1908
+ ObjectKeys(data).forEach((k) => {
1909
+ const key = ns && k.includes(ns)
1910
+ ? k.replace(ns, '').replace(/[A-Z]/, (match) => toLowerCase(match))
1911
+ : k;
1912
+
1913
+ dataOps[key] = normalizeValue(data[k]);
1914
+ });
1915
+
1916
+ ObjectKeys(inputOps).forEach((k) => {
1917
+ inputOps[k] = normalizeValue(inputOps[k]);
1918
+ });
1919
+
1920
+ ObjectKeys(defaultOps).forEach((k) => {
1921
+ if (k in inputOps) {
1922
+ normalOps[k] = inputOps[k];
1923
+ } else if (k in dataOps) {
1924
+ normalOps[k] = dataOps[k];
1925
+ } else {
1926
+ normalOps[k] = k === title
1927
+ ? getAttribute(element, title)
1928
+ : defaultOps[k];
1929
+ }
1930
+ });
1931
+
1932
+ return normalOps;
1933
+ }
1934
+
1935
+ /**
1936
+ * Utility to force re-paint of an `HTMLElement` target.
1937
+ *
1938
+ * @param {HTMLElement | Element} element is the target
1939
+ * @return {number} the `Element.offsetHeight` value
1940
+ */
1941
+ // @ts-ignore
1942
+ const reflow = (element) => element.offsetHeight;
1943
+
1944
+ /**
1945
+ * Utility to focus an `HTMLElement` target.
1946
+ *
1947
+ * @param {HTMLElement | Element} element is the target
1948
+ */
1949
+ // @ts-ignore -- `Element`s resulted from querySelector can focus too
1950
+ const focus = (element) => element.focus();
1951
+
1952
+ /**
1953
+ * Check class in `HTMLElement.classList`.
1954
+ *
1955
+ * @param {HTMLElement | Element} element target
1956
+ * @param {string} classNAME to check
1957
+ * @returns {boolean}
1958
+ */
1959
+ function hasClass(element, classNAME) {
1960
+ return element.classList.contains(classNAME);
1961
+ }
1962
+
1963
+ /**
1964
+ * Add class to `HTMLElement.classList`.
1965
+ *
1966
+ * @param {HTMLElement | Element} element target
1967
+ * @param {string} classNAME to add
1968
+ * @returns {void}
1969
+ */
1970
+ function addClass(element, classNAME) {
1971
+ element.classList.add(classNAME);
1972
+ }
1973
+
1974
+ /**
1975
+ * Remove class from `HTMLElement.classList`.
1976
+ *
1977
+ * @param {HTMLElement | Element} element target
1978
+ * @param {string} classNAME to remove
1979
+ * @returns {void}
1980
+ */
1981
+ function removeClass(element, classNAME) {
1982
+ element.classList.remove(classNAME);
1983
+ }
1984
+
1985
+ /**
1986
+ * Shortcut for `HTMLElement.removeAttribute()` method.
1987
+ * @param {HTMLElement | Element} element target element
1988
+ * @param {string} attribute attribute name
1989
+ * @returns {void}
1990
+ */
1991
+ const removeAttribute = (element, attribute) => element.removeAttribute(attribute);
1992
+
1993
+ /** @type {Record<string, string>} */
1994
+ const colorPickerLabels = {
1995
+ pickerLabel: 'Colour Picker',
1996
+ appearanceLabel: 'Colour Appearance',
1997
+ valueLabel: 'Colour Value',
1998
+ toggleLabel: 'Select Colour',
1999
+ presetsLabel: 'Colour Presets',
2000
+ defaultsLabel: 'Colour Defaults',
2001
+ formatLabel: 'Format',
2002
+ alphaLabel: 'Alpha',
2003
+ hexLabel: 'Hexadecimal',
2004
+ hueLabel: 'Hue',
2005
+ whitenessLabel: 'Whiteness',
2006
+ blacknessLabel: 'Blackness',
2007
+ saturationLabel: 'Saturation',
2008
+ lightnessLabel: 'Lightness',
2009
+ redLabel: 'Red',
2010
+ greenLabel: 'Green',
2011
+ blueLabel: 'Blue',
2012
+ };
2013
+
2014
+ /**
2015
+ * A list of 17 color names used for WAI-ARIA compliance.
2016
+ * @type {string[]}
2017
+ */
2018
+ const colorNames = ['white', 'black', 'grey', 'red', 'orange', 'brown', 'gold', 'olive', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'magenta', 'pink'];
2019
+
2020
+ /**
2021
+ * Shortcut for `String.toUpperCase()`.
2022
+ *
2023
+ * @param {string} source input string
2024
+ * @returns {string} uppercase output string
2025
+ */
2026
+ const toUpperCase = (source) => source.toUpperCase();
2027
+
2028
+ const vHidden = 'v-hidden';
2029
+
2030
+ /**
2031
+ * Returns the color form for `ColorPicker`.
2032
+ *
2033
+ * @param {CP.ColorPicker} self the `ColorPicker` instance
2034
+ * @returns {HTMLElement | Element} a new `<div>` element with color component `<input>`
2035
+ */
2036
+ function getColorForm(self) {
2037
+ const { format, id, componentLabels } = self;
2038
+ const colorForm = createElement({
2039
+ tagName: 'div',
2040
+ className: `color-form ${format}`,
2041
+ });
2042
+
2043
+ let components = ['hex'];
2044
+ if (format === 'rgb') components = ['red', 'green', 'blue', 'alpha'];
2045
+ else if (format === 'hsl') components = ['hue', 'saturation', 'lightness', 'alpha'];
2046
+ else if (format === 'hwb') components = ['hue', 'whiteness', 'blackness', 'alpha'];
2047
+
2048
+ components.forEach((c) => {
2049
+ const [C] = format === 'hex' ? ['#'] : toUpperCase(c).split('');
2050
+ const cID = `color_${format}_${c}_${id}`;
2051
+ const formatLabel = componentLabels[`${c}Label`];
2052
+ const cInputLabel = createElement({ tagName: 'label' });
2053
+ setAttribute(cInputLabel, 'for', cID);
2054
+ cInputLabel.append(
2055
+ createElement({ tagName: 'span', ariaHidden: 'true', innerText: `${C}:` }),
2056
+ createElement({ tagName: 'span', className: vHidden, innerText: formatLabel }),
2057
+ );
2058
+ const cInput = createElement({
2059
+ tagName: 'input',
2060
+ id: cID,
2061
+ // name: cID, - prevent saving the value to a form
2062
+ type: format === 'hex' ? 'text' : 'number',
2063
+ value: c === 'alpha' ? '100' : '0',
2064
+ className: `color-input ${c}`,
2065
+ });
2066
+ setAttribute(cInput, 'autocomplete', 'off');
2067
+ setAttribute(cInput, 'spellcheck', 'false');
2068
+
2069
+ // alpha
2070
+ let max = '100';
2071
+ let step = '1';
2072
+ if (c !== 'alpha') {
2073
+ if (format === 'rgb') {
2074
+ max = '255'; step = '1';
2075
+ } else if (c === 'hue') {
2076
+ max = '360'; step = '1';
2077
+ }
2078
+ }
2079
+ ObjectAssign(cInput, {
2080
+ min: '0',
2081
+ max,
2082
+ step,
2083
+ });
2084
+ colorForm.append(cInputLabel, cInput);
2085
+ });
2086
+ return colorForm;
2087
+ }
2088
+
2089
+ /**
2090
+ * A global namespace for aria-label.
2091
+ * @type {string}
2092
+ */
2093
+ const ariaLabel = 'aria-label';
2094
+
2095
+ /**
2096
+ * A global namespace for aria-valuemin.
2097
+ * @type {string}
2098
+ */
2099
+ const ariaValueMin = 'aria-valuemin';
2100
+
2101
+ /**
2102
+ * A global namespace for aria-valuemax.
2103
+ * @type {string}
2104
+ */
2105
+ const ariaValueMax = 'aria-valuemax';
2106
+
2107
+ const tabIndex = 'tabindex';
2108
+
2109
+ /**
2110
+ * Returns all color controls for `ColorPicker`.
2111
+ *
2112
+ * @param {CP.ColorPicker} self the `ColorPicker` instance
2113
+ * @returns {HTMLElement | Element} color controls
2114
+ */
2115
+ function getColorControls(self) {
2116
+ const { format, componentLabels } = self;
2117
+ const {
2118
+ hueLabel, alphaLabel, lightnessLabel, saturationLabel,
2119
+ whitenessLabel, blacknessLabel,
2120
+ } = componentLabels;
2121
+
2122
+ const max1 = format === 'hsl' ? 360 : 100;
2123
+ const max2 = format === 'hsl' ? 100 : 360;
2124
+ const max3 = 100;
2125
+
2126
+ let ctrl1Label = format === 'hsl'
2127
+ ? `${hueLabel} & ${lightnessLabel}`
2128
+ : `${lightnessLabel} & ${saturationLabel}`;
2129
+
2130
+ ctrl1Label = format === 'hwb'
2131
+ ? `${whitenessLabel} & ${blacknessLabel}`
2132
+ : ctrl1Label;
2133
+
2134
+ const ctrl2Label = format === 'hsl'
2135
+ ? `${saturationLabel}`
2136
+ : `${hueLabel}`;
2137
+
2138
+ const colorControls = createElement({
2139
+ tagName: 'div',
2140
+ className: `color-controls ${format}`,
2141
+ });
2142
+
2143
+ const colorPointer = 'color-pointer';
2144
+ const colorSlider = 'color-slider';
2145
+
2146
+ const controls = [
2147
+ {
2148
+ i: 1,
2149
+ c: colorPointer,
2150
+ l: ctrl1Label,
2151
+ min: 0,
2152
+ max: max1,
2153
+ },
2154
+ {
2155
+ i: 2,
2156
+ c: colorSlider,
2157
+ l: ctrl2Label,
2158
+ min: 0,
2159
+ max: max2,
2160
+ },
2161
+ {
2162
+ i: 3,
2163
+ c: colorSlider,
2164
+ l: alphaLabel,
2165
+ min: 0,
2166
+ max: max3,
2167
+ },
2168
+ ];
2169
+
2170
+ controls.forEach((template) => {
2171
+ const {
2172
+ i, c, l, min, max,
2173
+ } = template;
2174
+ const control = createElement({
2175
+ tagName: 'div',
2176
+ className: 'color-control',
2177
+ });
2178
+ setAttribute(control, 'role', 'presentation');
2179
+
2180
+ control.append(
2181
+ createElement({
2182
+ tagName: 'div',
2183
+ className: `visual-control visual-control${i}`,
2184
+ }),
2185
+ );
2186
+
2187
+ const knob = createElement({
2188
+ tagName: 'div',
2189
+ className: `${c} knob`,
2190
+ ariaLive: 'polite',
2191
+ });
2192
+
2193
+ setAttribute(knob, ariaLabel, l);
2194
+ setAttribute(knob, 'role', 'slider');
2195
+ setAttribute(knob, tabIndex, '0');
2196
+ setAttribute(knob, ariaValueMin, `${min}`);
2197
+ setAttribute(knob, ariaValueMax, `${max}`);
2198
+ control.append(knob);
2199
+ colorControls.append(control);
2200
+ });
2201
+
2202
+ return colorControls;
2203
+ }
2204
+
2205
+ /**
2206
+ * Helps setting CSS variables to the color-menu.
2207
+ * @param {HTMLElement} element
2208
+ * @param {Record<string,any>} props
2209
+ */
2210
+ function setCSSProperties(element, props) {
2211
+ ObjectKeys(props).forEach((prop) => {
2212
+ element.style.setProperty(prop, props[prop]);
2213
+ });
2214
+ }
2215
+
2216
+ /**
2217
+ * @class
2218
+ * Returns a color palette with a given set of parameters.
2219
+ * @example
2220
+ * new ColorPalette(0, 12, 10);
2221
+ * // => { hue: 0, hueSteps: 12, lightSteps: 10, colors: array }
2222
+ */
2223
+ class ColorPalette {
2224
+ /**
2225
+ * The `hue` parameter is optional, which would be set to 0.
2226
+ * @param {number[]} args represeinting hue, hueSteps, lightSteps
2227
+ * * `args.hue` the starting Hue [0, 360]
2228
+ * * `args.hueSteps` Hue Steps Count [5, 24]
2229
+ * * `args.lightSteps` Lightness Steps Count [5, 12]
2230
+ */
2231
+ constructor(...args) {
2232
+ let hue = 0;
2233
+ let hueSteps = 12;
2234
+ let lightSteps = 10;
2235
+ let lightnessArray = [0.5];
2236
+
2237
+ if (args.length === 3) {
2238
+ [hue, hueSteps, lightSteps] = args;
2239
+ } else if (args.length === 2) {
2240
+ [hueSteps, lightSteps] = args;
2241
+ } else {
2242
+ throw TypeError('ColorPalette requires minimum 2 arguments');
2243
+ }
2244
+
2245
+ /** @type {string[]} */
2246
+ const colors = [];
2247
+
2248
+ const hueStep = 360 / hueSteps;
2249
+ const half = roundPart((lightSteps - (lightSteps % 2 ? 1 : 0)) / 2);
2250
+ const estimatedStep = 100 / (lightSteps + (lightSteps % 2 ? 0 : 1)) / 100;
2251
+
2252
+ let lightStep = 0.25;
2253
+ lightStep = [4, 5].includes(lightSteps) ? 0.2 : lightStep;
2254
+ lightStep = [6, 7].includes(lightSteps) ? 0.15 : lightStep;
2255
+ lightStep = [8, 9].includes(lightSteps) ? 0.11 : lightStep;
2256
+ lightStep = [10, 11].includes(lightSteps) ? 0.09 : lightStep;
2257
+ lightStep = [12, 13].includes(lightSteps) ? 0.075 : lightStep;
2258
+ lightStep = lightSteps > 13 ? estimatedStep : lightStep;
2259
+
2260
+ // light tints
2261
+ for (let i = 1; i < half + 1; i += 1) {
2262
+ lightnessArray = [...lightnessArray, (0.5 + lightStep * (i))];
2263
+ }
2264
+
2265
+ // dark tints
2266
+ for (let i = 1; i < lightSteps - half; i += 1) {
2267
+ lightnessArray = [(0.5 - lightStep * (i)), ...lightnessArray];
2268
+ }
2269
+
2270
+ // feed `colors` Array
2271
+ for (let i = 0; i < hueSteps; i += 1) {
2272
+ const currentHue = ((hue + i * hueStep) % 360) / 360;
2273
+ lightnessArray.forEach((l) => {
2274
+ colors.push(new Color({ h: currentHue, s: 1, l }).toHexString());
2275
+ });
2276
+ }
2277
+
2278
+ this.hue = hue;
2279
+ this.hueSteps = hueSteps;
2280
+ this.lightSteps = lightSteps;
2281
+ this.colors = colors;
2282
+ }
2283
+ }
2284
+
2285
+ /**
2286
+ * Returns a color-defaults with given values and class.
2287
+ * @param {CP.ColorPicker} self
2288
+ * @param {CP.ColorPalette | string[]} colorsSource
2289
+ * @param {string} menuClass
2290
+ * @returns {HTMLElement | Element}
2291
+ */
2292
+ function getColorMenu(self, colorsSource, menuClass) {
2293
+ const { input, format, componentLabels } = self;
2294
+ const { defaultsLabel, presetsLabel } = componentLabels;
2295
+ const isOptionsMenu = menuClass === 'color-options';
2296
+ const isPalette = colorsSource instanceof ColorPalette;
2297
+ const menuLabel = isOptionsMenu ? presetsLabel : defaultsLabel;
2298
+ let colorsArray = isPalette ? colorsSource.colors : colorsSource;
2299
+ colorsArray = colorsArray instanceof Array ? colorsArray : [];
2300
+ const colorsCount = colorsArray.length;
2301
+ const { lightSteps } = isPalette ? colorsSource : { lightSteps: null };
2302
+ const fit = lightSteps || [9, 10].find((x) => colorsCount > x * 2 && !(colorsCount % x)) || 5;
2303
+ const isMultiLine = isOptionsMenu && colorsCount > fit;
2304
+ let rowCountHover = 2;
2305
+ rowCountHover = isMultiLine && colorsCount >= fit * 2 ? 3 : rowCountHover;
2306
+ rowCountHover = colorsCount >= fit * 3 ? 4 : rowCountHover;
2307
+ rowCountHover = colorsCount >= fit * 4 ? 5 : rowCountHover;
2308
+ const rowCount = rowCountHover - (colorsCount < fit * 3 ? 1 : 2);
2309
+ const isScrollable = isMultiLine && colorsCount > rowCount * fit;
2310
+ let finalClass = menuClass;
2311
+ finalClass += isScrollable ? ' scrollable' : '';
2312
+ finalClass += isMultiLine ? ' multiline' : '';
2313
+ const gap = isMultiLine ? '1px' : '0.25rem';
2314
+ let optionSize = isMultiLine ? 1.75 : 2;
2315
+ optionSize = fit > 5 && isMultiLine ? 1.5 : optionSize;
2316
+ const menuHeight = `${(rowCount || 1) * optionSize}rem`;
2317
+ const menuHeightHover = `calc(${rowCountHover} * ${optionSize}rem + ${rowCountHover - 1} * ${gap})`;
2318
+
2319
+ const menu = createElement({
2320
+ tagName: 'ul',
2321
+ className: finalClass,
2322
+ });
2323
+ setAttribute(menu, 'role', 'listbox');
2324
+ setAttribute(menu, ariaLabel, menuLabel);
2325
+
2326
+ if (isScrollable) { // @ts-ignore
2327
+ setCSSProperties(menu, {
2328
+ '--grid-item-size': `${optionSize}rem`,
2329
+ '--grid-fit': fit,
2330
+ '--grid-gap': gap,
2331
+ '--grid-height': menuHeight,
2332
+ '--grid-hover-height': menuHeightHover,
2333
+ });
2334
+ }
2335
+
2336
+ colorsArray.forEach((x) => {
2337
+ const [value, label] = x.trim().split(':');
2338
+ const xRealColor = new Color(value, format).toString();
2339
+ const isActive = xRealColor === getAttribute(input, 'value');
2340
+ const active = isActive ? ' active' : '';
2341
+
2342
+ const option = createElement({
2343
+ tagName: 'li',
2344
+ className: `color-option${active}`,
2345
+ innerText: `${label || x}`,
2346
+ });
2347
+
2348
+ setAttribute(option, tabIndex, '0');
2349
+ setAttribute(option, 'data-value', `${value}`);
2350
+ setAttribute(option, 'role', 'option');
2351
+ setAttribute(option, ariaSelected, isActive ? 'true' : 'false');
2352
+
2353
+ if (isOptionsMenu) {
2354
+ setElementStyle(option, { backgroundColor: x });
2355
+ }
2356
+
2357
+ menu.append(option);
2358
+ });
2359
+ return menu;
2360
+ }
2361
+
2362
+ /**
2363
+ * Check if a string is valid JSON string.
2364
+ * @param {string} str the string input
2365
+ * @returns {boolean} the query result
2366
+ */
2367
+ function isValidJSON(str) {
2368
+ try {
2369
+ JSON.parse(str);
2370
+ } catch (e) {
2371
+ return false;
2372
+ }
2373
+ return true;
2374
+ }
2375
+
2376
+ var version = "0.0.1";
2377
+
2378
+ // @ts-ignore
2379
+
2380
+ const Version = version;
2381
+
2382
+ // ColorPicker GC
2383
+ // ==============
2384
+ const colorPickerString = 'color-picker';
2385
+ const colorPickerSelector = `[data-function="${colorPickerString}"]`;
2386
+ const colorPickerParentSelector = `.${colorPickerString},${colorPickerString}`;
2387
+ const colorPickerDefaults = {
2388
+ componentLabels: colorPickerLabels,
2389
+ colorLabels: colorNames,
2390
+ format: 'rgb',
2391
+ colorPresets: false,
2392
+ colorKeywords: false,
2393
+ };
2394
+
2395
+ // ColorPicker Static Methods
2396
+ // ==========================
2397
+
2398
+ /** @type {CP.GetInstance<ColorPicker>} */
2399
+ const getColorPickerInstance = (element) => getInstance(element, colorPickerString);
2400
+
2401
+ /** @type {CP.InitCallback<ColorPicker>} */
2402
+ const initColorPicker = (element) => new ColorPicker(element);
2403
+
2404
+ // ColorPicker Private Methods
2405
+ // ===========================
2406
+
2407
+ /**
2408
+ * Generate HTML markup and update instance properties.
2409
+ * @param {ColorPicker} self
2410
+ */
2411
+ function initCallback(self) {
2412
+ const {
2413
+ input, parent, format, id, componentLabels, colorKeywords, colorPresets,
2414
+ } = self;
2415
+ const colorValue = getAttribute(input, 'value') || '#fff';
2416
+
2417
+ const {
2418
+ toggleLabel, pickerLabel, formatLabel, hexLabel,
2419
+ } = componentLabels;
2420
+
2421
+ // update color
2422
+ const color = nonColors.includes(colorValue) ? '#fff' : colorValue;
2423
+ self.color = new Color(color, format);
2424
+
2425
+ // set initial controls dimensions
2426
+ // make the controls smaller on mobile
2427
+ const dropClass = isMobile ? ' mobile' : '';
2428
+ const formatString = format === 'hex' ? hexLabel : format.toUpperCase();
2429
+
2430
+ const pickerBtn = createElement({
2431
+ id: `picker-btn-${id}`,
2432
+ tagName: 'button',
2433
+ className: 'picker-toggle btn-appearance',
2434
+ });
2435
+ setAttribute(pickerBtn, ariaExpanded, 'false');
2436
+ setAttribute(pickerBtn, ariaHasPopup, 'true');
2437
+ pickerBtn.append(createElement({
2438
+ tagName: 'span',
2439
+ className: vHidden,
2440
+ innerText: `${pickerLabel}. ${formatLabel}: ${formatString}`,
2441
+ }));
2442
+
2443
+ const pickerDropdown = createElement({
2444
+ tagName: 'div',
2445
+ className: `color-dropdown picker${dropClass}`,
2446
+ });
2447
+ setAttribute(pickerDropdown, ariaLabelledBy, `picker-btn-${id}`);
2448
+ setAttribute(pickerDropdown, 'role', 'group');
2449
+
2450
+ const colorControls = getColorControls(self);
2451
+ const colorForm = getColorForm(self);
2452
+
2453
+ pickerDropdown.append(colorControls, colorForm);
2454
+ input.before(pickerBtn);
2455
+ parent.append(pickerDropdown);
2456
+
2457
+ // set colour key menu template
2458
+ if (colorKeywords || colorPresets) {
2459
+ const presetsDropdown = createElement({
2460
+ tagName: 'div',
2461
+ className: `color-dropdown scrollable menu${dropClass}`,
2462
+ });
2463
+
2464
+ // color presets
2465
+ if ((colorPresets instanceof Array && colorPresets.length)
2466
+ || (colorPresets instanceof ColorPalette && colorPresets.colors)) {
2467
+ const presetsMenu = getColorMenu(self, colorPresets, 'color-options');
2468
+ presetsDropdown.append(presetsMenu);
2469
+ }
2470
+
2471
+ // explicit defaults [reset, initial, inherit, transparent, currentColor]
2472
+ if (colorKeywords && colorKeywords.length) {
2473
+ const keywordsMenu = getColorMenu(self, colorKeywords, 'color-defaults');
2474
+ presetsDropdown.append(keywordsMenu);
2475
+ }
2476
+
2477
+ const presetsBtn = createElement({
2478
+ tagName: 'button',
2479
+ className: 'menu-toggle btn-appearance',
2480
+ });
2481
+ setAttribute(presetsBtn, tabIndex, '-1');
2482
+ setAttribute(presetsBtn, ariaExpanded, 'false');
2483
+ setAttribute(presetsBtn, ariaHasPopup, 'true');
2484
+
2485
+ const xmlns = encodeURI('http://www.w3.org/2000/svg');
2486
+ const presetsIcon = createElementNS(xmlns, { tagName: 'svg' });
2487
+ setAttribute(presetsIcon, 'xmlns', xmlns);
2488
+ setAttribute(presetsIcon, 'viewBox', '0 0 512 512');
2489
+ setAttribute(presetsIcon, ariaHidden, 'true');
2490
+
2491
+ const path = createElementNS(xmlns, { tagName: 'path' });
2492
+ setAttribute(path, 'd', 'M98,158l157,156L411,158l27,27L255,368L71,185L98,158z');
2493
+ setAttribute(path, 'fill', '#fff');
2494
+ presetsIcon.append(path);
2495
+ presetsBtn.append(createElement({
2496
+ tagName: 'span',
2497
+ className: vHidden,
2498
+ innerText: `${toggleLabel}`,
2499
+ }), presetsIcon);
2500
+
2501
+ parent.append(presetsBtn, presetsDropdown);
2502
+ }
2503
+
2504
+ // solve non-colors after settings save
2505
+ if (colorKeywords && nonColors.includes(colorValue)) {
2506
+ self.value = colorValue;
2507
+ }
2508
+ setAttribute(input, tabIndex, '-1');
2509
+ }
2510
+
2511
+ /**
2512
+ * Add / remove `ColorPicker` main event listeners.
2513
+ * @param {ColorPicker} self
2514
+ * @param {boolean=} action
2515
+ */
2516
+ function toggleEvents(self, action) {
2517
+ const fn = action ? addListener : removeListener;
2518
+ const { input, pickerToggle, menuToggle } = self;
2519
+
2520
+ fn(input, focusinEvent, self.showPicker);
2521
+ fn(pickerToggle, mouseclickEvent, self.togglePicker);
2522
+
2523
+ fn(input, keydownEvent, self.keyToggle);
2524
+
2525
+ if (menuToggle) {
2526
+ fn(menuToggle, mouseclickEvent, self.toggleMenu);
2527
+ }
2528
+ }
2529
+
2530
+ /**
2531
+ * Add / remove `ColorPicker` event listeners active only when open.
2532
+ * @param {ColorPicker} self
2533
+ * @param {boolean=} action
2534
+ */
2535
+ function toggleEventsOnShown(self, action) {
2536
+ const fn = action ? addListener : removeListener;
2537
+ const { input, colorMenu, parent } = self;
2538
+ const doc = getDocument(input);
2539
+ const win = getWindow(input);
2540
+ const pointerEvents = `on${touchstartEvent}` in doc
2541
+ ? { down: touchstartEvent, move: touchmoveEvent, up: touchendEvent }
2542
+ : { down: mousedownEvent, move: mousemoveEvent, up: mouseupEvent };
2543
+
2544
+ fn(self.controls, pointerEvents.down, self.pointerDown);
2545
+ self.controlKnobs.forEach((x) => fn(x, keydownEvent, self.handleKnobs));
2546
+
2547
+ // @ts-ignore -- this is `Window`
2548
+ fn(win, scrollEvent, self.handleScroll);
2549
+ // @ts-ignore -- this is `Window`
2550
+ fn(win, resizeEvent, self.update);
2551
+
2552
+ [input, ...self.inputs].forEach((x) => fn(x, changeEvent, self.changeHandler));
2553
+
2554
+ if (colorMenu) {
2555
+ fn(colorMenu, mouseclickEvent, self.menuClickHandler);
2556
+ fn(colorMenu, keydownEvent, self.menuKeyHandler);
2557
+ }
2558
+
2559
+ fn(doc, pointerEvents.move, self.pointerMove);
2560
+ fn(doc, pointerEvents.up, self.pointerUp);
2561
+ fn(parent, focusoutEvent, self.handleFocusOut);
2562
+ // @ts-ignore -- this is `Window`
2563
+ fn(win, keyupEvent, self.handleDismiss);
2564
+ }
2565
+
2566
+ /**
2567
+ * Triggers the `ColorPicker` original event.
2568
+ * @param {ColorPicker} self
2569
+ */
2570
+ function firePickerChange(self) {
2571
+ dispatchEvent(self.input, new CustomEvent('colorpicker.change'));
2572
+ }
2573
+
2574
+ /**
2575
+ * Hides a visible dropdown.
2576
+ * @param {HTMLElement} element
2577
+ * @returns {void}
2578
+ */
2579
+ function removePosition(element) {
2580
+ if (element) {
2581
+ ['bottom', 'top'].forEach((x) => removeClass(element, x));
2582
+ }
2583
+ }
2584
+
2585
+ /**
2586
+ * Shows a `ColorPicker` dropdown and close the curent open dropdown.
2587
+ * @param {ColorPicker} self
2588
+ * @param {HTMLElement | Element} dropdown
2589
+ */
2590
+ function showDropdown(self, dropdown) {
2591
+ const {
2592
+ colorPicker, colorMenu, menuToggle, pickerToggle, parent,
2593
+ } = self;
2594
+ const isPicker = dropdown === colorPicker;
2595
+ const openDropdown = isPicker ? colorMenu : colorPicker;
2596
+ const activeBtn = isPicker ? menuToggle : pickerToggle;
2597
+ const nextBtn = !isPicker ? menuToggle : pickerToggle;
2598
+
2599
+ if (!hasClass(parent, 'open')) {
2600
+ addClass(parent, 'open');
2601
+ }
2602
+ if (openDropdown) {
2603
+ removeClass(openDropdown, 'show');
2604
+ removePosition(openDropdown);
2605
+ }
2606
+ addClass(dropdown, 'bottom');
2607
+ reflow(dropdown);
2608
+ addClass(dropdown, 'show');
2609
+
2610
+ if (isPicker) self.update();
2611
+
2612
+ if (!self.isOpen) {
2613
+ toggleEventsOnShown(self, true);
2614
+ self.updateDropdownPosition();
2615
+ self.isOpen = true;
2616
+ setAttribute(self.input, tabIndex, '0');
2617
+ if (menuToggle) {
2618
+ setAttribute(menuToggle, tabIndex, '0');
2619
+ }
2620
+ }
2621
+
2622
+ setAttribute(nextBtn, ariaExpanded, 'true');
2623
+ if (activeBtn) {
2624
+ setAttribute(activeBtn, ariaExpanded, 'false');
2625
+ }
2626
+ }
2627
+
2628
+ /**
2629
+ * Color Picker Web Component
2630
+ * @see http://thednp.github.io/color-picker
2631
+ */
2632
+ class ColorPicker {
2633
+ /**
2634
+ * Returns a new `ColorPicker` instance. The target of this constructor
2635
+ * must be an `HTMLInputElement`.
2636
+ *
2637
+ * @param {HTMLInputElement | string} target the target `<input>` element
2638
+ * @param {CP.ColorPickerOptions=} config instance options
2639
+ */
2640
+ constructor(target, config) {
2641
+ const self = this;
2642
+ /** @type {HTMLInputElement} */
2643
+ // @ts-ignore
2644
+ const input = querySelector(target);
2645
+
2646
+ // invalidate
2647
+ if (!input) throw new TypeError(`ColorPicker target ${target} cannot be found.`);
2648
+ self.input = input;
2649
+
2650
+ const parent = closest(input, colorPickerParentSelector);
2651
+ if (!parent) throw new TypeError('ColorPicker requires a specific markup to work.');
2652
+
2653
+ /** @type {HTMLElement} */
2654
+ // @ts-ignore
2655
+ self.parent = parent;
2656
+
2657
+ /** @type {number} */
2658
+ self.id = getUID(input, colorPickerString);
2659
+
2660
+ // set initial state
2661
+ /** @type {HTMLElement?} */
2662
+ self.dragElement = null;
2663
+ /** @type {boolean} */
2664
+ self.isOpen = false;
2665
+ /** @type {Record<string, number>} */
2666
+ self.controlPositions = {
2667
+ c1x: 0, c1y: 0, c2y: 0, c3y: 0,
2668
+ };
2669
+ /** @type {Record<string, string>} */
2670
+ self.colorLabels = {};
2671
+ /** @type {string[]=} */
2672
+ self.colorKeywords = undefined;
2673
+ /** @type {(ColorPalette | string[])=} */
2674
+ self.colorPresets = undefined;
2675
+
2676
+ // process options
2677
+ const {
2678
+ format, componentLabels, colorLabels, colorKeywords, colorPresets,
2679
+ } = normalizeOptions(this.isCE ? parent : input, colorPickerDefaults, config || {});
2680
+
2681
+ let translatedColorLabels = colorNames;
2682
+ if (colorLabels instanceof Array && colorLabels.length === 17) {
2683
+ translatedColorLabels = colorLabels;
2684
+ } else if (colorLabels && colorLabels.split(',').length === 17) {
2685
+ translatedColorLabels = colorLabels.split(',');
2686
+ }
2687
+
2688
+ // expose colour labels to all methods
2689
+ colorNames.forEach((c, i) => {
2690
+ self.colorLabels[c] = translatedColorLabels[i].trim();
2691
+ });
2692
+
2693
+ // update and expose component labels
2694
+ const tempLabels = ObjectAssign({}, colorPickerLabels);
2695
+ const jsonLabels = componentLabels && isValidJSON(componentLabels)
2696
+ ? JSON.parse(componentLabels) : componentLabels || {};
2697
+
2698
+ /** @type {Record<string, string>} */
2699
+ self.componentLabels = ObjectAssign(tempLabels, jsonLabels);
2700
+
2701
+ /** @type {Color} */
2702
+ self.color = new Color('white', format);
2703
+
2704
+ /** @type {CP.ColorFormats} */
2705
+ self.format = format;
2706
+
2707
+ // set colour defaults
2708
+ if (colorKeywords instanceof Array) {
2709
+ self.colorKeywords = colorKeywords;
2710
+ } else if (typeof colorKeywords === 'string' && colorKeywords.length) {
2711
+ self.colorKeywords = colorKeywords.split(',');
2712
+ }
2713
+
2714
+ // set colour presets
2715
+ if (colorPresets instanceof Array) {
2716
+ self.colorPresets = colorPresets;
2717
+ } else if (typeof colorPresets === 'string' && colorPresets.length) {
2718
+ if (isValidJSON(colorPresets)) {
2719
+ const { hue, hueSteps, lightSteps } = JSON.parse(colorPresets);
2720
+ self.colorPresets = new ColorPalette(hue, hueSteps, lightSteps);
2721
+ } else {
2722
+ self.colorPresets = colorPresets.split(',').map((x) => x.trim());
2723
+ }
2724
+ }
2725
+
2726
+ // bind events
2727
+ self.showPicker = self.showPicker.bind(self);
2728
+ self.togglePicker = self.togglePicker.bind(self);
2729
+ self.toggleMenu = self.toggleMenu.bind(self);
2730
+ self.menuClickHandler = self.menuClickHandler.bind(self);
2731
+ self.menuKeyHandler = self.menuKeyHandler.bind(self);
2732
+ self.pointerDown = self.pointerDown.bind(self);
2733
+ self.pointerMove = self.pointerMove.bind(self);
2734
+ self.pointerUp = self.pointerUp.bind(self);
2735
+ self.update = self.update.bind(self);
2736
+ self.handleScroll = self.handleScroll.bind(self);
2737
+ self.handleFocusOut = self.handleFocusOut.bind(self);
2738
+ self.changeHandler = self.changeHandler.bind(self);
2739
+ self.handleDismiss = self.handleDismiss.bind(self);
2740
+ self.keyToggle = self.keyToggle.bind(self);
2741
+ self.handleKnobs = self.handleKnobs.bind(self);
2742
+
2743
+ // generate markup
2744
+ initCallback(self);
2745
+
2746
+ const [colorPicker, colorMenu] = getElementsByClassName('color-dropdown', parent);
2747
+ // set main elements
2748
+ /** @type {HTMLElement} */
2749
+ // @ts-ignore
2750
+ self.pickerToggle = querySelector('.picker-toggle', parent);
2751
+ /** @type {HTMLElement} */
2752
+ // @ts-ignore
2753
+ self.menuToggle = querySelector('.menu-toggle', parent);
2754
+ /** @type {HTMLElement} */
2755
+ // @ts-ignore
2756
+ self.colorPicker = colorPicker;
2757
+ /** @type {HTMLElement} */
2758
+ // @ts-ignore
2759
+ self.colorMenu = colorMenu;
2760
+ /** @type {HTMLInputElement[]} */
2761
+ // @ts-ignore
2762
+ self.inputs = [...getElementsByClassName('color-input', parent)];
2763
+ const [controls] = getElementsByClassName('color-controls', parent);
2764
+ self.controls = controls;
2765
+ /** @type {(HTMLElement | Element)[]} */
2766
+ self.controlKnobs = [...getElementsByClassName('knob', controls)];
2767
+ /** @type {(HTMLElement)[]} */
2768
+ // @ts-ignore
2769
+ self.visuals = [...getElementsByClassName('visual-control', controls)];
2770
+
2771
+ // update colour picker controls, inputs and visuals
2772
+ self.update();
2773
+
2774
+ // add main events listeners
2775
+ toggleEvents(self, true);
2776
+
2777
+ // set component data
2778
+ Data.set(input, colorPickerString, self);
2779
+ }
2780
+
2781
+ /** Returns the current colour value */
2782
+ get value() { return this.input.value; }
2783
+
2784
+ /**
2785
+ * Sets a new colour value.
2786
+ * @param {string} v new colour value
2787
+ */
2788
+ set value(v) { this.input.value = v; }
2789
+
2790
+ /** Check if the colour presets include any non-colour. */
2791
+ get hasNonColor() {
2792
+ return this.colorKeywords instanceof Array
2793
+ && this.colorKeywords.some((x) => nonColors.includes(x));
2794
+ }
2795
+
2796
+ /** Check if the parent of the target is a `ColorPickerElement` instance. */
2797
+ get isCE() { return this.parent.localName === colorPickerString; }
2798
+
2799
+ /** Returns hexadecimal value of the current colour. */
2800
+ get hex() { return this.color.toHex(true); }
2801
+
2802
+ /** Returns the current colour value in {h,s,v,a} object format. */
2803
+ get hsv() { return this.color.toHsv(); }
2804
+
2805
+ /** Returns the current colour value in {h,s,l,a} object format. */
2806
+ get hsl() { return this.color.toHsl(); }
2807
+
2808
+ /** Returns the current colour value in {h,w,b,a} object format. */
2809
+ get hwb() { return this.color.toHwb(); }
2810
+
2811
+ /** Returns the current colour value in {r,g,b,a} object format. */
2812
+ get rgb() { return this.color.toRgb(); }
2813
+
2814
+ /** Returns the current colour brightness. */
2815
+ get brightness() { return this.color.brightness; }
2816
+
2817
+ /** Returns the current colour luminance. */
2818
+ get luminance() { return this.color.luminance; }
2819
+
2820
+ /** Checks if the current colour requires a light text colour. */
2821
+ get isDark() {
2822
+ const { color, brightness } = this;
2823
+ return brightness < 120 && color.a > 0.33;
2824
+ }
2825
+
2826
+ /** Checks if the current input value is a valid colour. */
2827
+ get isValid() {
2828
+ const inputValue = this.input.value;
2829
+ return inputValue !== '' && new Color(inputValue).isValid;
2830
+ }
2831
+
2832
+ /** Updates `ColorPicker` visuals. */
2833
+ updateVisuals() {
2834
+ const self = this;
2835
+ const {
2836
+ format, controlPositions, visuals,
2837
+ } = self;
2838
+ const [v1, v2, v3] = visuals;
2839
+ const { offsetWidth, offsetHeight } = v1;
2840
+ const hue = format === 'hsl'
2841
+ ? controlPositions.c1x / offsetWidth
2842
+ : controlPositions.c2y / offsetHeight;
2843
+ // @ts-ignore - `hslToRgb` is assigned to `Color` as static method
2844
+ const { r, g, b } = Color.hslToRgb(hue, 1, 0.5);
2845
+ const whiteGrad = 'linear-gradient(rgb(255,255,255) 0%, rgb(255,255,255) 100%)';
2846
+ const alpha = 1 - controlPositions.c3y / offsetHeight;
2847
+ const roundA = roundPart((alpha * 100)) / 100;
2848
+
2849
+ if (format !== 'hsl') {
2850
+ const fill = new Color({
2851
+ h: hue, s: 1, l: 0.5, a: alpha,
2852
+ }).toRgbString();
2853
+ const hueGradient = `linear-gradient(
2854
+ rgb(255,0,0) 0%, rgb(255,255,0) 16.67%,
2855
+ rgb(0,255,0) 33.33%, rgb(0,255,255) 50%,
2856
+ rgb(0,0,255) 66.67%, rgb(255,0,255) 83.33%,
2857
+ rgb(255,0,0) 100%)`;
2858
+ setElementStyle(v1, {
2859
+ background: `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,${roundA}) 100%),
2860
+ linear-gradient(to right, rgba(255,255,255,${roundA}) 0%, ${fill} 100%),
2861
+ ${whiteGrad}`,
2862
+ });
2863
+ setElementStyle(v2, { background: hueGradient });
2864
+ } else {
2865
+ const saturation = roundPart((controlPositions.c2y / offsetHeight) * 100);
2866
+ const fill0 = new Color({
2867
+ r: 255, g: 0, b: 0, a: alpha,
2868
+ }).saturate(-saturation).toRgbString();
2869
+ const fill1 = new Color({
2870
+ r: 255, g: 255, b: 0, a: alpha,
2871
+ }).saturate(-saturation).toRgbString();
2872
+ const fill2 = new Color({
2873
+ r: 0, g: 255, b: 0, a: alpha,
2874
+ }).saturate(-saturation).toRgbString();
2875
+ const fill3 = new Color({
2876
+ r: 0, g: 255, b: 255, a: alpha,
2877
+ }).saturate(-saturation).toRgbString();
2878
+ const fill4 = new Color({
2879
+ r: 0, g: 0, b: 255, a: alpha,
2880
+ }).saturate(-saturation).toRgbString();
2881
+ const fill5 = new Color({
2882
+ r: 255, g: 0, b: 255, a: alpha,
2883
+ }).saturate(-saturation).toRgbString();
2884
+ const fill6 = new Color({
2885
+ r: 255, g: 0, b: 0, a: alpha,
2886
+ }).saturate(-saturation).toRgbString();
2887
+ const fillGradient = `linear-gradient(to right,
2888
+ ${fill0} 0%, ${fill1} 16.67%, ${fill2} 33.33%, ${fill3} 50%,
2889
+ ${fill4} 66.67%, ${fill5} 83.33%, ${fill6} 100%)`;
2890
+ const lightGrad = `linear-gradient(rgba(255,255,255,${roundA}) 0%, rgba(255,255,255,0) 50%),
2891
+ linear-gradient(rgba(0,0,0,0) 50%, rgba(0,0,0,${roundA}) 100%)`;
2892
+
2893
+ setElementStyle(v1, { background: `${lightGrad},${fillGradient},${whiteGrad}` });
2894
+ const {
2895
+ r: gr, g: gg, b: gb,
2896
+ } = new Color({ r, g, b }).greyscale().toRgb();
2897
+
2898
+ setElementStyle(v2, {
2899
+ background: `linear-gradient(rgb(${r},${g},${b}) 0%, rgb(${gr},${gg},${gb}) 100%)`,
2900
+ });
2901
+ }
2902
+ setElementStyle(v3, {
2903
+ background: `linear-gradient(rgba(${r},${g},${b},1) 0%,rgba(${r},${g},${b},0) 100%)`,
2904
+ });
2905
+ }
2906
+
2907
+ /**
2908
+ * The `ColorPicker` *focusout* event listener when open.
2909
+ * @param {FocusEvent} e
2910
+ * @this {ColorPicker}
2911
+ */
2912
+ handleFocusOut({ relatedTarget }) {
2913
+ // @ts-ignore
2914
+ if (relatedTarget && !this.parent.contains(relatedTarget)) {
2915
+ this.hide(true);
2916
+ }
2917
+ }
2918
+
2919
+ /**
2920
+ * The `ColorPicker` *keyup* event listener when open.
2921
+ * @param {KeyboardEvent} e
2922
+ * @this {ColorPicker}
2923
+ */
2924
+ handleDismiss({ code }) {
2925
+ const self = this;
2926
+ if (self.isOpen && code === keyEscape) {
2927
+ self.hide();
2928
+ }
2929
+ }
2930
+
2931
+ /**
2932
+ * The `ColorPicker` *scroll* event listener when open.
2933
+ * @param {Event} e
2934
+ * @this {ColorPicker}
2935
+ */
2936
+ handleScroll(e) {
2937
+ const self = this;
2938
+ const { activeElement } = getDocument(self.input);
2939
+
2940
+ if ((isMobile && self.dragElement)
2941
+ || (activeElement && self.controlKnobs.includes(activeElement))) {
2942
+ e.stopPropagation();
2943
+ e.preventDefault();
2944
+ }
2945
+
2946
+ self.updateDropdownPosition();
2947
+ }
2948
+
2949
+ /**
2950
+ * The `ColorPicker` keyboard event listener for menu navigation.
2951
+ * @param {KeyboardEvent} e
2952
+ * @this {ColorPicker}
2953
+ */
2954
+ menuKeyHandler(e) {
2955
+ const { target, code } = e;
2956
+ // @ts-ignore
2957
+ const { previousElementSibling, nextElementSibling, parentElement } = target;
2958
+ const isColorOptionsMenu = parentElement && hasClass(parentElement, 'color-options');
2959
+ const allSiblings = [...parentElement.children];
2960
+ const columnsCount = isColorOptionsMenu
2961
+ && getElementStyle(parentElement, 'grid-template-columns').split(' ').length;
2962
+ const currentIndex = allSiblings.indexOf(target);
2963
+ const previousElement = currentIndex > -1
2964
+ && columnsCount && allSiblings[currentIndex - columnsCount];
2965
+ const nextElement = currentIndex > -1
2966
+ && columnsCount && allSiblings[currentIndex + columnsCount];
2967
+
2968
+ if ([keyArrowDown, keyArrowUp, keySpace].includes(code)) {
2969
+ // prevent scroll when navigating the menu via arrow keys / Space
2970
+ e.preventDefault();
2971
+ }
2972
+ if (isColorOptionsMenu) {
2973
+ if (previousElement && code === keyArrowUp) {
2974
+ focus(previousElement);
2975
+ } else if (nextElement && code === keyArrowDown) {
2976
+ focus(nextElement);
2977
+ } else if (previousElementSibling && code === keyArrowLeft) {
2978
+ focus(previousElementSibling);
2979
+ } else if (nextElementSibling && code === keyArrowRight) {
2980
+ focus(nextElementSibling);
2981
+ }
2982
+ } else if (previousElementSibling && [keyArrowLeft, keyArrowUp].includes(code)) {
2983
+ focus(previousElementSibling);
2984
+ } else if (nextElementSibling && [keyArrowRight, keyArrowDown].includes(code)) {
2985
+ focus(nextElementSibling);
2986
+ }
2987
+
2988
+ if ([keyEnter, keySpace].includes(code)) {
2989
+ this.menuClickHandler({ target });
2990
+ }
2991
+ }
2992
+
2993
+ /**
2994
+ * The `ColorPicker` click event listener for the colour menu presets / defaults.
2995
+ * @param {Partial<Event>} e
2996
+ * @this {ColorPicker}
2997
+ */
2998
+ menuClickHandler(e) {
2999
+ const self = this;
3000
+ /** @type {*} */
3001
+ const { target } = e;
3002
+ const { colorMenu } = self;
3003
+ const newOption = (getAttribute(target, 'data-value') || '').trim();
3004
+ // invalidate for targets other than color options
3005
+ if (!newOption.length) return;
3006
+ const currentActive = querySelector('li.active', colorMenu);
3007
+ let newColor = nonColors.includes(newOption) ? 'white' : newOption;
3008
+ newColor = newOption === 'transparent' ? 'rgba(0,0,0,0)' : newOption;
3009
+
3010
+ const {
3011
+ r, g, b, a,
3012
+ } = new Color(newColor);
3013
+
3014
+ ObjectAssign(self.color, {
3015
+ r, g, b, a,
3016
+ });
3017
+
3018
+ self.update();
3019
+
3020
+ if (currentActive !== target) {
3021
+ if (currentActive) {
3022
+ removeClass(currentActive, 'active');
3023
+ removeAttribute(currentActive, ariaSelected);
3024
+ }
3025
+
3026
+ addClass(target, 'active');
3027
+ setAttribute(target, ariaSelected, 'true');
3028
+
3029
+ if (nonColors.includes(newOption)) {
3030
+ self.value = newOption;
3031
+ }
3032
+ firePickerChange(self);
3033
+ }
3034
+ }
3035
+
3036
+ /**
3037
+ * The `ColorPicker` *touchstart* / *mousedown* events listener for control knobs.
3038
+ * @param {TouchEvent} e
3039
+ * @this {ColorPicker}
3040
+ */
3041
+ pointerDown(e) {
3042
+ const self = this;
3043
+ /** @type {*} */
3044
+ const {
3045
+ type, target, touches, pageX, pageY,
3046
+ } = e;
3047
+ const { colorMenu, visuals, controlKnobs } = self;
3048
+ const [v1, v2, v3] = visuals;
3049
+ const [c1, c2, c3] = controlKnobs;
3050
+ /** @type {HTMLElement} */
3051
+ const visual = hasClass(target, 'visual-control')
3052
+ ? target : querySelector('.visual-control', target.parentElement);
3053
+ const visualRect = getBoundingClientRect(visual);
3054
+ const X = type === 'touchstart' ? touches[0].pageX : pageX;
3055
+ const Y = type === 'touchstart' ? touches[0].pageY : pageY;
3056
+ const offsetX = X - window.pageXOffset - visualRect.left;
3057
+ const offsetY = Y - window.pageYOffset - visualRect.top;
3058
+
3059
+ if (target === v1 || target === c1) {
3060
+ self.dragElement = visual;
3061
+ self.changeControl1(offsetX, offsetY);
3062
+ } else if (target === v2 || target === c2) {
3063
+ self.dragElement = visual;
3064
+ self.changeControl2(offsetY);
3065
+ } else if (target === v3 || target === c3) {
3066
+ self.dragElement = visual;
3067
+ self.changeAlpha(offsetY);
3068
+ }
3069
+
3070
+ if (colorMenu) {
3071
+ const currentActive = querySelector('li.active', colorMenu);
3072
+ if (currentActive) {
3073
+ removeClass(currentActive, 'active');
3074
+ removeAttribute(currentActive, ariaSelected);
3075
+ }
3076
+ }
3077
+ e.preventDefault();
3078
+ }
3079
+
3080
+ /**
3081
+ * The `ColorPicker` *touchend* / *mouseup* events listener for control knobs.
3082
+ * @param {TouchEvent} e
3083
+ * @this {ColorPicker}
3084
+ */
3085
+ pointerUp({ target }) {
3086
+ const self = this;
3087
+ const { parent } = self;
3088
+ const doc = getDocument(parent);
3089
+ const currentOpen = querySelector(`${colorPickerParentSelector}.open`, doc) !== null;
3090
+ const selection = doc.getSelection();
3091
+ // @ts-ignore
3092
+ if (!self.dragElement && !selection.toString().length
3093
+ // @ts-ignore
3094
+ && !parent.contains(target)) {
3095
+ self.hide(currentOpen);
3096
+ }
3097
+
3098
+ self.dragElement = null;
3099
+ }
3100
+
3101
+ /**
3102
+ * The `ColorPicker` *touchmove* / *mousemove* events listener for control knobs.
3103
+ * @param {TouchEvent} e
3104
+ */
3105
+ pointerMove(e) {
3106
+ const self = this;
3107
+ const { dragElement, visuals } = self;
3108
+ const [v1, v2, v3] = visuals;
3109
+ const {
3110
+ // @ts-ignore
3111
+ type, touches, pageX, pageY,
3112
+ } = e;
3113
+
3114
+ if (!dragElement) return;
3115
+
3116
+ const controlRect = getBoundingClientRect(dragElement);
3117
+ const X = type === 'touchmove' ? touches[0].pageX : pageX;
3118
+ const Y = type === 'touchmove' ? touches[0].pageY : pageY;
3119
+ const offsetX = X - window.pageXOffset - controlRect.left;
3120
+ const offsetY = Y - window.pageYOffset - controlRect.top;
3121
+
3122
+ if (dragElement === v1) {
3123
+ self.changeControl1(offsetX, offsetY);
3124
+ }
3125
+
3126
+ if (dragElement === v2) {
3127
+ self.changeControl2(offsetY);
3128
+ }
3129
+
3130
+ if (dragElement === v3) {
3131
+ self.changeAlpha(offsetY);
3132
+ }
3133
+ }
3134
+
3135
+ /**
3136
+ * The `ColorPicker` *keydown* event listener for control knobs.
3137
+ * @param {KeyboardEvent} e
3138
+ */
3139
+ handleKnobs(e) {
3140
+ const { target, code } = e;
3141
+ const self = this;
3142
+
3143
+ // only react to arrow buttons
3144
+ if (![keyArrowUp, keyArrowDown, keyArrowLeft, keyArrowRight].includes(code)) return;
3145
+ e.preventDefault();
3146
+
3147
+ const { format, controlKnobs, visuals } = self;
3148
+ const { offsetWidth, offsetHeight } = visuals[0];
3149
+ const [c1, c2, c3] = controlKnobs;
3150
+ const { activeElement } = getDocument(c1);
3151
+ const currentKnob = controlKnobs.find((x) => x === activeElement);
3152
+ const yRatio = offsetHeight / (format === 'hsl' ? 100 : 360);
3153
+
3154
+ if (currentKnob) {
3155
+ let offsetX = 0;
3156
+ let offsetY = 0;
3157
+
3158
+ if (target === c1) {
3159
+ const xRatio = offsetWidth / (format === 'hsl' ? 360 : 100);
3160
+
3161
+ if ([keyArrowLeft, keyArrowRight].includes(code)) {
3162
+ self.controlPositions.c1x += code === keyArrowRight ? xRatio : -xRatio;
3163
+ } else if ([keyArrowUp, keyArrowDown].includes(code)) {
3164
+ self.controlPositions.c1y += code === keyArrowDown ? yRatio : -yRatio;
3165
+ }
3166
+
3167
+ offsetX = self.controlPositions.c1x;
3168
+ offsetY = self.controlPositions.c1y;
3169
+ self.changeControl1(offsetX, offsetY);
3170
+ } else if (target === c2) {
3171
+ self.controlPositions.c2y += [keyArrowDown, keyArrowRight].includes(code)
3172
+ ? yRatio
3173
+ : -yRatio;
3174
+
3175
+ offsetY = self.controlPositions.c2y;
3176
+ self.changeControl2(offsetY);
3177
+ } else if (target === c3) {
3178
+ self.controlPositions.c3y += [keyArrowDown, keyArrowRight].includes(code)
3179
+ ? yRatio
3180
+ : -yRatio;
3181
+
3182
+ offsetY = self.controlPositions.c3y;
3183
+ self.changeAlpha(offsetY);
3184
+ }
3185
+ self.handleScroll(e);
3186
+ }
3187
+ }
3188
+
3189
+ /** The event listener of the colour form inputs. */
3190
+ changeHandler() {
3191
+ const self = this;
3192
+ let colorSource;
3193
+ const {
3194
+ inputs, format, value: currentValue, input, controlPositions, visuals,
3195
+ } = self;
3196
+ /** @type {*} */
3197
+ const { activeElement } = getDocument(input);
3198
+ const { offsetHeight } = visuals[0];
3199
+ const [i1,,, i4] = inputs;
3200
+ const [v1, v2, v3, v4] = format === 'rgb'
3201
+ ? inputs.map((i) => parseFloat(i.value) / (i === i4 ? 100 : 1))
3202
+ : inputs.map((i) => parseFloat(i.value) / (i !== i1 ? 100 : 360));
3203
+ const isNonColorValue = self.hasNonColor && nonColors.includes(currentValue);
3204
+ const alpha = i4 ? v4 : (1 - controlPositions.c3y / offsetHeight);
3205
+
3206
+ if (activeElement === input || (activeElement && inputs.includes(activeElement))) {
3207
+ if (activeElement === input) {
3208
+ if (isNonColorValue) {
3209
+ colorSource = 'white';
3210
+ } else {
3211
+ colorSource = currentValue;
3212
+ }
3213
+ } else if (format === 'hex') {
3214
+ colorSource = i1.value;
3215
+ } else if (format === 'hsl') {
3216
+ colorSource = {
3217
+ h: v1, s: v2, l: v3, a: alpha,
3218
+ };
3219
+ } else if (format === 'hwb') {
3220
+ colorSource = {
3221
+ h: v1, w: v2, b: v3, a: alpha,
3222
+ };
3223
+ } else {
3224
+ colorSource = {
3225
+ r: v1, g: v2, b: v3, a: alpha,
3226
+ };
3227
+ }
3228
+
3229
+ const {
3230
+ r, g, b, a,
3231
+ } = new Color(colorSource);
3232
+
3233
+ ObjectAssign(self.color, {
3234
+ r, g, b, a,
3235
+ });
3236
+ self.setControlPositions();
3237
+ self.updateAppearance();
3238
+ self.updateInputs();
3239
+ self.updateControls();
3240
+ self.updateVisuals();
3241
+
3242
+ // set non-color keyword
3243
+ if (activeElement === input && isNonColorValue) {
3244
+ self.value = currentValue;
3245
+ }
3246
+ }
3247
+ }
3248
+
3249
+ /**
3250
+ * Updates `ColorPicker` first control:
3251
+ * * `lightness` and `saturation` for HEX/RGB;
3252
+ * * `lightness` and `hue` for HSL.
3253
+ *
3254
+ * @param {number} X the X component of the offset
3255
+ * @param {number} Y the Y component of the offset
3256
+ */
3257
+ changeControl1(X, Y) {
3258
+ const self = this;
3259
+ let [offsetX, offsetY] = [0, 0];
3260
+ const {
3261
+ format, controlPositions, visuals,
3262
+ } = self;
3263
+ const { offsetHeight, offsetWidth } = visuals[0];
3264
+
3265
+ if (X > offsetWidth) offsetX = offsetWidth;
3266
+ else if (X >= 0) offsetX = X;
3267
+
3268
+ if (Y > offsetHeight) offsetY = offsetHeight;
3269
+ else if (Y >= 0) offsetY = Y;
3270
+
3271
+ const hue = format === 'hsl'
3272
+ ? offsetX / offsetWidth
3273
+ : controlPositions.c2y / offsetHeight;
3274
+
3275
+ const saturation = format === 'hsl'
3276
+ ? 1 - controlPositions.c2y / offsetHeight
3277
+ : offsetX / offsetWidth;
3278
+
3279
+ const lightness = 1 - offsetY / offsetHeight;
3280
+ const alpha = 1 - controlPositions.c3y / offsetHeight;
3281
+
3282
+ const colorObject = format === 'hsl'
3283
+ ? {
3284
+ h: hue, s: saturation, l: lightness, a: alpha,
3285
+ }
3286
+ : {
3287
+ h: hue, s: saturation, v: lightness, a: alpha,
3288
+ };
3289
+
3290
+ // new color
3291
+ const {
3292
+ r, g, b, a,
3293
+ } = new Color(colorObject);
3294
+
3295
+ ObjectAssign(self.color, {
3296
+ r, g, b, a,
3297
+ });
3298
+
3299
+ // new positions
3300
+ self.controlPositions.c1x = offsetX;
3301
+ self.controlPositions.c1y = offsetY;
3302
+
3303
+ // update color picker
3304
+ self.updateAppearance();
3305
+ self.updateInputs();
3306
+ self.updateControls();
3307
+ self.updateVisuals();
3308
+ }
3309
+
3310
+ /**
3311
+ * Updates `ColorPicker` second control:
3312
+ * * `hue` for HEX/RGB/HWB;
3313
+ * * `saturation` for HSL.
3314
+ *
3315
+ * @param {number} Y the Y offset
3316
+ */
3317
+ changeControl2(Y) {
3318
+ const self = this;
3319
+ const {
3320
+ format, controlPositions, visuals,
3321
+ } = self;
3322
+ const { offsetHeight, offsetWidth } = visuals[0];
3323
+
3324
+ let offsetY = 0;
3325
+
3326
+ if (Y > offsetHeight) offsetY = offsetHeight;
3327
+ else if (Y >= 0) offsetY = Y;
3328
+
3329
+ const hue = format === 'hsl'
3330
+ ? controlPositions.c1x / offsetWidth
3331
+ : offsetY / offsetHeight;
3332
+ const saturation = format === 'hsl'
3333
+ ? 1 - offsetY / offsetHeight
3334
+ : controlPositions.c1x / offsetWidth;
3335
+ const lightness = 1 - controlPositions.c1y / offsetHeight;
3336
+ const alpha = 1 - controlPositions.c3y / offsetHeight;
3337
+ const colorObject = format === 'hsl'
3338
+ ? {
3339
+ h: hue, s: saturation, l: lightness, a: alpha,
3340
+ }
3341
+ : {
3342
+ h: hue, s: saturation, v: lightness, a: alpha,
3343
+ };
3344
+
3345
+ // new color
3346
+ const {
3347
+ r, g, b, a,
3348
+ } = new Color(colorObject);
3349
+
3350
+ ObjectAssign(self.color, {
3351
+ r, g, b, a,
3352
+ });
3353
+
3354
+ // new position
3355
+ self.controlPositions.c2y = offsetY;
3356
+ // update color picker
3357
+ self.updateAppearance();
3358
+ self.updateInputs();
3359
+ self.updateControls();
3360
+ self.updateVisuals();
3361
+ }
3362
+
3363
+ /**
3364
+ * Updates `ColorPicker` last control,
3365
+ * the `alpha` channel.
3366
+ *
3367
+ * @param {number} Y
3368
+ */
3369
+ changeAlpha(Y) {
3370
+ const self = this;
3371
+ const { visuals } = self;
3372
+ const { offsetHeight } = visuals[0];
3373
+ let offsetY = 0;
3374
+
3375
+ if (Y > offsetHeight) offsetY = offsetHeight;
3376
+ else if (Y >= 0) offsetY = Y;
3377
+
3378
+ // update color alpha
3379
+ const alpha = 1 - offsetY / offsetHeight;
3380
+ self.color.setAlpha(alpha);
3381
+ // update position
3382
+ self.controlPositions.c3y = offsetY;
3383
+ // update color picker
3384
+ self.updateAppearance();
3385
+ self.updateInputs();
3386
+ self.updateControls();
3387
+ self.updateVisuals();
3388
+ }
3389
+
3390
+ /**
3391
+ * Updates `ColorPicker` control positions on:
3392
+ * * initialization
3393
+ * * window resize
3394
+ */
3395
+ update() {
3396
+ const self = this;
3397
+ self.updateDropdownPosition();
3398
+ self.updateAppearance();
3399
+ self.setControlPositions();
3400
+ self.updateInputs(true);
3401
+ self.updateControls();
3402
+ self.updateVisuals();
3403
+ }
3404
+
3405
+ /** Updates the open dropdown position on *scroll* event. */
3406
+ updateDropdownPosition() {
3407
+ const self = this;
3408
+ const { input, colorPicker, colorMenu } = self;
3409
+ const elRect = getBoundingClientRect(input);
3410
+ const { top, bottom } = elRect;
3411
+ const { offsetHeight: elHeight } = input;
3412
+ const windowHeight = getDocumentElement(input).clientHeight;
3413
+ const isPicker = hasClass(colorPicker, 'show');
3414
+ const dropdown = isPicker ? colorPicker : colorMenu;
3415
+ if (!dropdown) return;
3416
+ const { offsetHeight: dropHeight } = dropdown;
3417
+ const distanceBottom = windowHeight - bottom;
3418
+ const distanceTop = top;
3419
+ const bottomExceed = top + dropHeight + elHeight > windowHeight; // show
3420
+ const topExceed = top - dropHeight < 0; // show-top
3421
+
3422
+ if ((hasClass(dropdown, 'bottom') || !topExceed) && distanceBottom < distanceTop && bottomExceed) {
3423
+ removeClass(dropdown, 'bottom');
3424
+ addClass(dropdown, 'top');
3425
+ } else {
3426
+ removeClass(dropdown, 'top');
3427
+ addClass(dropdown, 'bottom');
3428
+ }
3429
+ }
3430
+
3431
+ /** Updates control knobs' positions. */
3432
+ setControlPositions() {
3433
+ const self = this;
3434
+ const {
3435
+ format, visuals, color, hsl, hsv,
3436
+ } = self;
3437
+ const { offsetHeight, offsetWidth } = visuals[0];
3438
+ const alpha = color.a;
3439
+ const hue = hsl.h;
3440
+
3441
+ const saturation = format !== 'hsl' ? hsv.s : hsl.s;
3442
+ const lightness = format !== 'hsl' ? hsv.v : hsl.l;
3443
+
3444
+ self.controlPositions.c1x = format !== 'hsl' ? saturation * offsetWidth : hue * offsetWidth;
3445
+ self.controlPositions.c1y = (1 - lightness) * offsetHeight;
3446
+ self.controlPositions.c2y = format !== 'hsl' ? hue * offsetHeight : (1 - saturation) * offsetHeight;
3447
+ self.controlPositions.c3y = (1 - alpha) * offsetHeight;
3448
+ }
3449
+
3450
+ /** Update the visual appearance label and control knob labels. */
3451
+ updateAppearance() {
3452
+ const self = this;
3453
+ const {
3454
+ componentLabels, colorLabels, color, parent,
3455
+ hsl, hsv, hex, format, controlKnobs,
3456
+ } = self;
3457
+ const {
3458
+ appearanceLabel, hexLabel, valueLabel,
3459
+ } = componentLabels;
3460
+ const { r, g, b } = color.toRgb();
3461
+ const [knob1, knob2, knob3] = controlKnobs;
3462
+ const hue = roundPart(hsl.h * 360);
3463
+ const alpha = color.a;
3464
+ const saturationSource = format === 'hsl' ? hsl.s : hsv.s;
3465
+ const saturation = roundPart(saturationSource * 100);
3466
+ const lightness = roundPart(hsl.l * 100);
3467
+ const hsvl = hsv.v * 100;
3468
+ let colorName;
3469
+
3470
+ // determine color appearance
3471
+ if (lightness === 100 && saturation === 0) {
3472
+ colorName = colorLabels.white;
3473
+ } else if (lightness === 0) {
3474
+ colorName = colorLabels.black;
3475
+ } else if (saturation === 0) {
3476
+ colorName = colorLabels.grey;
3477
+ } else if (hue < 15 || hue >= 345) {
3478
+ colorName = colorLabels.red;
3479
+ } else if (hue >= 15 && hue < 45) {
3480
+ colorName = hsvl > 80 && saturation > 80 ? colorLabels.orange : colorLabels.brown;
3481
+ } else if (hue >= 45 && hue < 75) {
3482
+ const isGold = hue > 46 && hue < 54 && hsvl < 80 && saturation > 90;
3483
+ const isOlive = hue >= 54 && hue < 75 && hsvl < 80;
3484
+ colorName = isGold ? colorLabels.gold : colorLabels.yellow;
3485
+ colorName = isOlive ? colorLabels.olive : colorName;
3486
+ } else if (hue >= 75 && hue < 155) {
3487
+ colorName = hsvl < 68 ? colorLabels.green : colorLabels.lime;
3488
+ } else if (hue >= 155 && hue < 175) {
3489
+ colorName = colorLabels.teal;
3490
+ } else if (hue >= 175 && hue < 195) {
3491
+ colorName = colorLabels.cyan;
3492
+ } else if (hue >= 195 && hue < 255) {
3493
+ colorName = colorLabels.blue;
3494
+ } else if (hue >= 255 && hue < 270) {
3495
+ colorName = colorLabels.violet;
3496
+ } else if (hue >= 270 && hue < 295) {
3497
+ colorName = colorLabels.magenta;
3498
+ } else if (hue >= 295 && hue < 345) {
3499
+ colorName = colorLabels.pink;
3500
+ }
3501
+
3502
+ let colorLabel = `${hexLabel} ${hex.split('').join(' ')}`;
3503
+
3504
+ if (format === 'hsl') {
3505
+ colorLabel = `HSL: ${hue}°, ${saturation}%, ${lightness}%`;
3506
+ setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3507
+ setAttribute(knob1, ariaValueText, `${hue}° & ${lightness}%`);
3508
+ setAttribute(knob1, ariaValueNow, `${hue}`);
3509
+ setAttribute(knob2, ariaValueText, `${saturation}%`);
3510
+ setAttribute(knob2, ariaValueNow, `${saturation}`);
3511
+ } else if (format === 'hwb') {
3512
+ const { hwb } = self;
3513
+ const whiteness = roundPart(hwb.w * 100);
3514
+ const blackness = roundPart(hwb.b * 100);
3515
+ colorLabel = `HWB: ${hue}°, ${whiteness}%, ${blackness}%`;
3516
+ setAttribute(knob1, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3517
+ setAttribute(knob1, ariaValueText, `${whiteness}% & ${blackness}%`);
3518
+ setAttribute(knob1, ariaValueNow, `${whiteness}`);
3519
+ setAttribute(knob2, ariaValueText, `${hue}%`);
3520
+ setAttribute(knob2, ariaValueNow, `${hue}`);
3521
+ } else {
3522
+ colorLabel = format === 'rgb' ? `RGB: ${r}, ${g}, ${b}` : colorLabel;
3523
+ setAttribute(knob2, ariaDescription, `${valueLabel}: ${colorLabel}. ${appearanceLabel}: ${colorName}.`);
3524
+ setAttribute(knob1, ariaValueText, `${lightness}% & ${saturation}%`);
3525
+ setAttribute(knob1, ariaValueNow, `${lightness}`);
3526
+ setAttribute(knob2, ariaValueText, `${hue}°`);
3527
+ setAttribute(knob2, ariaValueNow, `${hue}`);
3528
+ }
3529
+
3530
+ const alphaValue = roundPart(alpha * 100);
3531
+ setAttribute(knob3, ariaValueText, `${alphaValue}%`);
3532
+ setAttribute(knob3, ariaValueNow, `${alphaValue}`);
3533
+
3534
+ // update the input backgroundColor
3535
+ const newColor = color.toString();
3536
+ setElementStyle(self.input, { backgroundColor: newColor });
3537
+
3538
+ // toggle dark/light classes will also style the placeholder
3539
+ // dark sets color white, light sets color black
3540
+ // isDark ? '#000' : '#fff'
3541
+ if (!self.isDark) {
3542
+ if (hasClass(parent, 'txt-dark')) removeClass(parent, 'txt-dark');
3543
+ if (!hasClass(parent, 'txt-light')) addClass(parent, 'txt-light');
3544
+ } else {
3545
+ if (hasClass(parent, 'txt-light')) removeClass(parent, 'txt-light');
3546
+ if (!hasClass(parent, 'txt-dark')) addClass(parent, 'txt-dark');
3547
+ }
3548
+ }
3549
+
3550
+ /** Updates the control knobs actual positions. */
3551
+ updateControls() {
3552
+ const { controlKnobs, controlPositions } = this;
3553
+ let {
3554
+ c1x, c1y, c2y, c3y,
3555
+ } = controlPositions;
3556
+ const [control1, control2, control3] = controlKnobs;
3557
+ // round control positions
3558
+ [c1x, c1y, c2y, c3y] = [c1x, c1y, c2y, c3y].map(roundPart);
3559
+
3560
+ setElementStyle(control1, { transform: `translate3d(${c1x - 4}px,${c1y - 4}px,0)` });
3561
+ setElementStyle(control2, { transform: `translate3d(0,${c2y - 4}px,0)` });
3562
+ setElementStyle(control3, { transform: `translate3d(0,${c3y - 4}px,0)` });
3563
+ }
3564
+
3565
+ /**
3566
+ * Updates all color form inputs.
3567
+ * @param {boolean=} isPrevented when `true`, the component original event is prevented
3568
+ */
3569
+ updateInputs(isPrevented) {
3570
+ const self = this;
3571
+ const {
3572
+ value: oldColor, format, inputs, color, hsl,
3573
+ } = self;
3574
+ const [i1, i2, i3, i4] = inputs;
3575
+ const alpha = roundPart(color.a * 100);
3576
+ const hue = roundPart(hsl.h * 360);
3577
+ let newColor;
3578
+
3579
+ if (format === 'hex') {
3580
+ newColor = self.color.toHexString(true);
3581
+ i1.value = self.hex;
3582
+ } else if (format === 'hsl') {
3583
+ const lightness = roundPart(hsl.l * 100);
3584
+ const saturation = roundPart(hsl.s * 100);
3585
+ newColor = self.color.toHslString();
3586
+ i1.value = `${hue}`;
3587
+ i2.value = `${saturation}`;
3588
+ i3.value = `${lightness}`;
3589
+ i4.value = `${alpha}`;
3590
+ } else if (format === 'hwb') {
3591
+ const { w, b } = self.hwb;
3592
+ const whiteness = roundPart(w * 100);
3593
+ const blackness = roundPart(b * 100);
3594
+
3595
+ newColor = self.color.toHwbString();
3596
+ i1.value = `${hue}`;
3597
+ i2.value = `${whiteness}`;
3598
+ i3.value = `${blackness}`;
3599
+ i4.value = `${alpha}`;
3600
+ } else if (format === 'rgb') {
3601
+ let { r, g, b } = self.rgb;
3602
+ [r, g, b] = [r, g, b].map(roundPart);
3603
+
3604
+ newColor = self.color.toRgbString();
3605
+ i1.value = `${r}`;
3606
+ i2.value = `${g}`;
3607
+ i3.value = `${b}`;
3608
+ i4.value = `${alpha}`;
3609
+ }
3610
+
3611
+ // update the color value
3612
+ self.value = `${newColor}`;
3613
+
3614
+ // don't trigger the custom event unless it's really changed
3615
+ if (!isPrevented && newColor !== oldColor) {
3616
+ firePickerChange(self);
3617
+ }
3618
+ }
3619
+
3620
+ /**
3621
+ * The `Space` & `Enter` keys specific event listener.
3622
+ * Toggle visibility of the `ColorPicker` / the presets menu, showing one will hide the other.
3623
+ * @param {KeyboardEvent} e
3624
+ * @this {ColorPicker}
3625
+ */
3626
+ keyToggle(e) {
3627
+ const self = this;
3628
+ const { menuToggle } = self;
3629
+ const { activeElement } = getDocument(menuToggle);
3630
+ const { code } = e;
3631
+
3632
+ if ([keyEnter, keySpace].includes(code)) {
3633
+ if ((menuToggle && activeElement === menuToggle) || !activeElement) {
3634
+ e.preventDefault();
3635
+ if (!activeElement) {
3636
+ self.togglePicker(e);
3637
+ } else {
3638
+ self.toggleMenu();
3639
+ }
3640
+ }
3641
+ }
3642
+ }
3643
+
3644
+ /**
3645
+ * Toggle the `ColorPicker` dropdown visibility.
3646
+ * @param {Event} e
3647
+ * @this {ColorPicker}
3648
+ */
3649
+ togglePicker(e) {
3650
+ e.preventDefault();
3651
+ const self = this;
3652
+ const { colorPicker } = self;
3653
+
3654
+ if (self.isOpen && hasClass(colorPicker, 'show')) {
3655
+ self.hide(true);
3656
+ } else {
3657
+ showDropdown(self, colorPicker);
3658
+ }
3659
+ }
3660
+
3661
+ /** Shows the `ColorPicker` dropdown. */
3662
+ showPicker() {
3663
+ const self = this;
3664
+ const { colorPicker } = self;
3665
+
3666
+ if (!['top', 'bottom'].some((c) => hasClass(colorPicker, c))) {
3667
+ showDropdown(self, colorPicker);
3668
+ }
3669
+ }
3670
+
3671
+ /** Toggles the visibility of the `ColorPicker` presets menu. */
3672
+ toggleMenu() {
3673
+ const self = this;
3674
+ const { colorMenu } = self;
3675
+
3676
+ if (self.isOpen && hasClass(colorMenu, 'show')) {
3677
+ self.hide(true);
3678
+ } else {
3679
+ showDropdown(self, colorMenu);
3680
+ }
3681
+ }
3682
+
3683
+ /**
3684
+ * Hides the currently open `ColorPicker` dropdown.
3685
+ * @param {boolean=} focusPrevented
3686
+ */
3687
+ hide(focusPrevented) {
3688
+ const self = this;
3689
+ if (self.isOpen) {
3690
+ const {
3691
+ pickerToggle, menuToggle, colorPicker, colorMenu, parent, input,
3692
+ } = self;
3693
+ const openPicker = hasClass(colorPicker, 'show');
3694
+ const openDropdown = openPicker ? colorPicker : colorMenu;
3695
+ const relatedBtn = openPicker ? pickerToggle : menuToggle;
3696
+ const animationDuration = openDropdown && getElementTransitionDuration(openDropdown);
3697
+
3698
+ if (openDropdown) {
3699
+ removeClass(openDropdown, 'show');
3700
+ setAttribute(relatedBtn, ariaExpanded, 'false');
3701
+ setTimeout(() => {
3702
+ removePosition(openDropdown);
3703
+ if (!querySelector('.show', parent)) {
3704
+ removeClass(parent, 'open');
3705
+ toggleEventsOnShown(self);
3706
+ self.isOpen = false;
3707
+ }
3708
+ }, animationDuration);
3709
+ }
3710
+
3711
+ if (!self.isValid) {
3712
+ self.value = self.color.toString();
3713
+ }
3714
+ if (!focusPrevented) {
3715
+ focus(pickerToggle);
3716
+ }
3717
+ setAttribute(input, tabIndex, '-1');
3718
+ if (menuToggle) {
3719
+ setAttribute(menuToggle, tabIndex, '-1');
3720
+ }
3721
+ }
3722
+ }
3723
+
3724
+ /** Removes `ColorPicker` from target `<input>`. */
3725
+ dispose() {
3726
+ const self = this;
3727
+ const { input, parent } = self;
3728
+ self.hide(true);
3729
+ toggleEvents(self);
3730
+ [...parent.children].forEach((el) => {
3731
+ if (el !== input) el.remove();
3732
+ });
3733
+
3734
+ removeAttribute(input, tabIndex);
3735
+ setElementStyle(input, { backgroundColor: '' });
3736
+
3737
+ ['txt-light', 'txt-dark'].forEach((c) => removeClass(parent, c));
3738
+ Data.remove(input, colorPickerString);
3739
+ }
3740
+ }
3741
+
3742
+ ObjectAssign(ColorPicker, {
3743
+ Color,
3744
+ ColorPalette,
3745
+ Version,
3746
+ getInstance: getColorPickerInstance,
3747
+ init: initColorPicker,
3748
+ selector: colorPickerSelector,
3749
+ // utils important for render
3750
+ roundPart,
3751
+ setElementStyle,
3752
+ setAttribute,
3753
+ getBoundingClientRect,
3754
+ });
3755
+
3756
+ let CPID = 0;
3757
+
3758
+ /**
3759
+ * `ColorPickerElement` Web Component.
3760
+ * @example
3761
+ * <label for="UNIQUE_ID">Label</label>
3762
+ * <color-picker data-format="hex" data-value="#075">
3763
+ * <input id="UNIQUE_ID" type="text" class="color-preview btn-appearance">
3764
+ * </color-picker>
3765
+ */
3766
+ class ColorPickerElement extends HTMLElement {
3767
+ constructor() {
3768
+ super();
3769
+ /** @type {boolean} */
3770
+ this.isDisconnected = true;
3771
+ this.attachShadow({ mode: 'open' });
3772
+ }
3773
+
3774
+ /**
3775
+ * Returns the current color value.
3776
+ * @returns {string?}
3777
+ */
3778
+ get value() { return this.input ? this.input.value : null; }
3779
+
3780
+ connectedCallback() {
3781
+ if (this.colorPicker) {
3782
+ if (this.isDisconnected) {
3783
+ this.isDisconnected = false;
3784
+ }
3785
+ return;
3786
+ }
3787
+
3788
+ const inputs = getElementsByTagName('input', this);
3789
+
3790
+ if (!inputs.length) {
3791
+ const label = getAttribute(this, 'data-label');
3792
+ const value = getAttribute(this, 'data-value') || '#069';
3793
+ const format = getAttribute(this, 'data-format') || 'rgb';
3794
+ const newInput = createElement({
3795
+ tagName: 'input',
3796
+ type: 'text',
3797
+ className: 'color-preview btn-appearance',
3798
+ });
3799
+ let id = getAttribute(this, 'data-id');
3800
+ if (!id) {
3801
+ id = `color-picker-${format}-${CPID}`;
3802
+ CPID += 1;
3803
+ }
3804
+
3805
+ const labelElement = createElement({ tagName: 'label', innerText: label || 'Color Picker' });
3806
+ this.before(labelElement);
3807
+ setAttribute(labelElement, 'for', id);
3808
+ setAttribute(newInput, 'id', id);
3809
+ setAttribute(newInput, 'name', id);
3810
+ setAttribute(newInput, 'autocomplete', 'off');
3811
+ setAttribute(newInput, 'spellcheck', 'false');
3812
+ setAttribute(newInput, 'value', value);
3813
+ this.append(newInput);
3814
+ }
3815
+
3816
+ const [input] = inputs;
3817
+
3818
+ if (input) {
3819
+ /** @type {HTMLInputElement} */
3820
+ // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3821
+ this.input = input;
3822
+
3823
+ // @ts-ignore - `HTMLInputElement` is `HTMLElement`
3824
+ this.colorPicker = new ColorPicker(input);
3825
+ this.color = this.colorPicker.color;
3826
+
3827
+ if (this.shadowRoot) {
3828
+ this.shadowRoot.append(createElement('slot'));
3829
+ }
3830
+
3831
+ this.isDisconnected = false;
3832
+ }
3833
+ }
3834
+
3835
+ disconnectedCallback() {
3836
+ if (this.colorPicker) this.colorPicker.dispose();
3837
+ this.isDisconnected = true;
3838
+ }
3839
+ }
3840
+
3841
+ ObjectAssign(ColorPickerElement, {
3842
+ Color,
3843
+ ColorPicker,
3844
+ ColorPalette,
3845
+ getInstance: getColorPickerInstance,
3846
+ Version,
3847
+ });
3848
+
3849
+ customElements.define('color-picker', ColorPickerElement);
3850
+
3851
+ export default ColorPickerElement;