@thednp/color-picker 0.0.1-alpha1 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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;