Skip to content

Commit d23ffb8

Browse files
authored
fix-next(css): className to preserve root views classes (#7725)
1 parent 92c3338 commit d23ffb8

13 files changed

Lines changed: 262 additions & 65 deletions

File tree

tests/app/ui/styling/root-views-css-classes-tests.ts

Lines changed: 110 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
} from "tns-core-modules/ui/core/view/view-common";
2323
import { DeviceType } from "tns-core-modules/ui/enums/enums";
2424

25+
const CLASS_NAME = "class-name";
2526
const ROOT_CSS_CLASS = "ns-root";
2627
const MODAL_CSS_CLASS = "ns-modal";
2728
const ANDROID_PLATFORM_CSS_CLASS = "ns-android";
@@ -32,18 +33,41 @@ const PORTRAIT_ORIENTATION_CSS_CLASS = "ns-portrait";
3233
const LANDSCAPE_ORIENTATION_CSS_CLASS = "ns-landscape";
3334
const UNKNOWN_ORIENTATION_CSS_CLASS = "ns-unknown";
3435

35-
export function test_root_view_root_css_class() {
36-
const rootViewCssClasses = getRootView().cssClasses;
36+
function _test_root_view_root_css_class(shouldSetClassName: boolean) {
37+
const rootView = getRootView();
38+
if (shouldSetClassName) {
39+
rootView.className = CLASS_NAME;
40+
}
3741

42+
const rootViewCssClasses = rootView.cssClasses;
3843
TKUnit.assertTrue(rootViewCssClasses.has(
3944
ROOT_CSS_CLASS),
4045
`${ROOT_CSS_CLASS} CSS class is missing`
4146
);
47+
48+
if (shouldSetClassName) {
49+
TKUnit.assertTrue(rootViewCssClasses.has(
50+
CLASS_NAME),
51+
`${CLASS_NAME} CSS class is missing`
52+
);
53+
}
4254
}
4355

44-
export function test_root_view_platform_css_class() {
45-
const rootViewCssClasses = getRootView().cssClasses;
56+
export function test_root_view_root_css_class() {
57+
_test_root_view_root_css_class(false);
58+
}
59+
60+
export function test_root_view_class_name_preserve_root_css_class() {
61+
_test_root_view_root_css_class(true);
62+
}
4663

64+
function _test_root_view_platform_css_class(shouldSetClassName: boolean) {
65+
const rootView = getRootView();
66+
if (shouldSetClassName) {
67+
rootView.className = CLASS_NAME;
68+
}
69+
70+
const rootViewCssClasses = rootView.cssClasses;
4771
if (isAndroid) {
4872
TKUnit.assertTrue(rootViewCssClasses.has(
4973
ANDROID_PLATFORM_CSS_CLASS),
@@ -63,10 +87,30 @@ export function test_root_view_platform_css_class() {
6387
`${ANDROID_PLATFORM_CSS_CLASS} CSS class is present`
6488
);
6589
}
90+
91+
if (shouldSetClassName) {
92+
TKUnit.assertTrue(rootViewCssClasses.has(
93+
CLASS_NAME),
94+
`${CLASS_NAME} CSS class is missing`
95+
);
96+
}
6697
}
6798

68-
export function test_root_view_device_type_css_class() {
69-
const rootViewCssClasses = getRootView().cssClasses;
99+
export function test_root_view_platform_css_class() {
100+
_test_root_view_platform_css_class(false);
101+
}
102+
103+
export function test_root_view_class_name_preserve_platform_css_class() {
104+
_test_root_view_platform_css_class(true);
105+
}
106+
107+
function _test_root_view_device_type_css_class(shouldSetClassName: boolean) {
108+
const rootView = getRootView();
109+
if (shouldSetClassName) {
110+
rootView.className = CLASS_NAME;
111+
}
112+
113+
const rootViewCssClasses = rootView.cssClasses;
70114
const deviceType = device.deviceType;
71115

72116
if (deviceType === DeviceType.Phone) {
@@ -88,10 +132,30 @@ export function test_root_view_device_type_css_class() {
88132
`${PHONE_DEVICE_TYPE_CSS_CLASS} CSS class is present`
89133
);
90134
}
135+
136+
if (shouldSetClassName) {
137+
TKUnit.assertTrue(rootViewCssClasses.has(
138+
CLASS_NAME),
139+
`${CLASS_NAME} CSS class is missing`
140+
);
141+
}
91142
}
92143

93-
export function test_root_view_orientation_css_class() {
94-
const rootViewCssClasses = getRootView().cssClasses;
144+
export function test_root_view_device_type_css_class() {
145+
_test_root_view_device_type_css_class(false);
146+
}
147+
148+
export function test_root_view_class_name_preserve_device_type_css_class() {
149+
_test_root_view_device_type_css_class(true);
150+
}
151+
152+
function _test_root_view_orientation_css_class(shouldSetClassName: boolean) {
153+
const rootView = getRootView();
154+
if (shouldSetClassName) {
155+
rootView.className = CLASS_NAME;
156+
}
157+
158+
const rootViewCssClasses = rootView.cssClasses;
95159
let appOrientation;
96160

97161
if (isAndroid) {
@@ -140,9 +204,24 @@ export function test_root_view_orientation_css_class() {
140204
`${PORTRAIT_ORIENTATION_CSS_CLASS} CSS class is present`
141205
);
142206
}
207+
208+
if (shouldSetClassName) {
209+
TKUnit.assertTrue(rootViewCssClasses.has(
210+
CLASS_NAME),
211+
`${CLASS_NAME} CSS class is missing`
212+
);
213+
}
143214
}
144215

145-
export function test_modal_root_view_modal_css_class() {
216+
export function test_root_view_orientation_css_class() {
217+
_test_root_view_orientation_css_class(false);
218+
}
219+
220+
export function test_root_view_class_name_preserve_orientation_css_class() {
221+
_test_root_view_orientation_css_class(true);
222+
}
223+
224+
function _test_modal_root_view_modal_css_class(shouldSetClassName: boolean) {
146225
let modalClosed = false;
147226

148227
const modalCloseCallback = function () {
@@ -153,7 +232,20 @@ export function test_modal_root_view_modal_css_class() {
153232
const page = <Page>args.object;
154233
page.off(View.shownModallyEvent, modalPageShownModallyEventHandler);
155234

156-
TKUnit.assertTrue(_rootModalViews[0].cssClasses.has(MODAL_CSS_CLASS));
235+
const rootModalView = _rootModalViews[0];
236+
if (shouldSetClassName) {
237+
rootModalView.className = CLASS_NAME;
238+
}
239+
240+
const rootModalViewCssClasses = rootModalView.cssClasses;
241+
TKUnit.assertTrue(rootModalViewCssClasses.has(MODAL_CSS_CLASS),
242+
`${MODAL_CSS_CLASS} CSS class is missing`);
243+
244+
if (shouldSetClassName) {
245+
TKUnit.assertTrue(rootModalViewCssClasses.has(CLASS_NAME),
246+
`${CLASS_NAME} CSS class is missing`);
247+
}
248+
157249
args.closeCallback();
158250
};
159251

@@ -186,3 +278,11 @@ export function test_modal_root_view_modal_css_class() {
186278
helper.navigate(hostPageFactory);
187279
TKUnit.waitUntilReady(() => modalClosed);
188280
}
281+
282+
export function test_modal_root_view_modal_css_class() {
283+
_test_modal_root_view_modal_css_class(false);
284+
}
285+
286+
export function test_modal_root_view_class_name_preserve_modal_css_class() {
287+
_test_modal_root_view_modal_css_class(true);
288+
}

tests/app/ui/styling/style-tests.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ export function test_multiple_class_selector() {
225225
let page = helper.getClearCurrentPage();
226226
let btnWithClasses: buttonModule.Button;
227227

228-
page.css = ".style1 { color: red; } .style2 { background-color: blue } ";
228+
page.css = ".style1 { color: red; } .style2 { background-color: blue; } ";
229229

230230
//// Will be styled
231231
btnWithClasses = new buttonModule.Button();
@@ -239,6 +239,24 @@ export function test_multiple_class_selector() {
239239
helper.assertViewBackgroundColor(btnWithClasses, "#0000FF");
240240
}
241241

242+
export function test_class_selector_overwriting() {
243+
const page = helper.getClearCurrentPage();
244+
page.css = ".first { color: red; } .second { background-color: blue; }";
245+
246+
const btnWithClass = new buttonModule.Button();
247+
const stack = new stackModule.StackLayout();
248+
page.content = stack;
249+
stack.addChild(btnWithClass);
250+
251+
btnWithClass.className = "first";
252+
helper.assertViewColor(btnWithClass, "#FF0000");
253+
TKUnit.assert(btnWithClass.style.backgroundColor === undefined, " Background color should not have a value");
254+
255+
btnWithClass.className = "second";
256+
TKUnit.assert(btnWithClass.style.color === undefined, "Color should not have a value");
257+
helper.assertViewBackgroundColor(btnWithClass, "#0000FF");
258+
}
259+
242260
export function test_id_selector() {
243261
let page = helper.getClearCurrentPage();
244262
page.style.color = unsetValue;
@@ -1497,7 +1515,7 @@ export function test_css_calc() {
14971515
TKUnit.assertEqual(stack.width as any, 125, "Stack - width === 125");
14981516

14991517
(stack as any).style = `width: calc(100% / 2)`;
1500-
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 0.5 }, "Stack - width === 50%");
1518+
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 0.5 }, "Stack - width === 50%");
15011519

15021520
// This should log an error for the invalid css-calc expression, but not cause a crash
15031521
stack.className = "invalid-css-calc";
@@ -1568,7 +1586,7 @@ export function test_nested_css_calc() {
15681586

15691587
(stack as any).style = `width: calc(100% * calc(1 / 2)`;
15701588

1571-
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 0.5 }, "Stack - width === 50%");
1589+
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 0.5 }, "Stack - width === 50%");
15721590
}
15731591

15741592
export function test_css_variables() {
@@ -1678,7 +1696,7 @@ export function test_css_calc_and_variables() {
16781696

16791697
// Test setting the CSS variable via the style-attribute, this should override any value set via css-class
16801698
(stack as any).style = `${cssVarName}: 0.5`;
1681-
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 0.5 }, "Stack - width === 50%");
1699+
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 0.5 }, "Stack - width === 50%");
16821700
}
16831701

