@symbo.ls/scratch 0.2.26

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/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # Φ / Scratch framework
2
+ Scratch is CSS framework and methodology to build web, mobile and TV applications with one code base. Using it means to follow some practices to manage design system and make configuration based cascading styles.
3
+
4
+ [![npm version](https://badge.fury.io/js/%40rackai%2Fscratch.svg)](https://badge.fury.io/js/%40rackai%2Fscratch)
5
+
6
+ ### TODO:
7
+ - [ ] Generate CSS variables
8
+ - [ ] Accessibility (WCAG) automated tests
9
+ - [ ] Scratch on `node`
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@symbo.ls/scratch",
3
+ "description": "Φ / CSS framework and methodology.",
4
+ "author": "Symbols",
5
+ "version": "0.2.26",
6
+ "files": [
7
+ "src"
8
+ ],
9
+ "repository": "https://github.com/rackai/scratch",
10
+ "main": "src/index.js",
11
+ "publishConfig": {},
12
+ "dependencies": {
13
+ "color-contrast-checker": "^1.5.0"
14
+ },
15
+ "scripts": {
16
+ "standard": "npx standard \"src/**/*.js\"",
17
+ "test": "yarn standard && jest --coverage --coverageReporters=text-lcov | coveralls",
18
+ "test-watch": "jest --watch",
19
+ "bump": "npx np"
20
+ },
21
+ "devDependencies": {
22
+ "@babel/core": "^7.10.4",
23
+ "@babel/preset-env": "^7.10.4",
24
+ "babel-eslint": "^10.0.3",
25
+ "babel-jest": "^27.0.2",
26
+ "babel-preset-env": "^1.7.0",
27
+ "coveralls": "^3.0.5",
28
+ "eslint": "^7.12.1",
29
+ "eslint-plugin-jest": "^24.1.0",
30
+ "jest": "^27.0.6",
31
+ "nodemon": "^2.0.6",
32
+ "np": "^7.2.0",
33
+ "parcel-bundler": "^1.12.4",
34
+ "standard": "^16.0.1"
35
+ },
36
+ "jest": {
37
+ "collectCoverageFrom": [
38
+ "src/**/*.js"
39
+ ]
40
+ },
41
+ "standard": {
42
+ "parser": "babel-eslint",
43
+ "env": [
44
+ "jest"
45
+ ]
46
+ },
47
+ "browserslist": [
48
+ "> 1%",
49
+ "ie >= 9"
50
+ ]
51
+ }
@@ -0,0 +1,10 @@
1
+ 'use strict'
2
+
3
+ var proto = { // eslint-disable-line
4
+ name: '',
5
+ value: '',
6
+ themes: {}
7
+ }
8
+
9
+ export const COLOR = {}
10
+ export const GRADIENT = {}
@@ -0,0 +1,10 @@
1
+ 'use strict'
2
+
3
+ import { FONT_FAMILY, THEME, TYPOGRAPHY } from '.'
4
+
5
+ export const DOCUMENT = {
6
+ theme: THEME.document,
7
+ fontFamily: FONT_FAMILY[Object.keys(FONT_FAMILY)[0]],
8
+ fontSize: TYPOGRAPHY.base,
9
+ lineHeight: TYPOGRAPHY.styles.lineHeight
10
+ }
@@ -0,0 +1,16 @@
1
+ 'use strict'
2
+
3
+ import { getFontFace } from '../utils'
4
+
5
+ var defaultFont = { // eslint-disable-line
6
+ name: '',
7
+ family: '',
8
+ type: ''
9
+ }
10
+
11
+ export const FONT_FAMILY = {}
12
+ export const FONT_FAMILY_TYPES = {
13
+ serif: 'Helvetica, Arial, sans-serif, --system-default',
14
+ 'sans-serif': 'Times New Roman, Georgia, serif, --system-default'
15
+ }
16
+ export const FONT_FACE = getFontFace(FONT_FAMILY)
@@ -0,0 +1,12 @@
1
+ 'use strict'
2
+
3
+ var defFont = { // eslint-disable-line
4
+ name: '',
5
+ url: '',
6
+ fontStyle: '',
7
+ fontWeight: 500,
8
+ fontStretch: 'normal',
9
+ fontOpticalSizing: 'auto'
10
+ }
11
+
12
+ export const FONT = {}
@@ -0,0 +1,17 @@
1
+ 'use strict'
2
+
3
+ export * from './sequence'
4
+ export * from './unit'
5
+
6
+ export * from './typography'
7
+ export * from './font'
8
+ export * from './font-family'
9
+
10
+ export * from './spacing'
11
+
12
+ export * from './color'
13
+ export * from './theme'
14
+
15
+ export * from './responsive'
16
+
17
+ export * from './document'
File without changes
@@ -0,0 +1,13 @@
1
+ 'use strict'
2
+
3
+ export const RESPONSIVE = {
4
+ screenL: 1920,
5
+ screenD: 1680,
6
+ screenS: 1440,
7
+ tabletL: 1366,
8
+ tabletM: 1280,
9
+ tabletS: 1024,
10
+ mobileL: 768,
11
+ mobileM: 560,
12
+ mobileS: 480
13
+ }
@@ -0,0 +1,24 @@
1
+ 'use strict'
2
+
3
+ export const SEQUENCE = {
4
+ 'minor-second': 1.067,
5
+ 'major-second': 1.125,
6
+ 'minor-third': 1.2,
7
+ 'major-third': 1.25,
8
+ 'perfect-fourth': 1.333,
9
+ 'augmented-fourth': 1.414,
10
+ 'perfect-fifth': 1.5,
11
+ 'minor-sixth': 1.6,
12
+ phi: 1.618, // golden-ratio
13
+ 'major-sixth': 1.667,
14
+ 'square-root-3': 1.732, // theodorus
15
+ 'minor-seventh': 1.778,
16
+ 'major-seventh': 1.875,
17
+ octave: 2,
18
+ 'square-root-5': 2.23, // pythagoras
19
+ 'major-tenth': 2.5,
20
+ 'major-eleventh': 2.667,
21
+ 'major-twelfth': 3,
22
+ pi: 3.14, // archimedes
23
+ 'double-octave': 4
24
+ }
@@ -0,0 +1,66 @@
1
+ 'use strict'
2
+
3
+ import { SEQUENCE, TYPOGRAPHY } from '.'
4
+ import { Arrayize, fallBack, generateSequence } from '../utils'
5
+
6
+ const defaultProps = {
7
+ base: TYPOGRAPHY.base,
8
+ type: 'spacing',
9
+ ratio: SEQUENCE['phi'],
10
+ range: [-5, +7],
11
+ subSequence: true,
12
+ sequence: {},
13
+ scales: {}
14
+ }
15
+
16
+ generateSequence(defaultProps)
17
+
18
+ const getSequence = (props) => {
19
+ if (!props) return
20
+ const hasGenerated = Object.keys(props.sequence).length > 0
21
+ return hasGenerated ? props : generateSequence(props)
22
+ }
23
+
24
+ export const mapSpacing = (val, property = 'padding', props) => {
25
+ const prefix = '--spacing-'
26
+
27
+ const generatedSequence = getSequence(props)
28
+ const type = (generatedSequence || defaultProps).sequence
29
+
30
+ const stack = Arrayize(val)
31
+ if (!stack) return
32
+
33
+ const length = stack.length
34
+
35
+ const wrapFallBack = (prop, i) => fallBack({
36
+ type,
37
+ prop,
38
+ val: stack[i],
39
+ prefix
40
+ })
41
+
42
+ if (length === 2) {
43
+ return [
44
+ wrapFallBack(property + 'Block', 0),
45
+ wrapFallBack(property + 'Inline', 1)
46
+ ]
47
+ }
48
+ if (length === 3) {
49
+ return [
50
+ wrapFallBack(property + 'BlockStart', 0),
51
+ wrapFallBack(property + 'Inline', 1),
52
+ wrapFallBack(property + 'BlockEnd', 2)
53
+ ]
54
+ } else if (length === 4) {
55
+ return [
56
+ wrapFallBack(property + 'BlockStart', 0),
57
+ wrapFallBack(property + 'InlineStart', 3),
58
+ wrapFallBack(property + 'BlockEnd', 2),
59
+ wrapFallBack(property + 'InlineEnd', 1)
60
+ ]
61
+ }
62
+
63
+ return fallBack({ type, prop: property, val, prefix })
64
+ }
65
+
66
+ export const SPACING = defaultProps
@@ -0,0 +1,20 @@
1
+ 'use strict'
2
+
3
+ const themeA = { // eslint-disable-line no-unused-vars
4
+ text: 'blue',
5
+ background: 'white',
6
+ border: 'black', // .opacity(0.2),
7
+ active: {},
8
+ helpers: [],
9
+ themes: {},
10
+ inverse: {} // schemeAInverse
11
+ }
12
+
13
+ export const THEME = {}
14
+
15
+ export const returnSubThemeOrDefault = (orig, theme) => {
16
+ if (!orig) return
17
+ if (orig.themes && orig.themes[theme]) return orig.themes[theme]
18
+ if (orig[theme]) return [orig, orig[theme]]
19
+ return orig
20
+ }
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ export const TIMING = {
4
+ base: 16
5
+ }
@@ -0,0 +1,39 @@
1
+ 'use strict'
2
+
3
+ import { SEQUENCE } from '.'
4
+ import { fallBack, generateSequence, findHeadings } from '../utils'
5
+
6
+ const defaultProps = {
7
+ default: 16,
8
+ base: 16,
9
+ type: 'font-size',
10
+ ratio: SEQUENCE['minor-third'],
11
+ range: [-3, +12],
12
+ h1Matches: +6,
13
+ styles: {
14
+ lineHeight: 1.5
15
+ },
16
+ sequence: {},
17
+ scales: {}
18
+ }
19
+
20
+ generateSequence(defaultProps)
21
+
22
+ if (defaultProps.h1Matches) {
23
+ var HEADINGS = findHeadings(defaultProps)
24
+ const { styles } = defaultProps
25
+ for (const k in HEADINGS) {
26
+ styles[`h${parseInt(k) + 1}`] = {
27
+ fontSize: `${HEADINGS[k].scaling}em`
28
+ }
29
+ }
30
+ }
31
+
32
+ export const mapFontSize = val => fallBack({
33
+ type: defaultProps.sequence,
34
+ prop: 'fontSize',
35
+ val,
36
+ prefix: '--font-size-'
37
+ })
38
+
39
+ export const TYPOGRAPHY = defaultProps
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ export const UNIT = {
4
+ default: 'em'
5
+ }
package/src/factory.js ADDED
@@ -0,0 +1,6 @@
1
+ 'use strict'
2
+
3
+ import * as CONFIG from './config'
4
+
5
+ export const CSS_VARS = {}
6
+ export default CONFIG
package/src/index.js ADDED
@@ -0,0 +1,11 @@
1
+ 'use strict'
2
+
3
+ import CONFIG from './factory'
4
+
5
+ export * from './factory'
6
+ export * from './utils'
7
+ export * from './methods'
8
+ export * from './config'
9
+ export * from './reset'
10
+
11
+ export default CONFIG
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ import base from '../base'
4
+
5
+ var create = (what, params, preset) => {
6
+ base[what] = preset(params)
7
+ }
8
+
9
+ export default { create }
@@ -0,0 +1,40 @@
1
+ 'use strict'
2
+
3
+ import { themeMap } from '../config/theme'
4
+
5
+ // var pairAsInvert = (scheme, referenced) => cx(scheme, referenced)
6
+
7
+ var mapThemeCSS = scheme => {
8
+ var str = ''
9
+ for (const prop in scheme) {
10
+ var mappedProp = themeMap[prop]
11
+ var value = scheme[prop]
12
+ if (mappedProp && value) {
13
+ str += `${mappedProp}: ${value}`
14
+ }
15
+ }
16
+ return str
17
+ }
18
+
19
+ var generateTheme = scheme => {
20
+ var { helpers, inverse } = scheme
21
+
22
+ var rule = `
23
+ ${mapThemeCSS(scheme)}
24
+ `
25
+
26
+ if (inverse) {
27
+ rule += `&.inverse { ${inverse} }`
28
+ }
29
+
30
+ if (helpers) {
31
+ for (var prop in helpers) {
32
+ var value = helpers[prop]
33
+ rule += `.${prop} { ${value} }`
34
+ }
35
+ }
36
+
37
+ return rule
38
+ }
39
+
40
+ export default { generateTheme }
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ export * from './set'
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ var global = ''
4
+
5
+ export default global
@@ -0,0 +1,235 @@
1
+ 'use strict'
2
+
3
+ import CONFIG, { CSS_VARS } from '../factory'
4
+ // import { FONT, FONT_FAMILY_TYPES } from '../config'
5
+ import {
6
+ isArray,
7
+ colorStringToRgbaArray,
8
+ isObject,
9
+ isString,
10
+ isObjectLike,
11
+ getColorShade,
12
+ hexToRgbArray,
13
+ rgbArrayToHex,
14
+ getDefaultOrFirstKey,
15
+ getFontFaceEach
16
+ } from '../utils'
17
+
18
+ const setColor = (val, key) => {
19
+ const [r, g, b, a = 1] = colorStringToRgbaArray(val.value || val)
20
+ const alpha = parseFloat(a.toFixed(2))
21
+ const CSSVar = `--color-${key}`
22
+ const rgb = `${r}, ${g}, ${b}`
23
+ const value = `rgba(${rgb}, ${alpha})`
24
+
25
+ return {
26
+ var: CSSVar,
27
+ rgb,
28
+ alpha,
29
+ value
30
+ }
31
+ }
32
+
33
+ const setGradient = (val, key) => {
34
+ const CSSVar = `--gradient-${key}`
35
+ return {
36
+ var: CSSVar,
37
+ value: val.value || val
38
+ }
39
+ }
40
+
41
+ export const getColor = value => {
42
+ if (!isString(value)) return console.warn(value, '- type for color is not valid')
43
+
44
+ const [name, alpha, tone] = isArray(value) ? value : value.split(' ')
45
+ const { COLOR, GRADIENT } = CONFIG
46
+ const val = COLOR[name] || GRADIENT[name]
47
+
48
+ if (!val) {
49
+ console.warn('Can\'t find color', name)
50
+ return value
51
+ }
52
+
53
+ // TODO: support variables
54
+ // if (alpha) return `rgba(var(${val[shade || ''].var}), ${modifier})`
55
+
56
+ let rgb = val.rgb
57
+ if (rgb) {
58
+ if (tone) {
59
+ if (!val[tone]) {
60
+ const toHex = rgbArrayToHex(rgb.split(', '))
61
+ rgb = hexToRgbArray(getColorShade(toHex, tone)).join(', ')
62
+ val[tone] = { rgb, var: `${val.var}-${tone}` }
63
+ } else rgb = val[tone].rgb
64
+ }
65
+ if (alpha) return `rgba(${val.rgb}, ${alpha})`
66
+ return `rgb(${rgb})`
67
+ } else return val.value
68
+ }
69
+
70
+ const setThemeValue = theme => {
71
+ const value = {}
72
+ const { state, variants, helpers, ...rest } = theme
73
+ const keys = Object.keys(rest)
74
+ keys.map(key => {
75
+ const conditions = ['color', 'Color', 'background', 'border']
76
+ const isColor = conditions.some(k => key.includes(k))
77
+ return (value[key] = isColor ? getColor(theme[key]) : theme[key])
78
+ })
79
+ return value
80
+ }
81
+
82
+ const getThemeValue = theme => {
83
+ if (theme.value) return theme.value
84
+ theme.value = setThemeValue(theme)
85
+ return theme.value
86
+ }
87
+
88
+ export const getTheme = value => {
89
+ const { THEME } = CONFIG
90
+ if (isObjectLike(value) && value[1]) {
91
+ const themeName = value[0]
92
+ const subThemeName = value[1]
93
+ const { helpers, variants, state } = THEME[themeName]
94
+ if (variants && variants[subThemeName]) return getThemeValue(variants[subThemeName])
95
+ if (helpers && helpers[subThemeName]) return getThemeValue(helpers[subThemeName])
96
+ if (state && state[subThemeName]) return getThemeValue(state[subThemeName])
97
+ } else if (isObject(value)) return setThemeValue(value)
98
+ else if (isString(value) && THEME[value]) return getThemeValue(THEME[value])
99
+ console.warn('Can\'t find theme', value)
100
+ }
101
+
102
+ const setPrefersScheme = (theme, key, variant, themeValue) => {
103
+ const result = getTheme(variant)
104
+ themeValue[`@media (prefers-color-scheme: ${key})`] = result
105
+ if (isObject(variant) && !variant.value) variant.value = result
106
+ }
107
+
108
+ const setInverseTheme = (theme, variant, value) => {
109
+ if (isObject(variant)) {
110
+ theme.variants.inverse.value = setThemeValue(variant)
111
+ } else if (variant === true) {
112
+ const { color, background } = value
113
+ theme.variants.inverse = {
114
+ value: {
115
+ color: background,
116
+ background: color
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ const goThroughHelpers = (theme, value) => {
123
+ const { helpers } = theme
124
+ if (!helpers) return
125
+ const keys = Object.keys(helpers)
126
+ keys.map(key => {
127
+ const helper = helpers[key]
128
+ if (isString(helper)) helpers[key] = CONFIG.THEME[helper]
129
+ else getThemeValue(helpers[key])
130
+ return theme
131
+ })
132
+ return theme
133
+ }
134
+
135
+ const goThroughVariants = (theme, value) => {
136
+ const { variants } = theme
137
+ if (!variants) return
138
+ const keys = Object.keys(variants)
139
+ keys.map(key => {
140
+ const variant = variants[key]
141
+ if (key === 'dark' || key === 'light') setPrefersScheme(theme, key, variant, value)
142
+ if (key === 'inverse') setInverseTheme(theme, variant, value)
143
+ return theme
144
+ })
145
+ return theme
146
+ }
147
+
148
+ const setPseudo = (theme, key, variant, themeValue) => {
149
+ const result = getTheme(variant)
150
+ themeValue[`&:${key}`] = result
151
+ if (isObject(variant) && !variant.value) variant.value = result
152
+ }
153
+
154
+ const goThroughInteractiveStates = (theme, value) => {
155
+ const { state } = theme
156
+ if (!state) return
157
+ const keys = Object.keys(state)
158
+ keys.map(key => {
159
+ const variant = state[key]
160
+ setPseudo(theme, key, variant, value)
161
+ return theme
162
+ })
163
+ return theme
164
+ }
165
+
166
+ const setTheme = (val, key) => {
167
+ const { state, variants, helpers } = val
168
+ const value = setThemeValue(val, key)
169
+ const CSSvar = `--theme-${key}`
170
+
171
+ goThroughInteractiveStates(val, value)
172
+ goThroughVariants(val, value)
173
+ goThroughHelpers(val, value)
174
+
175
+ return { var: CSSvar, value, state, variants, helpers }
176
+ }
177
+
178
+ const setFont = (val, key) => {
179
+ const CSSvar = `--font-${key}`
180
+ const fontFace = getFontFaceEach(key, val)
181
+ return { var: CSSvar, value: val, fontFace }
182
+ }
183
+
184
+ export const getFontFamily = (LIBRARY, key) => {
185
+ return getDefaultOrFirstKey(LIBRARY, key)
186
+ }
187
+
188
+ const setFontFamily = (val, key) => {
189
+ const { FONT_FAMILY, FONT_FAMILY_TYPES } = CONFIG
190
+ const { value, type } = val
191
+ if (val.default) FONT_FAMILY.default = key
192
+
193
+ const CSSvar = `--font-family-${key}`
194
+ const str = `${value.join(', ')}, ${FONT_FAMILY_TYPES[type]}`
195
+ return { var: CSSvar, value: str, arr: value, type }
196
+ }
197
+
198
+ export const SETTERS = {
199
+ color: setColor,
200
+ gradient: setGradient,
201
+ font: setFont,
202
+ font_family: setFontFamily,
203
+ theme: setTheme
204
+ }
205
+
206
+ /**
207
+ *
208
+ * @param {Object} FACTORY_NAME Defines which factory it goes to
209
+ * @param {*} value Value of the property
210
+ * @param {String} key Key, or the name of the property
211
+ * @returns {Object} Factory
212
+ */
213
+ export const setValue = (FACTORY_NAME, value, key) => {
214
+ const factoryName = FACTORY_NAME.toLowerCase()
215
+ const FACTORY = CONFIG[FACTORY_NAME]
216
+ const result = SETTERS[factoryName](value, key)
217
+ FACTORY[key] = result
218
+ CSS_VARS[result.var] = result.value
219
+ return FACTORY
220
+ }
221
+
222
+ export const setEach = (factoryName, props) => {
223
+ const FACTORY_NAME = factoryName.toUpperCase()
224
+ const keys = Object.keys(props)
225
+ keys.map(key => setValue(FACTORY_NAME, props[key], key))
226
+ return CONFIG[FACTORY_NAME]
227
+ }
228
+
229
+ export const set = config => {
230
+ const keys = Object.keys(config)
231
+ keys.map(key => setEach(key, config[key]))
232
+ return CONFIG
233
+ }
234
+
235
+ export default set
package/src/reset.js ADDED
@@ -0,0 +1,35 @@
1
+ 'use strict'
2
+
3
+ import * as CONFIG from './config'
4
+
5
+ export const RESET = {
6
+ html: {
7
+ position: 'fixed',
8
+ overflow: 'hidden',
9
+ width: '100%',
10
+ height: '100%',
11
+ top: '0',
12
+ left: '0',
13
+ margin: '0',
14
+ WebkitFontSmoothing: 'antialiased',
15
+
16
+ fontFamily: CONFIG.DOCUMENT.fontFamily,
17
+
18
+ fontSize: CONFIG.TYPOGRAPHY.default / CONFIG.DOCUMENT.fontSize + CONFIG.UNIT.default,
19
+ lineHeight: CONFIG.DOCUMENT.lineHeight
20
+ },
21
+
22
+ body: {
23
+ boxSizing: 'border-box',
24
+ height: '100%'
25
+ },
26
+
27
+ ...CONFIG.TYPOGRAPHY.styles,
28
+
29
+ // form elements
30
+ fieldset: {
31
+ border: 0,
32
+ padding: 0,
33
+ margin: 0
34
+ }
35
+ }
@@ -0,0 +1,4 @@
1
+ 'use strict'
2
+
3
+ export const testWCAGAA = (colorA, colorB) => {}
4
+ export const testWCAGAAA = (colorA, colorB) => {}
@@ -0,0 +1,292 @@
1
+ 'use strict'
2
+
3
+ import { UNIT } from '../config'
4
+
5
+ export const isString = arg => typeof arg === 'string'
6
+
7
+ export const isArray = arg => Array.isArray(arg)
8
+
9
+ export const isObject = arg => {
10
+ if (arg === null) return false
11
+ return (typeof arg === 'object') && (arg.constructor === Object)
12
+ }
13
+
14
+ export const isObjectLike = arg => {
15
+ if (arg === null) return false
16
+ return (typeof arg === 'object')
17
+ }
18
+
19
+ export const merge = (obj, original) => {
20
+ for (const e in original) {
21
+ const objProp = obj[e]
22
+ const originalProp = original[e]
23
+ if (objProp === undefined) {
24
+ obj[e] = originalProp
25
+ }
26
+ }
27
+ return obj
28
+ }
29
+
30
+ export const colorStringToRgbaArray = color => {
31
+ if (color === '') return
32
+ if (color.toLowerCase() === 'transparent') return [0, 0, 0, 0]
33
+
34
+ // convert #RGB and #RGBA to #RRGGBB and #RRGGBBAA
35
+ if (color[0] === '#') {
36
+ if (color.length < 7) {
37
+ color = '#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3] + (color.length > 4 ? color[4] + color[4] : '')
38
+ } return [parseInt(color.substr(1, 2), 16),
39
+ parseInt(color.substr(3, 2), 16),
40
+ parseInt(color.substr(5, 2), 16),
41
+ color.length > 7 ? parseInt(color.substr(7, 2), 16) / 255 : 1]
42
+ }
43
+
44
+ // convert named colors
45
+ if (color.indexOf('rgb') === -1) {
46
+ // intentionally use unknown tag to lower chances of css rule override with !important
47
+ const elem = document.body.appendChild(document.createElement('fictum'))
48
+ // this flag tested on chrome 59, ff 53, ie9, ie10, ie11, edge 14
49
+ const flag = 'rgb(1, 2, 3)'
50
+ elem.style.color = flag
51
+ // color set failed - some monstrous css rule is probably taking over the color of our object
52
+ if (elem.style.color !== flag) return
53
+ elem.style.color = color
54
+ if (elem.style.color === flag || elem.style.color === '') return // color parse failed
55
+ color = window.getComputedStyle(elem).color
56
+ document.body.removeChild(elem)
57
+ }
58
+
59
+ // convert 'rgb(R,G,B)' to 'rgb(R,G,B,A)' which looks awful but will pass the regxep below
60
+ if (color.indexOf('rgb') === 0) {
61
+ if (color.indexOf('rgba') === -1) color = `${color}, 1`
62
+ return color.match(/[\.\d]+/g).map(a => +a) // eslint-disable-line
63
+ }
64
+ }
65
+
66
+ export const mixTwoColors = (colorA, colorB, range = 0.5) => {
67
+ colorA = colorStringToRgbaArray(colorA)
68
+ colorB = colorStringToRgbaArray(colorB)
69
+ return mixTwoRgba(colorA, colorB, range)
70
+ }
71
+
72
+ export const hexToRgb = (hex, alpha = 1) => {
73
+ const [r, g, b] = hex.match(/\w\w/g).map(x => parseInt(x, 16))
74
+ return `rgb(${r},${g},${b})`
75
+ }
76
+
77
+ export const hexToRgbArray = (hex, alpha = 1) => {
78
+ const [r, g, b] = hex.match(/\w\w/g).map(x => parseInt(x, 16))
79
+ return [r, g, b]
80
+ }
81
+
82
+ export const rgbToHex = (r, g, b) => {
83
+ return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)
84
+ }
85
+
86
+ export const rgbArrayToHex = ([r, g, b]) => {
87
+ return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)
88
+ }
89
+
90
+ export const hexToRgba = (hex, alpha = 1) => {
91
+ const [r, g, b] = hex.match(/\w\w/g).map(x => parseInt(x, 16))
92
+ return `rgba(${r},${g},${b},${alpha})`
93
+ }
94
+
95
+ export const mixTwoRgb = (colorA, colorB, range = 0.5) => {
96
+ const arr = []
97
+ for (let i = 0; i < 3; i++) {
98
+ arr[i] = Math.round(
99
+ colorA[i] + (
100
+ (colorB[i] - colorA[i]) * range
101
+ )
102
+ )
103
+ }
104
+ return `rgb(${arr})`
105
+ }
106
+
107
+ export const getColorShade = (col, amt) => {
108
+ const num = parseInt(col, 16)
109
+
110
+ let r = (num >> 16) + amt
111
+
112
+ if (r > 255) r = 255
113
+ else if (r < 0) r = 0
114
+
115
+ let b = ((num >> 8) & 0x00FF) + amt
116
+
117
+ if (b > 255) b = 255
118
+ else if (b < 0) b = 0
119
+
120
+ let g = (num & 0x0000FF) + amt
121
+
122
+ if (g > 255) g = 255
123
+ else if (g < 0) g = 0
124
+
125
+ return (g | (b << 8) | (r << 16)).toString(16)
126
+ }
127
+
128
+ export const mixTwoRgba = (colorA, colorB, range = 0.5) => {
129
+ const arr = []
130
+ for (let i = 0; i < 4; i++) {
131
+ const round = (i === 3) ? x => x : Math.round
132
+ arr[i] = round(
133
+ (colorA[i] + (
134
+ (colorB[i] - colorA[i]) * range
135
+ ))
136
+ )
137
+ }
138
+ return `rgba(${arr})`
139
+ }
140
+
141
+ export const opacify = (color, opacity) => {
142
+ const arr = colorStringToRgbaArray(color)
143
+ if (!arr) return console.warn(color + 'color is not rgba')
144
+ arr[3] = opacity
145
+ return `rgba(${arr})`
146
+ }
147
+
148
+ export const getDefaultOrFirstKey = (LIBRARY, key) => {
149
+ if (LIBRARY[key]) return LIBRARY[key].value
150
+ if (LIBRARY.default) return LIBRARY[LIBRARY.default].value
151
+ return LIBRARY[Object.keys(LIBRARY)[0]].value
152
+ }
153
+
154
+ export const getFontFormat = url => url.split(/[#?]/)[0].split('.').pop().trim()
155
+
156
+ export const setCustomFont = (name, weight, url) => `@font-face {
157
+ font-family: '${name}';
158
+ font-style: normal;
159
+ font-weight: ${weight};
160
+ src: url('${url}') format('${getFontFormat(url)}');
161
+ }`
162
+ // src: url('${url}') format('${getFontFormat(url)}');
163
+
164
+ export const getFontFaceEach = (name, weightsObject) => {
165
+ const keys = Object.keys(weightsObject)
166
+ const weightsJoint = keys.map(key => {
167
+ const { fontWeight, url } = weightsObject[key]
168
+ return setCustomFont(name, fontWeight, url)
169
+ })
170
+ return weightsJoint.join('\n')
171
+ }
172
+
173
+ export const getFontFace = LIBRARY => {
174
+ const keys = Object.keys(LIBRARY)
175
+ const fontsJoint = keys.map(key => getFontFaceEach(key, LIBRARY[key].value))
176
+ return fontsJoint.join('\n')
177
+ }
178
+
179
+ export const numToLetterMap = {
180
+ '-6': 'U',
181
+ '-5': 'V',
182
+ '-4': 'W',
183
+ '-3': 'X',
184
+ '-2': 'Y',
185
+ '-1': 'Z',
186
+ 0: 'A',
187
+ 1: 'B',
188
+ 2: 'C',
189
+ 3: 'D',
190
+ 4: 'E',
191
+ 5: 'F',
192
+ 6: 'G',
193
+ 7: 'H',
194
+ 8: 'I',
195
+ 9: 'J',
196
+ 10: 'K',
197
+ 11: 'L',
198
+ 12: 'M',
199
+ 13: 'N',
200
+ 14: 'O',
201
+ 15: 'P'
202
+ }
203
+
204
+ const setSequenceValue = ({ key, variable, value, scaling, state, index }) => {
205
+ state.sequence[variable] = {
206
+ key,
207
+ decimal: Math.round(value * 100) / 100,
208
+ val: Math.round(value),
209
+ scaling,
210
+ index
211
+ }
212
+ state.scales[variable] = scaling
213
+ }
214
+
215
+ export const generateSubSequence = ({ key, base, value, ratio, variable, state }) => {
216
+ const next = value * ratio
217
+ const smallscale = (next - value) / ratio
218
+
219
+ const valueRounded = Math.round(value)
220
+ const nextRounded = Math.round(next)
221
+ const diffRounded = nextRounded - valueRounded
222
+
223
+ let arr = []
224
+ const first = next - smallscale
225
+ const second = value + smallscale
226
+ const middle = (first + second) / 2
227
+ if (diffRounded > 100) arr = [first, middle, second]
228
+ else arr = [first, second]
229
+ // else if (diffRounded > 2) arr = [first, second]
230
+ // else if (diffRounded > 1) arr = [middle]
231
+
232
+ arr.map((v, k) => {
233
+ const scaling = Math.round(v / base * 1000) / 1000
234
+ const newVar = variable + (k + 1)
235
+
236
+ return setSequenceValue({ key: key + (k + 1), variable: newVar, value: v, scaling, state })
237
+ })
238
+ }
239
+
240
+ export const generateSequence = ({ type, base, ratio, range, subSequence, ...state }) => {
241
+ const n = Math.abs(range[0]) + Math.abs(range[1])
242
+ const prefix = '--' + type + '-'
243
+ for (let i = 0; i <= n; i++) {
244
+ const key = range[1] - i
245
+ const letterKey = numToLetterMap[key]
246
+ const value = base * Math.pow(ratio, key)
247
+ const scaling = Math.round(value / base * 1000) / 1000
248
+ const variable = prefix + letterKey
249
+
250
+ setSequenceValue({ key: letterKey, variable, value, scaling, state, index: key })
251
+
252
+ if (subSequence) generateSubSequence({ key: letterKey, base, value, ratio, variable, state })
253
+ }
254
+ return state
255
+ }
256
+
257
+ export const fallBack = ({ type, prop, val = 'A', prefix = '--font-size-' }) => {
258
+ if (typeof val !== 'string') console.warn(prop, val, 'is not a string')
259
+
260
+ // const startsWithLetterRegex = /^[a-zA-Z]/i
261
+ const startsWithLetterRegex = /^-?[a-zA-Z]/i
262
+ // const hasLetter = /[A-Za-z]+/.test(val)
263
+ const startsWithLetter = startsWithLetterRegex.test(val)
264
+ if (!startsWithLetter) return ({ [prop]: val })
265
+
266
+ const letterVal = val.toUpperCase()
267
+ const isNegative = letterVal.slice(0, 1) === '-' ? '-' : ''
268
+ const simplyLetterVal = isNegative ? letterVal.slice(1) : letterVal
269
+
270
+ const value = type ? type[prefix + simplyLetterVal] : null
271
+ if (!value) return console.warn('can\'t find', type, prefix + simplyLetterVal, simplyLetterVal)
272
+
273
+ return ({
274
+ [prop]: isNegative + value.val + UNIT.default,
275
+ [prop]: isNegative + value.scaling + 'em'
276
+ })
277
+ }
278
+
279
+ export const Arrayize = val => {
280
+ const isString = typeof val === 'string'
281
+ if (isString) return val.split(' ')
282
+ if (isObject(val)) return Object.keys(val).map(v => val[v])
283
+ if (isArray(val)) return val
284
+ }
285
+
286
+ export const findHeadings = (TYPOGRAPHY) => {
287
+ const { h1Matches, type, sequence } = TYPOGRAPHY
288
+ return new Array(6).fill(null).map((_, i) => {
289
+ const findLetter = numToLetterMap[h1Matches - i]
290
+ return sequence[`--${type}-${findLetter}`]
291
+ })
292
+ }