From 0b344045f9bb91d82058c8ce1f3d775d74227b60 Mon Sep 17 00:00:00 2001 From: Jinoh Kang Date: Tue, 8 Feb 2022 17:12:07 +0900 Subject: [PATCH 1/6] feat: add theme color support --- README.md | 1 + docs/content/1.index.md | 4 +++- playground/nuxt.config.js | 9 ++++++++- src/module.ts | 4 ++++ src/script.ts | 18 ++++++++++++++++++ 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ab70bee8..a9cc2f4d 100755 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ - Nuxt 3 and Nuxt Bridge support - Add `.${color}-mode` class to `` for easy CSS theming +- Automatically set [theme color](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name/theme-color) of the page - Force a page to a specific color mode (perfect for incremental development) - Works with client-side and universal rendering - Auto detect system [color-mode](https://drafts.csswg.org/mediaqueries-5/#descdef-media-prefers-color-mode) diff --git a/docs/content/1.index.md b/docs/content/1.index.md index 37356a75..57d857b9 100644 --- a/docs/content/1.index.md +++ b/docs/content/1.index.md @@ -16,6 +16,7 @@ v3 of `@nuxtjs/color-mode` is compatible with [Nuxt 3 and Nuxt Bridge](https://v - Nuxt 3 and Nuxt Bridge support - Add `.${color}-mode` class to `` for easy CSS theming +- Automatically set [theme color](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name/theme-color) of the page - Force a page to a specific color mode (perfect for incremental development) - Works with client-side and universal rendering - Auto detect system [color-mode](https://drafts.csswg.org/mediaqueries-5/#descdef-media-prefers-color-mode) @@ -158,7 +159,8 @@ export default defineNuxtConfig({ componentName: 'ColorScheme', classPrefix: '', classSuffix: '-mode', - storageKey: 'nuxt-color-mode' + storageKey: 'nuxt-color-mode', + themeColors: null } }) ``` diff --git a/playground/nuxt.config.js b/playground/nuxt.config.js index 8fd092f8..0a202536 100644 --- a/playground/nuxt.config.js +++ b/playground/nuxt.config.js @@ -4,5 +4,12 @@ import colorModeModule from '..' export default defineNuxtConfig({ components: { global: true, dirs: ['~/components'] }, css: ['~/assets/main.css'], - modules: [colorModeModule] + modules: [colorModeModule], + colorMode: { + themeColors: { + dark: '#091a28', + light: '#f3f5f4', + sepia: '#f1e7d0' + } + } }) diff --git a/src/module.ts b/src/module.ts index c359b4cc..026ac0af 100644 --- a/src/module.ts +++ b/src/module.ts @@ -158,4 +158,8 @@ export interface ModuleOptions { * The script that will be injected into the head of the page */ script?: string + /** + * Default: null + */ + themeColors: Record | null } diff --git a/src/script.ts b/src/script.ts index 111beb5f..3a4ac3d7 100644 --- a/src/script.ts +++ b/src/script.ts @@ -7,6 +7,9 @@ const de = document.documentElement const knownColorSchemes = ['dark', 'light'] const preference = window.localStorage.getItem('<%= options.storageKey %>') || '<%= options.preference %>' +const themeColors = JSON.parse('<%= options.escapedThemeColors %>') +// Get previous meta element if the script is run the second time (e.g. in dev mode) +let themeColorMetaElm = d.head.querySelector('meta[data-nuxtjs-color-mode]') let value = preference === 'system' ? getColorScheme() : preference // Applied forced color mode const forcedColorMode = de.getAttribute('data-color-mode-forced') @@ -35,6 +38,21 @@ function addColorScheme (value) { if (dataValue) { de.setAttribute('data-' + dataValue, value) } + + const themeColor = themeColors && themeColors[value] + if (themeColor) { + if (!themeColorMetaElm) { + themeColorMetaElm = d.createElement('meta') + themeColorMetaElm.setAttribute('data-nuxtjs-color-mode', '') + themeColorMetaElm.name = 'theme-color' + } + themeColorMetaElm.content = themeColor + if (themeColorMetaElm.parentNode !== d.head) { + d.head.appendChild(themeColorMetaElm) + } + } else if (themeColorMetaElm && themeColorMetaElm.parentNode) { + themeColorMetaElm.parentNode.removeChild(themeColorMetaElm) + } } function removeColorScheme (value) { From 59579cf77d0c512b1c5718e9505fef85e2c20de8 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 14 Jun 2022 11:29:13 +0100 Subject: [PATCH 2/6] fix: slightly simplify implementation --- src/module.ts | 10 ++++++++-- src/script.ts | 8 ++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/module.ts b/src/module.ts index 026ac0af..b14a0587 100644 --- a/src/module.ts +++ b/src/module.ts @@ -14,7 +14,8 @@ const DEFAULTS: ModuleOptions = { classPrefix: '', classSuffix: '-mode', dataValue: '', - storageKey: 'nuxt-color-mode' + storageKey: 'nuxt-color-mode', + themeColors: null } export default defineNuxtModule({ @@ -33,7 +34,12 @@ export default defineNuxtModule({ // Read script from disk and add to options const scriptPath = await resolver.resolve('./script.min.js') const scriptT = await fsp.readFile(scriptPath, 'utf-8') - options.script = template(scriptT)({ options }) + options.script = template(scriptT)({ + options: { + ...options, + themeColors: JSON.stringify(options.themeColors) + } + }) // Inject options via virtual template nuxt.options.alias['#color-mode-options'] = addTemplate({ diff --git a/src/script.ts b/src/script.ts index 3a4ac3d7..d1a2fcc3 100644 --- a/src/script.ts +++ b/src/script.ts @@ -2,14 +2,15 @@ // Global variable minimizers const w = window -const de = document.documentElement +const d = document +const de = d.documentElement const knownColorSchemes = ['dark', 'light'] const preference = window.localStorage.getItem('<%= options.storageKey %>') || '<%= options.preference %>' -const themeColors = JSON.parse('<%= options.escapedThemeColors %>') +const themeColors = JSON.parse('<%= options.themeColors %>') // Get previous meta element if the script is run the second time (e.g. in dev mode) -let themeColorMetaElm = d.head.querySelector('meta[data-nuxtjs-color-mode]') +let themeColorMetaElm = d.head.querySelector('meta[name=theme-color]') let value = preference === 'system' ? getColorScheme() : preference // Applied forced color mode const forcedColorMode = de.getAttribute('data-color-mode-forced') @@ -43,7 +44,6 @@ function addColorScheme (value) { if (themeColor) { if (!themeColorMetaElm) { themeColorMetaElm = d.createElement('meta') - themeColorMetaElm.setAttribute('data-nuxtjs-color-mode', '') themeColorMetaElm.name = 'theme-color' } themeColorMetaElm.content = themeColor From 0300d45b3d3fbee86ed0f5f354f929445d5e62d3 Mon Sep 17 00:00:00 2001 From: Arecsu Date: Wed, 16 Aug 2023 22:35:57 -0300 Subject: [PATCH 3/6] meta `theme-color`: variable names and comments --- src/script.ts | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/script.ts b/src/script.ts index 84f4d9e4..4ba4680b 100644 --- a/src/script.ts +++ b/src/script.ts @@ -8,9 +8,9 @@ const knownColorSchemes = ['dark', 'light'] const preference = (window && window.localStorage && window.localStorage.getItem && window.localStorage.getItem('<%= options.storageKey %>')) || '<%= options.preference %>' - const themeColors = JSON.parse('<%= options.themeColors %>') + const metaThemeColors = JSON.parse('<%= options.themeColors %>') // Get previous meta element if the script is run the second time (e.g. in dev mode) - let themeColorMetaElm = d.head.querySelector('meta[name=theme-color]') + let metaElementThemeColor = d.head.querySelector('meta[name=theme-color]') let value = preference === 'system' ? getColorScheme() : preference // Applied forced color mode const forcedColorMode = de.getAttribute('data-color-mode-forced') @@ -42,19 +42,24 @@ de.setAttribute('data-' + dataValue, value) } - const themeColor = themeColors && themeColors[value] - if (themeColor) { - if (!themeColorMetaElm) { - themeColorMetaElm = d.createElement('meta') - themeColorMetaElm.name = 'theme-color' - } - themeColorMetaElm.content = themeColor - if (themeColorMetaElm.parentNode !== d.head) { - d.head.appendChild(themeColorMetaElm) + // theme-color refers to a meta attribute which indicates a + // suggested color user agents can use to customize the interface + // more info: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name/theme-color + const currentMetaThemeColor = metaThemeColors?.[value] + if (currentMetaThemeColor) { + if (!metaElementThemeColor) { + metaElementThemeColor = d.createElement('meta') + // @ts-ignore + metaElementThemeColor.name = 'theme-color' + } + // @ts-ignore + metaElementThemeColor.content = currentMetaThemeColor + if (metaElementThemeColor.parentNode !== d.head) { + d.head.appendChild(metaElementThemeColor) + } + } else if (metaElementThemeColor?.parentNode) { + metaElementThemeColor.parentNode.removeChild(metaElementThemeColor) } - } else if (themeColorMetaElm && themeColorMetaElm.parentNode) { - themeColorMetaElm.parentNode.removeChild(themeColorMetaElm) - } } // @ts-ignore From 857e92dcf601c30e74e8a70836b71d14d9de5f85 Mon Sep 17 00:00:00 2001 From: Arecsu Date: Wed, 16 Aug 2023 22:36:18 -0300 Subject: [PATCH 4/6] pnpm-lock update --- pnpm-lock.yaml | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d879fc20..48ce7834 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1205,7 +1205,7 @@ packages: consola: 3.2.3 create-require: 1.1.1 defu: 6.1.2 - destr: 2.0.0 + destr: 2.0.1 dotenv: 16.3.1 fs-extra: 11.1.1 git-url-parse: 13.1.0 @@ -1355,7 +1355,7 @@ packages: rollup-plugin-visualizer: 5.9.2(rollup@3.25.1) std-env: 3.3.3 strip-literal: 1.3.0 - ufo: 1.1.2 + ufo: 1.2.0 unplugin: 1.4.0 vite: 4.3.9(@types/node@20.3.1) vite-node: 0.33.0(@types/node@20.3.1) @@ -2860,6 +2860,7 @@ packages: /citty@0.1.1: resolution: {integrity: sha512-fL/EEp9TyXlNkgYFQYNqtMJhnAk2tAq8lCST7O5LPn1NrzWPsOKE5wafR7J+8W87oxqolpxNli+w7khq5WP7tg==} + dev: true /citty@0.1.2: resolution: {integrity: sha512-Me9nf0/BEmMOnuQzMOVXgpzkMUNbd0Am8lTl/13p0aRGAoLGk5T5sdet/42CrIGmWdG67BgHUhcKK1my1ujUEg==} @@ -4749,10 +4750,10 @@ packages: dependencies: cookie-es: 1.0.0 defu: 6.1.2 - destr: 2.0.0 + destr: 2.0.1 iron-webcrypto: 0.7.0 radix3: 1.0.1 - ufo: 1.1.2 + ufo: 1.2.0 uncrypto: 0.1.3 /h3@1.8.0-rc.3: @@ -5001,6 +5002,7 @@ packages: /ip-regex@5.0.0: resolution: {integrity: sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true /iron-webcrypto@0.7.0: resolution: {integrity: sha512-WkX32iTcwd79ZsWRPP5wq1Jq6XXfPwO783ZiUBY8uMw4/AByx5WvBmxvYGnpVt6AOVJ0F41Qo420r8lIneT9Wg==} @@ -5389,6 +5391,7 @@ packages: ip-regex: 5.0.0 node-forge: 1.3.1 ufo: 1.2.0 + dev: true /listhen@1.3.0: resolution: {integrity: sha512-QhlP01ReqSXpu8OgBaFQjYMU/4YJTCWLFtoDTxBhitPQWfu0UuBoG2HizMysaRkUEAr/CVxB/20T8ni0zQDPtw==} @@ -5930,11 +5933,11 @@ packages: c12: 1.4.2 chalk: 5.3.0 chokidar: 3.5.3 - citty: 0.1.1 + citty: 0.1.2 consola: 3.2.3 cookie-es: 1.0.0 defu: 6.1.2 - destr: 2.0.0 + destr: 2.0.1 dot-prop: 7.2.0 esbuild: 0.18.20 escape-string-regexp: 5.0.0 @@ -5950,7 +5953,7 @@ packages: jiti: 1.19.1 klona: 2.0.6 knitwork: 1.0.0 - listhen: 1.0.4 + listhen: 1.3.0 magic-string: 0.30.2 mime: 3.0.0 mlly: 1.4.0 @@ -5972,9 +5975,9 @@ packages: serve-static: 1.15.0 source-map-support: 0.5.21 std-env: 3.3.3 - ufo: 1.1.2 + ufo: 1.2.0 uncrypto: 0.1.3 - unenv: 1.5.1 + unenv: 1.7.1 unimport: 3.1.3(rollup@3.28.0) unstorage: 1.9.0 transitivePeerDependencies: @@ -6225,7 +6228,7 @@ packages: chokidar: 3.5.3 cookie-es: 1.0.0 defu: 6.1.2 - destr: 2.0.0 + destr: 2.0.1 devalue: 4.3.2 esbuild: 0.18.20 escape-string-regexp: 5.0.0 @@ -6249,12 +6252,12 @@ packages: perfect-debounce: 1.0.0 prompts: 2.4.2 scule: 1.0.0 - strip-literal: 1.0.1 - ufo: 1.1.2 + strip-literal: 1.3.0 + ufo: 1.2.0 ultrahtml: 1.2.0 uncrypto: 0.1.3 unctx: 2.3.1 - unenv: 1.5.1 + unenv: 1.7.1 unimport: 3.1.3(rollup@3.25.1) unplugin: 1.4.0 unplugin-vue-router: 0.6.4(rollup@3.25.1)(vue-router@4.2.4)(vue@3.3.4) @@ -7951,6 +7954,7 @@ packages: mime: 3.0.0 node-fetch-native: 1.2.0 pathe: 1.1.1 + dev: true /unenv@1.7.1: resolution: {integrity: sha512-iINrdDcqoAjGqoIeOW85TIfI13KGgW1VWwqNO/IzcvvZ/JGBApMAQPZhWcKhE5oC/woFSpCSXg5lc7r1UaLPng==} From 4b847d417437abe1f5eee1a6b48bd6fac603411d Mon Sep 17 00:00:00 2001 From: Arecsu Date: Fri, 18 Aug 2023 14:46:41 -0300 Subject: [PATCH 5/6] Fixed metaThemeColors not escaping quotes correctly --- src/script.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/script.ts b/src/script.ts index 4ba4680b..9abd19f9 100644 --- a/src/script.ts +++ b/src/script.ts @@ -8,7 +8,13 @@ const knownColorSchemes = ['dark', 'light'] const preference = (window && window.localStorage && window.localStorage.getItem && window.localStorage.getItem('<%= options.storageKey %>')) || '<%= options.preference %>' - const metaThemeColors = JSON.parse('<%= options.themeColors %>') + + /* Backticks are needed to proper escape quotes in options.themeColors. + Using ES template literals instead of "interpolate" delimiter (lodash) + is needed to preserve the backticks in the minify process + */ + const metaThemeColors = JSON.parse(`${options.themeColors}`) + // Get previous meta element if the script is run the second time (e.g. in dev mode) let metaElementThemeColor = d.head.querySelector('meta[name=theme-color]') let value = preference === 'system' ? getColorScheme() : preference From cdf55ecf3e629750f348616c3b46709f716999b9 Mon Sep 17 00:00:00 2001 From: Arecsu Date: Fri, 18 Aug 2023 14:47:20 -0300 Subject: [PATCH 6/6] global Typescript ignore to `script.ts` --- src/script.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/script.ts b/src/script.ts index 9abd19f9..96f28f73 100644 --- a/src/script.ts +++ b/src/script.ts @@ -1,3 +1,4 @@ +// @ts-nocheck // Add dark / light detection that runs before loading Nuxt (() => { // Global variable minimizers @@ -26,7 +27,6 @@ addColorScheme(value) - // @ts-ignore w['<%= options.globalName %>'] = { preference, value, @@ -35,7 +35,6 @@ removeColorScheme } - // @ts-ignore function addColorScheme (value) { const className = '<%= options.classPrefix %>' + value + '<%= options.classSuffix %>' const dataValue = '<%= options.dataValue %>' @@ -55,10 +54,8 @@ if (currentMetaThemeColor) { if (!metaElementThemeColor) { metaElementThemeColor = d.createElement('meta') - // @ts-ignore metaElementThemeColor.name = 'theme-color' } - // @ts-ignore metaElementThemeColor.content = currentMetaThemeColor if (metaElementThemeColor.parentNode !== d.head) { d.head.appendChild(metaElementThemeColor) @@ -68,7 +65,6 @@ } } - // @ts-ignore function removeColorScheme (value) { const className = '<%= options.classPrefix %>' + value + '<%= options.classSuffix %>' const dataValue = '<%= options.dataValue %>' @@ -82,13 +78,11 @@ } } - // @ts-ignore function prefersColorScheme (suffix) { return w.matchMedia('(prefers-color-scheme' + suffix + ')') } function getColorScheme () { - // @ts-ignore if (w.matchMedia && prefersColorScheme('').media !== 'not all') { for (const colorScheme of knownColorSchemes) { if (prefersColorScheme(':' + colorScheme).matches) {