pleroma-fe/src/services/theme_data/theme_data.service.js

809 lines
21 KiB
JavaScript
Raw Normal View History

2020-01-12 02:44:06 +01:00
import { convert, brightness, contrastRatio } from 'chromatism'
import { alphaBlend, alphaBlendLayers, getTextColor, mixrgb } from '../color_convert/color_convert.js'
2020-01-19 19:59:54 +01:00
/*
* # What's all this?
* Here be theme engine for pleromafe. All of this supposed to ease look
* and feel customization, making widget styles and make developer's life
* easier when it comes to supporting themes. Like many other theme systems
* it operates on color definitions, or "slots" - for example you define
* "button" color slot and then in UI component Button's CSS you refer to
* it as a CSS3 Variable.
*
* Some applications allow you to customize colors for certain things.
* Some UI toolkits allow you to define colors for each type of widget.
* Most of them are pretty barebones and have no assistance for common
* problems and cases, and in general themes themselves are very hard to
* maintain in all aspects. This theme engine tries to solve all of the
* common problems with themes.
*
* You don't have redefine several similar colors if you just want to
* change one color - all color slots are derived from other ones, so you
* can have at least one or two "basic" colors defined and have all other
* components inherit and modify basic ones.
*
* You don't have to test contrast ratio for colors or pick text color for
* each element even if you have light-on-dark elements in dark-on-light
* theme.
*
* You don't have to maintain order of code for inheriting slots from othet
* slots - dependency graph resolving does it for you.
*/
/* This indicates that this version of code outputs similar theme data and
* should be incremented if output changes - for instance if getTextColor
* function changes and older themes no longer render text colors as
* author intended previously.
*/
2020-01-12 02:44:06 +01:00
export const CURRENT_VERSION = 3
2020-01-19 19:59:54 +01:00
2020-01-12 02:44:06 +01:00
/* This is a definition of all layer combinations
* each key is a topmost layer, each value represents layer underneath
* this is essentially a simplified tree
*/
export const LAYERS = {
undelay: null, // root
topBar: null, // no transparency support
badge: null, // no transparency support
fg: null,
bg: 'underlay',
lightBg: 'bg',
2020-01-12 02:44:06 +01:00
panel: 'bg',
btn: 'bg',
btnPanel: 'panel',
btnTopBar: 'topBar',
input: 'bg',
inputPanel: 'panel',
inputTopBar: 'topBar',
alert: 'bg',
2020-01-12 22:41:11 +01:00
alertPanel: 'panel',
poll: 'bg'
2020-01-12 02:44:06 +01:00
}
2020-01-19 19:59:54 +01:00
/* By default opacity slots have 1 as default opacity
* this allows redefining it to something else
*/
export const DEFAULT_OPACITY = {
alert: 0.5,
input: 0.5,
faint: 0.5,
2020-01-16 19:53:05 +01:00
underlay: 0.15
}
2020-01-19 19:59:54 +01:00
/** SUBJECT TO CHANGE IN THE FUTURE, this is all beta
* Color and opacity slots definitions. Each key represents a slot.
*
* Short-hands:
* String beginning with `--` - value after dashes treated as sole
* dependency - i.e. `--value` equivalent to { depends: ['value']}
* String beginning with `#` - value would be treated as solid color
* defined in hexadecimal representation (i.e. #FFFFFF) and will be
* used as default. `#FFFFFF` is equivalent to { default: '#FFFFFF'}
*
* Full definition:
* @property {String[]} depends - color slot names this color depends ones.
* cyclic dependencies are supported to some extent but not recommended.
* @property {String} [opacity] - opacity slot used by this color slot.
* opacity is inherited from parents. To break inheritance graph use null
* @property {Number} [priority] - EXPERIMENTAL. used to pre-sort slots so
* that slots with higher priority come earlier
* @property {Function(mod, ...colors)} [color] - function that will be
* used to determine the color. By default it just copies first color in
* dependency list.
* @argument {Number} mod - `1` (light-on-dark) or `-1` (dark-on-light)
* depending on background color (for textColor)/given color.
* @argument {...Object} deps - each argument after mod represents each
* color from `depends` array. All colors take user customizations into
* account and represented by { r, g, b } objects.
* @returns {Object} resulting color, should be in { r, g, b } form
*
* @property {Boolean|String} [textColor] - true to mark color slot as text
* color. This enables automatic text color generation for the slot. Use
* 'preserve' string if you don't want text color to fall back to
* black/white. Use 'bw' to only ever use black or white. This also makes
* following properties required:
* @property {String} [layer] - which layer the text sit on top on - used
* to account for transparency in text color calculation
* layer is inherited from parents. To break inheritance graph use null
* @property {String} [variant] - which color slot is background (same as
* above, used to account for transparency)
*/
2020-01-12 02:44:06 +01:00
export const SLOT_INHERITANCE = {
2020-01-13 00:56:29 +01:00
bg: {
depends: [],
2020-01-16 22:09:46 +01:00
opacity: 'bg',
priority: 1
2020-01-13 00:56:29 +01:00
},
fg: {
depends: [],
priority: 1
},
text: {
depends: [],
priority: 1
},
2020-01-16 19:53:05 +01:00
underlay: {
default: '#000000',
opacity: 'underlay'
},
2020-01-13 00:56:29 +01:00
link: {
depends: ['accent'],
priority: 1
},
accent: {
depends: ['link'],
priority: 1
},
2020-01-16 19:53:05 +01:00
faint: {
depends: ['text'],
opacity: 'faint'
},
faintLink: {
depends: ['link'],
opacity: 'faint'
},
2020-01-12 02:44:06 +01:00
cBlue: '#0000ff',
cRed: '#FF0000',
cGreen: '#00FF00',
cOrange: '#E3FF00',
lightBg: {
depends: ['bg'],
color: (mod, bg) => brightness(5 * mod, bg).rgb
},
lightBgFaintText: {
depends: ['faint'],
layer: 'lightBg',
textColor: true
},
lightBgFaintLink: {
depends: ['faintLink'],
layer: 'lightBg',
textColor: 'preserve'
},
lightBgText: {
depends: ['text'],
layer: 'lightBg',
textColor: true
},
lightBgLink: {
depends: ['link'],
layer: 'lightBg',
textColor: 'preserve'
},
lightBgIcon: {
depends: ['lightBg', 'lightBgText'],
color: (mod, bg, text) => mixrgb(bg, text)
},
2020-01-13 19:40:16 +01:00
selectedPost: '--lightBg',
selectedPostFaintText: {
depends: ['lightBgFaintText'],
layer: 'lightBg',
2020-01-16 22:28:42 +01:00
variant: 'selectedPost',
2020-01-13 19:40:16 +01:00
textColor: true
},
selectedPostFaintLink: {
depends: ['lightBgFaintLink'],
layer: 'lightBg',
2020-01-16 22:28:42 +01:00
variant: 'selectedPost',
2020-01-13 19:40:16 +01:00
textColor: 'preserve'
},
selectedPostText: {
depends: ['lightBgText'],
layer: 'lightBg',
2020-01-16 22:28:42 +01:00
variant: 'selectedPost',
2020-01-13 19:40:16 +01:00
textColor: true
},
selectedPostLink: {
depends: ['lightBgLink'],
layer: 'lightBg',
2020-01-16 22:28:42 +01:00
variant: 'selectedPost',
2020-01-13 19:40:16 +01:00
textColor: 'preserve'
},
selectedPostIcon: {
depends: ['selectedPost', 'selectedPostText'],
color: (mod, bg, text) => mixrgb(bg, text)
},
selectedMenu: '--lightBg',
selectedMenuFaintText: {
depends: ['lightBgFaintText'],
layer: 'lightBg',
2020-01-16 22:28:42 +01:00
variant: 'selectedMenu',
2020-01-13 19:40:16 +01:00
textColor: true
},
selectedMenuFaintLink: {
depends: ['lightBgFaintLink'],
layer: 'lightBg',
2020-01-16 22:28:42 +01:00
variant: 'selectedMenu',
2020-01-13 19:40:16 +01:00
textColor: 'preserve'
},
selectedMenuText: {
depends: ['lightBgText'],
layer: 'lightBg',
2020-01-16 22:28:42 +01:00
variant: 'selectedMenu',
2020-01-13 19:40:16 +01:00
textColor: true
},
selectedMenuLink: {
depends: ['lightBgLink'],
layer: 'lightBg',
2020-01-16 22:28:42 +01:00
variant: 'selectedMenu',
2020-01-13 19:40:16 +01:00
textColor: 'preserve'
},
selectedMenuIcon: {
depends: ['selectedMenu', 'selectedMenuText'],
color: (mod, bg, text) => mixrgb(bg, text)
},
2020-01-12 02:44:06 +01:00
lightText: {
depends: ['text'],
color: (mod, text) => brightness(20 * mod, text).rgb
},
border: {
2020-01-12 14:04:05 +01:00
depends: ['fg'],
2020-01-16 19:53:05 +01:00
opacity: 'border',
2020-01-12 02:44:06 +01:00
color: (mod, fg) => brightness(2 * mod, fg).rgb
},
2020-01-12 22:41:11 +01:00
poll: {
2020-01-12 02:44:06 +01:00
depends: ['accent', 'bg'],
2020-01-16 19:53:05 +01:00
copacity: 'poll',
2020-01-12 22:41:11 +01:00
color: (mod, accent, bg) => alphaBlend(accent, 0.4, bg)
},
pollText: {
depends: ['text'],
layer: 'poll',
textColor: true
2020-01-12 02:44:06 +01:00
},
icon: {
depends: ['bg', 'text'],
2020-01-16 19:53:05 +01:00
inheritsOpacity: false,
2020-01-12 02:44:06 +01:00
color: (mod, bg, text) => mixrgb(bg, text)
},
// Foreground
fgText: {
depends: ['text'],
layer: 'fg',
textColor: true
},
fgLink: {
depends: ['link'],
layer: 'fg',
textColor: 'preserve'
},
// Panel header
2020-01-16 19:53:05 +01:00
panel: {
depends: ['fg'],
opacity: 'panel'
},
2020-01-12 02:44:06 +01:00
panelText: {
depends: ['fgText'],
layer: 'panel',
textColor: true
},
panelFaint: {
depends: ['fgText'],
layer: 'panel',
2020-01-16 19:53:05 +01:00
opacity: 'faint',
2020-01-12 02:44:06 +01:00
textColor: true
},
panelLink: {
depends: ['fgLink'],
layer: 'panel',
textColor: 'preserve'
},
// Top bar
topBar: '--fg',
topBarText: {
depends: ['fgText'],
layer: 'topBar',
textColor: true
},
topBarLink: {
depends: ['fgLink'],
layer: 'topBar',
textColor: 'preserve'
},
2020-01-13 21:19:19 +01:00
// Tabs
tab: '--btn',
tabText: {
depends: ['btnText'],
layer: 'btn',
textColor: true
},
tabActiveText: {
depends: ['text'],
layer: 'bg',
textColor: true
},
2020-01-12 02:44:06 +01:00
// Buttons
2020-01-16 19:53:05 +01:00
btn: {
depends: ['fg'],
opacity: 'btn'
},
2020-01-12 02:44:06 +01:00
btnText: {
depends: ['fgText'],
2020-01-13 20:30:55 +01:00
layer: 'btn',
textColor: true
2020-01-12 02:44:06 +01:00
},
btnPanelText: {
depends: ['panelText'],
layer: 'btnPanel',
variant: 'btn',
textColor: true
},
btnTopBarText: {
depends: ['topBarText'],
layer: 'btnTopBar',
variant: 'btn',
textColor: true
},
2020-01-13 21:19:19 +01:00
// Buttons: pressed
2020-01-13 00:56:29 +01:00
btnPressed: '--btn',
btnPressedText: {
depends: ['btnText'],
layer: 'btn',
variant: 'btnPressed',
textColor: true
},
2020-01-16 22:09:46 +01:00
btnPressedPanel: {
depends: ['btnPressed']
},
2020-01-13 00:56:29 +01:00
btnPressedPanelText: {
depends: ['btnPanelText'],
layer: 'btnPanel',
variant: 'btnPressed',
textColor: true
},
btnPressedTopBarText: {
depends: ['btnTopBarText'],
layer: 'btnTopBar',
variant: 'btnPressed',
textColor: true
},
2020-01-13 21:19:19 +01:00
// Buttons: toggled
btnToggled: {
depends: ['btn'],
color: (mod, btn) => brightness(mod * 20, btn).rgb
},
btnToggledText: {
depends: ['btnText'],
layer: 'btn',
variant: 'btnToggled',
textColor: true
},
btnToggledPanelText: {
depends: ['btnPanelText'],
layer: 'btnPanel',
variant: 'btnToggled',
textColor: true
},
btnToggledTopBarText: {
depends: ['btnTopBarText'],
layer: 'btnTopBar',
variant: 'btnToggled',
textColor: true
},
// Buttons: disabled
2020-01-13 20:30:55 +01:00
btnDisabled: {
depends: ['btn', 'bg'],
color: (mod, btn, bg) => alphaBlend(btn, 0.5, bg)
},
btnDisabledText: {
depends: ['btnText'],
layer: 'btn',
variant: 'btnDisabled',
textColor: true,
color: (mod, text) => brightness(mod * -60, text).rgb
},
btnDisabledPanelText: {
depends: ['btnPanelText'],
layer: 'btnPanel',
variant: 'btnDisabled',
textColor: true,
color: (mod, text) => brightness(mod * -60, text).rgb
},
btnDisabledTopBarText: {
depends: ['btnTopBarText'],
layer: 'btnTopBar',
variant: 'btnDisabled',
textColor: true,
color: (mod, text) => brightness(mod * -60, text).rgb
},
2020-01-12 02:44:06 +01:00
// Input fields
2020-01-16 19:53:05 +01:00
input: {
depends: ['fg'],
opacity: 'input'
},
2020-01-12 02:44:06 +01:00
inputText: {
depends: ['text'],
layer: 'input',
textColor: true
},
inputPanelText: {
depends: ['panelText'],
layer: 'inputPanel',
variant: 'input',
textColor: true
},
inputTopbarText: {
depends: ['topBarText'],
layer: 'inputTopBar',
variant: 'input',
textColor: true
},
2020-01-16 19:53:05 +01:00
alertError: {
depends: ['cRed'],
opacity: 'alert'
},
2020-01-12 02:44:06 +01:00
alertErrorText: {
depends: ['text'],
2020-01-12 02:44:06 +01:00
layer: 'alert',
variant: 'alertError',
textColor: true
},
alertErrorPanelText: {
depends: ['panelText'],
2020-01-12 02:44:06 +01:00
layer: 'alertPanel',
variant: 'alertError',
textColor: true
},
2020-01-16 19:53:05 +01:00
alertWarning: {
depends: ['cOrange'],
opacity: 'alert'
},
2020-01-12 02:44:06 +01:00
alertWarningText: {
depends: ['text'],
2020-01-12 02:44:06 +01:00
layer: 'alert',
variant: 'alertWarning',
textColor: true
},
alertWarningPanelText: {
depends: ['panelText'],
2020-01-12 02:44:06 +01:00
layer: 'alertPanel',
variant: 'alertWarning',
textColor: true
},
badgeNotification: '--cRed',
badgeNotificationText: {
depends: ['text', 'badgeNotification'],
layer: 'badge',
variant: 'badgeNotification',
textColor: 'bw'
}
}
export const getLayersArray = (layer, data = LAYERS) => {
let array = [layer]
let parent = data[layer]
while (parent) {
array.unshift(parent)
parent = data[parent]
}
return array
}
2020-01-16 20:34:33 +01:00
export const getLayers = (layer, variant = layer, opacitySlot, colors, opacity) => {
2020-01-12 02:44:06 +01:00
return getLayersArray(layer).map((currentLayer) => ([
currentLayer === layer
? colors[variant]
: colors[currentLayer],
2020-01-16 20:34:33 +01:00
currentLayer === layer
? opacity[opacitySlot] || 1
2020-01-13 20:30:55 +01:00
: opacity[currentLayer]
2020-01-12 02:44:06 +01:00
]))
}
const getDependencies = (key, inheritance) => {
const data = inheritance[key]
if (typeof data === 'string' && data.startsWith('--')) {
return [data.substring(2)]
} else {
if (data === null) return []
const { depends, layer, variant } = data
const layerDeps = layer
? getLayersArray(layer).map(currentLayer => {
return currentLayer === layer
? variant || layer
: currentLayer
})
: []
if (Array.isArray(depends)) {
return [...depends, ...layerDeps]
} else {
return [...layerDeps]
}
}
}
2020-01-19 19:59:54 +01:00
/**
* Sorts inheritance object topologically - dependant slots come after
* dependencies
*
* @property {Object} inheritance - object defining the nodes
* @property {Function} getDeps - function that returns dependencies for
* given value and inheritance object.
* @returns {String[]} keys of inheritance object, sorted in topological
* order
*/
2020-01-12 02:44:06 +01:00
export const topoSort = (
inheritance = SLOT_INHERITANCE,
getDeps = getDependencies
) => {
// This is an implementation of https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
const allKeys = Object.keys(inheritance)
const whites = new Set(allKeys)
const grays = new Set()
const blacks = new Set()
const unprocessed = [...allKeys]
const output = []
const step = (node) => {
if (whites.has(node)) {
// Make node "gray"
whites.delete(node)
grays.add(node)
// Do step for each node connected to it (one way)
getDeps(node, inheritance).forEach(step)
// Make node "black"
grays.delete(node)
blacks.add(node)
// Put it into the output list
output.push(node)
} else if (grays.has(node)) {
console.debug('Cyclic depenency in topoSort, ignoring')
output.push(node)
} else if (blacks.has(node)) {
// do nothing
} else {
throw new Error('Unintended condition in topoSort!')
}
}
while (unprocessed.length > 0) {
step(unprocessed.pop())
}
return output
}
2020-01-19 19:59:54 +01:00
/**
* retrieves opacity slot for given slot. This goes up the depenency graph
* to find which parent has opacity slot defined for it.
* TODO refactor this
*/
2020-01-16 19:53:05 +01:00
export const getOpacitySlot = (
v,
inheritance = SLOT_INHERITANCE,
getDeps = getDependencies
) => {
2020-01-16 22:09:46 +01:00
const value = typeof v === 'string'
? {
depends: v.startsWith('--') ? [v.substring(2)] : []
}
: v
if (value.opacity === null) return
if (value.opacity) return v.opacity
2020-01-16 19:53:05 +01:00
const findInheritedOpacity = (val) => {
const depSlot = val.depends[0]
if (depSlot === undefined) return
const dependency = getDeps(depSlot, inheritance)[0]
if (dependency === undefined) return
if (dependency.opacity || dependency === null) {
return dependency.opacity
} else if (dependency.depends) {
return findInheritedOpacity(dependency)
} else {
return null
}
}
2020-01-16 22:09:46 +01:00
if (value.depends) {
return findInheritedOpacity(value)
}
}
2020-01-19 19:59:54 +01:00
/**
* retrieves layer slot for given slot. This goes up the depenency graph
* to find which parent has opacity slot defined for it.
* this is basically copypaste of getOpacitySlot except it checks if key is
* in LAYERS
* TODO refactor this
*/
2020-01-16 22:09:46 +01:00
export const getLayerSlot = (
k,
v,
inheritance = SLOT_INHERITANCE,
getDeps = getDependencies
) => {
const value = typeof v === 'string'
? {
depends: v.startsWith('--') ? [v.substring(2)] : []
}
: v
if (LAYERS[k]) return k
if (value.layer === null) return
if (value.layer) return v.layer
const findInheritedLayer = (val) => {
const depSlot = val.depends[0]
if (depSlot === undefined) return
const dependency = getDeps(depSlot, inheritance)[0]
if (dependency === undefined) return
if (dependency.layer || dependency === null) {
return dependency.layer
} else if (dependency.depends) {
return findInheritedLayer(dependency)
} else {
return null
}
}
if (value.depends) {
return findInheritedLayer(value)
2020-01-16 19:53:05 +01:00
}
}
2020-01-19 19:59:54 +01:00
/**
* topologically sorted SLOT_INHERITANCE + additional priority sort
*/
2020-01-13 00:56:29 +01:00
export const SLOT_ORDERED = topoSort(
Object.entries(SLOT_INHERITANCE)
.sort(([aK, aV], [bK, bV]) => ((aV && aV.priority) || 0) - ((bV && bV.priority) || 0))
.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})
)
2020-01-19 19:59:54 +01:00
/**
* Dictionary where keys are color slots and values are opacity associated
* with them
*/
2020-01-16 19:53:05 +01:00
export const SLOTS_OPACITIES_DICT = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) => {
const opacity = getOpacitySlot(v, SLOT_INHERITANCE, getDependencies)
if (opacity) {
return { ...acc, [k]: opacity }
} else {
return acc
}
}, {})
2020-01-12 02:44:06 +01:00
2020-01-19 19:59:54 +01:00
/**
* All opacity slots used in color slots, their default values and affected
* color slots.
*/
2020-01-16 19:53:05 +01:00
export const OPACITIES = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) => {
const opacity = getOpacitySlot(v, SLOT_INHERITANCE, getDependencies)
if (opacity) {
return {
...acc,
[opacity]: {
defaultValue: DEFAULT_OPACITY[opacity] || 1,
affectedSlots: [...((acc[opacity] && acc[opacity].affectedSlots) || []), k]
}
}
} else {
return acc
}
}, {})
2020-01-19 19:59:54 +01:00
/**
* THE function you want to use. Takes provided colors and opacities, mod
* value and uses inheritance data to figure out color needed for the slot.
*/
2020-01-16 19:53:05 +01:00
export const getColors = (sourceColors, sourceOpacity, mod) => SLOT_ORDERED.reduce(({ colors, opacity }, key) => {
2020-01-12 02:44:06 +01:00
const value = SLOT_INHERITANCE[key]
2020-01-16 19:53:05 +01:00
const isObject = typeof value === 'object'
const isString = typeof value === 'string'
2020-01-13 00:56:29 +01:00
const sourceColor = sourceColors[key]
2020-01-16 19:53:05 +01:00
let outputColor = null
2020-01-13 00:56:29 +01:00
if (sourceColor) {
2020-01-16 19:53:05 +01:00
// Color is defined in source color
2020-01-13 00:56:29 +01:00
let targetColor = sourceColor
2020-01-16 19:53:05 +01:00
if (targetColor === 'transparent') {
2020-01-16 22:09:46 +01:00
// We take only layers below current one
const layers = getLayers(
getLayerSlot(key, value),
key,
value.opacity || key,
colors,
opacity
).slice(0, -1)
2020-01-16 19:53:05 +01:00
targetColor = {
// TODO: try to use alpha-blended background here
2020-01-16 22:09:46 +01:00
...alphaBlendLayers(
convert('#FF00FF').rgb,
layers
),
2020-01-16 19:53:05 +01:00
a: 0
}
} else if (typeof sourceColor === 'string' && sourceColor.startsWith('--')) {
// Color references other color
2020-01-13 00:56:29 +01:00
const [variable, modifier] = sourceColor.split(/,/g).map(str => str.trim())
const variableSlot = variable.substring(2)
2020-01-16 19:53:05 +01:00
targetColor = colors[variableSlot] || sourceColors[variableSlot]
2020-01-13 00:56:29 +01:00
if (modifier) {
targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb
}
2020-01-16 19:53:05 +01:00
} else if (typeof sourceColor === 'string' && sourceColor.startsWith('#')) {
targetColor = convert(targetColor).rgb
2020-01-13 00:56:29 +01:00
}
2020-01-16 19:53:05 +01:00
outputColor = { ...targetColor }
} else if (isString && value.startsWith('#')) {
// slot: '#000000' shorthand
outputColor = convert(value).rgb
} else if (isObject && value.default) {
// same as above except in object form
outputColor = convert(value.default).rgb
2020-01-12 02:44:06 +01:00
} else {
2020-01-16 19:53:05 +01:00
// calculate color
2020-01-12 02:44:06 +01:00
const defaultColorFunc = (mod, dep) => ({ ...dep })
const deps = getDependencies(key, SLOT_INHERITANCE)
const colorFunc = (isObject && value.color) || defaultColorFunc
if (value.textColor) {
2020-01-16 19:53:05 +01:00
// textColor case
2020-01-12 02:44:06 +01:00
const bg = alphaBlendLayers(
2020-01-16 19:53:05 +01:00
{ ...colors[deps[0]] },
2020-01-12 02:44:06 +01:00
getLayers(
value.layer,
value.variant || value.layer,
2020-01-16 20:34:33 +01:00
getOpacitySlot(SLOT_INHERITANCE[value.variant || value.layer]),
2020-01-16 19:53:05 +01:00
colors,
opacity
2020-01-12 02:44:06 +01:00
)
)
if (value.textColor === 'bw') {
2020-01-16 19:53:05 +01:00
outputColor = contrastRatio(bg).rgb
2020-01-12 02:44:06 +01:00
} else {
2020-01-16 19:53:05 +01:00
let color = { ...colors[deps[0]] }
2020-01-13 20:30:55 +01:00
if (value.color) {
const isLightOnDark = convert(bg).hsl.l < convert(color).hsl.l
const mod = isLightOnDark ? 1 : -1
2020-01-16 19:53:05 +01:00
color = value.color(mod, ...deps.map((dep) => ({ ...colors[dep] })))
2020-01-13 20:30:55 +01:00
}
2020-01-16 19:53:05 +01:00
outputColor = getTextColor(
bg,
{ ...color },
value.textColor === 'preserve'
2020-01-12 02:44:06 +01:00
)
}
2020-01-16 19:53:05 +01:00
} else {
// background color case
outputColor = colorFunc(
mod,
...deps.map((dep) => ({ ...colors[dep] }))
)
2020-01-12 02:44:06 +01:00
}
}
2020-01-16 19:53:05 +01:00
if (!outputColor) {
throw new Error('Couldn\'t generate color for ' + key)
}
const opacitySlot = SLOTS_OPACITIES_DICT[key]
if (opacitySlot && outputColor.a === undefined) {
outputColor.a = sourceOpacity[opacitySlot] || OPACITIES[opacitySlot].defaultValue || 1
}
2020-01-16 20:34:33 +01:00
if (opacitySlot) {
return {
colors: { ...colors, [key]: outputColor },
opacity: { ...opacity, [opacitySlot]: outputColor.a }
}
} else {
return {
colors: { ...colors, [key]: outputColor },
opacity
}
2020-01-16 19:53:05 +01:00
}
}, { colors: {}, opacity: {} })