Skip to content

Commit a9f3bf1

Browse files
authored
Merge pull request #1408 from mathjax/issue3490
Update handling of CSS `border` parts like `border-color` and add tests for that. (mathjax/MathJax#3490)
2 parents 06657a0 + 481e9d7 commit a9f3bf1

File tree

2 files changed

+80
-35
lines changed

2 files changed

+80
-35
lines changed

testsuite/tests/util/Styles.test.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -311,11 +311,14 @@ describe('CssStyles object', () => {
311311
cssTest('border-radius: 3px', {
312312
'border-radius': '3px',
313313
});
314-
cssTest('background: red; background-clip: none', {
315-
'background': 'red',
316-
'background-clip': 'none',
314+
cssTest('border-color: red', {
315+
'border-bottom-color': 'red',
316+
'border-color': 'red',
317+
'border-left-color': 'red',
318+
'border-right-color': 'red',
319+
'border-top-color': 'red',
317320
});
318-
cssTest(' border-top: inset blue 2px; border: 3px solid red', {
321+
cssTest('border-top: inset blue 2px; border: 3px solid red', {
319322
'border': '3px solid red',
320323
'border-top': '3px solid red',
321324
'border-top-color': 'red',
@@ -342,6 +345,13 @@ describe('CssStyles object', () => {
342345
}, 'border-top: 3px solid red;');
343346
});
344347

348+
test('background', () => {
349+
cssTest('background: red; background-clip: none', {
350+
'background': 'red',
351+
'background-clip': 'none',
352+
});
353+
});
354+
345355
test('font', () => {
346356
cssFontTest('font-family: arial', {'font-family': 'arial'});
347357
cssFontTest('font-size: 120%', {'font-size': '120%'});

ts/util/Styles.ts

Lines changed: 66 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ export type StyleList = { [name: string]: string };
3333
/* prettier-ignore */
3434
export type connection = {
3535
children: string[], // suffix names to add to the base name
36+
parts?: string[], // suffix names for sub-parts
3637
split: (name: string) => void, // function to split the value for the children
3738
combine: (name: string) => void // function to combine the child values when one changes
39+
subPart?: boolean // true means children combine to a different parent
3840
};
3941

4042
/**
@@ -53,7 +55,7 @@ export const WSC = ['width', 'style', 'color'];
5355
* Split a style at spaces (taking quotation marks and commas into account)
5456
*
5557
* @param {string} text The combined styles to be split at spaces
56-
* @returns {string[]} Array of parts of the style (separated by spaces)
58+
* @returns {string[]} Array of parts of the style (separated by spaces)
5759
*/
5860
function splitSpaces(text: string): string[] {
5961
const parts = text.split(/((?:'[^'\n]*'|"[^"\n]*"|,[\s\n]|[^\s\n])*)/g);
@@ -105,7 +107,7 @@ function combineTRBL(name: string) {
105107
const children = Styles.connect[name].children;
106108
const parts = [] as string[];
107109
for (const child of children) {
108-
const part = this.styles[name + '-' + child];
110+
const part = this.styles[this.childName(name, child)];
109111
if (!part) {
110112
delete this.styles[name];
111113
return;
@@ -124,6 +126,18 @@ function combineTRBL(name: string) {
124126
this.styles[name] = parts.join(' ');
125127
}
126128

129+
/**
130+
* Combine styles for a given border part (e.g., border-color)
131+
*
132+
* @param {string} name The name of the part to process
133+
*/
134+
function combinePart(name: string) {
135+
combineTRBL.call(this, name);
136+
this.combineChildren(name);
137+
combineSame.call(this, name);
138+
this.combineParent(name);
139+
}
140+
127141
/*********************************************************/
128142
/**
129143
* Use the same value for all children
@@ -151,7 +165,9 @@ function combineSame(name: string) {
151165
return;
152166
}
153167
}
154-
this.styles[name] = value;
168+
if (value) {
169+
this.styles[name] = value;
170+
}
155171
}
156172

157173
/*********************************************************/
@@ -198,7 +214,7 @@ function combineWSC(name: string) {
198214
parts.push(value);
199215
}
200216
}
201-
if (parts.length) {
217+
if (parts.length > 1) {
202218
this.styles[name] = parts.join(' ');
203219
} else {
204220
delete this.styles[name];
@@ -373,6 +389,7 @@ export class Styles {
373389

374390
border: {
375391
children: TRBL,
392+
parts: WSC,
376393
split: splitSame,
377394
combine: combineSame,
378395
},
@@ -399,17 +416,20 @@ export class Styles {
399416
'border-width': {
400417
children: TRBL,
401418
split: splitTRBL,
402-
combine: null, // means its children combine to a different parent
419+
combine: combinePart,
420+
subPart: true,
403421
},
404422
'border-style': {
405423
children: TRBL,
406424
split: splitTRBL,
407-
combine: null, // means its children combine to a different parent
425+
combine: combinePart,
426+
subPart: true,
408427
},
409428
'border-color': {
410429
children: TRBL,
411430
split: splitTRBL,
412-
combine: null, // means its children combine to a different parent
431+
combine: combinePart,
432+
subPart: true,
413433
},
414434

415435
font: {
@@ -469,9 +489,12 @@ export class Styles {
469489
for (const name of Object.keys(this.styles)) {
470490
const parent = this.parentName(name);
471491
const cname = name.replace(/.*-/, '');
492+
const pname = this.childName(this.parentName(parent), cname);
472493
if (
473-
!this.styles[parent] ||
474-
!Styles.connect[parent]?.children?.includes(cname)
494+
this.styles[name] &&
495+
!this.styles[pname] &&
496+
(!this.styles[parent] ||
497+
!Styles.connect[parent]?.children?.includes(cname))
475498
) {
476499
styles.push(`${name}: ${this.styles[name]};`);
477500
}
@@ -493,37 +516,49 @@ export class Styles {
493516
public set(name: string, value: string | number | boolean) {
494517
name = this.normalizeName(name);
495518
this.setStyle(name, String(value));
496-
//
497-
// If there is no combine function, the children combine to
498-
// a separate parent (e.g., border-width sets border-top-width, etc.
499-
// and combines to border-top)
500-
//
501-
if (Styles.connect[name] && !Styles.connect[name].combine) {
502-
this.combineChildren(name);
503-
delete this.styles[name];
519+
const connect = Styles.connect[name];
520+
if (connect?.subPart) {
521+
connect.combine.call(this, name);
522+
return;
504523
}
505-
//
506-
// If we just changed a child, we need to try to combine
507-
// it with its parent's other children
508-
//
524+
this.combineParent(name);
525+
if (name.match(/-.*-/)) {
526+
const pname = name.replace(/-.*-/, '-');
527+
combineSame.call(this, pname);
528+
}
529+
}
530+
531+
/**
532+
* When we change a child, we need to try to combine
533+
* it with its parent's other children.
534+
*
535+
* @param {string} name The child that was changed
536+
*/
537+
public combineParent(name: string) {
509538
while (name.match(/-/)) {
510539
const cname = name;
511540
name = this.parentName(name);
541+
const connect = Styles.connect[name];
512542
if (
513543
!Styles.connect[cname] &&
514-
!Styles.connect[name]?.children?.includes(
515-
cname.substring(name.length + 1)
516-
)
544+
!connect?.children?.includes(cname.substring(name.length + 1))
517545
) {
518546
break;
519547
}
520-
Styles.connect[name].combine.call(this, name);
548+
connect.combine.call(this, name);
549+
}
550+
if (!this.styles[name]) {
551+
return;
552+
}
553+
const connect = Styles.connect[name];
554+
for (const cname of connect?.parts || []) {
555+
delete this.styles[this.childName(name, cname)];
521556
}
522557
}
523558

524559
/**
525560
* @param {string} name The name of the style to get
526-
* @returns {string} The value of the style (or empty string if not defined)
561+
* @returns {string} The value of the style (or empty string if not defined)
527562
*/
528563
public get(name: string): string {
529564
name = this.normalizeName(name);
@@ -536,7 +571,7 @@ export class Styles {
536571
*/
537572
protected setStyle(name: string, value: string) {
538573
this.styles[name] = this.sanitizeValue(value);
539-
if (Styles.connect[name] && Styles.connect[name].children) {
574+
if (Styles.connect[name]?.children) {
540575
Styles.connect[name].split.call(this, name);
541576
}
542577
if (value === '') {
@@ -577,10 +612,10 @@ export class Styles {
577612
return child;
578613
}
579614
//
580-
// For non-combining styles, like border-width, insert
581-
// the child name before the find word, e.g., border-top-width
615+
// For sub-part styles, like border-width, insert
616+
// the child name before the final word, e.g., border-top-width
582617
//
583-
if (Styles.connect[name] && !Styles.connect[name].combine) {
618+
if (Styles.connect[name]?.subPart) {
584619
child += name.replace(/.*-/, '-');
585620
name = this.parentName(name);
586621
}
@@ -589,7 +624,7 @@ export class Styles {
589624

590625
/**
591626
* @param {string} name The name of a style to normalize
592-
* @returns {string} The name converted from CamelCase to lowercase with dashes
627+
* @returns {string} The name converted from CamelCase to lowercase with dashes
593628
*/
594629
protected normalizeName(name: string): string {
595630
return name.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase());

0 commit comments

Comments
 (0)