vscode-css-languageservice 6.3.2 → 6.3.3-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/esm/languageFacts/colors.js +198 -3
- package/lib/esm/parser/cssNodes.js +1 -0
- package/lib/esm/parser/cssParser.js +6 -3
- package/lib/esm/parser/cssScanner.js +11 -5
- package/lib/esm/services/cssNavigation.js +17 -1
- package/lib/esm/services/selectorPrinting.js +4 -3
- package/lib/umd/languageFacts/colors.js +207 -3
- package/lib/umd/parser/cssNodes.js +1 -0
- package/lib/umd/parser/cssParser.js +6 -3
- package/lib/umd/parser/cssScanner.js +11 -5
- package/lib/umd/services/cssNavigation.js +16 -0
- package/lib/umd/services/selectorPrinting.js +4 -3
- package/package.json +1 -1
|
@@ -126,8 +126,32 @@ export const colorFunctions = [
|
|
|
126
126
|
insertText: 'color-mix(in ${1|hsl,hwb,lch,oklch|} ${2|shorter hue,longer hue,increasing hue,decreasing hue|}, ${3:color} ${4:percentage}, ${5:color} ${6:percentage})',
|
|
127
127
|
desc: l10n.t('Mix two colors together in a polar color space.')
|
|
128
128
|
},
|
|
129
|
+
{
|
|
130
|
+
label: 'lab',
|
|
131
|
+
func: 'lab($lightness $channel_a $channel_b $alpha)',
|
|
132
|
+
insertText: 'lab(${1:lightness} ${2:a} ${3:b} ${4:alpha})',
|
|
133
|
+
desc: l10n.t('css.builtin.lab', 'Creates a Color from Lightness, Channel a, Channel b and alpha values.')
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
label: 'lab relative',
|
|
137
|
+
func: 'lab(from $color $lightness $channel_a $channel_b $alpha)',
|
|
138
|
+
insertText: 'lab(from ${1:color} ${2:lightness} ${3:channel_a} ${4:channel_b} ${5:alpha})',
|
|
139
|
+
desc: l10n.t('css.builtin.lab', 'Creates a Color from Lightness, Channel a, Channel b and alpha values of another Color.')
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
label: 'lch',
|
|
143
|
+
func: 'lch($lightness $chrome $hue $alpha)',
|
|
144
|
+
insertText: 'lch(${1:lightness} ${2:chrome} ${3:hue} ${4:alpha})',
|
|
145
|
+
desc: l10n.t('css.builtin.lab', 'Creates a Color from Lightness, Chroma, Hue and alpha values.')
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
label: 'lch relative',
|
|
149
|
+
func: 'lch(from $color $lightness $chrome $hue $alpha)',
|
|
150
|
+
insertText: 'lch(from ${1:color} ${2:lightness} ${3:chrome} ${4:hue} ${5:alpha})',
|
|
151
|
+
desc: l10n.t('css.builtin.lab', 'Creates a Color from Lightness, Chroma, Hue and alpha values of another Color.')
|
|
152
|
+
}
|
|
129
153
|
];
|
|
130
|
-
const colorFunctionNameRegExp = /^(rgb|rgba|hsl|hsla|hwb)$/i;
|
|
154
|
+
const colorFunctionNameRegExp = /^(rgb|rgba|hsl|hsla|hwb|lab|lch)$/i;
|
|
131
155
|
export const colors = {
|
|
132
156
|
aliceblue: '#f0f8ff',
|
|
133
157
|
antiquewhite: '#faebd7',
|
|
@@ -284,7 +308,7 @@ export const colorKeywords = {
|
|
|
284
308
|
'transparent': 'Fully transparent. This keyword can be considered a shorthand for rgba(0,0,0,0) which is its computed value.',
|
|
285
309
|
};
|
|
286
310
|
const colorKeywordsRegExp = new RegExp(`^(${Object.keys(colorKeywords).join('|')})$`, "i");
|
|
287
|
-
function getNumericValue(node, factor) {
|
|
311
|
+
function getNumericValue(node, factor, lowerLimit = 0, upperLimit = 1) {
|
|
288
312
|
const val = node.getText();
|
|
289
313
|
const m = val.match(/^([-+]?[0-9]*\.?[0-9]+)(%?)$/);
|
|
290
314
|
if (m) {
|
|
@@ -292,7 +316,7 @@ function getNumericValue(node, factor) {
|
|
|
292
316
|
factor = 100.0;
|
|
293
317
|
}
|
|
294
318
|
const result = parseFloat(m[1]) / factor;
|
|
295
|
-
if (result >=
|
|
319
|
+
if (result >= lowerLimit && result <= upperLimit) {
|
|
296
320
|
return result;
|
|
297
321
|
}
|
|
298
322
|
}
|
|
@@ -511,6 +535,163 @@ export function hwbFromColor(rgba) {
|
|
|
511
535
|
a: hsl.a
|
|
512
536
|
};
|
|
513
537
|
}
|
|
538
|
+
export function xyzFromLAB(lab) {
|
|
539
|
+
const xyz = {
|
|
540
|
+
x: 0,
|
|
541
|
+
y: 0,
|
|
542
|
+
z: 0,
|
|
543
|
+
alpha: lab.alpha ?? 1
|
|
544
|
+
};
|
|
545
|
+
xyz.y = (lab.l + 16.0) / 116.0;
|
|
546
|
+
xyz.x = (lab.a / 500.0) + xyz.y;
|
|
547
|
+
xyz.z = xyz.y - (lab.b / 200.0);
|
|
548
|
+
let key;
|
|
549
|
+
for (key in xyz) {
|
|
550
|
+
let pow = xyz[key] * xyz[key] * xyz[key];
|
|
551
|
+
if (pow > 0.008856) {
|
|
552
|
+
xyz[key] = pow;
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
xyz[key] = (xyz[key] - 16.0 / 116.0) / 7.787;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
xyz.x = xyz.x * 95.047;
|
|
559
|
+
xyz.y = xyz.y * 100.0;
|
|
560
|
+
xyz.z = xyz.z * 108.883;
|
|
561
|
+
return xyz;
|
|
562
|
+
}
|
|
563
|
+
export function xyzToRGB(xyz) {
|
|
564
|
+
const x = xyz.x / 100;
|
|
565
|
+
const y = xyz.y / 100;
|
|
566
|
+
const z = xyz.z / 100;
|
|
567
|
+
const r = 3.2406254773200533 * x - 1.5372079722103187 * y - 0.4986285986982479 * z;
|
|
568
|
+
const g = -0.9689307147293197 * x + 1.8757560608852415 * y + 0.041517523842953964 * z;
|
|
569
|
+
const b = 0.055710120445510616 * x + -0.2040210505984867 * y + 1.0569959422543882 * z;
|
|
570
|
+
const compand = (c) => {
|
|
571
|
+
return c <= 0.0031308 ?
|
|
572
|
+
12.92 * c :
|
|
573
|
+
Math.min(1.055 * Math.pow(c, 1 / 2.4) - 0.055, 1);
|
|
574
|
+
};
|
|
575
|
+
return {
|
|
576
|
+
red: Math.round(compand(r) * 255.0),
|
|
577
|
+
blue: Math.round(compand(b) * 255.0),
|
|
578
|
+
green: Math.round(compand(g) * 255.0),
|
|
579
|
+
alpha: xyz.alpha
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
export function RGBtoXYZ(rgba) {
|
|
583
|
+
let r = rgba.red, g = rgba.green, b = rgba.blue;
|
|
584
|
+
if (r > 0.04045) {
|
|
585
|
+
r = Math.pow((r + 0.055) / 1.055, 2.4);
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
r = r / 12.92;
|
|
589
|
+
}
|
|
590
|
+
if (g > 0.04045) {
|
|
591
|
+
g = Math.pow((g + 0.055) / 1.055, 2.4);
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
g = g / 12.92;
|
|
595
|
+
}
|
|
596
|
+
if (b > 0.04045) {
|
|
597
|
+
b = Math.pow((b + 0.055) / 1.055, 2.4);
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
b = b / 12.92;
|
|
601
|
+
}
|
|
602
|
+
r = r * 100;
|
|
603
|
+
g = g * 100;
|
|
604
|
+
b = b * 100;
|
|
605
|
+
//Observer = 2°, Illuminant = D65
|
|
606
|
+
const x = r * 0.4124 + g * 0.3576 + b * 0.1805;
|
|
607
|
+
const y = r * 0.2126 + g * 0.7152 + b * 0.0722;
|
|
608
|
+
const z = r * 0.0193 + g * 0.1192 + b * 0.9505;
|
|
609
|
+
return { x, y, z, alpha: rgba.alpha };
|
|
610
|
+
}
|
|
611
|
+
export function XYZtoLAB(xyz, round = true) {
|
|
612
|
+
const ref_X = 95.047, ref_Y = 100.000, ref_Z = 108.883;
|
|
613
|
+
let x = xyz.x / ref_X, y = xyz.y / ref_Y, z = xyz.z / ref_Z;
|
|
614
|
+
if (x > 0.008856) {
|
|
615
|
+
x = Math.pow(x, 1 / 3);
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
x = (7.787 * x) + (16 / 116);
|
|
619
|
+
}
|
|
620
|
+
if (y > 0.008856) {
|
|
621
|
+
y = Math.pow(y, 1 / 3);
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
y = (7.787 * y) + (16 / 116);
|
|
625
|
+
}
|
|
626
|
+
if (z > 0.008856) {
|
|
627
|
+
z = Math.pow(z, 1 / 3);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
z = (7.787 * z) + (16 / 116);
|
|
631
|
+
}
|
|
632
|
+
const l = (116 * y) - 16, a = 500 * (x - y), b = 200 * (y - z);
|
|
633
|
+
if (round) {
|
|
634
|
+
return {
|
|
635
|
+
l: Math.round((l + Number.EPSILON) * 100) / 100,
|
|
636
|
+
a: Math.round((a + Number.EPSILON) * 100) / 100,
|
|
637
|
+
b: Math.round((b + Number.EPSILON) * 100) / 100,
|
|
638
|
+
alpha: xyz.alpha
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
return {
|
|
643
|
+
l, a, b,
|
|
644
|
+
alpha: xyz.alpha
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
export function labFromColor(rgba, round = true) {
|
|
649
|
+
const xyz = RGBtoXYZ(rgba);
|
|
650
|
+
const lab = XYZtoLAB(xyz, round);
|
|
651
|
+
return lab;
|
|
652
|
+
}
|
|
653
|
+
export function lchFromColor(rgba) {
|
|
654
|
+
const lab = labFromColor(rgba, false);
|
|
655
|
+
const c = Math.sqrt(Math.pow(lab.a, 2) + Math.pow(lab.b, 2));
|
|
656
|
+
let h = Math.atan2(lab.b, lab.a) * (180 / Math.PI);
|
|
657
|
+
while (h < 0) {
|
|
658
|
+
h = h + 360;
|
|
659
|
+
}
|
|
660
|
+
return {
|
|
661
|
+
l: Math.round((lab.l + Number.EPSILON) * 100) / 100,
|
|
662
|
+
c: Math.round((c + Number.EPSILON) * 100) / 100,
|
|
663
|
+
h: Math.round((h + Number.EPSILON) * 100) / 100,
|
|
664
|
+
alpha: lab.alpha
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
export function colorFromLAB(l, a, b, alpha = 1.0) {
|
|
668
|
+
const lab = {
|
|
669
|
+
l,
|
|
670
|
+
a,
|
|
671
|
+
b,
|
|
672
|
+
alpha
|
|
673
|
+
};
|
|
674
|
+
const xyz = xyzFromLAB(lab);
|
|
675
|
+
const rgb = xyzToRGB(xyz);
|
|
676
|
+
return {
|
|
677
|
+
red: (rgb.red >= 0 ? (rgb.red <= 255 ? rgb.red : 255) : 0) / 255.0,
|
|
678
|
+
green: (rgb.green >= 0 ? (rgb.green <= 255 ? rgb.green : 255) : 0) / 255.0,
|
|
679
|
+
blue: (rgb.blue >= 0 ? (rgb.blue <= 255 ? rgb.blue : 255) : 0) / 255.0,
|
|
680
|
+
alpha
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
export function labFromLCH(l, c, h, alpha = 1.0) {
|
|
684
|
+
return {
|
|
685
|
+
l: l,
|
|
686
|
+
a: c * Math.cos(h * (Math.PI / 180)),
|
|
687
|
+
b: c * Math.sin(h * (Math.PI / 180)),
|
|
688
|
+
alpha: alpha
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
export function colorFromLCH(l, c, h, alpha = 1.0) {
|
|
692
|
+
const lab = labFromLCH(l, c, h, alpha);
|
|
693
|
+
return colorFromLAB(lab.l, lab.a, lab.b, alpha);
|
|
694
|
+
}
|
|
514
695
|
export function getColorValue(node) {
|
|
515
696
|
if (node.type === nodes.NodeType.HexColorValue) {
|
|
516
697
|
const text = node.getText();
|
|
@@ -560,6 +741,20 @@ export function getColorValue(node) {
|
|
|
560
741
|
const b = getNumericValue(colorValues[2], 100.0);
|
|
561
742
|
return colorFromHWB(h, w, b, alpha);
|
|
562
743
|
}
|
|
744
|
+
else if (name === 'lab') {
|
|
745
|
+
// Reference: https://mina86.com/2021/srgb-lab-lchab-conversions/
|
|
746
|
+
const l = getNumericValue(colorValues[0], 100.0);
|
|
747
|
+
// Since these two values can be negative, a lower limit of -1 has been added
|
|
748
|
+
const a = getNumericValue(colorValues[1], 125.0, -1);
|
|
749
|
+
const b = getNumericValue(colorValues[2], 125.0, -1);
|
|
750
|
+
return colorFromLAB(l * 100, a * 125, b * 125, alpha);
|
|
751
|
+
}
|
|
752
|
+
else if (name === 'lch') {
|
|
753
|
+
const l = getNumericValue(colorValues[0], 100.0);
|
|
754
|
+
const c = getNumericValue(colorValues[1], 230.0);
|
|
755
|
+
const h = getAngle(colorValues[2]);
|
|
756
|
+
return colorFromLCH(l * 100, c * 230, h, alpha);
|
|
757
|
+
}
|
|
563
758
|
}
|
|
564
759
|
catch (e) {
|
|
565
760
|
// parse error on numeric value
|
|
@@ -99,6 +99,7 @@ export var NodeType;
|
|
|
99
99
|
NodeType[NodeType["PropertyAtRule"] = 86] = "PropertyAtRule";
|
|
100
100
|
NodeType[NodeType["Container"] = 87] = "Container";
|
|
101
101
|
NodeType[NodeType["ModuleConfig"] = 88] = "ModuleConfig";
|
|
102
|
+
NodeType[NodeType["SelectorList"] = 89] = "SelectorList";
|
|
102
103
|
})(NodeType || (NodeType = {}));
|
|
103
104
|
export var ReferenceType;
|
|
104
105
|
(function (ReferenceType) {
|
|
@@ -1510,7 +1510,7 @@ export class Parser {
|
|
|
1510
1510
|
if (node) {
|
|
1511
1511
|
if (!this.hasWhitespace() && this.accept(TokenType.ParenthesisL)) {
|
|
1512
1512
|
const tryAsSelector = () => {
|
|
1513
|
-
const selectors = this.
|
|
1513
|
+
const selectors = this.createNode(nodes.NodeType.SelectorList);
|
|
1514
1514
|
if (!selectors.addChild(this._parseSelector(true))) {
|
|
1515
1515
|
return null;
|
|
1516
1516
|
}
|
|
@@ -1524,8 +1524,11 @@ export class Parser {
|
|
|
1524
1524
|
};
|
|
1525
1525
|
let hasSelector = node.addChild(this.try(tryAsSelector));
|
|
1526
1526
|
if (!hasSelector) {
|
|
1527
|
-
|
|
1528
|
-
|
|
1527
|
+
// accept the <an+b> syntax (not a proper expression) https://drafts.csswg.org/css-syntax/#anb
|
|
1528
|
+
while (!this.peekIdent('of') && (node.addChild(this._parseTerm()) || node.addChild(this._parseOperator()))) {
|
|
1529
|
+
// loop
|
|
1530
|
+
}
|
|
1531
|
+
if (this.acceptIdent('of') &&
|
|
1529
1532
|
!node.addChild(this.try(tryAsSelector))) {
|
|
1530
1533
|
return this.finish(node, ParseError.SelectorExpected);
|
|
1531
1534
|
}
|
|
@@ -393,15 +393,21 @@ export class Scanner {
|
|
|
393
393
|
return false;
|
|
394
394
|
}
|
|
395
395
|
_number() {
|
|
396
|
-
let npeek = 0
|
|
397
|
-
|
|
398
|
-
|
|
396
|
+
let npeek = 0;
|
|
397
|
+
let hasDot = false;
|
|
398
|
+
const peekFirst = this.stream.peekChar();
|
|
399
|
+
if (peekFirst === _PLS || peekFirst === _MIN) {
|
|
400
|
+
npeek++;
|
|
399
401
|
}
|
|
400
|
-
|
|
402
|
+
if (this.stream.peekChar(npeek) === _DOT) {
|
|
403
|
+
npeek++;
|
|
404
|
+
hasDot = true;
|
|
405
|
+
}
|
|
406
|
+
const ch = this.stream.peekChar(npeek);
|
|
401
407
|
if (ch >= _0 && ch <= _9) {
|
|
402
408
|
this.stream.advance(npeek + 1);
|
|
403
409
|
this.stream.advanceWhileChar((ch) => {
|
|
404
|
-
return ch >= _0 && ch <= _9 ||
|
|
410
|
+
return ch >= _0 && ch <= _9 || !hasDot && ch === _DOT;
|
|
405
411
|
});
|
|
406
412
|
return true;
|
|
407
413
|
}
|
|
@@ -7,7 +7,7 @@ import { DocumentHighlightKind, Location, Range, SymbolKind, TextEdit, FileType
|
|
|
7
7
|
import * as l10n from '@vscode/l10n';
|
|
8
8
|
import * as nodes from '../parser/cssNodes';
|
|
9
9
|
import { Symbols } from '../parser/cssSymbolScope';
|
|
10
|
-
import { getColorValue, hslFromColor, hwbFromColor } from '../languageFacts/facts';
|
|
10
|
+
import { getColorValue, hslFromColor, hwbFromColor, labFromColor, lchFromColor } from '../languageFacts/facts';
|
|
11
11
|
import { startsWith } from '../utils/strings';
|
|
12
12
|
import { dirname, joinPath } from '../utils/resources';
|
|
13
13
|
const startsWithSchemeRegex = /^\w+:\/\//;
|
|
@@ -304,6 +304,22 @@ export class CSSNavigation {
|
|
|
304
304
|
label = `hwb(${hwb.h} ${Math.round(hwb.w * 100)}% ${Math.round(hwb.b * 100)}% / ${hwb.a})`;
|
|
305
305
|
}
|
|
306
306
|
result.push({ label: label, textEdit: TextEdit.replace(range, label) });
|
|
307
|
+
const lab = labFromColor(color);
|
|
308
|
+
if (lab.alpha === 1) {
|
|
309
|
+
label = `lab(${lab.l}% ${lab.a} ${lab.b})`;
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
label = `lab(${lab.l}% ${lab.a} ${lab.b} / ${lab.alpha})`;
|
|
313
|
+
}
|
|
314
|
+
result.push({ label: label, textEdit: TextEdit.replace(range, label) });
|
|
315
|
+
const lch = lchFromColor(color);
|
|
316
|
+
if (lab.alpha === 1) {
|
|
317
|
+
label = `lch(${lch.l}% ${lch.c} ${lch.h})`;
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
label = `lch(${lch.l}% ${lch.c} ${lch.h} / ${lch.alpha})`;
|
|
321
|
+
}
|
|
322
|
+
result.push({ label: label, textEdit: TextEdit.replace(range, label) });
|
|
307
323
|
return result;
|
|
308
324
|
}
|
|
309
325
|
prepareRename(document, position, stylesheet) {
|
|
@@ -423,9 +423,10 @@ export class SelectorPrinting {
|
|
|
423
423
|
/* The specificity of the :nth-child(An+B [of S]?) pseudo-class is the specificity of a single pseudo-class plus, if S is specified, the specificity of the most specific complex selector in S */
|
|
424
424
|
// https://www.w3.org/TR/selectors-4/#the-nth-child-pseudo
|
|
425
425
|
specificity.attr++;
|
|
426
|
-
|
|
427
|
-
if (childElements.length
|
|
428
|
-
|
|
426
|
+
const lastChild = childElements[childElements.length - 1];
|
|
427
|
+
if (childElements.length > 2 && lastChild.type === nodes.NodeType.SelectorList) {
|
|
428
|
+
// e.g :nth-child(-n+3 of li.important)
|
|
429
|
+
let mostSpecificListItem = calculateMostSpecificListItem(lastChild.getChildren());
|
|
429
430
|
specificity.id += mostSpecificListItem.id;
|
|
430
431
|
specificity.attr += mostSpecificListItem.attr;
|
|
431
432
|
specificity.tag += mostSpecificListItem.tag;
|
|
@@ -24,6 +24,15 @@
|
|
|
24
24
|
exports.hslFromColor = hslFromColor;
|
|
25
25
|
exports.colorFromHWB = colorFromHWB;
|
|
26
26
|
exports.hwbFromColor = hwbFromColor;
|
|
27
|
+
exports.xyzFromLAB = xyzFromLAB;
|
|
28
|
+
exports.xyzToRGB = xyzToRGB;
|
|
29
|
+
exports.RGBtoXYZ = RGBtoXYZ;
|
|
30
|
+
exports.XYZtoLAB = XYZtoLAB;
|
|
31
|
+
exports.labFromColor = labFromColor;
|
|
32
|
+
exports.lchFromColor = lchFromColor;
|
|
33
|
+
exports.colorFromLAB = colorFromLAB;
|
|
34
|
+
exports.labFromLCH = labFromLCH;
|
|
35
|
+
exports.colorFromLCH = colorFromLCH;
|
|
27
36
|
exports.getColorValue = getColorValue;
|
|
28
37
|
const nodes = require("../parser/cssNodes");
|
|
29
38
|
const l10n = require("@vscode/l10n");
|
|
@@ -149,8 +158,32 @@
|
|
|
149
158
|
insertText: 'color-mix(in ${1|hsl,hwb,lch,oklch|} ${2|shorter hue,longer hue,increasing hue,decreasing hue|}, ${3:color} ${4:percentage}, ${5:color} ${6:percentage})',
|
|
150
159
|
desc: l10n.t('Mix two colors together in a polar color space.')
|
|
151
160
|
},
|
|
161
|
+
{
|
|
162
|
+
label: 'lab',
|
|
163
|
+
func: 'lab($lightness $channel_a $channel_b $alpha)',
|
|
164
|
+
insertText: 'lab(${1:lightness} ${2:a} ${3:b} ${4:alpha})',
|
|
165
|
+
desc: l10n.t('css.builtin.lab', 'Creates a Color from Lightness, Channel a, Channel b and alpha values.')
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
label: 'lab relative',
|
|
169
|
+
func: 'lab(from $color $lightness $channel_a $channel_b $alpha)',
|
|
170
|
+
insertText: 'lab(from ${1:color} ${2:lightness} ${3:channel_a} ${4:channel_b} ${5:alpha})',
|
|
171
|
+
desc: l10n.t('css.builtin.lab', 'Creates a Color from Lightness, Channel a, Channel b and alpha values of another Color.')
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
label: 'lch',
|
|
175
|
+
func: 'lch($lightness $chrome $hue $alpha)',
|
|
176
|
+
insertText: 'lch(${1:lightness} ${2:chrome} ${3:hue} ${4:alpha})',
|
|
177
|
+
desc: l10n.t('css.builtin.lab', 'Creates a Color from Lightness, Chroma, Hue and alpha values.')
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
label: 'lch relative',
|
|
181
|
+
func: 'lch(from $color $lightness $chrome $hue $alpha)',
|
|
182
|
+
insertText: 'lch(from ${1:color} ${2:lightness} ${3:chrome} ${4:hue} ${5:alpha})',
|
|
183
|
+
desc: l10n.t('css.builtin.lab', 'Creates a Color from Lightness, Chroma, Hue and alpha values of another Color.')
|
|
184
|
+
}
|
|
152
185
|
];
|
|
153
|
-
const colorFunctionNameRegExp = /^(rgb|rgba|hsl|hsla|hwb)$/i;
|
|
186
|
+
const colorFunctionNameRegExp = /^(rgb|rgba|hsl|hsla|hwb|lab|lch)$/i;
|
|
154
187
|
exports.colors = {
|
|
155
188
|
aliceblue: '#f0f8ff',
|
|
156
189
|
antiquewhite: '#faebd7',
|
|
@@ -307,7 +340,7 @@
|
|
|
307
340
|
'transparent': 'Fully transparent. This keyword can be considered a shorthand for rgba(0,0,0,0) which is its computed value.',
|
|
308
341
|
};
|
|
309
342
|
const colorKeywordsRegExp = new RegExp(`^(${Object.keys(exports.colorKeywords).join('|')})$`, "i");
|
|
310
|
-
function getNumericValue(node, factor) {
|
|
343
|
+
function getNumericValue(node, factor, lowerLimit = 0, upperLimit = 1) {
|
|
311
344
|
const val = node.getText();
|
|
312
345
|
const m = val.match(/^([-+]?[0-9]*\.?[0-9]+)(%?)$/);
|
|
313
346
|
if (m) {
|
|
@@ -315,7 +348,7 @@
|
|
|
315
348
|
factor = 100.0;
|
|
316
349
|
}
|
|
317
350
|
const result = parseFloat(m[1]) / factor;
|
|
318
|
-
if (result >=
|
|
351
|
+
if (result >= lowerLimit && result <= upperLimit) {
|
|
319
352
|
return result;
|
|
320
353
|
}
|
|
321
354
|
}
|
|
@@ -534,6 +567,163 @@
|
|
|
534
567
|
a: hsl.a
|
|
535
568
|
};
|
|
536
569
|
}
|
|
570
|
+
function xyzFromLAB(lab) {
|
|
571
|
+
const xyz = {
|
|
572
|
+
x: 0,
|
|
573
|
+
y: 0,
|
|
574
|
+
z: 0,
|
|
575
|
+
alpha: lab.alpha ?? 1
|
|
576
|
+
};
|
|
577
|
+
xyz.y = (lab.l + 16.0) / 116.0;
|
|
578
|
+
xyz.x = (lab.a / 500.0) + xyz.y;
|
|
579
|
+
xyz.z = xyz.y - (lab.b / 200.0);
|
|
580
|
+
let key;
|
|
581
|
+
for (key in xyz) {
|
|
582
|
+
let pow = xyz[key] * xyz[key] * xyz[key];
|
|
583
|
+
if (pow > 0.008856) {
|
|
584
|
+
xyz[key] = pow;
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
xyz[key] = (xyz[key] - 16.0 / 116.0) / 7.787;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
xyz.x = xyz.x * 95.047;
|
|
591
|
+
xyz.y = xyz.y * 100.0;
|
|
592
|
+
xyz.z = xyz.z * 108.883;
|
|
593
|
+
return xyz;
|
|
594
|
+
}
|
|
595
|
+
function xyzToRGB(xyz) {
|
|
596
|
+
const x = xyz.x / 100;
|
|
597
|
+
const y = xyz.y / 100;
|
|
598
|
+
const z = xyz.z / 100;
|
|
599
|
+
const r = 3.2406254773200533 * x - 1.5372079722103187 * y - 0.4986285986982479 * z;
|
|
600
|
+
const g = -0.9689307147293197 * x + 1.8757560608852415 * y + 0.041517523842953964 * z;
|
|
601
|
+
const b = 0.055710120445510616 * x + -0.2040210505984867 * y + 1.0569959422543882 * z;
|
|
602
|
+
const compand = (c) => {
|
|
603
|
+
return c <= 0.0031308 ?
|
|
604
|
+
12.92 * c :
|
|
605
|
+
Math.min(1.055 * Math.pow(c, 1 / 2.4) - 0.055, 1);
|
|
606
|
+
};
|
|
607
|
+
return {
|
|
608
|
+
red: Math.round(compand(r) * 255.0),
|
|
609
|
+
blue: Math.round(compand(b) * 255.0),
|
|
610
|
+
green: Math.round(compand(g) * 255.0),
|
|
611
|
+
alpha: xyz.alpha
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
function RGBtoXYZ(rgba) {
|
|
615
|
+
let r = rgba.red, g = rgba.green, b = rgba.blue;
|
|
616
|
+
if (r > 0.04045) {
|
|
617
|
+
r = Math.pow((r + 0.055) / 1.055, 2.4);
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
r = r / 12.92;
|
|
621
|
+
}
|
|
622
|
+
if (g > 0.04045) {
|
|
623
|
+
g = Math.pow((g + 0.055) / 1.055, 2.4);
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
g = g / 12.92;
|
|
627
|
+
}
|
|
628
|
+
if (b > 0.04045) {
|
|
629
|
+
b = Math.pow((b + 0.055) / 1.055, 2.4);
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
b = b / 12.92;
|
|
633
|
+
}
|
|
634
|
+
r = r * 100;
|
|
635
|
+
g = g * 100;
|
|
636
|
+
b = b * 100;
|
|
637
|
+
//Observer = 2°, Illuminant = D65
|
|
638
|
+
const x = r * 0.4124 + g * 0.3576 + b * 0.1805;
|
|
639
|
+
const y = r * 0.2126 + g * 0.7152 + b * 0.0722;
|
|
640
|
+
const z = r * 0.0193 + g * 0.1192 + b * 0.9505;
|
|
641
|
+
return { x, y, z, alpha: rgba.alpha };
|
|
642
|
+
}
|
|
643
|
+
function XYZtoLAB(xyz, round = true) {
|
|
644
|
+
const ref_X = 95.047, ref_Y = 100.000, ref_Z = 108.883;
|
|
645
|
+
let x = xyz.x / ref_X, y = xyz.y / ref_Y, z = xyz.z / ref_Z;
|
|
646
|
+
if (x > 0.008856) {
|
|
647
|
+
x = Math.pow(x, 1 / 3);
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
x = (7.787 * x) + (16 / 116);
|
|
651
|
+
}
|
|
652
|
+
if (y > 0.008856) {
|
|
653
|
+
y = Math.pow(y, 1 / 3);
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
y = (7.787 * y) + (16 / 116);
|
|
657
|
+
}
|
|
658
|
+
if (z > 0.008856) {
|
|
659
|
+
z = Math.pow(z, 1 / 3);
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
z = (7.787 * z) + (16 / 116);
|
|
663
|
+
}
|
|
664
|
+
const l = (116 * y) - 16, a = 500 * (x - y), b = 200 * (y - z);
|
|
665
|
+
if (round) {
|
|
666
|
+
return {
|
|
667
|
+
l: Math.round((l + Number.EPSILON) * 100) / 100,
|
|
668
|
+
a: Math.round((a + Number.EPSILON) * 100) / 100,
|
|
669
|
+
b: Math.round((b + Number.EPSILON) * 100) / 100,
|
|
670
|
+
alpha: xyz.alpha
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
return {
|
|
675
|
+
l, a, b,
|
|
676
|
+
alpha: xyz.alpha
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function labFromColor(rgba, round = true) {
|
|
681
|
+
const xyz = RGBtoXYZ(rgba);
|
|
682
|
+
const lab = XYZtoLAB(xyz, round);
|
|
683
|
+
return lab;
|
|
684
|
+
}
|
|
685
|
+
function lchFromColor(rgba) {
|
|
686
|
+
const lab = labFromColor(rgba, false);
|
|
687
|
+
const c = Math.sqrt(Math.pow(lab.a, 2) + Math.pow(lab.b, 2));
|
|
688
|
+
let h = Math.atan2(lab.b, lab.a) * (180 / Math.PI);
|
|
689
|
+
while (h < 0) {
|
|
690
|
+
h = h + 360;
|
|
691
|
+
}
|
|
692
|
+
return {
|
|
693
|
+
l: Math.round((lab.l + Number.EPSILON) * 100) / 100,
|
|
694
|
+
c: Math.round((c + Number.EPSILON) * 100) / 100,
|
|
695
|
+
h: Math.round((h + Number.EPSILON) * 100) / 100,
|
|
696
|
+
alpha: lab.alpha
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
function colorFromLAB(l, a, b, alpha = 1.0) {
|
|
700
|
+
const lab = {
|
|
701
|
+
l,
|
|
702
|
+
a,
|
|
703
|
+
b,
|
|
704
|
+
alpha
|
|
705
|
+
};
|
|
706
|
+
const xyz = xyzFromLAB(lab);
|
|
707
|
+
const rgb = xyzToRGB(xyz);
|
|
708
|
+
return {
|
|
709
|
+
red: (rgb.red >= 0 ? (rgb.red <= 255 ? rgb.red : 255) : 0) / 255.0,
|
|
710
|
+
green: (rgb.green >= 0 ? (rgb.green <= 255 ? rgb.green : 255) : 0) / 255.0,
|
|
711
|
+
blue: (rgb.blue >= 0 ? (rgb.blue <= 255 ? rgb.blue : 255) : 0) / 255.0,
|
|
712
|
+
alpha
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
function labFromLCH(l, c, h, alpha = 1.0) {
|
|
716
|
+
return {
|
|
717
|
+
l: l,
|
|
718
|
+
a: c * Math.cos(h * (Math.PI / 180)),
|
|
719
|
+
b: c * Math.sin(h * (Math.PI / 180)),
|
|
720
|
+
alpha: alpha
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
function colorFromLCH(l, c, h, alpha = 1.0) {
|
|
724
|
+
const lab = labFromLCH(l, c, h, alpha);
|
|
725
|
+
return colorFromLAB(lab.l, lab.a, lab.b, alpha);
|
|
726
|
+
}
|
|
537
727
|
function getColorValue(node) {
|
|
538
728
|
if (node.type === nodes.NodeType.HexColorValue) {
|
|
539
729
|
const text = node.getText();
|
|
@@ -583,6 +773,20 @@
|
|
|
583
773
|
const b = getNumericValue(colorValues[2], 100.0);
|
|
584
774
|
return colorFromHWB(h, w, b, alpha);
|
|
585
775
|
}
|
|
776
|
+
else if (name === 'lab') {
|
|
777
|
+
// Reference: https://mina86.com/2021/srgb-lab-lchab-conversions/
|
|
778
|
+
const l = getNumericValue(colorValues[0], 100.0);
|
|
779
|
+
// Since these two values can be negative, a lower limit of -1 has been added
|
|
780
|
+
const a = getNumericValue(colorValues[1], 125.0, -1);
|
|
781
|
+
const b = getNumericValue(colorValues[2], 125.0, -1);
|
|
782
|
+
return colorFromLAB(l * 100, a * 125, b * 125, alpha);
|
|
783
|
+
}
|
|
784
|
+
else if (name === 'lch') {
|
|
785
|
+
const l = getNumericValue(colorValues[0], 100.0);
|
|
786
|
+
const c = getNumericValue(colorValues[1], 230.0);
|
|
787
|
+
const h = getAngle(colorValues[2]);
|
|
788
|
+
return colorFromLCH(l * 100, c * 230, h, alpha);
|
|
789
|
+
}
|
|
586
790
|
}
|
|
587
791
|
catch (e) {
|
|
588
792
|
// parse error on numeric value
|
|
@@ -113,6 +113,7 @@
|
|
|
113
113
|
NodeType[NodeType["PropertyAtRule"] = 86] = "PropertyAtRule";
|
|
114
114
|
NodeType[NodeType["Container"] = 87] = "Container";
|
|
115
115
|
NodeType[NodeType["ModuleConfig"] = 88] = "ModuleConfig";
|
|
116
|
+
NodeType[NodeType["SelectorList"] = 89] = "SelectorList";
|
|
116
117
|
})(NodeType || (exports.NodeType = NodeType = {}));
|
|
117
118
|
var ReferenceType;
|
|
118
119
|
(function (ReferenceType) {
|
|
@@ -1521,7 +1521,7 @@
|
|
|
1521
1521
|
if (node) {
|
|
1522
1522
|
if (!this.hasWhitespace() && this.accept(cssScanner_1.TokenType.ParenthesisL)) {
|
|
1523
1523
|
const tryAsSelector = () => {
|
|
1524
|
-
const selectors = this.
|
|
1524
|
+
const selectors = this.createNode(nodes.NodeType.SelectorList);
|
|
1525
1525
|
if (!selectors.addChild(this._parseSelector(true))) {
|
|
1526
1526
|
return null;
|
|
1527
1527
|
}
|
|
@@ -1535,8 +1535,11 @@
|
|
|
1535
1535
|
};
|
|
1536
1536
|
let hasSelector = node.addChild(this.try(tryAsSelector));
|
|
1537
1537
|
if (!hasSelector) {
|
|
1538
|
-
|
|
1539
|
-
|
|
1538
|
+
// accept the <an+b> syntax (not a proper expression) https://drafts.csswg.org/css-syntax/#anb
|
|
1539
|
+
while (!this.peekIdent('of') && (node.addChild(this._parseTerm()) || node.addChild(this._parseOperator()))) {
|
|
1540
|
+
// loop
|
|
1541
|
+
}
|
|
1542
|
+
if (this.acceptIdent('of') &&
|
|
1540
1543
|
!node.addChild(this.try(tryAsSelector))) {
|
|
1541
1544
|
return this.finish(node, cssErrors_1.ParseError.SelectorExpected);
|
|
1542
1545
|
}
|
|
@@ -405,15 +405,21 @@
|
|
|
405
405
|
return false;
|
|
406
406
|
}
|
|
407
407
|
_number() {
|
|
408
|
-
let npeek = 0
|
|
409
|
-
|
|
410
|
-
|
|
408
|
+
let npeek = 0;
|
|
409
|
+
let hasDot = false;
|
|
410
|
+
const peekFirst = this.stream.peekChar();
|
|
411
|
+
if (peekFirst === _PLS || peekFirst === _MIN) {
|
|
412
|
+
npeek++;
|
|
411
413
|
}
|
|
412
|
-
|
|
414
|
+
if (this.stream.peekChar(npeek) === _DOT) {
|
|
415
|
+
npeek++;
|
|
416
|
+
hasDot = true;
|
|
417
|
+
}
|
|
418
|
+
const ch = this.stream.peekChar(npeek);
|
|
413
419
|
if (ch >= _0 && ch <= _9) {
|
|
414
420
|
this.stream.advance(npeek + 1);
|
|
415
421
|
this.stream.advanceWhileChar((ch) => {
|
|
416
|
-
return ch >= _0 && ch <= _9 ||
|
|
422
|
+
return ch >= _0 && ch <= _9 || !hasDot && ch === _DOT;
|
|
417
423
|
});
|
|
418
424
|
return true;
|
|
419
425
|
}
|
|
@@ -316,6 +316,22 @@
|
|
|
316
316
|
label = `hwb(${hwb.h} ${Math.round(hwb.w * 100)}% ${Math.round(hwb.b * 100)}% / ${hwb.a})`;
|
|
317
317
|
}
|
|
318
318
|
result.push({ label: label, textEdit: cssLanguageTypes_1.TextEdit.replace(range, label) });
|
|
319
|
+
const lab = (0, facts_1.labFromColor)(color);
|
|
320
|
+
if (lab.alpha === 1) {
|
|
321
|
+
label = `lab(${lab.l}% ${lab.a} ${lab.b})`;
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
label = `lab(${lab.l}% ${lab.a} ${lab.b} / ${lab.alpha})`;
|
|
325
|
+
}
|
|
326
|
+
result.push({ label: label, textEdit: cssLanguageTypes_1.TextEdit.replace(range, label) });
|
|
327
|
+
const lch = (0, facts_1.lchFromColor)(color);
|
|
328
|
+
if (lab.alpha === 1) {
|
|
329
|
+
label = `lch(${lch.l}% ${lch.c} ${lch.h})`;
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
label = `lch(${lch.l}% ${lch.c} ${lch.h} / ${lch.alpha})`;
|
|
333
|
+
}
|
|
334
|
+
result.push({ label: label, textEdit: cssLanguageTypes_1.TextEdit.replace(range, label) });
|
|
319
335
|
return result;
|
|
320
336
|
}
|
|
321
337
|
prepareRename(document, position, stylesheet) {
|
|
@@ -439,9 +439,10 @@
|
|
|
439
439
|
/* The specificity of the :nth-child(An+B [of S]?) pseudo-class is the specificity of a single pseudo-class plus, if S is specified, the specificity of the most specific complex selector in S */
|
|
440
440
|
// https://www.w3.org/TR/selectors-4/#the-nth-child-pseudo
|
|
441
441
|
specificity.attr++;
|
|
442
|
-
|
|
443
|
-
if (childElements.length
|
|
444
|
-
|
|
442
|
+
const lastChild = childElements[childElements.length - 1];
|
|
443
|
+
if (childElements.length > 2 && lastChild.type === nodes.NodeType.SelectorList) {
|
|
444
|
+
// e.g :nth-child(-n+3 of li.important)
|
|
445
|
+
let mostSpecificListItem = calculateMostSpecificListItem(lastChild.getChildren());
|
|
445
446
|
specificity.id += mostSpecificListItem.id;
|
|
446
447
|
specificity.attr += mostSpecificListItem.attr;
|
|
447
448
|
specificity.tag += mostSpecificListItem.tag;
|