diff --git a/README.md b/README.md index cfbbf45..f084752 100644 --- a/README.md +++ b/README.md @@ -190,3 +190,10 @@ Use `lightSelector: '[data-theme="light"]'` if you will switch theme by setting Type: `string[]`, `string`. Default: `['html', ':root']`. Selector for node for CSS Custom properties and dark/light theme classes. + + +## `useWhere` + +Type: `boolean`. Default: `true`. + +Should plugin wrap added selector to `:where()` to keep origin specificity. diff --git a/index.js b/index.js index e6bf9d8..3f713ad 100644 --- a/index.js +++ b/index.js @@ -16,6 +16,8 @@ module.exports = (opts = {}) => { let roots = opts.rootSelector || ['html', ':root'] if (!Array.isArray(roots)) roots = [roots] + let useWhere = opts.useWhere ?? true + let uniqueRoots = roots if (uniqueRoots.includes('html')) { uniqueRoots = uniqueRoots.filter(i => i !== ':root') @@ -27,12 +29,22 @@ module.exports = (opts = {}) => { for (let root of roots) { if (selector.includes(root)) { changed = true - selector = replaceAll(selector, root, root + add) + if (useWhere) { + selector = replaceAll(selector, root, `${root}:where(${add})`) + } else { + selector = replaceAll(selector, root, `${root}${add}`) + } } } if (!changed) { selector = uniqueRoots - .map(root => `${root}${add} ${selector}`) + .map(root => { + if (useWhere) { + return `:where(${root}${add}) ${selector}` + } else { + return `${root}${add} ${selector}` + } + }) .join(',') } return selector diff --git a/index.test.js b/index.test.js index a9e9f3c..536af8b 100644 --- a/index.test.js +++ b/index.test.js @@ -11,6 +11,27 @@ function run(input, output, opts) { } test('replaces selectors', () => { + run( + `@media (prefers-color-scheme:dark) { + html.is-a, + html, + :root, + a { } + }`, + `@media (prefers-color-scheme:dark) { + html:where(:not(.is-light)).is-a, + html:where(:not(.is-light)), + :root:where(:not(.is-light)), + :where(html:not(.is-light)) a { } + } + html:where(.is-dark).is-a, + html:where(.is-dark), + :root:where(.is-dark), + :where(html.is-dark) a { }` + ) +}) + +test('disables :where() of request', () => { run( `@media (prefers-color-scheme:dark) { html.is-a, @@ -27,22 +48,41 @@ test('replaces selectors', () => { html.is-dark.is-a, html.is-dark, :root.is-dark, - html.is-dark a { }` + html.is-dark a { }`, + { useWhere: false } ) }) test('processes inner at-rules', () => { run( `@media (prefers-color-scheme: dark) { - @media (min-width: 500px) { a { } } - @media (min-width: 500px) { @media (print) { a { } } } + @media (min-width: 500px) { + a { } + } + @media (min-width: 500px) { + @media (print) { + a { } + } + } }`, `@media (prefers-color-scheme: dark) { - @media (min-width: 500px) { html:not(.is-light) a { } } - @media (min-width: 500px) { @media (print) { html:not(.is-light) a { } } } + @media (min-width: 500px) { + :where(html:not(.is-light)) a { } + } + @media (min-width: 500px) { + @media (print) { + :where(html:not(.is-light)) a { } + } + } } - @media (min-width: 500px) { html.is-dark a { } } - @media (min-width: 500px) { @media (print) { html.is-dark a { } } }` + @media (min-width: 500px) { + :where(html.is-dark) a { } + } + @media (min-width: 500px) { + @media (print) { + :where(html.is-dark) a { } + } + }` ) }) @@ -69,27 +109,27 @@ test('ignores whitespaces', () => { a { color: white } }`, `@media ( prefers-color-scheme:dark ) { - html:not(.is-light) a { color: white } + :where(html:not(.is-light)) a { color: white } } - html.is-dark a { color: white }` + :where(html.is-dark) a { color: white }` ) }) test('reserve comments', () => { run( `@media (prefers-color-scheme:dark) { - /* some comments */ + /* a */ a { color: white } - @media (min-width: 500px) { /* another comments */ a { } } + @media (min-width: 500px) { /* b */ a { } } }`, `@media (prefers-color-scheme:dark) { - /* some comments */ - html:not(.is-light) a { color: white } - @media (min-width: 500px) { /* another comments */ html:not(.is-light) a { } } + /* a */ + :where(html:not(.is-light)) a { color: white } + @media (min-width: 500px) { /* b */ :where(html:not(.is-light)) a { } } } - /* some comments */ - html.is-dark a { color: white } - @media (min-width: 500px) { /* another comments */ html.is-dark a { } }` + /* a */ + :where(html.is-dark) a { color: white } + @media (min-width: 500px) { /* b */ :where(html.is-dark) a { } }` ) }) @@ -99,9 +139,9 @@ test('supports combined queries', () => { a { color: white } }`, `@media (min-width: 60px) and (prefers-color-scheme: dark) { - html:not(.is-light) a { color: white } + :where(html:not(.is-light)) a { color: white } }@media (min-width: 60px) { - html.is-dark a { color: white } + :where(html.is-dark) a { color: white } }` ) }) @@ -112,9 +152,9 @@ test('supports combined queries in the middle', () => { a { color: white } }`, `@media (width > 0) and (prefers-color-scheme: dark) and (width > 0) { - html:not(.is-light) a { color: white } + :where(html:not(.is-light)) a { color: white } }@media (width > 0) and (width > 0) { - html.is-dark a { color: white } + :where(html.is-dark) a { color: white } }` ) }) @@ -125,9 +165,9 @@ test('allows to change class', () => { a { color: white } }`, `@media (prefers-color-scheme: dark) { - html:not(.light-theme) a { color: white } + :where(html:not(.light-theme)) a { color: white } } - html.dark-theme a { color: white }`, + :where(html.dark-theme) a { color: white }`, { darkSelector: '.dark-theme', lightSelector: '.light-theme' } ) }) @@ -135,20 +175,20 @@ test('allows to change class', () => { test('changes root selectors', () => { run( `@media (prefers-color-scheme: dark) { - html, .storybook { --bg: black } + html, .s { --bg: black } p { color: white } } - html, .storybook { --bg: white } + html, .s { --bg: white } p { color: black }`, `@media (prefers-color-scheme: dark) { - html:not(.is-light), .storybook:not(.is-light) { --bg: black } - html:not(.is-light) p,.storybook:not(.is-light) p { color: white } + html:where(:not(.is-light)), .s:where(:not(.is-light)) { --bg: black } + :where(html:not(.is-light)) p,:where(.s:not(.is-light)) p { color: white } } - html.is-dark, .storybook.is-dark { --bg: black } - html.is-dark p,.storybook.is-dark p { color: white } - html, .storybook { --bg: white } + html:where(.is-dark), .s:where(.is-dark) { --bg: black } + :where(html.is-dark) p,:where(.s.is-dark) p { color: white } + html, .s { --bg: white } p { color: black }`, - { rootSelector: ['html', ':root', '.storybook'] } + { rootSelector: ['html', ':root', '.s'] } ) }) @@ -161,11 +201,11 @@ test('changes root selector', () => { body { --bg: white } p { color: black }`, `@media (prefers-color-scheme: dark) { - body:not(.is-light) { --bg: black } - body:not(.is-light) p { color: white } + body:where(:not(.is-light)) { --bg: black } + :where(body:not(.is-light)) p { color: white } } - body.is-dark { --bg: black } - body.is-dark p { color: white } + body:where(.is-dark) { --bg: black } + :where(body.is-dark) p { color: white } body { --bg: white } p { color: black }`, { rootSelector: 'body' } @@ -181,9 +221,9 @@ test('ignores already transformed rules', () => { :root { --bg: white }`, `@media (prefers-color-scheme: dark) { :root:not(.is-light) { --bg: black } - html:not(.is-light) p { color: white } + :where(html:not(.is-light)) p { color: white } } - html.is-dark p { color: white } + :where(html.is-dark) p { color: white } :root { --bg: white }` ) })