Skip to content

Commit 24798a2

Browse files
authored
Merge pull request #42 from tbela99/color_v4
add light-dark() and system colors #41
2 parents 23e90df + c8f14fc commit 24798a2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+379
-129
lines changed

.github/workflows/coverage.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ jobs:
66
runs-on: ubuntu-latest
77
steps:
88
# Your original steps
9-
- uses: actions/checkout@v3
10-
- uses: actions/setup-node@v3
9+
- uses: actions/checkout@v4
10+
- uses: actions/setup-node@v4
1111
- name: Install
1212
run: npm install
1313
- name: Test and Coverage

.github/workflows/jsr.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# publish to jsr
2+
name: Publish
3+
4+
on:
5+
push:
6+
branches:
7+
- master
8+
9+
jobs:
10+
publish:
11+
runs-on: ubuntu-latest
12+
permissions:
13+
contents: read
14+
id-token: write # The OIDC ID token is used for authentication with JSR.
15+
steps:
16+
- uses: actions/checkout@v4
17+
- run: npx jsr publish

.github/workflows/node.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ jobs:
1717
strategy:
1818
fail-fast: false
1919
matrix:
20-
node-version: [ 18.x, 20.x, 22.x ]
20+
node-version: [ 18.x, 20.x ]
2121
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
2222

2323
steps:
24-
- uses: actions/checkout@v3
24+
- uses: actions/checkout@v4
2525
- name: Use Node.js ${{ matrix.node-version }}
26-
uses: actions/setup-node@v3
26+
uses: actions/setup-node@v4
2727
with:
2828
node-version: ${{ matrix.node-version }}
2929
- name: Install NPM dependencies

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
# v0.6.0
4+
- [x] light-dark() color
5+
- [x] system color
6+
37
## V0.5.4
48

59
- [x] incorrectly expand css nesting rules

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ $ npm install @tbela99/css-parser
1616
- fault-tolerant parser, will try to fix invalid tokens according to the CSS syntax module 3 recommendations.
1717
- fast and efficient minification without unsafe transforms, see [benchmark](https://tbela99.github.io/css-parser/benchmark/index.html)
1818
- minify colors.
19-
- support css color level 4 & 5: color(), lab(), lch(), oklab(), oklch(), color-mix() and relative color
19+
- support css color level 4 & 5: color(), lab(), lch(), oklab(), oklch(), color-mix(), light-dark(), system colors and relative color
2020
- generate nested css rules
2121
- convert nested css rules to legacy syntax
2222
- generate sourcemap
@@ -76,7 +76,7 @@ import {transform} from '@tbela99/css-parser/web';
7676

7777
Javascript module from cdn
7878

79-
```javascript
79+
```html
8080

8181
<script type="module">
8282

dist/index-umd-web.js

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@
184184
const D50 = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585];
185185
const k = Math.pow(29, 3) / Math.pow(3, 3);
186186
const e = Math.pow(6, 3) / Math.pow(29, 3);
187+
// color module v4
188+
const systemColors = new Set(['ActiveText', 'ButtonBorder', 'ButtonFace', 'ButtonText', 'Canvas', 'CanvasText', 'Field', 'FieldText', 'GrayText', 'Highlight', 'HighlightText', 'LinkText', 'Mark', 'MarkText', 'VisitedText'].map(m => m.toLowerCase()));
189+
// deprecated
190+
const deprecatedSystemColors = new Set(['ActiveBorder', 'ActiveCaption', 'AppWorkspace', 'Background', 'ButtonFace', 'ButtonHighlight', 'ButtonShadow', 'ButtonText', 'CaptionText', 'GrayText', 'Highlight', 'HighlightText', 'InactiveBorder', 'InactiveCaption', 'InactiveCaptionText', 'InfoBackground', 'InfoText', 'Menu', 'MenuText', 'Scrollbar', 'ThreeDDarkShadow', 'ThreeDFace', 'ThreeDHighlight', 'ThreeDLightShadow', 'ThreeDShadow', 'Window', 'WindowFrame', 'WindowText'].map(t => t.toLowerCase()));
187191
// name to color
188192
const COLORS_NAMES = Object.seal({
189193
'aliceblue': '#f0f8ff',
@@ -2958,7 +2962,7 @@
29582962
}
29592963
}
29602964

