@cssxjs/runtime 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/constants.cjs +4 -0
- package/dimensions.js +5 -0
- package/entrypoints/react-native-teamplay.js +7 -0
- package/entrypoints/react-native.js +7 -0
- package/entrypoints/web-teamplay.js +7 -0
- package/entrypoints/web.js +7 -0
- package/matcher.js +127 -0
- package/package.json +56 -0
- package/platformHelpers/index.js +32 -0
- package/platformHelpers/react-native.js +9 -0
- package/platformHelpers/web.js +14 -0
- package/process.js +107 -0
- package/processCached.js +74 -0
- package/variables.js +9 -0
- package/vendor/react-native-css-media-query-processor/README.md +8 -0
- package/vendor/react-native-css-media-query-processor/index.js +46 -0
- package/vendor/react-native-css-media-query-processor/mediaquery.js +152 -0
- package/vendor/react-native-dynamic-style-processor/README.md +8 -0
- package/vendor/react-native-dynamic-style-processor/index.js +52 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# v0.2.0 (Tue Nov 04 2025)
|
|
2
|
+
|
|
3
|
+
#### 🚀 Enhancement
|
|
4
|
+
|
|
5
|
+
- feat: add TypeScript support, write a more comprehensive example in TSX ([@cray0000](https://github.com/cray0000))
|
|
6
|
+
- feat(runtime): implement support for both React Native and pure Web ([@cray0000](https://github.com/cray0000))
|
|
7
|
+
- feat: make it work for pure web through a babel plugin [#2](https://github.com/startupjs/cssx/pull/2) ([@cray0000](https://github.com/cray0000))
|
|
8
|
+
|
|
9
|
+
#### Authors: 1
|
|
10
|
+
|
|
11
|
+
- Pavel Zhukov ([@cray0000](https://github.com/cray0000))
|
package/constants.cjs
ADDED
package/dimensions.js
ADDED
package/matcher.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const ROOT_STYLE_PROP_NAME = 'style'
|
|
2
|
+
const PART_REGEX = /::?part\(([^)]+)\)/
|
|
3
|
+
|
|
4
|
+
const isArray = Array.isArray || function (arg) {
|
|
5
|
+
return Object.prototype.toString.call(arg) === '[object Array]'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default function matcher (
|
|
9
|
+
styleName,
|
|
10
|
+
fileStyles,
|
|
11
|
+
globalStyles,
|
|
12
|
+
localStyles,
|
|
13
|
+
inlineStyleProps
|
|
14
|
+
) {
|
|
15
|
+
// inlineStyleProps is used as an implicit indication of:
|
|
16
|
+
// w/ inlineStyleProps -- process all styles and return an object with style props
|
|
17
|
+
// w/o inlineStyleProps -- default inline styles addition is done externally,
|
|
18
|
+
// return styles object directly
|
|
19
|
+
const legacy = !inlineStyleProps
|
|
20
|
+
|
|
21
|
+
// Process styleName through the `classnames`-like function.
|
|
22
|
+
// This allows to specify styleName as an array or an object,
|
|
23
|
+
// not just the string.
|
|
24
|
+
styleName = cc(styleName)
|
|
25
|
+
|
|
26
|
+
const htmlClasses = (styleName || '').split(' ').filter(Boolean)
|
|
27
|
+
const resProps = getStyleProps(htmlClasses, fileStyles, legacy)
|
|
28
|
+
|
|
29
|
+
// In the legacy mode, return root styles right away
|
|
30
|
+
if (legacy) return resProps[ROOT_STYLE_PROP_NAME]
|
|
31
|
+
|
|
32
|
+
// 1. Add global styles
|
|
33
|
+
appendStyleProps(resProps, getStyleProps(htmlClasses, globalStyles))
|
|
34
|
+
|
|
35
|
+
// 2. Add local styles
|
|
36
|
+
appendStyleProps(resProps, getStyleProps(htmlClasses, localStyles))
|
|
37
|
+
|
|
38
|
+
// 3. Add inline styles
|
|
39
|
+
appendStyleProps(resProps, inlineStyleProps)
|
|
40
|
+
return resProps
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function appendStyleProps (target, appendProps) {
|
|
44
|
+
for (const propName in appendProps) {
|
|
45
|
+
if (target[propName]) {
|
|
46
|
+
if (isArray(appendProps[propName])) {
|
|
47
|
+
target[propName] = target[propName].concat(appendProps[propName])
|
|
48
|
+
} else {
|
|
49
|
+
target[propName].push(appendProps[propName])
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
target[propName] = appendProps[propName]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Process all styles, including the ::part() ones.
|
|
58
|
+
function getStyleProps (htmlClasses, styles, legacyRootOnly) {
|
|
59
|
+
const res = {}
|
|
60
|
+
for (const selector in styles) {
|
|
61
|
+
// Find out which part (or root) this selector is targeting
|
|
62
|
+
const match = selector.match(PART_REGEX)
|
|
63
|
+
const attr = match ? getPropName(match[1]) : ROOT_STYLE_PROP_NAME
|
|
64
|
+
|
|
65
|
+
// Don't process part if legacyRootOnly is specified
|
|
66
|
+
if (legacyRootOnly && attr !== ROOT_STYLE_PROP_NAME) continue
|
|
67
|
+
|
|
68
|
+
// Strip ::part() if it exists
|
|
69
|
+
const pureSelector = selector.replace(PART_REGEX, '')
|
|
70
|
+
|
|
71
|
+
// Check if the selector is matching our list of existing classes
|
|
72
|
+
const cssClasses = pureSelector.split('.')
|
|
73
|
+
if (!arrayContainedInArray(cssClasses, htmlClasses)) continue
|
|
74
|
+
|
|
75
|
+
// Push selector's style to the according part's array of styles.
|
|
76
|
+
// We have a nested array structure here to account for the selector specificity.
|
|
77
|
+
// This way styles for selector with 3 classes take priority
|
|
78
|
+
// over selectors with 2 classes, etc.
|
|
79
|
+
|
|
80
|
+
// Note: Specificity here does not strictly equal the standard
|
|
81
|
+
// since we only use classes to increase the specificity.
|
|
82
|
+
// In future this might change when we add support for tags, but for now
|
|
83
|
+
// it is a single digit increment starting from 0 and equalling the amount
|
|
84
|
+
// of classes in the selector.
|
|
85
|
+
const specificity = cssClasses.length - 1
|
|
86
|
+
if (!res[attr]) res[attr] = []
|
|
87
|
+
if (!res[attr][specificity]) res[attr][specificity] = []
|
|
88
|
+
res[attr][specificity].push(styles[selector])
|
|
89
|
+
}
|
|
90
|
+
return res
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getPropName (name) {
|
|
94
|
+
return name + 'Style'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function arrayContainedInArray (cssClasses, htmlClasses) {
|
|
98
|
+
for (let i = 0; i < cssClasses.length; i++) {
|
|
99
|
+
if (htmlClasses.indexOf(cssClasses[i]) === -1) return false
|
|
100
|
+
}
|
|
101
|
+
return true
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// classcat 4.0.2
|
|
105
|
+
// https://github.com/jorgebucaran/classcat
|
|
106
|
+
|
|
107
|
+
function cc (names) {
|
|
108
|
+
let i
|
|
109
|
+
let len
|
|
110
|
+
let tmp = typeof names
|
|
111
|
+
let out = ''
|
|
112
|
+
|
|
113
|
+
if (tmp === 'string' || tmp === 'number') return names || ''
|
|
114
|
+
|
|
115
|
+
if (isArray(names) && names.length > 0) {
|
|
116
|
+
for (i = 0, len = names.length; i < len; i++) {
|
|
117
|
+
if ((tmp = cc(names[i])) !== '') out += (out && ' ') + tmp
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
for (i in names) {
|
|
121
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
122
|
+
if (names.hasOwnProperty(i) && names[i]) out += (out && ' ') + i
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return out
|
|
127
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cssxjs/runtime",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "Dynamically resolve styleName in RN with support for multi-class selectors (for easier modifiers)",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"babel",
|
|
10
|
+
"babel-plugin",
|
|
11
|
+
"react-native",
|
|
12
|
+
"stylename",
|
|
13
|
+
"style"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
"./entrypoints/web": "./entrypoints/web.js",
|
|
17
|
+
"./entrypoints/react-native": "./entrypoints/react-native.js",
|
|
18
|
+
"./entrypoints/web-teamplay": "./entrypoints/web-teamplay.js",
|
|
19
|
+
"./entrypoints/react-native-teamplay": "./entrypoints/react-native-teamplay.js",
|
|
20
|
+
"./constants": "./constants.cjs",
|
|
21
|
+
"./dimensions": "./dimensions.js"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "mocha"
|
|
26
|
+
},
|
|
27
|
+
"author": "Pavel Zhukov",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/startupjs/startupjs"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@nx-js/observer-util": "^4.1.3",
|
|
35
|
+
"css-viewport-units-transform": "^0.10.2",
|
|
36
|
+
"deepmerge": "^3.2.0",
|
|
37
|
+
"micro-memoize": "^3.0.1"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@startupjs/css-to-react-native-transform": "^2.1.0-0",
|
|
41
|
+
"mocha": "^8.1.1"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"react-native": "*",
|
|
45
|
+
"teamplay": "*"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"react-native": {
|
|
49
|
+
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"teamplay": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"gitHead": "a80a44b4d14c116871ca03997ce772b6beabf5d8"
|
|
56
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// injection of platformHelpers
|
|
2
|
+
|
|
3
|
+
let platformHelpers
|
|
4
|
+
|
|
5
|
+
export function setPlatformHelpers (newPlatformHelpers) {
|
|
6
|
+
if (platformHelpers === newPlatformHelpers) return
|
|
7
|
+
platformHelpers = newPlatformHelpers
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getPlatformHelpers () {
|
|
11
|
+
return platformHelpers
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// facades to call the currently injected platform helper functions
|
|
15
|
+
|
|
16
|
+
export function getDimensions (...args) {
|
|
17
|
+
try {
|
|
18
|
+
return platformHelpers.getDimensions(...args)
|
|
19
|
+
} catch (err) {
|
|
20
|
+
console.error('[cssxjs] platform helpers \'getDimensions\' is not specified. Babel is probably misconfigured')
|
|
21
|
+
throw err
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getPlatform (...args) {
|
|
26
|
+
try {
|
|
27
|
+
return platformHelpers.getPlatform(...args)
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error('[cssxjs] platform helpers \'getPlatform\' is not specified. Babel is probably misconfigured')
|
|
30
|
+
throw err
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function getDimensions () {
|
|
2
|
+
if (typeof window === 'undefined' || !window.innerWidth || !window.innerHeight) {
|
|
3
|
+
console.warn('[cssx] No "window" global variable. Falling back to constant window width and height of 1024x768')
|
|
4
|
+
return { width: 1024, height: 768 }
|
|
5
|
+
}
|
|
6
|
+
return {
|
|
7
|
+
width: window.innerWidth,
|
|
8
|
+
height: window.innerHeight
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getPlatform () {
|
|
13
|
+
return 'web'
|
|
14
|
+
}
|
package/process.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { process as dynamicProcess } from './vendor/react-native-dynamic-style-processor/index.js'
|
|
2
|
+
import dimensions from './dimensions.js'
|
|
3
|
+
import singletonVariables, { defaultVariables } from './variables.js'
|
|
4
|
+
import matcher from './matcher.js'
|
|
5
|
+
|
|
6
|
+
// TODO: Improve css variables performance. Instead of rerunning finding variables each time
|
|
7
|
+
// it has to work as a pipeline and pass the variables from one step to the next.
|
|
8
|
+
|
|
9
|
+
const VARS_REGEX = /"var\(\s*(--[A-Za-z0-9_-]+)\s*,?\s*(.*?)\s*\)"/g
|
|
10
|
+
const HAS_VAR_REGEX = /"var\(/
|
|
11
|
+
|
|
12
|
+
export function process (
|
|
13
|
+
styleName,
|
|
14
|
+
fileStyles,
|
|
15
|
+
globalStyles,
|
|
16
|
+
localStyles,
|
|
17
|
+
inlineStyleProps
|
|
18
|
+
) {
|
|
19
|
+
fileStyles = transformStyles(fileStyles)
|
|
20
|
+
globalStyles = transformStyles(globalStyles)
|
|
21
|
+
localStyles = transformStyles(localStyles)
|
|
22
|
+
|
|
23
|
+
const res = matcher(
|
|
24
|
+
styleName, fileStyles, globalStyles, localStyles, inlineStyleProps
|
|
25
|
+
)
|
|
26
|
+
// flatten styles into single objects
|
|
27
|
+
for (const propName in res) {
|
|
28
|
+
if (Array.isArray(res[propName])) {
|
|
29
|
+
res[propName] = res[propName].flat(10)
|
|
30
|
+
res[propName] = Object.assign({}, ...res[propName])
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return res
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function hasMedia (styles = {}) {
|
|
37
|
+
for (const selector in styles) {
|
|
38
|
+
if (/^@media/.test(selector)) {
|
|
39
|
+
return true
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function hasVariables (...styleObjects) {
|
|
45
|
+
for (const styleObject of styleObjects) {
|
|
46
|
+
if (_hasVariables(styleObject)) return true
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function _hasVariables (styles = {}) {
|
|
51
|
+
return HAS_VAR_REGEX.test(JSON.stringify(styles))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function replaceVariables (styles = {}) {
|
|
55
|
+
let strStyles = JSON.stringify(styles)
|
|
56
|
+
strStyles = strStyles.replace(VARS_REGEX, (match, varName, varDefault) => {
|
|
57
|
+
let res
|
|
58
|
+
res = singletonVariables[varName] ?? defaultVariables[varName] ?? varDefault
|
|
59
|
+
if (typeof res === 'string') {
|
|
60
|
+
res = res.trim()
|
|
61
|
+
// replace 'px' value with a pure number
|
|
62
|
+
res = res.replace(/px$/, '')
|
|
63
|
+
// sometimes compiler returns wrapped brackets. Remove them
|
|
64
|
+
const bracketsCount = res.match(/^\(+/)?.[0]?.length || 0
|
|
65
|
+
res = res.substring(bracketsCount, res.length - bracketsCount)
|
|
66
|
+
}
|
|
67
|
+
if (!isNumeric(res)) {
|
|
68
|
+
res = `"${res}"`
|
|
69
|
+
}
|
|
70
|
+
return res
|
|
71
|
+
})
|
|
72
|
+
return JSON.parse(strStyles)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function transformStyles (styles) {
|
|
76
|
+
if (styles) {
|
|
77
|
+
// trigger rerender when cache is NOT used
|
|
78
|
+
if (hasMedia(styles)) listenForDimensionsChange()
|
|
79
|
+
|
|
80
|
+
// dynamically process @media queries and vh/vw units
|
|
81
|
+
styles = dynamicProcess(styles)
|
|
82
|
+
|
|
83
|
+
if (hasVariables(styles)) {
|
|
84
|
+
// Dynamically process css variables.
|
|
85
|
+
// This will also auto-trigger rerendering on variable change when cache is not used
|
|
86
|
+
styles = replaceVariables(styles)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return styles
|
|
90
|
+
} else {
|
|
91
|
+
return {}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// If @media is used, force trigger access to the observable value.
|
|
96
|
+
// `dimensions` is an observed Proxy so
|
|
97
|
+
// whenever its value changes the according components will
|
|
98
|
+
// automatically rerender.
|
|
99
|
+
// The change is triggered globally in startupjs/plugins/cssMediaUpdater.plugin.js
|
|
100
|
+
export function listenForDimensionsChange () {
|
|
101
|
+
// eslint-disable-next-line no-unused-expressions
|
|
102
|
+
if (dimensions.width) true
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function isNumeric (num) {
|
|
106
|
+
return (typeof num === 'number' || (typeof num === 'string' && num.trim() !== '')) && !isNaN(num)
|
|
107
|
+
}
|
package/processCached.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { singletonMemoize } from 'teamplay/cache'
|
|
2
|
+
import dimensions from './dimensions.js'
|
|
3
|
+
import singletonVariables from './variables.js'
|
|
4
|
+
import { process as _process, hasMedia, listenForDimensionsChange, hasVariables } from './process.js'
|
|
5
|
+
|
|
6
|
+
const VAR_NAMES_REGEX = /"var\(\s*--[A-Za-z0-9_-]+/g
|
|
7
|
+
|
|
8
|
+
export const process = singletonMemoize(_process, {
|
|
9
|
+
cacheName: 'styles',
|
|
10
|
+
// IMPORTANT: This should be the same as the ones which go into the singletonMemoize function
|
|
11
|
+
normalizer: (styleName, fileStyles, globalStyles, localStyles, inlineStyleProps) => simpleNumericHash(JSON.stringify([
|
|
12
|
+
styleName,
|
|
13
|
+
fileStyles?.__hash__ || fileStyles,
|
|
14
|
+
globalStyles?.__hash__ || globalStyles,
|
|
15
|
+
localStyles?.__hash__ || localStyles,
|
|
16
|
+
inlineStyleProps
|
|
17
|
+
])),
|
|
18
|
+
// IMPORTANT: This should be the same as the ones which go into the singletonMemoize function
|
|
19
|
+
forceUpdateWhenChanged: (styleName, fileStyles, globalStyles, localStyles, inlineStyleProps) => {
|
|
20
|
+
const args = {}
|
|
21
|
+
const watchWidthChange = hasMedia(fileStyles) || hasMedia(globalStyles) || hasMedia(localStyles)
|
|
22
|
+
if (watchWidthChange) {
|
|
23
|
+
// trigger rerender when cache is used
|
|
24
|
+
listenForDimensionsChange()
|
|
25
|
+
// Return the dimensionsWidth value itself to force
|
|
26
|
+
// the affected cache to recalculate
|
|
27
|
+
args.dimensionsWidth = dimensions.width
|
|
28
|
+
}
|
|
29
|
+
if (hasVariables(fileStyles, globalStyles, localStyles)) {
|
|
30
|
+
const variableNames = getVariableNames(fileStyles, globalStyles, localStyles)
|
|
31
|
+
// trigger rerender when cache is used
|
|
32
|
+
listenForVariablesChange(variableNames)
|
|
33
|
+
// Return the variable values themselves to force
|
|
34
|
+
// the affected cache to recalculate
|
|
35
|
+
for (const variableName of variableNames) {
|
|
36
|
+
args['VAR_' + variableName] = singletonVariables[variableName]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return simpleNumericHash(JSON.stringify(args))
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
function getVariableNames (...styleObjects) {
|
|
44
|
+
let res = []
|
|
45
|
+
for (const styleObject of styleObjects) {
|
|
46
|
+
res = res.concat(_getVariableNames(styleObject))
|
|
47
|
+
}
|
|
48
|
+
res = [...new Set(res)].sort() // remove duplicates and sort
|
|
49
|
+
return res
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function _getVariableNames (styles = {}) {
|
|
53
|
+
let res = JSON.stringify(styles).match(VAR_NAMES_REGEX) || []
|
|
54
|
+
res = res.map(i => i.replace(/"var\(\s*/, ''))
|
|
55
|
+
return res
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// If var() is used, force trigger access to the observable value.
|
|
59
|
+
// `singletonVariables` is an observed Proxy so
|
|
60
|
+
// whenever its value changes the according components will
|
|
61
|
+
// automatically rerender.
|
|
62
|
+
function listenForVariablesChange (variables = []) {
|
|
63
|
+
for (const variable of variables) {
|
|
64
|
+
// eslint-disable-next-line no-unused-expressions
|
|
65
|
+
if (singletonVariables[variable]) true
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ref: https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0?permalink_comment_id=2694461#gistcomment-2694461
|
|
70
|
+
function simpleNumericHash (s) {
|
|
71
|
+
let i, h
|
|
72
|
+
for (i = 0, h = 0; i < s.length; i++) h = Math.imul(31, h) + s.charCodeAt(i) | 0
|
|
73
|
+
return h
|
|
74
|
+
}
|
package/variables.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import merge from 'deepmerge'
|
|
2
|
+
import memoize from 'micro-memoize'
|
|
3
|
+
import mediaQuery from './mediaquery.js'
|
|
4
|
+
import { getPlatform } from '../../platformHelpers/index.js'
|
|
5
|
+
|
|
6
|
+
const PREFIX = '@media'
|
|
7
|
+
|
|
8
|
+
function isMediaQuery (str) {
|
|
9
|
+
return typeof str === 'string' && str.indexOf(PREFIX) === 0
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function filterMq (obj) {
|
|
13
|
+
return Object.keys(obj).filter(key => isMediaQuery(key))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function filterNonMq (obj) {
|
|
17
|
+
return Object.keys(obj).reduce((out, key) => {
|
|
18
|
+
if (!isMediaQuery(key) && key !== '__mediaQueries') {
|
|
19
|
+
out[key] = obj[key]
|
|
20
|
+
}
|
|
21
|
+
return out
|
|
22
|
+
}, {})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const mFilterMq = memoize(filterMq)
|
|
26
|
+
const mFilterNonMq = memoize(filterNonMq)
|
|
27
|
+
|
|
28
|
+
export function process (obj, matchObject) {
|
|
29
|
+
const mqKeys = mFilterMq(obj)
|
|
30
|
+
let res = mFilterNonMq(obj)
|
|
31
|
+
|
|
32
|
+
mqKeys.forEach(key => {
|
|
33
|
+
if (/^@media\s+(not\s+)?(ios|android|dom|macos|web|windows)/i.test(key)) {
|
|
34
|
+
matchObject.type = getPlatform()
|
|
35
|
+
} else {
|
|
36
|
+
matchObject.type = 'screen'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const isMatch = mediaQuery.match(obj.__mediaQueries[key], matchObject)
|
|
40
|
+
if (isMatch) {
|
|
41
|
+
res = merge(res, obj[key])
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return res
|
|
46
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2014, Yahoo! Inc. All rights reserved.
|
|
3
|
+
Copyrights licensed under the New BSD License.
|
|
4
|
+
See the accompanying LICENSE file for terms.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export default match
|
|
8
|
+
|
|
9
|
+
// -----------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
const RE_LENGTH_UNIT = /(em|rem|px|cm|mm|in|pt|pc)?\s*$/
|
|
12
|
+
const RE_RESOLUTION_UNIT = /(dpi|dpcm|dppx)?\s*$/
|
|
13
|
+
|
|
14
|
+
function match (parsed, values) {
|
|
15
|
+
if (!parsed) {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
if (parsed.length === 1) {
|
|
19
|
+
return matchQuery(parsed[0], values)
|
|
20
|
+
}
|
|
21
|
+
return parsed.some(mq => matchQuery(mq, values))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function matchQuery (query, values) {
|
|
25
|
+
const inverse = query.inverse
|
|
26
|
+
|
|
27
|
+
// Either the parsed or specified `type` is "all", or the types must be
|
|
28
|
+
// equal for a match.
|
|
29
|
+
const typeMatch = query.type === 'all' || values.type === query.type
|
|
30
|
+
|
|
31
|
+
if (query.expressions.length === 0) {
|
|
32
|
+
// Quit early when `type` doesn't match, but take "not" into account.
|
|
33
|
+
if ((typeMatch && inverse) || !(typeMatch || inverse)) {
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const expressionsMatch = query.expressions.every(function (expression) {
|
|
39
|
+
const feature = expression.feature
|
|
40
|
+
const modifier = expression.modifier
|
|
41
|
+
let expValue = expression.value
|
|
42
|
+
let value = values[feature]
|
|
43
|
+
|
|
44
|
+
// Missing or falsy values don't match.
|
|
45
|
+
if (!value) {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
switch (feature) {
|
|
50
|
+
case 'orientation':
|
|
51
|
+
case 'scan':
|
|
52
|
+
return value.toLowerCase() === expValue.toLowerCase()
|
|
53
|
+
|
|
54
|
+
case 'width':
|
|
55
|
+
case 'height':
|
|
56
|
+
case 'device-width':
|
|
57
|
+
case 'device-height':
|
|
58
|
+
expValue = toPx(expValue)
|
|
59
|
+
value = toPx(value)
|
|
60
|
+
break
|
|
61
|
+
|
|
62
|
+
case 'resolution':
|
|
63
|
+
expValue = toDpi(expValue)
|
|
64
|
+
value = toDpi(value)
|
|
65
|
+
break
|
|
66
|
+
|
|
67
|
+
case 'aspect-ratio':
|
|
68
|
+
case 'device-aspect-ratio':
|
|
69
|
+
case /* Deprecated */ 'device-pixel-ratio':
|
|
70
|
+
expValue = toDecimal(expValue)
|
|
71
|
+
value = toDecimal(value)
|
|
72
|
+
break
|
|
73
|
+
|
|
74
|
+
case 'grid':
|
|
75
|
+
case 'color':
|
|
76
|
+
case 'color-index':
|
|
77
|
+
case 'monochrome':
|
|
78
|
+
expValue = parseInt(expValue, 10) || 1
|
|
79
|
+
value = parseInt(value, 10) || 0
|
|
80
|
+
break
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
switch (modifier) {
|
|
84
|
+
case 'min':
|
|
85
|
+
return value >= expValue
|
|
86
|
+
case 'max':
|
|
87
|
+
return value <= expValue
|
|
88
|
+
default:
|
|
89
|
+
return value === expValue
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const isMatch = typeMatch && expressionsMatch
|
|
94
|
+
|
|
95
|
+
if (inverse) {
|
|
96
|
+
return !isMatch
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return isMatch
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// -- Utilities ----------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
function toDecimal (ratio) {
|
|
105
|
+
let decimal = Number(ratio)
|
|
106
|
+
let numbers
|
|
107
|
+
|
|
108
|
+
if (!decimal) {
|
|
109
|
+
numbers = ratio.match(/^(\d+)\s*\/\s*(\d+)$/)
|
|
110
|
+
decimal = numbers[1] / numbers[2]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return decimal
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function toDpi (resolution) {
|
|
117
|
+
const value = parseFloat(resolution)
|
|
118
|
+
const units = String(resolution).match(RE_RESOLUTION_UNIT)[1]
|
|
119
|
+
|
|
120
|
+
switch (units) {
|
|
121
|
+
case 'dpcm':
|
|
122
|
+
return value / 2.54
|
|
123
|
+
case 'dppx':
|
|
124
|
+
return value * 96
|
|
125
|
+
default:
|
|
126
|
+
return value
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function toPx (length) {
|
|
131
|
+
const value = parseFloat(length)
|
|
132
|
+
const units = String(length).match(RE_LENGTH_UNIT)[1]
|
|
133
|
+
|
|
134
|
+
switch (units) {
|
|
135
|
+
case 'em':
|
|
136
|
+
return value * 16
|
|
137
|
+
case 'rem':
|
|
138
|
+
return value * 16
|
|
139
|
+
case 'cm':
|
|
140
|
+
return (value * 96) / 2.54
|
|
141
|
+
case 'mm':
|
|
142
|
+
return (value * 96) / 2.54 / 10
|
|
143
|
+
case 'in':
|
|
144
|
+
return value * 96
|
|
145
|
+
case 'pt':
|
|
146
|
+
return value * 72
|
|
147
|
+
case 'pc':
|
|
148
|
+
return (value * 72) / 12
|
|
149
|
+
default:
|
|
150
|
+
return value
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { process as mediaQueriesProcess } from '../react-native-css-media-query-processor/index.js'
|
|
2
|
+
import { transform } from 'css-viewport-units-transform'
|
|
3
|
+
import memoize from 'micro-memoize'
|
|
4
|
+
import { getDimensions } from '../../platformHelpers/index.js'
|
|
5
|
+
|
|
6
|
+
function omit (obj, omitKey) {
|
|
7
|
+
return Object.keys(obj).reduce((result, key) => {
|
|
8
|
+
if (key !== omitKey) {
|
|
9
|
+
result[key] = obj[key]
|
|
10
|
+
}
|
|
11
|
+
return result
|
|
12
|
+
}, {})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const omitMemoized = memoize(omit)
|
|
16
|
+
|
|
17
|
+
function viewportUnitsTransform (obj, matchObject) {
|
|
18
|
+
const hasViewportUnits = '__viewportUnits' in obj
|
|
19
|
+
|
|
20
|
+
if (!hasViewportUnits) {
|
|
21
|
+
return obj
|
|
22
|
+
}
|
|
23
|
+
return transform(omitMemoized(obj, '__viewportUnits'), matchObject)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function mediaQueriesTransform (obj, matchObject) {
|
|
27
|
+
const hasParsedMQs = '__mediaQueries' in obj
|
|
28
|
+
|
|
29
|
+
if (!hasParsedMQs) {
|
|
30
|
+
return obj
|
|
31
|
+
}
|
|
32
|
+
return mediaQueriesProcess(obj, matchObject)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function process (obj) {
|
|
36
|
+
const matchObject = getMatchObject()
|
|
37
|
+
return viewportUnitsTransform(
|
|
38
|
+
mediaQueriesTransform(obj, matchObject),
|
|
39
|
+
matchObject
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getMatchObject () {
|
|
44
|
+
const win = getDimensions()
|
|
45
|
+
return {
|
|
46
|
+
width: win.width,
|
|
47
|
+
height: win.height,
|
|
48
|
+
orientation: win.width > win.height ? 'landscape' : 'portrait',
|
|
49
|
+
'aspect-ratio': win.width / win.height,
|
|
50
|
+
type: 'screen'
|
|
51
|
+
}
|
|
52
|
+
}
|