16841702
export function test_css_variable_fallback() {
@@ -1819,10 +1837,10 @@ export function test_nested_css_calc_and_variables() {
18191837
// Test setting the CSS variable via the style-attribute, this should override any value set via css-class
18201838
stack.className = "wide";
18211839
(stack as any).style = `${cssVarName}: 0.25`;
1822-
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 0.5 }, "Stack - width === 50%");
1840+
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 0.5 }, "Stack - width === 50%");
18231841

18241842
stack.className = "nested";
1825-
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 1 }, "Stack - width === 100%");
1843+
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 1 }, "Stack - width === 100%");
18261844
}
18271845

18281846
export function test_css_variable_is_applied_to_normal_properties() {

tns-core-modules/application/application-common.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ import {
4040
LoadAppCSSEventData,
4141
UnhandledErrorEventData
4242
} from "./application";
43+
44+
import { CLASS_PREFIX, pushToRootViewCssClasses, removeFromRootViewCssClasses } from "../css/system-classes";
4345
import { DeviceOrientation } from "../ui/enums/enums";
4446

4547
export { UnhandledErrorEventData, DiscardedErrorEventData, CssChangedEventData, LoadAppCSSEventData };
@@ -54,11 +56,10 @@ export const uncaughtErrorEvent = "uncaughtError";
5456
export const discardedErrorEvent = "discardedError";
5557
export const orientationChangedEvent = "orientationChanged";
5658

57-
export const CSS_CLASS_PREFIX = "ns-";
5859
const ORIENTATION_CSS_CLASSES = [
59-
`${CSS_CLASS_PREFIX}${DeviceOrientation.portrait}`,
60-
`${CSS_CLASS_PREFIX}${DeviceOrientation.landscape}`,
61-
`${CSS_CLASS_PREFIX}${DeviceOrientation.unknown}`
60+
`${CLASS_PREFIX}${DeviceOrientation.portrait}`,
61+
`${CLASS_PREFIX}${DeviceOrientation.landscape}`,
62+
`${CLASS_PREFIX}${DeviceOrientation.unknown}`
6263
];
6364

6465
let cssFile: string = "./app.css";
@@ -126,9 +127,15 @@ export function loadAppCss(): void {
126127
}
127128

128129
export function orientationChanged(rootView: View, newOrientation: "portrait" | "landscape" | "unknown"): void {
129-
const newOrientationCssClass = `${CSS_CLASS_PREFIX}${newOrientation}`;
130+
const newOrientationCssClass = `${CLASS_PREFIX}${newOrientation}`;
130131
if (!rootView.cssClasses.has(newOrientationCssClass)) {
131-
ORIENTATION_CSS_CLASSES.forEach(c => rootView.cssClasses.delete(c));
132+
const removeCssClass = (c: string) => {
133+
removeFromRootViewCssClasses(c);
134+
rootView.cssClasses.delete(c);
135+
};
136+
137+
ORIENTATION_CSS_CLASSES.forEach(c => removeCssClass(c));
138+
pushToRootViewCssClasses(newOrientationCssClass);
132139
rootView.cssClasses.add(newOrientationCssClass);
133140
rootView._onCssStateChange();
134141
}

tns-core-modules/application/application.d.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,6 @@ export const lowMemoryEvent: string;
5353
*/
5454
export const orientationChangedEvent: string;
5555

56-
/**
57-
* String value "ns-" used for CSS class prefix.
58-
*/
59-
export const CSS_CLASS_PREFIX: string;
60-
6156
/**
6257
* Event data containing information for the application events.
6358
*/

tns-core-modules/application/application.ios.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import {
32
ApplicationEventData,
43
CssChangedEventData,
@@ -9,28 +8,24 @@ import {
98
} from ".";
109

1110
import {
12-
CSS_CLASS_PREFIX, displayedEvent, exitEvent, getCssFileName, launchEvent, livesync,
13-
lowMemoryEvent, notify, on, orientationChanged, orientationChangedEvent, resumeEvent,
14-
setApplication, suspendEvent
11+
displayedEvent, exitEvent, getCssFileName, launchEvent, livesync, lowMemoryEvent, notify, on,
12+
orientationChanged, orientationChangedEvent, resumeEvent, setApplication, suspendEvent
1513
} from "./application-common";
1614

1715
// First reexport so that app module is initialized.
1816
export * from "./application-common";
1917

2018
// TODO: Remove this and get it from global to decouple builder for angular
2119
import { createViewFromEntry } from "../ui/builder";
20+
import { CLASS_PREFIX, getRootViewCssClasses, pushToRootViewCssClasses } from "../css/system-classes";
2221
import { ios as iosView, View } from "../ui/core/view";
2322
import { Frame, NavigationEntry } from "../ui/frame";
2423
import { device } from "../platform/platform";
2524
import { profile } from "../profiling";
2625
import { ios } from "../utils/utils";
2726

28-
const ROOT = "root";
2927
const IOS_PLATFORM = "ios";
30-
const ROOT_VIEW_CSS_CLASSES = [
31-
`${CSS_CLASS_PREFIX}${ROOT}`,
32-
`${CSS_CLASS_PREFIX}${IOS_PLATFORM}`
33-
];
28+
3429
const getVisibleViewController = ios.getVisibleViewController;
3530

3631
// NOTE: UIResponder with implementation of window - related to https://github.com/NativeScript/ios-runtime/issues/430
@@ -317,9 +312,12 @@ function createRootView(v?: View) {
317312
}
318313

319314
const deviceType = device.deviceType.toLowerCase();
320-
ROOT_VIEW_CSS_CLASSES.push(`${CSS_CLASS_PREFIX}${deviceType}`);
321-
ROOT_VIEW_CSS_CLASSES.push(`${CSS_CLASS_PREFIX}${iosApp.orientation}`);
322-
ROOT_VIEW_CSS_CLASSES.forEach(c => rootView.cssClasses.add(c));
315+
pushToRootViewCssClasses(`${CLASS_PREFIX}${IOS_PLATFORM}`);
316+
pushToRootViewCssClasses(`${CLASS_PREFIX}${deviceType}`);
317+
pushToRootViewCssClasses(`${CLASS_PREFIX}${iosApp.orientation}`);
318+
319+
const rootViewCssClasses = getRootViewCssClasses();
320+
rootViewCssClasses.forEach(c => rootView.cssClasses.add(c));
323321

324322
return rootView;
325323
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* @module "system-classes"
3+
*/ /** */
4+
5+
/**
6+
* String value "ns-" used for CSS system class prefix.
7+
*/
8+
export const CLASS_PREFIX: string;
9+
10+
/**
11+
* Gets CSS system class for modal root view.
12+
*/
13+
export function getModalRootViewCssClass(): string;
14+
15+
/**
16+
* Gets CSS system classes for root view.
17+
*/
18+
export function getRootViewCssClasses(): string[];
19+
20+
/**
21+
* * Appends new CSS class to the system classes and returns the new length of the array.
22+
* @param value New CSS system class.
23+
*/
24+
export function pushToRootViewCssClasses(value: string): number;
25+
26+
/**
27+
* Removes CSS class from the system classes and returns it.
28+
* @param value
29+
*/
30+
export function removeFromRootViewCssClasses(value: string): string;

0 commit comments

Comments
 (0)