mirror of
https://github.com/less/less.js.git
synced 2026-01-10 08:08:19 -05:00
fix(issue:4267) support starting-style at-rule (#4333)
* fix(issue:4267) support starting-style at-rule * Add support for the starting-style at-rule. * fix(issue:4267) improve comment and call handling * Improve at-rule comment and call handling and add more starting-style at-rule tests. * refactor(issue:4267) refactor starting-style code * Refactor new code introduced to broaded at-rule support for starting-syle at-rule and other at-rules. * chore(issue:4267) cleanup at-rule enhancement * Cleanup at-rule enhancement code for starting-style at-rule support. * fix: pin Playwright to exact version * Pin Playwright to exact version to try to resolve CI issues.
This commit is contained in:
@@ -84,7 +84,7 @@
|
||||
"less-plugin-clean-css": "^1.6.0",
|
||||
"minimist": "^1.2.0",
|
||||
"mocha": "^6.2.1",
|
||||
"playwright": "~1.50.1",
|
||||
"playwright": "1.50.1",
|
||||
"mocha-teamcity-reporter": "^3.0.0",
|
||||
"nock": "^11.8.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
|
||||
@@ -2102,6 +2102,9 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
||||
hasUnknown = true;
|
||||
isRooted = false;
|
||||
break;
|
||||
case '@starting-style':
|
||||
isRooted = false;
|
||||
break;
|
||||
default:
|
||||
hasUnknown = true;
|
||||
break;
|
||||
|
||||
@@ -2,6 +2,7 @@ import Node from './node';
|
||||
import Selector from './selector';
|
||||
import Ruleset from './ruleset';
|
||||
import Anonymous from './anonymous';
|
||||
import NestableAtRulePrototype from './nested-at-rule';
|
||||
|
||||
const AtRule = function(
|
||||
name,
|
||||
@@ -14,19 +15,45 @@ const AtRule = function(
|
||||
visibilityInfo
|
||||
) {
|
||||
let i;
|
||||
var selectors = (new Selector([], null, null, this._index, this._fileInfo)).createEmptySelectors();
|
||||
|
||||
this.name = name;
|
||||
this.value = (value instanceof Node) ? value : (value ? new Anonymous(value) : value);
|
||||
if (rules) {
|
||||
if (Array.isArray(rules)) {
|
||||
this.rules = rules;
|
||||
const allDeclarations = this.declarationsBlock(rules);
|
||||
|
||||
let allRulesetDeclarations = true;
|
||||
rules.forEach(rule => {
|
||||
if (rule.type === 'Ruleset' && rule.rules) allRulesetDeclarations = allRulesetDeclarations && this.declarationsBlock(rule.rules, true);
|
||||
});
|
||||
|
||||
if (allDeclarations && !isRooted) {
|
||||
this.simpleBlock = true;
|
||||
this.declarations = rules;
|
||||
} else if (allRulesetDeclarations && rules.length === 1 && !isRooted && !value) {
|
||||
this.simpleBlock = true;
|
||||
this.declarations = rules[0].rules ? rules[0].rules : rules;
|
||||
} else {
|
||||
this.rules = rules;
|
||||
}
|
||||
} else {
|
||||
this.rules = [rules];
|
||||
this.rules[0].selectors = (new Selector([], null, null, index, currentFileInfo)).createEmptySelectors();
|
||||
const allDeclarations = this.declarationsBlock(rules.rules);
|
||||
|
||||
if (allDeclarations && !isRooted && !value) {
|
||||
this.simpleBlock = true;
|
||||
this.declarations = rules.rules;
|
||||
} else {
|
||||
this.rules = [rules];
|
||||
this.rules[0].selectors = (new Selector([], null, null, index, currentFileInfo)).createEmptySelectors();
|
||||
}
|
||||
}
|
||||
for (i = 0; i < this.rules.length; i++) {
|
||||
this.rules[i].allowImports = true;
|
||||
if (!this.simpleBlock) {
|
||||
for (i = 0; i < this.rules.length; i++) {
|
||||
this.rules[i].allowImports = true;
|
||||
}
|
||||
}
|
||||
this.setParent(selectors, this);
|
||||
this.setParent(this.rules, this);
|
||||
}
|
||||
this._index = index;
|
||||
@@ -39,10 +66,24 @@ const AtRule = function(
|
||||
|
||||
AtRule.prototype = Object.assign(new Node(), {
|
||||
type: 'AtRule',
|
||||
|
||||
...NestableAtRulePrototype,
|
||||
|
||||
declarationsBlock(rules, mergeable = false) {
|
||||
if (!mergeable) {
|
||||
return rules.filter(function (node) { return (node.type === 'Declaration' || node.type === 'Comment') && !node.merge}).length === rules.length;
|
||||
} else {
|
||||
return rules.filter(function (node) { return (node.type === 'Declaration' || node.type === 'Comment'); }).length === rules.length;
|
||||
}
|
||||
},
|
||||
|
||||
accept(visitor) {
|
||||
const value = this.value, rules = this.rules;
|
||||
const value = this.value, rules = this.rules, declarations = this.declarations;
|
||||
|
||||
if (rules) {
|
||||
this.rules = visitor.visitArray(rules);
|
||||
} else if (declarations) {
|
||||
this.declarations = visitor.visitArray(declarations);
|
||||
}
|
||||
if (value) {
|
||||
this.value = visitor.visit(value);
|
||||
@@ -58,13 +99,15 @@ AtRule.prototype = Object.assign(new Node(), {
|
||||
},
|
||||
|
||||
genCSS(context, output) {
|
||||
const value = this.value, rules = this.rules;
|
||||
const value = this.value, rules = this.rules || this.declarations;
|
||||
output.add(this.name, this.fileInfo(), this.getIndex());
|
||||
if (value) {
|
||||
output.add(' ');
|
||||
value.genCSS(context, output);
|
||||
}
|
||||
if (rules) {
|
||||
if (this.simpleBlock) {
|
||||
this.outputRuleset(context, output, this.declarations);
|
||||
} else if (rules) {
|
||||
this.outputRuleset(context, output, rules);
|
||||
} else {
|
||||
output.add(';');
|
||||
@@ -72,8 +115,8 @@ AtRule.prototype = Object.assign(new Node(), {
|
||||
},
|
||||
|
||||
eval(context) {
|
||||
let mediaPathBackup, mediaBlocksBackup, value = this.value, rules = this.rules;
|
||||
|
||||
let mediaPathBackup, mediaBlocksBackup, value = this.value, rules = this.rules || this.declarations;
|
||||
|
||||
// media stored inside other atrule should not bubble over it
|
||||
// backpup media bubbling information
|
||||
mediaPathBackup = context.mediaPath;
|
||||
@@ -85,17 +128,78 @@ AtRule.prototype = Object.assign(new Node(), {
|
||||
if (value) {
|
||||
value = value.eval(context);
|
||||
}
|
||||
|
||||
if (rules) {
|
||||
// assuming that there is only one rule at this point - that is how parser constructs the rule
|
||||
rules = [rules[0].eval(context)];
|
||||
rules[0].root = true;
|
||||
rules = this.evalRoot(context, rules);
|
||||
}
|
||||
if (Array.isArray(rules) && rules[0].rules && Array.isArray(rules[0].rules) && rules[0].rules.length) {
|
||||
const allMergeableDeclarations = this.declarationsBlock(rules[0].rules, true);
|
||||
if (allMergeableDeclarations && !this.isRooted && !value) {
|
||||
var mergeRules = context.pluginManager.less.visitors.ToCSSVisitor.prototype._mergeRules;
|
||||
mergeRules(rules[0].rules);
|
||||
rules = rules[0].rules;
|
||||
rules.forEach(rule => rule.merge = false);
|
||||
}
|
||||
}
|
||||
if (this.simpleBlock && rules) {
|
||||
rules[0].functionRegistry = context.frames[0].functionRegistry.inherit();
|
||||
rules= rules.map(function (rule) { return rule.eval(context); });
|
||||
}
|
||||
|
||||
// restore media bubbling information
|
||||
context.mediaPath = mediaPathBackup;
|
||||
context.mediaBlocks = mediaBlocksBackup;
|
||||
return new AtRule(this.name, value, rules, this.getIndex(), this.fileInfo(), this.debugInfo, this.isRooted, this.visibilityInfo());
|
||||
},
|
||||
|
||||
return new AtRule(this.name, value, rules,
|
||||
this.getIndex(), this.fileInfo(), this.debugInfo, this.isRooted, this.visibilityInfo());
|
||||
evalRoot(context, rules) {
|
||||
let ampersandCount = 0;
|
||||
let noAmpersandCount = 0;
|
||||
let noAmpersands = true;
|
||||
let allAmpersands = false;
|
||||
|
||||
if (!this.simpleBlock) {
|
||||
rules = [rules[0].eval(context)];
|
||||
}
|
||||
|
||||
let precedingSelectors = [];
|
||||
if (context.frames.length > 0) {
|
||||
for (let index = 0; index < context.frames.length; index++) {
|
||||
const frame = context.frames[index];
|
||||
if (
|
||||
frame.type === 'Ruleset' &&
|
||||
frame.rules &&
|
||||
frame.rules.length > 0
|
||||
) {
|
||||
if (frame && !frame.root && frame.selectors && frame.selectors.length > 0) {
|
||||
precedingSelectors = precedingSelectors.concat(frame.selectors);
|
||||
}
|
||||
}
|
||||
if (precedingSelectors.length > 0) {
|
||||
let value = '';
|
||||
const output = { add: function (s) { value += s; } };
|
||||
for (let i = 0; i < precedingSelectors.length; i++) {
|
||||
precedingSelectors[i].genCSS(context, output);
|
||||
}
|
||||
if (/^&+$/.test(value.replace(/\s+/g, ''))) {
|
||||
noAmpersands = false;
|
||||
noAmpersandCount++;
|
||||
} else {
|
||||
allAmpersands = false;
|
||||
ampersandCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mixedAmpersands = ampersandCount > 0 && noAmpersandCount > 0 && !allAmpersands && !noAmpersands;
|
||||
if (
|
||||
(this.isRooted && ampersandCount > 0 && noAmpersandCount === 0 && !allAmpersands && noAmpersands)
|
||||
|| !mixedAmpersands
|
||||
) {
|
||||
rules[0].root = true;
|
||||
}
|
||||
return rules;
|
||||
},
|
||||
|
||||
variable(name) {
|
||||
|
||||
@@ -166,7 +166,17 @@ ImportVisitor.prototype = {
|
||||
}
|
||||
},
|
||||
visitAtRule: function (atRuleNode, visitArgs) {
|
||||
this.context.frames.unshift(atRuleNode);
|
||||
if (atRuleNode.value) {
|
||||
this.context.frames.unshift(atRuleNode);
|
||||
} else if (atRuleNode.declarations && atRuleNode.declarations.length) {
|
||||
if (atRuleNode.isRooted) {
|
||||
this.context.frames.unshift(atRuleNode);
|
||||
} else {
|
||||
this.context.frames.unshift(atRuleNode.declarations[0]);
|
||||
}
|
||||
} else if (atRuleNode.rules && atRuleNode.rules.length) {
|
||||
this.context.frames.unshift(atRuleNode);
|
||||
}
|
||||
},
|
||||
visitAtRuleOut: function (atRuleNode) {
|
||||
this.context.frames.shift();
|
||||
|
||||
@@ -52,7 +52,11 @@ class JoinSelectorVisitor {
|
||||
|
||||
visitAtRule(atRuleNode, visitArgs) {
|
||||
const context = this.contexts[this.contexts.length - 1];
|
||||
if (atRuleNode.rules && atRuleNode.rules.length) {
|
||||
|
||||
if (atRuleNode.declarations && atRuleNode.declarations.length) {
|
||||
atRuleNode.declarations[0].root = (context.length === 0 || context[0].multiMedia);
|
||||
}
|
||||
else if (atRuleNode.rules && atRuleNode.rules.length) {
|
||||
atRuleNode.rules[0].root = (atRuleNode.isRooted || context.length === 0 || null);
|
||||
}
|
||||
}
|
||||
|
||||
55
packages/test-data/css/_main/starting-style.css
Normal file
55
packages/test-data/css/_main/starting-style.css
Normal file
@@ -0,0 +1,55 @@
|
||||
#nav {
|
||||
transition: background-color 3.5s;
|
||||
background-color: gray;
|
||||
}
|
||||
[popover]:popover-open {
|
||||
opacity: 1;
|
||||
transform: scaleX(1);
|
||||
@starting-style {
|
||||
opacity: 0;
|
||||
transform: scaleX(0);
|
||||
}
|
||||
}
|
||||
#target {
|
||||
transition: background-color 1.5s;
|
||||
background-color: green;
|
||||
}
|
||||
@starting-style {
|
||||
#target {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
#source {
|
||||
transition: background-color 2.5s;
|
||||
background-color: red;
|
||||
}
|
||||
source:first {
|
||||
opacity: 1;
|
||||
transform: scaleX(1);
|
||||
@starting-style {
|
||||
opacity: 0;
|
||||
transform: scaleX(0);
|
||||
}
|
||||
}
|
||||
#footer {
|
||||
color: yellow;
|
||||
}
|
||||
@starting-style {
|
||||
#footer {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
nav > [popover]:popover-open {
|
||||
opacity: 1;
|
||||
transform: scaleX(1);
|
||||
@starting-style {
|
||||
padding: 10px 8px 6px 4px;
|
||||
}
|
||||
}
|
||||
aside > [popover]:popover-open {
|
||||
opacity: 1;
|
||||
transform: scaleX(1);
|
||||
@starting-style {
|
||||
padding: 10px 20px 30px 40px;
|
||||
}
|
||||
}
|
||||
74
packages/test-data/less/_main/starting-style.less
Normal file
74
packages/test-data/less/_main/starting-style.less
Normal file
@@ -0,0 +1,74 @@
|
||||
#nav {
|
||||
transition: background-color 3.5s;
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
[popover]:popover-open {
|
||||
opacity: 1;
|
||||
transform: scaleX(1);
|
||||
|
||||
@starting-style {
|
||||
opacity: 0;
|
||||
transform: scaleX(0);
|
||||
}
|
||||
}
|
||||
|
||||
#target {
|
||||
transition: background-color 1.5s;
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
@starting-style {
|
||||
#target {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
#source {
|
||||
transition: background-color 2.5s;
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
source:first {
|
||||
opacity: 1;
|
||||
transform: scaleX(1);
|
||||
|
||||
@starting-style {
|
||||
opacity: 0;
|
||||
transform: scaleX(0);
|
||||
}
|
||||
}
|
||||
|
||||
#footer {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
@starting-style {
|
||||
#footer {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
nav > [popover]:popover-open {
|
||||
opacity: 1;
|
||||
transform: scaleX(1);
|
||||
|
||||
@starting-style {
|
||||
padding+_: 10px;
|
||||
padding+_: 8px;
|
||||
padding+_: 6px;
|
||||
padding+_: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
aside > [popover]:popover-open {
|
||||
opacity: 1;
|
||||
transform: scaleX(1);
|
||||
|
||||
@starting-style {
|
||||
// vector math
|
||||
each(1 2 3 4, {
|
||||
padding+_: (@value * 10px);
|
||||
});
|
||||
}
|
||||
}
|
||||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@@ -136,7 +136,7 @@ importers:
|
||||
specifier: ^2.2.3
|
||||
version: 2.9.3
|
||||
playwright:
|
||||
specifier: ~1.50.1
|
||||
specifier: 1.50.1
|
||||
version: 1.50.1
|
||||
promise:
|
||||
specifier: ^7.1.1
|
||||
|
||||
Reference in New Issue
Block a user