2961-
const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk', 'color-mix', 'color', 'oklab', 'lab', 'oklch', 'lch'];
2965+
const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk', 'color-mix', 'color', 'oklab', 'lab', 'oklch', 'lch', 'light-dark'];
29622966
function reduceNumber(val) {
29632967
val = String(+val);
29642968
if (val === '0') {
@@ -3226,6 +3230,9 @@
32263230
case exports.EnumToken.Div:
32273231
return '/';
32283232
case exports.EnumToken.ColorTokenType:
3233+
if (token.kin == 'light-dark') {
3234+
return token.val + '(' + token.chi.reduce((acc, curr) => acc + renderToken(curr, options, cache), '') + ')';
3235+
}
32293236
if (options.convertColor) {
32303237
if (token.cal == 'mix' && token.val == 'color-mix') {
32313238
const children = token.chi.reduce((acc, t) => {
@@ -3319,7 +3326,7 @@
33193326
return reduceHexValue(value);
33203327
}
33213328
}
3322-
if (token.kin == 'hex' || token.kin == 'lit') {
3329+
if (['hex', 'lit', 'sys', 'dpsys'].includes(token.kin)) {
33233330
return token.val;
33243331
}
33253332
if (Array.isArray(token.chi)) {
@@ -3585,6 +3592,15 @@
35853592
}
35863593
let isLegacySyntax = false;
35873594
if (token.typ == exports.EnumToken.FunctionTokenType && token.chi.length > 0 && colorsFunc.includes(token.val)) {
3595+
if (token.val == 'light-dark') {
3596+
const children = token.chi.filter((t) => [exports.EnumToken.IdenTokenType, exports.EnumToken.NumberTokenType, exports.EnumToken.LiteralTokenType, exports.EnumToken.ColorTokenType, exports.EnumToken.FunctionTokenType, exports.EnumToken.PercentageTokenType].includes(t.typ));
3597+
if (children.length != 2) {
3598+
return false;
3599+
}
3600+
if (isColor(children[0]) && isColor(children[1])) {
3601+
return true;
3602+
}
3603+
}
35883604
if (token.val == 'color') {
35893605
const children = token.chi.filter((t) => [exports.EnumToken.IdenTokenType, exports.EnumToken.NumberTokenType, exports.EnumToken.LiteralTokenType, exports.EnumToken.ColorTokenType, exports.EnumToken.FunctionTokenType, exports.EnumToken.PercentageTokenType].includes(t.typ));
35903606
const isRelative = children[0].typ == exports.EnumToken.IdenTokenType && children[0].val == 'from';
@@ -6574,9 +6590,9 @@
65746590
}));
65756591
}
65766592
function getTokenType(val, hint) {
6577-
if (val === '' && hint == null) {
6578-
throw new Error('empty string?');
6579-
}
6593+
// if (val === '' && hint == null) {
6594+
// throw new Error('empty string?');
6595+
// }
65806596
if (hint != null) {
65816597
return enumTokenHints.has(hint) ? { typ: hint } : { typ: hint, val };
65826598
}
@@ -6705,6 +6721,20 @@
67056721
};
67066722
}
67076723
if (isIdent(val)) {
6724+
if (systemColors.has(val.toLowerCase())) {
6725+
return {
6726+
typ: exports.EnumToken.ColorTokenType,
6727+
val,
6728+
kin: 'sys'
6729+
};
6730+
}
6731+
if (deprecatedSystemColors.has(val.toLowerCase())) {
6732+
return {
6733+
typ: exports.EnumToken.ColorTokenType,
6734+
val,
6735+
kin: 'dpsys'
6736+
};
6737+
}
67086738
return {
67096739
typ: val.startsWith('--') ? exports.EnumToken.DashedIdenTokenType : exports.EnumToken.IdenTokenType,
67106740
val
@@ -6986,7 +7016,11 @@
69867016
// t.chi = t.chi.filter((t: Token) => [EnumToken.IdenTokenType, EnumToken.NumberTokenType, EnumToken.PercentageTokenType].includes(t.typ));
69877017
}
69887018
}
6989-
t.chi = t.chi.filter((t) => ![exports.EnumToken.WhitespaceTokenType, exports.EnumToken.CommaTokenType, exports.EnumToken.CommentTokenType].includes(t.typ));
7019+
const filter = [exports.EnumToken.WhitespaceTokenType, exports.EnumToken.CommentTokenType];
7020+
if (t.val != 'light-dark') {
7021+
filter.push(exports.EnumToken.CommaTokenType);
7022+
}
7023+
t.chi = t.chi.filter((t) => !filter.includes(t.typ));
69907024
continue;
69917025
}
69927026
if (t.typ == exports.EnumToken.UrlFunctionTokenType) {
@@ -9491,6 +9525,7 @@
94919525
const path = resolve(url, currentFile).absolute;
94929526
t = new URL(path, self.origin);
94939527
}
9528+
// @ts-ignore
94949529
return fetch(t, t.origin != self.origin ? { mode: 'cors' } : {}).then(parseResponse);
94959530
}
94969531

