From 7f02c9960dd65e4c79cf542a86ce2d97dd4786ad Mon Sep 17 00:00:00 2001 From: Mahdi Hosseinzadeh Date: Sun, 13 Mar 2022 18:47:46 +0330 Subject: [PATCH] Add and migrate to Rollup version of scripts --- .github/workflows/publish-new-release.yml | 2 +- README.md | 4 +- demo/index.html | 2 +- dist/theme-switch.js | 270 ++++++++++++++++++++++ dist/theme-switch.min.js | 2 + dist/theme-switch.min.js.map | 1 + package.json | 14 +- test/template-1.html | 2 +- test/template-2.html | 2 +- test/template-3.html | 2 +- test/template-4.html | 2 +- test/template-5.html | 2 +- test/theme-switch.test.js | 2 +- 13 files changed, 288 insertions(+), 19 deletions(-) create mode 100644 dist/theme-switch.js create mode 100644 dist/theme-switch.min.js create mode 100644 dist/theme-switch.min.js.map diff --git a/.github/workflows/publish-new-release.yml b/.github/workflows/publish-new-release.yml index c039592..8ef238c 100644 --- a/.github/workflows/publish-new-release.yml +++ b/.github/workflows/publish-new-release.yml @@ -121,4 +121,4 @@ jobs: uses: softprops/action-gh-release@v1 with: body_path: changelog.txt - files: theme-switch.min.js + files: dist/theme-switch.min.js diff --git a/README.md b/README.md index 9fa324c..b95454c 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ and [this library](https://github.com/GoogleChromeLabs/dark-mode-toggle). ## Use it in your page -Download the [theme-switch.min.js](theme-switch.min.js) file and reference it at the top of your HTML: +Download the [theme-switch.min.js](dist/theme-switch.min.js) file and reference it at the top of your HTML: ```html @@ -119,7 +119,7 @@ In your *angular.json* file at the root of your project update the `scripts` pro ```json "scripts": [ { - "input": "node_modules/@mahozad/theme-switch/theme-switch.min.js", + "input": "node_modules/@mahozad/theme-switch/dist/theme-switch.min.js", "inject": false, "bundleName": "theme-switch" } diff --git a/demo/index.html b/demo/index.html index 6fe9498..30248e4 100644 --- a/demo/index.html +++ b/demo/index.html @@ -3,7 +3,7 @@ Theme switch demo - + diff --git a/dist/theme-switch.js b/dist/theme-switch.js new file mode 100644 index 0000000..a3944fb --- /dev/null +++ b/dist/theme-switch.js @@ -0,0 +1,270 @@ +'use strict'; + +/* +* NOTE: Do not use this script as an ES6 module. +* ES6 modules are deferred and we don't want that because +* we want the user previous theme selection to be applied +* as soon as possible (before the page is rendered). +* */ + +/* +* There are two types of modules mostly used in JavaScript. +* One is created by Node.js and is used inside the Node environment +* and has been available for a long time. It is called CommonJS. +* Another is the standard native JavaScript modules introduced in ES6. +* +* The Node variant (CommonJS) uses `module.exports` (or simply `exports`) and +* `require()` to export and import scripts, functions, variables, etc. +* Browsers do not know about `exports` or `require` functions and throw error +* because they are objects and functions created just in Node environment and set globally. +* If you want to use this type of module in browsers, you should bundle the files +* (merge all of them into a single JS file which eliminates the need for exports and require) +* with tools like babel, webpack, rollup, etc. +* +* ES6 modules use `export` and `import` keywords for the same purpose. +* +* Example Node modules: +* +* my-calculator.js +* const PI = 3.14; +* function calculate() {} +* modules.exports.calculate = calculate; +* modules.exports.PI = PI; +* +* main.js +* const calculator = require("my-calculator"); +* const perimeter = 2 * calculator.PI; +* const result = calculator.calculate(); +* +* Example ES6 modules: +* +* my-calculator.js +* export const PI = 3.14; +* export function calculate() {} +* +* main.js +* import { calculate, PI } from "my-calculator.js"; +* const perimeter = 2 * PI; +* const result = calculate(); +* +* See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules +* and https://stackoverflow.com/a/9901097/8583692 +* */ + +/* +* Minify the script either through command line with babel: +* - babel theme-switch.js --source-maps --out-file result.min.js +* or with babel-minify (also has an alias called minify) which is useful if +* you don't already use babel (as a preset) or want to run minification standalone. +* Note that it does not take into account babel.config.json settings. +* - babel-minify (or minify) theme-switch.js --mangle --no-comments --out-file result.min.js` +* Or automate it with IntelliJ file watcher +* - babel +* + program: $ProjectFileDir$\node_modules\.bin\babel +* + arguments: $FilePathRelativeToProjectRoot$ --out-file $FileNameWithoutExtension$.min.js --source-maps +* Note that setting "sourceMaps": "true" in babel.config.json does not work because of +* this bug: https://github.com/babel/babel/issues/5261 ("sourceMaps": "inline" works, however) +* - UglifyJS +* - YUI compressor (seems to be deprecated and removed in newer versions of IntelliJ) +* +* Babel preset-env inserts a semicolon at the start of the minified file. +* See why: https://stackoverflow.com/q/1873983/8583692 +* */ + +// TODO: extract Jest configuration to a jest.config.js file +// TODO: Add an attribute so the user can define key name stored in localstorage +// TODO: Make the component customizable by adding custom attributes: +// See https://medium.com/technofunnel/creating-passing-data-to-html-custom-elements-using-attributes-bfd9aa759fd4 + +/* +* NOTE: To avoid name collisions if another script declares variables or functions with the same name +* as ours (i.e. defining them in the global scope) and browsers complaining about identifiers +* being redeclared, we wrap all our code in a closure or IIFE (sort of creating a namespace for it). +* ES6 now supports block scope as well (simply wrapping the whole code in {}). +* I am now using the babel-plugin-iife-wrap plugin to wrap the whole result (minified) +* code in an IIFE. +* For examples, see these libraries: +* - https://github.com/highlightjs/highlight.js/blob/main/src/highlight.js +* - https://github.com/jashkenas/underscore/blob/master/underscore.js +* See +* - https://www.w3schools.com/js/js_scope.asp +* - https://github.com/jhnns/rewire/issues/136#issuecomment-380829197 +* - https://stackoverflow.com/a/32750216/8583692 +* - https://stackoverflow.com/q/8228281/8583692 +* - https://stackoverflow.com/q/881515/8583692 +* - https://stackoverflow.com/q/39388777/8583692 +* - https://stackoverflow.com/a/47207686/8583692 +* We could also do something like these libraries: +* - https://github.com/juliangarnier/anime/blob/master/build.js +* - https://github.com/mrdoob/three.js/ +* - https://github.com/moment/moment +* - https://github.com/floating-ui/floating-ui +* */ + +const ELEMENT_NAME = "theme-switch"; +const ICON_SIZE = 24 /* px */; +const ICON_COLOR = "#000"; +const THEME_KEY = "theme"; +const THEME_AUTO = "auto"; +const THEME_DARK = "dark"; +const THEME_LIGHT = "light"; +const THEME_VALUES = [THEME_AUTO, THEME_DARK, THEME_LIGHT]; +const THEME_DEFAULT = THEME_LIGHT; +const THEME_ATTRIBUTE = "data-theme"; +const COLOR_SCHEME_DARK = "(prefers-color-scheme: dark)"; +const CUSTOM_EVENT_NAME = "themeToggle"; +// circleRadius, raysOpacity, eclipseCenterX, letterOffset +const ICON_INITIAL_STATE_FOR_AUTO = [10, 0, 33, 0]; +const ICON_INITIAL_STATE_FOR_DARK = [10, 0, 20, 1]; +const ICON_INITIAL_STATE_FOR_LIGHT = [5, 1, 33, 1]; + +class ThemeSwitchElement extends HTMLElement { + shadowRoot; + static counter = 0; // See https://stackoverflow.com/a/43116254/8583692 + identifier = ThemeSwitchElement.counter++; + + constructor() { + super(); + // See https://stackoverflow.com/q/2305654/8583692 + this.shadowRoot = this.attachShadow({ mode: "open" }); + this.shadowRoot.innerHTML = generateIcon(...getInitialStateForIcon()); + // Add the click listener to the top-most parent (the custom element itself) + // so the padding etc. on the element be also clickable + this.shadowRoot.host.addEventListener("click", this.onClick); + // If another theme switch in page toggled, update my icon too + document.addEventListener(CUSTOM_EVENT_NAME, event => { + if (event.detail.originId !== this.identifier) { + this.adaptToTheme(); + } + }); + // Create some CSS to apply to the shadow DOM + // See https://css-tricks.com/styling-a-web-component/ + const style = document.createElement("style"); + style.textContent = generateStyle(); + this.shadowRoot.append(style); + } + + onClick() { + const oldTheme = getUserThemeSelection(); + this.toggleTheme(oldTheme); + const newTheme = getUserThemeSelection(); + const event = this.createEvent(oldTheme, newTheme); + this.dispatchEvent(event); + } + + // See https://stackoverflow.com/a/53804106/8583692 + createEvent(oldTheme, newTheme) { + return new CustomEvent(CUSTOM_EVENT_NAME, { + detail: { + originId: this.identifier, + oldState: oldTheme, + newState: newTheme + }, + bubbles: true, + composed: true, + cancelable: false + }); + } + + // See https://stackoverflow.com/q/48316611 + toggleTheme(currentTheme) { + if (currentTheme === THEME_AUTO) { + localStorage.setItem(THEME_KEY, THEME_LIGHT); + this.animateThemeButtonIconToLight(); + } else if (currentTheme === THEME_DARK) { + localStorage.setItem(THEME_KEY, THEME_AUTO); + this.animateThemeButtonIconToAuto(); + } else /* if (theme === THEME_LIGHT) */ { + localStorage.setItem(THEME_KEY, THEME_DARK); + this.animateThemeButtonIconToDark(); + } + updateTheme(); + } + + adaptToTheme() { + const theme = getUserThemeSelection(); + if (theme === THEME_AUTO) { + this.animateThemeButtonIconToAuto(); + } else if (theme === THEME_DARK) { + this.animateThemeButtonIconToDark(); + } else /* if (theme === THEME_LIGHT) */ { + this.animateThemeButtonIconToLight(); + } + } + + animateThemeButtonIconToLight() { + this.shadowRoot.getElementById("letter-anim-hide").beginElement(); + this.shadowRoot.getElementById("core-anim-shrink").beginElement(); + this.shadowRoot.getElementById("rays-anim-rotate").beginElement(); + this.shadowRoot.getElementById("rays-anim-show").beginElement(); + } + + animateThemeButtonIconToAuto() { + this.shadowRoot.getElementById("eclipse-anim-go").beginElement(); + this.shadowRoot.getElementById("letter-anim-show").beginElement(); + } + + animateThemeButtonIconToDark() { + this.shadowRoot.getElementById("rays-anim-hide").beginElement(); + this.shadowRoot.getElementById("core-anim-enlarge").beginElement(); + this.shadowRoot.getElementById("eclipse-anim-come").beginElement(); + } +} + +function generateIcon(circleRadius, raysOpacity, eclipseCenterX, letterOffset) { + return ` `; +} + +function generateStyle() { + return `:host{display:flex;width:var(--dummy-variable,${ICON_SIZE}px);aspect-ratio:1/1;cursor:pointer}:host([hidden]){display:none}button{padding:0;border:none;background:0 0;display:flex;cursor:pointer}#circle{fill:var(--theme-switch-icon-color,${ICON_COLOR})}#rays{stroke:var(--theme-switch-icon-color,${ICON_COLOR})}`; +} + +updateTheme(); +window.customElements.define(ELEMENT_NAME, ThemeSwitchElement); +window + .matchMedia(COLOR_SCHEME_DARK) + .addEventListener("change", updateTheme); + +function updateTheme() { + let theme = getUserThemeSelection(); + if (theme === THEME_AUTO) theme = getSystemTheme(); + document.documentElement.setAttribute(THEME_ATTRIBUTE, theme); +} + +function getUserThemeSelection() { + const userSelection = localStorage.getItem(THEME_KEY); + return THEME_VALUES.includes(userSelection) ? userSelection : THEME_DEFAULT; +} + +function getSystemTheme() { + const isDark = window.matchMedia(COLOR_SCHEME_DARK).matches; + return isDark ? THEME_DARK : THEME_LIGHT; +} + +function getInitialStateForIcon() { + const theme = getUserThemeSelection(); + if (theme === THEME_AUTO) { + return ICON_INITIAL_STATE_FOR_AUTO; + } else if (theme === THEME_DARK) { + return ICON_INITIAL_STATE_FOR_DARK; + } else /* if (theme === THEME_LIGHT) */ { + return ICON_INITIAL_STATE_FOR_LIGHT; + } +} + +// Export for tests run by npm (no longer needed; kept for future reference) +// See https://stackoverflow.com/q/63752210/8583692 +// and https://stackoverflow.com/a/54680602/8583692 +// and https://stackoverflow.com/q/43042889/8583692 +// and https://stackoverflow.com/a/1984728/8583692 +// if (typeof module !== "undefined") { +// module.exports = { +// updateTheme, +// toggleTheme, +// getSystemTheme, +// getInitialStateForIcon, +// animateThemeButtonIconToAuto, +// animateThemeButtonIconToDark, +// animateThemeButtonIconToLight +// }; +// } diff --git a/dist/theme-switch.min.js b/dist/theme-switch.min.js new file mode 100644 index 0000000..f46375a --- /dev/null +++ b/dist/theme-switch.min.js @@ -0,0 +1,2 @@ +!function(){"use strict";const e="theme",t="auto",i="dark",n="light",o=[t,i,n],a="(prefers-color-scheme: dark)",s="themeToggle",m=[10,0,33,0],r=[10,0,20,1],d=[5,1,33,1];class l extends HTMLElement{shadowRoot;static counter=0;identifier=l.counter++;constructor(){super(),this.shadowRoot=this.attachShadow({mode:"open"}),this.shadowRoot.innerHTML=function(e,t,i,n){return` `}(...function(){const e=h();return e===t?m:e===i?r:d}()),this.shadowRoot.host.addEventListener("click",this.onClick),document.addEventListener(s,(e=>{e.detail.originId!==this.identifier&&this.adaptToTheme()}));const e=document.createElement("style");e.textContent=":host{display:flex;width:var(--dummy-variable,24px);aspect-ratio:1/1;cursor:pointer}:host([hidden]){display:none}button{padding:0;border:none;background:0 0;display:flex;cursor:pointer}#circle{fill:var(--theme-switch-icon-color,#000)}#rays{stroke:var(--theme-switch-icon-color,#000)}",this.shadowRoot.append(e)}onClick(){const e=h();this.toggleTheme(e);const t=h(),i=this.createEvent(e,t);this.dispatchEvent(i)}createEvent(e,t){return new CustomEvent(s,{detail:{originId:this.identifier,oldState:e,newState:t},bubbles:!0,composed:!0,cancelable:!1})}toggleTheme(o){o===t?(localStorage.setItem(e,n),this.animateThemeButtonIconToLight()):o===i?(localStorage.setItem(e,t),this.animateThemeButtonIconToAuto()):(localStorage.setItem(e,i),this.animateThemeButtonIconToDark()),c()}adaptToTheme(){const e=h();e===t?this.animateThemeButtonIconToAuto():e===i?this.animateThemeButtonIconToDark():this.animateThemeButtonIconToLight()}animateThemeButtonIconToLight(){this.shadowRoot.getElementById("letter-anim-hide").beginElement(),this.shadowRoot.getElementById("core-anim-shrink").beginElement(),this.shadowRoot.getElementById("rays-anim-rotate").beginElement(),this.shadowRoot.getElementById("rays-anim-show").beginElement()}animateThemeButtonIconToAuto(){this.shadowRoot.getElementById("eclipse-anim-go").beginElement(),this.shadowRoot.getElementById("letter-anim-show").beginElement()}animateThemeButtonIconToDark(){this.shadowRoot.getElementById("rays-anim-hide").beginElement(),this.shadowRoot.getElementById("core-anim-enlarge").beginElement(),this.shadowRoot.getElementById("eclipse-anim-come").beginElement()}}function c(){let e=h();e===t&&(e=window.matchMedia(a).matches?i:n),document.documentElement.setAttribute("data-theme",e)}function h(){const t=localStorage.getItem(e);return o.includes(t)?t:"light"}c(),window.customElements.define("theme-switch",l),window.matchMedia(a).addEventListener("change",c)}(); +//# sourceMappingURL=theme-switch.min.js.map diff --git a/dist/theme-switch.min.js.map b/dist/theme-switch.min.js.map new file mode 100644 index 0000000..e3ad11e --- /dev/null +++ b/dist/theme-switch.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"theme-switch.min.js","sources":["../theme-switch.js"],"sourcesContent":["/*\r\n* NOTE: Do not use this script as an ES6 module.\r\n* ES6 modules are deferred and we don't want that because\r\n* we want the user previous theme selection to be applied\r\n* as soon as possible (before the page is rendered).\r\n* */\r\n\r\n/*\r\n* There are two types of modules mostly used in JavaScript.\r\n* One is created by Node.js and is used inside the Node environment\r\n* and has been available for a long time. It is called CommonJS.\r\n* Another is the standard native JavaScript modules introduced in ES6.\r\n*\r\n* The Node variant (CommonJS) uses `module.exports` (or simply `exports`) and\r\n* `require()` to export and import scripts, functions, variables, etc.\r\n* Browsers do not know about `exports` or `require` functions and throw error\r\n* because they are objects and functions created just in Node environment and set globally.\r\n* If you want to use this type of module in browsers, you should bundle the files\r\n* (merge all of them into a single JS file which eliminates the need for exports and require)\r\n* with tools like babel, webpack, rollup, etc.\r\n*\r\n* ES6 modules use `export` and `import` keywords for the same purpose.\r\n*\r\n* Example Node modules:\r\n*\r\n* my-calculator.js\r\n* const PI = 3.14;\r\n* function calculate() {}\r\n* modules.exports.calculate = calculate;\r\n* modules.exports.PI = PI;\r\n*\r\n* main.js\r\n* const calculator = require(\"my-calculator\");\r\n* const perimeter = 2 * calculator.PI;\r\n* const result = calculator.calculate();\r\n*\r\n* Example ES6 modules:\r\n*\r\n* my-calculator.js\r\n* export const PI = 3.14;\r\n* export function calculate() {}\r\n*\r\n* main.js\r\n* import { calculate, PI } from \"my-calculator.js\";\r\n* const perimeter = 2 * PI;\r\n* const result = calculate();\r\n*\r\n* See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules\r\n* and https://stackoverflow.com/a/9901097/8583692\r\n* */\r\n\r\n/*\r\n* Minify the script either through command line with babel:\r\n* - babel theme-switch.js --source-maps --out-file result.min.js\r\n* or with babel-minify (also has an alias called minify) which is useful if\r\n* you don't already use babel (as a preset) or want to run minification standalone.\r\n* Note that it does not take into account babel.config.json settings.\r\n* - babel-minify (or minify) theme-switch.js --mangle --no-comments --out-file result.min.js`\r\n* Or automate it with IntelliJ file watcher\r\n* - babel\r\n* + program: $ProjectFileDir$\\node_modules\\.bin\\babel\r\n* + arguments: $FilePathRelativeToProjectRoot$ --out-file $FileNameWithoutExtension$.min.js --source-maps\r\n* Note that setting \"sourceMaps\": \"true\" in babel.config.json does not work because of\r\n* this bug: https://github.com/babel/babel/issues/5261 (\"sourceMaps\": \"inline\" works, however)\r\n* - UglifyJS\r\n* - YUI compressor (seems to be deprecated and removed in newer versions of IntelliJ)\r\n*\r\n* Babel preset-env inserts a semicolon at the start of the minified file.\r\n* See why: https://stackoverflow.com/q/1873983/8583692\r\n* */\r\n\r\n// TODO: extract Jest configuration to a jest.config.js file\r\n// TODO: Add an attribute so the user can define key name stored in localstorage\r\n// TODO: Make the component customizable by adding custom attributes:\r\n// See https://medium.com/technofunnel/creating-passing-data-to-html-custom-elements-using-attributes-bfd9aa759fd4\r\n\r\n/*\r\n* NOTE: To avoid name collisions if another script declares variables or functions with the same name\r\n* as ours (i.e. defining them in the global scope) and browsers complaining about identifiers\r\n* being redeclared, we wrap all our code in a closure or IIFE (sort of creating a namespace for it).\r\n* ES6 now supports block scope as well (simply wrapping the whole code in {}).\r\n* I am now using the babel-plugin-iife-wrap plugin to wrap the whole result (minified)\r\n* code in an IIFE.\r\n* For examples, see these libraries:\r\n* - https://github.com/highlightjs/highlight.js/blob/main/src/highlight.js\r\n* - https://github.com/jashkenas/underscore/blob/master/underscore.js\r\n* See\r\n* - https://www.w3schools.com/js/js_scope.asp\r\n* - https://github.com/jhnns/rewire/issues/136#issuecomment-380829197\r\n* - https://stackoverflow.com/a/32750216/8583692\r\n* - https://stackoverflow.com/q/8228281/8583692\r\n* - https://stackoverflow.com/q/881515/8583692\r\n* - https://stackoverflow.com/q/39388777/8583692\r\n* - https://stackoverflow.com/a/47207686/8583692\r\n* We could also do something like these libraries:\r\n* - https://github.com/juliangarnier/anime/blob/master/build.js\r\n* - https://github.com/mrdoob/three.js/\r\n* - https://github.com/moment/moment\r\n* - https://github.com/floating-ui/floating-ui\r\n* */\r\n\r\nconst ELEMENT_NAME = \"theme-switch\";\r\nconst ICON_SIZE = 24 /* px */;\r\nconst ICON_COLOR = \"#000\";\r\nconst THEME_KEY = \"theme\";\r\nconst THEME_AUTO = \"auto\";\r\nconst THEME_DARK = \"dark\";\r\nconst THEME_LIGHT = \"light\";\r\nconst THEME_VALUES = [THEME_AUTO, THEME_DARK, THEME_LIGHT];\r\nconst THEME_DEFAULT = THEME_LIGHT;\r\nconst THEME_ATTRIBUTE = \"data-theme\";\r\nconst COLOR_SCHEME_DARK = \"(prefers-color-scheme: dark)\";\r\nconst CUSTOM_EVENT_NAME = \"themeToggle\";\r\n// circleRadius, raysOpacity, eclipseCenterX, letterOffset\r\nconst ICON_INITIAL_STATE_FOR_AUTO = [10, 0, 33, 0];\r\nconst ICON_INITIAL_STATE_FOR_DARK = [10, 0, 20, 1];\r\nconst ICON_INITIAL_STATE_FOR_LIGHT = [5, 1, 33, 1];\r\n\r\nclass ThemeSwitchElement extends HTMLElement {\r\n shadowRoot;\r\n static counter = 0; // See https://stackoverflow.com/a/43116254/8583692\r\n identifier = ThemeSwitchElement.counter++;\r\n\r\n constructor() {\r\n super();\r\n // See https://stackoverflow.com/q/2305654/8583692\r\n this.shadowRoot = this.attachShadow({ mode: \"open\" });\r\n this.shadowRoot.innerHTML = generateIcon(...getInitialStateForIcon());\r\n // Add the click listener to the top-most parent (the custom element itself)\r\n // so the padding etc. on the element be also clickable\r\n this.shadowRoot.host.addEventListener(\"click\", this.onClick);\r\n // If another theme switch in page toggled, update my icon too\r\n document.addEventListener(CUSTOM_EVENT_NAME, event => {\r\n if (event.detail.originId !== this.identifier) {\r\n this.adaptToTheme();\r\n }\r\n });\r\n // Create some CSS to apply to the shadow DOM\r\n // See https://css-tricks.com/styling-a-web-component/\r\n const style = document.createElement(\"style\");\r\n style.textContent = generateStyle();\r\n this.shadowRoot.append(style);\r\n }\r\n\r\n onClick() {\r\n const oldTheme = getUserThemeSelection();\r\n this.toggleTheme(oldTheme);\r\n const newTheme = getUserThemeSelection();\r\n const event = this.createEvent(oldTheme, newTheme);\r\n this.dispatchEvent(event);\r\n }\r\n\r\n // See https://stackoverflow.com/a/53804106/8583692\r\n createEvent(oldTheme, newTheme) {\r\n return new CustomEvent(CUSTOM_EVENT_NAME, {\r\n detail: {\r\n originId: this.identifier,\r\n oldState: oldTheme,\r\n newState: newTheme\r\n },\r\n bubbles: true,\r\n composed: true,\r\n cancelable: false\r\n });\r\n }\r\n\r\n // See https://stackoverflow.com/q/48316611\r\n toggleTheme(currentTheme) {\r\n if (currentTheme === THEME_AUTO) {\r\n localStorage.setItem(THEME_KEY, THEME_LIGHT);\r\n this.animateThemeButtonIconToLight();\r\n } else if (currentTheme === THEME_DARK) {\r\n localStorage.setItem(THEME_KEY, THEME_AUTO);\r\n this.animateThemeButtonIconToAuto();\r\n } else /* if (theme === THEME_LIGHT) */ {\r\n localStorage.setItem(THEME_KEY, THEME_DARK);\r\n this.animateThemeButtonIconToDark();\r\n }\r\n updateTheme();\r\n }\r\n\r\n adaptToTheme() {\r\n const theme = getUserThemeSelection();\r\n if (theme === THEME_AUTO) {\r\n this.animateThemeButtonIconToAuto();\r\n } else if (theme === THEME_DARK) {\r\n this.animateThemeButtonIconToDark();\r\n } else /* if (theme === THEME_LIGHT) */ {\r\n this.animateThemeButtonIconToLight();\r\n }\r\n }\r\n\r\n animateThemeButtonIconToLight() {\r\n this.shadowRoot.getElementById(\"letter-anim-hide\").beginElement();\r\n this.shadowRoot.getElementById(\"core-anim-shrink\").beginElement();\r\n this.shadowRoot.getElementById(\"rays-anim-rotate\").beginElement();\r\n this.shadowRoot.getElementById(\"rays-anim-show\").beginElement();\r\n }\r\n\r\n animateThemeButtonIconToAuto() {\r\n this.shadowRoot.getElementById(\"eclipse-anim-go\").beginElement();\r\n this.shadowRoot.getElementById(\"letter-anim-show\").beginElement();\r\n }\r\n\r\n animateThemeButtonIconToDark() {\r\n this.shadowRoot.getElementById(\"rays-anim-hide\").beginElement();\r\n this.shadowRoot.getElementById(\"core-anim-enlarge\").beginElement();\r\n this.shadowRoot.getElementById(\"eclipse-anim-come\").beginElement();\r\n }\r\n}\r\n\r\nfunction generateIcon(circleRadius, raysOpacity, eclipseCenterX, letterOffset) {\r\n return `ICON_TEMPLATE`;\r\n}\r\n\r\nfunction generateStyle() {\r\n return `STYLES_TEMPLATE`;\r\n}\r\n\r\nupdateTheme();\r\nwindow.customElements.define(ELEMENT_NAME, ThemeSwitchElement);\r\nwindow\r\n .matchMedia(COLOR_SCHEME_DARK)\r\n .addEventListener(\"change\", updateTheme);\r\n\r\nfunction updateTheme() {\r\n let theme = getUserThemeSelection();\r\n if (theme === THEME_AUTO) theme = getSystemTheme();\r\n document.documentElement.setAttribute(THEME_ATTRIBUTE, theme);\r\n}\r\n\r\nfunction getUserThemeSelection() {\r\n const userSelection = localStorage.getItem(THEME_KEY);\r\n return THEME_VALUES.includes(userSelection) ? userSelection : THEME_DEFAULT;\r\n}\r\n\r\nfunction getSystemTheme() {\r\n const isDark = window.matchMedia(COLOR_SCHEME_DARK).matches;\r\n return isDark ? THEME_DARK : THEME_LIGHT;\r\n}\r\n\r\nfunction getInitialStateForIcon() {\r\n const theme = getUserThemeSelection();\r\n if (theme === THEME_AUTO) {\r\n return ICON_INITIAL_STATE_FOR_AUTO;\r\n } else if (theme === THEME_DARK) {\r\n return ICON_INITIAL_STATE_FOR_DARK;\r\n } else /* if (theme === THEME_LIGHT) */ {\r\n return ICON_INITIAL_STATE_FOR_LIGHT;\r\n }\r\n}\r\n\r\n// Export for tests run by npm (no longer needed; kept for future reference)\r\n// See https://stackoverflow.com/q/63752210/8583692\r\n// and https://stackoverflow.com/a/54680602/8583692\r\n// and https://stackoverflow.com/q/43042889/8583692\r\n// and https://stackoverflow.com/a/1984728/8583692\r\n// if (typeof module !== \"undefined\") {\r\n// module.exports = {\r\n// updateTheme,\r\n// toggleTheme,\r\n// getSystemTheme,\r\n// getInitialStateForIcon,\r\n// animateThemeButtonIconToAuto,\r\n// animateThemeButtonIconToDark,\r\n// animateThemeButtonIconToLight\r\n// };\r\n// }\r\n"],"names":["THEME_KEY","THEME_AUTO","THEME_DARK","THEME_LIGHT","THEME_VALUES","COLOR_SCHEME_DARK","CUSTOM_EVENT_NAME","ICON_INITIAL_STATE_FOR_AUTO","ICON_INITIAL_STATE_FOR_DARK","ICON_INITIAL_STATE_FOR_LIGHT","ThemeSwitchElement","HTMLElement","shadowRoot","static","identifier","counter","constructor","super","this","attachShadow","mode","innerHTML","circleRadius","raysOpacity","eclipseCenterX","letterOffset","generateIcon","theme","getUserThemeSelection","getInitialStateForIcon","host","addEventListener","onClick","document","event","detail","originId","adaptToTheme","style","createElement","textContent","append","oldTheme","toggleTheme","newTheme","createEvent","dispatchEvent","CustomEvent","oldState","newState","bubbles","composed","cancelable","currentTheme","localStorage","setItem","animateThemeButtonIconToLight","animateThemeButtonIconToAuto","animateThemeButtonIconToDark","updateTheme","getElementById","beginElement","window","matchMedia","matches","documentElement","setAttribute","userSelection","getItem","includes","customElements","define"],"mappings":"yBAqGA,MAGMA,EAAY,QACZC,EAAa,OACbC,EAAa,OACbC,EAAc,QACdC,EAAe,CAACH,EAAYC,EAAYC,GAGxCE,EAAoB,+BACpBC,EAAoB,cAEpBC,EAA8B,CAAC,GAAI,EAAG,GAAI,GAC1CC,EAA8B,CAAC,GAAI,EAAG,GAAI,GAC1CC,EAA+B,CAAC,EAAG,EAAG,GAAI,GAEhD,MAAMC,UAA2BC,YAC7BC,WACAC,eAAiB,EACjBC,WAAaJ,EAAmBK,UAEhCC,cACIC,QAEAC,KAAKN,WAAaM,KAAKC,aAAa,CAAEC,KAAM,SAC5CF,KAAKN,WAAWS,UAoFxB,SAAsBC,EAAcC,EAAaC,EAAgBC,GAC7D,MAAO,8NAAcD,+gBAAAC,seAAAF,44BAAAD,uYArFWI,IAkHpC,WACI,MAAMC,EAAQC,IACd,OAAID,IAAU1B,EACHM,EACAoB,IAAUzB,EACVM,EAEAC,EAzHqCoB,IAG5CX,KAAKN,WAAWkB,KAAKC,iBAAiB,QAASb,KAAKc,SAEpDC,SAASF,iBAAiBzB,GAAmB4B,IACrCA,EAAMC,OAAOC,WAAalB,KAAKJ,YAC/BI,KAAKmB,kBAKb,MAAMC,EAAQL,SAASM,cAAc,SACrCD,EAAME,YA4EH,8RA3EHtB,KAAKN,WAAW6B,OAAOH,GAG3BN,UACI,MAAMU,EAAWd,IACjBV,KAAKyB,YAAYD,GACjB,MAAME,EAAWhB,IACXM,EAAQhB,KAAK2B,YAAYH,EAAUE,GACzC1B,KAAK4B,cAAcZ,GAIvBW,YAAYH,EAAUE,GAClB,OAAO,IAAIG,YAAYzC,EAAmB,CACtC6B,OAAQ,CACJC,SAAUlB,KAAKJ,WACfkC,SAAUN,EACVO,SAAUL,GAEdM,SAAS,EACTC,UAAU,EACVC,YAAY,IAKpBT,YAAYU,GACJA,IAAiBpD,GACjBqD,aAAaC,QAAQvD,EAAWG,GAChCe,KAAKsC,iCACEH,IAAiBnD,GACxBoD,aAAaC,QAAQvD,EAAWC,GAChCiB,KAAKuC,iCAELH,aAAaC,QAAQvD,EAAWE,GAChCgB,KAAKwC,gCAETC,IAGJtB,eACI,MAAMV,EAAQC,IACVD,IAAU1B,EACViB,KAAKuC,+BACE9B,IAAUzB,EACjBgB,KAAKwC,+BAELxC,KAAKsC,gCAIbA,gCACItC,KAAKN,WAAWgD,eAAe,oBAAoBC,eACnD3C,KAAKN,WAAWgD,eAAe,oBAAoBC,eACnD3C,KAAKN,WAAWgD,eAAe,oBAAoBC,eACnD3C,KAAKN,WAAWgD,eAAe,kBAAkBC,eAGrDJ,+BACIvC,KAAKN,WAAWgD,eAAe,mBAAmBC,eAClD3C,KAAKN,WAAWgD,eAAe,oBAAoBC,eAGvDH,+BACIxC,KAAKN,WAAWgD,eAAe,kBAAkBC,eACjD3C,KAAKN,WAAWgD,eAAe,qBAAqBC,eACpD3C,KAAKN,WAAWgD,eAAe,qBAAqBC,gBAkB5D,SAASF,IACL,IAAIhC,EAAQC,IACRD,IAAU1B,IAAY0B,EAUXmC,OAAOC,WAAW1D,GAAmB2D,QACpC9D,EAAaC,GAV7B8B,SAASgC,gBAAgBC,aAtHL,aAsHmCvC,GAG3D,SAASC,IACL,MAAMuC,EAAgBb,aAAac,QAAQpE,GAC3C,OAAOI,EAAaiE,SAASF,GAAiBA,EA5H5BhE,QA8GtBwD,IACAG,OAAOQ,eAAeC,OAvHD,eAuHsB7D,GAC3CoD,OACKC,WAAW1D,GACX0B,iBAAiB,SAAU4B"} \ No newline at end of file diff --git a/package.json b/package.json index 8d2a78b..fac22e5 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "test-continuous": "jest --env=node --colors --watch-all test", "test-with-coverage": "jest --env=node --colors --coverage test", "lint": "eslint --color theme-switch.js", - "lint-html": "eslint --format html --output-file lint.html theme-switch.js", + "lint-html": "eslint --format html --output-file lint.html dist/theme-switch.js", "prepublishOnly": "npm test" }, "jest": { @@ -52,14 +52,10 @@ "name": "@mahozad/theme-switch", "version": "1.2.1", "description": "Animated toggle button to switch between light/dark/system theme.", - "main": "theme-switch.js", - "files": [ - "theme-switch.js", - "theme-switch.min.js", - "theme-switch.min.js.map" - ], - "unpkg": "theme-switch.min.js", - "jsdelivr": "theme-switch.min.js", + "main": "dist/theme-switch.js", + "files": ["dist/"], + "unpkg": "dist/theme-switch.min.js", + "jsdelivr": "dist/theme-switch.min.js", "keywords": [ "dark", "dark-mode", diff --git a/test/template-1.html b/test/template-1.html index b12b783..f00d36e 100644 --- a/test/template-1.html +++ b/test/template-1.html @@ -6,7 +6,7 @@ - + diff --git a/test/template-2.html b/test/template-2.html index 1c078aa..4ae5e18 100644 --- a/test/template-2.html +++ b/test/template-2.html @@ -3,7 +3,7 @@ Theme switch test 2 - +