Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,7 @@ Define style objects. Each key of the object passed to `create` must define a st
{% call macro.prop('flatten', '(styles: Style) => Object') %}
Flatten an array of styles into a single style object. **This is not recommended as it is not compatible with static extraction of styles to CSS.**
{% endcall %}

{% call macro.prop('setCascadeLayer', '(name: string) => void') %}
A web-only method that nests the generated CSS in the [cascade layer](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer) `name`, including the CSS rendered on the server. Call it before rendering. See [Cascade layers]({{ '/docs/styling/#cascade-layers' | url }}).
{% endcall %}
23 changes: 23 additions & 0 deletions packages/react-native-web-docs/src/pages/docs/concepts/styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,26 @@ This ensures that CSS order doesn't impact rendering and CSS rules are efficient
Class names are deterministic, which means that the resulting CSS and HTML is consistent across builds – important for large apps using code-splitting and deploying incremental updates.

At runtime registered styles are resolved to DOM style props and memoized. Any dynamic styles that contain declarations previously registered as static styles can also be converted to CSS class names. Otherwise, they render as inline styles.

### Cascade layers

The generated CSS is not in a [cascade layer](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer), so it takes precedence over every rule an app puts in one. An app that orders its own CSS in layers can nest the generated CSS in a layer and position it among them:

```js
StyleSheet.setCascadeLayer('rnw');
```

```css
@layer rnw, base, components, utilities;
```

The CSS rendered on the server is nested in the same layer. Alternatively, a page can declare the layer in the style sheet's element before {{ site.name }} loads:

```html
<style id="react-native-stylesheet">
@layer rnw {
}
</style>
```

CSS outside any layer, such as a global reset, then takes precedence over every style defined with `StyleSheet`.
Original file line number Diff line number Diff line change
Expand Up @@ -192,5 +192,24 @@ describe('AppRegistry', () => {
const third = getApplicationStyles('App');
expect(third).toEqual(second);
});

test('"getStyleElement" renders the sheet in the cascade layer set for it', () => {
jest.isolateModules(() => {
const IsolatedAppRegistry = require('..');
require('../../StyleSheet').setCascadeLayer('rnw');
IsolatedAppRegistry.registerComponent('App', () => NoopComponent);
const { getStyleElement } = IsolatedAppRegistry.getApplication('App');
const styleElement = ReactDOMServer.renderToStaticMarkup(
getStyleElement()
);

expect(
styleElement.startsWith(
'<style id="react-native-stylesheet">@layer rnw{[stylesheet-group="0"]{}'
)
).toBe(true);
expect(styleElement.endsWith('}</style>')).toBe(true);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Copyright (c) Nicolas Gallagher.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import { findCascadeLayer, wrapInCascadeLayer } from '../dom/cascadeLayer';

const createStyleSheet = (cssText) => {
const element = document.createElement('style');
element.appendChild(document.createTextNode(cssText));
document.head.appendChild(element);
return element.sheet;
};

describe('findCascadeLayer', () => {
test('finds the layer block the page declared', () => {
const sheet = createStyleSheet('@layer rnw{}');
const layer = findCascadeLayer(sheet);
expect(layer).not.toBe(null);
expect(layer.name).toBe('rnw');
expect(layer.rule).toBe(sheet.cssRules[0]);
});

test('reads the name of a nested layer', () => {
const layer = findCascadeLayer(createStyleSheet('@layer framework.rnw{}'));
expect(layer.name).toBe('framework.rnw');
});

test('reads an anonymous layer as an empty name', () => {
const layer = findCascadeLayer(createStyleSheet('@layer{}'));
expect(layer.name).toBe('');
});

test('skips rules that are not layer blocks', () => {
const sheet = createStyleSheet(
'.a{color:red;}@media print{.b{color:red;}}@layer rnw{}'
);
expect(findCascadeLayer(sheet).rule).toBe(sheet.cssRules[2]);
});

test('ignores a layer statement, which groups no rules', () => {
// A CSSLayerStatementRule has no 'insertRule'
const sheet = {
cssRules: [{ cssText: '@layer rnw;' }],
insertRule() {
return 0;
},
deleteRule() {}
};
expect(findCascadeLayer(sheet)).toBe(null);
});

test('finds nothing in an unlayered or absent sheet', () => {
expect(findCascadeLayer(createStyleSheet('.a{color:red;}'))).toBe(null);
expect(findCascadeLayer(null)).toBe(null);
});
});

describe('wrapInCascadeLayer', () => {
test('wraps css text in a layer that is found under the same name', () => {
['rnw', 'framework.rnw', ''].forEach((name) => {
const sheet = createStyleSheet(
wrapInCascadeLayer('.a{opacity:1;}', name)
);
const layer = findCascadeLayer(sheet);
expect(layer.name).toBe(name);
expect(layer.rule.cssRules).toHaveLength(1);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -165,5 +165,21 @@ describe('createOrderedCSSStyleSheet', () => {
.b {color: red;}"
`);
});

test('leaves rules the page declared before the first group to the page', () => {
element.appendChild(
document.createTextNode(
'.page { color: blue }\n[stylesheet-group="0"]{}\n.a { color: red }'
)
);
const clientSheet = createOrderedCSSStyleSheet(element.sheet);
clientSheet.insert('.page { color: red }', 0);
expect(clientSheet.getTextContent()).toMatchInlineSnapshot(`
"[stylesheet-group="0"] {}
.a {color: red;}
.page { color: red }"
`);
expect(element.sheet.cssRules[0].cssText).toBe('.page {color: blue;}');
});
});
});
Loading