Refactor how pointerEvents styles are managed

This commit is contained in:
Nicolas Gallagher
2018-01-20 13:30:47 -08:00
parent 73a731f2da
commit 670d43ba04
2 changed files with 42 additions and 52 deletions
+2 -2
View File
@@ -39,7 +39,7 @@ No benchmark will run for more than 20 seconds.
MacBook Pro (13-inch, Early 2011); 2.3 GHz Intel Core i5; 8 GB 1333 MHz DDR3 RAM. Google Chrome 63.
Typical render timings*: mean ± standard deviations.
Typical render timings: mean ± standard deviations.
| Implementation | Mount deep tree (ms) | Mount wide tree (ms) | Dynamic update (ms) |
| :--- | ---: | ---: | ---: |
@@ -66,7 +66,7 @@ Other libraries
Moto G4 (Android 7); Octa-core (4x1.5 GHz & 4x1.2 Ghz); 2 GB RAM. Google Chrome 63
Typical render timings*: mean ± standard deviations.
Typical render timings: mean ± standard deviations.
| Implementation | Mount deep tree (ms) | Mount wide tree (ms) | Dynamic update (ms) |
| :--- | ---: | ---: | ---: |
@@ -21,57 +21,43 @@ const createClassName = (prop, value) => {
return process.env.NODE_ENV !== 'production' ? `rn-${prop}-${hashed}` : `rn-${hashed}`;
};
const createCssRule = (className, prop, value) => {
const css = generateCss({ [prop]: value });
const selector = `.${className}`;
return `${selector}{${css}}`;
};
const createCssRules = (selector, prop, value) => {
const rules = [];
let v = value;
const pointerEvents = {
auto: createClassName('pointerEvents', 'auto'),
boxNone: createClassName('pointerEvents', 'box-none'),
boxOnly: createClassName('pointerEvents', 'box-only'),
none: createClassName('pointerEvents', 'none')
// pointerEvents is a special case that requires custom values and additional css rules
if (prop === 'pointerEvents') {
if (value === 'auto' || value === 'box-only') {
v = 'auto !important';
if (value === 'box-only') {
const css = generateCss({ [prop]: 'none' });
rules.push(`${selector} > *{${css}}`);
}
} else if (value === 'none' || value === 'box-none') {
v = 'none !important';
if (value === 'box-none') {
const css = generateCss({ [prop]: 'auto' });
rules.push(`${selector} > *{${css}}`);
}
}
}
const css = generateCss({ [prop]: v });
rules.push(`${selector}{${css}}`);
return rules;
};
// See #513
const pointerEventsCss =
`.${pointerEvents.auto}{pointer-events:auto !important;}\n` +
`.${pointerEvents.boxOnly}{pointer-events:auto !important;}\n` +
`.${pointerEvents.none}{pointer-events:none !important;}\n` +
`.${pointerEvents.boxNone}{pointer-events:none !important;}\n` +
`.${pointerEvents.boxNone} > *{pointer-events:auto;}\n` +
`.${pointerEvents.boxOnly} > *{pointer-events:none;}`;
export default class StyleSheetManager {
cache = null;
mainSheet = null;
constructor() {
// custom pointer event values are implemented using descendent selectors,
// so we manually create the CSS and pre-register the declarations
const pointerEventsPropName = 'pointerEvents';
this.cache = {
byClassName: {
[pointerEvents.auto]: { prop: pointerEventsPropName, value: 'auto' },
[pointerEvents.boxNone]: {
prop: pointerEventsPropName,
value: 'box-none'
},
[pointerEvents.boxOnly]: {
prop: pointerEventsPropName,
value: 'box-only'
},
[pointerEvents.none]: { prop: pointerEventsPropName, value: 'none' }
},
byProp: {
pointerEvents: {
auto: pointerEvents.auto,
'box-none': pointerEvents.boxNone,
'box-only': pointerEvents.boxOnly,
none: pointerEvents.none
}
}
byClassName: {},
byProp: {}
};
// on the client we check for an existing style sheet before injecting style sheets
@@ -84,6 +70,11 @@ export default class StyleSheetManager {
this.mainSheet = document.getElementById(STYLE_ELEMENT_ID);
}
}
// need to pre-register pointerEvents as they have no inline-style equivalent
['box-only', 'box-none', 'auto', 'none'].forEach(v => {
this.setDeclaration('pointerEvents', v);
});
}
getClassName(prop, value) {
@@ -110,13 +101,12 @@ export default class StyleSheetManager {
const mainSheetTextContext = Object.keys(cache)
.reduce((rules, prop) => {
if (prop !== 'pointerEvents') {
Object.keys(cache[prop]).forEach(value => {
const className = this.getClassName(prop, value);
const rule = createCssRule(className, prop, value);
rules.push(rule);
});
}
Object.keys(cache[prop]).forEach(value => {
const className = this.getClassName(prop, value);
const moreRules = createCssRules(`.${className}`, prop, value);
rules.push(...moreRules);
});
return rules;
}, [])
.join('\n');
@@ -124,7 +114,7 @@ export default class StyleSheetManager {
return [
{
id: 'react-native-stylesheet-static',
textContent: `${staticCss}\n${pointerEventsCss}`
textContent: `${staticCss}`
},
{
id: STYLE_ELEMENT_ID,
@@ -142,8 +132,8 @@ export default class StyleSheetManager {
const sheet = this.mainSheet.sheet;
// avoid injecting if the rule already exists (e.g., server rendered, hot reload)
if (this.mainSheet.textContent.indexOf(className) === -1) {
const rule = createCssRule(className, prop, value);
sheet.insertRule(rule, sheet.cssRules.length);
const rules = createCssRules(`.${className}`, prop, value);
rules.forEach(rule => sheet.insertRule(rule, sheet.cssRules.length));
}
}
}