diff --git a/CHANGELOG.md b/CHANGELOG.md index c2272bc..d251f9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Upgrade dependent packages to the latest version ([#332](https://github.com/marp-team/marpit/pull/332)) +### Fixed + +- Match `:root` selector specificity to original exactly ([#330](https://github.com/marp-team/marpit/issues/330), [#333](https://github.com/marp-team/marpit/pull/333)) + ### Removed - Continuous test against Node.js 10 ([#291](https://github.com/marp-team/marpit/issues/291), [#332](https://github.com/marp-team/marpit/pull/332)) diff --git a/docs/theme-css.md b/docs/theme-css.md index 9cc2367..f546416 100644 --- a/docs/theme-css.md +++ b/docs/theme-css.md @@ -37,19 +37,9 @@ h2 { We have no any extra classes or mixins, and do almost not need require to know extra rules for creating theme. This is a key factor of Marpit different from other slide framework. -### Metadata - -The `@theme` metadata is always required by Marpit. Define metadata through CSS comment. - -```css -/* @theme name */ -``` - -!> You should use the `/*! comment */` syntax to prevent removing comments if you're using the compressed output of [Sass]. - ### `:root` pseudo-class selector -Since v1.6.0, [`:root` pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:root) indicates the viewport of each slide pages in the context of Marpit theme CSS, by replacing `:root` into `section` automatically. +In the context of Marpit, [`:root` pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:root) indicates each `
` elements for the slide page instead of ``. The following is similar theme definition to the example shown earlier, but it's using `:root` selector. @@ -75,6 +65,18 @@ h2 { [`rem` units](https://developer.mozilla.org/en-US/docs/Web/CSS/font-size#Rems) in Marpit theme will automatically transform into the calculated relative value from the parent `
` element, so anyone don't have to worry the effect from `font-size` in the root `` that placed Marpit slide. Everything would work as the theme author expected. +?> `:root` selector can use just like as `section` selector, but there is a difference that `:root` has higher [CSS specificity](https://developer.mozilla.org/docs/Web/CSS/Specificity) than `section`. If both selectors have mixed in a theme CSS, declarations in `:root` selector will be prefered than `section` selector. + +### Metadata + +**The `@theme` metadata is always required by Marpit.** You must define metadata through CSS comment. + +```css +/* @theme name */ +``` + +!> You should use the `/*! comment */` syntax to prevent removing comments if you're using the compressed output of [Sass]. + ## Styling ### Slide size diff --git a/src/postcss/root/increasing_specificity.js b/src/postcss/root/increasing_specificity.js index 4a5e117..35265cf 100644 --- a/src/postcss/root/increasing_specificity.js +++ b/src/postcss/root/increasing_specificity.js @@ -3,14 +3,15 @@ import postcssPlugin from '../../helpers/postcss_plugin' export const pseudoClass = ':marpit-root' -const matcher = new RegExp(`\\b${pseudoClass}\\b`, 'g') +const matcher = new RegExp(`\\b(?:section)?${pseudoClass}\\b`, 'g') /** * Marpit PostCSS root increasing specificity plugin. * - * Replace `:marpit-root` pseudo-class selector into `:not(a)`, to increase - * specificity. `:marpit-root` is always added to `section` selector by root - * replace plugin so `:not(a)` must always match too. + * Replace specific pseudo-class selector to `:where(section):not([\20 root])`, + * to increase specificity. `:marpit-root` is always added to `section` selector + * by root replace plugin so `:where(section):not([\20 root])` must always match + * too (HTML does not allow U+0020 SPACE in the attribute name.). * * @alias module:postcss/root/increasing_specificity */ @@ -19,7 +20,7 @@ const plugin = postcssPlugin( () => (css) => css.walkRules((rule) => { rule.selectors = rule.selectors.map((selector) => - selector.replace(matcher, ':not(a)') + selector.replace(matcher, ':where(section):not([\\20 root])') ) }) ) diff --git a/src/postcss/root/replace.js b/src/postcss/root/replace.js index 627c3a6..2f2283c 100644 --- a/src/postcss/root/replace.js +++ b/src/postcss/root/replace.js @@ -4,7 +4,9 @@ import postcssPlugin from '../../helpers/postcss_plugin' /** * Marpit PostCSS root replace plugin. * - * Replace `:root` pseudo-class selector into `section`. + * Replace `:root` pseudo-class selector into `section`. It can add custom + * pseudo class through `pseudoClass` option to make distinguishable from + * `section` selector. * * @alias module:postcss/root/replace */ diff --git a/src/postcss/section_size.js b/src/postcss/section_size.js index ba68eee..ec83e5b 100644 --- a/src/postcss/section_size.js +++ b/src/postcss/section_size.js @@ -10,24 +10,48 @@ import postcssPlugin from '../helpers/postcss_plugin' */ const plugin = postcssPlugin( 'marpit-postcss-section-size', - ({ pseudoClass } = {}) => { + ({ preferedPseudoClass } = {}) => { const rootSectionMatcher = new RegExp( - `^(?:section|\\*?:root)${pseudoClass ? `(?:${pseudoClass})?` : ''}$` + `^section${preferedPseudoClass ? `(${preferedPseudoClass})?` : ''}$` ) return (css, { result }) => { - result.marpitSectionSize = result.marpitSectionSize || {} + const originalSize = result.marpitSectionSize || {} + const detectedSize = {} + const preferedSize = {} + + let matched css.walkRules((rule) => { - if (rule.selectors.some((s) => rootSectionMatcher.test(s))) { + if ( + rule.selectors.some((s) => { + matched = s.match(rootSectionMatcher) + return !!matched + }) + ) { rule.walkDecls(/^(width|height)$/, (decl) => { const { prop } = decl const value = decl.value.trim() - result.marpitSectionSize[prop] = value + if (matched[1]) { + preferedSize[prop] = value + } else { + detectedSize[prop] = value + } }) } }) + + const width = + preferedSize.width || detectedSize.width || originalSize.width + + const height = + preferedSize.height || detectedSize.height || originalSize.height + + result.marpitSectionSize = { ...originalSize } + + if (width) result.marpitSectionSize.width = width + if (height) result.marpitSectionSize.height = height } } ) diff --git a/src/theme.js b/src/theme.js index 85c860b..142b7bc 100644 --- a/src/theme.js +++ b/src/theme.js @@ -1,6 +1,8 @@ import postcss from 'postcss' import postcssImportParse from './postcss/import/parse' import postcssMeta from './postcss/meta' +import { pseudoClass } from './postcss/root/increasing_specificity' +import postcssRootReplace from './postcss/root/replace' import postcssSectionSize from './postcss/section_size' import skipThemeValidationSymbol from './theme/symbol' @@ -99,7 +101,8 @@ class Theme { const { css, result } = postcss([ postcssMeta({ metaType }), - postcssSectionSize, + postcssRootReplace({ pseudoClass }), + postcssSectionSize({ preferedPseudoClass: pseudoClass }), postcssImportParse, ]).process(cssString) diff --git a/test/postcss/root/increasing_specificity.js b/test/postcss/root/increasing_specificity.js index 9b5a0bc..861392e 100644 --- a/test/postcss/root/increasing_specificity.js +++ b/test/postcss/root/increasing_specificity.js @@ -10,12 +10,18 @@ describe('Marpit PostCSS root increasing specificity plugin', () => { from: undefined, }) - it('replaces specific pseudo-class into ":not(a)" to increase specificity', () => { - expect(run(`section${pseudoClass} {}`).css).toBe('section:not(a) {}') + it('replaces specific pseudo-class into ":where(section):not([\\20 root])" to increase specificity', () => { + expect(run(`section${pseudoClass} {}`).css).toBe( + ':where(section):not([\\20 root]) {}' + ) // With replaced :root selector via root replace plugin - expect(run(`:root {}`).css).toBe('section:not(a) {}') - expect(run(`section :root {}`).css).toBe('section section:not(a) {}') - expect(run(`:root.klass div {}`).css).toBe('section:not(a).klass div {}') + expect(run(`:root {}`).css).toBe(':where(section):not([\\20 root]) {}') + expect(run(`section :root {}`).css).toBe( + 'section :where(section):not([\\20 root]) {}' + ) + expect(run(`:root.klass div {}`).css).toBe( + ':where(section):not([\\20 root]).klass div {}' + ) }) }) diff --git a/test/postcss/section_size.js b/test/postcss/section_size.js index 2114f88..a5bb119 100644 --- a/test/postcss/section_size.js +++ b/test/postcss/section_size.js @@ -31,4 +31,21 @@ describe('Marpit PostCSS section size plugin', () => { run('section:first-child { width: 123px; height: 456px; }').then((result) => expect(result.marpitSectionSize).toStrictEqual({}) )) + + context('with preferedPseudoClass', () => { + const run = (input) => + postcss([sectionSize({ preferedPseudoClass: ':test' })]).process(input, { + from: undefined, + }) + + it('prefers defined size within section selector with specific pseudo selector than plain selector', () => + run( + 'section:test { width: 123px; height: 123px; } section { width: 456px; height: 456px; } ' + ).then((result) => + expect(result.marpitSectionSize).toStrictEqual({ + width: '123px', + height: '123px', + }) + )) + }) }) diff --git a/test/theme.js b/test/theme.js index 8a981be..1c35665 100644 --- a/test/theme.js +++ b/test/theme.js @@ -62,9 +62,13 @@ describe('Theme', () => { width: 960px; height: 720px; } + section { + width: 123px; + height: 456px; + } `) - it('returns Theme instance that has width and height props', () => { + it('returns Theme instance that has width and height props defined in :root selector', () => { expect(instance.width).toBe('960px') expect(instance.height).toBe('720px') })