dist/index.cjs

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
var process = require('node:process');
34
var promises = require('node:fs/promises');
45

56
exports.EnumToken = void 0;
@@ -182,6 +183,10 @@ const colorFuncColorSpace = ['srgb', 'srgb-linear', 'display-p3', 'prophoto-rgb'
182183
const D50 = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585];
183184
const k = Math.pow(29, 3) / Math.pow(3, 3);
184185
const e = Math.pow(6, 3) / Math.pow(29, 3);
186+
// color module v4
187+
const systemColors = new Set(['ActiveText', 'ButtonBorder', 'ButtonFace', 'ButtonText', 'Canvas', 'CanvasText', 'Field', 'FieldText', 'GrayText', 'Highlight', 'HighlightText', 'LinkText', 'Mark', 'MarkText', 'VisitedText'].map(m => m.toLowerCase()));
188+
// deprecated
189+
const deprecatedSystemColors = new Set(['ActiveBorder', 'ActiveCaption', 'AppWorkspace', 'Background', 'ButtonFace', 'ButtonHighlight', 'ButtonShadow', 'ButtonText', 'CaptionText', 'GrayText', 'Highlight', 'HighlightText', 'InactiveBorder', 'InactiveCaption', 'InactiveCaptionText', 'InfoBackground', 'InfoText', 'Menu', 'MenuText', 'Scrollbar', 'ThreeDDarkShadow', 'ThreeDFace', 'ThreeDHighlight', 'ThreeDLightShadow', 'ThreeDShadow', 'Window', 'WindowFrame', 'WindowText'].map(t => t.toLowerCase()));
185190
// name to color
186191
const COLORS_NAMES = Object.seal({
187192
'aliceblue': '#f0f8ff',
@@ -2956,7 +2961,7 @@ class SourceMap {
29562961
}
29572962
}
29582963

2959-
const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk', 'color-mix', 'color', 'oklab', 'lab', 'oklch', 'lch'];
2964+
const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk', 'color-mix', 'color', 'oklab', 'lab', 'oklch', 'lch', 'light-dark'];
29602965
function reduceNumber(val) {
29612966
val = String(+val);
29622967
if (val === '0') {
@@ -3224,6 +3229,9 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer,
32243229
case exports.EnumToken.Div:
32253230
return '/';
32263231
case exports.EnumToken.ColorTokenType:
3232+
if (token.kin == 'light-dark') {
3233+
return token.val + '(' + token.chi.reduce((acc, curr) => acc + renderToken(curr, options, cache), '') + ')';
3234+
}
32273235
if (options.convertColor) {
32283236
if (token.cal == 'mix' && token.val == 'color-mix') {
32293237
const children = token.chi.reduce((acc, t) => {
@@ -3317,7 +3325,7 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer,
33173325
return reduceHexValue(value);
33183326
}
33193327
}
3320-
if (token.kin == 'hex' || token.kin == 'lit') {
3328+
if (['hex', 'lit', 'sys', 'dpsys'].includes(token.kin)) {
33213329
return token.val;
33223330
}
33233331
if (Array.isArray(token.chi)) {
@@ -3583,6 +3591,15 @@ function isColor(token) {
35833591
}
35843592
let isLegacySyntax = false;
35853593
if (token.typ == exports.EnumToken.FunctionTokenType && token.chi.length > 0 && colorsFunc.includes(token.val)) {
3594+
if (token.val == 'light-dark') {
3595+
const children = token.chi.filter((t) => [exports.EnumToken.IdenTokenType, exports.EnumToken.NumberTokenType, exports.EnumToken.LiteralTokenType, exports.EnumToken.ColorTokenType, exports.EnumToken.FunctionTokenType, exports.EnumToken.PercentageTokenType].includes(t.typ));
3596+
if (children.length != 2) {
3597+
return false;
3598+
}
3599+
if (isColor(children[0]) && isColor(children[1])) {
3600+
return true;
3601+
}
3602+
}
35863603
if (token.val == 'color') {
35873604
const children = token.chi.filter((t) => [exports.EnumToken.IdenTokenType, exports.EnumToken.NumberTokenType, exports.EnumToken.LiteralTokenType, exports.EnumToken.ColorTokenType, exports.EnumToken.FunctionTokenType, exports.EnumToken.PercentageTokenType].includes(t.typ));
35883605
const isRelative = children[0].typ == exports.EnumToken.IdenTokenType && children[0].val == 'from';
@@ -6572,9 +6589,9 @@ function parseString(src, options = { location: false }) {
65726589
}));
65736590
}
65746591
function getTokenType(val, hint) {
6575-
if (val === '' && hint == null) {
6576-
throw new Error('empty string?');
6577-
}
6592+
// if (val === '' && hint == null) {
6593+
// throw new Error('empty string?');
6594+
// }
65786595
if (hint != null) {
65796596
return enumTokenHints.has(hint) ? { typ: hint } : { typ: hint, val };
65806597
}
@@ -6703,6 +6720,20 @@ function getTokenType(val, hint) {
67036720
};
67046721
}
67056722
if (isIdent(val)) {
6723+
if (systemColors.has(val.toLowerCase())) {
6724+
return {
6725+
typ: exports.EnumToken.ColorTokenType,
6726+
val,
6727+
kin: 'sys'
6728+
};
6729+
}
6730+
if (deprecatedSystemColors.has(val.toLowerCase())) {
6731+
return {
6732+
typ: exports.EnumToken.ColorTokenType,
6733+
val,
6734+
kin: 'dpsys'
6735+
};
6736+
}
67066737
return {
67076738
typ: val.startsWith('--') ? exports.EnumToken.DashedIdenTokenType : exports.EnumToken.IdenTokenType,
67086739
val
@@ -6984,7 +7015,11 @@ function parseTokens(tokens, options = {}) {
69847015
// t.chi = t.chi.filter((t: Token) => [EnumToken.IdenTokenType, EnumToken.NumberTokenType, EnumToken.PercentageTokenType].includes(t.typ));
69857016
}
69867017
}
6987-
t.chi = t.chi.filter((t) => ![exports.EnumToken.WhitespaceTokenType, exports.EnumToken.CommaTokenType, exports.EnumToken.CommentTokenType].includes(t.typ));
7018+
const filter = [exports.EnumToken.WhitespaceTokenType, exports.EnumToken.CommentTokenType];
7019+
if (t.val != 'light-dark') {
7020+
filter.push(exports.EnumToken.CommaTokenType);
7021+
}
7022+
t.chi = t.chi.filter((t) => !filter.includes(t.typ));
69887023
continue;
69897024
}
69907025
if (t.typ == exports.EnumToken.UrlFunctionTokenType) {

dist/index.d.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ export declare interface ImportantToken extends BaseToken {
459459
typ: EnumToken.ImportantTokenType;
460460
}
461461

462-
export declare type ColorKind = 'lit' | 'hex' | 'rgb' | 'rgba' | 'hsl' | 'hsla' | 'hwb' | 'device-cmyk' | 'oklab' | 'oklch' | 'lab' | 'lch' | 'color';
462+
export declare type ColorKind = 'sys' | 'dpsys' | 'lit' | 'hex' | 'rgb' | 'rgba' | 'hsl' | 'hsla' | 'hwb' | 'device-cmyk' | 'oklab' | 'oklch' | 'lab' | 'lch' | 'color' | 'light-dark';
463463

464464
// export declare type HueInterpolationMethod = 'shorter' | 'longer' | 'increasing' | 'decreasing';
465465

@@ -820,11 +820,11 @@ export declare interface MinifyFeature {
820820

821821
ordering: number;
822822

823-
register: (options: MinifyOptions | ParserOptions) => void;
824-
run: (ast: AstRule | AstAtRule, options: ParserOptions = {}, parent: AstRule | AstAtRule | AstRuleStyleSheet, context: {
825-
[key: string]: any
826-
}) => void;
827-
cleanup?: (ast: AstRuleStyleSheet, options: ParserOptions = {}, context: { [key: string]: any }) => void;
823+
register(options: MinifyOptions | ParserOptions): void;
824+
825+
// run(ast: AstRule | AstAtRule, options: ParserOptions = {}, parent: AstRule | AstAtRule | AstRuleStyleSheet, context: { [key: string]: any }): void;
826+
827+
// cleanup?(ast: AstRuleStyleSheet, options: ParserOptions = {}, context: { [key: string]: any }): void;
828828
}
829829

830830
export declare interface MinifyFeature {

dist/lib/parser/parse.js

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { walkValues, walk } from '../ast/walk.js';
55
import { expand } from '../ast/expand.js';
66
import { parseDeclaration } from './utils/declaration.js';
77
import { renderToken } from '../renderer/render.js';
8-
import { COLORS_NAMES } from '../renderer/color/utils/constants.js';
8+
import { COLORS_NAMES, systemColors, deprecatedSystemColors } from '../renderer/color/utils/constants.js';
99
import { tokenize } from './tokenize.js';
1010

1111
const urlTokenMatcher = /^(["']?)[a-zA-Z0-9_/.-][a-zA-Z0-9_/:.#?-]+(\1)$/;
@@ -539,9 +539,9 @@ function parseString(src, options = { location: false }) {
539539
}));
540540
}
541541
function getTokenType(val, hint) {
542-
if (val === '' && hint == null) {
543-
throw new Error('empty string?');
544-
}
542+
// if (val === '' && hint == null) {
543+
// throw new Error('empty string?');
544+
// }
545545
if (hint != null) {
546546
return enumTokenHints.has(hint) ? { typ: hint } : { typ: hint, val };
547547
}
@@ -670,6 +670,20 @@ function getTokenType(val, hint) {
670670
};
671671
}
672672
if (isIdent(val)) {
673+
if (systemColors.has(val.toLowerCase())) {
674+
return {
675+
typ: EnumToken.ColorTokenType,
676+
val,
677+
kin: 'sys'
678+
};
679+
}
680+
if (deprecatedSystemColors.has(val.toLowerCase())) {
681+
return {
682+
typ: EnumToken.ColorTokenType,
683+
val,
684+
kin: 'dpsys'
685+
};
686+
}
673687
return {
674688
typ: val.startsWith('--') ? EnumToken.DashedIdenTokenType : EnumToken.IdenTokenType,
675689
val
@@ -951,7 +965,11 @@ function parseTokens(tokens, options = {}) {
951965
// t.chi = t.chi.filter((t: Token) => [EnumToken.IdenTokenType, EnumToken.NumberTokenType, EnumToken.PercentageTokenType].includes(t.typ));
952966
}
953967
}
954-
t.chi = t.chi.filter((t) => ![EnumToken.WhitespaceTokenType, EnumToken.CommaTokenType, EnumToken.CommentTokenType].includes(t.typ));
968+
const filter = [EnumToken.WhitespaceTokenType, EnumToken.CommentTokenType];
969+
if (t.val != 'light-dark') {
970+
filter.push(EnumToken.CommaTokenType);
971+
}
972+
t.chi = t.chi.filter((t) => !filter.includes(t.typ));
955973
continue;
956974
}
957975
if (t.typ == EnumToken.UrlFunctionTokenType) {

dist/lib/parser/utils/syntax.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@ function isColor(token) {
6363
}
6464
let isLegacySyntax = false;
6565
if (token.typ == EnumToken.FunctionTokenType && token.chi.length > 0 && colorsFunc.includes(token.val)) {
66+
if (token.val == 'light-dark') {
67+
const children = token.chi.filter((t) => [EnumToken.IdenTokenType, EnumToken.NumberTokenType, EnumToken.LiteralTokenType, EnumToken.ColorTokenType, EnumToken.FunctionTokenType, EnumToken.PercentageTokenType].includes(t.typ));
68+
if (children.length != 2) {
69+
return false;
70+
}
71+
if (isColor(children[0]) && isColor(children[1])) {
72+
return true;
73+
}
74+
}
6675
if (token.val == 'color') {
6776
const children = token.chi.filter((t) => [EnumToken.IdenTokenType, EnumToken.NumberTokenType, EnumToken.LiteralTokenType, EnumToken.ColorTokenType, EnumToken.FunctionTokenType, EnumToken.PercentageTokenType].includes(t.typ));
6877
const isRelative = children[0].typ == EnumToken.IdenTokenType && children[0].val == 'from';

0 commit comments

Comments
 (0)