Skip to content

Commit

Permalink
add light-dark() and system colors #41
Browse files Browse the repository at this point in the history
  • Loading branch information
tbela99 committed Jul 20, 2024
1 parent 23e90df commit 5bf5a00
Show file tree
Hide file tree
Showing 56 changed files with 349 additions and 102 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/jsr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# publish to jsr
name: Publish

on:
push:
branches:
- master

jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # The OIDC ID token is used for authentication with JSR.
steps:
- uses: actions/checkout@v4
- run: npx jsr publish
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

# v0.6.0
- [x] light-dark() color
- [x] system color

## V0.5.4

- [x] incorrectly expand css nesting rules
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ $ npm install @tbela99/css-parser
- fault-tolerant parser, will try to fix invalid tokens according to the CSS syntax module 3 recommendations.
- fast and efficient minification without unsafe transforms, see [benchmark](https://tbela99.github.io/css-parser/benchmark/index.html)
- minify colors.
- support css color level 4 & 5: color(), lab(), lch(), oklab(), oklch(), color-mix() and relative color
- support css color level 4 & 5: color(), lab(), lch(), oklab(), oklch(), color-mix(), light-dark(), system colors and relative color
- generate nested css rules
- convert nested css rules to legacy syntax
- generate sourcemap
Expand Down
46 changes: 40 additions & 6 deletions dist/index-umd-web.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@
const D50 = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585];
const k = Math.pow(29, 3) / Math.pow(3, 3);
const e = Math.pow(6, 3) / Math.pow(29, 3);
// color module v4
const systemColors = new Set(['ActiveText', 'ButtonBorder', 'ButtonFace', 'ButtonText', 'Canvas', 'CanvasText', 'Field', 'FieldText', 'GrayText', 'Highlight', 'HighlightText', 'LinkText', 'Mark', 'MarkText', 'VisitedText'].map(m => m.toLowerCase()));
// deprecated
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()));
// name to color
const COLORS_NAMES = Object.seal({
'aliceblue': '#f0f8ff',
Expand Down Expand Up @@ -2958,7 +2962,7 @@
}
}

