commit: 23dc2d1a5b6b3db7e5daa30c71eda1add2273e34
parent 4a10417ed47cdfe08b2ff4939212116dba3e965f
Author: Henry Jameson <me@hjkos.com>
Date: Mon, 19 Feb 2024 20:05:49 +0200
refactor ISS stuff into separate file
Diffstat:
2 files changed, 128 insertions(+), 119 deletions(-)
diff --git a/src/services/theme_data/iss_utils.js b/src/services/theme_data/iss_utils.js
@@ -0,0 +1,119 @@
+// "Unrolls" a tree structure of item: { parent: { ...item2, parent: { ...item3, parent: {...} } }}
+// into an array [item2, item3] for iterating
+export const unroll = (item) => {
+ const out = []
+ let currentParent = item
+ while (currentParent) {
+ out.push(currentParent)
+ currentParent = currentParent.parent
+ }
+ return out
+}
+
+// This gives you an array of arrays of all possible unique (i.e. order-insensitive) combinations
+export const getAllPossibleCombinations = (array) => {
+ const combos = [array.map(x => [x])]
+ for (let comboSize = 2; comboSize <= array.length; comboSize++) {
+ const previous = combos[combos.length - 1]
+ const selfSet = new Set()
+ const newCombos = previous.map(self => {
+ self.forEach(x => selfSet.add(x))
+ const nonSelf = array.filter(x => !selfSet.has(x))
+ return nonSelf.map(x => [...self, x])
+ })
+ const flatCombos = newCombos.reduce((acc, x) => [...acc, ...x], [])
+ combos.push(flatCombos)
+ }
+ return combos.reduce((acc, x) => [...acc, ...x], [])
+}
+
+// Converts rule, parents and their criteria into a CSS (or path if ignoreOutOfTreeSelector == true) selector
+export const genericRuleToSelector = components => (rule, ignoreOutOfTreeSelector, isParent) => {
+ if (!rule && !isParent) return null
+ const component = components[rule.component]
+ const { states, variants, selector, outOfTreeSelector } = component
+
+ const applicableStates = ((rule.state || []).filter(x => x !== 'normal')).map(state => states[state])
+
+ const applicableVariantName = (rule.variant || 'normal')
+ let applicableVariant = ''
+ if (applicableVariantName !== 'normal') {
+ applicableVariant = variants[applicableVariantName]
+ } else {
+ applicableVariant = variants?.normal ?? ''
+ }
+
+ let realSelector
+ if (selector === ':root') {
+ realSelector = ''
+ } else if (isParent) {
+ realSelector = selector
+ } else {
+ if (outOfTreeSelector && !ignoreOutOfTreeSelector) realSelector = outOfTreeSelector
+ else realSelector = selector
+ }
+
+ const selectors = [realSelector, applicableVariant, ...applicableStates]
+ .toSorted((a, b) => {
+ if (a.startsWith(':')) return 1
+ if (/^[a-z]/.exec(a)) return -1
+ else return 0
+ })
+ .join('')
+
+ if (rule.parent) {
+ return (genericRuleToSelector(components)(rule.parent, ignoreOutOfTreeSelector, true) + ' ' + selectors).trim()
+ }
+ return selectors.trim()
+}
+
+export const combinationsMatch = (criteria, subject, strict) => {
+ if (criteria.component !== subject.component) return false
+
+ // All variants inherit from normal
+ if (subject.variant !== 'normal' || strict) {
+ if (criteria.variant !== subject.variant) return false
+ }
+
+ // Subject states > 1 essentially means state is "normal" and therefore matches
+ if (subject.state.length > 1 || strict) {
+ const subjectStatesSet = new Set(subject.state)
+ const criteriaStatesSet = new Set(criteria.state)
+
+ const setsAreEqual =
+ [...criteriaStatesSet].every(state => subjectStatesSet.has(state)) &&
+ [...subjectStatesSet].every(state => criteriaStatesSet.has(state))
+
+ if (!setsAreEqual) return false
+ }
+ return true
+}
+
+export const findRules = (criteria, strict) => subject => {
+ // If we searching for "general" rules - ignore "specific" ones
+ if (criteria.parent === null && !!subject.parent) return false
+ if (!combinationsMatch(criteria, subject, strict)) return false
+
+ if (criteria.parent !== undefined && criteria.parent !== null) {
+ if (!subject.parent && !strict) 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, strict)) return false
+ }
+ }
+ return true
+}
+
+export const normalizeCombination = rule => {
+ rule.variant = rule.variant ?? 'normal'
+ rule.state = [...new Set(['normal', ...(rule.state || [])])]
+}
diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js
@@ -13,6 +13,14 @@ import {
process
} from './theme3_slot_functions.js'
+import {
+ unroll,
+ getAllPossibleCombinations,
+ genericRuleToSelector,
+ normalizeCombination,
+ findRules
+} from './iss_utils.js'
+
const DEBUG = false
// Ensuring the order of components
@@ -109,125 +117,7 @@ componentsContext.keys().forEach(key => {
components[component.name] = component
})
-// "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
- while (currentParent) {
- out.push(currentParent)
- currentParent = currentParent.parent
- }
- return out
-}
-
-// This gives you an array of arrays of all possible unique (i.e. order-insensitive) combinations
-export const getAllPossibleCombinations = (array) => {
- const combos = [array.map(x => [x])]
- for (let comboSize = 2; comboSize <= array.length; comboSize++) {
- const previous = combos[combos.length - 1]
- const selfSet = new Set()
- const newCombos = previous.map(self => {
- self.forEach(x => selfSet.add(x))
- const nonSelf = array.filter(x => !selfSet.has(x))
- return nonSelf.map(x => [...self, x])
- })
- const flatCombos = newCombos.reduce((acc, x) => [...acc, ...x], [])
- combos.push(flatCombos)
- }
- return combos.reduce((acc, x) => [...acc, ...x], [])
-}
-
-// 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
-
- const applicableStates = ((rule.state || []).filter(x => x !== 'normal')).map(state => states[state])
-
- const applicableVariantName = (rule.variant || 'normal')
- let applicableVariant = ''
- if (applicableVariantName !== 'normal') {
- applicableVariant = variants[applicableVariantName]
- } else {
- applicableVariant = variants?.normal ?? ''
- }
-
- let realSelector
- if (selector === ':root') {
- realSelector = ''
- } else if (isParent) {
- realSelector = selector
- } else {
- if (outOfTreeSelector && !ignoreOutOfTreeSelector) realSelector = outOfTreeSelector
- else realSelector = selector
- }
-
- const selectors = [realSelector, applicableVariant, ...applicableStates]
- .toSorted((a, b) => {
- if (a.startsWith(':')) return 1
- if (/^[a-z]/.exec(a)) return -1
- else return 0
- })
- .join('')
-
- if (rule.parent) {
- return (ruleToSelector(rule.parent, ignoreOutOfTreeSelector, true) + ' ' + selectors).trim()
- }
- return selectors.trim()
-}
-
-const combinationsMatch = (criteria, subject, strict) => {
- if (criteria.component !== subject.component) return false
-
- // All variants inherit from normal
- if (subject.variant !== 'normal' || strict) {
- if (criteria.variant !== subject.variant) return false
- }
-
- // Subject states > 1 essentially means state is "normal" and therefore matches
- if (subject.state.length > 1 || strict) {
- const subjectStatesSet = new Set(subject.state)
- const criteriaStatesSet = new Set(criteria.state)
-
- const setsAreEqual =
- [...criteriaStatesSet].every(state => subjectStatesSet.has(state)) &&
- [...subjectStatesSet].every(state => criteriaStatesSet.has(state))
-
- if (!setsAreEqual) return false
- }
- return true
-}
-
-const findRules = (criteria, strict) => subject => {
- // If we searching for "general" rules - ignore "specific" ones
- if (criteria.parent === null && !!subject.parent) return false
- if (!combinationsMatch(criteria, subject, strict)) return false
-
- if (criteria.parent !== undefined && criteria.parent !== null) {
- if (!subject.parent && !strict) 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, strict)) return false
- }
- }
- return true
-}
-
-const normalizeCombination = rule => {
- rule.variant = rule.variant ?? 'normal'
- rule.state = [...new Set(['normal', ...(rule.state || [])])]
-}
+const ruleToSelector = genericRuleToSelector(components)
export const init = (extraRuleset, palette) => {
const stacked = {}