kempo-ui 0.0.34 → 0.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ColorPicker.js +12 -3
- package/dist/components/ThemeSwitcher.js +2 -2
- package/dist/utils/context.js +1 -0
- package/dist/utils/theme.js +1 -0
- package/docs/components/color-picker.html +174 -0
- package/docs/components/theme-switcher.html +18 -6
- package/docs/index.html +18 -6
- package/docs/nav-1.inc.html +2 -0
- package/docs/nav.inc.html +2 -0
- package/docs/src/components/ColorPicker.js +12 -3
- package/docs/src/components/ThemeSwitcher.js +2 -2
- package/docs/src/utils/context.js +1 -0
- package/docs/src/utils/theme.js +1 -0
- package/docs/utils/context.html +160 -0
- package/docs/utils/theme.html +153 -0
- package/package.json +1 -1
- package/src/components/ColorPicker.js +64 -1
- package/src/components/ThemeSwitcher.js +17 -30
- package/src/utils/context.js +40 -0
- package/src/utils/theme.js +50 -0
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";export default class ColorPicker extends ShadowComponent{static formAssociated=!0;static properties={name:{type:String,reflect:!0,attribute:"name"},format:{type:String,reflect:!0,attribute:"format"},value:{type:String,reflect:!0,attribute:"value"},red:{type:Number},green:{type:Number},blue:{type:Number},alpha:{type:Number}};constructor(){super(),this.internals=this.attachInternals(),this.format="hex",this.red=0,this.green=0,this.blue=0,this.alpha=1}connectedCallback(){super.connectedCallback(),this.originalHadFormat=this.hasAttribute("format"),this.originalValue=this.getAttribute("value")}firstUpdated(t){if(super.firstUpdated(t),this.originalValue&&!this.originalHadFormat)for(const[t,a]of Object.entries(this.constructor.formats))if(a.detect(this.originalValue)){this.format=t;break}}updated(t){super.updated(t),t.has("format")&&this.requestUpdate(),(t.has("red")||t.has("green")||t.has("blue")||t.has("alpha")||t.has("format"))&&this.updateFormValue()}get value(){if(void 0===this.red||void 0===this.green||void 0===this.blue){const t=this.constructor.formats[this.format];return t&&t.toString?t.toString(0,0,0,1):"#000000"}const t=this.constructor.formats[this.format];return t&&t.toString?t.toString(this.red,this.green,this.blue,this.alpha||1):"#000000"}set value(t){if(t&&"string"==typeof t){for(const[a,s]of Object.entries(this.constructor.formats))if(s.detect(t)){const a=s.parse(t);if(a)return this.red=a.r,this.green=a.g,this.blue=a.b,a.hasAlpha&&(this.alpha=a.a),void this.updateFormValue()}this.red=0,this.green=0,this.blue=0,this.alpha=1,this.updateFormValue()}}updateFormValue(){const t=this.value;this.internals.setFormValue(t)}onColorInputChange(t){const a=this.constructor.formats.hex.parse(t.target.value);a&&(this.red=a.r,this.green=a.g,this.blue=a.b,this.alpha=1)}onTextInputChange(t){this.value=t.target.value}render(){return html`
|
|
1
|
+
import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";export default class ColorPicker extends ShadowComponent{static formAssociated=!0;static properties={name:{type:String,reflect:!0,attribute:"name"},format:{type:String,reflect:!0,attribute:"format"},value:{type:String,reflect:!0,attribute:"value"},red:{type:Number},green:{type:Number},blue:{type:Number},alpha:{type:Number}};constructor(){super(),this.internals=this.attachInternals(),this.format="hex",this.red=0,this.green=0,this.blue=0,this.alpha=1}connectedCallback(){super.connectedCallback(),this.originalHadFormat=this.hasAttribute("format"),this.originalValue=this.getAttribute("value")}firstUpdated(t){if(super.firstUpdated(t),this.originalValue&&!this.originalHadFormat)for(const[t,a]of Object.entries(this.constructor.formats))if(a.detect(this.originalValue)){this.format=t;break}}updated(t){super.updated(t),t.has("format")&&this.requestUpdate(),(t.has("red")||t.has("green")||t.has("blue")||t.has("alpha")||t.has("format"))&&(this.updateFormValue(),this.syncInputs(t.has("format")))}syncInputs(t=!1){const a=this.shadowRoot?.querySelector("#text"),s=this.shadowRoot?.querySelector("#color"),r=this.shadowRoot?.querySelector("#format"),e=this.shadowRoot?.activeElement;a&&(t||e!==a)&&(a.value=this.value),s&&e!==s&&(s.value=this.constructor.formats.hex.toString(this.red||0,this.green||0,this.blue||0,1)),r&&e!==r&&(r.value=this.format)}get value(){if(void 0===this.red||void 0===this.green||void 0===this.blue){const t=this.constructor.formats[this.format];return t&&t.toString?t.toString(0,0,0,1):"#000000"}const t=this.constructor.formats[this.format];return t&&t.toString?t.toString(this.red,this.green,this.blue,this.alpha||1):"#000000"}set value(t){if(t&&"string"==typeof t){for(const[a,s]of Object.entries(this.constructor.formats))if(s.detect(t)){const a=s.parse(t);if(a)return this.red=a.r,this.green=a.g,this.blue=a.b,a.hasAlpha&&(this.alpha=a.a),void this.updateFormValue()}this.red=0,this.green=0,this.blue=0,this.alpha=1,this.updateFormValue()}}updateFormValue(){const t=this.value;this.internals.setFormValue(t)}onColorInputChange(t){const a=this.constructor.formats.hex.parse(t.target.value);a&&(this.red=a.r,this.green=a.g,this.blue=a.b,this.alpha=1),this.forwardEvent(t)}onTextInputChange(t){this.value=t.target.value,this.forwardEvent(t)}onTextInput(t){this.value=t.target.value,this.forwardEvent(t)}onColorInput(t){const a=this.constructor.formats.hex.parse(t.target.value);a&&(this.red=a.r,this.green=a.g,this.blue=a.b,this.alpha=1),this.forwardEvent(t)}onFormatChange(t){this.format=t.target.value,this.forwardEvent(t)}onFormatInput(t){this.format=t.target.value,this.forwardEvent(t)}forwardEvent=t=>{this.dispatchEvent(new t.constructor(t.type,t))};render(){return html`
|
|
2
2
|
<div id="container">
|
|
3
3
|
<select
|
|
4
4
|
id="format"
|
|
5
5
|
value="${this.format}"
|
|
6
|
-
@change=${
|
|
6
|
+
@change=${this.onFormatChange}
|
|
7
|
+
@input=${this.onFormatInput}
|
|
8
|
+
@focus=${this.forwardEvent}
|
|
9
|
+
@blur=${this.forwardEvent}
|
|
7
10
|
>${this.constructor.formats?Object.keys(this.constructor.formats).map(t=>html`<option value="${t}" ?selected=${this.format===t}>${t.toUpperCase()}</option>`):null}</select>
|
|
8
11
|
<input
|
|
9
12
|
id="text"
|
|
10
13
|
type="text"
|
|
11
14
|
value="${this.value}"
|
|
12
15
|
@change=${this.onTextInputChange}
|
|
16
|
+
@input=${this.onTextInput}
|
|
17
|
+
@focus=${this.forwardEvent}
|
|
18
|
+
@blur=${this.forwardEvent}
|
|
13
19
|
/>
|
|
14
20
|
<div id="color-wrapper">
|
|
15
21
|
<input
|
|
@@ -17,6 +23,9 @@ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-al
|
|
|
17
23
|
type="color"
|
|
18
24
|
value="${this.constructor.formats.hex.toString(this.red||0,this.green||0,this.blue||0,1)}"
|
|
19
25
|
@change=${this.onColorInputChange}
|
|
26
|
+
@input=${this.onColorInput}
|
|
27
|
+
@focus=${this.forwardEvent}
|
|
28
|
+
@blur=${this.forwardEvent}
|
|
20
29
|
/>
|
|
21
30
|
</div>
|
|
22
31
|
</div>
|
|
@@ -42,4 +51,4 @@ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-al
|
|
|
42
51
|
aspect-ratio: 1;
|
|
43
52
|
height: 100%;
|
|
44
53
|
}
|
|
45
|
-
`;static formats={hex:{detect:t=>/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{8}|[A-Fa-f0-9]{4})$/.test(t),parse:t=>{let a,s,e
|
|
54
|
+
`;static formats={hex:{detect:t=>/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{8}|[A-Fa-f0-9]{4})$/.test(t),parse:t=>{let a,s,r,e=t.slice(1),o=e.length,h=1,n=!1;return 3===o||4===o?(a=parseInt(e[0]+e[0],16),s=parseInt(e[1]+e[1],16),r=parseInt(e[2]+e[2],16),4===o&&(h=parseInt(e[3]+e[3],16)/255,n=!0)):6!==o&&8!==o||(a=parseInt(e.slice(0,2),16),s=parseInt(e.slice(2,4),16),r=parseInt(e.slice(4,6),16),8===o&&(h=parseInt(e.slice(6,8),16)/255,n=!0)),{r:a,g:s,b:r,a:h,hasAlpha:n}},toString:(t,a,s,r)=>{let e=((t=Math.round(t))<<16|(a=Math.round(a))<<8|(s=Math.round(s))).toString(16).padStart(6,"0");if(r<1){e+=Math.round(255*r).toString(16).padStart(2,"0")}return"#"+e}},rgb:{detect:t=>/^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(?:,\s*(?:0?\.\d+|1(?:\.0*)?|\d+(?:\.\d*)?))?\s*\)$/.test(t),parse:t=>{const a=t.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)$/);if(!a)return null;return{r:parseInt(a[1],10),g:parseInt(a[2],10),b:parseInt(a[3],10),a:a[4]?parseFloat(a[4]):1,hasAlpha:!!a[4]}},toString:(t,a,s,r)=>(t=Math.round(t),a=Math.round(a),s=Math.round(s),r<1?`rgba(${t}, ${a}, ${s}, ${r})`:`rgb(${t}, ${a}, ${s})`)},hsl:{detect:t=>/^hsla?\(\s*\d+(?:\.\d+)?\s*,\s*\d+(?:\.\d+)?%\s*,\s*\d+(?:\.\d+)?%\s*(?:,\s*(?:0?\.\d+|1(?:\.0*)?|\d+(?:\.\d*)?))?\s*\)$/.test(t),parse:t=>{const a=t.match(/^hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)%\s*,\s*(\d+(?:\.\d+)?)%\s*(?:,\s*([\d.]+))?\s*\)$/);if(!a)return null;let s=parseFloat(a[1])/360,r=parseFloat(a[2])/100,e=parseFloat(a[3])/100,o=a[4]?parseFloat(a[4]):1;const h=!!a[4];let n,i,d;if(0===r)n=i=d=e;else{const t=(t,a,s)=>(s<0&&(s+=1),s>1&&(s-=1),s<1/6?t+6*(a-t)*s:s<.5?a:s<2/3?t+(a-t)*(2/3-s)*6:t),a=e<.5?e*(1+r):e+r-e*r,o=2*e-a;n=t(o,a,s+1/3),i=t(o,a,s),d=t(o,a,s-1/3)}return{r:Math.round(255*n),g:Math.round(255*i),b:Math.round(255*d),a:o,hasAlpha:h}},toString:(t,a,s,r)=>{t/=255,a/=255,s/=255;const e=Math.max(t,a,s),o=Math.min(t,a,s);let h,n,i=(e+o)/2;if(e===o)h=n=0;else{const r=e-o;switch(n=i>.5?r/(2-e-o):r/(e+o),e){case t:h=(a-s)/r+(a<s?6:0);break;case a:h=(s-t)/r+2;break;case s:h=(t-a)/r+4}h/=6}return h=Math.round(360*h),n=Math.round(100*n),i=Math.round(100*i),r<1?`hsla(${h}, ${n}%, ${i}%, ${r})`:`hsl(${h}, ${n}%, ${i}%)`}},hwb:{detect:t=>/^hwb\(\s*(\d+(?:\.\d+)?)(?:deg)?\s+(\d+(?:\.\d+)?)%\s+(\d+(?:\.\d+)?)%\s*(?:\/\s*([\d.]+))?\s*\)$/i.test(t),parse:t=>{const a=t.match(/^hwb\(\s*(\d+(?:\.\d+)?)(?:deg)?\s+(\d+(?:\.\d+)?)%\s+(\d+(?:\.\d+)?)%\s*(?:\/\s*([\d.]+))?\s*\)$/i);if(!a)return null;let s,r,e,o=parseFloat(a[1]),h=parseFloat(a[2])/100,n=parseFloat(a[3])/100,i=a[4]?parseFloat(a[4]):1,d=1-h-n;if(d>0){let t=o/360,a=1-Math.abs(6*t%2-1),n=h+d*(1-a),i=h+d*a;t<1/6?(s=h+d,r=i,e=h):t<2/6?(s=n,r=h+d,e=h):t<.5?(s=h,r=h+d,e=i):t<4/6?(s=h,r=n,e=h+d):t<5/6?(s=i,r=h,e=h+d):(s=h+d,r=h,e=n)}else s=r=e=h;const l=!!a[4];return{r:Math.round(255*s),g:Math.round(255*r),b:Math.round(255*e),a:i,hasAlpha:l}},toString:(t,a,s,r)=>{t/=255,a/=255,s/=255;const e=Math.max(t,a,s),o=Math.min(t,a,s),h=e-o;let n=0;if(0!==h){switch(e){case t:n=(a-s)/h%6;break;case a:n=(s-t)/h+2;break;case s:n=(t-a)/h+4}n=Math.round(60*n),n<0&&(n+=360)}const i=Math.round(100*o),d=Math.round(100*(1-e));return r<1?`hwb(${n} ${i}% ${d}% / ${r})`:`hwb(${n} ${i}% ${d}%)`}},lab:{detect:t=>/^lab\(\s*(\d+(?:\.\d+)?)%\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s*(?:\/\s*([\d.]+))?\s*\)$/i.test(t),parse:t=>{const a=t.match(/^lab\(\s*(\d+(?:\.\d+)?)%\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s*(?:\/\s*([\d.]+))?\s*\)$/i);if(!a)return null;let s=parseFloat(a[1])/100,r=parseFloat(a[2]),e=parseFloat(a[3]),o=a[4]?parseFloat(a[4]):1,h=(100*s+16)/116,n=h+r/500,i=h-e/200,d=.95047*(n>.206893034?n*n*n:(n-16/116)/7.787),l=1*(h>.206893034?h*h*h:(h-16/116)/7.787),u=1.08883*(i>.206893034?i*i*i:(i-16/116)/7.787),p=3.2404542*d-1.5371385*l-.4985314*u,c=-.969266*d+1.8760108*l+.041556*u,M=.0556434*d-.2040259*l+1.0572252*u,m=p<=.0031308?12.92*p:1.055*Math.pow(p,1/2.4)-.055,f=c<=.0031308?12.92*c:1.055*Math.pow(c,1/2.4)-.055,$=M<=.0031308?12.92*M:1.055*Math.pow(M,1/2.4)-.055;const g=!!a[4];return{r:Math.round(255*Math.max(0,Math.min(1,m))),g:Math.round(255*Math.max(0,Math.min(1,f))),b:Math.round(255*Math.max(0,Math.min(1,$))),a:o,hasAlpha:g}},toString:(t,a,s,r)=>{a/=255,s/=255;const e=(.4124564*(t=(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4))+.3575761*(a=a<=.04045?a/12.92:Math.pow((a+.055)/1.055,2.4))+.1804375*(s=s<=.04045?s/12.92:Math.pow((s+.055)/1.055,2.4)))/.95047,o=(.2126729*t+.7151522*a+.072175*s)/1,h=(.0193339*t+.119192*a+.9503041*s)/1.08883,n=e>.008856?Math.pow(e,1/3):7.787*e+16/116,i=o>.008856?Math.pow(o,1/3):7.787*o+16/116,d=h>.008856?Math.pow(h,1/3):7.787*h+16/116,l=Math.round(100*(116*i-16))/100,u=Math.round(500*(n-i)*100)/100,p=Math.round(200*(i-d)*100)/100;return r<1?`lab(${l}% ${u} ${p} / ${r})`:`lab(${l}% ${u} ${p})`}},lch:{detect:t=>/^lch\(\s*(\d+(?:\.\d+)?)%\s+(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)(?:deg)?\s*(?:\/\s*([\d.]+))?\s*\)$/i.test(t),parse:t=>{const a=t.match(/^lch\(\s*(\d+(?:\.\d+)?)%\s+(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)(?:deg)?\s*(?:\/\s*([\d.]+))?\s*\)$/i);if(!a)return null;let s=parseFloat(a[1]),r=parseFloat(a[2]),e=parseFloat(a[3]),o=a[4]?parseFloat(a[4]):1,h=(100*(s/100)+16)/116,n=h+r*Math.cos(e*Math.PI/180)/500,i=h-r*Math.sin(e*Math.PI/180)/200,d=.95047*(n>.206893034?n*n*n:(n-16/116)/7.787),l=1*(h>.206893034?h*h*h:(h-16/116)/7.787),u=1.08883*(i>.206893034?i*i*i:(i-16/116)/7.787),p=3.2404542*d-1.5371385*l-.4985314*u,c=-.969266*d+1.8760108*l+.041556*u,M=.0556434*d-.2040259*l+1.0572252*u,m=p<=.0031308?12.92*p:1.055*Math.pow(p,1/2.4)-.055,f=c<=.0031308?12.92*c:1.055*Math.pow(c,1/2.4)-.055,$=M<=.0031308?12.92*M:1.055*Math.pow(M,1/2.4)-.055;const g=!!a[4];return{r:Math.round(255*Math.max(0,Math.min(1,m))),g:Math.round(255*Math.max(0,Math.min(1,f))),b:Math.round(255*Math.max(0,Math.min(1,$))),a:o,hasAlpha:g}},toString:(t,a,s,r)=>{a/=255,s/=255;const e=(.4124564*(t=(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4))+.3575761*(a=a<=.04045?a/12.92:Math.pow((a+.055)/1.055,2.4))+.1804375*(s=s<=.04045?s/12.92:Math.pow((s+.055)/1.055,2.4)))/.95047,o=(.2126729*t+.7151522*a+.072175*s)/1,h=(.0193339*t+.119192*a+.9503041*s)/1.08883,n=e>.008856?Math.pow(e,1/3):7.787*e+16/116,i=o>.008856?Math.pow(o,1/3):7.787*o+16/116,d=(116*i-16)/100,l=500*(n-i),u=200*(i-(h>.008856?Math.pow(h,1/3):7.787*h+16/116)),p=Math.sqrt(l*l+u*u);let c=180*Math.atan2(u,l)/Math.PI;c<0&&(c+=360);const M=Math.round(100*d*100)/100,m=Math.round(100*p)/100,f=Math.round(100*c)/100;return r<1?`lch(${M}% ${m} ${f} / ${r})`:`lch(${M}% ${m} ${f})`}},oklab:{detect:t=>/^oklab\(\s*(\d+(?:\.\d+)?)%\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s*(?:\/\s*([\d.]+))?\s*\)$/i.test(t),parse:t=>{const a=t.match(/^oklab\(\s*(\d+(?:\.\d+)?)%\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s*(?:\/\s*([\d.]+))?\s*\)$/i);if(!a)return null;let s=parseFloat(a[1])/100,r=parseFloat(a[2]),e=parseFloat(a[3]),o=a[4]?parseFloat(a[4]):1,h=s+.3963377774*r+.2158037573*e,n=s-.1055613458*r-.0638541728*e,i=s-.0894841775*r-1.291485548*e,d=4.0767416621*h-3.3077115913*n+.2309699292*i,l=-1.2684380046*h+2.6097574011*n-.3413193965*i,u=-.0041960863*h-.7034186147*n+1.707614701*i,p=d<=.0031308?12.92*d:1.055*Math.pow(d,1/2.4)-.055,c=l<=.0031308?12.92*l:1.055*Math.pow(l,1/2.4)-.055,M=u<=.0031308?12.92*u:1.055*Math.pow(u,1/2.4)-.055;const m=!!a[4];return{r:Math.round(255*Math.max(0,Math.min(1,p))),g:Math.round(255*Math.max(0,Math.min(1,c))),b:Math.round(255*Math.max(0,Math.min(1,M))),a:o,hasAlpha:m}},toString:(t,a,s,r)=>{a/=255,s/=255;const e=.4122214708*(t=(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4))+.5363325363*(a=a<=.04045?a/12.92:Math.pow((a+.055)/1.055,2.4))+.0514459929*(s=s<=.04045?s/12.92:Math.pow((s+.055)/1.055,2.4)),o=.2119034982*t+.6806995451*a+.1073969566*s,h=.0883024619*t+.2817188376*a+.6299787005*s,n=Math.pow(e,1/3),i=Math.pow(o,1/3),d=Math.pow(h,1/3),l=.2104542553*n+.793617785*i-.0040720468*d,u=1.9779984951*n-2.428592205*i+.4505937099*d,p=.0259040371*n+.7827717662*i-.808675766*d,c=Math.round(100*l*100)/100,M=Math.round(100*u)/100,m=Math.round(100*p)/100;return r<1?`oklab(${c}% ${M} ${m} / ${r})`:`oklab(${c}% ${M} ${m})`}},oklch:{detect:t=>/^oklch\(\s*(\d+(?:\.\d+)?)%\s+(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)(?:deg)?\s*(?:\/\s*([\d.]+))?\s*\)$/i.test(t),parse:t=>{const a=t.match(/^oklch\(\s*(\d+(?:\.\d+)?)%\s+(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)(?:deg)?\s*(?:\/\s*([\d.]+))?\s*\)$/i);if(!a)return null;let s=parseFloat(a[1])/100,r=parseFloat(a[2]),e=parseFloat(a[3]),o=a[4]?parseFloat(a[4]):1,h=r*Math.cos(e*Math.PI/180),n=r*Math.sin(e*Math.PI/180),i=s+.3963377774*h+.2158037573*n,d=s-.1055613458*h-.0638541728*n,l=s-.0894841775*h-1.291485548*n,u=4.0767416621*i-3.3077115913*d+.2309699292*l,p=-1.2684380046*i+2.6097574011*d-.3413193965*l,c=-.0041960863*i-.7034186147*d+1.707614701*l,M=u<=.0031308?12.92*u:1.055*Math.pow(u,1/2.4)-.055,m=p<=.0031308?12.92*p:1.055*Math.pow(p,1/2.4)-.055,f=c<=.0031308?12.92*c:1.055*Math.pow(c,1/2.4)-.055;const $=!!a[4];return{r:Math.round(255*Math.max(0,Math.min(1,M))),g:Math.round(255*Math.max(0,Math.min(1,m))),b:Math.round(255*Math.max(0,Math.min(1,f))),a:o,hasAlpha:$}},toString:(t,a,s,r)=>{a/=255,s/=255;const e=.4122214708*(t=(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4))+.5363325363*(a=a<=.04045?a/12.92:Math.pow((a+.055)/1.055,2.4))+.0514459929*(s=s<=.04045?s/12.92:Math.pow((s+.055)/1.055,2.4)),o=.2119034982*t+.6806995451*a+.1073969566*s,h=.0883024619*t+.2817188376*a+.6299787005*s,n=Math.pow(e,1/3),i=Math.pow(o,1/3),d=Math.pow(h,1/3),l=.2104542553*n+.793617785*i-.0040720468*d,u=1.9779984951*n-2.428592205*i+.4505937099*d,p=.0259040371*n+.7827717662*i-.808675766*d,c=Math.sqrt(u*u+p*p);let M=180*Math.atan2(p,u)/Math.PI;M<0&&(M+=360);const m=Math.round(100*l*100)/100,f=Math.round(100*c)/100,$=Math.round(100*M)/100;return r<1?`oklch(${m}% ${f} ${$} / ${r})`:`oklch(${m}% ${f} ${$})`}}}}window.customElements.define("k-color-picker",ColorPicker);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";import"./Icon.js";export default class ThemeSwitcher extends ShadowComponent{static properties={currentTheme:{type:String,reflect:!0,attribute:"current-theme"}};constructor(){super(),this.currentTheme=
|
|
1
|
+
import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-all.min.js";import theme from"../utils/theme.js";import"./Icon.js";export default class ThemeSwitcher extends ShadowComponent{static properties={currentTheme:{type:String,reflect:!0,attribute:"current-theme"}};constructor(){super(),this.currentTheme=theme.get()}handleClick=()=>{const e=theme.get();"auto"===e&&theme.set("light"),"light"===e&&theme.set("dark"),"dark"===e&&theme.set("auto")};connectedCallback(){super.connectedCallback(),this.unsubscribe=theme.subscribe(e=>{this.currentTheme=e})}disconnectedCallback(){super.disconnectedCallback(),this.unsubscribe&&this.unsubscribe()}static styles=css`
|
|
2
2
|
:host {
|
|
3
3
|
--padding: var(--spacer);
|
|
4
4
|
display: flex;
|
|
@@ -20,4 +20,4 @@ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-al
|
|
|
20
20
|
button.no-btn {
|
|
21
21
|
padding: var(--padding);
|
|
22
22
|
}
|
|
23
|
-
`;static setTheme(e){
|
|
23
|
+
`;static setTheme(e){theme.set(e)}static getCurrentTheme(){return theme.get()}static getCalculatedCurrentTheme(){return theme.getCalculated()}}customElements.define("k-theme-switcher",ThemeSwitcher);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const contexts={},getContext=e=>(contexts[e]||(contexts[e]={value:void 0,subscribers:new Set}),contexts[e]);export const createContext=(e="global",t)=>{const s=getContext(e);return void 0!==t&&void 0===s.value&&(s.value=t),{get:()=>s.value,set:e=>{s.value=e,s.subscribers.forEach(t=>t(e))},subscribe:e=>(s.subscribers.add(e),e(s.value),()=>s.subscribers.delete(e)),unsubscribe:e=>s.subscribers.delete(e)}};export default createContext;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import createContext from"./context.js";const getInitialTheme=()=>{let e=document.documentElement.getAttribute("theme");return e||(e=localStorage.getItem("theme")),e||"auto"},themeContext=createContext("theme",getInitialTheme());themeContext.subscribe(e=>{localStorage.setItem("theme",e),document.documentElement.setAttribute("theme",e)});export const setTheme=e=>themeContext.set(e);export const getTheme=()=>themeContext.get();export const subscribeToTheme=e=>themeContext.subscribe(e);export const getCalculatedTheme=()=>{const e=getTheme();return"auto"===e?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":e};const colorSchemeQuery=window.matchMedia("(prefers-color-scheme: dark)"),colorSchemeChangeHandler=e=>document.documentElement.setAttribute("auto-theme",e.matches?"dark":"light");colorSchemeQuery.addEventListener("change",colorSchemeChangeHandler),colorSchemeChangeHandler(colorSchemeQuery);export default{get:getTheme,set:setTheme,subscribe:subscribeToTheme,getCalculated:getCalculatedTheme};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Context Utility - Kempo UI</title>
|
|
7
|
+
<link rel="icon" type="image/png" sizes="48x48" href="../media/icon48.png">
|
|
8
|
+
<link rel="manifest" href="../manifest.json" />
|
|
9
|
+
<link rel="stylesheet" href="../kempo-vars.css" />
|
|
10
|
+
<link rel="stylesheet" href="../kempo.min.css" />
|
|
11
|
+
<link rel="stylesheet" href="../kempo-hljs.css" />
|
|
12
|
+
<link rel="stylesheet" href="../styles.css" />
|
|
13
|
+
<script>window.litDisableBundleWarning = true;</script>
|
|
14
|
+
<script src="../init-1.js" type="module"></script>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<k-import src="../nav-1.inc.html"></k-import>
|
|
18
|
+
<main>
|
|
19
|
+
<h1>Context Utility</h1>
|
|
20
|
+
|
|
21
|
+
<k-accordion persistent-id="toc" class="b r mb">
|
|
22
|
+
<k-accordion-header for-panel="toc-panel">Table of Contents</k-accordion-header>
|
|
23
|
+
<k-accordion-panel name="toc-panel">
|
|
24
|
+
<div class="m ">
|
|
25
|
+
<h6>Usage</h6>
|
|
26
|
+
<a href="#basicUsage">Basic Usage</a><br />
|
|
27
|
+
<a href="#api">API Methods</a><br />
|
|
28
|
+
<a href="#examples">Examples</a><br />
|
|
29
|
+
</div>
|
|
30
|
+
</k-accordion-panel>
|
|
31
|
+
</k-accordion>
|
|
32
|
+
|
|
33
|
+
<h3>Description</h3>
|
|
34
|
+
<p>The <code>createContext</code> utility provides a simple way to create global or named shared state that components can subscribe to. It enables reactive state management with automatic subscriber notifications on value changes.</p>
|
|
35
|
+
|
|
36
|
+
<h3 id="basicUsage"><a href="#basicUsage" class="no-link">Basic Usage</a></h3>
|
|
37
|
+
<p>Import and create a context to manage shared state:</p>
|
|
38
|
+
<pre><code class="hljs javascript"><span class="hljs-keyword">import</span> createContext <span class="hljs-keyword">from</span> <span class="hljs-string">'./utils/context.js'</span>;<br /><br /><span class="hljs-comment">// Create a named context with initial value</span><br /><span class="hljs-keyword">const</span> userContext = createContext(<span class="hljs-string">'user'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-string">'Guest'</span> });<br /><br /><span class="hljs-comment">// Subscribe to changes</span><br />userContext.subscribe(<span class="hljs-function">(<span class="hljs-params">user</span>) =></span> {<br /> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'User changed:'</span>, user);<br />});<br /><br /><span class="hljs-comment">// Update the context value</span><br />userContext.set({ <span class="hljs-attr">name</span>: <span class="hljs-string">'John Doe'</span> });</code></pre>
|
|
39
|
+
|
|
40
|
+
<h3 id="api"><a href="#api" class="no-link">API Methods</a></h3>
|
|
41
|
+
|
|
42
|
+
<h4><code>createContext(name, initialValue)</code></h4>
|
|
43
|
+
<p>Creates or retrieves a context with the specified name.</p>
|
|
44
|
+
<h5><code>name<i>: string</i></code> (optional)</h5>
|
|
45
|
+
<p>The name of the context. Defaults to <code>'global'</code>. Using the same name returns the same context instance.</p>
|
|
46
|
+
<h5><code>initialValue<i>: any</i></code> (optional)</h5>
|
|
47
|
+
<p>The initial value for the context. Only set if the context doesn't already have a value.</p>
|
|
48
|
+
<h5>Returns: <code>Object</code></h5>
|
|
49
|
+
<p>An object with the following methods:</p>
|
|
50
|
+
|
|
51
|
+
<h4><code>get()</code></h4>
|
|
52
|
+
<p>Returns the current value of the context.</p>
|
|
53
|
+
<pre><code class="hljs javascript"><span class="hljs-keyword">const</span> currentValue = userContext.get();</code></pre>
|
|
54
|
+
|
|
55
|
+
<h4><code>set(value)</code></h4>
|
|
56
|
+
<p>Updates the context value and notifies all subscribers.</p>
|
|
57
|
+
<pre><code class="hljs javascript">userContext.set({ <span class="hljs-attr">name</span>: <span class="hljs-string">'Jane Doe'</span>, <span class="hljs-attr">role</span>: <span class="hljs-string">'admin'</span> });</code></pre>
|
|
58
|
+
|
|
59
|
+
<h4><code>subscribe(callback)</code></h4>
|
|
60
|
+
<p>Subscribes to context changes. The callback is immediately invoked with the current value, then called again whenever the value changes.</p>
|
|
61
|
+
<h5><code>callback<i>: Function</i></code></h5>
|
|
62
|
+
<p>Function to call when the context value changes. Receives the new value as an argument.</p>
|
|
63
|
+
<h5>Returns: <code>Function</code></h5>
|
|
64
|
+
<p>An unsubscribe function to remove the subscription.</p>
|
|
65
|
+
<pre><code class="hljs javascript"><span class="hljs-keyword">const</span> unsubscribe = userContext.subscribe(<span class="hljs-function">(<span class="hljs-params">value</span>) =></span> {<br /> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'New value:'</span>, value);<br />});<br /><br /><span class="hljs-comment">// Later, stop listening</span><br />unsubscribe();</code></pre>
|
|
66
|
+
|
|
67
|
+
<h4><code>unsubscribe(callback)</code></h4>
|
|
68
|
+
<p>Removes a subscriber callback from the context.</p>
|
|
69
|
+
<pre><code class="hljs javascript"><span class="hljs-keyword">const</span> handler = <span class="hljs-function">(<span class="hljs-params">value</span>) =></span> <span class="hljs-built_in">console</span>.log(value);<br />userContext.subscribe(handler);<br />userContext.unsubscribe(handler);</code></pre>
|
|
70
|
+
|
|
71
|
+
<h3 id="examples"><a href="#examples" class="no-link">Examples</a></h3>
|
|
72
|
+
|
|
73
|
+
<h4>Theme Management</h4>
|
|
74
|
+
<pre><code class="hljs javascript"><span class="hljs-keyword">import</span> createContext <span class="hljs-keyword">from</span> <span class="hljs-string">'./utils/context.js'</span>;<br /><br /><span class="hljs-keyword">const</span> themeContext = createContext(<span class="hljs-string">'theme'</span>, <span class="hljs-string">'light'</span>);<br /><br /><span class="hljs-comment">// Components can subscribe to theme changes</span><br />themeContext.subscribe(<span class="hljs-function">(<span class="hljs-params">theme</span>) =></span> {<br /> <span class="hljs-built_in">document</span>.body.className = theme;<br />});<br /><br /><span class="hljs-comment">// Toggle theme from anywhere</span><br /><span class="hljs-keyword">const</span> toggleTheme = <span class="hljs-function"><span class="hljs-params">()</span> =></span> {<br /> <span class="hljs-keyword">const</span> current = themeContext.get();<br /> themeContext.set(current === <span class="hljs-string">'light'</span> ? <span class="hljs-string">'dark'</span> : <span class="hljs-string">'light'</span>);<br />};</code></pre>
|
|
75
|
+
|
|
76
|
+
<h4>Component State Sharing</h4>
|
|
77
|
+
<pre><code class="hljs javascript"><span class="hljs-keyword">import</span> createContext <span class="hljs-keyword">from</span> <span class="hljs-string">'./utils/context.js'</span>;<br /><br /><span class="hljs-keyword">const</span> cartContext = createContext(<span class="hljs-string">'cart'</span>, []);<br /><br /><span class="hljs-comment">// Add item from one component</span><br /><span class="hljs-keyword">const</span> addToCart = <span class="hljs-function">(<span class="hljs-params">item</span>) =></span> {<br /> <span class="hljs-keyword">const</span> items = cartContext.get();<br /> cartContext.set([...items, item]);<br />};<br /><br /><span class="hljs-comment">// Display cart in another component</span><br />cartContext.subscribe(<span class="hljs-function">(<span class="hljs-params">items</span>) =></span> {<br /> <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'.cart-count'</span>).textContent = items.length;<br />});</code></pre>
|
|
78
|
+
|
|
79
|
+
<h4>Global vs Named Contexts</h4>
|
|
80
|
+
<pre><code class="hljs javascript"><span class="hljs-keyword">import</span> createContext <span class="hljs-keyword">from</span> <span class="hljs-string">'./utils/context.js'</span>;<br /><br /><span class="hljs-comment">// Use default global context</span><br /><span class="hljs-keyword">const</span> globalCtx = createContext();<br />globalCtx.set({ <span class="hljs-attr">appReady</span>: <span class="hljs-literal">true</span> });<br /><br /><span class="hljs-comment">// Named contexts are isolated</span><br /><span class="hljs-keyword">const</span> userCtx = createContext(<span class="hljs-string">'user'</span>);<br /><span class="hljs-keyword">const</span> settingsCtx = createContext(<span class="hljs-string">'settings'</span>);<br /><br /><span class="hljs-comment">// Same name returns the same context instance</span><br /><span class="hljs-keyword">const</span> userCtx2 = createContext(<span class="hljs-string">'user'</span>);<br /><span class="hljs-built_in">console</span>.log(userCtx === userCtx2); <span class="hljs-comment">// true</span></code></pre>
|
|
81
|
+
|
|
82
|
+
<h4>Working Example</h4>
|
|
83
|
+
<div class="row -mx mb">
|
|
84
|
+
<div class="col d-span-6 m-span-12 px">
|
|
85
|
+
<k-card label="HTML & JavaScript">
|
|
86
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"context-demo"</span>></span>
|
|
87
|
+
<span class="hljs-tag"><<span class="hljs-name">p</span>></span>Current count: <span class="hljs-tag"><<span class="hljs-name">strong</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"count-display"</span>></span>0<span class="hljs-tag"></<span class="hljs-name">strong</span>></span><span class="hljs-tag"></<span class="hljs-name">p</span>></span>
|
|
88
|
+
<span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"increment"</span>></span>Increment<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
|
|
89
|
+
<span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"decrement"</span>></span>Decrement<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
|
|
90
|
+
<span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"reset"</span>></span>Reset<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
|
|
91
|
+
<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
|
|
92
|
+
|
|
93
|
+
<span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>></span><span class="javascript">
|
|
94
|
+
<span class="hljs-keyword">import</span> createContext <span class="hljs-keyword">from</span> <span class="hljs-string">'../src/utils/context.js'</span>;
|
|
95
|
+
|
|
96
|
+
<span class="hljs-keyword">const</span> counterContext = createContext(<span class="hljs-string">'counter'</span>, <span class="hljs-number">0</span>);
|
|
97
|
+
|
|
98
|
+
<span class="hljs-comment">// Subscribe to updates</span>
|
|
99
|
+
counterContext.subscribe(<span class="hljs-function">(<span class="hljs-params">count</span>) =></span> {
|
|
100
|
+
<span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'count-display'</span>).textContent = count;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
<span class="hljs-comment">// Button handlers</span>
|
|
104
|
+
<span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'increment'</span>)
|
|
105
|
+
.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'click'</span>, <span class="hljs-function"><span class="hljs-params">()</span> =></span> {
|
|
106
|
+
counterContext.<span class="hljs-title function_">set</span>(counterContext.<span class="hljs-title function_">get</span>() + <span class="hljs-number">1</span>);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
<span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'decrement'</span>)
|
|
110
|
+
.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'click'</span>, <span class="hljs-function"><span class="hljs-params">()</span> =></span> {
|
|
111
|
+
counterContext.<span class="hljs-title function_">set</span>(counterContext.<span class="hljs-title function_">get</span>() - <span class="hljs-number">1</span>);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
<span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'reset'</span>)
|
|
115
|
+
.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'click'</span>, <span class="hljs-function"><span class="hljs-params">()</span> =></span> {
|
|
116
|
+
counterContext.<span class="hljs-title function_">set</span>(<span class="hljs-number">0</span>);
|
|
117
|
+
});
|
|
118
|
+
</span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre>
|
|
119
|
+
</k-card>
|
|
120
|
+
</div>
|
|
121
|
+
<div class="col d-span-6 m-span-12 px">
|
|
122
|
+
<k-card label="Results">
|
|
123
|
+
<div id="context-demo">
|
|
124
|
+
<p>Current count: <strong id="count-display">0</strong></p>
|
|
125
|
+
<button id="increment" class="primary">Increment</button>
|
|
126
|
+
<button id="decrement">Decrement</button>
|
|
127
|
+
<button id="reset" class="danger">Reset</button>
|
|
128
|
+
</div>
|
|
129
|
+
</k-card>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
</main>
|
|
134
|
+
<div style="height:33vh"></div>
|
|
135
|
+
<script type="module" src="../src/components/Import.js"></script>
|
|
136
|
+
<script type="module" src="../src/components/Accordion.js"></script>
|
|
137
|
+
<script type="module" src="../src/components/Card.js"></script>
|
|
138
|
+
<script type="module">
|
|
139
|
+
import createContext from '../src/utils/context.js';
|
|
140
|
+
|
|
141
|
+
const counterContext = createContext('counter-demo', 0);
|
|
142
|
+
|
|
143
|
+
counterContext.subscribe(count => {
|
|
144
|
+
document.getElementById('count-display').textContent = count;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
document.getElementById('increment').addEventListener('click', () => {
|
|
148
|
+
counterContext.set(counterContext.get() + 1);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
document.getElementById('decrement').addEventListener('click', () => {
|
|
152
|
+
counterContext.set(counterContext.get() - 1);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
document.getElementById('reset').addEventListener('click', () => {
|
|
156
|
+
counterContext.set(0);
|
|
157
|
+
});
|
|
158
|
+
</script>
|
|
159
|
+
</body>
|
|
160
|
+
</html>
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Theme Utility - Kempo UI</title>
|
|
7
|
+
<link rel="icon" type="image/png" sizes="48x48" href="../media/icon48.png">
|
|
8
|
+
<link rel="manifest" href="../manifest.json" />
|
|
9
|
+
<link rel="stylesheet" href="../kempo-vars.css" />
|
|
10
|
+
<link rel="stylesheet" href="../kempo.min.css" />
|
|
11
|
+
<link rel="stylesheet" href="../kempo-hljs.css" />
|
|
12
|
+
<link rel="stylesheet" href="../styles.css" />
|
|
13
|
+
<script>window.litDisableBundleWarning = true;</script>
|
|
14
|
+
<script src="../init-1.js" type="module"></script>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<k-import src="../nav-1.inc.html"></k-import>
|
|
18
|
+
<main>
|
|
19
|
+
<h1>Theme Utility</h1>
|
|
20
|
+
|
|
21
|
+
<k-accordion persistent-id="toc" class="b r mb">
|
|
22
|
+
<k-accordion-header for-panel="toc-panel">Table of Contents</k-accordion-header>
|
|
23
|
+
<k-accordion-panel name="toc-panel">
|
|
24
|
+
<div class="m ">
|
|
25
|
+
<h6>Usage</h6>
|
|
26
|
+
<a href="#basicUsage">Basic Usage</a><br />
|
|
27
|
+
<a href="#api">API Methods</a><br />
|
|
28
|
+
<a href="#examples">Examples</a><br />
|
|
29
|
+
</div>
|
|
30
|
+
</k-accordion-panel>
|
|
31
|
+
</k-accordion>
|
|
32
|
+
|
|
33
|
+
<h3>Description</h3>
|
|
34
|
+
<p>The <code>theme</code> utility provides a centralized way to manage the application's theme state. It handles theme persistence via localStorage, automatic system preference detection, and reactive updates across components. This utility works independently of the ThemeSwitcher component, allowing any part of your application to read, set, or subscribe to theme changes.</p>
|
|
35
|
+
|
|
36
|
+
<h3 id="basicUsage"><a href="#basicUsage" class="no-link">Basic Usage</a></h3>
|
|
37
|
+
<p>Import and use the theme utility to manage application themes:</p>
|
|
38
|
+
<pre><code class="hljs javascript"><span class="hljs-keyword">import</span> theme <span class="hljs-keyword">from</span> <span class="hljs-string">'./utils/theme.js'</span>;<br /><br /><span class="hljs-comment">// Get current theme</span><br /><span class="hljs-keyword">const</span> currentTheme = theme.get(); <span class="hljs-comment">// 'auto', 'light', or 'dark'</span><br /><br /><span class="hljs-comment">// Set theme</span><br />theme.set(<span class="hljs-string">'dark'</span>);<br /><br /><span class="hljs-comment">// Subscribe to theme changes</span><br /><span class="hljs-keyword">const</span> unsubscribe = theme.subscribe(<span class="hljs-function">(<span class="hljs-params">newTheme</span>) =></span> {<br /> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Theme changed to:'</span>, newTheme);<br />});</code></pre>
|
|
39
|
+
|
|
40
|
+
<h3 id="api"><a href="#api" class="no-link">API Methods</a></h3>
|
|
41
|
+
|
|
42
|
+
<h4><code>theme.get()</code></h4>
|
|
43
|
+
<p>Returns the current theme value.</p>
|
|
44
|
+
<h5>Returns: <code>string</code></h5>
|
|
45
|
+
<p>One of: <code>'auto'</code>, <code>'light'</code>, or <code>'dark'</code></p>
|
|
46
|
+
<pre><code class="hljs javascript"><span class="hljs-keyword">const</span> currentTheme = theme.get();<br /><span class="hljs-built_in">console</span>.log(currentTheme); <span class="hljs-comment">// 'auto', 'light', or 'dark'</span></code></pre>
|
|
47
|
+
|
|
48
|
+
<h4><code>theme.set(value)</code></h4>
|
|
49
|
+
<p>Sets the theme and automatically persists it to localStorage and updates the DOM.</p>
|
|
50
|
+
<h5><code>value<i>: string</i></code></h5>
|
|
51
|
+
<p>The theme to set. Must be one of: <code>'auto'</code>, <code>'light'</code>, or <code>'dark'</code></p>
|
|
52
|
+
<pre><code class="hljs javascript">theme.set(<span class="hljs-string">'dark'</span>);</code></pre>
|
|
53
|
+
|
|
54
|
+
<h4><code>theme.subscribe(callback)</code></h4>
|
|
55
|
+
<p>Subscribes to theme changes. The callback is immediately invoked with the current theme, then called again whenever the theme changes.</p>
|
|
56
|
+
<h5><code>callback<i>: Function</i></code></h5>
|
|
57
|
+
<p>Function to call when the theme changes. Receives the new theme value as an argument.</p>
|
|
58
|
+
<h5>Returns: <code>Function</code></h5>
|
|
59
|
+
<p>An unsubscribe function to remove the subscription.</p>
|
|
60
|
+
<pre><code class="hljs javascript"><span class="hljs-keyword">const</span> unsubscribe = theme.subscribe(<span class="hljs-function">(<span class="hljs-params">newTheme</span>) =></span> {<br /> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Theme is now:'</span>, newTheme);<br />});<br /><br /><span class="hljs-comment">// Later, stop listening</span><br />unsubscribe();</code></pre>
|
|
61
|
+
|
|
62
|
+
<h4><code>theme.getCalculated()</code></h4>
|
|
63
|
+
<p>Returns the effective theme, resolving <code>'auto'</code> to either <code>'light'</code> or <code>'dark'</code> based on the user's system preference.</p>
|
|
64
|
+
<h5>Returns: <code>string</code></h5>
|
|
65
|
+
<p>Either <code>'light'</code> or <code>'dark'</code></p>
|
|
66
|
+
<pre><code class="hljs javascript"><span class="hljs-keyword">const</span> effectiveTheme = theme.getCalculated();<br /><span class="hljs-built_in">console</span>.log(effectiveTheme); <span class="hljs-comment">// Always 'light' or 'dark'</span></code></pre>
|
|
67
|
+
|
|
68
|
+
<h3 id="examples"><a href="#examples" class="no-link">Examples</a></h3>
|
|
69
|
+
|
|
70
|
+
<h4>Reading Theme Without Component</h4>
|
|
71
|
+
<pre><code class="hljs javascript"><span class="hljs-keyword">import</span> theme <span class="hljs-keyword">from</span> <span class="hljs-string">'./utils/theme.js'</span>;<br /><br /><span class="hljs-comment">// Get theme anywhere in your app</span><br /><span class="hljs-keyword">const</span> currentTheme = theme.get();<br /><span class="hljs-keyword">const</span> isDark = theme.getCalculated() === <span class="hljs-string">'dark'</span>;<br /><br /><span class="hljs-keyword">if</span>(isDark){<br /> <span class="hljs-comment">// Apply dark-specific logic</span><br />}</code></pre>
|
|
72
|
+
|
|
73
|
+
<h4>Component Subscribing to Theme</h4>
|
|
74
|
+
<pre><code class="hljs javascript"><span class="hljs-keyword">import</span> { LitElement, html } <span class="hljs-keyword">from</span> <span class="hljs-string">'lit'</span>;<br /><span class="hljs-keyword">import</span> theme <span class="hljs-keyword">from</span> <span class="hljs-string">'./utils/theme.js'</span>;<br /><br /><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyComponent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">LitElement</span> {<br /> <span class="hljs-keyword">static</span> properties = {<br /> <span class="hljs-attr">theme</span>: { <span class="hljs-attr">type</span>: <span class="hljs-title class_">String</span> }<br /> };<br /><br /> <span class="hljs-title function_">connectedCallback</span>() {<br /> <span class="hljs-variable language_">super</span>.<span class="hljs-title function_">connectedCallback</span>();<br /> <span class="hljs-variable language_">this</span>.<span class="hljs-property">unsubscribe</span> = theme.<span class="hljs-title function_">subscribe</span>(<span class="hljs-function">(<span class="hljs-params">t</span>) =></span> {<br /> <span class="hljs-variable language_">this</span>.<span class="hljs-property">theme</span> = t;<br /> });<br /> }<br /><br /> <span class="hljs-title function_">disconnectedCallback</span>() {<br /> <span class="hljs-variable language_">super</span>.<span class="hljs-title function_">disconnectedCallback</span>();<br /> <span class="hljs-keyword">if</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">unsubscribe</span>) <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">unsubscribe</span>();<br /> }<br /><br /> <span class="hljs-title function_">render</span>() {<br /> <span class="hljs-keyword">return</span> html<span class="hljs-string">`<p>Current theme: ${this.theme}</p>`</span>;<br /> }<br />}</code></pre>
|
|
75
|
+
|
|
76
|
+
<h4>Setting Theme Programmatically</h4>
|
|
77
|
+
<pre><code class="hljs javascript"><span class="hljs-keyword">import</span> theme <span class="hljs-keyword">from</span> <span class="hljs-string">'./utils/theme.js'</span>;<br /><br /><span class="hljs-comment">// Set theme based on user preference from API</span><br /><span class="hljs-keyword">const</span> userPreferences = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetchUserPreferences</span>();<br />theme.<span class="hljs-title function_">set</span>(userPreferences.<span class="hljs-property">theme</span>);<br /><br /><span class="hljs-comment">// Toggle between light and dark</span><br /><span class="hljs-keyword">const</span> <span class="hljs-title function_">toggleTheme</span> = <span class="hljs-function"><span class="hljs-params">()</span> =></span> {<br /> <span class="hljs-keyword">const</span> current = theme.<span class="hljs-title function_">get</span>();<br /> <span class="hljs-keyword">if</span>(current === <span class="hljs-string">'dark'</span>) theme.<span class="hljs-title function_">set</span>(<span class="hljs-string">'light'</span>);<br /> <span class="hljs-keyword">else</span> theme.<span class="hljs-title function_">set</span>(<span class="hljs-string">'dark'</span>);<br />};</code></pre>
|
|
78
|
+
|
|
79
|
+
<h4>Working Example</h4>
|
|
80
|
+
<div class="row -mx mb">
|
|
81
|
+
<div class="col d-span-6 m-span-12 px">
|
|
82
|
+
<k-card label="HTML & JavaScript">
|
|
83
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"theme-demo"</span>></span>
|
|
84
|
+
<span class="hljs-tag"><<span class="hljs-name">p</span>></span>Current theme: <span class="hljs-tag"><<span class="hljs-name">strong</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"theme-display"</span>></span>auto<span class="hljs-tag"></<span class="hljs-name">strong</span>></span><span class="hljs-tag"></<span class="hljs-name">p</span>></span>
|
|
85
|
+
<span class="hljs-tag"><<span class="hljs-name">p</span>></span>Calculated: <span class="hljs-tag"><<span class="hljs-name">strong</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"calculated-display"</span>></span>dark<span class="hljs-tag"></<span class="hljs-name">strong</span>></span><span class="hljs-tag"></<span class="hljs-name">p</span>></span>
|
|
86
|
+
<span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"set-auto"</span>></span>Auto<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
|
|
87
|
+
<span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"set-light"</span>></span>Light<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
|
|
88
|
+
<span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"set-dark"</span>></span>Dark<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
|
|
89
|
+
<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
|
|
90
|
+
|
|
91
|
+
<span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>></span><span class="javascript">
|
|
92
|
+
<span class="hljs-keyword">import</span> theme <span class="hljs-keyword">from</span> <span class="hljs-string">'../src/utils/theme.js'</span>;
|
|
93
|
+
|
|
94
|
+
<span class="hljs-comment">// Subscribe to updates</span>
|
|
95
|
+
theme.subscribe(<span class="hljs-function">(<span class="hljs-params">t</span>) =></span> {
|
|
96
|
+
<span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'theme-display'</span>).textContent = t;
|
|
97
|
+
<span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'calculated-display'</span>).textContent =
|
|
98
|
+
theme.<span class="hljs-title function_">getCalculated</span>();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
<span class="hljs-comment">// Button handlers</span>
|
|
102
|
+
<span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'set-auto'</span>)
|
|
103
|
+
.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'click'</span>, <span class="hljs-function"><span class="hljs-params">()</span> =></span> theme.<span class="hljs-title function_">set</span>(<span class="hljs-string">'auto'</span>));
|
|
104
|
+
<span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'set-light'</span>)
|
|
105
|
+
.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'click'</span>, <span class="hljs-function"><span class="hljs-params">()</span> =></span> theme.<span class="hljs-title function_">set</span>(<span class="hljs-string">'light'</span>));
|
|
106
|
+
<span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'set-dark'</span>)
|
|
107
|
+
.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'click'</span>, <span class="hljs-function"><span class="hljs-params">()</span> =></span> theme.<span class="hljs-title function_">set</span>(<span class="hljs-string">'dark'</span>));
|
|
108
|
+
</span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre>
|
|
109
|
+
</k-card>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="col d-span-6 m-span-12 px">
|
|
112
|
+
<k-card label="Results">
|
|
113
|
+
<div id="theme-demo">
|
|
114
|
+
<p>Current theme: <strong id="theme-display">auto</strong></p>
|
|
115
|
+
<p>Calculated: <strong id="calculated-display">dark</strong></p>
|
|
116
|
+
<button id="set-auto">Auto</button>
|
|
117
|
+
<button id="set-light" class="primary">Light</button>
|
|
118
|
+
<button id="set-dark" class="danger">Dark</button>
|
|
119
|
+
</div>
|
|
120
|
+
</k-card>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<h3>Automatic Features</h3>
|
|
125
|
+
<h4>localStorage Persistence</h4>
|
|
126
|
+
<p>The theme utility automatically saves the current theme to <code>localStorage</code> with the key <code>'theme'</code>. This means the user's theme preference persists across page reloads and browser sessions.</p>
|
|
127
|
+
|
|
128
|
+
<h4>DOM Attribute Updates & Kempo CSS Integration</h4>
|
|
129
|
+
<p>Whenever the theme changes, it's automatically applied to <code>document.documentElement</code> as a <code>theme</code> attribute. This sets the <code>color-scheme</code> property which enables Kempo CSS's <code>light-dark()</code> function to work correctly.</p>
|
|
130
|
+
<p>Kempo CSS provides theme-aware CSS custom properties that automatically adapt to light and dark modes. See the <a href="https://dustinpoissant.github.io/kempo-css/" target="_blank" rel="noopener noreferrer">Kempo CSS documentation</a> for details on available color variables and theming.</p>
|
|
131
|
+
|
|
132
|
+
<h4>System Preference Detection</h4>
|
|
133
|
+
<p>When the theme is set to <code>'auto'</code>, the utility monitors the system's <code>prefers-color-scheme</code> media query and updates the <code>auto-theme</code> attribute on the document element. The <code>getCalculated()</code> method resolves the actual theme based on this preference.</p>
|
|
134
|
+
|
|
135
|
+
</main>
|
|
136
|
+
<div style="height:33vh"></div>
|
|
137
|
+
<script type="module" src="../src/components/Import.js"></script>
|
|
138
|
+
<script type="module" src="../src/components/Accordion.js"></script>
|
|
139
|
+
<script type="module" src="../src/components/Card.js"></script>
|
|
140
|
+
<script type="module">
|
|
141
|
+
import theme from '../src/utils/theme.js';
|
|
142
|
+
|
|
143
|
+
theme.subscribe(t => {
|
|
144
|
+
document.getElementById('theme-display').textContent = t;
|
|
145
|
+
document.getElementById('calculated-display').textContent = theme.getCalculated();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
document.getElementById('set-auto').addEventListener('click', () => theme.set('auto'));
|
|
149
|
+
document.getElementById('set-light').addEventListener('click', () => theme.set('light'));
|
|
150
|
+
document.getElementById('set-dark').addEventListener('click', () => theme.set('dark'));
|
|
151
|
+
</script>
|
|
152
|
+
</body>
|
|
153
|
+
</html>
|
package/package.json
CHANGED
|
@@ -75,6 +75,29 @@ export default class ColorPicker extends ShadowComponent {
|
|
|
75
75
|
|
|
76
76
|
if(changedProperties.has('red') || changedProperties.has('green') || changedProperties.has('blue') || changedProperties.has('alpha') || changedProperties.has('format')){
|
|
77
77
|
this.updateFormValue();
|
|
78
|
+
this.syncInputs(changedProperties.has('format'));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
syncInputs(formatChanged = false){
|
|
83
|
+
const textInput = this.shadowRoot?.querySelector('#text');
|
|
84
|
+
const colorInput = this.shadowRoot?.querySelector('#color');
|
|
85
|
+
const formatSelect = this.shadowRoot?.querySelector('#format');
|
|
86
|
+
const activeElement = this.shadowRoot?.activeElement;
|
|
87
|
+
|
|
88
|
+
// Always update text input if format changed, otherwise only if not focused
|
|
89
|
+
if(textInput && (formatChanged || activeElement !== textInput)){
|
|
90
|
+
textInput.value = this.value;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Update color input if it's not focused
|
|
94
|
+
if(colorInput && activeElement !== colorInput){
|
|
95
|
+
colorInput.value = this.constructor.formats.hex.toString(this.red || 0, this.green || 0, this.blue || 0, 1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Update format select if it's not focused
|
|
99
|
+
if(formatSelect && activeElement !== formatSelect){
|
|
100
|
+
formatSelect.value = this.format;
|
|
78
101
|
}
|
|
79
102
|
}
|
|
80
103
|
|
|
@@ -137,11 +160,42 @@ export default class ColorPicker extends ShadowComponent {
|
|
|
137
160
|
this.blue = parsed.b;
|
|
138
161
|
this.alpha = 1;
|
|
139
162
|
}
|
|
163
|
+
this.forwardEvent(event);
|
|
140
164
|
}
|
|
141
165
|
onTextInputChange(event){
|
|
142
166
|
this.value = event.target.value;
|
|
167
|
+
this.forwardEvent(event);
|
|
168
|
+
}
|
|
169
|
+
onTextInput(event){
|
|
170
|
+
this.value = event.target.value;
|
|
171
|
+
this.forwardEvent(event);
|
|
172
|
+
}
|
|
173
|
+
onColorInput(event){
|
|
174
|
+
const parsed = this.constructor.formats.hex.parse(event.target.value);
|
|
175
|
+
if (parsed) {
|
|
176
|
+
this.red = parsed.r;
|
|
177
|
+
this.green = parsed.g;
|
|
178
|
+
this.blue = parsed.b;
|
|
179
|
+
this.alpha = 1;
|
|
180
|
+
}
|
|
181
|
+
this.forwardEvent(event);
|
|
182
|
+
}
|
|
183
|
+
onFormatChange(event){
|
|
184
|
+
this.format = event.target.value;
|
|
185
|
+
this.forwardEvent(event);
|
|
186
|
+
}
|
|
187
|
+
onFormatInput(event){
|
|
188
|
+
this.format = event.target.value;
|
|
189
|
+
this.forwardEvent(event);
|
|
143
190
|
}
|
|
144
191
|
|
|
192
|
+
/*
|
|
193
|
+
Event Forwarding
|
|
194
|
+
*/
|
|
195
|
+
forwardEvent = (event) => {
|
|
196
|
+
this.dispatchEvent(new event.constructor(event.type, event));
|
|
197
|
+
};
|
|
198
|
+
|
|
145
199
|
/* Rendering */
|
|
146
200
|
render(){
|
|
147
201
|
return html`
|
|
@@ -149,7 +203,10 @@ export default class ColorPicker extends ShadowComponent {
|
|
|
149
203
|
<select
|
|
150
204
|
id="format"
|
|
151
205
|
value="${this.format}"
|
|
152
|
-
@change=${
|
|
206
|
+
@change=${this.onFormatChange}
|
|
207
|
+
@input=${this.onFormatInput}
|
|
208
|
+
@focus=${this.forwardEvent}
|
|
209
|
+
@blur=${this.forwardEvent}
|
|
153
210
|
>${
|
|
154
211
|
this.constructor.formats ? Object.keys(this.constructor.formats).map(formatName => html`<option value="${formatName}" ?selected=${this.format === formatName}>${formatName.toUpperCase()}</option>`) : null
|
|
155
212
|
}</select>
|
|
@@ -158,6 +215,9 @@ export default class ColorPicker extends ShadowComponent {
|
|
|
158
215
|
type="text"
|
|
159
216
|
value="${this.value}"
|
|
160
217
|
@change=${this.onTextInputChange}
|
|
218
|
+
@input=${this.onTextInput}
|
|
219
|
+
@focus=${this.forwardEvent}
|
|
220
|
+
@blur=${this.forwardEvent}
|
|
161
221
|
/>
|
|
162
222
|
<div id="color-wrapper">
|
|
163
223
|
<input
|
|
@@ -165,6 +225,9 @@ export default class ColorPicker extends ShadowComponent {
|
|
|
165
225
|
type="color"
|
|
166
226
|
value="${this.constructor.formats.hex.toString(this.red || 0, this.green || 0, this.blue || 0, 1)}"
|
|
167
227
|
@change=${this.onColorInputChange}
|
|
228
|
+
@input=${this.onColorInput}
|
|
229
|
+
@focus=${this.forwardEvent}
|
|
230
|
+
@blur=${this.forwardEvent}
|
|
168
231
|
/>
|
|
169
232
|
</div>
|
|
170
233
|
</div>
|