├── .appveyor.yml
├── .editorconfig
├── .gitignore
├── .rollup.js
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── index.js
├── lib
├── CSSKeywordValue.js
├── CSSMathInvert.js
├── CSSMathMax.js
├── CSSMathMin.js
├── CSSMathProduct.js
├── CSSMathSum.js
├── CSSNumericValue.js
├── CSSStyleValue.js
├── CSSUnitValue.js
├── StylePropertyMap.js
├── parse-as-value.js
├── polyfill.js
└── units.js
├── package.json
└── test.js
/.appveyor.yml:
--------------------------------------------------------------------------------
1 | # https://www.appveyor.com/docs/appveyor-yml
2 |
3 | environment:
4 | matrix:
5 | - nodejs_version: 4
6 |
7 | version: "{build}"
8 | build: off
9 | deploy: off
10 |
11 | install:
12 | - ps: Install-Product node $env:nodejs_version
13 | - npm install --ignore-scripts
14 |
15 | test_script:
16 | - node --version
17 | - npm --version
18 | - cmd: "npm test"
19 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_style = tab
7 | insert_final_newline = true
8 | trim_trailing_whitespace = true
9 |
10 | [*.md]
11 | trim_trailing_whitespace = false
12 |
13 | [*.{json,md,yml}]
14 | indent_size = 2
15 | indent_style = space
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | index.*.js
3 | package-lock.json
4 | *.log*
5 | *.result.css
6 | .*
7 | !.appveyor.yml
8 | !.editorconfig
9 | !.gitignore
10 | !.rollup.js
11 | !.tape.js
12 | !.travis.yml
13 |
--------------------------------------------------------------------------------
/.rollup.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 |
3 | export default {
4 | input: 'index.js',
5 | output: [
6 | { file: 'index.umd.js', format: 'umd', name: 'cssTypedOm' },
7 | { file: 'index.es.js', format: 'es' }
8 | ],
9 | plugins: [
10 | babel({
11 | plugins: [
12 | 'array-includes'
13 | ],
14 | presets: [
15 | ['env', { modules: false, targets: { node: 4 } }]
16 | ]
17 | })
18 | ]
19 | };
20 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # https://docs.travis-ci.com/user/travis-lint
2 |
3 | language: node_js
4 |
5 | node_js:
6 | - 10
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changes to CSS Typed Object Model
2 |
3 | ### 0.4.0 (May 11, 2018)
4 |
5 | - Fix add method
6 | - Support multiplication and division by number primatives
7 |
8 | ### 0.3.0 (April 17, 2018)
9 |
10 | - Only polyfill constructors on the window object passed into `polyfill()`
11 | - Support CSS calc, like `add()`, `sub()`, `mul()`, and `div()`
12 | - Support CSS min and max, like `min()` and `max()`
13 |
14 | ### 0.2.0 (April 4, 2018)
15 |
16 | - Safely checks for existing methods before polyfilling
17 | - `CSSUnitValue` and `CSSKeywordValue` have realistic getters and setters
18 |
19 | ### 0.1.0 (April 2, 2018)
20 |
21 | - Default export is now `polyfill`
22 | - Parsing existing CSS values is improved
23 | - Secondary underscore-prefixed values removed from `CSSUnitValue`
24 |
25 | ### 0.0.0 (March 28, 2018)
26 |
27 | - Initial version
28 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to CSS Typed Object Model
2 |
3 | You want to help? You rock! Now, take a moment to be sure your contributions
4 | make sense to everyone else.
5 |
6 | ## Reporting Issues
7 |
8 | Found a problem? Want a new feature?
9 |
10 | - See if your issue or idea has [already been reported].
11 | - Provide a [reduced test case] or a [live example].
12 |
13 | Remember, a bug is a _demonstrable problem_ caused by _our_ code.
14 |
15 | ## Submitting Pull Requests
16 |
17 | Pull requests are the greatest contributions, so be sure they are focused in
18 | scope and avoid unrelated commits.
19 |
20 | 1. To begin; [fork this project], clone your fork, and add our upstream.
21 | ```bash
22 | # Clone your fork of the repo into the current directory
23 | git clone git@github.com:YOUR_USER/css-typed-om.git
24 |
25 | # Navigate to the newly cloned directory
26 | cd css-typed-om
27 |
28 | # Assign the original repo to a remote called "upstream"
29 | git remote add upstream git@github.com:csstools/css-typed-om.git
30 |
31 | # Install the tools necessary for testing
32 | npm install
33 | ```
34 |
35 | 2. Create a branch for your feature or fix:
36 | ```bash
37 | # Move into a new branch for your feature
38 | git checkout -b feature/thing
39 | ```
40 | ```bash
41 | # Move into a new branch for your fix
42 | git checkout -b fix/something
43 | ```
44 |
45 | 3. If your code follows our practices, then push your feature branch:
46 | ```bash
47 | # Test current code
48 | npm test
49 | ```
50 | ```bash
51 | # Push the branch for your new feature
52 | git push origin feature/thing
53 | ```
54 | ```bash
55 | # Or, push the branch for your update
56 | git push origin update/something
57 | ```
58 |
59 | That’s it! Now [open a pull request] with a clear title and description.
60 |
61 | [already been reported]: issues
62 | [fork this project]: fork
63 | [live example]: https://codepen.io/pen
64 | [open a pull request]: https://help.github.com/articles/using-pull-requests/
65 | [reduced test case]: https://css-tricks.com/reduced-test-cases/
66 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # CC0 1.0 Universal
2 |
3 | ## Statement of Purpose
4 |
5 | The laws of most jurisdictions throughout the world automatically confer
6 | exclusive Copyright and Related Rights (defined below) upon the creator and
7 | subsequent owner(s) (each and all, an “owner”) of an original work of
8 | authorship and/or a database (each, a “Work”).
9 |
10 | Certain owners wish to permanently relinquish those rights to a Work for the
11 | purpose of contributing to a commons of creative, cultural and scientific works
12 | (“Commons”) that the public can reliably and without fear of later claims of
13 | infringement build upon, modify, incorporate in other works, reuse and
14 | redistribute as freely as possible in any form whatsoever and for any purposes,
15 | including without limitation commercial purposes. These owners may contribute
16 | to the Commons to promote the ideal of a free culture and the further
17 | production of creative, cultural and scientific works, or to gain reputation or
18 | greater distribution for their Work in part through the use and efforts of
19 | others.
20 |
21 | For these and/or other purposes and motivations, and without any expectation of
22 | additional consideration or compensation, the person associating CC0 with a
23 | Work (the “Affirmer”), to the extent that he or she is an owner of Copyright
24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and
25 | publicly distribute the Work under its terms, with knowledge of his or her
26 | Copyright and Related Rights in the Work and the meaning and intended legal
27 | effect of CC0 on those rights.
28 |
29 | 1. Copyright and Related Rights. A Work made available under CC0 may be
30 | protected by copyright and related or neighboring rights (“Copyright and
31 | Related Rights”). Copyright and Related Rights include, but are not limited
32 | to, the following:
33 | 1. the right to reproduce, adapt, distribute, perform, display, communicate,
34 | and translate a Work;
35 | 2. moral rights retained by the original author(s) and/or performer(s);
36 | 3. publicity and privacy rights pertaining to a person’s image or likeness
37 | depicted in a Work;
38 | 4. rights protecting against unfair competition in regards to a Work,
39 | subject to the limitations in paragraph 4(i), below;
40 | 5. rights protecting the extraction, dissemination, use and reuse of data in
41 | a Work;
42 | 6. database rights (such as those arising under Directive 96/9/EC of the
43 | European Parliament and of the Council of 11 March 1996 on the legal
44 | protection of databases, and under any national implementation thereof,
45 | including any amended or successor version of such directive); and
46 | 7. other similar, equivalent or corresponding rights throughout the world
47 | based on applicable law or treaty, and any national implementations
48 | thereof.
49 |
50 | 2. Waiver. To the greatest extent permitted by, but not in contravention of,
51 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
52 | unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright
53 | and Related Rights and associated claims and causes of action, whether now
54 | known or unknown (including existing as well as future claims and causes of
55 | action), in the Work (i) in all territories worldwide, (ii) for the maximum
56 | duration provided by applicable law or treaty (including future time
57 | extensions), (iii) in any current or future medium and for any number of
58 | copies, and (iv) for any purpose whatsoever, including without limitation
59 | commercial, advertising or promotional purposes (the “Waiver”). Affirmer
60 | makes the Waiver for the benefit of each member of the public at large and
61 | to the detriment of Affirmer’s heirs and successors, fully intending that
62 | such Waiver shall not be subject to revocation, rescission, cancellation,
63 | termination, or any other legal or equitable action to disrupt the quiet
64 | enjoyment of the Work by the public as contemplated by Affirmer’s express
65 | Statement of Purpose.
66 |
67 | 3. Public License Fallback. Should any part of the Waiver for any reason be
68 | judged legally invalid or ineffective under applicable law, then the Waiver
69 | shall be preserved to the maximum extent permitted taking into account
70 | Affirmer’s express Statement of Purpose. In addition, to the extent the
71 | Waiver is so judged Affirmer hereby grants to each affected person a
72 | royalty-free, non transferable, non sublicensable, non exclusive,
73 | irrevocable and unconditional license to exercise Affirmer’s Copyright and
74 | Related Rights in the Work (i) in all territories worldwide, (ii) for the
75 | maximum duration provided by applicable law or treaty (including future time
76 | extensions), (iii) in any current or future medium and for any number of
77 | copies, and (iv) for any purpose whatsoever, including without limitation
78 | commercial, advertising or promotional purposes (the “License”). The License
79 | shall be deemed effective as of the date CC0 was applied by Affirmer to the
80 | Work. Should any part of the License for any reason be judged legally
81 | invalid or ineffective under applicable law, such partial invalidity or
82 | ineffectiveness shall not invalidate the remainder of the License, and in
83 | such case Affirmer hereby affirms that he or she will not (i) exercise any
84 | of his or her remaining Copyright and Related Rights in the Work or (ii)
85 | assert any associated claims and causes of action with respect to the Work,
86 | in either case contrary to Affirmer’s express Statement of Purpose.
87 |
88 | 4. Limitations and Disclaimers.
89 | 1. No trademark or patent rights held by Affirmer are waived, abandoned,
90 | surrendered, licensed or otherwise affected by this document.
91 | 2. Affirmer offers the Work as-is and makes no representations or warranties
92 | of any kind concerning the Work, express, implied, statutory or
93 | otherwise, including without limitation warranties of title,
94 | merchantability, fitness for a particular purpose, non infringement, or
95 | the absence of latent or other defects, accuracy, or the present or
96 | absence of errors, whether or not discoverable, all to the greatest
97 | extent permissible under applicable law.
98 | 3. Affirmer disclaims responsibility for clearing rights of other persons
99 | that may apply to the Work or any use thereof, including without
100 | limitation any person’s Copyright and Related Rights in the Work.
101 | Further, Affirmer disclaims responsibility for obtaining any necessary
102 | consents, permissions or other rights required for any use of the Work.
103 | 4. Affirmer understands and acknowledges that Creative Commons is not a
104 | party to this document and has no duty or obligation with respect to this
105 | CC0 or use of the Work.
106 |
107 | For more information, please see
108 | http://creativecommons.org/publicdomain/zero/1.0/.
109 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CSS Typed Object Model [
][CSS Typed Object Model]
2 |
3 | [![NPM Version][npm-img]][npm-url]
4 | [![Build Status][cli-img]][cli-url]
5 |
6 | [CSS Typed Object Model] is a work-in-progress polyfill for using
7 | [CSS Typed OM Level 1] in the browser.
8 |
9 | **Pull Requests are welcome. Please don’t use this in production until there is
10 | a v1.0.0.**
11 |
12 | ```bash
13 | npm install css-typed-om
14 | ```
15 |
16 | Polyfill the `window` object:
17 |
18 | ```js
19 | import polyfill from 'css-typed-om';
20 |
21 | polyfill(window);
22 | ```
23 |
24 | Use [CSS Typed Object Model] features:
25 |
26 | ```js
27 | // Element styles
28 | document.body.attributeStyleMap.set('padding-top', CSS.px(42));
29 | document.body.attributeStyleMap.get('padding-top') /* CSSUnitValue {
30 | value: 42,
31 | unit: 'px'
32 | }.toString() => 42px */
33 |
34 | document.body.attributeStyleMap.set('opacity', 0.3);
35 | typeof document.body.attributeStyleMap.get('opacity').value // number
36 | document.body.attributeStyleMap.get('opacity').unit // "number"
37 |
38 | // Stylesheet rules
39 | document.styleSheets[0].cssRules[0].styleMap.set('padding-top', '100px');
40 | document.styleSheets[0].cssRules[0].styleMap.get('padding-top'); /* CSSUnitValue {
41 | value: 100,
42 | unit: 'px'
43 | }.toString() => 100px */
44 |
45 | // Math products
46 | CSS.px(15).add(CSS.rem(10), CSS.em(5)) /* CSSMathSum {
47 | operator: "sum",
48 | values: [
49 | CSSUnitValue { value: 15, unit: 'px' },
50 | CSSUnitValue { value: 10, unit: 'rem' },
51 | CSSUnitValu { value: 5, unit: 'em' }
52 | ]
53 | }.toString() => calc(15px + 10rem + 5em) */
54 |
55 | CSS.px(15).mul(CSS.rem(10), CSS.em(5)) /* CSSMathProduct {
56 | operator: "product",
57 | values: [
58 | CSSUnitValue { value: 15, unit: 'px' },
59 | CSSUnitValue { value: 10, unit: 'rem' },
60 | CSSUnitValu { value: 5, unit: 'em' }
61 | ]
62 | }.toString() => calc(15px * 10rem * 5em) */
63 |
64 | CSS.px(15).sub(CSS.rem(10), CSS.em(5)) /* CSSMathSum {
65 | operator: "sum",
66 | values: [
67 | CSSUnitValue { value: 15, unit: 'px' },
68 | CSSUnitValue { value: -10, unit: 'rem' },
69 | CSSUnitValu { value: -5, unit: 'em' }
70 | ]
71 | }.toString() => calc(15px + -10rem + -5em) */
72 |
73 | CSS.px(15).div(CSS.rem(10), CSS.em(5)) /* CSSMathProduct {
74 | operator: "product",
75 | values: [
76 | CSSUnitValue { value: 15, unit: 'px' },
77 | CSSMathInvert {
78 | operator: 'invert',
79 | value: CSSUnitValue { value: 10, unit: 'rem' }
80 | },
81 | CSSMathInvert {
82 | operator: 'invert',
83 | value: CSSUnitValue { value: 5, unit: 'em' }
84 | }
85 | ]
86 | }.toString() => calc(15px / 10rem / 5em) */
87 |
88 | CSS.px(15).max(CSS.rem(10), CSS.em(5)) /* CSSMathMax {
89 | operator: 'max',
90 | values: [
91 | CSSUnitValue { value: 15, unit: 'px' },
92 | CSSUnitValue { value: 10, unit: 'rem' },
93 | CSSUnitValu { value: 5, unit: 'em' }
94 | ],
95 | }.toString() => max(15px, 10rem, 5em) */
96 |
97 | CSS.px(15).min(CSS.rem(10), CSS.em(5)) /* CSSMathMin {
98 | operator: 'min',
99 | values: [
100 | CSSUnitValue { value: 15, unit: 'px' },
101 | CSSUnitValue { value: 10, unit: 'rem' },
102 | CSSUnitValu { value: 5, unit: 'em' }
103 | ],
104 | }.toString() => min(15px, 10rem, 5em) */
105 | ```
106 |
107 | ## Features
108 |
109 | ### polyfill
110 |
111 | The `polyfill` function adds the following functions to `window` if they do not
112 | already exist:
113 |
114 | - `CSS`
115 | - `CSSKeywordValue`
116 | - `CSSMathInvert`
117 | - `CSSMathMax`
118 | - `CSSMathMin`
119 | - `CSSMathProduct`
120 | - `CSSMathSum`
121 | - `CSSStyleValue`
122 | - `CSSUnitValue`
123 | - `StylePropertyMap`
124 |
125 | It then adds the following functions to `CSS` if they do not already exist:
126 |
127 | - `number`
128 | - `percent`
129 | - `em`
130 | - `ex`
131 | - `ch`
132 | - `rem`
133 | - `vw`
134 | - `vh`
135 | - `vmin`
136 | - `vmax`
137 | - `cm`
138 | - `mm`
139 | - `in`
140 | - `pt`
141 | - `pc`
142 | - `px`
143 | - `Q`
144 | - `deg`
145 | - `grad`
146 | - `rad`
147 | - `turn`
148 | - `s`
149 | - `ms`
150 | - `Hz`
151 | - `kHz`
152 | - `dpi`
153 | - `dpcm`
154 | - `dppx`
155 | - `fr`
156 |
157 | The new `CSSUnitValue` instances returned by these methods extend
158 | `CSSNumericValue`, which allow them to use the following methods:
159 |
160 | - `add`
161 | - `div`
162 | - `max`
163 | - `min`
164 | - `mul`
165 | - `sub`
166 |
167 | The result of these transforms may be a new `CSSUnitValue` instance or a new
168 | `CSSMathProduct`, `CSSMathMax`, `CSSMathMin`, or `CSSMathSum` instance.
169 |
170 | They all stringify back into compliant CSS.
171 |
172 | [npm-url]: https://www.npmjs.com/package/css-typed-om
173 | [npm-img]: https://img.shields.io/npm/v/css-typed-om.svg
174 | [cli-url]: https://travis-ci.org/csstools/css-typed-om
175 | [cli-img]: https://img.shields.io/travis/csstools/css-typed-om.svg
176 |
177 | [CSS Typed Object Model]: https://github.com/csstools/css-typed-om
178 | [CSS Typed OM Level 1]: https://drafts.css-houdini.org/css-typed-om-1/
179 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import polyfill from './lib/polyfill';
2 | import CSSKeywordValue from './lib/CSSKeywordValue';
3 | import CSSStyleValue from './lib/CSSStyleValue';
4 | import CSSUnitValue from './lib/CSSUnitValue';
5 | import StylePropertyMap from './lib/StylePropertyMap';
6 |
7 | export default polyfill
8 |
9 | export {
10 | CSSKeywordValue,
11 | CSSStyleValue,
12 | CSSUnitValue,
13 | StylePropertyMap
14 | };
15 |
--------------------------------------------------------------------------------
/lib/CSSKeywordValue.js:
--------------------------------------------------------------------------------
1 | const _value = new WeakMap();
2 |
3 | export default class CSSKeywordValue {
4 | get value() {
5 | return _value.get(this);
6 | }
7 |
8 | set value(newValue) {
9 | _value.set(this, String(newValue));
10 | }
11 |
12 | toString() {
13 | return `${this.value}`;
14 | }
15 |
16 | constructor(...args) {
17 | if (args.length < 1) {
18 | throw new TypeError(`Failed to construct 'CSSKeywordValue': 1 arguments required, but only ${args.length} present.`);
19 | }
20 |
21 | _value.set(this, String(args[0]));
22 | }
23 | }
24 |
25 | Object.defineProperties(CSSKeywordValue.prototype, {
26 | value: { enumerable: true }
27 | });
28 |
--------------------------------------------------------------------------------
/lib/CSSMathInvert.js:
--------------------------------------------------------------------------------
1 | const _value = new WeakMap();
2 |
3 | export default class CSSMathInvert {
4 | get operator() {
5 | return 'invert';
6 | }
7 |
8 | get value() {
9 | return _value.get(this);
10 | }
11 |
12 | toString() {
13 | return `calc(1 / ${_value.get(this)})`
14 | }
15 |
16 | constructor(value) {
17 | _value.set(this, value)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/CSSMathMax.js:
--------------------------------------------------------------------------------
1 | const _values = new WeakMap();
2 |
3 | export default class CSSMathMax {
4 | get operator() {
5 | return 'max';
6 | }
7 |
8 | get values() {
9 | return _values.get(this);
10 | }
11 |
12 | toString() {
13 | return `max(${_values.get(this).join(', ')})`
14 | }
15 |
16 | constructor(...values) {
17 | _values.set(this, values);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/CSSMathMin.js:
--------------------------------------------------------------------------------
1 | const _values = new WeakMap();
2 |
3 | export default class CSSMathMin {
4 | get operator() {
5 | return 'min';
6 | }
7 |
8 | get values() {
9 | return _values.get(this);
10 | }
11 |
12 | toString() {
13 | return `min(${_values.get(this).join(', ')})`
14 | }
15 |
16 | constructor(...values) {
17 | _values.set(this, values);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/CSSMathProduct.js:
--------------------------------------------------------------------------------
1 | import CSSMathInvert from './CSSMathInvert'
2 |
3 | const _values = new WeakMap();
4 |
5 | export default class CSSMathProduct {
6 | get operator() {
7 | return 'product';
8 | }
9 |
10 | get values() {
11 | return _values.get(this);
12 | }
13 |
14 | toString() {
15 | return `calc(${_values.get(this).reduce(
16 | (contents, value) => `${value instanceof CSSMathInvert
17 | ? `${contents
18 | ? `${contents} / `
19 | : '1 / '
20 | }${value.value}`
21 | : `${contents
22 | ? `${contents} * `
23 | : ''}${value}`
24 | }`,
25 | ''
26 | )})`
27 | }
28 |
29 | constructor(...values) {
30 | _values.set(this, values);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/CSSMathSum.js:
--------------------------------------------------------------------------------
1 | const _values = new WeakMap();
2 |
3 | export default class CSSMathSum {
4 | get operator() {
5 | return 'product';
6 | }
7 |
8 | get values() {
9 | return _values.get(this);
10 | }
11 |
12 | toString() {
13 | return `calc(${_values.get(this).reduce(
14 | (contents, value) => `${contents ? `${contents} + ` : ''}${value}`,
15 | ''
16 | )})`
17 | }
18 |
19 | constructor(...values) {
20 | _values.set(this, values);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/CSSNumericValue.js:
--------------------------------------------------------------------------------
1 | import CSSMathMax from './CSSMathMax'
2 | import CSSMathMin from './CSSMathMin'
3 | import CSSUnitValue from './CSSUnitValue';
4 | import CSSMathProduct from './CSSMathProduct'
5 | import CSSMathInvert from './CSSMathInvert'
6 | import CSSMathSum from './CSSMathSum'
7 |
8 | export default class CSSNumericValue {
9 | add(...args) {
10 | const Constructor = this.constructor
11 | const result = new Constructor(this.value, this.unit)
12 | const values = []
13 |
14 | for (let arg of args) {
15 | if (arg instanceof Constructor) {
16 | if (values.length || result.unit !== arg.unit) {
17 | values.push(arg)
18 | } else {
19 | result.value += arg.value
20 | }
21 | } else if (
22 | arg instanceof CSSMathProduct ||
23 | arg instanceof CSSMathMax ||
24 | arg instanceof CSSMathMin ||
25 | arg instanceof CSSMathInvert
26 | ) {
27 | values.push(arg)
28 | } else {
29 | return null
30 | }
31 | }
32 |
33 | return values.length ? new CSSMathSum(result, ...values) : result
34 | }
35 |
36 | div(...args) {
37 | const Constructor = this.constructor
38 | const result = new Constructor(this.value, this.unit)
39 | const values = []
40 |
41 | for (let arg of args) {
42 | if (typeof arg === 'number') {
43 | arg = new CSSUnitValue(arg, 'number')
44 | }
45 |
46 | if (arg instanceof Constructor) {
47 | if (values.length || result.unit !== arg.unit && arg.unit !== 'number') {
48 | values.push(arg)
49 | } else {
50 | result.value /= arg.value
51 | }
52 | } else {
53 | return null
54 | }
55 | }
56 |
57 | return values.length ? new CSSMathProduct(result, ...values.map(
58 | value => new CSSMathInvert(value)
59 | )) : result
60 | }
61 |
62 | max(...args) {
63 | const result = new CSSUnitValue(this.value, this.unit)
64 | const values = [result]
65 |
66 | for (let arg of args) {
67 | if (arg instanceof CSSUnitValue) {
68 | if (values.length > 1 || result.unit !== arg.unit) {
69 | values.push(arg)
70 | } else {
71 | result.value = Math.max(result.value, arg.value)
72 | }
73 | } else {
74 | return null
75 | }
76 | }
77 |
78 | return values.length > 1 ? new CSSMathMax(...values) : result
79 | }
80 |
81 | min(...args) {
82 | const result = new CSSUnitValue(this.value, this.unit)
83 | const values = [result]
84 |
85 | for (let arg of args) {
86 | if (arg instanceof CSSUnitValue) {
87 | if (values.length > 1 || result.unit !== arg.unit) {
88 | values.push(arg)
89 | } else {
90 | result.value = Math.min(result.value, arg.value)
91 | }
92 | } else {
93 | return null
94 | }
95 | }
96 |
97 | return values.length > 1 ? new CSSMathMin(...values) : result
98 | }
99 |
100 | mul(...args) {
101 | const Constructor = this.constructor
102 | const result = new Constructor(this.value, this.unit)
103 | const values = []
104 |
105 | for (let arg of args) {
106 | if (typeof arg === 'number') {
107 | arg = new CSSUnitValue(arg, 'number')
108 | }
109 |
110 | if (arg instanceof Constructor) {
111 | if (values.length || result.unit !== arg.unit && arg.unit !== 'number') {
112 | values.push(arg)
113 | } else {
114 | result.value *= arg.value
115 | }
116 | } else {
117 | return null
118 | }
119 | }
120 |
121 | return values.length ? new CSSMathProduct(result, ...values) : result
122 | }
123 |
124 | sub(...args) {
125 | const Constructor = this.constructor
126 | const result = new Constructor(this.value, this.unit)
127 | const values = []
128 |
129 | for (let arg of args) {
130 | if (arg instanceof Constructor) {
131 | if (values.length || result.unit !== arg.unit) {
132 | values.push(new Constructor(arg.value * -1, arg.unit))
133 | } else {
134 | result.value -= arg.value
135 | }
136 | } else {
137 | return null
138 | }
139 | }
140 |
141 | return values.length ? new CSSMathSum(result, ...values) : result
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/lib/CSSStyleValue.js:
--------------------------------------------------------------------------------
1 | export default class CSSStyleValue {
2 | constructor() {
3 | throw new TypeError('Illegal constructor');
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/lib/CSSUnitValue.js:
--------------------------------------------------------------------------------
1 | import units from './units';
2 | import CSSNumericValue from './CSSNumericValue'
3 |
4 | const _value = new WeakMap();
5 | const _unit = new WeakMap();
6 |
7 | export default class CSSUnitValue extends CSSNumericValue {
8 | get value() {
9 | return _value.get(this);
10 | }
11 |
12 | set value(newValue) {
13 | _value.set(this, getFiniteNumber(newValue));
14 | }
15 |
16 | get unit() {
17 | return _unit.get(this);
18 | }
19 |
20 | toString() {
21 | return `${this.value}${units[this.unit]}`;
22 | }
23 |
24 | constructor(...args) {
25 | super()
26 |
27 | if (args.length < 2) {
28 | throw new TypeError(`Failed to construct 'CSSUnitValue': 2 arguments required, but only ${args.length} present.`);
29 | }
30 |
31 | _value.set(this, getFiniteNumber(args[0]));
32 | _unit.set(this, getUnit(args[1]));
33 | }
34 | }
35 |
36 | Object.defineProperties(CSSUnitValue.prototype, {
37 | value: { enumerable: true },
38 | unit: { enumerable: true }
39 | });
40 |
41 | function getFiniteNumber(value) {
42 | if (isNaN(value) || Math.abs(value) === Infinity) {
43 | throw new TypeError(`Failed to set the 'value' property on 'CSSUnitValue': The provided double value is non-finite.`);
44 | }
45 |
46 | return Number(value);
47 | }
48 |
49 | function getUnit(unit) {
50 | if (!Object.keys(units).includes(unit)) {
51 | throw new TypeError(`Failed to construct 'CSSUnitValue': Invalid unit: ${unit}`);
52 | }
53 |
54 | return unit
55 | }
56 |
--------------------------------------------------------------------------------
/lib/StylePropertyMap.js:
--------------------------------------------------------------------------------
1 | import parseAsValue from './parse-as-value';
2 |
3 | export default class StylePropertyMap {
4 | get(...args) {
5 | if (args.length < 1) {
6 | throw new TypeError(`Failed to execute 'get' on 'StylePropertyMapReadOnly': 1 argument required, but only ${args.length} present.`);
7 | }
8 |
9 | const [ property ] = args;
10 | const value = this.style[property];
11 |
12 | if (value) {
13 | return parseAsValue(value);
14 | }
15 |
16 | return null;
17 | }
18 |
19 | set(...args) {
20 | if (args.length < 2) {
21 | throw new TypeError(`Failed to execute 'set' on 'StylePropertyMap': 2 arguments required, but only ${args.length} present.`);
22 | }
23 |
24 | const [ property, value ] = args;
25 |
26 | this.style[property] = String(value);
27 | }
28 |
29 | constructor() {
30 | throw new TypeError('Illegal constructor');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/parse-as-value.js:
--------------------------------------------------------------------------------
1 | import units from './units';
2 | import CSSUnitValue from './CSSUnitValue';
3 | import CSSKeywordValue from './CSSKeywordValue';
4 |
5 | export default string => {
6 | const unitParsingMatch = String(string).match(unitParsingMatcher);
7 |
8 | if (unitParsingMatch) {
9 | const [, value, unit] = unitParsingMatch;
10 |
11 | return new CSSUnitValue(value, unitKeys[unitValues.indexOf(unit || '')]);
12 | }
13 |
14 | return new CSSKeywordValue(string);
15 | }
16 |
17 | const unitKeys = Object.keys(units);
18 | const unitValues = Object.values(units);
19 | const unitParsingMatcher = new RegExp(`^([-+]?[0-9]*\.?[0-9]+)(${unitValues.join('|')})?$`);
20 |
--------------------------------------------------------------------------------
/lib/polyfill.js:
--------------------------------------------------------------------------------
1 | import CSSKeywordValue from './CSSKeywordValue';
2 | import CSSMathInvert from './CSSMathInvert';
3 | import CSSMathMax from './CSSMathMax';
4 | import CSSMathMin from './CSSMathMin';
5 | import CSSMathProduct from './CSSMathProduct';
6 | import CSSMathSum from './CSSMathSum';
7 | import CSSStyleValue from './CSSStyleValue';
8 | import CSSUnitValue from './CSSUnitValue';
9 | import StylePropertyMap from './StylePropertyMap';
10 | import units from './units';
11 |
12 | export default function polyfill(window) {
13 | if (!window.CSS) window.CSS = class CSS {}
14 |
15 | Object.keys(units).forEach(
16 | unit => {
17 | if (!(unit in window.CSS)) {
18 | window.CSS[unit] = value => new CSSUnitValue(value, unit);
19 | }
20 | }
21 | );
22 |
23 | defineProperty(
24 | window.CSSRule.prototype,
25 | 'styleMap',
26 | context => context.style
27 | );
28 |
29 | defineProperty(
30 | window.Element.prototype,
31 | 'attributeStyleMap',
32 | context => context.style
33 | );
34 |
35 | defineProperty(
36 | window.Element.prototype,
37 | 'computedStyleMap',
38 | context => getComputedStyle(context)
39 | );
40 |
41 | if (!window.CSSKeywordValue) window.CSSKeywordValue = CSSKeywordValue;
42 | if (!window.CSSMathInvert) window.CSSMathInvert = CSSMathInvert;
43 | if (!window.CSSMathMax) window.CSSMathMax = CSSMathMax;
44 | if (!window.CSSMathMin) window.CSSMathMin = CSSMathMin;
45 | if (!window.CSSMathProduct) window.CSSMathProduct = CSSMathProduct;
46 | if (!window.CSSMathSum) window.CSSMathSum = CSSMathSum;
47 | if (!window.CSSStyleValue) window.CSSStyleValue = CSSStyleValue;
48 | if (!window.CSSUnitValue) window.CSSUnitValue = CSSUnitValue;
49 | if (!window.StylePropertyMap) window.StylePropertyMap = StylePropertyMap;
50 |
51 | function defineProperty(prototype, property, getStyle) {
52 | if (!(property in prototype)) {
53 | Object.defineProperty(prototype, property, {
54 | configurable: true,
55 | enumerable: true,
56 | get() {
57 | const computedStyleMap = Object.create(StylePropertyMap.prototype);
58 |
59 | computedStyleMap.style = getStyle(this);
60 |
61 | return computedStyleMap;
62 | }
63 | });
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/lib/units.js:
--------------------------------------------------------------------------------
1 | export default {
2 | number: '',
3 | percent: '%',
4 | em: 'em',
5 | ex: 'ex',
6 | ch: 'ch',
7 | rem: 'rem',
8 | vw: 'vw',
9 | vh: 'vh',
10 | vmin: 'vmin',
11 | vmax: 'vmax',
12 | cm: 'cm',
13 | mm: 'mm',
14 | in: 'in',
15 | pt: 'pt',
16 | pc: 'pc',
17 | px: 'px',
18 | Q: 'Q',
19 | deg: 'deg',
20 | grad: 'grad',
21 | rad: 'rad',
22 | turn: 'turn',
23 | s: 's',
24 | ms: 'ms',
25 | Hz: 'Hz',
26 | kHz: 'kHz',
27 | dpi: 'dpi',
28 | dpcm: 'dpcm',
29 | dppx: 'dppx',
30 | fr: 'fr'
31 | };
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "css-typed-om",
3 | "version": "0.4.0",
4 | "description": "Use CSS Typed Object Model in the browser",
5 | "author": "Jonathan Neal ",
6 | "license": "CC0-1.0",
7 | "repository": "csstools/css-typed-om",
8 | "homepage": "https://github.com/csstools/css-typed-om#readme",
9 | "bugs": "https://github.com/csstools/css-typed-om/issues",
10 | "main": "index.umd.js",
11 | "module": "index.es.js",
12 | "files": [
13 | "index.umd.js",
14 | "index.es.js"
15 | ],
16 | "scripts": {
17 | "prepublishOnly": "npm test",
18 | "pretest": "rollup -c .rollup.js --silent",
19 | "test": "echo 'Running tests...'; npm run test:js && npm run test:tape",
20 | "test:js": "eslint *.js lib/*.js --cache --ignore-path .gitignore --quiet",
21 | "test:tape": "node test"
22 | },
23 | "engines": {
24 | "node": ">=4.0.0"
25 | },
26 | "devDependencies": {
27 | "babel-core": "^6.26.0",
28 | "babel-eslint": "^8.2.3",
29 | "babel-plugin-array-includes": "^2.0.3",
30 | "babel-preset-env": "^1.6.1",
31 | "eslint": "^4.19.1",
32 | "eslint-config-dev": "^2.0.0",
33 | "pre-commit": "^1.2.2",
34 | "rollup": "^0.58.0",
35 | "rollup-plugin-babel": "^3.0.3"
36 | },
37 | "eslintConfig": {
38 | "extends": "dev",
39 | "parser": "babel-eslint"
40 | },
41 | "keywords": [
42 | "css",
43 | "number",
44 | "percent",
45 | "em",
46 | "ex",
47 | "ch",
48 | "rem",
49 | "vw",
50 | "vh",
51 | "vmin",
52 | "vmax",
53 | "cm",
54 | "mm",
55 | "in",
56 | "pt",
57 | "pc",
58 | "px",
59 | "Q",
60 | "deg",
61 | "grad",
62 | "rad",
63 | "turn",
64 | "s",
65 | "ms",
66 | "Hz",
67 | "kHz",
68 | "dpi",
69 | "dpcm",
70 | "dppx",
71 | "fr",
72 | "StylePropertyMap",
73 | "CSSUnitValue",
74 | "CSSStyleValue",
75 | "CSSKeywordValue",
76 | "styleMap",
77 | "attributeStyleMap",
78 | "computedStyleMap"
79 | ]
80 | }
81 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | const polyfill = require('.').default
2 | const CSS = {}
3 | const window = { CSS, CSSRule: { prototype: {} }, Element: { prototype: {} } };
4 |
5 | polyfill(window)
6 |
7 | const units = {
8 | ch: 'ch',
9 | rem: 'rem',
10 | vw: 'vw',
11 | vh: 'vh',
12 | vmin: 'vmin',
13 | vmax: 'vmax',
14 | cm: 'cm',
15 | mm: 'mm',
16 | in: 'in',
17 | pt: 'pt',
18 | pc: 'pc',
19 | px: 'px',
20 | Q: 'Q',
21 | deg: 'deg',
22 | grad: 'grad',
23 | rad: 'rad',
24 | turn: 'turn',
25 | s: 's',
26 | ms: 'ms',
27 | Hz: 'Hz',
28 | kHz: 'kHz',
29 | dpi: 'dpi',
30 | dpcm: 'dpcm',
31 | dppx: 'dppx',
32 | fr: 'fr'
33 | }
34 |
35 | const code = Object.keys(units).every(unit => [
36 | compare(
37 | CSS[unit](10),
38 | `10${unit}`
39 | ),
40 | compare(
41 | CSS[unit](10).add(CSS[unit](5), CSS[unit](5)),
42 | `20${unit}`
43 | ),
44 | compare(
45 | CSS[unit](10).sub(CSS[unit](2), CSS[unit](3)),
46 | `5${unit}`
47 | ),
48 | compare(
49 | CSS[unit](10).sub(CSS[unit](2), CSS[unit](3)),
50 | `5${unit}`
51 | ),
52 | compare(
53 | CSS[unit](10).mul(2, 3),
54 | `60${unit}`
55 | ),
56 | compare(
57 | CSS[unit](10).div(2, 5),
58 | `1${unit}`
59 | )
60 | ].every(result => Boolean(result))) && compare(
61 | CSS.px(15).add(CSS.rem(10), CSS.em(5)),
62 | 'calc(15px + 10rem + 5em)'
63 | ) ? 0 : 1;
64 |
65 | console.log(code ? 'Fail...' : 'Pass!')
66 |
67 | process.exit(code);
68 |
69 | function compare(a, b) {
70 | if (String(a) === String(b)) {
71 | return true;
72 | } else {
73 | return false;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------