const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk', 'color-mix', 'color', 'oklab', 'lab', 'oklch', 'lch'];
const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk', 'color-mix', 'color', 'oklab', 'lab', 'oklch', 'lch', 'light-dark'];
function reduceNumber(val) {
val = String(+val);
if (val === '0') {
Expand Down Expand Up @@ -3226,6 +3230,9 @@
case exports.EnumToken.Div:
return '/';
case exports.EnumToken.ColorTokenType:
if (token.kin == 'light-dark') {
return token.val + '(' + token.chi.reduce((acc, curr) => acc + renderToken(curr, options, cache), '') + ')';
}
if (options.convertColor) {
if (token.cal == 'mix' && token.val == 'color-mix') {
const children = token.chi.reduce((acc, t) => {
Expand Down Expand Up @@ -3319,7 +3326,7 @@
return reduceHexValue(value);
}
}
if (token.kin == 'hex' || token.kin == 'lit') {
if (['hex', 'lit', 'sys', 'dpsys'].includes(token.kin)) {
return token.val;
}
if (Array.isArray(token.chi)) {
Expand Down Expand Up @@ -3585,6 +3592,15 @@
}
let isLegacySyntax = false;
if (token.typ == exports.EnumToken.FunctionTokenType && token.chi.length > 0 && colorsFunc.includes(token.val)) {
if (token.val == 'light-dark') {
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));
if (children.length != 2) {
return false;
}
if (isColor(children[0]) && isColor(children[1])) {
return true;
}
}
if (token.val == 'color') {
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));
const isRelative = children[0].typ == exports.EnumToken.IdenTokenType && children[0].val == 'from';
Expand Down Expand Up @@ -6574,9 +6590,9 @@
}));
}
function getTokenType(val, hint) {
if (val === '' && hint == null) {
throw new Error('empty string?');
}
// if (val === '' && hint == null) {
// throw new Error('empty string?');
// }
if (hint != null) {
return enumTokenHints.has(hint) ? { typ: hint } : { typ: hint, val };
}
Expand Down Expand Up @@ -6705,6 +6721,20 @@
};
}
if (isIdent(val)) {
if (systemColors.has(val.toLowerCase())) {
return {
typ: exports.EnumToken.ColorTokenType,
val,
kin: 'sys'
};
}
if (deprecatedSystemColors.has(val.toLowerCase())) {
return {
typ: exports.EnumToken.ColorTokenType,
val,
kin: 'dpsys'
};
}
return {
typ: val.startsWith('--') ? exports.EnumToken.DashedIdenTokenType : exports.EnumToken.IdenTokenType,
val
Expand Down Expand Up @@ -6986,7 +7016,11 @@
// t.chi = t.chi.filter((t: Token) => [EnumToken.IdenTokenType, EnumToken.NumberTokenType, EnumToken.PercentageTokenType].includes(t.typ));
}
}
t.chi = t.chi.filter((t) => ![exports.EnumToken.WhitespaceTokenType, exports.EnumToken.CommaTokenType, exports.EnumToken.CommentTokenType].includes(t.typ));
const filter = [exports.EnumToken.WhitespaceTokenType, exports.EnumToken.CommentTokenType];
if (t.val != 'light-dark') {
filter.push(exports.EnumToken.CommaTokenType);
}
t.chi = t.chi.filter((t) => !filter.includes(t.typ));
continue;
}
if (t.typ == exports.EnumToken.UrlFunctionTokenType) {
Expand Down
46 changes: 40 additions & 6 deletions dist/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ const colorFuncColorSpace = ['srgb', 'srgb-linear', 'display-p3', 'prophoto-rgb'
const D50 = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585];
const k = Math.pow(29, 3) / Math.pow(3, 3);
const e = Math.pow(6, 3) / Math.pow(29, 3);
// color module v4
const systemColors = new Set(['ActiveText', 'ButtonBorder', 'ButtonFace', 'ButtonText', 'Canvas', 'CanvasText', 'Field', 'FieldText', 'GrayText', 'Highlight', 'HighlightText', 'LinkText', 'Mark', 'MarkText', 'VisitedText'].map(m => m.toLowerCase()));
// deprecated
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()));
// name to color
const COLORS_NAMES = Object.seal({
'aliceblue': '#f0f8ff',
Expand Down Expand Up @@ -2956,7 +2960,7 @@ class SourceMap {
}
}

const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk', 'color-mix', 'color', 'oklab', 'lab', 'oklch', 'lch'];
const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk', 'color-mix', 'color', 'oklab', 'lab', 'oklch', 'lch', 'light-dark'];
function reduceNumber(val) {
val = String(+val);
if (val === '0') {
Expand Down Expand Up @@ -3224,6 +3228,9 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer,
case exports.EnumToken.Div:
return '/';
case exports.EnumToken.ColorTokenType:
if (token.kin == 'light-dark') {
return token.val + '(' + token.chi.reduce((acc, curr) => acc + renderToken(curr, options, cache), '') + ')';
}
if (options.convertColor) {
if (token.cal == 'mix' && token.val == 'color-mix') {
const children = token.chi.reduce((acc, t) => {
Expand Down Expand Up @@ -3317,7 +3324,7 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer,
return reduceHexValue(value);
}
}
if (token.kin == 'hex' || token.kin == 'lit') {
if (['hex', 'lit', 'sys', 'dpsys'].includes(token.kin)) {
return token.val;
}
if (Array.isArray(token.chi)) {
Expand Down Expand Up @@ -3583,6 +3590,15 @@ function isColor(token) {
}
let isLegacySyntax = false;
if (token.typ == exports.EnumToken.FunctionTokenType && token.chi.length > 0 && colorsFunc.includes(token.val)) {
if (token.val == 'light-dark') {
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));
if (children.length != 2) {
return false;
}
if (isColor(children[0]) && isColor(children[1])) {
return true;
}
}
if (token.val == 'color') {
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));
const isRelative = children[0].typ == exports.EnumToken.IdenTokenType && children[0].val == 'from';
Expand Down Expand Up @@ -6572,9 +6588,9 @@ function parseString(src, options = { location: false }) {
}));
}
function getTokenType(val, hint) {
if (val === '' && hint == null) {
throw new Error('empty string?');
}
// if (val === '' && hint == null) {
// throw new Error('empty string?');
// }
if (hint != null) {
return enumTokenHints.has(hint) ? { typ: hint } : { typ: hint, val };
}
Expand Down Expand Up @@ -6703,6 +6719,20 @@ function getTokenType(val, hint) {
};
}
if (isIdent(val)) {
if (systemColors.has(val.toLowerCase())) {
return {
typ: exports.EnumToken.ColorTokenType,
val,
kin: 'sys'
};
}
if (deprecatedSystemColors.has(val.toLowerCase())) {
return {
typ: exports.EnumToken.ColorTokenType,
val,
kin: 'dpsys'
};
}
return {
typ: val.startsWith('--') ? exports.EnumToken.DashedIdenTokenType : exports.EnumToken.IdenTokenType,
val
Expand Down Expand Up @@ -6984,7 +7014,11 @@ function parseTokens(tokens, options = {}) {
// t.chi = t.chi.filter((t: Token) => [EnumToken.IdenTokenType, EnumToken.NumberTokenType, EnumToken.PercentageTokenType].includes(t.typ));
}
}
t.chi = t.chi.filter((t) => ![exports.EnumToken.WhitespaceTokenType, exports.EnumToken.CommaTokenType, exports.EnumToken.CommentTokenType].includes(t.typ));
const filter = [exports.EnumToken.WhitespaceTokenType, exports.EnumToken.CommentTokenType];
if (t.val != 'light-dark') {
filter.push(exports.EnumToken.CommaTokenType);
}
t.chi = t.chi.filter((t) => !filter.includes(t.typ));
continue;
}
if (t.typ == exports.EnumToken.UrlFunctionTokenType) {
Expand Down
2 changes: 1 addition & 1 deletion dist/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ export declare interface ImportantToken extends BaseToken {
typ: EnumToken.ImportantTokenType;
}

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

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

Expand Down
28 changes: 23 additions & 5 deletions dist/lib/parser/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { walkValues, walk } from '../ast/walk.js';
import { expand } from '../ast/expand.js';
import { parseDeclaration } from './utils/declaration.js';
import { renderToken } from '../renderer/render.js';
import { COLORS_NAMES } from '../renderer/color/utils/constants.js';
import { COLORS_NAMES, systemColors, deprecatedSystemColors } from '../renderer/color/utils/constants.js';
import { tokenize } from './tokenize.js';

const urlTokenMatcher = /^(["']?)[a-zA-Z0-9_/.-][a-zA-Z0-9_/:.#?-]+(\1)$/;
Expand Down Expand Up @@ -539,9 +539,9 @@ function parseString(src, options = { location: false }) {
}));
}
function getTokenType(val, hint) {
if (val === '' && hint == null) {
throw new Error('empty string?');
}
// if (val === '' && hint == null) {
// throw new Error('empty string?');
// }
if (hint != null) {
return enumTokenHints.has(hint) ? { typ: hint } : { typ: hint, val };
}
Expand Down Expand Up @@ -670,6 +670,20 @@ function getTokenType(val, hint) {
};
}
if (isIdent(val)) {
if (systemColors.has(val.toLowerCase())) {
return {
typ: EnumToken.ColorTokenType,
val,
kin: 'sys'
};
}
if (deprecatedSystemColors.has(val.toLowerCase())) {
return {
typ: EnumToken.ColorTokenType,
val,
kin: 'dpsys'
};
}
return {
typ: val.startsWith('--') ? EnumToken.DashedIdenTokenType : EnumToken.IdenTokenType,
val
Expand Down Expand Up @@ -951,7 +965,11 @@ function parseTokens(tokens, options = {}) {
// t.chi = t.chi.filter((t: Token) => [EnumToken.IdenTokenType, EnumToken.NumberTokenType, EnumToken.PercentageTokenType].includes(t.typ));
}
}
t.chi = t.chi.filter((t) => ![EnumToken.WhitespaceTokenType, EnumToken.CommaTokenType, EnumToken.CommentTokenType].includes(t.typ));
const filter = [EnumToken.WhitespaceTokenType, EnumToken.CommentTokenType];
if (t.val != 'light-dark') {
filter.push(EnumToken.CommaTokenType);
}
t.chi = t.chi.filter((t) => !filter.includes(t.typ));
continue;
}
if (t.typ == EnumToken.UrlFunctionTokenType) {
Expand Down
9 changes: 9 additions & 0 deletions dist/lib/parser/utils/syntax.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ function isColor(token) {
}
let isLegacySyntax = false;
if (token.typ == EnumToken.FunctionTokenType && token.chi.length > 0 && colorsFunc.includes(token.val)) {
if (token.val == 'light-dark') {
const children = token.chi.filter((t) => [EnumToken.IdenTokenType, EnumToken.NumberTokenType, EnumToken.LiteralTokenType, EnumToken.ColorTokenType, EnumToken.FunctionTokenType, EnumToken.PercentageTokenType].includes(t.typ));
if (children.length != 2) {
return false;
}
if (isColor(children[0]) && isColor(children[1])) {
return true;
}
}
if (token.val == 'color') {
const children = token.chi.filter((t) => [EnumToken.IdenTokenType, EnumToken.NumberTokenType, EnumToken.LiteralTokenType, EnumToken.ColorTokenType, EnumToken.FunctionTokenType, EnumToken.PercentageTokenType].includes(t.typ));
const isRelative = children[0].typ == EnumToken.IdenTokenType && children[0].val == 'from';
Expand Down
6 changes: 5 additions & 1 deletion dist/lib/renderer/color/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ const colorFuncColorSpace = ['srgb', 'srgb-linear', 'display-p3', 'prophoto-rgb'
const D50 = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585];
const k = Math.pow(29, 3) / Math.pow(3, 3);
const e = Math.pow(6, 3) / Math.pow(29, 3);
// color module v4
const systemColors = new Set(['ActiveText', 'ButtonBorder', 'ButtonFace', 'ButtonText', 'Canvas', 'CanvasText', 'Field', 'FieldText', 'GrayText', 'Highlight', 'HighlightText', 'LinkText', 'Mark', 'MarkText', 'VisitedText'].map(m => m.toLowerCase()));
// deprecated
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()));
// name to color
const COLORS_NAMES = Object.seal({
'aliceblue': '#f0f8ff',
Expand Down Expand Up @@ -188,4 +192,4 @@ const NAMES_COLORS = Object.seal(Object.entries(COLORS_NAMES).reduce((acc, [key,
return acc;
}, Object.create(null)));

export { COLORS_NAMES, D50, NAMES_COLORS, colorFuncColorSpace, colorRange, e, k };
export { COLORS_NAMES, D50, NAMES_COLORS, colorFuncColorSpace, colorRange, deprecatedSystemColors, e, k, systemColors };
7 changes: 5 additions & 2 deletions dist/lib/renderer/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { SourceMap } from './sourcemap/sourcemap.js';
import '../parser/parse.js';
import { isColor, isNewLine } from '../parser/utils/syntax.js';

const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk', 'color-mix', 'color', 'oklab', 'lab', 'oklch', 'lch'];
const colorsFunc = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk', 'color-mix', 'color', 'oklab', 'lab', 'oklch', 'lch', 'light-dark'];
function reduceNumber(val) {
val = String(+val);
if (val === '0') {
Expand Down Expand Up @@ -279,6 +279,9 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer,
case EnumToken.Div:
return '/';
case EnumToken.ColorTokenType:
if (token.kin == 'light-dark') {
return token.val + '(' + token.chi.reduce((acc, curr) => acc + renderToken(curr, options, cache), '') + ')';
}
if (options.convertColor) {
if (token.cal == 'mix' && token.val == 'color-mix') {
const children = token.chi.reduce((acc, t) => {
Expand Down Expand Up @@ -372,7 +375,7 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer,
return reduceHexValue(value);
}
}
if (token.kin == 'hex' || token.kin == 'lit') {
if (['hex', 'lit', 'sys', 'dpsys'].includes(token.kin)) {
return token.val;
}
if (Array.isArray(token.chi)) {
Expand Down
29 changes: 29 additions & 0 deletions jsr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@tbela99/css-parser",
"version": "0.6.0",
"publish": {
"exclude": [
"dist",
"test",
".gitattributes",
".github",
".idea",
".npmignore",
".npmrc",
"tsconfig.json",
"Writerside",
"build.sh",
"docs",
"package-lock.json",
"CHANGELOG.md",
"ROADMAP.md",
"rollup.config.js",
"tools"
]
},
"exports": {
".": "./src/node/index.ts",
"./node": "./src/node/index.ts",
"./web": "./src/web/index.ts"
}
}
Loading

0 comments on commit 5bf5a00

Please sign in to comment.