Skip to content

Commit cda7bbf

Browse files
committed
fix(core): make background: none clear the background color
`background: none` left background-color unset, so a native control such as a button kept its own background. The shorthand parser did not accept the `none` keyword at all, so the declaration failed to expand and none of the background longhands changed. The background parser now reads `none` as the image component, and the `background` shorthand maps it to no image and, unless the declaration also gives a color, to a transparent background-color, the initial value CSS resets it to. Shorthands that only give an image, repeat or position still leave background-color unset, so views keep their native default background behind an image.
1 parent aeac327 commit cda7bbf

5 files changed

Lines changed: 67 additions & 0 deletions

File tree

‎apps/automated/src/ui/styling/style-properties-tests.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ export function test_setting_backgroundImage_property_from_CSS_is_applied_to_Sty
3939
test_property_from_CSS_is_applied_to_style('backgroundImage', 'background-image', "url('~/pages/test2.png')");
4040
}
4141

42+
export function test_setting_background_none_from_CSS_makes_backgroundColor_transparent() {
43+
test_property_from_CSS_is_applied_to_style('backgroundColor', 'background', new Color('transparent'), 'none');
44+
}
45+
4246
export function test_setting_borderWidth_property_from_CSS_is_applied_to_Style() {
4347
test_property_from_CSS_is_applied_to_style('borderWidth', 'border-width', 5, '5', true);
4448
}

‎packages/core/css/parser.spec.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ describe('css', () => {
122122
size: { x: { value: 100, unit: 'px' }, y: { value: 100, unit: 'px' } },
123123
},
124124
});
125+
test(parseBackground, 'none', { start: 0, end: 4, value: { image: 'none' } });
126+
test(parseBackground, ' #00ff00 none no-repeat ', { start: 0, end: 24, value: { color: new Color('#00ff00'), image: 'none', repeat: 'no-repeat' } });
127+
test(parseBackground, 'nonesuch', null);
125128
test(parseBackground, ' linear-gradient(to right top) ', {
126129
start: 0,
127130
end: 32,

‎packages/core/css/parser.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,11 @@ export function parseBackground(text: string, start = 0): Parsed<Background> {
601601
end = url.end;
602602
continue;
603603
}
604+
if (keyword && keyword.value.toLowerCase() === 'none') {
605+
value.image = 'none';
606+
end = keyword.end;
607+
continue;
608+
}
604609
const gradient = parseLinearGradient(text, end);
605610
if (gradient) {
606611
value.image = gradient.value;

‎packages/core/ui/styling/style-properties.spec.ts‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest';
22

33
import { Label } from '../label';
44
import { Color } from '../../color';
5+
import { StackLayout } from '../layouts/stack-layout';
6+
import { StyleScope } from './style-scope';
57

68
describe('border-color shorthand', () => {
79
it('applies a function color with nested arguments to all sides', () => {
@@ -61,3 +63,46 @@ describe('corner-shape', () => {
6163
expect(() => (label.style.cornerShape = 'bevel' as any)).toThrow();
6264
});
6365
});
66+
67+
describe('background shorthand', () => {
68+
it('none makes the background transparent and removes the image', () => {
69+
const label = new Label();
70+
label.style.backgroundColor = new Color('red');
71+
label.style.backgroundImage = 'res://logo';
72+
73+
label.style.background = 'none' as any;
74+
75+
expect(label.style.backgroundColor).toEqual(new Color('transparent'));
76+
expect(label.style.backgroundImage).toBeUndefined();
77+
});
78+
79+
it('none applies from CSS', () => {
80+
const scope = new StyleScope();
81+
scope.css = 'Label { background-color: red; } .reset { background: none; }';
82+
const root: any = new StackLayout();
83+
root._styleScope = scope;
84+
const label: any = new Label();
85+
label.className = 'reset';
86+
root.addChild(label);
87+
label._styleScope = scope;
88+
label._cssState.onLoaded();
89+
90+
expect(label.style.backgroundColor).toEqual(new Color('transparent'));
91+
});
92+
93+
it('none keeps a color given with it', () => {
94+
const label = new Label();
95+
label.style.background = 'none #00ff00' as any;
96+
97+
expect(label.style.backgroundColor).toEqual(new Color('#00ff00'));
98+
expect(label.style.backgroundImage).toBeUndefined();
99+
});
100+
101+
it('an image without a color still leaves the color unset', () => {
102+
const label = new Label();
103+
label.style.background = 'url(res://logo) no-repeat' as any;
104+
105+
expect(label.style.backgroundColor).toBeUndefined();
106+
expect(label.style.backgroundImage).toBe('res://logo');
107+
});
108+
});

‎packages/core/ui/styling/style-properties.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,17 +132,27 @@ function parseBorderColorPositioning(value: string): ShorthandPositioning {
132132
return positioningFromParts(splitOnTopLevelSpacesAndCommas(value.trim()), value);
133133
}
134134

135+
// Colors are immutable, so every `background: none` shares one.
136+
const TRANSPARENT = new Color('transparent');
137+
135138
function convertToBackgrounds(value: string): [CssProperty<any, any> | CssAnimationProperty<any, any>, any][] {
136139
if (typeof value === 'string') {
137140
const backgrounds = parseBackground(value).value;
138141
let backgroundColor = unsetValue;
139142
if (backgrounds.color) {
140143
backgroundColor = backgrounds.color instanceof Color ? backgrounds.color : new Color(backgrounds.color);
144+
} else if (backgrounds.image === 'none') {
145+
// `background: none` resets background-color to its initial value,
146+
// transparent, which also clears a native control's own background.
147+
// Other shorthands without a color still leave the native default.
148+
backgroundColor = TRANSPARENT;
141149
}
142150

143151
let backgroundImage: string | LinearGradient;
144152
if (typeof backgrounds.image === 'object' && backgrounds.image) {
145153
backgroundImage = LinearGradient.parse(backgrounds.image);
154+
} else if (backgrounds.image === 'none') {
155+
backgroundImage = unsetValue;
146156
} else {
147157
backgroundImage = backgrounds.image || unsetValue;
148158
}

0 commit comments

Comments
 (0)