jss-plugin-isolate
Version:
True rules isolation through automatic properties reset.
121 lines (99 loc) • 3.23 kB
JavaScript
import inheritedInitials from 'css-initials/inherited'
import allInitials from 'css-initials/all'
const resetSheetOptions = {
meta: 'jss-plugin-isolate',
// Lets make it always the first one in sheets for testing
// and specificity.
index: -Infinity,
link: true
}
const initialsMap = {
inherited: inheritedInitials,
all: allInitials
}
const getStyle = (option = 'inherited') => {
// Option is either "inherited" or "all".
if (typeof option === 'string') return initialsMap[option]
if (typeof option === 'object') {
// Option is ["all/inherited", {...style}]
if (Array.isArray(option)) {
const type = option[0]
const style = option[1]
return {...initialsMap[type], ...style}
}
// Option is a style object, use inherited initials by default.
return {...inheritedInitials, ...option}
}
return inheritedInitials
}
const shouldIsolate = (rule, sheet, options) => {
const {parent} = rule.options
if (parent && (parent.type === 'keyframes' || parent.type === 'conditional')) {
return false
}
let isolate = options.isolate == null ? true : options.isolate
if (sheet.options.isolate != null) isolate = sheet.options.isolate
if (rule.style.isolate != null) {
isolate = rule.style.isolate
delete rule.style.isolate
}
// Option `isolate` may be for e.g. `{isolate: 'root'}`.
// In this case it must match the rule name in order to isolate it.
if (typeof isolate === 'string') {
return isolate === rule.key
}
return isolate
}
/**
* Performance optimized debounce without using setTimeout.
* Returns a function which:
* - will execute the passed fn not more than once per delay
* - will not execute the passed fn if last try was within delay
*/
const createDebounced = (fn, delay = 3) => {
let time = Date.now()
return () => {
const now = Date.now()
if (now - time < delay) return false
time = now
fn()
return true
}
}
export default function jssIsolate(options = {}) {
let setSelectorDone = false
const selectors = []
let resetSheet
let resetRule
const setSelector = () => {
resetRule.selector = selectors.join(',\n')
}
const setSelectorDebounced = createDebounced(setSelector)
function onProcessRule(rule, sheet) {
if (!sheet || sheet === resetSheet || rule.type !== 'style') return
// Type it as a StyleRule
const styleRule = rule
if (!shouldIsolate(styleRule, sheet, options)) return
// Create a reset Style Sheet once and use it for all rules.
if (!resetRule) {
resetSheet = rule.options.jss.createStyleSheet({}, resetSheetOptions)
resetRule = resetSheet.addRule('reset', getStyle(options.reset))
resetSheet.attach()
}
// Add reset rule class name to the classes map of users Style Sheet.
const {selector} = styleRule
if (selectors.indexOf(selector) === -1) {
selectors.push(selector)
setSelectorDone = setSelectorDebounced()
}
}
// We make sure selector is set, because `debaunceMaybe` will not execute
// the fn if called within delay.
function onProcessSheet() {
if (!setSelectorDone && selectors.length) setSelector()
}
return {
onProcessRule,
onProcessSheet
}
}