@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 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
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ GLOBAL_NAME: '__CSS_GLOBAL__',
3
+ LOCAL_NAME: '__CSS_LOCAL__'
4
+ }
package/dimensions.js ADDED
@@ -0,0 +1,5 @@
1
+ import { observable } from '@nx-js/observer-util'
2
+
3
+ export default observable({
4
+ width: 0
5
+ })
@@ -0,0 +1,7 @@
1
+ import * as platformHelpers from '../platformHelpers/react-native.js'
2
+ import { setPlatformHelpers } from '../platformHelpers/index.js'
3
+ import { process } from '../processCached.js'
4
+
5
+ setPlatformHelpers(platformHelpers)
6
+
7
+ export default process
@@ -0,0 +1,7 @@
1
+ import * as platformHelpers from '../platformHelpers/react-native.js'
2
+ import { setPlatformHelpers } from '../platformHelpers/index.js'
3
+ import { process } from '../process.js'
4
+
5
+ setPlatformHelpers(platformHelpers)
6
+
7
+ export default process
@@ -0,0 +1,7 @@
1
+ import { setPlatformHelpers } from '../platformHelpers/index.js'
2
+ import * as platformHelpers from '../platformHelpers/web.js'
3
+ import { process } from '../processCached.js'
4
+
5
+ setPlatformHelpers(platformHelpers)
6
+
7
+ export default process
@@ -0,0 +1,7 @@
1
+ import { setPlatformHelpers } from '../platformHelpers/index.js'
2
+ import * as platformHelpers from '../platformHelpers/web.js'
3
+ import { process } from '../process.js'
4
+
5
+ setPlatformHelpers(platformHelpers)
6
+
7
+ export default process
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,9 @@
1
+ import { Dimensions, Platform } from 'react-native'
2
+
3
+ export function getDimensions () {
4
+ return Dimensions.get('window')
5
+ }
6
+
7
+ export function getPlatform () {
8
+ return Platform.OS
9
+ }
@@ -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
+ }
@@ -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,9 @@
1
+ import { observable } from '@nx-js/observer-util'
2
+
3
+ export let defaultVariables = {}
4
+
5
+ export default observable({})
6
+
7
+ export function setDefaultVariables (variables = {}) {
8
+ defaultVariables = { ...variables }
9
+ }
@@ -0,0 +1,8 @@
1
+ # Credits
2
+
3
+ [kristerkary](https://github.com/kristerkari)
4
+
5
+ Original code taken from:
6
+ https://github.com/kristerkari/react-native-css-media-query-processor
7
+
8
+ Original version: 0.21.3
@@ -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,8 @@
1
+ # Credits
2
+
3
+ [kristerkary](https://github.com/kristerkari)
4
+
5
+ Original code taken from:
6
+ https://github.com/kristerkari/react-native-dynamic-style-processor
7
+
8
+ Original version: 0.21.0
@@ -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
+ }