@mattsjones/css-core
Advanced tools
Comparing version 0.0.13 to 0.0.14
# @mattsjones/css-core | ||
## 0.0.14 | ||
### Patch Changes | ||
- 3d39457: Remove lodash from core | ||
- 9b1bf7e: Add 'keyframes' and 'globalKeyframes' functions | ||
- 08c2b1f: Add 'fontFace' and 'globalFontFace' functions | ||
- bad1361: Remove PostCSS from core | ||
- 4360915: Add 'fallbackVar' function | ||
## 0.0.13 | ||
@@ -4,0 +14,0 @@ |
@@ -1,8 +0,10 @@ | ||
import type { GlobalStyleRule, StyleRule } from './types'; | ||
declare type MapLeafNodes<Obj, LeafType> = { | ||
[Prop in keyof Obj]: Obj[Prop] extends Record<string | number, any> ? MapLeafNodes<Obj[Prop], LeafType> : LeafType; | ||
}; | ||
import type { GlobalStyleRule, StyleRule, FontFaceRule, CSSKeyframes, MapLeafNodes } from './types'; | ||
export declare function createVar(debugId?: string): string; | ||
export declare function fallbackVar(...values: [...Array<string>, string | number]): string; | ||
export declare function style(rule: StyleRule, debugId?: string): string; | ||
export declare function globalStyle(selector: string, rule: GlobalStyleRule): void; | ||
export declare function fontFace(rule: FontFaceRule, debugId?: string): string; | ||
export declare function globalFontFace(fontFamily: string, rule: FontFaceRule): void; | ||
export declare function keyframes(rule: CSSKeyframes, debugId?: string): string; | ||
export declare function globalKeyframes(name: string, rule: CSSKeyframes): void; | ||
export declare function mapToStyles<StyleMap extends Record<string | number, StyleRule>>(styleMap: StyleMap, debugId?: string): Record<keyof StyleMap, string>; | ||
@@ -9,0 +11,0 @@ export declare function mapToStyles<Data extends Record<string | number, unknown>>(data: Data, mapData: <Key extends keyof Data>(value: Data[Key], key: Key) => StyleRule, debugId?: string): Record<keyof Data, string>; |
@@ -8,3 +8,3 @@ import type { CSS } from './types'; | ||
} | ||
export declare function transformCss({ localClassNames, cssObjs }: TransformCSSParams): any; | ||
export declare function transformCss({ localClassNames, cssObjs }: TransformCSSParams): string[]; | ||
export {}; |
@@ -1,2 +0,2 @@ | ||
import type { PropertiesFallback } from 'csstype'; | ||
import type { PropertiesFallback, AtRule } from 'csstype'; | ||
import type { SimplePseudos } from './transformCSS'; | ||
@@ -11,3 +11,2 @@ declare type BasicCSSProperties = PropertiesFallback<string | number>; | ||
}; | ||
'@keyframes'?: CSSKeyframes | string; | ||
}; | ||
@@ -36,7 +35,19 @@ declare type PseudoProperties = { | ||
export declare type GlobalStyleRule = CSSProperties & MediaQueries<CSSProperties & FeatureQueries<CSSProperties>> & FeatureQueries<CSSProperties & MediaQueries<CSSProperties>>; | ||
export interface CSS { | ||
export declare type GlobalFontFaceRule = Omit<AtRule.FontFaceFallback, 'src'> & Required<Pick<AtRule.FontFaceFallback, 'src'>>; | ||
export declare type FontFaceRule = Omit<GlobalFontFaceRule, 'fontFamily'>; | ||
export declare type CSSStyleBlock = { | ||
type: 'local' | 'global'; | ||
selector: string; | ||
rule: StyleRule; | ||
} | ||
}; | ||
export declare type CSSFontFaceBlock = { | ||
type: 'fontFace'; | ||
rule: GlobalFontFaceRule; | ||
}; | ||
export declare type CSSKeyframesBlock = { | ||
type: 'keyframes'; | ||
name: string; | ||
rule: CSSKeyframes; | ||
}; | ||
export declare type CSS = CSSStyleBlock | CSSFontFaceBlock | CSSKeyframesBlock; | ||
export interface Adapter { | ||
@@ -47,2 +58,5 @@ appendCss: (css: CSS, fileScope: string) => void; | ||
} | ||
export declare type MapLeafNodes<Obj, LeafType> = { | ||
[Prop in keyof Obj]: Obj[Prop] extends Record<string | number, any> ? MapLeafNodes<Obj[Prop], LeafType> : LeafType; | ||
}; | ||
export {}; |
@@ -5,23 +5,286 @@ 'use strict'; | ||
var generateCss_dist_mattsjonesCssCoreGenerateCss = require('../generateCss/dist/mattsjones-css-core-generateCss.browser.cjs.js'); | ||
var cssesc = require('cssesc'); | ||
var utils = require('./utils-655d9a10.browser.cjs.js'); | ||
var adapter_dist_mattsjonesCssCoreAdapter = require('../adapter/dist/mattsjones-css-core-adapter.browser.cjs.js'); | ||
var hash = require('@emotion/hash'); | ||
var get = require('lodash/get'); | ||
var cssesc = require('cssesc'); | ||
var dedent = require('dedent'); | ||
var fileScope_dist_mattsjonesCssCoreFileScope = require('../fileScope/dist/mattsjones-css-core-fileScope.browser.cjs.js'); | ||
require('postcss-js'); | ||
require('postcss'); | ||
require('lodash/each'); | ||
require('lodash/omit'); | ||
require('lodash/isEqual'); | ||
require('lodash/mapKeys'); | ||
require('css-selector-parser'); | ||
require('dedent'); | ||
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; } | ||
var cssesc__default = /*#__PURE__*/_interopDefault(cssesc); | ||
var hash__default = /*#__PURE__*/_interopDefault(hash); | ||
var get__default = /*#__PURE__*/_interopDefault(get); | ||
var cssesc__default = /*#__PURE__*/_interopDefault(cssesc); | ||
var dedent__default = /*#__PURE__*/_interopDefault(dedent); | ||
const UNITLESS = { | ||
boxFlex: true, | ||
boxFlexGroup: true, | ||
columnCount: true, | ||
flex: true, | ||
flexGrow: true, | ||
flexPositive: true, | ||
flexShrink: true, | ||
flexNegative: true, | ||
fontWeight: true, | ||
lineClamp: true, | ||
lineHeight: true, | ||
opacity: true, | ||
order: true, | ||
orphans: true, | ||
tabSize: true, | ||
widows: true, | ||
zIndex: true, | ||
zoom: true, | ||
fillOpacity: true, | ||
strokeDashoffset: true, | ||
strokeOpacity: true, | ||
strokeWidth: true | ||
}; | ||
const simplePseudos = [':-moz-any-link', ':-moz-full-screen', ':-moz-placeholder', ':-moz-read-only', ':-moz-read-write', ':-ms-fullscreen', ':-ms-input-placeholder', ':-webkit-any-link', ':-webkit-full-screen', '::-moz-placeholder', '::-moz-progress-bar', '::-moz-range-progress', '::-moz-range-thumb', '::-moz-range-track', '::-moz-selection', '::-ms-backdrop', '::-ms-browse', '::-ms-check', '::-ms-clear', '::-ms-fill', '::-ms-fill-lower', '::-ms-fill-upper', '::-ms-reveal', '::-ms-thumb', '::-ms-ticks-after', '::-ms-ticks-before', '::-ms-tooltip', '::-ms-track', '::-ms-value', '::-webkit-backdrop', '::-webkit-input-placeholder', '::-webkit-progress-bar', '::-webkit-progress-inner-value', '::-webkit-progress-value', '::-webkit-slider-runnable-track', '::-webkit-slider-thumb', '::after', '::backdrop', '::before', '::cue', '::first-letter', '::first-line', '::grammar-error', '::placeholder', '::selection', '::spelling-error', ':active', ':after', ':any-link', ':before', ':blank', ':checked', ':default', ':defined', ':disabled', ':empty', ':enabled', ':first', ':first-child', ':first-letter', ':first-line', ':first-of-type', ':focus', ':focus-visible', ':focus-within', ':fullscreen', ':hover', ':in-range', ':indeterminate', ':invalid', ':last-child', ':last-of-type', ':left', ':link', ':only-child', ':only-of-type', ':optional', ':out-of-range', ':placeholder-shown', ':read-only', ':read-write', ':required', ':right', ':root', ':scope', ':target', ':valid', ':visited']; | ||
function dashify(str) { | ||
return str.replace(/([A-Z])/g, '-$1').replace(/^ms-/, '-ms-').toLowerCase(); | ||
} | ||
const DOUBLE_SPACE = ' '; | ||
const simplePseudoSet = new Set(simplePseudos); | ||
const specialKeys = [...simplePseudos, '@media', '@supports', 'selectors']; | ||
class Stylesheet { | ||
constructor(localClassNames) { | ||
this.rules = []; | ||
this.conditionalRules = []; | ||
this.fontFaceRules = []; | ||
this.keyframesRules = []; | ||
this.localClassNameRegex = localClassNames.length > 0 ? RegExp(`(${localClassNames.join('|')})`, 'g') : null; | ||
} | ||
processCssObj(root) { | ||
if (root.type === 'fontFace') { | ||
this.fontFaceRules.push(root.rule); | ||
return; | ||
} | ||
if (root.type === 'keyframes') { | ||
this.keyframesRules.push(root); | ||
return; | ||
} // Add main styles | ||
const mainRule = utils.omit(root.rule, specialKeys); | ||
this.addRule({ | ||
selector: root.selector, | ||
rule: mainRule | ||
}); | ||
this.transformSimplePsuedos(root, root.rule); | ||
this.transformMedia(root, root.rule['@media']); | ||
this.transformSupports(root, root.rule['@supports']); | ||
this.transformSelectors(root, root.rule); | ||
} | ||
addRule(cssRule) { | ||
// Run `pixelifyProperties` before `transformVars` as we don't want to pixelify CSS Vars | ||
const rule = this.transformVars(this.pixelifyProperties(cssRule.rule)); | ||
const selector = this.transformSelector(cssRule.selector); | ||
if (cssRule.conditions) { | ||
this.conditionalRules.push({ | ||
selector, | ||
rule, | ||
conditions: cssRule.conditions.sort() | ||
}); | ||
} else { | ||
this.rules.push({ | ||
selector, | ||
rule | ||
}); | ||
} | ||
} | ||
pixelifyProperties(cssRule) { | ||
utils.forEach(cssRule, (value, key) => { | ||
if (typeof value === 'number' && value !== 0 && !UNITLESS[key]) { | ||
// @ts-expect-error Any ideas? | ||
cssRule[key] = `${value}px`; | ||
} | ||
}); | ||
return cssRule; | ||
} | ||
transformVars({ | ||
vars, | ||
...rest | ||
}) { | ||
if (!vars) { | ||
return rest; | ||
} | ||
return { ...utils.mapKeys(vars, (_value, key) => { | ||
const matches = key.match(/^var\((.*)\)$/); | ||
if (matches) { | ||
return matches[1]; | ||
} | ||
return key; | ||
}), | ||
...rest | ||
}; | ||
} | ||
transformSelector(selector) { | ||
return this.localClassNameRegex ? selector.replace(this.localClassNameRegex, (_, className, index) => { | ||
if (index > 0 && selector[index - 1] === '.') { | ||
return className; | ||
} | ||
return `.${cssesc__default['default'](className, { | ||
isIdentifier: true | ||
})}`; | ||
}) : selector; | ||
} | ||
transformSelectors(root, rule, conditions) { | ||
utils.forEach(rule.selectors, (selectorRule, selector) => { | ||
if (root.type === 'global') { | ||
throw new Error('Selectors are not allowed within globalStyle'); | ||
} | ||
const transformedSelector = this.transformSelector(selector.replace(RegExp('&', 'g'), root.selector)); | ||
utils.validateSelector(transformedSelector, root.selector); | ||
this.addRule({ | ||
conditions, | ||
selector: transformedSelector, | ||
rule: selectorRule | ||
}); | ||
}); | ||
} | ||
transformMedia(root, rules, parentConditions = []) { | ||
utils.forEach(rules, (mediaRule, query) => { | ||
const conditions = [`@media ${query}`, ...parentConditions]; | ||
this.addRule({ | ||
conditions, | ||
selector: root.selector, | ||
rule: utils.omit(mediaRule, specialKeys) | ||
}); | ||
this.transformSimplePsuedos(root, mediaRule, conditions); | ||
this.transformSelectors(root, mediaRule, conditions); | ||
this.transformSupports(root, mediaRule['@supports'], conditions); | ||
}); | ||
} | ||
transformSupports(root, rules, parentConditions = []) { | ||
utils.forEach(rules, (supportsRule, query) => { | ||
const conditions = [`@supports ${query}`, ...parentConditions]; | ||
this.addRule({ | ||
conditions, | ||
selector: root.selector, | ||
rule: utils.omit(supportsRule, specialKeys) | ||
}); | ||
this.transformSimplePsuedos(root, supportsRule, conditions); | ||
this.transformSelectors(root, supportsRule, conditions); | ||
this.transformMedia(root, supportsRule['@media'], conditions); | ||
}); | ||
} | ||
transformSimplePsuedos(root, rule, conditions) { | ||
for (const key of Object.keys(rule)) { | ||
// Process simple psuedos | ||
if (simplePseudoSet.has(key)) { | ||
if (root.type === 'global') { | ||
throw new Error('Simple pseudos are not valid in globalStyles'); | ||
} | ||
this.addRule({ | ||
conditions, | ||
selector: `${root.selector}${key}`, | ||
rule: rule[key] | ||
}); | ||
} | ||
} | ||
} | ||
toPostcssJs() { | ||
const styles = {}; | ||
if (this.fontFaceRules.length > 0) { | ||
styles['@font-face'] = this.fontFaceRules; | ||
} | ||
this.keyframesRules.forEach(rule => { | ||
styles[`@keyframes ${rule.name}`] = rule.rule; | ||
}); | ||
for (const rule of [...this.rules, ...this.conditionalRules]) { | ||
if (rule.conditions && utils.isEqual(styles[rule.selector], rule.rule)) { | ||
// Ignore conditional rules if they are identical to a non-conditional rule | ||
continue; | ||
} | ||
if (Object.keys(rule.rule).length === 0) { | ||
// Ignore empty rules | ||
continue; | ||
} | ||
let styleNode = styles; | ||
for (const condition of rule.conditions || []) { | ||
if (!styleNode[condition]) { | ||
styleNode[condition] = {}; | ||
} | ||
styleNode = styleNode[condition]; | ||
} | ||
styleNode[rule.selector] = { ...styleNode[rule.selector], | ||
...rule.rule | ||
}; | ||
} | ||
return styles; | ||
} | ||
toCss() { | ||
const styles = this.toPostcssJs(); | ||
function walkCss(v, indent = '') { | ||
const rules = []; | ||
for (const key of Object.keys(v)) { | ||
const value = v[key]; | ||
if (value && Array.isArray(value)) { | ||
rules.push(...value.map(v => walkCss({ | ||
[key]: v | ||
}, indent).join('\n'))); | ||
} else if (value && typeof value === 'object') { | ||
rules.push(`${indent}${key} {\n${walkCss(value, indent + DOUBLE_SPACE).join('\n')}\n${indent}}\n`); | ||
} else { | ||
rules.push(`${indent}${key.startsWith('--') ? key : dashify(key)}: ${value};`); | ||
} | ||
} | ||
return rules; | ||
} | ||
return walkCss(styles); | ||
} | ||
} | ||
function transformCss({ | ||
localClassNames, | ||
cssObjs | ||
}) { | ||
const stylesheet = new Stylesheet(localClassNames); | ||
for (const root of cssObjs) { | ||
stylesheet.processCssObj(root); | ||
} | ||
return stylesheet.toCss(); | ||
} | ||
let styleSheet; | ||
@@ -55,3 +318,3 @@ const localClassNames = new Set(); | ||
onEndFileScope: () => { | ||
const css = generateCss_dist_mattsjonesCssCoreGenerateCss.generateCss({ | ||
const css = transformCss({ | ||
localClassNames: Array.from(localClassNames), | ||
@@ -85,28 +348,8 @@ cssObjs: bufferedCSSObjs | ||
const generateClassName = debugId => { | ||
const generateIdentifier = debugId => { | ||
const refCount = fileScope_dist_mattsjonesCssCoreFileScope.getAndIncrementRefCounter(); | ||
const className = process.env.NODE_ENV !== 'production' && debugId ? `${getShortFileName()}_${debugId}__${hash__default['default'](fileScope_dist_mattsjonesCssCoreFileScope.getFileScope())}${refCount}` : `${hash__default['default'](fileScope_dist_mattsjonesCssCoreFileScope.getFileScope())}${refCount}`; | ||
return className.match(/^[0-9]/) ? `_${className}` : className; | ||
const identifier = process.env.NODE_ENV !== 'production' && debugId ? `${getShortFileName()}_${debugId}__${hash__default['default'](fileScope_dist_mattsjonesCssCoreFileScope.getFileScope())}${refCount}` : `${hash__default['default'](fileScope_dist_mattsjonesCssCoreFileScope.getFileScope())}${refCount}`; | ||
return identifier.match(/^[0-9]/) ? `_${identifier}` : identifier; | ||
}; | ||
const walkObject = (obj, fn, path = []) => { | ||
// @ts-expect-error | ||
const clone = obj.constructor(); | ||
for (let key in obj) { | ||
const value = obj[key]; | ||
const currentPath = [...path, key]; | ||
if (typeof value === 'object') { | ||
clone[key] = value ? walkObject(value, fn, currentPath) : value; | ||
} else if (typeof value === 'string' || typeof value === 'number') { | ||
clone[key] = fn(value, currentPath); | ||
} else { | ||
console.warn(`Skipping invalid key "${currentPath.join('.')}". Should be a string, number or object. Received: "${typeof value}"`); | ||
} | ||
} | ||
return clone; | ||
}; | ||
function createVar(debugId) { | ||
@@ -122,4 +365,19 @@ const refCount = fileScope_dist_mattsjonesCssCoreFileScope.getAndIncrementRefCounter(); | ||
} | ||
function fallbackVar(...values) { | ||
let finalValue = ''; | ||
values.reverse().forEach(value => { | ||
if (finalValue === '') { | ||
finalValue = String(value); | ||
} else { | ||
if (typeof value !== 'string' || !/^var\(--.*\)$/.test(value)) { | ||
throw new Error(`Invalid variable name: ${value}`); | ||
} | ||
finalValue = value.replace(/\)$/, `, ${finalValue})`); | ||
} | ||
}); | ||
return finalValue; | ||
} | ||
function style(rule, debugId) { | ||
const className = generateClassName(debugId); | ||
const className = generateIdentifier(debugId); | ||
adapter_dist_mattsjonesCssCoreAdapter.registerClassName(className); | ||
@@ -140,2 +398,49 @@ adapter_dist_mattsjonesCssCoreAdapter.appendCss({ | ||
} | ||
function fontFace(rule, debugId) { | ||
const fontFamily = `"${cssesc__default['default'](generateIdentifier(debugId), { | ||
quotes: 'double' | ||
})}"`; | ||
if ('fontFamily' in rule) { | ||
throw new Error(dedent__default['default']` | ||
This function creates and returns a hashed font-family name, so the "fontFamily" property should not be provided. | ||
If you'd like to define a globally scoped custom font, you can use the "globalFontFace" function instead. | ||
`); | ||
} | ||
adapter_dist_mattsjonesCssCoreAdapter.appendCss({ | ||
type: 'fontFace', | ||
rule: { ...rule, | ||
fontFamily | ||
} | ||
}, fileScope_dist_mattsjonesCssCoreFileScope.getFileScope()); | ||
return fontFamily; | ||
} | ||
function globalFontFace(fontFamily, rule) { | ||
adapter_dist_mattsjonesCssCoreAdapter.appendCss({ | ||
type: 'fontFace', | ||
rule: { ...rule, | ||
fontFamily | ||
} | ||
}, fileScope_dist_mattsjonesCssCoreFileScope.getFileScope()); | ||
} | ||
function keyframes(rule, debugId) { | ||
const name = cssesc__default['default'](generateIdentifier(debugId), { | ||
isIdentifier: true | ||
}); | ||
adapter_dist_mattsjonesCssCoreAdapter.appendCss({ | ||
type: 'keyframes', | ||
name, | ||
rule | ||
}, fileScope_dist_mattsjonesCssCoreFileScope.getFileScope()); | ||
return name; | ||
} | ||
function globalKeyframes(name, rule) { | ||
adapter_dist_mattsjonesCssCoreAdapter.appendCss({ | ||
type: 'keyframes', | ||
name, | ||
rule | ||
}, fileScope_dist_mattsjonesCssCoreFileScope.getFileScope()); | ||
} | ||
function mapToStyles(...args) { | ||
@@ -166,3 +471,3 @@ if (typeof args[1] === 'function') { | ||
function createThemeVars(themeContract) { | ||
return walkObject(themeContract, (_value, path) => { | ||
return utils.walkObject(themeContract, (_value, path) => { | ||
return createVar(path.join('-')); | ||
@@ -178,4 +483,4 @@ }); | ||
walkObject(tokens, (value, path) => { | ||
varSetters[get__default['default'](varContract, path)] = value; | ||
utils.walkObject(tokens, (value, path) => { | ||
varSetters[utils.get(varContract, path)] = value; | ||
}); | ||
@@ -201,3 +506,3 @@ return varSetters; | ||
function createTheme(arg1, arg2, arg3) { | ||
const themeClassName = generateClassName(typeof arg2 === 'object' ? arg3 : arg2); | ||
const themeClassName = generateIdentifier(typeof arg2 === 'object' ? arg3 : arg2); | ||
adapter_dist_mattsjonesCssCoreAdapter.registerClassName(themeClassName); | ||
@@ -214,4 +519,4 @@ const vars = typeof arg2 === 'object' ? createGlobalTheme(themeClassName, arg1, arg2) : createGlobalTheme(themeClassName, arg1); | ||
walkObject(tokens, (value, path) => { | ||
const varName = get__default['default'](themeVars, path); | ||
utils.walkObject(tokens, (value, path) => { | ||
const varName = utils.get(themeVars, path); | ||
styles[varName.substring(4, varName.length - 1)] = String(value); | ||
@@ -238,4 +543,9 @@ }); | ||
exports.createVar = createVar; | ||
exports.fallbackVar = fallbackVar; | ||
exports.fontFace = fontFace; | ||
exports.globalFontFace = globalFontFace; | ||
exports.globalKeyframes = globalKeyframes; | ||
exports.globalStyle = globalStyle; | ||
exports.keyframes = keyframes; | ||
exports.mapToStyles = mapToStyles; | ||
exports.style = style; |
@@ -1,16 +0,279 @@ | ||
import { generateCss } from '../generateCss/dist/mattsjones-css-core-generateCss.browser.esm.js'; | ||
import cssesc from 'cssesc'; | ||
import { o as omit, f as forEach, m as mapKeys, v as validateSelector, i as isEqual, w as walkObject, g as get } from './utils-cba7c8d6.browser.esm.js'; | ||
import { registerClassName, appendCss, setAdapter } from '../adapter/dist/mattsjones-css-core-adapter.browser.esm.js'; | ||
import hash from '@emotion/hash'; | ||
import get from 'lodash/get'; | ||
import cssesc from 'cssesc'; | ||
import dedent from 'dedent'; | ||
import { getAndIncrementRefCounter, getFileScope } from '../fileScope/dist/mattsjones-css-core-fileScope.browser.esm.js'; | ||
import 'postcss-js'; | ||
import 'postcss'; | ||
import 'lodash/each'; | ||
import 'lodash/omit'; | ||
import 'lodash/isEqual'; | ||
import 'lodash/mapKeys'; | ||
import 'css-selector-parser'; | ||
import 'dedent'; | ||
const UNITLESS = { | ||
boxFlex: true, | ||
boxFlexGroup: true, | ||
columnCount: true, | ||
flex: true, | ||
flexGrow: true, | ||
flexPositive: true, | ||
flexShrink: true, | ||
flexNegative: true, | ||
fontWeight: true, | ||
lineClamp: true, | ||
lineHeight: true, | ||
opacity: true, | ||
order: true, | ||
orphans: true, | ||
tabSize: true, | ||
widows: true, | ||
zIndex: true, | ||
zoom: true, | ||
fillOpacity: true, | ||
strokeDashoffset: true, | ||
strokeOpacity: true, | ||
strokeWidth: true | ||
}; | ||
const simplePseudos = [':-moz-any-link', ':-moz-full-screen', ':-moz-placeholder', ':-moz-read-only', ':-moz-read-write', ':-ms-fullscreen', ':-ms-input-placeholder', ':-webkit-any-link', ':-webkit-full-screen', '::-moz-placeholder', '::-moz-progress-bar', '::-moz-range-progress', '::-moz-range-thumb', '::-moz-range-track', '::-moz-selection', '::-ms-backdrop', '::-ms-browse', '::-ms-check', '::-ms-clear', '::-ms-fill', '::-ms-fill-lower', '::-ms-fill-upper', '::-ms-reveal', '::-ms-thumb', '::-ms-ticks-after', '::-ms-ticks-before', '::-ms-tooltip', '::-ms-track', '::-ms-value', '::-webkit-backdrop', '::-webkit-input-placeholder', '::-webkit-progress-bar', '::-webkit-progress-inner-value', '::-webkit-progress-value', '::-webkit-slider-runnable-track', '::-webkit-slider-thumb', '::after', '::backdrop', '::before', '::cue', '::first-letter', '::first-line', '::grammar-error', '::placeholder', '::selection', '::spelling-error', ':active', ':after', ':any-link', ':before', ':blank', ':checked', ':default', ':defined', ':disabled', ':empty', ':enabled', ':first', ':first-child', ':first-letter', ':first-line', ':first-of-type', ':focus', ':focus-visible', ':focus-within', ':fullscreen', ':hover', ':in-range', ':indeterminate', ':invalid', ':last-child', ':last-of-type', ':left', ':link', ':only-child', ':only-of-type', ':optional', ':out-of-range', ':placeholder-shown', ':read-only', ':read-write', ':required', ':right', ':root', ':scope', ':target', ':valid', ':visited']; | ||
function dashify(str) { | ||
return str.replace(/([A-Z])/g, '-$1').replace(/^ms-/, '-ms-').toLowerCase(); | ||
} | ||
const DOUBLE_SPACE = ' '; | ||
const simplePseudoSet = new Set(simplePseudos); | ||
const specialKeys = [...simplePseudos, '@media', '@supports', 'selectors']; | ||
class Stylesheet { | ||
constructor(localClassNames) { | ||
this.rules = []; | ||
this.conditionalRules = []; | ||
this.fontFaceRules = []; | ||
this.keyframesRules = []; | ||
this.localClassNameRegex = localClassNames.length > 0 ? RegExp(`(${localClassNames.join('|')})`, 'g') : null; | ||
} | ||
processCssObj(root) { | ||
if (root.type === 'fontFace') { | ||
this.fontFaceRules.push(root.rule); | ||
return; | ||
} | ||
if (root.type === 'keyframes') { | ||
this.keyframesRules.push(root); | ||
return; | ||
} // Add main styles | ||
const mainRule = omit(root.rule, specialKeys); | ||
this.addRule({ | ||
selector: root.selector, | ||
rule: mainRule | ||
}); | ||
this.transformSimplePsuedos(root, root.rule); | ||
this.transformMedia(root, root.rule['@media']); | ||
this.transformSupports(root, root.rule['@supports']); | ||
this.transformSelectors(root, root.rule); | ||
} | ||
addRule(cssRule) { | ||
// Run `pixelifyProperties` before `transformVars` as we don't want to pixelify CSS Vars | ||
const rule = this.transformVars(this.pixelifyProperties(cssRule.rule)); | ||
const selector = this.transformSelector(cssRule.selector); | ||
if (cssRule.conditions) { | ||
this.conditionalRules.push({ | ||
selector, | ||
rule, | ||
conditions: cssRule.conditions.sort() | ||
}); | ||
} else { | ||
this.rules.push({ | ||
selector, | ||
rule | ||
}); | ||
} | ||
} | ||
pixelifyProperties(cssRule) { | ||
forEach(cssRule, (value, key) => { | ||
if (typeof value === 'number' && value !== 0 && !UNITLESS[key]) { | ||
// @ts-expect-error Any ideas? | ||
cssRule[key] = `${value}px`; | ||
} | ||
}); | ||
return cssRule; | ||
} | ||
transformVars({ | ||
vars, | ||
...rest | ||
}) { | ||
if (!vars) { | ||
return rest; | ||
} | ||
return { ...mapKeys(vars, (_value, key) => { | ||
const matches = key.match(/^var\((.*)\)$/); | ||
if (matches) { | ||
return matches[1]; | ||
} | ||
return key; | ||
}), | ||
...rest | ||
}; | ||
} | ||
transformSelector(selector) { | ||
return this.localClassNameRegex ? selector.replace(this.localClassNameRegex, (_, className, index) => { | ||
if (index > 0 && selector[index - 1] === '.') { | ||
return className; | ||
} | ||
return `.${cssesc(className, { | ||
isIdentifier: true | ||
})}`; | ||
}) : selector; | ||
} | ||
transformSelectors(root, rule, conditions) { | ||
forEach(rule.selectors, (selectorRule, selector) => { | ||
if (root.type === 'global') { | ||
throw new Error('Selectors are not allowed within globalStyle'); | ||
} | ||
const transformedSelector = this.transformSelector(selector.replace(RegExp('&', 'g'), root.selector)); | ||
validateSelector(transformedSelector, root.selector); | ||
this.addRule({ | ||
conditions, | ||
selector: transformedSelector, | ||
rule: selectorRule | ||
}); | ||
}); | ||
} | ||
transformMedia(root, rules, parentConditions = []) { | ||
forEach(rules, (mediaRule, query) => { | ||
const conditions = [`@media ${query}`, ...parentConditions]; | ||
this.addRule({ | ||
conditions, | ||
selector: root.selector, | ||
rule: omit(mediaRule, specialKeys) | ||
}); | ||
this.transformSimplePsuedos(root, mediaRule, conditions); | ||
this.transformSelectors(root, mediaRule, conditions); | ||
this.transformSupports(root, mediaRule['@supports'], conditions); | ||
}); | ||
} | ||
transformSupports(root, rules, parentConditions = []) { | ||
forEach(rules, (supportsRule, query) => { | ||
const conditions = [`@supports ${query}`, ...parentConditions]; | ||
this.addRule({ | ||
conditions, | ||
selector: root.selector, | ||
rule: omit(supportsRule, specialKeys) | ||
}); | ||
this.transformSimplePsuedos(root, supportsRule, conditions); | ||
this.transformSelectors(root, supportsRule, conditions); | ||
this.transformMedia(root, supportsRule['@media'], conditions); | ||
}); | ||
} | ||
transformSimplePsuedos(root, rule, conditions) { | ||
for (const key of Object.keys(rule)) { | ||
// Process simple psuedos | ||
if (simplePseudoSet.has(key)) { | ||
if (root.type === 'global') { | ||
throw new Error('Simple pseudos are not valid in globalStyles'); | ||
} | ||
this.addRule({ | ||
conditions, | ||
selector: `${root.selector}${key}`, | ||
rule: rule[key] | ||
}); | ||
} | ||
} | ||
} | ||
toPostcssJs() { | ||
const styles = {}; | ||
if (this.fontFaceRules.length > 0) { | ||
styles['@font-face'] = this.fontFaceRules; | ||
} | ||
this.keyframesRules.forEach(rule => { | ||
styles[`@keyframes ${rule.name}`] = rule.rule; | ||
}); | ||
for (const rule of [...this.rules, ...this.conditionalRules]) { | ||
if (rule.conditions && isEqual(styles[rule.selector], rule.rule)) { | ||
// Ignore conditional rules if they are identical to a non-conditional rule | ||
continue; | ||
} | ||
if (Object.keys(rule.rule).length === 0) { | ||
// Ignore empty rules | ||
continue; | ||
} | ||
let styleNode = styles; | ||
for (const condition of rule.conditions || []) { | ||
if (!styleNode[condition]) { | ||
styleNode[condition] = {}; | ||
} | ||
styleNode = styleNode[condition]; | ||
} | ||
styleNode[rule.selector] = { ...styleNode[rule.selector], | ||
...rule.rule | ||
}; | ||
} | ||
return styles; | ||
} | ||
toCss() { | ||
const styles = this.toPostcssJs(); | ||
function walkCss(v, indent = '') { | ||
const rules = []; | ||
for (const key of Object.keys(v)) { | ||
const value = v[key]; | ||
if (value && Array.isArray(value)) { | ||
rules.push(...value.map(v => walkCss({ | ||
[key]: v | ||
}, indent).join('\n'))); | ||
} else if (value && typeof value === 'object') { | ||
rules.push(`${indent}${key} {\n${walkCss(value, indent + DOUBLE_SPACE).join('\n')}\n${indent}}\n`); | ||
} else { | ||
rules.push(`${indent}${key.startsWith('--') ? key : dashify(key)}: ${value};`); | ||
} | ||
} | ||
return rules; | ||
} | ||
return walkCss(styles); | ||
} | ||
} | ||
function transformCss({ | ||
localClassNames, | ||
cssObjs | ||
}) { | ||
const stylesheet = new Stylesheet(localClassNames); | ||
for (const root of cssObjs) { | ||
stylesheet.processCssObj(root); | ||
} | ||
return stylesheet.toCss(); | ||
} | ||
let styleSheet; | ||
@@ -44,3 +307,3 @@ const localClassNames = new Set(); | ||
onEndFileScope: () => { | ||
const css = generateCss({ | ||
const css = transformCss({ | ||
localClassNames: Array.from(localClassNames), | ||
@@ -74,28 +337,8 @@ cssObjs: bufferedCSSObjs | ||
const generateClassName = debugId => { | ||
const generateIdentifier = debugId => { | ||
const refCount = getAndIncrementRefCounter(); | ||
const className = process.env.NODE_ENV !== 'production' && debugId ? `${getShortFileName()}_${debugId}__${hash(getFileScope())}${refCount}` : `${hash(getFileScope())}${refCount}`; | ||
return className.match(/^[0-9]/) ? `_${className}` : className; | ||
const identifier = process.env.NODE_ENV !== 'production' && debugId ? `${getShortFileName()}_${debugId}__${hash(getFileScope())}${refCount}` : `${hash(getFileScope())}${refCount}`; | ||
return identifier.match(/^[0-9]/) ? `_${identifier}` : identifier; | ||
}; | ||
const walkObject = (obj, fn, path = []) => { | ||
// @ts-expect-error | ||
const clone = obj.constructor(); | ||
for (let key in obj) { | ||
const value = obj[key]; | ||
const currentPath = [...path, key]; | ||
if (typeof value === 'object') { | ||
clone[key] = value ? walkObject(value, fn, currentPath) : value; | ||
} else if (typeof value === 'string' || typeof value === 'number') { | ||
clone[key] = fn(value, currentPath); | ||
} else { | ||
console.warn(`Skipping invalid key "${currentPath.join('.')}". Should be a string, number or object. Received: "${typeof value}"`); | ||
} | ||
} | ||
return clone; | ||
}; | ||
function createVar(debugId) { | ||
@@ -111,4 +354,19 @@ const refCount = getAndIncrementRefCounter(); | ||
} | ||
function fallbackVar(...values) { | ||
let finalValue = ''; | ||
values.reverse().forEach(value => { | ||
if (finalValue === '') { | ||
finalValue = String(value); | ||
} else { | ||
if (typeof value !== 'string' || !/^var\(--.*\)$/.test(value)) { | ||
throw new Error(`Invalid variable name: ${value}`); | ||
} | ||
finalValue = value.replace(/\)$/, `, ${finalValue})`); | ||
} | ||
}); | ||
return finalValue; | ||
} | ||
function style(rule, debugId) { | ||
const className = generateClassName(debugId); | ||
const className = generateIdentifier(debugId); | ||
registerClassName(className); | ||
@@ -129,2 +387,49 @@ appendCss({ | ||
} | ||
function fontFace(rule, debugId) { | ||
const fontFamily = `"${cssesc(generateIdentifier(debugId), { | ||
quotes: 'double' | ||
})}"`; | ||
if ('fontFamily' in rule) { | ||
throw new Error(dedent` | ||
This function creates and returns a hashed font-family name, so the "fontFamily" property should not be provided. | ||
If you'd like to define a globally scoped custom font, you can use the "globalFontFace" function instead. | ||
`); | ||
} | ||
appendCss({ | ||
type: 'fontFace', | ||
rule: { ...rule, | ||
fontFamily | ||
} | ||
}, getFileScope()); | ||
return fontFamily; | ||
} | ||
function globalFontFace(fontFamily, rule) { | ||
appendCss({ | ||
type: 'fontFace', | ||
rule: { ...rule, | ||
fontFamily | ||
} | ||
}, getFileScope()); | ||
} | ||
function keyframes(rule, debugId) { | ||
const name = cssesc(generateIdentifier(debugId), { | ||
isIdentifier: true | ||
}); | ||
appendCss({ | ||
type: 'keyframes', | ||
name, | ||
rule | ||
}, getFileScope()); | ||
return name; | ||
} | ||
function globalKeyframes(name, rule) { | ||
appendCss({ | ||
type: 'keyframes', | ||
name, | ||
rule | ||
}, getFileScope()); | ||
} | ||
function mapToStyles(...args) { | ||
@@ -188,3 +493,3 @@ if (typeof args[1] === 'function') { | ||
function createTheme(arg1, arg2, arg3) { | ||
const themeClassName = generateClassName(typeof arg2 === 'object' ? arg3 : arg2); | ||
const themeClassName = generateIdentifier(typeof arg2 === 'object' ? arg3 : arg2); | ||
registerClassName(themeClassName); | ||
@@ -218,2 +523,2 @@ const vars = typeof arg2 === 'object' ? createGlobalTheme(themeClassName, arg1, arg2) : createGlobalTheme(themeClassName, arg1); | ||
export { assignVars, createGlobalTheme, createInlineTheme, createTheme, createThemeVars, createVar, globalStyle, mapToStyles, style }; | ||
export { assignVars, createGlobalTheme, createInlineTheme, createTheme, createThemeVars, createVar, fallbackVar, fontFace, globalFontFace, globalKeyframes, globalStyle, keyframes, mapToStyles, style }; |
@@ -5,23 +5,286 @@ 'use strict'; | ||
var generateCss_dist_mattsjonesCssCoreGenerateCss = require('../generateCss/dist/mattsjones-css-core-generateCss.cjs.dev.js'); | ||
var cssesc = require('cssesc'); | ||
var utils = require('./utils-e55d05cd.cjs.dev.js'); | ||
var adapter_dist_mattsjonesCssCoreAdapter = require('../adapter/dist/mattsjones-css-core-adapter.cjs.dev.js'); | ||
var hash = require('@emotion/hash'); | ||
var get = require('lodash/get'); | ||
var cssesc = require('cssesc'); | ||
var dedent = require('dedent'); | ||
var fileScope_dist_mattsjonesCssCoreFileScope = require('../fileScope/dist/mattsjones-css-core-fileScope.cjs.dev.js'); | ||
require('postcss-js'); | ||
require('postcss'); | ||
require('lodash/each'); | ||
require('lodash/omit'); | ||
require('lodash/isEqual'); | ||
require('lodash/mapKeys'); | ||
require('css-selector-parser'); | ||
require('dedent'); | ||
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; } | ||
var cssesc__default = /*#__PURE__*/_interopDefault(cssesc); | ||
var hash__default = /*#__PURE__*/_interopDefault(hash); | ||
var get__default = /*#__PURE__*/_interopDefault(get); | ||
var cssesc__default = /*#__PURE__*/_interopDefault(cssesc); | ||
var dedent__default = /*#__PURE__*/_interopDefault(dedent); | ||
const UNITLESS = { | ||
boxFlex: true, | ||
boxFlexGroup: true, | ||
columnCount: true, | ||
flex: true, | ||
flexGrow: true, | ||
flexPositive: true, | ||
flexShrink: true, | ||
flexNegative: true, | ||
fontWeight: true, | ||
lineClamp: true, | ||
lineHeight: true, | ||
opacity: true, | ||
order: true, | ||
orphans: true, | ||
tabSize: true, | ||
widows: true, | ||
zIndex: true, | ||
zoom: true, | ||
fillOpacity: true, | ||
strokeDashoffset: true, | ||
strokeOpacity: true, | ||
strokeWidth: true | ||
}; | ||
const simplePseudos = [':-moz-any-link', ':-moz-full-screen', ':-moz-placeholder', ':-moz-read-only', ':-moz-read-write', ':-ms-fullscreen', ':-ms-input-placeholder', ':-webkit-any-link', ':-webkit-full-screen', '::-moz-placeholder', '::-moz-progress-bar', '::-moz-range-progress', '::-moz-range-thumb', '::-moz-range-track', '::-moz-selection', '::-ms-backdrop', '::-ms-browse', '::-ms-check', '::-ms-clear', '::-ms-fill', '::-ms-fill-lower', '::-ms-fill-upper', '::-ms-reveal', '::-ms-thumb', '::-ms-ticks-after', '::-ms-ticks-before', '::-ms-tooltip', '::-ms-track', '::-ms-value', '::-webkit-backdrop', '::-webkit-input-placeholder', '::-webkit-progress-bar', '::-webkit-progress-inner-value', '::-webkit-progress-value', '::-webkit-slider-runnable-track', '::-webkit-slider-thumb', '::after', '::backdrop', '::before', '::cue', '::first-letter', '::first-line', '::grammar-error', '::placeholder', '::selection', '::spelling-error', ':active', ':after', ':any-link', ':before', ':blank', ':checked', ':default', ':defined', ':disabled', ':empty', ':enabled', ':first', ':first-child', ':first-letter', ':first-line', ':first-of-type', ':focus', ':focus-visible', ':focus-within', ':fullscreen', ':hover', ':in-range', ':indeterminate', ':invalid', ':last-child', ':last-of-type', ':left', ':link', ':only-child', ':only-of-type', ':optional', ':out-of-range', ':placeholder-shown', ':read-only', ':read-write', ':required', ':right', ':root', ':scope', ':target', ':valid', ':visited']; | ||
function dashify(str) { | ||
return str.replace(/([A-Z])/g, '-$1').replace(/^ms-/, '-ms-').toLowerCase(); | ||
} | ||
const DOUBLE_SPACE = ' '; | ||
const simplePseudoSet = new Set(simplePseudos); | ||
const specialKeys = [...simplePseudos, '@media', '@supports', 'selectors']; | ||
class Stylesheet { | ||
constructor(localClassNames) { | ||
this.rules = []; | ||
this.conditionalRules = []; | ||
this.fontFaceRules = []; | ||
this.keyframesRules = []; | ||
this.localClassNameRegex = localClassNames.length > 0 ? RegExp(`(${localClassNames.join('|')})`, 'g') : null; | ||
} | ||
processCssObj(root) { | ||
if (root.type === 'fontFace') { | ||
this.fontFaceRules.push(root.rule); | ||
return; | ||
} | ||
if (root.type === 'keyframes') { | ||
this.keyframesRules.push(root); | ||
return; | ||
} // Add main styles | ||
const mainRule = utils.omit(root.rule, specialKeys); | ||
this.addRule({ | ||
selector: root.selector, | ||
rule: mainRule | ||
}); | ||
this.transformSimplePsuedos(root, root.rule); | ||
this.transformMedia(root, root.rule['@media']); | ||
this.transformSupports(root, root.rule['@supports']); | ||
this.transformSelectors(root, root.rule); | ||
} | ||
addRule(cssRule) { | ||
// Run `pixelifyProperties` before `transformVars` as we don't want to pixelify CSS Vars | ||
const rule = this.transformVars(this.pixelifyProperties(cssRule.rule)); | ||
const selector = this.transformSelector(cssRule.selector); | ||
if (cssRule.conditions) { | ||
this.conditionalRules.push({ | ||
selector, | ||
rule, | ||
conditions: cssRule.conditions.sort() | ||
}); | ||
} else { | ||
this.rules.push({ | ||
selector, | ||
rule | ||
}); | ||
} | ||
} | ||
pixelifyProperties(cssRule) { | ||
utils.forEach(cssRule, (value, key) => { | ||
if (typeof value === 'number' && value !== 0 && !UNITLESS[key]) { | ||
// @ts-expect-error Any ideas? | ||
cssRule[key] = `${value}px`; | ||
} | ||
}); | ||
return cssRule; | ||
} | ||
transformVars({ | ||
vars, | ||
...rest | ||
}) { | ||
if (!vars) { | ||
return rest; | ||
} | ||
return { ...utils.mapKeys(vars, (_value, key) => { | ||
const matches = key.match(/^var\((.*)\)$/); | ||
if (matches) { | ||
return matches[1]; | ||
} | ||
return key; | ||
}), | ||
...rest | ||
}; | ||
} | ||
transformSelector(selector) { | ||
return this.localClassNameRegex ? selector.replace(this.localClassNameRegex, (_, className, index) => { | ||
if (index > 0 && selector[index - 1] === '.') { | ||
return className; | ||
} | ||
return `.${cssesc__default['default'](className, { | ||
isIdentifier: true | ||
})}`; | ||
}) : selector; | ||
} | ||
transformSelectors(root, rule, conditions) { | ||
utils.forEach(rule.selectors, (selectorRule, selector) => { | ||
if (root.type === 'global') { | ||
throw new Error('Selectors are not allowed within globalStyle'); | ||
} | ||
const transformedSelector = this.transformSelector(selector.replace(RegExp('&', 'g'), root.selector)); | ||
utils.validateSelector(transformedSelector, root.selector); | ||
this.addRule({ | ||
conditions, | ||
selector: transformedSelector, | ||
rule: selectorRule | ||
}); | ||
}); | ||
} | ||
transformMedia(root, rules, parentConditions = []) { | ||
utils.forEach(rules, (mediaRule, query) => { | ||
const conditions = [`@media ${query}`, ...parentConditions]; | ||
this.addRule({ | ||
conditions, | ||
selector: root.selector, | ||
rule: utils.omit(mediaRule, specialKeys) | ||
}); | ||
this.transformSimplePsuedos(root, mediaRule, conditions); | ||
this.transformSelectors(root, mediaRule, conditions); | ||
this.transformSupports(root, mediaRule['@supports'], conditions); | ||
}); | ||
} | ||
transformSupports(root, rules, parentConditions = []) { | ||
utils.forEach(rules, (supportsRule, query) => { | ||
const conditions = [`@supports ${query}`, ...parentConditions]; | ||
this.addRule({ | ||
conditions, | ||
selector: root.selector, | ||
rule: utils.omit(supportsRule, specialKeys) | ||
}); | ||
this.transformSimplePsuedos(root, supportsRule, conditions); | ||
this.transformSelectors(root, supportsRule, conditions); | ||
this.transformMedia(root, supportsRule['@media'], conditions); | ||
}); | ||
} | ||
transformSimplePsuedos(root, rule, conditions) { | ||
for (const key of Object.keys(rule)) { | ||
// Process simple psuedos | ||
if (simplePseudoSet.has(key)) { | ||
if (root.type === 'global') { | ||
throw new Error('Simple pseudos are not valid in globalStyles'); | ||
} | ||
this.addRule({ | ||
conditions, | ||
selector: `${root.selector}${key}`, | ||
rule: rule[key] | ||
}); | ||
} | ||
} | ||
} | ||
toPostcssJs() { | ||
const styles = {}; | ||
if (this.fontFaceRules.length > 0) { | ||
styles['@font-face'] = this.fontFaceRules; | ||
} | ||
this.keyframesRules.forEach(rule => { | ||
styles[`@keyframes ${rule.name}`] = rule.rule; | ||
}); | ||
for (const rule of [...this.rules, ...this.conditionalRules]) { | ||
if (rule.conditions && utils.isEqual(styles[rule.selector], rule.rule)) { | ||
// Ignore conditional rules if they are identical to a non-conditional rule | ||
continue; | ||
} | ||
if (Object.keys(rule.rule).length === 0) { | ||
// Ignore empty rules | ||
continue; | ||
} | ||
let styleNode = styles; | ||
for (const condition of rule.conditions || []) { | ||
if (!styleNode[condition]) { | ||
styleNode[condition] = {}; | ||
} | ||
styleNode = styleNode[condition]; | ||
} | ||
styleNode[rule.selector] = { ...styleNode[rule.selector], | ||
...rule.rule | ||
}; | ||
} | ||
return styles; | ||
} | ||
toCss() { | ||
const styles = this.toPostcssJs(); | ||
function walkCss(v, indent = '') { | ||
const rules = []; | ||
for (const key of Object.keys(v)) { | ||
const value = v[key]; | ||
if (value && Array.isArray(value)) { | ||
rules.push(...value.map(v => walkCss({ | ||
[key]: v | ||
}, indent).join('\n'))); | ||
} else if (value && typeof value === 'object') { | ||
rules.push(`${indent}${key} {\n${walkCss(value, indent + DOUBLE_SPACE).join('\n')}\n${indent}}\n`); | ||
} else { | ||
rules.push(`${indent}${key.startsWith('--') ? key : dashify(key)}: ${value};`); | ||
} | ||
} | ||
return rules; | ||
} | ||
return walkCss(styles); | ||
} | ||
} | ||
function transformCss({ | ||
localClassNames, | ||
cssObjs | ||
}) { | ||
const stylesheet = new Stylesheet(localClassNames); | ||
for (const root of cssObjs) { | ||
stylesheet.processCssObj(root); | ||
} | ||
return stylesheet.toCss(); | ||
} | ||
let styleSheet; | ||
@@ -55,3 +318,3 @@ const localClassNames = new Set(); | ||
onEndFileScope: () => { | ||
const css = generateCss_dist_mattsjonesCssCoreGenerateCss.generateCss({ | ||
const css = transformCss({ | ||
localClassNames: Array.from(localClassNames), | ||
@@ -85,28 +348,8 @@ cssObjs: bufferedCSSObjs | ||
const generateClassName = debugId => { | ||
const generateIdentifier = debugId => { | ||
const refCount = fileScope_dist_mattsjonesCssCoreFileScope.getAndIncrementRefCounter(); | ||
const className = process.env.NODE_ENV !== 'production' && debugId ? `${getShortFileName()}_${debugId}__${hash__default['default'](fileScope_dist_mattsjonesCssCoreFileScope.getFileScope())}${refCount}` : `${hash__default['default'](fileScope_dist_mattsjonesCssCoreFileScope.getFileScope())}${refCount}`; | ||
return className.match(/^[0-9]/) ? `_${className}` : className; | ||
const identifier = process.env.NODE_ENV !== 'production' && debugId ? `${getShortFileName()}_${debugId}__${hash__default['default'](fileScope_dist_mattsjonesCssCoreFileScope.getFileScope())}${refCount}` : `${hash__default['default'](fileScope_dist_mattsjonesCssCoreFileScope.getFileScope())}${refCount}`; | ||
return identifier.match(/^[0-9]/) ? `_${identifier}` : identifier; | ||
}; | ||
const walkObject = (obj, fn, path = []) => { | ||
// @ts-expect-error | ||
const clone = obj.constructor(); | ||
for (let key in obj) { | ||
const value = obj[key]; | ||
const currentPath = [...path, key]; | ||
if (typeof value === 'object') { | ||
clone[key] = value ? walkObject(value, fn, currentPath) : value; | ||
} else if (typeof value === 'string' || typeof value === 'number') { | ||
clone[key] = fn(value, currentPath); | ||
} else { | ||
console.warn(`Skipping invalid key "${currentPath.join('.')}". Should be a string, number or object. Received: "${typeof value}"`); | ||
} | ||
} | ||
return clone; | ||
}; | ||
function createVar(debugId) { | ||
@@ -122,4 +365,19 @@ const refCount = fileScope_dist_mattsjonesCssCoreFileScope.getAndIncrementRefCounter(); | ||
} | ||
function fallbackVar(...values) { | ||
let finalValue = ''; | ||
values.reverse().forEach(value => { | ||
if (finalValue === '') { | ||
finalValue = String(value); | ||
} else { | ||
if (typeof value !== 'string' || !/^var\(--.*\)$/.test(value)) { | ||
throw new Error(`Invalid variable name: ${value}`); | ||
} | ||
finalValue = value.replace(/\)$/, `, ${finalValue})`); | ||
} | ||
}); | ||
return finalValue; | ||
} | ||
function style(rule, debugId) { | ||
const className = generateClassName(debugId); | ||
const className = generateIdentifier(debugId); | ||
adapter_dist_mattsjonesCssCoreAdapter.registerClassName(className); | ||
@@ -140,2 +398,49 @@ adapter_dist_mattsjonesCssCoreAdapter.appendCss({ | ||
} | ||
function fontFace(rule, debugId) { | ||
const fontFamily = `"${cssesc__default['default'](generateIdentifier(debugId), { | ||
quotes: 'double' | ||
})}"`; | ||
if ('fontFamily' in rule) { | ||
throw new Error(dedent__default['default']` | ||
This function creates and returns a hashed font-family name, so the "fontFamily" property should not be provided. | ||
If you'd like to define a globally scoped custom font, you can use the "globalFontFace" function instead. | ||
`); | ||
} | ||
adapter_dist_mattsjonesCssCoreAdapter.appendCss({ | ||
type: 'fontFace', | ||
rule: { ...rule, | ||
fontFamily | ||
} | ||
}, fileScope_dist_mattsjonesCssCoreFileScope.getFileScope()); | ||
return fontFamily; | ||
} | ||
function globalFontFace(fontFamily, rule) { | ||
adapter_dist_mattsjonesCssCoreAdapter.appendCss({ | ||
type: 'fontFace', | ||
rule: { ...rule, | ||
fontFamily | ||
} | ||
}, fileScope_dist_mattsjonesCssCoreFileScope.getFileScope()); | ||
} | ||
function keyframes(rule, debugId) { | ||
const name = cssesc__default['default'](generateIdentifier(debugId), { | ||
isIdentifier: true | ||
}); | ||
adapter_dist_mattsjonesCssCoreAdapter.appendCss({ | ||
type: 'keyframes', | ||
name, | ||
rule | ||
}, fileScope_dist_mattsjonesCssCoreFileScope.getFileScope()); | ||
return name; | ||
} | ||
function globalKeyframes(name, rule) { | ||
adapter_dist_mattsjonesCssCoreAdapter.appendCss({ | ||
type: 'keyframes', | ||
name, | ||
rule | ||
}, fileScope_dist_mattsjonesCssCoreFileScope.getFileScope()); | ||
} | ||
function mapToStyles(...args) { | ||
@@ -166,3 +471,3 @@ if (typeof args[1] === 'function') { | ||
function createThemeVars(themeContract) { | ||
return walkObject(themeContract, (_value, path) => { | ||
return utils.walkObject(themeContract, (_value, path) => { | ||
return createVar(path.join('-')); | ||
@@ -178,4 +483,4 @@ }); | ||
walkObject(tokens, (value, path) => { | ||
varSetters[get__default['default'](varContract, path)] = value; | ||
utils.walkObject(tokens, (value, path) => { | ||
varSetters[utils.get(varContract, path)] = value; | ||
}); | ||
@@ -201,3 +506,3 @@ return varSetters; | ||
function createTheme(arg1, arg2, arg3) { | ||
const themeClassName = generateClassName(typeof arg2 === 'object' ? arg3 : arg2); | ||
const themeClassName = generateIdentifier(typeof arg2 === 'object' ? arg3 : arg2); | ||
adapter_dist_mattsjonesCssCoreAdapter.registerClassName(themeClassName); | ||
@@ -214,4 +519,4 @@ const vars = typeof arg2 === 'object' ? createGlobalTheme(themeClassName, arg1, arg2) : createGlobalTheme(themeClassName, arg1); | ||
walkObject(tokens, (value, path) => { | ||
const varName = get__default['default'](themeVars, path); | ||
utils.walkObject(tokens, (value, path) => { | ||
const varName = utils.get(themeVars, path); | ||
styles[varName.substring(4, varName.length - 1)] = String(value); | ||
@@ -238,4 +543,9 @@ }); | ||
exports.createVar = createVar; | ||
exports.fallbackVar = fallbackVar; | ||
exports.fontFace = fontFace; | ||
exports.globalFontFace = globalFontFace; | ||
exports.globalKeyframes = globalKeyframes; | ||
exports.globalStyle = globalStyle; | ||
exports.keyframes = keyframes; | ||
exports.mapToStyles = mapToStyles; | ||
exports.style = style; |
@@ -5,23 +5,286 @@ 'use strict'; | ||
var generateCss_dist_mattsjonesCssCoreGenerateCss = require('../generateCss/dist/mattsjones-css-core-generateCss.cjs.prod.js'); | ||
var cssesc = require('cssesc'); | ||
var utils = require('./utils-ddaa8dd7.cjs.prod.js'); | ||
var adapter_dist_mattsjonesCssCoreAdapter = require('../adapter/dist/mattsjones-css-core-adapter.cjs.prod.js'); | ||
var hash = require('@emotion/hash'); | ||
var get = require('lodash/get'); | ||
var cssesc = require('cssesc'); | ||
var dedent = require('dedent'); | ||
var fileScope_dist_mattsjonesCssCoreFileScope = require('../fileScope/dist/mattsjones-css-core-fileScope.cjs.prod.js'); | ||
require('postcss-js'); | ||
require('postcss'); | ||
require('lodash/each'); | ||
require('lodash/omit'); | ||
require('lodash/isEqual'); | ||
require('lodash/mapKeys'); | ||
require('css-selector-parser'); | ||
require('dedent'); | ||
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; } | ||
var cssesc__default = /*#__PURE__*/_interopDefault(cssesc); | ||
var hash__default = /*#__PURE__*/_interopDefault(hash); | ||
var get__default = /*#__PURE__*/_interopDefault(get); | ||
var cssesc__default = /*#__PURE__*/_interopDefault(cssesc); | ||
var dedent__default = /*#__PURE__*/_interopDefault(dedent); | ||
const UNITLESS = { | ||
boxFlex: true, | ||
boxFlexGroup: true, | ||
columnCount: true, | ||
flex: true, | ||
flexGrow: true, | ||
flexPositive: true, | ||
flexShrink: true, | ||
flexNegative: true, | ||
fontWeight: true, | ||
lineClamp: true, | ||
lineHeight: true, | ||
opacity: true, | ||
order: true, | ||
orphans: true, | ||
tabSize: true, | ||
widows: true, | ||
zIndex: true, | ||
zoom: true, | ||
fillOpacity: true, | ||
strokeDashoffset: true, | ||
strokeOpacity: true, | ||
strokeWidth: true | ||
}; | ||
const simplePseudos = [':-moz-any-link', ':-moz-full-screen', ':-moz-placeholder', ':-moz-read-only', ':-moz-read-write', ':-ms-fullscreen', ':-ms-input-placeholder', ':-webkit-any-link', ':-webkit-full-screen', '::-moz-placeholder', '::-moz-progress-bar', '::-moz-range-progress', '::-moz-range-thumb', '::-moz-range-track', '::-moz-selection', '::-ms-backdrop', '::-ms-browse', '::-ms-check', '::-ms-clear', '::-ms-fill', '::-ms-fill-lower', '::-ms-fill-upper', '::-ms-reveal', '::-ms-thumb', '::-ms-ticks-after', '::-ms-ticks-before', '::-ms-tooltip', '::-ms-track', '::-ms-value', '::-webkit-backdrop', '::-webkit-input-placeholder', '::-webkit-progress-bar', '::-webkit-progress-inner-value', '::-webkit-progress-value', '::-webkit-slider-runnable-track', '::-webkit-slider-thumb', '::after', '::backdrop', '::before', '::cue', '::first-letter', '::first-line', '::grammar-error', '::placeholder', '::selection', '::spelling-error', ':active', ':after', ':any-link', ':before', ':blank', ':checked', ':default', ':defined', ':disabled', ':empty', ':enabled', ':first', ':first-child', ':first-letter', ':first-line', ':first-of-type', ':focus', ':focus-visible', ':focus-within', ':fullscreen', ':hover', ':in-range', ':indeterminate', ':invalid', ':last-child', ':last-of-type', ':left', ':link', ':only-child', ':only-of-type', ':optional', ':out-of-range', ':placeholder-shown', ':read-only', ':read-write', ':required', ':right', ':root', ':scope', ':target', ':valid', ':visited']; | ||
function dashify(str) { | ||
return str.replace(/([A-Z])/g, '-$1').replace(/^ms-/, '-ms-').toLowerCase(); | ||
} | ||
const DOUBLE_SPACE = ' '; | ||
const simplePseudoSet = new Set(simplePseudos); | ||
const specialKeys = [...simplePseudos, '@media', '@supports', 'selectors']; | ||
class Stylesheet { | ||
constructor(localClassNames) { | ||
this.rules = []; | ||
this.conditionalRules = []; | ||
this.fontFaceRules = []; | ||
this.keyframesRules = []; | ||
this.localClassNameRegex = localClassNames.length > 0 ? RegExp(`(${localClassNames.join('|')})`, 'g') : null; | ||
} | ||
processCssObj(root) { | ||
if (root.type === 'fontFace') { | ||
this.fontFaceRules.push(root.rule); | ||
return; | ||
} | ||
if (root.type === 'keyframes') { | ||
this.keyframesRules.push(root); | ||
return; | ||
} // Add main styles | ||
const mainRule = utils.omit(root.rule, specialKeys); | ||
this.addRule({ | ||
selector: root.selector, | ||
rule: mainRule | ||
}); | ||
this.transformSimplePsuedos(root, root.rule); | ||
this.transformMedia(root, root.rule['@media']); | ||
this.transformSupports(root, root.rule['@supports']); | ||
this.transformSelectors(root, root.rule); | ||
} | ||
addRule(cssRule) { | ||
// Run `pixelifyProperties` before `transformVars` as we don't want to pixelify CSS Vars | ||
const rule = this.transformVars(this.pixelifyProperties(cssRule.rule)); | ||
const selector = this.transformSelector(cssRule.selector); | ||
if (cssRule.conditions) { | ||
this.conditionalRules.push({ | ||
selector, | ||
rule, | ||
conditions: cssRule.conditions.sort() | ||
}); | ||
} else { | ||
this.rules.push({ | ||
selector, | ||
rule | ||
}); | ||
} | ||
} | ||
pixelifyProperties(cssRule) { | ||
utils.forEach(cssRule, (value, key) => { | ||
if (typeof value === 'number' && value !== 0 && !UNITLESS[key]) { | ||
// @ts-expect-error Any ideas? | ||
cssRule[key] = `${value}px`; | ||
} | ||
}); | ||
return cssRule; | ||
} | ||
transformVars({ | ||
vars, | ||
...rest | ||
}) { | ||
if (!vars) { | ||
return rest; | ||
} | ||
return { ...utils.mapKeys(vars, (_value, key) => { | ||
const matches = key.match(/^var\((.*)\)$/); | ||
if (matches) { | ||
return matches[1]; | ||
} | ||
return key; | ||
}), | ||
...rest | ||
}; | ||
} | ||
transformSelector(selector) { | ||
return this.localClassNameRegex ? selector.replace(this.localClassNameRegex, (_, className, index) => { | ||
if (index > 0 && selector[index - 1] === '.') { | ||
return className; | ||
} | ||
return `.${cssesc__default['default'](className, { | ||
isIdentifier: true | ||
})}`; | ||
}) : selector; | ||
} | ||
transformSelectors(root, rule, conditions) { | ||
utils.forEach(rule.selectors, (selectorRule, selector) => { | ||
if (root.type === 'global') { | ||
throw new Error('Selectors are not allowed within globalStyle'); | ||
} | ||
const transformedSelector = this.transformSelector(selector.replace(RegExp('&', 'g'), root.selector)); | ||
utils.validateSelector(transformedSelector, root.selector); | ||
this.addRule({ | ||
conditions, | ||
selector: transformedSelector, | ||
rule: selectorRule | ||
}); | ||
}); | ||
} | ||
transformMedia(root, rules, parentConditions = []) { | ||
utils.forEach(rules, (mediaRule, query) => { | ||
const conditions = [`@media ${query}`, ...parentConditions]; | ||
this.addRule({ | ||
conditions, | ||
selector: root.selector, | ||
rule: utils.omit(mediaRule, specialKeys) | ||
}); | ||
this.transformSimplePsuedos(root, mediaRule, conditions); | ||
this.transformSelectors(root, mediaRule, conditions); | ||
this.transformSupports(root, mediaRule['@supports'], conditions); | ||
}); | ||
} | ||
transformSupports(root, rules, parentConditions = []) { | ||
utils.forEach(rules, (supportsRule, query) => { | ||
const conditions = [`@supports ${query}`, ...parentConditions]; | ||
this.addRule({ | ||
conditions, | ||
selector: root.selector, | ||
rule: utils.omit(supportsRule, specialKeys) | ||
}); | ||
this.transformSimplePsuedos(root, supportsRule, conditions); | ||
this.transformSelectors(root, supportsRule, conditions); | ||
this.transformMedia(root, supportsRule['@media'], conditions); | ||
}); | ||
} | ||
transformSimplePsuedos(root, rule, conditions) { | ||
for (const key of Object.keys(rule)) { | ||
// Process simple psuedos | ||
if (simplePseudoSet.has(key)) { | ||
if (root.type === 'global') { | ||
throw new Error('Simple pseudos are not valid in globalStyles'); | ||
} | ||
this.addRule({ | ||
conditions, | ||
selector: `${root.selector}${key}`, | ||
rule: rule[key] | ||
}); | ||
} | ||
} | ||
} | ||
toPostcssJs() { | ||
const styles = {}; | ||
if (this.fontFaceRules.length > 0) { | ||
styles['@font-face'] = this.fontFaceRules; | ||
} | ||
this.keyframesRules.forEach(rule => { | ||
styles[`@keyframes ${rule.name}`] = rule.rule; | ||
}); | ||
for (const rule of [...this.rules, ...this.conditionalRules]) { | ||
if (rule.conditions && utils.isEqual(styles[rule.selector], rule.rule)) { | ||
// Ignore conditional rules if they are identical to a non-conditional rule | ||
continue; | ||
} | ||
if (Object.keys(rule.rule).length === 0) { | ||
// Ignore empty rules | ||
continue; | ||
} | ||
let styleNode = styles; | ||
for (const condition of rule.conditions || []) { | ||
if (!styleNode[condition]) { | ||
styleNode[condition] = {}; | ||
} | ||
styleNode = styleNode[condition]; | ||
} | ||
styleNode[rule.selector] = { ...styleNode[rule.selector], | ||
...rule.rule | ||
}; | ||
} | ||
return styles; | ||
} | ||
toCss() { | ||
const styles = this.toPostcssJs(); | ||
function walkCss(v, indent = '') { | ||
const rules = []; | ||
for (const key of Object.keys(v)) { | ||
const value = v[key]; | ||
if (value && Array.isArray(value)) { | ||
rules.push(...value.map(v => walkCss({ | ||
[key]: v | ||
}, indent).join('\n'))); | ||
} else if (value && typeof value === 'object') { | ||
rules.push(`${indent}${key} {\n${walkCss(value, indent + DOUBLE_SPACE).join('\n')}\n${indent}}\n`); | ||
} else { | ||
rules.push(`${indent}${key.startsWith('--') ? key : dashify(key)}: ${value};`); | ||
} | ||
} | ||
return rules; | ||
} | ||
return walkCss(styles); | ||
} | ||
} | ||
function transformCss({ | ||
localClassNames, | ||
cssObjs | ||
}) { | ||
const stylesheet = new Stylesheet(localClassNames); | ||
for (const root of cssObjs) { | ||
stylesheet.processCssObj(root); | ||
} | ||
return stylesheet.toCss(); | ||
} | ||
let styleSheet; | ||
@@ -55,3 +318,3 @@ const localClassNames = new Set(); | ||
onEndFileScope: () => { | ||
const css = generateCss_dist_mattsjonesCssCoreGenerateCss.generateCss({ | ||
const css = transformCss({ | ||
localClassNames: Array.from(localClassNames), | ||
@@ -74,28 +337,8 @@ cssObjs: bufferedCSSObjs | ||
const generateClassName = debugId => { | ||
const generateIdentifier = debugId => { | ||
const refCount = fileScope_dist_mattsjonesCssCoreFileScope.getAndIncrementRefCounter(); | ||
const className = `${hash__default['default'](fileScope_dist_mattsjonesCssCoreFileScope.getFileScope())}${refCount}`; | ||
return className.match(/^[0-9]/) ? `_${className}` : className; | ||
const identifier = `${hash__default['default'](fileScope_dist_mattsjonesCssCoreFileScope.getFileScope())}${refCount}`; | ||
return identifier.match(/^[0-9]/) ? `_${identifier}` : identifier; | ||
}; | ||
const walkObject = (obj, fn, path = []) => { | ||
// @ts-expect-error | ||
const clone = obj.constructor(); | ||
for (let key in obj) { | ||
const value = obj[key]; | ||
const currentPath = [...path, key]; | ||
if (typeof value === 'object') { | ||
clone[key] = value ? walkObject(value, fn, currentPath) : value; | ||
} else if (typeof value === 'string' || typeof value === 'number') { | ||
clone[key] = fn(value, currentPath); | ||
} else { | ||
console.warn(`Skipping invalid key "${currentPath.join('.')}". Should be a string, number or object. Received: "${typeof value}"`); | ||
} | ||
} | ||
return clone; | ||
}; | ||
function createVar(debugId) { | ||
@@ -111,4 +354,19 @@ const refCount = fileScope_dist_mattsjonesCssCoreFileScope.getAndIncrementRefCounter(); | ||
} | ||
function fallbackVar(...values) { | ||
let finalValue = ''; | ||
values.reverse().forEach(value => { | ||
if (finalValue === '') { | ||
finalValue = String(value); | ||
} else { | ||
if (typeof value !== 'string' || !/^var\(--.*\)$/.test(value)) { | ||
throw new Error(`Invalid variable name: ${value}`); | ||
} | ||
finalValue = value.replace(/\)$/, `, ${finalValue})`); | ||
} | ||
}); | ||
return finalValue; | ||
} | ||
function style(rule, debugId) { | ||
const className = generateClassName(); | ||
const className = generateIdentifier(); | ||
adapter_dist_mattsjonesCssCoreAdapter.registerClassName(className); | ||
@@ -129,2 +387,49 @@ adapter_dist_mattsjonesCssCoreAdapter.appendCss({ | ||
} | ||
function fontFace(rule, debugId) { | ||
const fontFamily = `"${cssesc__default['default'](generateIdentifier(), { | ||
quotes: 'double' | ||
})}"`; | ||
if ('fontFamily' in rule) { | ||
throw new Error(dedent__default['default']` | ||
This function creates and returns a hashed font-family name, so the "fontFamily" property should not be provided. | ||
If you'd like to define a globally scoped custom font, you can use the "globalFontFace" function instead. | ||
`); | ||
} | ||
adapter_dist_mattsjonesCssCoreAdapter.appendCss({ | ||
type: 'fontFace', | ||
rule: { ...rule, | ||
fontFamily | ||
} | ||
}, fileScope_dist_mattsjonesCssCoreFileScope.getFileScope()); | ||
return fontFamily; | ||
} | ||
function globalFontFace(fontFamily, rule) { | ||
adapter_dist_mattsjonesCssCoreAdapter.appendCss({ | ||
type: 'fontFace', | ||
rule: { ...rule, | ||
fontFamily | ||
} | ||
}, fileScope_dist_mattsjonesCssCoreFileScope.getFileScope()); | ||
} | ||
function keyframes(rule, debugId) { | ||
const name = cssesc__default['default'](generateIdentifier(), { | ||
isIdentifier: true | ||
}); | ||
adapter_dist_mattsjonesCssCoreAdapter.appendCss({ | ||
type: 'keyframes', | ||
name, | ||
rule | ||
}, fileScope_dist_mattsjonesCssCoreFileScope.getFileScope()); | ||
return name; | ||
} | ||
function globalKeyframes(name, rule) { | ||
adapter_dist_mattsjonesCssCoreAdapter.appendCss({ | ||
type: 'keyframes', | ||
name, | ||
rule | ||
}, fileScope_dist_mattsjonesCssCoreFileScope.getFileScope()); | ||
} | ||
function mapToStyles(...args) { | ||
@@ -155,3 +460,3 @@ if (typeof args[1] === 'function') { | ||
function createThemeVars(themeContract) { | ||
return walkObject(themeContract, (_value, path) => { | ||
return utils.walkObject(themeContract, (_value, path) => { | ||
return createVar(path.join('-')); | ||
@@ -167,4 +472,4 @@ }); | ||
walkObject(tokens, (value, path) => { | ||
varSetters[get__default['default'](varContract, path)] = value; | ||
utils.walkObject(tokens, (value, path) => { | ||
varSetters[utils.get(varContract, path)] = value; | ||
}); | ||
@@ -190,3 +495,3 @@ return varSetters; | ||
function createTheme(arg1, arg2, arg3) { | ||
const themeClassName = generateClassName(); | ||
const themeClassName = generateIdentifier(); | ||
adapter_dist_mattsjonesCssCoreAdapter.registerClassName(themeClassName); | ||
@@ -203,4 +508,4 @@ const vars = typeof arg2 === 'object' ? createGlobalTheme(themeClassName, arg1, arg2) : createGlobalTheme(themeClassName, arg1); | ||
walkObject(tokens, (value, path) => { | ||
const varName = get__default['default'](themeVars, path); | ||
utils.walkObject(tokens, (value, path) => { | ||
const varName = utils.get(themeVars, path); | ||
styles[varName.substring(4, varName.length - 1)] = String(value); | ||
@@ -227,4 +532,9 @@ }); | ||
exports.createVar = createVar; | ||
exports.fallbackVar = fallbackVar; | ||
exports.fontFace = fontFace; | ||
exports.globalFontFace = globalFontFace; | ||
exports.globalKeyframes = globalKeyframes; | ||
exports.globalStyle = globalStyle; | ||
exports.keyframes = keyframes; | ||
exports.mapToStyles = mapToStyles; | ||
exports.style = style; |
@@ -1,16 +0,279 @@ | ||
import { generateCss } from '../generateCss/dist/mattsjones-css-core-generateCss.esm.js'; | ||
import cssesc from 'cssesc'; | ||
import { o as omit, f as forEach, m as mapKeys, v as validateSelector, i as isEqual, w as walkObject, g as get } from './utils-d61c10ab.esm.js'; | ||
import { registerClassName, appendCss, setAdapter } from '../adapter/dist/mattsjones-css-core-adapter.esm.js'; | ||
import hash from '@emotion/hash'; | ||
import get from 'lodash/get'; | ||
import cssesc from 'cssesc'; | ||
import dedent from 'dedent'; | ||
import { getAndIncrementRefCounter, getFileScope } from '../fileScope/dist/mattsjones-css-core-fileScope.esm.js'; | ||
import 'postcss-js'; | ||
import 'postcss'; | ||
import 'lodash/each'; | ||
import 'lodash/omit'; | ||
import 'lodash/isEqual'; | ||
import 'lodash/mapKeys'; | ||
import 'css-selector-parser'; | ||
import 'dedent'; | ||
const UNITLESS = { | ||
boxFlex: true, | ||
boxFlexGroup: true, | ||
columnCount: true, | ||
flex: true, | ||
flexGrow: true, | ||
flexPositive: true, | ||
flexShrink: true, | ||
flexNegative: true, | ||
fontWeight: true, | ||
lineClamp: true, | ||
lineHeight: true, | ||
opacity: true, | ||
order: true, | ||
orphans: true, | ||
tabSize: true, | ||
widows: true, | ||
zIndex: true, | ||
zoom: true, | ||
fillOpacity: true, | ||
strokeDashoffset: true, | ||
strokeOpacity: true, | ||
strokeWidth: true | ||
}; | ||
const simplePseudos = [':-moz-any-link', ':-moz-full-screen', ':-moz-placeholder', ':-moz-read-only', ':-moz-read-write', ':-ms-fullscreen', ':-ms-input-placeholder', ':-webkit-any-link', ':-webkit-full-screen', '::-moz-placeholder', '::-moz-progress-bar', '::-moz-range-progress', '::-moz-range-thumb', '::-moz-range-track', '::-moz-selection', '::-ms-backdrop', '::-ms-browse', '::-ms-check', '::-ms-clear', '::-ms-fill', '::-ms-fill-lower', '::-ms-fill-upper', '::-ms-reveal', '::-ms-thumb', '::-ms-ticks-after', '::-ms-ticks-before', '::-ms-tooltip', '::-ms-track', '::-ms-value', '::-webkit-backdrop', '::-webkit-input-placeholder', '::-webkit-progress-bar', '::-webkit-progress-inner-value', '::-webkit-progress-value', '::-webkit-slider-runnable-track', '::-webkit-slider-thumb', '::after', '::backdrop', '::before', '::cue', '::first-letter', '::first-line', '::grammar-error', '::placeholder', '::selection', '::spelling-error', ':active', ':after', ':any-link', ':before', ':blank', ':checked', ':default', ':defined', ':disabled', ':empty', ':enabled', ':first', ':first-child', ':first-letter', ':first-line', ':first-of-type', ':focus', ':focus-visible', ':focus-within', ':fullscreen', ':hover', ':in-range', ':indeterminate', ':invalid', ':last-child', ':last-of-type', ':left', ':link', ':only-child', ':only-of-type', ':optional', ':out-of-range', ':placeholder-shown', ':read-only', ':read-write', ':required', ':right', ':root', ':scope', ':target', ':valid', ':visited']; | ||
function dashify(str) { | ||
return str.replace(/([A-Z])/g, '-$1').replace(/^ms-/, '-ms-').toLowerCase(); | ||
} | ||
const DOUBLE_SPACE = ' '; | ||
const simplePseudoSet = new Set(simplePseudos); | ||
const specialKeys = [...simplePseudos, '@media', '@supports', 'selectors']; | ||
class Stylesheet { | ||
constructor(localClassNames) { | ||
this.rules = []; | ||
this.conditionalRules = []; | ||
this.fontFaceRules = []; | ||
this.keyframesRules = []; | ||
this.localClassNameRegex = localClassNames.length > 0 ? RegExp(`(${localClassNames.join('|')})`, 'g') : null; | ||
} | ||
processCssObj(root) { | ||
if (root.type === 'fontFace') { | ||
this.fontFaceRules.push(root.rule); | ||
return; | ||
} | ||
if (root.type === 'keyframes') { | ||
this.keyframesRules.push(root); | ||
return; | ||
} // Add main styles | ||
const mainRule = omit(root.rule, specialKeys); | ||
this.addRule({ | ||
selector: root.selector, | ||
rule: mainRule | ||
}); | ||
this.transformSimplePsuedos(root, root.rule); | ||
this.transformMedia(root, root.rule['@media']); | ||
this.transformSupports(root, root.rule['@supports']); | ||
this.transformSelectors(root, root.rule); | ||
} | ||
addRule(cssRule) { | ||
// Run `pixelifyProperties` before `transformVars` as we don't want to pixelify CSS Vars | ||
const rule = this.transformVars(this.pixelifyProperties(cssRule.rule)); | ||
const selector = this.transformSelector(cssRule.selector); | ||
if (cssRule.conditions) { | ||
this.conditionalRules.push({ | ||
selector, | ||
rule, | ||
conditions: cssRule.conditions.sort() | ||
}); | ||
} else { | ||
this.rules.push({ | ||
selector, | ||
rule | ||
}); | ||
} | ||
} | ||
pixelifyProperties(cssRule) { | ||
forEach(cssRule, (value, key) => { | ||
if (typeof value === 'number' && value !== 0 && !UNITLESS[key]) { | ||
// @ts-expect-error Any ideas? | ||
cssRule[key] = `${value}px`; | ||
} | ||
}); | ||
return cssRule; | ||
} | ||
transformVars({ | ||
vars, | ||
...rest | ||
}) { | ||
if (!vars) { | ||
return rest; | ||
} | ||
return { ...mapKeys(vars, (_value, key) => { | ||
const matches = key.match(/^var\((.*)\)$/); | ||
if (matches) { | ||
return matches[1]; | ||
} | ||
return key; | ||
}), | ||
...rest | ||
}; | ||
} | ||
transformSelector(selector) { | ||
return this.localClassNameRegex ? selector.replace(this.localClassNameRegex, (_, className, index) => { | ||
if (index > 0 && selector[index - 1] === '.') { | ||
return className; | ||
} | ||
return `.${cssesc(className, { | ||
isIdentifier: true | ||
})}`; | ||
}) : selector; | ||
} | ||
transformSelectors(root, rule, conditions) { | ||
forEach(rule.selectors, (selectorRule, selector) => { | ||
if (root.type === 'global') { | ||
throw new Error('Selectors are not allowed within globalStyle'); | ||
} | ||
const transformedSelector = this.transformSelector(selector.replace(RegExp('&', 'g'), root.selector)); | ||
validateSelector(transformedSelector, root.selector); | ||
this.addRule({ | ||
conditions, | ||
selector: transformedSelector, | ||
rule: selectorRule | ||
}); | ||
}); | ||
} | ||
transformMedia(root, rules, parentConditions = []) { | ||
forEach(rules, (mediaRule, query) => { | ||
const conditions = [`@media ${query}`, ...parentConditions]; | ||
this.addRule({ | ||
conditions, | ||
selector: root.selector, | ||
rule: omit(mediaRule, specialKeys) | ||
}); | ||
this.transformSimplePsuedos(root, mediaRule, conditions); | ||
this.transformSelectors(root, mediaRule, conditions); | ||
this.transformSupports(root, mediaRule['@supports'], conditions); | ||
}); | ||
} | ||
transformSupports(root, rules, parentConditions = []) { | ||
forEach(rules, (supportsRule, query) => { | ||
const conditions = [`@supports ${query}`, ...parentConditions]; | ||
this.addRule({ | ||
conditions, | ||
selector: root.selector, | ||
rule: omit(supportsRule, specialKeys) | ||
}); | ||
this.transformSimplePsuedos(root, supportsRule, conditions); | ||
this.transformSelectors(root, supportsRule, conditions); | ||
this.transformMedia(root, supportsRule['@media'], conditions); | ||
}); | ||
} | ||
transformSimplePsuedos(root, rule, conditions) { | ||
for (const key of Object.keys(rule)) { | ||
// Process simple psuedos | ||
if (simplePseudoSet.has(key)) { | ||
if (root.type === 'global') { | ||
throw new Error('Simple pseudos are not valid in globalStyles'); | ||
} | ||
this.addRule({ | ||
conditions, | ||
selector: `${root.selector}${key}`, | ||
rule: rule[key] | ||
}); | ||
} | ||
} | ||
} | ||
toPostcssJs() { | ||
const styles = {}; | ||
if (this.fontFaceRules.length > 0) { | ||
styles['@font-face'] = this.fontFaceRules; | ||
} | ||
this.keyframesRules.forEach(rule => { | ||
styles[`@keyframes ${rule.name}`] = rule.rule; | ||
}); | ||
for (const rule of [...this.rules, ...this.conditionalRules]) { | ||
if (rule.conditions && isEqual(styles[rule.selector], rule.rule)) { | ||
// Ignore conditional rules if they are identical to a non-conditional rule | ||
continue; | ||
} | ||
if (Object.keys(rule.rule).length === 0) { | ||
// Ignore empty rules | ||
continue; | ||
} | ||
let styleNode = styles; | ||
for (const condition of rule.conditions || []) { | ||
if (!styleNode[condition]) { | ||
styleNode[condition] = {}; | ||
} | ||
styleNode = styleNode[condition]; | ||
} | ||
styleNode[rule.selector] = { ...styleNode[rule.selector], | ||
...rule.rule | ||
}; | ||
} | ||
return styles; | ||
} | ||
toCss() { | ||
const styles = this.toPostcssJs(); | ||
function walkCss(v, indent = '') { | ||
const rules = []; | ||
for (const key of Object.keys(v)) { | ||
const value = v[key]; | ||
if (value && Array.isArray(value)) { | ||
rules.push(...value.map(v => walkCss({ | ||
[key]: v | ||
}, indent).join('\n'))); | ||
} else if (value && typeof value === 'object') { | ||
rules.push(`${indent}${key} {\n${walkCss(value, indent + DOUBLE_SPACE).join('\n')}\n${indent}}\n`); | ||
} else { | ||
rules.push(`${indent}${key.startsWith('--') ? key : dashify(key)}: ${value};`); | ||
} | ||
} | ||
return rules; | ||
} | ||
return walkCss(styles); | ||
} | ||
} | ||
function transformCss({ | ||
localClassNames, | ||
cssObjs | ||
}) { | ||
const stylesheet = new Stylesheet(localClassNames); | ||
for (const root of cssObjs) { | ||
stylesheet.processCssObj(root); | ||
} | ||
return stylesheet.toCss(); | ||
} | ||
let styleSheet; | ||
@@ -44,3 +307,3 @@ const localClassNames = new Set(); | ||
onEndFileScope: () => { | ||
const css = generateCss({ | ||
const css = transformCss({ | ||
localClassNames: Array.from(localClassNames), | ||
@@ -74,28 +337,8 @@ cssObjs: bufferedCSSObjs | ||
const generateClassName = debugId => { | ||
const generateIdentifier = debugId => { | ||
const refCount = getAndIncrementRefCounter(); | ||
const className = process.env.NODE_ENV !== 'production' && debugId ? `${getShortFileName()}_${debugId}__${hash(getFileScope())}${refCount}` : `${hash(getFileScope())}${refCount}`; | ||
return className.match(/^[0-9]/) ? `_${className}` : className; | ||
const identifier = process.env.NODE_ENV !== 'production' && debugId ? `${getShortFileName()}_${debugId}__${hash(getFileScope())}${refCount}` : `${hash(getFileScope())}${refCount}`; | ||
return identifier.match(/^[0-9]/) ? `_${identifier}` : identifier; | ||
}; | ||
const walkObject = (obj, fn, path = []) => { | ||
// @ts-expect-error | ||
const clone = obj.constructor(); | ||
for (let key in obj) { | ||
const value = obj[key]; | ||
const currentPath = [...path, key]; | ||
if (typeof value === 'object') { | ||
clone[key] = value ? walkObject(value, fn, currentPath) : value; | ||
} else if (typeof value === 'string' || typeof value === 'number') { | ||
clone[key] = fn(value, currentPath); | ||
} else { | ||
console.warn(`Skipping invalid key "${currentPath.join('.')}". Should be a string, number or object. Received: "${typeof value}"`); | ||
} | ||
} | ||
return clone; | ||
}; | ||
function createVar(debugId) { | ||
@@ -111,4 +354,19 @@ const refCount = getAndIncrementRefCounter(); | ||
} | ||
function fallbackVar(...values) { | ||
let finalValue = ''; | ||
values.reverse().forEach(value => { | ||
if (finalValue === '') { | ||
finalValue = String(value); | ||
} else { | ||
if (typeof value !== 'string' || !/^var\(--.*\)$/.test(value)) { | ||
throw new Error(`Invalid variable name: ${value}`); | ||
} | ||
finalValue = value.replace(/\)$/, `, ${finalValue})`); | ||
} | ||
}); | ||
return finalValue; | ||
} | ||
function style(rule, debugId) { | ||
const className = generateClassName(debugId); | ||
const className = generateIdentifier(debugId); | ||
registerClassName(className); | ||
@@ -129,2 +387,49 @@ appendCss({ | ||
} | ||
function fontFace(rule, debugId) { | ||
const fontFamily = `"${cssesc(generateIdentifier(debugId), { | ||
quotes: 'double' | ||
})}"`; | ||
if ('fontFamily' in rule) { | ||
throw new Error(dedent` | ||
This function creates and returns a hashed font-family name, so the "fontFamily" property should not be provided. | ||
If you'd like to define a globally scoped custom font, you can use the "globalFontFace" function instead. | ||
`); | ||
} | ||
appendCss({ | ||
type: 'fontFace', | ||
rule: { ...rule, | ||
fontFamily | ||
} | ||
}, getFileScope()); | ||
return fontFamily; | ||
} | ||
function globalFontFace(fontFamily, rule) { | ||
appendCss({ | ||
type: 'fontFace', | ||
rule: { ...rule, | ||
fontFamily | ||
} | ||
}, getFileScope()); | ||
} | ||
function keyframes(rule, debugId) { | ||
const name = cssesc(generateIdentifier(debugId), { | ||
isIdentifier: true | ||
}); | ||
appendCss({ | ||
type: 'keyframes', | ||
name, | ||
rule | ||
}, getFileScope()); | ||
return name; | ||
} | ||
function globalKeyframes(name, rule) { | ||
appendCss({ | ||
type: 'keyframes', | ||
name, | ||
rule | ||
}, getFileScope()); | ||
} | ||
function mapToStyles(...args) { | ||
@@ -188,3 +493,3 @@ if (typeof args[1] === 'function') { | ||
function createTheme(arg1, arg2, arg3) { | ||
const themeClassName = generateClassName(typeof arg2 === 'object' ? arg3 : arg2); | ||
const themeClassName = generateIdentifier(typeof arg2 === 'object' ? arg3 : arg2); | ||
registerClassName(themeClassName); | ||
@@ -218,2 +523,2 @@ const vars = typeof arg2 === 'object' ? createGlobalTheme(themeClassName, arg1, arg2) : createGlobalTheme(themeClassName, arg1); | ||
export { assignVars, createGlobalTheme, createInlineTheme, createTheme, createThemeVars, createVar, globalStyle, mapToStyles, style }; | ||
export { assignVars, createGlobalTheme, createInlineTheme, createTheme, createThemeVars, createVar, fallbackVar, fontFace, globalFontFace, globalKeyframes, globalStyle, keyframes, mapToStyles, style }; |
{ | ||
"name": "@mattsjones/css-core", | ||
"version": "0.0.13", | ||
"version": "0.0.14", | ||
"main": "dist/mattsjones-css-core.cjs.js", | ||
@@ -14,3 +14,3 @@ "module": "dist/mattsjones-css-core.esm.js", | ||
"adapter.ts", | ||
"generateCss.ts", | ||
"transformCss.ts", | ||
"fileScope.ts" | ||
@@ -22,6 +22,6 @@ ] | ||
"/adapter", | ||
"/generateCss", | ||
"/transformCss", | ||
"/fileScope" | ||
], | ||
"author": "Matt Jones", | ||
"author": "SEEK", | ||
"license": "MIT", | ||
@@ -33,12 +33,8 @@ "dependencies": { | ||
"csstype": "^3.0.7", | ||
"dedent": "^0.7.0", | ||
"lodash": "^4.17.21", | ||
"postcss": "^8.2.7", | ||
"postcss-js": "^3.0.3" | ||
"dedent": "^0.7.0" | ||
}, | ||
"devDependencies": { | ||
"@types/cssesc": "^3.0.0", | ||
"@types/dedent": "^0.7.0", | ||
"@types/lodash": "^4.14.168" | ||
"@types/dedent": "^0.7.0" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
164301
5
2
47
4491
1
- Removedlodash@^4.17.21
- Removedpostcss@^8.2.7
- Removedpostcss-js@^3.0.3
- Removedcamelcase-css@2.0.1(transitive)
- Removedlodash@4.17.21(transitive)
- Removednanoid@3.3.8(transitive)
- Removedpicocolors@1.1.1(transitive)
- Removedpostcss@8.4.49(transitive)
- Removedpostcss-js@3.0.3(transitive)
- Removedsource-map-js@1.2.1(transitive)