From c15e30f41fcda76c10ec1550c5d23214bd78edb8 Mon Sep 17 00:00:00 2001
From: Romain Menke
Date: Sat, 14 Jan 2023 15:02:23 +0100
Subject: [PATCH 1/3] postcss-logical-viewport-units
---
package-lock.json | 28 +++
.../postcss-logical-viewport-units/.gitignore | 6 +
plugins/postcss-logical-viewport-units/.nvmrc | 1 +
.../postcss-logical-viewport-units/.tape.mjs | 29 +++
.../CHANGELOG.md | 5 +
.../postcss-logical-viewport-units/INSTALL.md | 235 ++++++++++++++++++
.../postcss-logical-viewport-units/LICENSE.md | 108 ++++++++
.../postcss-logical-viewport-units/README.md | 102 ++++++++
.../dist/index.cjs | 1 +
.../dist/index.d.ts | 10 +
.../dist/index.mjs | 1 +
.../dist/transform.d.ts | 4 +
.../docs/README.md | 70 ++++++
.../package.json | 78 ++++++
.../src/index.ts | 58 +++++
.../src/transform.ts | 44 ++++
.../test/_import.mjs | 6 +
.../test/_require.cjs | 6 +
.../test/basic.css | 15 ++
.../test/basic.expect.css | 19 ++
.../test/basic.vertical.expect.css | 19 ++
.../test/examples/example.css | 3 +
.../test/examples/example.expect.css | 4 +
.../example.preserve-false.expect.css | 3 +
.../test/examples/example.vertical.expect.css | 4 +
.../tsconfig.json | 9 +
26 files changed, 868 insertions(+)
create mode 100644 plugins/postcss-logical-viewport-units/.gitignore
create mode 100644 plugins/postcss-logical-viewport-units/.nvmrc
create mode 100644 plugins/postcss-logical-viewport-units/.tape.mjs
create mode 100644 plugins/postcss-logical-viewport-units/CHANGELOG.md
create mode 100644 plugins/postcss-logical-viewport-units/INSTALL.md
create mode 100644 plugins/postcss-logical-viewport-units/LICENSE.md
create mode 100644 plugins/postcss-logical-viewport-units/README.md
create mode 100644 plugins/postcss-logical-viewport-units/dist/index.cjs
create mode 100644 plugins/postcss-logical-viewport-units/dist/index.d.ts
create mode 100644 plugins/postcss-logical-viewport-units/dist/index.mjs
create mode 100644 plugins/postcss-logical-viewport-units/dist/transform.d.ts
create mode 100644 plugins/postcss-logical-viewport-units/docs/README.md
create mode 100644 plugins/postcss-logical-viewport-units/package.json
create mode 100644 plugins/postcss-logical-viewport-units/src/index.ts
create mode 100644 plugins/postcss-logical-viewport-units/src/transform.ts
create mode 100644 plugins/postcss-logical-viewport-units/test/_import.mjs
create mode 100644 plugins/postcss-logical-viewport-units/test/_require.cjs
create mode 100644 plugins/postcss-logical-viewport-units/test/basic.css
create mode 100644 plugins/postcss-logical-viewport-units/test/basic.expect.css
create mode 100644 plugins/postcss-logical-viewport-units/test/basic.vertical.expect.css
create mode 100644 plugins/postcss-logical-viewport-units/test/examples/example.css
create mode 100644 plugins/postcss-logical-viewport-units/test/examples/example.expect.css
create mode 100644 plugins/postcss-logical-viewport-units/test/examples/example.preserve-false.expect.css
create mode 100644 plugins/postcss-logical-viewport-units/test/examples/example.vertical.expect.css
create mode 100644 plugins/postcss-logical-viewport-units/tsconfig.json
diff --git a/package-lock.json b/package-lock.json
index a55be7f11..e3e5e9696 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1961,6 +1961,10 @@
"resolved": "plugins/postcss-is-pseudo-class",
"link": true
},
+ "node_modules/@csstools/postcss-logical-viewport-units": {
+ "resolved": "plugins/postcss-logical-viewport-units",
+ "link": true
+ },
"node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": {
"resolved": "plugins/postcss-media-queries-aspect-ratio-number-values",
"link": true
@@ -7778,6 +7782,24 @@
"postcss": "^8.4"
}
},
+ "plugins/postcss-logical-viewport-units": {
+ "name": "@csstools/postcss-logical-viewport-units",
+ "version": "1.0.0",
+ "license": "CC0-1.0",
+ "devDependencies": {
+ "@csstools/css-tokenizer": "^1.0.0"
+ },
+ "engines": {
+ "node": "^14 || ^16 || >=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4"
+ }
+ },
"plugins/postcss-media-queries-aspect-ratio-number-values": {
"name": "@csstools/postcss-media-queries-aspect-ratio-number-values",
"version": "1.0.0",
@@ -9401,6 +9423,12 @@
"puppeteer": "^19.0.0"
}
},
+ "@csstools/postcss-logical-viewport-units": {
+ "version": "file:plugins/postcss-logical-viewport-units",
+ "requires": {
+ "@csstools/css-tokenizer": "^1.0.0"
+ }
+ },
"@csstools/postcss-media-queries-aspect-ratio-number-values": {
"version": "file:plugins/postcss-media-queries-aspect-ratio-number-values",
"requires": {
diff --git a/plugins/postcss-logical-viewport-units/.gitignore b/plugins/postcss-logical-viewport-units/.gitignore
new file mode 100644
index 000000000..e5b28db4a
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+package-lock.json
+yarn.lock
+*.result.css
+*.result.css.map
+*.result.html
diff --git a/plugins/postcss-logical-viewport-units/.nvmrc b/plugins/postcss-logical-viewport-units/.nvmrc
new file mode 100644
index 000000000..39e593ebe
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/.nvmrc
@@ -0,0 +1 @@
+v18.8.0
diff --git a/plugins/postcss-logical-viewport-units/.tape.mjs b/plugins/postcss-logical-viewport-units/.tape.mjs
new file mode 100644
index 000000000..534476da1
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/.tape.mjs
@@ -0,0 +1,29 @@
+import postcssTape from '../../packages/postcss-tape/dist/index.mjs';
+import plugin from '@csstools/postcss-logical-viewport-units';
+
+postcssTape(plugin)({
+ basic: {
+ message: "supports basic usage",
+ },
+ 'basic:vertical': {
+ message: "supports vertical writing mode",
+ options: {
+ writingMode: 'vertical',
+ }
+ },
+ 'examples/example': {
+ message: 'minimal example',
+ },
+ 'examples/example:vertical': {
+ message: 'minimal example',
+ options: {
+ writingMode: 'vertical',
+ }
+ },
+ 'examples/example:preserve-false': {
+ message: 'minimal example',
+ options: {
+ preserve: false
+ }
+ },
+});
diff --git a/plugins/postcss-logical-viewport-units/CHANGELOG.md b/plugins/postcss-logical-viewport-units/CHANGELOG.md
new file mode 100644
index 000000000..dd92bbe73
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Changes to PostCSS Logical Viewport Units
+
+### 1.0.0 (Unreleased)
+
+- Initial version
diff --git a/plugins/postcss-logical-viewport-units/INSTALL.md b/plugins/postcss-logical-viewport-units/INSTALL.md
new file mode 100644
index 000000000..625923c4a
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/INSTALL.md
@@ -0,0 +1,235 @@
+# Installing PostCSS Logical Viewport Units
+
+[PostCSS Logical Viewport Units] runs in all Node environments, with special instructions for:
+
+- [Node](#node)
+- [PostCSS CLI](#postcss-cli)
+- [PostCSS Load Config](#postcss-load-config)
+- [Webpack](#webpack)
+- [Next.js](#nextjs)
+- [Gulp](#gulp)
+- [Grunt](#grunt)
+
+
+
+## Node
+
+Add [PostCSS Logical Viewport Units] to your project:
+
+```bash
+npm install postcss @csstools/postcss-logical-viewport-units --save-dev
+```
+
+Use it as a [PostCSS] plugin:
+
+```js
+// commonjs
+const postcss = require('postcss');
+const postcssLogicalViewportUnits = require('@csstools/postcss-logical-viewport-units');
+
+postcss([
+ postcssLogicalViewportUnits(/* pluginOptions */)
+]).process(YOUR_CSS /*, processOptions */);
+```
+
+```js
+// esm
+import postcss from 'postcss';
+import postcssLogicalViewportUnits from '@csstools/postcss-logical-viewport-units';
+
+postcss([
+ postcssLogicalViewportUnits(/* pluginOptions */)
+]).process(YOUR_CSS /*, processOptions */);
+```
+
+## PostCSS CLI
+
+Add [PostCSS CLI] to your project:
+
+```bash
+npm install postcss-cli @csstools/postcss-logical-viewport-units --save-dev
+```
+
+Use [PostCSS Logical Viewport Units] in your `postcss.config.js` configuration file:
+
+```js
+const postcssLogicalViewportUnits = require('@csstools/postcss-logical-viewport-units');
+
+module.exports = {
+ plugins: [
+ postcssLogicalViewportUnits(/* pluginOptions */)
+ ]
+}
+```
+
+## PostCSS Load Config
+
+If your framework/CLI supports [`postcss-load-config`](https://github.com/postcss/postcss-load-config).
+
+```bash
+npm install @csstools/postcss-logical-viewport-units --save-dev
+```
+
+`package.json`:
+
+```json
+{
+ "postcss": {
+ "plugins": {
+ "@csstools/postcss-logical-viewport-units": {}
+ }
+ }
+}
+```
+
+`.postcssrc.json`:
+
+```json
+{
+ "plugins": {
+ "@csstools/postcss-logical-viewport-units": {}
+ }
+}
+```
+
+_See the [README of `postcss-load-config`](https://github.com/postcss/postcss-load-config#usage) for more usage options._
+
+## Webpack
+
+_Webpack version 5_
+
+Add [PostCSS Loader] to your project:
+
+```bash
+npm install postcss-loader @csstools/postcss-logical-viewport-units --save-dev
+```
+
+Use [PostCSS Logical Viewport Units] in your Webpack configuration:
+
+```js
+module.exports = {
+ module: {
+ rules: [
+ {
+ test: /\.css$/i,
+ use: [
+ "style-loader",
+ {
+ loader: "css-loader",
+ options: { importLoaders: 1 },
+ },
+ {
+ loader: "postcss-loader",
+ options: {
+ postcssOptions: {
+ plugins: [
+ // Other plugins,
+ [
+ "@csstools/postcss-logical-viewport-units",
+ {
+ // Options
+ },
+ ],
+ ],
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+};
+```
+
+## Next.js
+
+Read the instructions on how to [customize the PostCSS configuration in Next.js](https://nextjs.org/docs/advanced-features/customizing-postcss-config)
+
+```bash
+npm install @csstools/postcss-logical-viewport-units --save-dev
+```
+
+Use [PostCSS Logical Viewport Units] in your `postcss.config.json` file:
+
+```json
+{
+ "plugins": [
+ "@csstools/postcss-logical-viewport-units"
+ ]
+}
+```
+
+```json5
+{
+ "plugins": [
+ [
+ "@csstools/postcss-logical-viewport-units",
+ {
+ // Optionally add plugin options
+ }
+ ]
+ ]
+}
+```
+
+## Gulp
+
+Add [Gulp PostCSS] to your project:
+
+```bash
+npm install gulp-postcss @csstools/postcss-logical-viewport-units --save-dev
+```
+
+Use [PostCSS Logical Viewport Units] in your Gulpfile:
+
+```js
+const postcss = require('gulp-postcss');
+const postcssLogicalViewportUnits = require('@csstools/postcss-logical-viewport-units');
+
+gulp.task('css', function () {
+ var plugins = [
+ postcssLogicalViewportUnits(/* pluginOptions */)
+ ];
+
+ return gulp.src('./src/*.css')
+ .pipe(postcss(plugins))
+ .pipe(gulp.dest('.'));
+});
+```
+
+## Grunt
+
+Add [Grunt PostCSS] to your project:
+
+```bash
+npm install grunt-postcss @csstools/postcss-logical-viewport-units --save-dev
+```
+
+Use [PostCSS Logical Viewport Units] in your Gruntfile:
+
+```js
+const postcssLogicalViewportUnits = require('@csstools/postcss-logical-viewport-units');
+
+grunt.loadNpmTasks('grunt-postcss');
+
+grunt.initConfig({
+ postcss: {
+ options: {
+ processors: [
+ postcssLogicalViewportUnits(/* pluginOptions */)
+ ]
+ },
+ dist: {
+ src: '*.css'
+ }
+ }
+});
+```
+
+[Gulp PostCSS]: https://github.com/postcss/gulp-postcss
+[Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss
+[PostCSS]: https://github.com/postcss/postcss
+[PostCSS CLI]: https://github.com/postcss/postcss-cli
+[PostCSS Loader]: https://github.com/postcss/postcss-loader
+[PostCSS Logical Viewport Units]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-logical-viewport-units
+[Next.js]: https://nextjs.org
diff --git a/plugins/postcss-logical-viewport-units/LICENSE.md b/plugins/postcss-logical-viewport-units/LICENSE.md
new file mode 100644
index 000000000..0bc1fa706
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/LICENSE.md
@@ -0,0 +1,108 @@
+# CC0 1.0 Universal
+
+## Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator and
+subsequent owner(s) (each and all, an “owner”) of an original work of
+authorship and/or a database (each, a “Work”).
+
+Certain owners wish to permanently relinquish those rights to a Work for the
+purpose of contributing to a commons of creative, cultural and scientific works
+(“Commons”) that the public can reliably and without fear of later claims of
+infringement build upon, modify, incorporate in other works, reuse and
+redistribute as freely as possible in any form whatsoever and for any purposes,
+including without limitation commercial purposes. These owners may contribute
+to the Commons to promote the ideal of a free culture and the further
+production of creative, cultural and scientific works, or to gain reputation or
+greater distribution for their Work in part through the use and efforts of
+others.
+
+For these and/or other purposes and motivations, and without any expectation of
+additional consideration or compensation, the person associating CC0 with a
+Work (the “Affirmer”), to the extent that he or she is an owner of Copyright
+and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and
+publicly distribute the Work under its terms, with knowledge of his or her
+Copyright and Related Rights in the Work and the meaning and intended legal
+effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+ protected by copyright and related or neighboring rights (“Copyright and
+ Related Rights”). Copyright and Related Rights include, but are not limited
+ to, the following:
+ 1. the right to reproduce, adapt, distribute, perform, display, communicate,
+ and translate a Work;
+ 2. moral rights retained by the original author(s) and/or performer(s);
+ 3. publicity and privacy rights pertaining to a person’s image or likeness
+ depicted in a Work;
+ 4. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(i), below;
+ 5. rights protecting the extraction, dissemination, use and reuse of data in
+ a Work;
+ 6. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation thereof,
+ including any amended or successor version of such directive); and
+ 7. other similar, equivalent or corresponding rights throughout the world
+ based on applicable law or treaty, and any national implementations
+ thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention of,
+ applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
+ unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright
+ and Related Rights and associated claims and causes of action, whether now
+ known or unknown (including existing as well as future claims and causes of
+ action), in the Work (i) in all territories worldwide, (ii) for the maximum
+ duration provided by applicable law or treaty (including future time
+ extensions), (iii) in any current or future medium and for any number of
+ copies, and (iv) for any purpose whatsoever, including without limitation
+ commercial, advertising or promotional purposes (the “Waiver”). Affirmer
+ makes the Waiver for the benefit of each member of the public at large and
+ to the detriment of Affirmer’s heirs and successors, fully intending that
+ such Waiver shall not be subject to revocation, rescission, cancellation,
+ termination, or any other legal or equitable action to disrupt the quiet
+ enjoyment of the Work by the public as contemplated by Affirmer’s express
+ Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason be
+ judged legally invalid or ineffective under applicable law, then the Waiver
+ shall be preserved to the maximum extent permitted taking into account
+ Affirmer’s express Statement of Purpose. In addition, to the extent the
+ Waiver is so judged Affirmer hereby grants to each affected person a
+ royalty-free, non transferable, non sublicensable, non exclusive,
+ irrevocable and unconditional license to exercise Affirmer’s Copyright and
+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
+ maximum duration provided by applicable law or treaty (including future time
+ extensions), (iii) in any current or future medium and for any number of
+ copies, and (iv) for any purpose whatsoever, including without limitation
+ commercial, advertising or promotional purposes (the “License”). The License
+ shall be deemed effective as of the date CC0 was applied by Affirmer to the
+ Work. Should any part of the License for any reason be judged legally
+ invalid or ineffective under applicable law, such partial invalidity or
+ ineffectiveness shall not invalidate the remainder of the License, and in
+ such case Affirmer hereby affirms that he or she will not (i) exercise any
+ of his or her remaining Copyright and Related Rights in the Work or (ii)
+ assert any associated claims and causes of action with respect to the Work,
+ in either case contrary to Affirmer’s express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+ 1. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ 2. Affirmer offers the Work as-is and makes no representations or warranties
+ of any kind concerning the Work, express, implied, statutory or
+ otherwise, including without limitation warranties of title,
+ merchantability, fitness for a particular purpose, non infringement, or
+ the absence of latent or other defects, accuracy, or the present or
+ absence of errors, whether or not discoverable, all to the greatest
+ extent permissible under applicable law.
+ 3. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person’s Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the Work.
+ 4. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to this
+ CC0 or use of the Work.
+
+For more information, please see
+http://creativecommons.org/publicdomain/zero/1.0/.
diff --git a/plugins/postcss-logical-viewport-units/README.md b/plugins/postcss-logical-viewport-units/README.md
new file mode 100644
index 000000000..e464db3d4
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/README.md
@@ -0,0 +1,102 @@
+# PostCSS Logical Viewport Units [
][PostCSS]
+
+[
][npm-url] [
][css-url] [
][cli-url] [
][discord]
+
+[PostCSS Logical Viewport Units] lets you easily use `vb` and `vi` length units following the [CSS-Values-4 Specification].
+
+```pcss
+.foo {
+ margin: 10vi 20vb;
+}
+
+/* becomes */
+
+.foo {
+ margin: 10vw 20vh;
+ margin: 10vi 20vb;
+}
+```
+
+## Usage
+
+Add [PostCSS Logical Viewport Units] to your project:
+
+```bash
+npm install postcss @csstools/postcss-logical-viewport-units --save-dev
+```
+
+Use it as a [PostCSS] plugin:
+
+```js
+const postcss = require('postcss');
+const postcssLogicalViewportUnits = require('@csstools/postcss-logical-viewport-units');
+
+postcss([
+ postcssLogicalViewportUnits(/* pluginOptions */)
+]).process(YOUR_CSS /*, processOptions */);
+```
+
+[PostCSS Logical Viewport Units] runs in all Node environments, with special
+instructions for:
+
+- [Node](INSTALL.md#node)
+- [PostCSS CLI](INSTALL.md#postcss-cli)
+- [PostCSS Load Config](INSTALL.md#postcss-load-config)
+- [Webpack](INSTALL.md#webpack)
+- [Next.js](INSTALL.md#nextjs)
+- [Gulp](INSTALL.md#gulp)
+- [Grunt](INSTALL.md#grunt)
+
+## Options
+
+### writingMode
+
+The `writingMode` option determines whether fallback values are generated for documents with a horizontal or vertical writing mode.
+By default documents have a horizontal writing mode.
+
+```js
+postcssLogicalViewportUnits({ writingMode: 'vertical' })
+```
+
+```pcss
+.foo {
+ margin: 10vi 20vb;
+}
+
+/* becomes */
+
+.foo {
+ margin: 10vh 20vw;
+ margin: 10vi 20vb;
+}
+```
+
+### preserve
+
+The `preserve` option determines whether the original notation
+is preserved. By default, it is preserved.
+
+```js
+postcssLogicalViewportUnits({ preserve: false })
+```
+
+```pcss
+.foo {
+ margin: 10vi 20vb;
+}
+
+/* becomes */
+
+.foo {
+ margin: 10vw 20vh;
+}
+```
+
+[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
+[css-url]: https://cssdb.org/#logical-viewport-units
+[discord]: https://discord.gg/bUadyRwkJS
+[npm-url]: https://www.npmjs.com/package/@csstools/postcss-logical-viewport-units
+
+[PostCSS]: https://github.com/postcss/postcss
+[PostCSS Logical Viewport Units]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-logical-viewport-units
+[CSS-Values-4 Specification]: https://www.w3.org/TR/css-values-4/#viewport-relative-units
diff --git a/plugins/postcss-logical-viewport-units/dist/index.cjs b/plugins/postcss-logical-viewport-units/dist/index.cjs
new file mode 100644
index 000000000..8097abdbd
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/dist/index.cjs
@@ -0,0 +1 @@
+"use strict";var e=require("@csstools/css-tokenizer");function transform(o,t){{const e=o.toLowerCase();if(!e.includes("vb")&&!e.includes("vi"))return o}const n=e.tokenizer({css:o}),s=[];let i=!1;for(;;){const o=n.nextToken();if(s.push(o),o[0]===e.TokenType.EOF)break;if(o[0]!==e.TokenType.Dimension)continue;const r=t[o[4].unit.toLowerCase()];r&&(o[1]=o[4].value.toString()+r,o[4].unit=r,i=!0)}return i?e.stringify(...s):o}const creator=e=>{const o=Object.assign({writingMode:"horizontal",preserve:!0},e);"vertical"!==o.writingMode&&(o.writingMode="horizontal");const t="horizontal"===o.writingMode?{vi:"vw",vb:"vh"}:{vi:"vh",vb:"vw"};return{postcssPlugin:"postcss-logical-viewport-units",Declaration(e){const n=transform(e.value,t);n!==e.value&&(e.cloneBefore({value:n}),o.preserve||e.remove())}}};creator.postcss=!0,module.exports=creator;
diff --git a/plugins/postcss-logical-viewport-units/dist/index.d.ts b/plugins/postcss-logical-viewport-units/dist/index.d.ts
new file mode 100644
index 000000000..8fb99b2e0
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/dist/index.d.ts
@@ -0,0 +1,10 @@
+import type { PluginCreator } from 'postcss';
+/** postcss-logical-viewport-units plugin options */
+export type pluginOptions = {
+ /** Preserve the original notation. default: false */
+ preserve?: boolean;
+ /** Control how logical viewport units are replaced. default: "horizontal" */
+ writingMode: 'horizontal' | 'vertical';
+};
+declare const creator: PluginCreator;
+export default creator;
diff --git a/plugins/postcss-logical-viewport-units/dist/index.mjs b/plugins/postcss-logical-viewport-units/dist/index.mjs
new file mode 100644
index 000000000..1f5542b21
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/dist/index.mjs
@@ -0,0 +1 @@
+import{tokenizer as o,TokenType as t,stringify as e}from"@csstools/css-tokenizer";function transform(n,s){{const o=n.toLowerCase();if(!o.includes("vb")&&!o.includes("vi"))return n}const i=o({css:n}),r=[];let c=!1;for(;;){const o=i.nextToken();if(r.push(o),o[0]===t.EOF)break;if(o[0]!==t.Dimension)continue;const e=s[o[4].unit.toLowerCase()];e&&(o[1]=o[4].value.toString()+e,o[4].unit=e,c=!0)}return c?e(...r):n}const creator=o=>{const t=Object.assign({writingMode:"horizontal",preserve:!0},o);"vertical"!==t.writingMode&&(t.writingMode="horizontal");const e="horizontal"===t.writingMode?{vi:"vw",vb:"vh"}:{vi:"vh",vb:"vw"};return{postcssPlugin:"postcss-logical-viewport-units",Declaration(o){const n=transform(o.value,e);n!==o.value&&(o.cloneBefore({value:n}),t.preserve||o.remove())}}};creator.postcss=!0;export{creator as default};
diff --git a/plugins/postcss-logical-viewport-units/dist/transform.d.ts b/plugins/postcss-logical-viewport-units/dist/transform.d.ts
new file mode 100644
index 000000000..0d007ffa5
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/dist/transform.d.ts
@@ -0,0 +1,4 @@
+export declare function transform(source: string, replacements: {
+ vi: 'vw' | 'vh';
+ vb: 'vw' | 'vh';
+}): string;
diff --git a/plugins/postcss-logical-viewport-units/docs/README.md b/plugins/postcss-logical-viewport-units/docs/README.md
new file mode 100644
index 000000000..0a34aa98b
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/docs/README.md
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[] lets you easily use `vb` and `vi` length units following the [CSS-Values-4 Specification].
+
+```pcss
+
+
+/* becomes */
+
+
+```
+
+
+
+
+
+## Options
+
+### writingMode
+
+The `writingMode` option determines whether fallback values are generated for documents with a horizontal or vertical writing mode.
+By default documents have a horizontal writing mode.
+
+```js
+({ writingMode: 'vertical' })
+```
+
+```pcss
+
+
+/* becomes */
+
+
+```
+
+### preserve
+
+The `preserve` option determines whether the original notation
+is preserved. By default, it is preserved.
+
+```js
+({ preserve: false })
+```
+
+```pcss
+
+
+/* becomes */
+
+
+```
+
+
+[CSS-Values-4 Specification]:
diff --git a/plugins/postcss-logical-viewport-units/package.json b/plugins/postcss-logical-viewport-units/package.json
new file mode 100644
index 000000000..6f4a933fb
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/package.json
@@ -0,0 +1,78 @@
+{
+ "name": "@csstools/postcss-logical-viewport-units",
+ "description": "Use vb and vi length units in CSS",
+ "version": "1.0.0",
+ "contributors": [
+ {
+ "name": "Antonio Laguna",
+ "email": "antonio@laguna.es",
+ "url": "https://antonio.laguna.es"
+ },
+ {
+ "name": "Romain Menke",
+ "email": "romainmenke@gmail.com"
+ }
+ ],
+ "license": "CC0-1.0",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ },
+ "engines": {
+ "node": "^14 || ^16 || >=18"
+ },
+ "main": "dist/index.cjs",
+ "module": "dist/index.mjs",
+ "types": "dist/index.d.ts",
+ "exports": {
+ ".": {
+ "import": "./dist/index.mjs",
+ "require": "./dist/index.cjs",
+ "default": "./dist/index.mjs"
+ }
+ },
+ "files": [
+ "CHANGELOG.md",
+ "LICENSE.md",
+ "README.md",
+ "dist"
+ ],
+ "peerDependencies": {
+ "postcss": "^8.4"
+ },
+ "devDependencies": {
+ "@csstools/css-tokenizer": "^1.0.0"
+ },
+ "scripts": {
+ "prebuild": "npm run clean",
+ "build": "rollup -c ../../rollup/default.mjs",
+ "clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true }); fs.mkdirSync('./dist');\"",
+ "docs": "node ../../.github/bin/generate-docs/install.mjs && node ../../.github/bin/generate-docs/readme.mjs",
+ "lint": "npm run lint:eslint && npm run lint:package-json",
+ "lint:eslint": "eslint ./src --ext .js --ext .ts --ext .mjs --no-error-on-unmatched-pattern",
+ "lint:package-json": "node ../../.github/bin/format-package-json.mjs",
+ "prepublishOnly": "npm run clean && npm run build && npm run test",
+ "test": "node .tape.mjs && npm run test:exports",
+ "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs",
+ "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs"
+ },
+ "homepage": "https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-logical-viewport-units#readme",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/csstools/postcss-plugins.git",
+ "directory": "plugins/postcss-logical-viewport-units"
+ },
+ "bugs": "https://github.com/csstools/postcss-plugins/issues",
+ "keywords": [
+ "postcss-plugin"
+ ],
+ "csstools": {
+ "cssdbId": "logical-viewport-units",
+ "exportName": "postcssLogicalViewportUnits",
+ "humanReadableName": "PostCSS Logical Viewport Units",
+ "specUrl": "https://www.w3.org/TR/css-values-4/#viewport-relative-units"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ }
+}
diff --git a/plugins/postcss-logical-viewport-units/src/index.ts b/plugins/postcss-logical-viewport-units/src/index.ts
new file mode 100644
index 000000000..7878f6e8f
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/src/index.ts
@@ -0,0 +1,58 @@
+import type { PluginCreator } from 'postcss';
+import { transform } from './transform';
+
+/** postcss-logical-viewport-units plugin options */
+export type pluginOptions = {
+ /** Preserve the original notation. default: false */
+ preserve?: boolean,
+ /** Control how logical viewport units are replaced. default: "horizontal" */
+ writingMode: 'horizontal' | 'vertical',
+};
+
+const creator: PluginCreator = (opts?: pluginOptions) => {
+ const options = Object.assign(
+ // Default options
+ {
+ writingMode: 'horizontal',
+ preserve: true,
+ },
+ // Provided options
+ opts,
+ );
+
+ if (options.writingMode !== 'vertical') {
+ options.writingMode = 'horizontal';
+ }
+
+ const replacements: { vi: 'vw' | 'vh', vb: 'vw' | 'vh' } = options.writingMode === 'horizontal' ? {
+ vi: 'vw',
+ vb: 'vh',
+ } : {
+ vi: 'vh',
+ vb: 'vw',
+ };
+
+ return {
+ postcssPlugin: 'postcss-logical-viewport-units',
+ Declaration(decl) {
+ const modifiedValue = transform(decl.value, replacements);
+ if (modifiedValue === decl.value) {
+ return;
+ }
+
+ decl.cloneBefore({
+ value: modifiedValue,
+ });
+
+ if (options.preserve) {
+ return;
+ }
+
+ decl.remove();
+ },
+ };
+};
+
+creator.postcss = true;
+
+export default creator;
diff --git a/plugins/postcss-logical-viewport-units/src/transform.ts b/plugins/postcss-logical-viewport-units/src/transform.ts
new file mode 100644
index 000000000..35d6a20d1
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/src/transform.ts
@@ -0,0 +1,44 @@
+import { stringify, tokenizer, TokenType } from '@csstools/css-tokenizer';
+
+export function transform(source: string, replacements: { vi: 'vw' | 'vh', vb: 'vw' | 'vh' }): string {
+ {
+ const lowerCaseValue = source.toLowerCase();
+ if (!(lowerCaseValue.includes('vb') || lowerCaseValue.includes('vi'))) {
+ return source;
+ }
+ }
+
+ const t = tokenizer({ css: source });
+ const tokens = [];
+ let didTransformUnits = false;
+
+ // eslint-disable-next-line no-constant-condition
+ while (true) {
+ const token = t.nextToken();
+ tokens.push(token);
+
+ if (token[0] === TokenType.EOF) {
+ break;
+ }
+
+ if (token[0] !== TokenType.Dimension) {
+ continue;
+ }
+
+ const unit = token[4].unit.toLowerCase();
+ const replacement = replacements[unit];
+ if (!replacement) {
+ continue;
+ }
+
+ token[1] = token[4].value.toString() + replacement;
+ token[4].unit = replacement;
+ didTransformUnits = true;
+ }
+
+ if (!didTransformUnits) {
+ return source;
+ }
+
+ return stringify(...tokens);
+}
diff --git a/plugins/postcss-logical-viewport-units/test/_import.mjs b/plugins/postcss-logical-viewport-units/test/_import.mjs
new file mode 100644
index 000000000..0f1eedf3a
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/test/_import.mjs
@@ -0,0 +1,6 @@
+import assert from 'assert';
+import plugin from '@csstools/postcss-logical-viewport-units';
+plugin();
+
+assert.ok(plugin.postcss, 'should have "postcss flag"');
+assert.equal(typeof plugin, 'function', 'should return a function');
diff --git a/plugins/postcss-logical-viewport-units/test/_require.cjs b/plugins/postcss-logical-viewport-units/test/_require.cjs
new file mode 100644
index 000000000..12d9ec20e
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/test/_require.cjs
@@ -0,0 +1,6 @@
+const assert = require('assert');
+const plugin = require('@csstools/postcss-logical-viewport-units');
+plugin();
+
+assert.ok(plugin.postcss, 'should have "postcss flag"');
+assert.equal(typeof plugin, 'function', 'should return a function');
diff --git a/plugins/postcss-logical-viewport-units/test/basic.css b/plugins/postcss-logical-viewport-units/test/basic.css
new file mode 100644
index 000000000..201d2aa70
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/test/basic.css
@@ -0,0 +1,15 @@
+.foo {
+ margin: 10vi 20vb;
+}
+
+.foo {
+ margin: calc(10vi + 5vb);
+}
+
+.vb {
+ left: 5vb;
+}
+
+.vi {
+ left: 5vi;
+}
diff --git a/plugins/postcss-logical-viewport-units/test/basic.expect.css b/plugins/postcss-logical-viewport-units/test/basic.expect.css
new file mode 100644
index 000000000..54d07b22f
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/test/basic.expect.css
@@ -0,0 +1,19 @@
+.foo {
+ margin: 10vw 20vh;
+ margin: 10vi 20vb;
+}
+
+.foo {
+ margin: calc(10vw + 5vh);
+ margin: calc(10vi + 5vb);
+}
+
+.vb {
+ left: 5vh;
+ left: 5vb;
+}
+
+.vi {
+ left: 5vw;
+ left: 5vi;
+}
diff --git a/plugins/postcss-logical-viewport-units/test/basic.vertical.expect.css b/plugins/postcss-logical-viewport-units/test/basic.vertical.expect.css
new file mode 100644
index 000000000..015117cfe
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/test/basic.vertical.expect.css
@@ -0,0 +1,19 @@
+.foo {
+ margin: 10vh 20vw;
+ margin: 10vi 20vb;
+}
+
+.foo {
+ margin: calc(10vh + 5vw);
+ margin: calc(10vi + 5vb);
+}
+
+.vb {
+ left: 5vw;
+ left: 5vb;
+}
+
+.vi {
+ left: 5vh;
+ left: 5vi;
+}
diff --git a/plugins/postcss-logical-viewport-units/test/examples/example.css b/plugins/postcss-logical-viewport-units/test/examples/example.css
new file mode 100644
index 000000000..1950f801f
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/test/examples/example.css
@@ -0,0 +1,3 @@
+.foo {
+ margin: 10vi 20vb;
+}
diff --git a/plugins/postcss-logical-viewport-units/test/examples/example.expect.css b/plugins/postcss-logical-viewport-units/test/examples/example.expect.css
new file mode 100644
index 000000000..ebe269938
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/test/examples/example.expect.css
@@ -0,0 +1,4 @@
+.foo {
+ margin: 10vw 20vh;
+ margin: 10vi 20vb;
+}
diff --git a/plugins/postcss-logical-viewport-units/test/examples/example.preserve-false.expect.css b/plugins/postcss-logical-viewport-units/test/examples/example.preserve-false.expect.css
new file mode 100644
index 000000000..ef698fbc2
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/test/examples/example.preserve-false.expect.css
@@ -0,0 +1,3 @@
+.foo {
+ margin: 10vw 20vh;
+}
diff --git a/plugins/postcss-logical-viewport-units/test/examples/example.vertical.expect.css b/plugins/postcss-logical-viewport-units/test/examples/example.vertical.expect.css
new file mode 100644
index 000000000..bfd40e32c
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/test/examples/example.vertical.expect.css
@@ -0,0 +1,4 @@
+.foo {
+ margin: 10vh 20vw;
+ margin: 10vi 20vb;
+}
diff --git a/plugins/postcss-logical-viewport-units/tsconfig.json b/plugins/postcss-logical-viewport-units/tsconfig.json
new file mode 100644
index 000000000..e0d06239c
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "declarationDir": "."
+ },
+ "include": ["./src/**/*"],
+ "exclude": ["dist"],
+}
From dd6e54d83ff2188253c2dd84c5bbb549306ad1bd Mon Sep 17 00:00:00 2001
From: Romain Menke
Date: Sat, 14 Jan 2023 17:34:16 +0100
Subject: [PATCH 2/3] finish up
---
.../dist/has-feature.d.ts | 1 +
.../dist/has-supports-at-rule-ancestor.d.ts | 2 +
.../dist/index.cjs | 2 +-
.../dist/index.mjs | 2 +-
.../src/has-feature.ts | 31 +++++++++++
.../src/has-supports-at-rule-ancestor.ts | 22 ++++++++
.../src/index.ts | 55 ++++++++++++++++---
.../src/transform.ts | 7 ---
.../test/basic.css | 15 +++++
.../test/basic.expect.css | 22 ++++++++
.../test/basic.vertical.expect.css | 22 ++++++++
11 files changed, 164 insertions(+), 17 deletions(-)
create mode 100644 plugins/postcss-logical-viewport-units/dist/has-feature.d.ts
create mode 100644 plugins/postcss-logical-viewport-units/dist/has-supports-at-rule-ancestor.d.ts
create mode 100644 plugins/postcss-logical-viewport-units/src/has-feature.ts
create mode 100644 plugins/postcss-logical-viewport-units/src/has-supports-at-rule-ancestor.ts
diff --git a/plugins/postcss-logical-viewport-units/dist/has-feature.d.ts b/plugins/postcss-logical-viewport-units/dist/has-feature.d.ts
new file mode 100644
index 000000000..a902b0da2
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/dist/has-feature.d.ts
@@ -0,0 +1 @@
+export declare function hasFeature(source: string): boolean;
diff --git a/plugins/postcss-logical-viewport-units/dist/has-supports-at-rule-ancestor.d.ts b/plugins/postcss-logical-viewport-units/dist/has-supports-at-rule-ancestor.d.ts
new file mode 100644
index 000000000..a46009ecc
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/dist/has-supports-at-rule-ancestor.d.ts
@@ -0,0 +1,2 @@
+import type { Node } from 'postcss';
+export declare function hasSupportsAtRuleAncestor(node: Node): boolean;
diff --git a/plugins/postcss-logical-viewport-units/dist/index.cjs b/plugins/postcss-logical-viewport-units/dist/index.cjs
index 8097abdbd..5078d5018 100644
--- a/plugins/postcss-logical-viewport-units/dist/index.cjs
+++ b/plugins/postcss-logical-viewport-units/dist/index.cjs
@@ -1 +1 @@
-"use strict";var e=require("@csstools/css-tokenizer");function transform(o,t){{const e=o.toLowerCase();if(!e.includes("vb")&&!e.includes("vi"))return o}const n=e.tokenizer({css:o}),s=[];let i=!1;for(;;){const o=n.nextToken();if(s.push(o),o[0]===e.TokenType.EOF)break;if(o[0]!==e.TokenType.Dimension)continue;const r=t[o[4].unit.toLowerCase()];r&&(o[1]=o[4].value.toString()+r,o[4].unit=r,i=!0)}return i?e.stringify(...s):o}const creator=e=>{const o=Object.assign({writingMode:"horizontal",preserve:!0},e);"vertical"!==o.writingMode&&(o.writingMode="horizontal");const t="horizontal"===o.writingMode?{vi:"vw",vb:"vh"}:{vi:"vh",vb:"vw"};return{postcssPlugin:"postcss-logical-viewport-units",Declaration(e){const n=transform(e.value,t);n!==e.value&&(e.cloneBefore({value:n}),o.preserve||e.remove())}}};creator.postcss=!0,module.exports=creator;
+"use strict";var e=require("@csstools/css-tokenizer");function hasFeature(t){{const e=t.toLowerCase();if(!e.includes("vb")&&!e.includes("vi"))return!1}const r=e.tokenizer({css:t});for(;;){const t=r.nextToken();if(t[0]===e.TokenType.EOF)break;if(t[0]!==e.TokenType.Dimension)continue;const n=t[4].unit.toLowerCase();if("vb"===n||"vi"===n)return!0}return!1}function hasSupportsAtRuleAncestor(e){let t=e.parent;for(;t;)if("atrule"===t.type){if("supports"===t.name.toLowerCase()&&hasFeature(t.params))return!0;t=t.parent}else t=t.parent;return!1}function transform(t,r){const n=e.tokenizer({css:t}),o=[];let s=!1;for(;;){const t=n.nextToken();if(o.push(t),t[0]===e.TokenType.EOF)break;if(t[0]!==e.TokenType.Dimension)continue;const i=r[t[4].unit.toLowerCase()];i&&(t[1]=t[4].value.toString()+i,t[4].unit=i,s=!0)}return s?e.stringify(...o):t}const creator=e=>{const t=Object.assign({writingMode:"horizontal",preserve:!0},e);"vertical"!==t.writingMode&&(t.writingMode="horizontal");const r={vb:"vh",vi:"vw"};return"vertical"===t.writingMode&&(r.vb="vw",r.vi="vh"),{postcssPlugin:"postcss-logical-viewport-units",Declaration(e,{atRule:n}){{const t=e.value.toLowerCase();if(!t.includes("vb")&&!t.includes("vi"))return;const r=e.prev();if(r&&"decl"===r.type&&r.prop===e.prop)return;if(hasSupportsAtRuleAncestor(e))return}const o=transform(e.value,r);if(o===e.value)return;if(e.cloneBefore({value:o}),!t.preserve)return void e.remove();if(!e.variable)return;const s=n({name:"supports",params:"(top: 1vi)",source:e.source}),i=e.parent,u=e.parent.cloneAfter({nodes:[]});u.append(e),s.append(u),i.after(s)}}};creator.postcss=!0,module.exports=creator;
diff --git a/plugins/postcss-logical-viewport-units/dist/index.mjs b/plugins/postcss-logical-viewport-units/dist/index.mjs
index 1f5542b21..455f0ae04 100644
--- a/plugins/postcss-logical-viewport-units/dist/index.mjs
+++ b/plugins/postcss-logical-viewport-units/dist/index.mjs
@@ -1 +1 @@
-import{tokenizer as o,TokenType as t,stringify as e}from"@csstools/css-tokenizer";function transform(n,s){{const o=n.toLowerCase();if(!o.includes("vb")&&!o.includes("vi"))return n}const i=o({css:n}),r=[];let c=!1;for(;;){const o=i.nextToken();if(r.push(o),o[0]===t.EOF)break;if(o[0]!==t.Dimension)continue;const e=s[o[4].unit.toLowerCase()];e&&(o[1]=o[4].value.toString()+e,o[4].unit=e,c=!0)}return c?e(...r):n}const creator=o=>{const t=Object.assign({writingMode:"horizontal",preserve:!0},o);"vertical"!==t.writingMode&&(t.writingMode="horizontal");const e="horizontal"===t.writingMode?{vi:"vw",vb:"vh"}:{vi:"vh",vb:"vw"};return{postcssPlugin:"postcss-logical-viewport-units",Declaration(o){const n=transform(o.value,e);n!==o.value&&(o.cloneBefore({value:n}),t.preserve||o.remove())}}};creator.postcss=!0;export{creator as default};
+import{tokenizer as e,TokenType as t,stringify as r}from"@csstools/css-tokenizer";function hasFeature(r){{const e=r.toLowerCase();if(!e.includes("vb")&&!e.includes("vi"))return!1}const n=e({css:r});for(;;){const e=n.nextToken();if(e[0]===t.EOF)break;if(e[0]!==t.Dimension)continue;const r=e[4].unit.toLowerCase();if("vb"===r||"vi"===r)return!0}return!1}function hasSupportsAtRuleAncestor(e){let t=e.parent;for(;t;)if("atrule"===t.type){if("supports"===t.name.toLowerCase()&&hasFeature(t.params))return!0;t=t.parent}else t=t.parent;return!1}function transform(n,o){const s=e({css:n}),i=[];let a=!1;for(;;){const e=s.nextToken();if(i.push(e),e[0]===t.EOF)break;if(e[0]!==t.Dimension)continue;const r=o[e[4].unit.toLowerCase()];r&&(e[1]=e[4].value.toString()+r,e[4].unit=r,a=!0)}return a?r(...i):n}const creator=e=>{const t=Object.assign({writingMode:"horizontal",preserve:!0},e);"vertical"!==t.writingMode&&(t.writingMode="horizontal");const r={vb:"vh",vi:"vw"};return"vertical"===t.writingMode&&(r.vb="vw",r.vi="vh"),{postcssPlugin:"postcss-logical-viewport-units",Declaration(e,{atRule:n}){{const t=e.value.toLowerCase();if(!t.includes("vb")&&!t.includes("vi"))return;const r=e.prev();if(r&&"decl"===r.type&&r.prop===e.prop)return;if(hasSupportsAtRuleAncestor(e))return}const o=transform(e.value,r);if(o===e.value)return;if(e.cloneBefore({value:o}),!t.preserve)return void e.remove();if(!e.variable)return;const s=n({name:"supports",params:"(top: 1vi)",source:e.source}),i=e.parent,a=e.parent.cloneAfter({nodes:[]});a.append(e),s.append(a),i.after(s)}}};creator.postcss=!0;export{creator as default};
diff --git a/plugins/postcss-logical-viewport-units/src/has-feature.ts b/plugins/postcss-logical-viewport-units/src/has-feature.ts
new file mode 100644
index 000000000..5e8c3a35e
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/src/has-feature.ts
@@ -0,0 +1,31 @@
+import { tokenizer, TokenType } from '@csstools/css-tokenizer';
+
+export function hasFeature(source: string): boolean {
+ {
+ const lowerCaseValue = source.toLowerCase();
+ if (!(lowerCaseValue.includes('vb') || lowerCaseValue.includes('vi'))) {
+ return false;
+ }
+ }
+
+ const t = tokenizer({ css: source });
+
+ // eslint-disable-next-line no-constant-condition
+ while (true) {
+ const token = t.nextToken();
+ if (token[0] === TokenType.EOF) {
+ break;
+ }
+
+ if (token[0] !== TokenType.Dimension) {
+ continue;
+ }
+
+ const unit = token[4].unit.toLowerCase();
+ if (unit === 'vb' || unit === 'vi') {
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/plugins/postcss-logical-viewport-units/src/has-supports-at-rule-ancestor.ts b/plugins/postcss-logical-viewport-units/src/has-supports-at-rule-ancestor.ts
new file mode 100644
index 000000000..3141478b7
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/src/has-supports-at-rule-ancestor.ts
@@ -0,0 +1,22 @@
+import type { Node, AtRule } from 'postcss';
+import { hasFeature } from './has-feature';
+
+export function hasSupportsAtRuleAncestor(node: Node): boolean {
+ let parent = node.parent;
+ while (parent) {
+ if (parent.type !== 'atrule') {
+ parent = parent.parent;
+ continue;
+ }
+
+ if ((parent as AtRule).name.toLowerCase() === 'supports') {
+ if (hasFeature((parent as AtRule).params)) {
+ return true;
+ }
+ }
+
+ parent = parent.parent;
+ }
+
+ return false;
+}
diff --git a/plugins/postcss-logical-viewport-units/src/index.ts b/plugins/postcss-logical-viewport-units/src/index.ts
index 7878f6e8f..a5e89fa2f 100644
--- a/plugins/postcss-logical-viewport-units/src/index.ts
+++ b/plugins/postcss-logical-viewport-units/src/index.ts
@@ -1,4 +1,5 @@
import type { PluginCreator } from 'postcss';
+import { hasSupportsAtRuleAncestor } from './has-supports-at-rule-ancestor';
import { transform } from './transform';
/** postcss-logical-viewport-units plugin options */
@@ -24,17 +25,38 @@ const creator: PluginCreator = (opts?: pluginOptions) => {
options.writingMode = 'horizontal';
}
- const replacements: { vi: 'vw' | 'vh', vb: 'vw' | 'vh' } = options.writingMode === 'horizontal' ? {
- vi: 'vw',
+ const replacements: { vi: 'vw' | 'vh', vb: 'vw' | 'vh' } = {
vb: 'vh',
- } : {
- vi: 'vh',
- vb: 'vw',
+ vi: 'vw',
};
+ if (options.writingMode === 'vertical') {
+ replacements.vb = 'vw';
+ replacements.vi = 'vh';
+ }
+
return {
postcssPlugin: 'postcss-logical-viewport-units',
- Declaration(decl) {
+ Declaration(decl, { atRule }) {
+ {
+ // Fast check
+ const lowerCaseValue = decl.value.toLowerCase();
+ if (!(lowerCaseValue.includes('vb') || lowerCaseValue.includes('vi'))) {
+ return;
+ }
+
+ // Declaration already has a fallback
+ const prev = decl.prev();
+ if (prev && prev.type === 'decl' && prev.prop === decl.prop) {
+ return;
+ }
+
+ // Is wrapped in a relevant `@supports`
+ if (hasSupportsAtRuleAncestor(decl)) {
+ return;
+ }
+ }
+
const modifiedValue = transform(decl.value, replacements);
if (modifiedValue === decl.value) {
return;
@@ -44,11 +66,28 @@ const creator: PluginCreator = (opts?: pluginOptions) => {
value: modifiedValue,
});
- if (options.preserve) {
+ if (!options.preserve) {
+ decl.remove();
return;
}
- decl.remove();
+ if (!decl.variable) {
+ return;
+ }
+
+ const supports = atRule({
+ name: 'supports',
+ params: '(top: 1vi)',
+ source: decl.source,
+ });
+
+ const parent = decl.parent;
+ const parentClone = decl.parent.cloneAfter({ nodes: [] });
+
+ parentClone.append(decl);
+ supports.append(parentClone);
+
+ parent.after(supports);
},
};
};
diff --git a/plugins/postcss-logical-viewport-units/src/transform.ts b/plugins/postcss-logical-viewport-units/src/transform.ts
index 35d6a20d1..0b0c2e697 100644
--- a/plugins/postcss-logical-viewport-units/src/transform.ts
+++ b/plugins/postcss-logical-viewport-units/src/transform.ts
@@ -1,13 +1,6 @@
import { stringify, tokenizer, TokenType } from '@csstools/css-tokenizer';
export function transform(source: string, replacements: { vi: 'vw' | 'vh', vb: 'vw' | 'vh' }): string {
- {
- const lowerCaseValue = source.toLowerCase();
- if (!(lowerCaseValue.includes('vb') || lowerCaseValue.includes('vi'))) {
- return source;
- }
- }
-
const t = tokenizer({ css: source });
const tokens = [];
let didTransformUnits = false;
diff --git a/plugins/postcss-logical-viewport-units/test/basic.css b/plugins/postcss-logical-viewport-units/test/basic.css
index 201d2aa70..0cc0ba9ac 100644
--- a/plugins/postcss-logical-viewport-units/test/basic.css
+++ b/plugins/postcss-logical-viewport-units/test/basic.css
@@ -13,3 +13,18 @@
.vi {
left: 5vi;
}
+
+@supports (width: 100vb) {
+ .vi {
+ left: 5vi;
+ }
+}
+
+:root {
+ --var: 5vi;
+}
+
+.fallback {
+ left: 5vw;
+ left: 5vi;
+}
diff --git a/plugins/postcss-logical-viewport-units/test/basic.expect.css b/plugins/postcss-logical-viewport-units/test/basic.expect.css
index 54d07b22f..79dc02cd2 100644
--- a/plugins/postcss-logical-viewport-units/test/basic.expect.css
+++ b/plugins/postcss-logical-viewport-units/test/basic.expect.css
@@ -17,3 +17,25 @@
left: 5vw;
left: 5vi;
}
+
+@supports (width: 100vb) {
+ .vi {
+ left: 5vi;
+ }
+}
+
+:root {
+ --var: 5vw;
+}
+
+@supports (top: 1vi) {
+
+:root {
+ --var: 5vi;
+}
+}
+
+.fallback {
+ left: 5vw;
+ left: 5vi;
+}
diff --git a/plugins/postcss-logical-viewport-units/test/basic.vertical.expect.css b/plugins/postcss-logical-viewport-units/test/basic.vertical.expect.css
index 015117cfe..dd691b29e 100644
--- a/plugins/postcss-logical-viewport-units/test/basic.vertical.expect.css
+++ b/plugins/postcss-logical-viewport-units/test/basic.vertical.expect.css
@@ -17,3 +17,25 @@
left: 5vh;
left: 5vi;
}
+
+@supports (width: 100vb) {
+ .vi {
+ left: 5vi;
+ }
+}
+
+:root {
+ --var: 5vh;
+}
+
+@supports (top: 1vi) {
+
+:root {
+ --var: 5vi;
+}
+}
+
+.fallback {
+ left: 5vw;
+ left: 5vi;
+}
From 2d177b4173913ed37820d498b9ebccf26d24a94d Mon Sep 17 00:00:00 2001
From: Romain Menke
Date: Mon, 16 Jan 2023 10:52:44 +0100
Subject: [PATCH 3/3] match other logical plugins
---
.github/ISSUE_TEMPLATE/css-issue.yml | 1 +
.github/ISSUE_TEMPLATE/plugin-issue.yml | 1 +
.github/labeler.yml | 4 ++
.../postcss-logical-viewport-units/.tape.mjs | 12 ++++--
.../postcss-logical-viewport-units/README.md | 34 ++++++++++-----
.../dist/index.cjs | 2 +-
.../dist/index.d.ts | 5 ++-
.../dist/index.mjs | 2 +-
.../dist/lib/types.d.ts | 18 ++++++++
.../docs/README.md | 34 ++++++++++-----
.../src/index.ts | 16 +++++---
.../src/lib/types.ts | 19 +++++++++
.../test/basic.hebrew.expect.css | 41 +++++++++++++++++++
13 files changed, 154 insertions(+), 35 deletions(-)
create mode 100644 plugins/postcss-logical-viewport-units/dist/lib/types.d.ts
create mode 100644 plugins/postcss-logical-viewport-units/src/lib/types.ts
create mode 100644 plugins/postcss-logical-viewport-units/test/basic.hebrew.expect.css
diff --git a/.github/ISSUE_TEMPLATE/css-issue.yml b/.github/ISSUE_TEMPLATE/css-issue.yml
index 135995ae7..9e5322213 100644
--- a/.github/ISSUE_TEMPLATE/css-issue.yml
+++ b/.github/ISSUE_TEMPLATE/css-issue.yml
@@ -87,6 +87,7 @@ body:
- PostCSS Is Pseudo Class
- PostCSS Lab Function
- PostCSS Logical
+ - PostCSS Logical Viewport Units
- PostCSS Media Queries Aspect-Ratio Number Values
- PostCSS Media Query Ranges
- PostCSS Nested Calc
diff --git a/.github/ISSUE_TEMPLATE/plugin-issue.yml b/.github/ISSUE_TEMPLATE/plugin-issue.yml
index 2fa9f2915..c3fb620c3 100644
--- a/.github/ISSUE_TEMPLATE/plugin-issue.yml
+++ b/.github/ISSUE_TEMPLATE/plugin-issue.yml
@@ -89,6 +89,7 @@ body:
- PostCSS Is Pseudo Class
- PostCSS Lab Function
- PostCSS Logical
+ - PostCSS Logical Viewport Units
- PostCSS Media Queries Aspect-Ratio Number Values
- PostCSS Media Query Ranges
- PostCSS Nested Calc
diff --git a/.github/labeler.yml b/.github/labeler.yml
index c874c1372..c762356b6 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -136,6 +136,10 @@
- plugins/postcss-logical/**
- experimental/postcss-logical/**
+"plugins/postcss-logical-viewport-units":
+ - plugins/postcss-logical-viewport-units/**
+ - experimental/postcss-logical-viewport-units/**
+
"plugins/media-queries-aspect-ratio-number-values":
- plugins/postcss-media-queries-aspect-ratio-number-values/**
- experimental/postcss-media-queries-aspect-ratio-number-values/**
diff --git a/plugins/postcss-logical-viewport-units/.tape.mjs b/plugins/postcss-logical-viewport-units/.tape.mjs
index 534476da1..ac23bcc9d 100644
--- a/plugins/postcss-logical-viewport-units/.tape.mjs
+++ b/plugins/postcss-logical-viewport-units/.tape.mjs
@@ -5,10 +5,16 @@ postcssTape(plugin)({
basic: {
message: "supports basic usage",
},
+ 'basic:hebrew': {
+ message: "supports { inlineDirection: 'right-to-left' }",
+ options: {
+ inlineDirection: 'right-to-left'
+ }
+ },
'basic:vertical': {
- message: "supports vertical writing mode",
+ message: "supports { inlineDirection: 'top-to-bottom' }",
options: {
- writingMode: 'vertical',
+ inlineDirection: 'top-to-bottom'
}
},
'examples/example': {
@@ -17,7 +23,7 @@ postcssTape(plugin)({
'examples/example:vertical': {
message: 'minimal example',
options: {
- writingMode: 'vertical',
+ inlineDirection: 'top-to-bottom'
}
},
'examples/example:preserve-false': {
diff --git a/plugins/postcss-logical-viewport-units/README.md b/plugins/postcss-logical-viewport-units/README.md
index e464db3d4..2ccc78f71 100644
--- a/plugins/postcss-logical-viewport-units/README.md
+++ b/plugins/postcss-logical-viewport-units/README.md
@@ -49,27 +49,39 @@ instructions for:
## Options
-### writingMode
+ ### inlineDirection
-The `writingMode` option determines whether fallback values are generated for documents with a horizontal or vertical writing mode.
-By default documents have a horizontal writing mode.
+ The `inlineDirection` option allows you to specify the direction of the inline axe. The default value is `left-to-right` respectively which would match any latin language.
-```js
-postcssLogicalViewportUnits({ writingMode: 'vertical' })
-```
+ You might want to tweak these value if you are using a different writing system, such as Arabic, Hebrew or Chinese for example.
-```pcss
-.foo {
+ ```js
+ postcssLogicalViewportUnits({
+ inlineDirection: 'top-to-bottom'
+ })
+ ```
+
+ ```pcss
+ .foo {
margin: 10vi 20vb;
}
-/* becomes */
+ /* becomes */
-.foo {
+ .foo {
margin: 10vh 20vw;
margin: 10vi 20vb;
}
-```
+ ```
+
+ Each direction must be one of the following:
+
+ - `top-to-bottom`
+ - `bottom-to-top`
+ - `left-to-right`
+ - `right-to-left`
+
+ Please do note that transformations won't do anything particular for `right-to-left` or `bottom-to-top`.
### preserve
diff --git a/plugins/postcss-logical-viewport-units/dist/index.cjs b/plugins/postcss-logical-viewport-units/dist/index.cjs
index 5078d5018..f94381309 100644
--- a/plugins/postcss-logical-viewport-units/dist/index.cjs
+++ b/plugins/postcss-logical-viewport-units/dist/index.cjs
@@ -1 +1 @@
-"use strict";var e=require("@csstools/css-tokenizer");function hasFeature(t){{const e=t.toLowerCase();if(!e.includes("vb")&&!e.includes("vi"))return!1}const r=e.tokenizer({css:t});for(;;){const t=r.nextToken();if(t[0]===e.TokenType.EOF)break;if(t[0]!==e.TokenType.Dimension)continue;const n=t[4].unit.toLowerCase();if("vb"===n||"vi"===n)return!0}return!1}function hasSupportsAtRuleAncestor(e){let t=e.parent;for(;t;)if("atrule"===t.type){if("supports"===t.name.toLowerCase()&&hasFeature(t.params))return!0;t=t.parent}else t=t.parent;return!1}function transform(t,r){const n=e.tokenizer({css:t}),o=[];let s=!1;for(;;){const t=n.nextToken();if(o.push(t),t[0]===e.TokenType.EOF)break;if(t[0]!==e.TokenType.Dimension)continue;const i=r[t[4].unit.toLowerCase()];i&&(t[1]=t[4].value.toString()+i,t[4].unit=i,s=!0)}return s?e.stringify(...o):t}const creator=e=>{const t=Object.assign({writingMode:"horizontal",preserve:!0},e);"vertical"!==t.writingMode&&(t.writingMode="horizontal");const r={vb:"vh",vi:"vw"};return"vertical"===t.writingMode&&(r.vb="vw",r.vi="vh"),{postcssPlugin:"postcss-logical-viewport-units",Declaration(e,{atRule:n}){{const t=e.value.toLowerCase();if(!t.includes("vb")&&!t.includes("vi"))return;const r=e.prev();if(r&&"decl"===r.type&&r.prop===e.prop)return;if(hasSupportsAtRuleAncestor(e))return}const o=transform(e.value,r);if(o===e.value)return;if(e.cloneBefore({value:o}),!t.preserve)return void e.remove();if(!e.variable)return;const s=n({name:"supports",params:"(top: 1vi)",source:e.source}),i=e.parent,u=e.parent.cloneAfter({nodes:[]});u.append(e),s.append(u),i.after(s)}}};creator.postcss=!0,module.exports=creator;
+"use strict";var e,t,o=require("@csstools/css-tokenizer");function hasFeature(e){{const t=e.toLowerCase();if(!t.includes("vb")&&!t.includes("vi"))return!1}const t=o.tokenizer({css:e});for(;;){const e=t.nextToken();if(e[0]===o.TokenType.EOF)break;if(e[0]!==o.TokenType.Dimension)continue;const n=e[4].unit.toLowerCase();if("vb"===n||"vi"===n)return!0}return!1}function hasSupportsAtRuleAncestor(e){let t=e.parent;for(;t;)if("atrule"===t.type){if("supports"===t.name.toLowerCase()&&hasFeature(t.params))return!0;t=t.parent}else t=t.parent;return!1}function transform(e,t){const n=o.tokenizer({css:e}),r=[];let i=!1;for(;;){const e=n.nextToken();if(r.push(e),e[0]===o.TokenType.EOF)break;if(e[0]!==o.TokenType.Dimension)continue;const s=t[e[4].unit.toLowerCase()];s&&(e[1]=e[4].value.toString()+s,e[4].unit=s,i=!0)}return i?o.stringify(...r):e}!function(e){e.TopToBottom="top-to-bottom",e.BottomToTop="bottom-to-top",e.RightToLeft="right-to-left",e.LeftToRight="left-to-right"}(e||(e={})),function(e){e.Top="top",e.Right="right",e.Bottom="bottom",e.Left="left"}(t||(t={}));const creator=t=>{const o=Object.assign({inlineDirection:e.LeftToRight,preserve:!0},t),n=Object.values(e);if(!n.includes(o.inlineDirection))throw new Error(`[postcss-logical-viewport-units] "inlineDirection" must be one of ${n.join(", ")}`);const r=[e.LeftToRight,e.RightToLeft].includes(o.inlineDirection),i={vb:"vh",vi:"vw"};return r||(i.vb="vw",i.vi="vh"),{postcssPlugin:"postcss-logical-viewport-units",Declaration(e,{atRule:t}){{const t=e.value.toLowerCase();if(!t.includes("vb")&&!t.includes("vi"))return;const o=e.prev();if(o&&"decl"===o.type&&o.prop===e.prop)return;if(hasSupportsAtRuleAncestor(e))return}const n=transform(e.value,i);if(n===e.value)return;if(e.cloneBefore({value:n}),!o.preserve)return void e.remove();if(!e.variable)return;const r=t({name:"supports",params:"(top: 1vi)",source:e.source}),s=e.parent,u=e.parent.cloneAfter({nodes:[]});u.append(e),r.append(u),s.after(r)}}};creator.postcss=!0,module.exports=creator;
diff --git a/plugins/postcss-logical-viewport-units/dist/index.d.ts b/plugins/postcss-logical-viewport-units/dist/index.d.ts
index 8fb99b2e0..bf4c6641b 100644
--- a/plugins/postcss-logical-viewport-units/dist/index.d.ts
+++ b/plugins/postcss-logical-viewport-units/dist/index.d.ts
@@ -1,10 +1,11 @@
import type { PluginCreator } from 'postcss';
+import { DirectionFlow } from './lib/types';
/** postcss-logical-viewport-units plugin options */
export type pluginOptions = {
/** Preserve the original notation. default: false */
preserve?: boolean;
- /** Control how logical viewport units are replaced. default: "horizontal" */
- writingMode: 'horizontal' | 'vertical';
+ /** Sets the direction for inline. default: left-to-right */
+ inlineDirection?: DirectionFlow;
};
declare const creator: PluginCreator;
export default creator;
diff --git a/plugins/postcss-logical-viewport-units/dist/index.mjs b/plugins/postcss-logical-viewport-units/dist/index.mjs
index 455f0ae04..e8e18bad5 100644
--- a/plugins/postcss-logical-viewport-units/dist/index.mjs
+++ b/plugins/postcss-logical-viewport-units/dist/index.mjs
@@ -1 +1 @@
-import{tokenizer as e,TokenType as t,stringify as r}from"@csstools/css-tokenizer";function hasFeature(r){{const e=r.toLowerCase();if(!e.includes("vb")&&!e.includes("vi"))return!1}const n=e({css:r});for(;;){const e=n.nextToken();if(e[0]===t.EOF)break;if(e[0]!==t.Dimension)continue;const r=e[4].unit.toLowerCase();if("vb"===r||"vi"===r)return!0}return!1}function hasSupportsAtRuleAncestor(e){let t=e.parent;for(;t;)if("atrule"===t.type){if("supports"===t.name.toLowerCase()&&hasFeature(t.params))return!0;t=t.parent}else t=t.parent;return!1}function transform(n,o){const s=e({css:n}),i=[];let a=!1;for(;;){const e=s.nextToken();if(i.push(e),e[0]===t.EOF)break;if(e[0]!==t.Dimension)continue;const r=o[e[4].unit.toLowerCase()];r&&(e[1]=e[4].value.toString()+r,e[4].unit=r,a=!0)}return a?r(...i):n}const creator=e=>{const t=Object.assign({writingMode:"horizontal",preserve:!0},e);"vertical"!==t.writingMode&&(t.writingMode="horizontal");const r={vb:"vh",vi:"vw"};return"vertical"===t.writingMode&&(r.vb="vw",r.vi="vh"),{postcssPlugin:"postcss-logical-viewport-units",Declaration(e,{atRule:n}){{const t=e.value.toLowerCase();if(!t.includes("vb")&&!t.includes("vi"))return;const r=e.prev();if(r&&"decl"===r.type&&r.prop===e.prop)return;if(hasSupportsAtRuleAncestor(e))return}const o=transform(e.value,r);if(o===e.value)return;if(e.cloneBefore({value:o}),!t.preserve)return void e.remove();if(!e.variable)return;const s=n({name:"supports",params:"(top: 1vi)",source:e.source}),i=e.parent,a=e.parent.cloneAfter({nodes:[]});a.append(e),s.append(a),i.after(s)}}};creator.postcss=!0;export{creator as default};
+import{tokenizer as t,TokenType as e,stringify as o}from"@csstools/css-tokenizer";function hasFeature(o){{const t=o.toLowerCase();if(!t.includes("vb")&&!t.includes("vi"))return!1}const n=t({css:o});for(;;){const t=n.nextToken();if(t[0]===e.EOF)break;if(t[0]!==e.Dimension)continue;const o=t[4].unit.toLowerCase();if("vb"===o||"vi"===o)return!0}return!1}function hasSupportsAtRuleAncestor(t){let e=t.parent;for(;e;)if("atrule"===e.type){if("supports"===e.name.toLowerCase()&&hasFeature(e.params))return!0;e=e.parent}else e=e.parent;return!1}var n,r;function transform(n,r){const i=t({css:n}),s=[];let u=!1;for(;;){const t=i.nextToken();if(s.push(t),t[0]===e.EOF)break;if(t[0]!==e.Dimension)continue;const o=r[t[4].unit.toLowerCase()];o&&(t[1]=t[4].value.toString()+o,t[4].unit=o,u=!0)}return u?o(...s):n}!function(t){t.TopToBottom="top-to-bottom",t.BottomToTop="bottom-to-top",t.RightToLeft="right-to-left",t.LeftToRight="left-to-right"}(n||(n={})),function(t){t.Top="top",t.Right="right",t.Bottom="bottom",t.Left="left"}(r||(r={}));const creator=t=>{const e=Object.assign({inlineDirection:n.LeftToRight,preserve:!0},t),o=Object.values(n);if(!o.includes(e.inlineDirection))throw new Error(`[postcss-logical-viewport-units] "inlineDirection" must be one of ${o.join(", ")}`);const r=[n.LeftToRight,n.RightToLeft].includes(e.inlineDirection),i={vb:"vh",vi:"vw"};return r||(i.vb="vw",i.vi="vh"),{postcssPlugin:"postcss-logical-viewport-units",Declaration(t,{atRule:o}){{const e=t.value.toLowerCase();if(!e.includes("vb")&&!e.includes("vi"))return;const o=t.prev();if(o&&"decl"===o.type&&o.prop===t.prop)return;if(hasSupportsAtRuleAncestor(t))return}const n=transform(t.value,i);if(n===t.value)return;if(t.cloneBefore({value:n}),!e.preserve)return void t.remove();if(!t.variable)return;const r=o({name:"supports",params:"(top: 1vi)",source:t.source}),s=t.parent,u=t.parent.cloneAfter({nodes:[]});u.append(t),r.append(u),s.after(r)}}};creator.postcss=!0;export{creator as default};
diff --git a/plugins/postcss-logical-viewport-units/dist/lib/types.d.ts b/plugins/postcss-logical-viewport-units/dist/lib/types.d.ts
new file mode 100644
index 000000000..2b3c47f9b
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/dist/lib/types.d.ts
@@ -0,0 +1,18 @@
+export declare const DirectionValues: {
+ BlockStart: string;
+ BlockEnd: string;
+ InlineStart: string;
+ InlineEnd: string;
+};
+export declare enum DirectionFlow {
+ TopToBottom = "top-to-bottom",
+ BottomToTop = "bottom-to-top",
+ RightToLeft = "right-to-left",
+ LeftToRight = "left-to-right"
+}
+export declare enum Axes {
+ Top = "top",
+ Right = "right",
+ Bottom = "bottom",
+ Left = "left"
+}
diff --git a/plugins/postcss-logical-viewport-units/docs/README.md b/plugins/postcss-logical-viewport-units/docs/README.md
index 0a34aa98b..1072b4f42 100644
--- a/plugins/postcss-logical-viewport-units/docs/README.md
+++ b/plugins/postcss-logical-viewport-units/docs/README.md
@@ -32,22 +32,34 @@
## Options
-### writingMode
+ ### inlineDirection
-The `writingMode` option determines whether fallback values are generated for documents with a horizontal or vertical writing mode.
-By default documents have a horizontal writing mode.
+ The `inlineDirection` option allows you to specify the direction of the inline axe. The default value is `left-to-right` respectively which would match any latin language.
-```js
-({ writingMode: 'vertical' })
-```
+ You might want to tweak these value if you are using a different writing system, such as Arabic, Hebrew or Chinese for example.
-```pcss
-
+ ```js
+ ({
+ inlineDirection: 'top-to-bottom'
+ })
+ ```
-/* becomes */
+ ```pcss
+
-
-```
+ /* becomes */
+
+
+ ```
+
+ Each direction must be one of the following:
+
+ - `top-to-bottom`
+ - `bottom-to-top`
+ - `left-to-right`
+ - `right-to-left`
+
+ Please do note that transformations won't do anything particular for `right-to-left` or `bottom-to-top`.
### preserve
diff --git a/plugins/postcss-logical-viewport-units/src/index.ts b/plugins/postcss-logical-viewport-units/src/index.ts
index a5e89fa2f..5868e6a41 100644
--- a/plugins/postcss-logical-viewport-units/src/index.ts
+++ b/plugins/postcss-logical-viewport-units/src/index.ts
@@ -1,36 +1,40 @@
import type { PluginCreator } from 'postcss';
import { hasSupportsAtRuleAncestor } from './has-supports-at-rule-ancestor';
+import { DirectionFlow } from './lib/types';
import { transform } from './transform';
/** postcss-logical-viewport-units plugin options */
export type pluginOptions = {
/** Preserve the original notation. default: false */
preserve?: boolean,
- /** Control how logical viewport units are replaced. default: "horizontal" */
- writingMode: 'horizontal' | 'vertical',
+ /** Sets the direction for inline. default: left-to-right */
+ inlineDirection?: DirectionFlow,
};
const creator: PluginCreator = (opts?: pluginOptions) => {
const options = Object.assign(
// Default options
{
- writingMode: 'horizontal',
+ inlineDirection: DirectionFlow.LeftToRight,
preserve: true,
},
// Provided options
opts,
);
- if (options.writingMode !== 'vertical') {
- options.writingMode = 'horizontal';
+ const directionValues = Object.values(DirectionFlow);
+ if (!directionValues.includes(options.inlineDirection)) {
+ throw new Error(`[postcss-logical-viewport-units] "inlineDirection" must be one of ${directionValues.join(', ')}`);
}
+ const isHorizontal = [DirectionFlow.LeftToRight, DirectionFlow.RightToLeft].includes(options.inlineDirection);
+
const replacements: { vi: 'vw' | 'vh', vb: 'vw' | 'vh' } = {
vb: 'vh',
vi: 'vw',
};
- if (options.writingMode === 'vertical') {
+ if (!isHorizontal) {
replacements.vb = 'vw';
replacements.vi = 'vh';
}
diff --git a/plugins/postcss-logical-viewport-units/src/lib/types.ts b/plugins/postcss-logical-viewport-units/src/lib/types.ts
new file mode 100644
index 000000000..06807b9c0
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/src/lib/types.ts
@@ -0,0 +1,19 @@
+export const DirectionValues = {
+ BlockStart: 'block-start',
+ BlockEnd: 'block-end',
+ InlineStart: 'inline-start',
+ InlineEnd: 'inline-end',
+};
+export enum DirectionFlow {
+ TopToBottom = 'top-to-bottom',
+ BottomToTop = 'bottom-to-top',
+ RightToLeft = 'right-to-left',
+ LeftToRight = 'left-to-right',
+}
+
+export enum Axes {
+ Top = 'top',
+ Right = 'right',
+ Bottom = 'bottom',
+ Left = 'left',
+}
diff --git a/plugins/postcss-logical-viewport-units/test/basic.hebrew.expect.css b/plugins/postcss-logical-viewport-units/test/basic.hebrew.expect.css
new file mode 100644
index 000000000..79dc02cd2
--- /dev/null
+++ b/plugins/postcss-logical-viewport-units/test/basic.hebrew.expect.css
@@ -0,0 +1,41 @@
+.foo {
+ margin: 10vw 20vh;
+ margin: 10vi 20vb;
+}
+
+.foo {
+ margin: calc(10vw + 5vh);
+ margin: calc(10vi + 5vb);
+}
+
+.vb {
+ left: 5vh;
+ left: 5vb;
+}
+
+.vi {
+ left: 5vw;
+ left: 5vi;
+}
+
+@supports (width: 100vb) {
+ .vi {
+ left: 5vi;
+ }
+}
+
+:root {
+ --var: 5vw;
+}
+
+@supports (top: 1vi) {
+
+:root {
+ --var: 5vi;
+}
+}
+
+.fallback {
+ left: 5vw;
+ left: 5vi;
+}