update, should inherit stuff properly now.

This commit is contained in:
Henry Jameson 2024-02-07 15:53:34 +02:00
parent d4795d2e3c
commit c34590c439
16 changed files with 804 additions and 236 deletions

View File

@ -371,8 +371,7 @@ nav {
border-radius: $fallback--btnRadius;
border-radius: var(--btnRadius, $fallback--btnRadius);
cursor: pointer;
box-shadow: $fallback--buttonShadow;
box-shadow: var(--buttonShadow);
box-shadow: var(--shadow);
font-size: 1em;
font-family: sans-serif;
font-family: var(--interfaceFont, sans-serif);
@ -383,25 +382,14 @@ nav {
i[class*="icon-"],
.svg-inline--fa {
color: $fallback--text;
color: var(--btnText, $fallback--text);
color: var(--icon);
}
&::-moz-focus-inner {
border: none;
}
&:hover {
box-shadow: 0 0 4px rgb(255 255 255 / 30%);
box-shadow: var(--buttonHoverShadow);
}
&:active {
box-shadow:
0 0 4px 0 rgb(255 255 255 / 30%),
0 1px 0 0 rgb(0 0 0 / 20%) inset,
0 -1px 0 0 rgb(255 255 255 / 20%) inset;
box-shadow: var(--buttonPressedShadow);
color: $fallback--text;
color: var(--btnPressedText, $fallback--text);
background-color: $fallback--fg;
@ -487,7 +475,12 @@ nav {
}
input,
textarea,
textarea {
border: none;
display: inline-block;
outline: none;
}
.input {
&.unstyled {
border-radius: 0;
@ -501,15 +494,7 @@ textarea,
border: none;
border-radius: $fallback--inputRadius;
border-radius: var(--inputRadius, $fallback--inputRadius);
box-shadow:
0 1px 0 0 rgb(0 0 0 / 20%) inset,
0 -1px 0 0 rgb(255 255 255 / 20%) inset,
0 0 2px 0 rgb(0 0 0 / 100%) inset;
box-shadow: var(--inputShadow);
background-color: $fallback--fg;
background-color: var(--input, $fallback--fg);
color: $fallback--lightText;
color: var(--inputText, $fallback--lightText);
box-shadow: var(--shadow);
font-family: sans-serif;
font-family: var(--inputFont, sans-serif);
font-size: 1em;
@ -561,11 +546,9 @@ textarea,
width: 1.1em;
height: 1.1em;
border-radius: 100%; // Radio buttons should always be circle
box-shadow: 0 0 2px black inset;
box-shadow: var(--inputShadow);
background-color: var(--background);
box-shadow: var(--shadow);
margin-right: 0.5em;
background-color: $fallback--fg;
background-color: var(--input, $fallback--fg);
vertical-align: top;
text-align: center;
line-height: 1.1;
@ -578,8 +561,9 @@ textarea,
&[type="checkbox"] {
&:checked + label::before {
color: $fallback--text;
color: var(--inputText, $fallback--text);
color: var(--text);
background-color: var(--background);
box-shadow: var(--shadow);
}
&:disabled {

View File

@ -1,11 +1,34 @@
const border = (top, shadow) => ({
x: 0,
y: top ? 1 : -1,
blur: 0,
spread: 0,
color: shadow ? '#000000' : '#FFFFFF',
alpha: 0.2,
inset: true
})
const buttonInsetFakeBorders = [border(true, false), border(false, true)]
const inputInsetFakeBorders = [border(true, true), border(false, false)]
const hoverGlow = {
x: 0,
y: 0,
blur: 4,
spread: 0,
color: '--text',
alpha: 1
}
export default {
name: 'Button',
selector: '.btn',
selector: '.button-default',
states: {
disabled: ':disabled',
toggled: '.toggled',
pressed: ':active',
hover: ':hover'
hover: ':hover',
focused: ':focus-within'
},
variants: {
danger: '.danger',
@ -20,14 +43,49 @@ export default {
{
component: 'Button',
directives: {
background: '--fg'
background: '--fg',
shadow: [{
x: 0,
y: 0,
blur: 2,
spread: 0,
color: '#000000',
alpha: 1
}, ...buttonInsetFakeBorders]
}
},
{
component: 'Button',
state: ['hover'],
directives: {
background: '#FFFFFF'
shadow: [hoverGlow, ...buttonInsetFakeBorders]
}
},
{
component: 'Button',
state: ['hover', 'pressed'],
directives: {
background: '--accent,-24.2',
shadow: [hoverGlow, ...inputInsetFakeBorders]
}
},
{
component: 'Button',
state: ['disabled'],
directives: {
background: '$blend(--background, 0.25, --parent)',
shadow: [...buttonInsetFakeBorders]
}
},
{
component: 'Text',
parent: {
component: 'Button',
state: ['disabled']
},
directives: {
textOpacity: 0.25,
textOpacityMode: 'blend'
}
}
]

View File

@ -0,0 +1,19 @@
export default {
name: 'DropdownMenu',
selector: '.dropdown',
validInnerComponents: [
'Text',
'Icon',
'Input'
],
states: {
hover: ':hover'
},
defaultRules: [
{
directives: {
background: '--fg'
}
}
]
}

View File

@ -0,0 +1,60 @@
const border = (top, shadow) => ({
x: 0,
y: top ? 1 : -1,
blur: 0,
spread: 0,
color: shadow ? '#000000' : '#FFFFFF',
alpha: 0.2,
inset: true
})
const inputInsetFakeBorders = [border(true, true), border(false, false)]
const hoverGlow = {
x: 0,
y: 0,
blur: 4,
spread: 0,
color: '--text',
alpha: 1
}
export default {
name: 'Input',
selector: '.input',
states: {
disabled: ':disabled',
pressed: ':active',
hover: ':hover',
focused: ':focus-within'
},
variants: {
danger: '.danger',
unstyled: '.unstyled',
sublime: '.sublime'
},
validInnerComponents: [
'Text'
],
defaultRules: [
{
directives: {
background: '--fg',
shadow: [{
x: 0,
y: 0,
blur: 2,
spread: 0,
color: '#000000',
alpha: 1
}, ...inputInsetFakeBorders]
}
},
{
state: ['hover'],
directives: {
shadow: [hoverGlow, ...inputInsetFakeBorders]
}
}
]
}

View File

@ -6,13 +6,14 @@ export default {
'Link',
'Icon',
'Button',
'PanelHeader'
'Input',
'PanelHeader',
'DropdownMenu'
],
defaultRules: [
{
component: 'Panel',
directives: {
background: '--fg'
background: '--bg'
}
}
]

View File

@ -12,7 +12,6 @@ export default {
component: 'PanelHeader',
directives: {
background: '--fg'
// opacity: 0.9
}
}
]

View File

@ -0,0 +1,20 @@
export default {
name: 'Popover',
selector: '.popover',
validInnerComponents: [
'Text',
'Link',
'Icon',
'Button',
'Input',
'PanelHeader',
'DropdownMenu'
],
defaultRules: [
{
directives: {
background: '--fg'
}
}
]
}

View File

@ -0,0 +1,17 @@
export default {
name: 'Root',
selector: ':root',
validInnerComponents: [
'Underlay',
'TopBar',
'Popover'
],
defaultRules: [
{
directives: {
background: '--bg',
opacity: 0
}
}
]
}

View File

@ -0,0 +1,18 @@
export default {
name: 'TopBar',
selector: 'nav',
validInnerComponents: [
'Link',
'Text',
'Icon',
'Button',
'Input'
],
defaultRules: [
{
directives: {
background: '--fg'
}
}
]
}

View File

@ -1,6 +1,6 @@
export default {
name: 'Underlay',
selector: '#content',
selector: 'body', // Should be '#content' but for now this is better for testing until I have proper popovers and such...
outOfTreeSelector: '.underlay',
validInnerComponents: [
'Panel'

View File

@ -6,6 +6,10 @@
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
.panel-heading {
background-color: inherit;
}
&::after,
& {
border-radius: $fallback--panelRadius;
@ -131,12 +135,9 @@
align-items: start;
// panel theme
color: var(--panelText);
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
&::after {
background-color: $fallback--fg;
background-color: var(--panel, $fallback--fg);
background-color: var(--background);
z-index: -2;
border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;

View File

@ -20,8 +20,8 @@ export const applyTheme = (input) => {
styleSheet.toString()
styleSheet.insertRule(`:root { ${rules.radii} }`, 'index-max')
styleSheet.insertRule(`:root { ${rules.colors} }`, 'index-max')
styleSheet.insertRule(`:root { ${rules.shadows} }`, 'index-max')
// styleSheet.insertRule(`:root { ${rules.colors} }`, 'index-max')
// styleSheet.insertRule(`:root { ${rules.shadows} }`, 'index-max')
styleSheet.insertRule(`:root { ${rules.fonts} }`, 'index-max')
themes3.css.forEach(rule => {
console.log(rule)

View File

@ -0,0 +1,176 @@
export default [
'bg',
'wallpaper',
'fg',
'text',
'underlay',
'link',
'accent',
'faint',
'faintLink',
'postFaintLink',
'cBlue',
'cRed',
'cGreen',
'cOrange',
'profileBg',
'profileTint',
'highlight',
'highlightLightText',
'highlightPostLink',
'highlightFaintText',
'highlightFaintLink',
'highlightPostFaintLink',
'highlightText',
'highlightLink',
'highlightIcon',
'popover',
'popoverLightText',
'popoverPostLink',
'popoverFaintText',
'popoverFaintLink',
'popoverPostFaintLink',
'popoverText',
'popoverLink',
'popoverIcon',
'selectedPost',
'selectedPostFaintText',
'selectedPostLightText',
'selectedPostPostLink',
'selectedPostFaintLink',
'selectedPostText',
'selectedPostLink',
'selectedPostIcon',
'selectedMenu',
'selectedMenuLightText',
'selectedMenuFaintText',
'selectedMenuFaintLink',
'selectedMenuText',
'selectedMenuLink',
'selectedMenuIcon',
'selectedMenuPopover',
'selectedMenuPopoverLightText',
'selectedMenuPopoverFaintText',
'selectedMenuPopoverFaintLink',
'selectedMenuPopoverText',
'selectedMenuPopoverLink',
'selectedMenuPopoverIcon',
'lightText',
'postLink',
'postGreentext',
'postCyantext',
'border',
'poll',
'pollText',
'icon',
// Foreground,
'fgText',
'fgLink',
// Panel header,
'panel',
'panelText',
'panelFaint',
'panelLink',
// Top bar,
'topBar',
'topBarLink',
// Tabs,
'tab',
'tabText',
'tabActiveText',
// Buttons,
'btn',
'btnText',
'btnPanelText',
'btnTopBarText',
// Buttons: pressed,
'btnPressed',
'btnPressedText',
'btnPressedPanel',
'btnPressedPanelText',
'btnPressedTopBar',
'btnPressedTopBarText',
// Buttons: toggled,
'btnToggled',
'btnToggledText',
'btnToggledPanelText',
'btnToggledTopBarText',
// Buttons: disabled,
'btnDisabled',
'btnDisabledText',
'btnDisabledPanelText',
'btnDisabledTopBarText',
// Input fields,
'input',
'inputText',
'inputPanelText',
'inputTopbarText',
'alertError',
'alertErrorText',
'alertErrorPanelText',
'alertWarning',
'alertWarningText',
'alertWarningPanelText',
'alertSuccess',
'alertSuccessText',
'alertSuccessPanelText',
'alertNeutral',
'alertNeutralText',
'alertNeutralPanelText',
'alertPopupError',
'alertPopupErrorText',
'alertPopupWarning',
'alertPopupWarningText',
'alertPopupSuccess',
'alertPopupSuccessText',
'alertPopupNeutral',
'alertPopupNeutralText',
'badgeNotification',
'badgeNotificationText',
'badgeNeutral',
'badgeNeutralText',
'chatBg',
'chatMessageIncomingBg',
'chatMessageIncomingText',
'chatMessageIncomingLink',
'chatMessageIncomingBorder',
'chatMessageOutgoingBg',
'chatMessageOutgoingText',
'chatMessageOutgoingLink',
'chatMessageOutgoingBorder'
]

View File

@ -0,0 +1,58 @@
import allKeys from './theme2_keys'
// keys that are meant to be used globally, i.e. what's the rest of the theme is based upon.
const basePaletteKeys = new Set([
'bg',
'fg',
'text',
'link',
'accent',
'cBlue',
'cRed',
'cGreen',
'cOrange'
])
// Keys that are not available in editor and never meant to be edited
const hiddenKeys = new Set([
'profileBg',
'profileTint'
])
const extendedBasePrefixes = [
'border',
'icon',
'highlight',
'lightText',
'popover',
'panel',
'topBar',
'tab',
'btn',
'input',
'selectedMenu',
'alert',
'badge',
'post',
'selectedPost', // wrong nomenclature
'poll',
'chatBg',
'chatMessageIncoming',
'chatMessageOutgoing'
]
const extendedBaseKeys = Object.fromEntries(extendedBasePrefixes.map(prefix => [prefix, allKeys.filter(k => k.startsWith(prefix))]))
// Keysets that are only really used intermideately, i.e. to generate other colors
const temporary = new Set([
'border',
'highlight'
])
const temporaryColors = {}

View File

@ -1,24 +1,89 @@
import { convert, brightness } from 'chromatism'
import merge from 'lodash.merge'
import { alphaBlend, getTextColor, rgba2css, mixrgb, relativeLuminance } from '../color_convert/color_convert.js'
import {
alphaBlend,
getTextColor,
rgba2css,
mixrgb,
relativeLuminance
} from '../color_convert/color_convert.js'
import Root from 'src/components/root.style.js'
import TopBar from 'src/components/top_bar.style.js'
import Underlay from 'src/components/underlay.style.js'
import Popover from 'src/components/popover.style.js'
import DropdownMenu from 'src/components/dropdown_menu.style.js'
import Panel from 'src/components/panel.style.js'
import PanelHeader from 'src/components/panel_header.style.js'
import Button from 'src/components/button.style.js'
import Input from 'src/components/input.style.js'
import Text from 'src/components/text.style.js'
import Link from 'src/components/link.style.js'
import Icon from 'src/components/icon.style.js'
const root = Underlay
export const DEFAULT_SHADOWS = {
panel: [{
x: 1,
y: 1,
blur: 4,
spread: 0,
color: '#000000',
alpha: 0.6
}],
topBar: [{
x: 0,
y: 0,
blur: 4,
spread: 0,
color: '#000000',
alpha: 0.6
}],
popup: [{
x: 2,
y: 2,
blur: 3,
spread: 0,
color: '#000000',
alpha: 0.5
}],
avatar: [{
x: 0,
y: 1,
blur: 8,
spread: 0,
color: '#000000',
alpha: 0.7
}],
avatarStatus: [],
panelHeader: []
}
const components = {
Underlay,
Panel,
PanelHeader,
Button,
Root,
Text,
Link,
Icon
Icon,
Underlay,
Popover,
DropdownMenu,
Panel,
PanelHeader,
TopBar,
Button,
Input
}
// "Unrolls" a tree structure of item: { parent: { ...item2, parent: { ...item3, parent: {...} } }}
// into an array [item2, item3] for iterating
const unroll = (item) => {
const out = []
let currentParent = item.parent
while (currentParent) {
const { parent: newParent, ...rest } = currentParent
out.push(rest)
currentParent = newParent
}
return out
}
// This gives you an array of arrays of all possible unique (i.e. order-insensitive) combinations
@ -38,7 +103,9 @@ export const getAllPossibleCombinations = (array) => {
return combos.reduce((acc, x) => [...acc, ...x], [])
}
export const ruleToSelector = (rule, isParent) => {
// Converts rule, parents and their criteria into a CSS (or path if ignoreOutOfTreeSelector == true) selector
export const ruleToSelector = (rule, ignoreOutOfTreeSelector, isParent) => {
if (!rule && !isParent) return null
const component = components[rule.component]
const { states, variants, selector, outOfTreeSelector } = component
@ -51,10 +118,12 @@ export const ruleToSelector = (rule, isParent) => {
}
let realSelector
if (isParent) {
if (selector === ':root') {
realSelector = ''
} else if (isParent) {
realSelector = selector
} else {
if (outOfTreeSelector) realSelector = outOfTreeSelector
if (outOfTreeSelector && !ignoreOutOfTreeSelector) realSelector = outOfTreeSelector
else realSelector = selector
}
@ -67,123 +136,133 @@ export const ruleToSelector = (rule, isParent) => {
.join('')
if (rule.parent) {
return ruleToSelector(rule.parent, true) + ' ' + selectors
return (ruleToSelector(rule.parent, ignoreOutOfTreeSelector, true) + ' ' + selectors).trim()
}
return selectors
return selectors.trim()
}
const combinationsMatch = (criteria, subject) => {
if (criteria.component !== subject.component) return false
// All variants inherit from normal
const subjectVariant = Object.prototype.hasOwnProperty.call(subject, 'variant') ? subject.variant : 'normal'
if (subjectVariant !== 'normal') {
if (criteria.variant !== subject.variant) return false
}
const subjectStatesSet = new Set(['normal', ...(subject.state || [])])
const criteriaStatesSet = new Set(['normal', ...(criteria.state || [])])
// Subject states > 1 essentially means state is "normal" and therefore matches
if (subjectStatesSet.size > 1) {
const setsAreEqual =
[...criteriaStatesSet].every(state => subjectStatesSet.has(state)) &&
[...subjectStatesSet].every(state => criteriaStatesSet.has(state))
if (!setsAreEqual) return false
}
return true
}
const findRules = criteria => subject => {
// If we searching for "general" rules - ignore "specific" ones
if (criteria.parent === null && !!subject.parent) return false
if (!combinationsMatch(criteria, subject)) return false
if (criteria.parent !== undefined && criteria.parent !== null) {
if (!subject.parent) return true
const pathCriteria = unroll(criteria)
const pathSubject = unroll(subject)
if (pathCriteria.length < pathSubject.length) return false
// Search: .a .b .c
// Matches: .a .b .c; .b .c; .c; .z .a .b .c
// Does not match .a .b .c .d, .a .b .e
for (let i = 0; i < pathCriteria.length; i++) {
const criteriaParent = pathCriteria[i]
const subjectParent = pathSubject[i]
if (!subjectParent) return true
if (!combinationsMatch(criteriaParent, subjectParent)) return false
}
}
return true
}
export const init = (extraRuleset, palette) => {
const rootName = root.name
const cache = {}
const computed = {}
const rules = []
const rulesByComponent = {}
const ruleset = [
...Object.values(components).map(c => c.defaultRules || []).reduce((acc, arr) => [...acc, ...arr], []),
...Object.values(components).map(c => c.defaultRules.map(r => ({ component: c.name, ...r })) || []).reduce((acc, arr) => [...acc, ...arr], []),
...extraRuleset
]
const virtualComponents = new Set(Object.values(components).filter(c => c.virtual).map(c => c.name))
const addRule = (rule) => {
rules.push(rule)
rulesByComponent[rule.component] = rulesByComponent[rule.component] || []
rulesByComponent[rule.component].push(rule)
}
const findRules = (searchCombination, parent) => rule => {
// inexact search
const doesCombinationMatch = () => {
if (searchCombination.component !== rule.component) return false
const ruleVariant = Object.prototype.hasOwnProperty.call(rule, 'variant') ? rule.variant : 'normal'
if (ruleVariant !== 'normal') {
if (searchCombination.variant !== rule.variant) return false
}
const ruleHasStateDefined = Object.prototype.hasOwnProperty.call(rule, 'state')
let ruleStateSet
if (ruleHasStateDefined) {
ruleStateSet = new Set(['normal', ...rule.state])
} else {
ruleStateSet = new Set(['normal'])
}
if (ruleStateSet.size > 1) {
const ruleStatesSet = ruleStateSet
const combinationSet = new Set(['normal', ...searchCombination.state])
const setsAreEqual = searchCombination.state.every(state => ruleStatesSet.has(state)) &&
[...ruleStatesSet].every(state => combinationSet.has(state))
return setsAreEqual
} else {
return true
}
}
const combinationMatches = doesCombinationMatch()
if (!parent || !combinationMatches) return combinationMatches
// exact search
// unroll parents into array
const unroll = (item) => {
const out = []
let currentParent = item.parent
while (currentParent) {
const { parent: newParent, ...rest } = currentParent
out.push(rest)
currentParent = newParent
}
return out
}
const { parent: _, ...rest } = parent
const pathSearch = [rest, ...unroll(parent)]
const pathRule = unroll(rule)
if (pathSearch.length !== pathRule.length) return false
const pathsMatch = pathSearch.every((searchRule, i) => {
const existingRule = pathRule[i]
if (existingRule.component !== searchRule.component) return false
if (existingRule.variant !== searchRule.variant) return false
const existingRuleStatesSet = new Set(['normal', ...(existingRule.state || [])])
const searchStatesSet = new Set(['normal', ...(searchRule.state || [])])
const setsAreEqual = existingRule.state.every(state => searchStatesSet.has(state)) &&
[...searchStatesSet].every(state => existingRuleStatesSet.has(state))
return setsAreEqual
})
return pathsMatch
}
const findLowerLevelRule = (parent, filter = () => true) => {
let lowerLevelComponent = null
let currentParent = parent
while (currentParent) {
const rulesParent = ruleset.filter(findRules(currentParent))
rulesParent > 1 && console.warn('OOPS')
lowerLevelComponent = rulesParent[rulesParent.length - 1]
currentParent = currentParent.parent
if (lowerLevelComponent && filter(lowerLevelComponent)) currentParent = null
}
return filter(lowerLevelComponent) ? lowerLevelComponent : null
}
const findColor = (color, background) => {
if (typeof color !== 'string' || !color.startsWith('--')) return color
const findColor = (color, inheritedBackground, lowerLevelBackground) => {
if (typeof color !== 'string' || (!color.startsWith('--') && !color.startsWith('$'))) return color
let targetColor = null
// Color references other color
if (color.startsWith('--')) {
const [variable, modifier] = color.split(/,/g).map(str => str.trim())
const variableSlot = variable.substring(2)
if (variableSlot.startsWith('parent')) {
// TODO support more than just background?
if (variableSlot === 'parent') {
targetColor = lowerLevelBackground
}
} else {
switch (variableSlot) {
case 'background':
targetColor = inheritedBackground
break
default:
targetColor = palette[variableSlot]
}
}
if (modifier) {
const effectiveBackground = background ?? targetColor
const effectiveBackground = lowerLevelBackground ?? targetColor
const isLightOnDark = relativeLuminance(convert(effectiveBackground).rgb) < 0.5
const mod = isLightOnDark ? 1 : -1
targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb
}
}
if (color.startsWith('$')) {
try {
const { funcName, argsString } = /\$(?<funcName>\w+)\((?<argsString>[a-zA-Z0-9-,.'"\s]*)\)/.exec(color).groups
const args = argsString.split(/,/g).map(a => a.trim())
switch (funcName) {
case 'blend': {
if (args.length !== 3) {
throw new Error(`$blend requires 3 arguments, ${args.length} were provided`)
}
const backgroundArg = findColor(args[2], inheritedBackground, lowerLevelBackground)
const foregroundArg = findColor(args[0], inheritedBackground, lowerLevelBackground)
const amount = Number(args[1])
targetColor = alphaBlend(backgroundArg, amount, foregroundArg)
break
}
}
} catch (e) {
console.error('Failure executing color function', e)
targetColor = '#FF00FF'
}
}
// Color references other color
return targetColor
}
const getTextColorAlpha = (rule, lowerRule, value) => {
const cssColorString = (color, alpha) => rgba2css({ ...convert(color).rgb, a: alpha })
const getTextColorAlpha = (rule, lowerColor, value) => {
const opacity = rule.directives.textOpacity
const backgroundColor = convert(lowerRule.cache.background).rgb
const backgroundColor = convert(lowerColor).rgb
const textColor = convert(findColor(value, backgroundColor)).rgb
if (opacity === null || opacity === undefined || opacity >= 1) {
return convert(textColor).hex
@ -202,6 +281,44 @@ export const init = (extraRuleset, palette) => {
}
}
const getCssShadow = (input, usesDropShadow) => {
if (input.length === 0) {
return 'none'
}
return input
.filter(_ => usesDropShadow ? _.inset : _)
.map((shad) => [
shad.x,
shad.y,
shad.blur,
shad.spread
].map(_ => _ + 'px ').concat([
cssColorString(findColor(shad.color), shad.alpha),
shad.inset ? 'inset' : ''
]).join(' ')).join(', ')
}
const getCssShadowFilter = (input) => {
if (input.length === 0) {
return 'none'
}
return input
// drop-shadow doesn't support inset or spread
.filter((shad) => !shad.inset && Number(shad.spread) === 0)
.map((shad) => [
shad.x,
shad.y,
// drop-shadow's blur is twice as strong compared to box-shadow
shad.blur / 2
].map(_ => _ + 'px').concat([
cssColorString(findColor(shad.color), shad.alpha)
]).join(' '))
.map(_ => `drop-shadow(${_})`)
.join(' ')
}
const processInnerComponent = (component, parent) => {
const {
validInnerComponents = [],
@ -210,6 +327,7 @@ export const init = (extraRuleset, palette) => {
name
} = component
// Normalizing states and variants to always include "normal"
const states = { normal: '', ...originalStates }
const variants = { normal: '', ...originalVariants }
const innerComponents = validInnerComponents.map(name => components[name])
@ -219,13 +337,24 @@ export const init = (extraRuleset, palette) => {
return stateCombinations.map(state => ({ variant, state }))
}).reduce((acc, x) => [...acc, ...x], [])
const VIRTUAL_COMPONENTS = new Set(['Text', 'Link', 'Icon'])
stateVariantCombination.forEach(combination => {
let needRuleAdd = false
const soloSelector = ruleToSelector({ component: component.name, ...combination }, true)
const selector = ruleToSelector({ component: component.name, ...combination, parent }, true)
if (VIRTUAL_COMPONENTS.has(component.name)) {
const selector = component.name + ruleToSelector({ component: component.name, ...combination })
// Inheriting all of the applicable rules
const existingRules = ruleset.filter(findRules({ component: component.name, ...combination, parent }))
const { directives: computedDirectives } = existingRules.reduce((acc, rule) => merge(acc, rule), {})
const computedRule = {
component: component.name,
...combination,
parent,
directives: computedDirectives
}
computed[selector] = computed[selector] || {}
computed[selector].computedRule = computedRule
if (virtualComponents.has(component.name)) {
const virtualName = [
'--',
component.name.toLowerCase(),
@ -235,52 +364,46 @@ export const init = (extraRuleset, palette) => {
...combination.state.filter(x => x !== 'normal').toSorted().map(state => state[0].toUpperCase() + state.slice(1).toLowerCase())
].join('')
const lowerLevel = findLowerLevelRule(parent, (r) => {
if (!r) return false
if (components[r.component].validInnerComponents.indexOf(component.name) < 0) return false
if (r.cache.background === undefined) return false
if (r.cache.textDefined) {
return !r.cache.textDefined[selector]
}
return true
})
let inheritedTextColor = computedDirectives.textColor
let inheritedTextOpacity = computedDirectives.textOpacity
let inheritedTextOpacityMode = computedDirectives.textOpacityMode
const lowerLevelTextSelector = [...selector.split(/ /g).slice(0, -1), soloSelector].join(' ')
const lowerLevelTextRule = computed[lowerLevelTextSelector]
if (!lowerLevel) return
let inheritedTextColorRule
const inheritedTextColorRules = findLowerLevelRule(parent, (r) => {
return r.cache?.textDefined?.[selector]
})
if (!inheritedTextColorRule) {
const generalTextColorRules = ruleset.filter(findRules({ component: component.name, ...combination }, null, true))
inheritedTextColorRule = generalTextColorRules.reduce((acc, rule) => merge(acc, rule), {})
} else {
inheritedTextColorRule = inheritedTextColorRules.reduce((acc, rule) => merge(acc, rule), {})
if (inheritedTextColor == null || inheritedTextOpacity == null || inheritedTextOpacityMode == null) {
inheritedTextColor = computedDirectives.textColor ?? lowerLevelTextRule.textColor
inheritedTextOpacity = computedDirectives.textOpacity ?? lowerLevelTextRule.textOpacity
inheritedTextOpacityMode = computedDirectives.textOpacityMode ?? lowerLevelTextRule.textOpacityMode
}
let inheritedTextColor
let inheritedTextOpacity = {}
if (inheritedTextColorRule) {
inheritedTextColor = findColor(inheritedTextColorRule.directives.textColor, convert(lowerLevel.cache.background).rgb)
// also inherit opacity settings
const { textOpacity, textOpacityMode } = inheritedTextColorRule.directives
inheritedTextOpacity = { textOpacity, textOpacityMode }
} else {
// Emergency fallback
inheritedTextColor = '#000000'
const newTextRule = {
...computedRule,
directives: {
...computedRule.directives,
textColor: inheritedTextColor,
textOpacity: inheritedTextOpacity,
textOpacityMode: inheritedTextOpacityMode
}
}
const lowerLevelSelector = selector.split(/ /g).slice(0, -1).join(' ')
const lowerLevelBackground = cache[lowerLevelSelector].background
const textColor = getTextColor(
convert(lowerLevel.cache.background).rgb,
convert(inheritedTextColor).rgb,
component.name === 'Link' // make it configurable?
convert(lowerLevelBackground).rgb,
// TODO properly provide "parent" text color?
convert(findColor(inheritedTextColor, null, lowerLevelBackground)).rgb,
true // component.name === 'Link' || combination.variant === 'greentext' // make it configurable?
)
lowerLevel.cache.textDefined = lowerLevel.cache.textDefined || {}
lowerLevel.cache.textDefined[selector] = textColor
lowerLevel.virtualDirectives = lowerLevel.virtualDirectives || {}
lowerLevel.virtualDirectives[virtualName] = getTextColorAlpha(inheritedTextColorRule, lowerLevel, textColor)
// Storing color data in lower layer to use as custom css properties
cache[lowerLevelSelector].textDefined = cache[lowerLevelSelector].textDefined || {}
cache[lowerLevelSelector].textDefined[selector] = textColor
const virtualDirectives = {}
virtualDirectives[virtualName] = getTextColorAlpha(newTextRule, lowerLevelBackground, textColor)
// lastRule.computed = lastRule.computed || {}
const directives = {
textColor,
@ -288,45 +411,54 @@ export const init = (extraRuleset, palette) => {
}
// Debug: lets you see what it think background color should be
directives.background = convert(lowerLevel.cache.background).hex
// directives.background = convert(cache[lowerLevelSelector].background).hex
addRule({
parent,
virtual: true,
component: component.name,
...combination,
cache: { background: lowerLevel.cache.background },
directives
directives,
virtualDirectives
})
} else {
const existingGlobalRules = ruleset.filter(findRules({ component: component.name, ...combination }, null))
const existingRules = ruleset.filter(findRules({ component: component.name, ...combination }, parent))
cache[selector] = cache[selector] || {}
computed[selector] = computed[selector] || {}
// Global (general) rules
if (existingGlobalRules.length !== 0) {
const totalRule = existingGlobalRules.reduce((acc, rule) => merge(acc, rule), {})
const { directives } = totalRule
if (computedDirectives.background) {
let inheritRule = null
const variantRules = ruleset.filter(findRules({ component: component.name, variant: combination.variant, parent }))
const lastVariantRule = variantRules[variantRules.length - 1]
if (lastVariantRule) {
inheritRule = lastVariantRule
} else {
const normalRules = ruleset.filter(findRules({ component: component.name, parent }))
const lastNormalRule = normalRules[normalRules.length - 1]
inheritRule = lastNormalRule
}
// last rule is used as a cache
const lastRule = existingGlobalRules[existingGlobalRules.length - 1]
lastRule.cache = lastRule.cache || {}
if (directives.background) {
const rgb = convert(findColor(directives.background)).rgb
const inheritSelector = ruleToSelector({ ...inheritRule, parent }, true)
const inheritedBackground = cache[inheritSelector].background
const lowerLevelSelector = selector.split(/ /g).slice(0, -1).join(' ')
// TODO: DEFAULT TEXT COLOR
const bg = findLowerLevelRule(parent)?.cache.background || convert('#FFFFFF').rgb
const bg = cache[lowerLevelSelector]?.background || convert('#FFFFFF').rgb
if (!lastRule.cache.background) {
const blend = directives.opacity < 1 ? alphaBlend(rgb, directives.opacity, bg) : rgb
lastRule.cache.background = blend
console.log('SELECTOR', lowerLevelSelector)
needRuleAdd = true
}
}
const rgb = convert(findColor(computedDirectives.background, inheritedBackground, cache[lowerLevelSelector].background)).rgb
if (needRuleAdd) {
addRule(lastRule)
if (!cache[selector].background) {
const blend = computedDirectives.opacity < 1 ? alphaBlend(rgb, computedDirectives.opacity, bg) : rgb
cache[selector].background = blend
computed[selector].background = rgb
addRule({
component: component.name,
...combination,
parent,
directives: computedDirectives
})
}
}
@ -338,12 +470,12 @@ export const init = (extraRuleset, palette) => {
})
}
processInnerComponent(components[rootName])
processInnerComponent(components.Root, { component: 'Root' })
return {
raw: rules,
css: rules.map(rule => {
if (rule.virtual) return ''
// if (rule.virtual) return ''
let selector = ruleToSelector(rule).replace(/\/\*.*\*\//g, '')
if (!selector) {
@ -356,10 +488,23 @@ export const init = (extraRuleset, palette) => {
return ' ' + k + ': ' + v
}).join(';\n')
const directives = Object.entries(rule.directives).map(([k, v]) => {
let directives
if (rule.component !== 'Root') {
directives = Object.entries(rule.directives).map(([k, v]) => {
switch (k) {
case 'shadow': {
return ' ' + [
'--shadow: ' + getCssShadow(v),
'--shadowFilter: ' + getCssShadowFilter(v),
'--shadowInset: ' + getCssShadow(v, true)
].join(';\n ')
}
case 'background': {
return 'background-color: ' + rgba2css({ ...convert(findColor(v)).rgb, a: rule.directives.opacity ?? 1 })
const color = cssColorString(computed[ruleToSelector(rule, true)].background, rule.directives.opacity)
return [
'background-color: ' + color,
' --background: ' + color
].join(';\n')
}
case 'textColor': {
return 'color: ' + v
@ -367,11 +512,14 @@ export const init = (extraRuleset, palette) => {
default: return ''
}
}).filter(x => x).map(x => ' ' + x).join(';\n')
} else {
directives = {}
}
return [
header,
directives + ';',
' color: var(--text);',
!rule.virtual ? ' color: var(--text);' : '',
'',
virtualDirectives,
footer

9
top_bar.style.js Normal file
View File

@ -0,0 +1,9 @@
export default {
name: 'TopBar',
selector: 'nav',
validInnerComponents: [
'Link',
'Text',
'Icon'
]
}