diff --git a/src/marpit.js b/src/marpit.js index 14a0529..9175f70 100644 --- a/src/marpit.js +++ b/src/marpit.js @@ -22,6 +22,7 @@ import ThemeSet from './theme_set' const defaultOptions = { anchor: true, container: marpitContainer, + cssContainerQuery: false, headingDivider: false, lang: undefined, looseYAML: false, @@ -68,6 +69,10 @@ class Marpit { * @param {false|Element|Element[]} * [opts.container={@link module:element.marpitContainer}] Container * element(s) wrapping whole slide deck. + * @param {boolean|string|string[]} [opts.cssContainerQuery=false] Set whether + * to enable CSS container query (`@container`). By setting the string or + * string array, you can specify the container name(s) for the CSS + * container. * @param {false|number|number[]} [opts.headingDivider=false] Start a new * slide page at before of headings. it would apply to headings whose * larger than or equal to the specified level if a number is given, or @@ -264,6 +269,7 @@ class Marpit { ], inlineSVG: this.inlineSVGOptions, printable: this.options.printable, + containerQuery: this.options.cssContainerQuery, } } diff --git a/src/postcss/container_query.js b/src/postcss/container_query.js new file mode 100644 index 0000000..d54f11a --- /dev/null +++ b/src/postcss/container_query.js @@ -0,0 +1,53 @@ +/** @module */ +import cssesc from 'cssesc' +import postcssPlugin from '../helpers/postcss_plugin' + +const reservedNames = [ + 'none', + 'inherit', + 'initial', + 'revert', + 'revert-layer', + 'unset', +] + +/** + * Marpit PostCSS container query plugin. + * + * Add support of container queries for child elements of the `section` element. + * (`@container` at-rule, and `cqw` `cqh` `cqi` `cqb` `cqmin` `cqmax` units) + * + * @function meta + * @param {string|string[]} [containerName=undefined] Container name + * @param {boolean} [escape=true] Set whether to escape container name + */ +export const containerQuery = postcssPlugin( + 'marpit-postcss-container-query', + (containerName = undefined, escape = true) => + (css) => { + const containerNames = ( + Array.isArray(containerName) ? containerName : [containerName] + ).filter((name) => name && !reservedNames.includes(name)) + + const containerNameDeclaration = + containerNames.length > 0 + ? `\n container-name: ${containerNames + .map((name) => + escape + ? cssesc(name.toString(), { isIdentifier: true }) + : name.toString(), + ) + .join(' ')};` + : '' + + css.first.before( + ` +:where(section) { + container-type: size;${containerNameDeclaration} +} +`.trim(), + ) + }, +) + +export default containerQuery diff --git a/src/theme_set.js b/src/theme_set.js index f20173f..1a9d8ac 100644 --- a/src/theme_set.js +++ b/src/theme_set.js @@ -1,6 +1,7 @@ import postcss from 'postcss' import postcssPlugin from './helpers/postcss_plugin' import postcssAdvancedBackground from './postcss/advanced_background' +import postcssContainerQuery from './postcss/container_query' import postcssImportHoisting from './postcss/import/hoisting' import postcssImportReplace from './postcss/import/replace' import postcssImportSuppress from './postcss/import/suppress' @@ -236,6 +237,9 @@ class ThemeSet { * @param {string} [opts.before] A CSS string to prepend into before theme. * @param {Element[]} [opts.containers] Container elements wrapping whole * slide deck. + * @param {boolean|string|string[]} [opts.containerQuery] Enable CSS container + * query by setting `true`. You can also specify the name of container for + * CSS container query used by the `@container` at-rule in child elements. * @param {boolean} [opts.printable] Make style printable to PDF. * @param {Marpit~InlineSVGOptions} [opts.inlineSVG] Apply a hierarchy of * inline SVG to CSS selector by setting `true`. _(Experimental)_ @@ -263,6 +267,12 @@ class ThemeSet { const after = additionalCSS(opts.after) const before = additionalCSS(opts.before) + const containerName = + typeof opts.containerQuery === 'string' || + Array.isArray(opts.containerQuery) + ? opts.containerQuery + : undefined + const packer = postcss( [ before && @@ -274,6 +284,7 @@ class ThemeSet { postcssPlugin('marpit-pack-after', () => (css) => { css.last.after(after) }), + opts.containerQuery && postcssContainerQuery(containerName), postcssImportHoisting, postcssImportReplace(this), opts.printable &&