@symbo.ls/connect 3.2.7
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/build.js +205 -0
- package/dist/assets/1024x1024.png +0 -0
- package/dist/assets/128x128.png +0 -0
- package/dist/assets/144x144.png +0 -0
- package/dist/assets/192x192.png +0 -0
- package/dist/assets/48x48.png +0 -0
- package/dist/assets/512x512.png +0 -0
- package/dist/assets/72x72.png +0 -0
- package/dist/assets/96x96.png +0 -0
- package/dist/assets/active_cursor.png +0 -0
- package/dist/assets/favicon.svg +6 -0
- package/dist/assets/old/144x144.png +0 -0
- package/dist/assets/old/192x192.png +0 -0
- package/dist/assets/old/48x48.png +0 -0
- package/dist/assets/old/48x48_faint.png +0 -0
- package/dist/assets/old/512x512.png +0 -0
- package/dist/assets/old/72x72.png +0 -0
- package/dist/assets/old/96x96.png +0 -0
- package/dist/auth.js +373 -0
- package/dist/content.css +46 -0
- package/dist/content.js +1171 -0
- package/dist/content.js.map +7 -0
- package/dist/devtools.html +7 -0
- package/dist/devtools.js +5 -0
- package/dist/manifest.json +87 -0
- package/dist/page-agent.js +727 -0
- package/dist/panel.css +2239 -0
- package/dist/panel.html +235 -0
- package/dist/panel.js +4973 -0
- package/dist/picker.html +111 -0
- package/dist/picker.js +300 -0
- package/dist/service_worker.js +219 -0
- package/dist/service_worker.js.map +7 -0
- package/dist/settings.css +128 -0
- package/dist/settings.html +26 -0
- package/dist/settings_ui.js +57 -0
- package/dist/settings_ui.js.map +7 -0
- package/package.json +20 -0
- package/src/content.js +104 -0
- package/src/grabber/clean.js +605 -0
- package/src/grabber/computed.js +78 -0
- package/src/grabber/parse.js +268 -0
- package/src/grabber/stylesheets.js +117 -0
- package/src/grabber/utils.js +238 -0
- package/src/service_worker.js +246 -0
- package/src/settings/settings_ui.js +52 -0
- package/src/settings/settings_utils.js +70 -0
- package/static/assets/1024x1024.png +0 -0
- package/static/assets/128x128.png +0 -0
- package/static/assets/144x144.png +0 -0
- package/static/assets/192x192.png +0 -0
- package/static/assets/48x48.png +0 -0
- package/static/assets/512x512.png +0 -0
- package/static/assets/72x72.png +0 -0
- package/static/assets/96x96.png +0 -0
- package/static/assets/active_cursor.png +0 -0
- package/static/assets/favicon.svg +6 -0
- package/static/assets/old/144x144.png +0 -0
- package/static/assets/old/192x192.png +0 -0
- package/static/assets/old/48x48.png +0 -0
- package/static/assets/old/48x48_faint.png +0 -0
- package/static/assets/old/512x512.png +0 -0
- package/static/assets/old/72x72.png +0 -0
- package/static/assets/old/96x96.png +0 -0
- package/static/auth.js +373 -0
- package/static/content.css +46 -0
- package/static/devtools.html +7 -0
- package/static/devtools.js +5 -0
- package/static/manifest.json +56 -0
- package/static/page-agent.js +727 -0
- package/static/panel.css +2239 -0
- package/static/panel.html +235 -0
- package/static/panel.js +4973 -0
- package/static/picker.html +111 -0
- package/static/picker.js +300 -0
- package/static/settings.css +128 -0
- package/static/settings.html +26 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import {
|
|
2
|
+
consolidateStyles,
|
|
3
|
+
consolidateFlexCSS,
|
|
4
|
+
consolidateGridCSS,
|
|
5
|
+
splitPropsFromStyles
|
|
6
|
+
} from './clean'
|
|
7
|
+
import {
|
|
8
|
+
getAppliedComputedStyles,
|
|
9
|
+
getStyleDefaultsFromEmptyNode
|
|
10
|
+
} from './computed'
|
|
11
|
+
import { getSettings } from '../settings/settings_utils'
|
|
12
|
+
import { getAllAppliedSheetStyles } from './stylesheets'
|
|
13
|
+
import {
|
|
14
|
+
capitalize,
|
|
15
|
+
cleanSharedProperties,
|
|
16
|
+
convertKeysToCamelCase,
|
|
17
|
+
extractRootVars,
|
|
18
|
+
reformatChildren
|
|
19
|
+
} from './utils'
|
|
20
|
+
|
|
21
|
+
export function getNodeText(node) {
|
|
22
|
+
if (node.nodeType === window.Node.TEXT_NODE) {
|
|
23
|
+
return window.Node.TEXT_NODE // This node itself is text
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let val = ''
|
|
27
|
+
for (const child of node.childNodes) {
|
|
28
|
+
if (
|
|
29
|
+
child.nodeType === window.Node.TEXT_NODE &&
|
|
30
|
+
child.textContent.trim() !== ''
|
|
31
|
+
) {
|
|
32
|
+
val += child.textContent.trim()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (val.trim() !== '') {
|
|
37
|
+
return val
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function parseNodeAttributes(node) {
|
|
42
|
+
const attributes = {}
|
|
43
|
+
|
|
44
|
+
if (node && node.attributes) {
|
|
45
|
+
for (const { name, value } of node.attributes) {
|
|
46
|
+
if (name !== 'class' && name !== 'style') {
|
|
47
|
+
attributes[name] = value
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return Object.keys(attributes).length ? attributes : null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function extendsFromTag(props) {
|
|
56
|
+
const extend = []
|
|
57
|
+
|
|
58
|
+
const tag = (props.tag ?? 'div').toLowerCase()
|
|
59
|
+
|
|
60
|
+
if (tag === 'a') {
|
|
61
|
+
extend.push('Link')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const validTags = [
|
|
65
|
+
'input',
|
|
66
|
+
'button',
|
|
67
|
+
'img',
|
|
68
|
+
'span',
|
|
69
|
+
'strong',
|
|
70
|
+
'section',
|
|
71
|
+
'picture',
|
|
72
|
+
'form',
|
|
73
|
+
'dialog',
|
|
74
|
+
'p',
|
|
75
|
+
'hr',
|
|
76
|
+
'h1',
|
|
77
|
+
'h2',
|
|
78
|
+
'h3',
|
|
79
|
+
'h4',
|
|
80
|
+
'h5',
|
|
81
|
+
'h6',
|
|
82
|
+
'header',
|
|
83
|
+
'footer',
|
|
84
|
+
'progress',
|
|
85
|
+
'select',
|
|
86
|
+
'textarea',
|
|
87
|
+
'aside',
|
|
88
|
+
'pre',
|
|
89
|
+
'code',
|
|
90
|
+
'svg',
|
|
91
|
+
'label',
|
|
92
|
+
'hgroup'
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
if (validTags.includes(tag)) {
|
|
96
|
+
extend.push(capitalize(tag))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return extend
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param {HTMLElement} node
|
|
104
|
+
* @param {CSSStyleDeclaration} rootStyles
|
|
105
|
+
* @returns
|
|
106
|
+
*/
|
|
107
|
+
export async function parseElement(node, rootStyles) {
|
|
108
|
+
const tag = node.tagName.toLowerCase()
|
|
109
|
+
const defaults = getStyleDefaultsFromEmptyNode(tag)
|
|
110
|
+
|
|
111
|
+
const settings = await getSettings()
|
|
112
|
+
|
|
113
|
+
const [sheets, conditionalSheets] =
|
|
114
|
+
settings.useStylesheets ?? true
|
|
115
|
+
? getAllAppliedSheetStyles(node, defaults)
|
|
116
|
+
: {}
|
|
117
|
+
const computed = settings.useComputed
|
|
118
|
+
? getAppliedComputedStyles(node, defaults)
|
|
119
|
+
: {}
|
|
120
|
+
|
|
121
|
+
const rawStyles = {
|
|
122
|
+
...sheets,
|
|
123
|
+
...computed
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let rootVars = extractRootVars(rawStyles, rootStyles)
|
|
127
|
+
|
|
128
|
+
const camelCased = convertKeysToCamelCase(rawStyles)
|
|
129
|
+
|
|
130
|
+
// Process all conditional styles updating both variables as well as resulting props/styles
|
|
131
|
+
const camelCasedConditionals = {}
|
|
132
|
+
Object.entries(conditionalSheets).forEach(([query, cSheet]) => {
|
|
133
|
+
rootVars = { ...extractRootVars(cSheet, rootStyles), ...rootVars }
|
|
134
|
+
|
|
135
|
+
const { props, style } = splitPropsFromStyles(
|
|
136
|
+
convertKeysToCamelCase(cSheet)
|
|
137
|
+
)
|
|
138
|
+
camelCasedConditionals[query] = {}
|
|
139
|
+
if (Object.keys(props).length) {
|
|
140
|
+
camelCasedConditionals[query] = props
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (Object.keys(style).length) {
|
|
144
|
+
camelCasedConditionals[query].style = style
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
let props = { ...rootVars }
|
|
149
|
+
if (tag !== 'div') {
|
|
150
|
+
props.tag = tag
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const { props: styleProps, style } = splitPropsFromStyles(
|
|
154
|
+
consolidateStyles(camelCased)
|
|
155
|
+
)
|
|
156
|
+
props = { ...props, ...styleProps }
|
|
157
|
+
|
|
158
|
+
const text = getNodeText(node)
|
|
159
|
+
|
|
160
|
+
let children = []
|
|
161
|
+
const childPromises = []
|
|
162
|
+
|
|
163
|
+
if (node.children.length > 0) {
|
|
164
|
+
const ignore = ['STYLE', 'SCRIPT']
|
|
165
|
+
|
|
166
|
+
for (const childNode of node.children) {
|
|
167
|
+
if (!ignore.includes(childNode.tagName)) {
|
|
168
|
+
if (childNode.tagName === 'svg') {
|
|
169
|
+
const html = childNode.innerHTML
|
|
170
|
+
const attr = parseNodeAttributes(childNode)
|
|
171
|
+
|
|
172
|
+
children.push({
|
|
173
|
+
extend: ['Svg'],
|
|
174
|
+
attr,
|
|
175
|
+
props: { html }
|
|
176
|
+
})
|
|
177
|
+
} else {
|
|
178
|
+
childPromises.push(parseElement(childNode, rootStyles))
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const results = await Promise.all(childPromises)
|
|
185
|
+
children = [...children, ...results]
|
|
186
|
+
|
|
187
|
+
// check all children for shared props and hoist them to childProps
|
|
188
|
+
let childProps = null
|
|
189
|
+
if (children.length > 1) {
|
|
190
|
+
childProps = cleanSharedProperties(children)
|
|
191
|
+
// remove shared props from each child
|
|
192
|
+
Object.keys(childProps).forEach((key) => {
|
|
193
|
+
children.forEach((child) => delete child.props[key])
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const attr = parseNodeAttributes(node)
|
|
198
|
+
// process attributes of specific tags to props
|
|
199
|
+
const copyAttrFromThese = ['a', 'img', 'input', 'button', 'iframe']
|
|
200
|
+
if (attr && copyAttrFromThese.includes(tag)) {
|
|
201
|
+
Object.keys(attr).forEach((prop) => {
|
|
202
|
+
props[prop] = attr[prop]
|
|
203
|
+
delete attr[prop]
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// fix relative src for images
|
|
208
|
+
if (tag === 'img' && props.src && props.src.startsWith('/')) {
|
|
209
|
+
props.src = window.location.origin + props.src
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (tag === 'a' && props.href && props.href.startsWith('/')) {
|
|
213
|
+
props.href = window.location.origin + props.href
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const extend = extendsFromTag(props)
|
|
217
|
+
if (extend.length === 1) {
|
|
218
|
+
delete props.tag
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (props.display === 'flex') {
|
|
222
|
+
props = consolidateFlexCSS(props)
|
|
223
|
+
delete props.display
|
|
224
|
+
extend.push('Flex')
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (props.display === 'grid') {
|
|
228
|
+
props = consolidateGridCSS(props)
|
|
229
|
+
delete props.display
|
|
230
|
+
extend.push('Grid')
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
let element = {
|
|
234
|
+
props: { ...props }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (Object.keys(style).length) {
|
|
238
|
+
element.props.style = style
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (Object.keys(camelCasedConditionals).length) {
|
|
242
|
+
element.props = { ...element.props, ...camelCasedConditionals }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (text) {
|
|
246
|
+
element.props.text = text
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (attr && Object.keys(attr).length) {
|
|
250
|
+
element.props.attr = attr
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (extend.length) {
|
|
254
|
+
element.extend = extend
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (children.length) {
|
|
258
|
+
if (childProps && Object.keys(childProps).length) {
|
|
259
|
+
element.props.childProps = childProps
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
element = { ...element, ...reformatChildren(children) }
|
|
263
|
+
} else if (childProps) {
|
|
264
|
+
console.warn('childProps without children')
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return element
|
|
268
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const getRelevantRules = (node, rules) => {
|
|
2
|
+
let relevantRules = []
|
|
3
|
+
|
|
4
|
+
for (const rule of rules) {
|
|
5
|
+
const { selectorText, conditionText, media, cssRules } = rule
|
|
6
|
+
|
|
7
|
+
if (selectorText && node.matches(selectorText)) {
|
|
8
|
+
relevantRules.push(rule)
|
|
9
|
+
} else if ((conditionText || (media && media.length > 0)) && cssRules) {
|
|
10
|
+
relevantRules = [
|
|
11
|
+
...relevantRules,
|
|
12
|
+
...getRelevantRules(node, rule.cssRules)
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return relevantRules
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const extractStylesFromRule = (rule, defaults = {}) => {
|
|
21
|
+
const { style } = rule
|
|
22
|
+
const results = {}
|
|
23
|
+
|
|
24
|
+
// Loop through each property and add it to the styles object
|
|
25
|
+
for (let k = 0; k < style.length; k++) {
|
|
26
|
+
const prop = style[k]
|
|
27
|
+
const value = style.getPropertyValue(prop)
|
|
28
|
+
const defaultValue = defaults[prop]
|
|
29
|
+
|
|
30
|
+
if (value && value !== defaultValue) {
|
|
31
|
+
results[prop] = value
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return results
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const processMediaQueries = (rule, parentRule) => {
|
|
39
|
+
const { conditionText } = parentRule
|
|
40
|
+
let key = `@media ${conditionText}`
|
|
41
|
+
if (
|
|
42
|
+
['(prefers-color-scheme: dark)', '(prefers-color-scheme: light)'].includes(
|
|
43
|
+
conditionText
|
|
44
|
+
)
|
|
45
|
+
) {
|
|
46
|
+
key = conditionText.includes('dark') ? '@dark' : '@light'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { [key]: { ...extractStylesFromRule(rule) } }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const getAppliedStylesheets = (node) => {
|
|
53
|
+
let styles = {}
|
|
54
|
+
let conditionalStyles = {}
|
|
55
|
+
|
|
56
|
+
// Loop through all stylesheets
|
|
57
|
+
for (const styleSheet of document.styleSheets) {
|
|
58
|
+
let baseRules = null
|
|
59
|
+
// Try to access the cssRules of the stylesheet (some stylesheets may be cross-origin and not accessible)
|
|
60
|
+
try {
|
|
61
|
+
baseRules = styleSheet.cssRules
|
|
62
|
+
} catch (error) {
|
|
63
|
+
// Unable to access stylesheet (possibly cross-origin), skip it
|
|
64
|
+
console.log('Could not access stylesheet rules:', { styleSheet, error })
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (baseRules !== null) {
|
|
68
|
+
// filter rules down to relevant ones with selectors
|
|
69
|
+
const rules = getRelevantRules(node, baseRules)
|
|
70
|
+
|
|
71
|
+
// Loop through all rules within the stylesheet
|
|
72
|
+
for (const rule of rules) {
|
|
73
|
+
const { parentRule } = rule
|
|
74
|
+
|
|
75
|
+
// Rule is enclosed within a media query
|
|
76
|
+
if (parentRule && parentRule.conditionText) {
|
|
77
|
+
conditionalStyles = {
|
|
78
|
+
...conditionalStyles,
|
|
79
|
+
...processMediaQueries(rule, parentRule)
|
|
80
|
+
}
|
|
81
|
+
} else if (rule.selectorText.includes(':hover')) {
|
|
82
|
+
// Hover styles should be extracted separately as they do not
|
|
83
|
+
// follow the same patterns as other conditional styles
|
|
84
|
+
const hoverKey = ':hover'
|
|
85
|
+
conditionalStyles[hoverKey] = {
|
|
86
|
+
...conditionalStyles[hoverKey],
|
|
87
|
+
...extractStylesFromRule(rule)
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
styles = { ...styles, ...extractStylesFromRule(rule) }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return [styles, conditionalStyles]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const getInlineStyles = (node, defaults) => {
|
|
100
|
+
const styles = {}
|
|
101
|
+
for (let i = 0; i < node.style.length; i++) {
|
|
102
|
+
const prop = node.style[i]
|
|
103
|
+
const val = node.style.getPropertyValue(prop)
|
|
104
|
+
|
|
105
|
+
if (val && val !== defaults[prop]) {
|
|
106
|
+
styles[prop] = val
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return styles
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function getAllAppliedSheetStyles(node, defaults) {
|
|
114
|
+
const inlineStyles = getInlineStyles(node, defaults)
|
|
115
|
+
const [stylesheetStyles, conditionalStyles] = getAppliedStylesheets(node)
|
|
116
|
+
return [{ ...stylesheetStyles, ...inlineStyles }, conditionalStyles]
|
|
117
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/** Helper function to check if all values in an array are equal
|
|
2
|
+
* @param {Array} arr
|
|
3
|
+
* @returns {boolean}
|
|
4
|
+
*/
|
|
5
|
+
export const allEqual = (arr) => arr.every((v) => v === arr[0])
|
|
6
|
+
|
|
7
|
+
// Capitalize function for the border properties
|
|
8
|
+
export const capitalize = (string) =>
|
|
9
|
+
string.charAt(0).toUpperCase() + string.slice(1)
|
|
10
|
+
|
|
11
|
+
// Convert kebab-case to camelCase
|
|
12
|
+
export const toCamelCase = (str) =>
|
|
13
|
+
str.replace(/[_\- ]+(?<first>[a-z])/giu, (_, char, offSet) =>
|
|
14
|
+
offSet > 0 ? char.toUpperCase() : char
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Convert all keys in a style object to camelCase, separating out CSS variables
|
|
19
|
+
* @param {Record<string, string>} styles
|
|
20
|
+
* @returns {Record<string, string>}
|
|
21
|
+
*/
|
|
22
|
+
export function extractCssVars(styles) {
|
|
23
|
+
const cssVariables = {}
|
|
24
|
+
|
|
25
|
+
for (const key in styles) {
|
|
26
|
+
if (Object.hasOwn(styles, key) && key.includes('--')) {
|
|
27
|
+
cssVariables[key] = styles[key]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return cssVariables
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Convert all keys in a style object to camelCase except CSS variables
|
|
36
|
+
* @param {Record<string, string>} styles
|
|
37
|
+
* @returns {Record<string, string>}
|
|
38
|
+
*/
|
|
39
|
+
export function convertKeysToCamelCase(styles) {
|
|
40
|
+
const camelCased = {}
|
|
41
|
+
|
|
42
|
+
for (const key in styles) {
|
|
43
|
+
if (Object.hasOwn(styles, key) && !key.includes('--')) {
|
|
44
|
+
camelCased[toCamelCase(key)] = styles[key]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return camelCased
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function findVarPattern(input) {
|
|
52
|
+
// Regex to capture variable names with letters, numbers, and hyphens
|
|
53
|
+
const regex = /var\(--(?<varName>[a-zA-Z0-9-]+)(?:,\s*[a-zA-Z0-9%-]+)?\)/gu
|
|
54
|
+
const matches = []
|
|
55
|
+
let match = regex.exec(input)
|
|
56
|
+
|
|
57
|
+
// Find all matches in the input string
|
|
58
|
+
while (match !== null) {
|
|
59
|
+
const varName = `--${match.groups.varName}`
|
|
60
|
+
matches.push(varName)
|
|
61
|
+
|
|
62
|
+
match = regex.exec(input)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Remove duplicates and return the result
|
|
66
|
+
return [...new Set(matches)]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {Record<string, string>} css
|
|
71
|
+
* @param {CSSStyleDeclaration} globalStyles
|
|
72
|
+
* @returns {Record<string, string>} extracted root variables
|
|
73
|
+
*/
|
|
74
|
+
export const extractRootVars = (css, globalStyles) => {
|
|
75
|
+
const variables = extractCssVars(css)
|
|
76
|
+
let rootVars = Object.values(css).reduce((acc, value) => {
|
|
77
|
+
const vars = {}
|
|
78
|
+
|
|
79
|
+
if (value.includes('var(')) {
|
|
80
|
+
const varNames = findVarPattern(value)
|
|
81
|
+
varNames.forEach((v) => {
|
|
82
|
+
let val = variables[v]
|
|
83
|
+
if (val || val === '') {
|
|
84
|
+
vars[v] = val || ' '
|
|
85
|
+
} else {
|
|
86
|
+
val = globalStyles.getPropertyValue(v)
|
|
87
|
+
if (val || val === '') {
|
|
88
|
+
vars[v] = val || ' '
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { ...acc, ...vars }
|
|
95
|
+
}, {})
|
|
96
|
+
|
|
97
|
+
// Check for nested root variables
|
|
98
|
+
if (Object.keys(rootVars).length) {
|
|
99
|
+
let checkingNested = true
|
|
100
|
+
let nested = {}
|
|
101
|
+
while (checkingNested) {
|
|
102
|
+
const parsed = Object.values({
|
|
103
|
+
...rootVars,
|
|
104
|
+
...variables,
|
|
105
|
+
...nested
|
|
106
|
+
}).reduce(
|
|
107
|
+
/* eslint-disable-next-line no-loop-func -- checking the contents of rootVars and nested is safe */
|
|
108
|
+
(acc, value) => {
|
|
109
|
+
const vars = {}
|
|
110
|
+
if (value.includes('var(')) {
|
|
111
|
+
const varNames = findVarPattern(value)
|
|
112
|
+
varNames.forEach((v) => {
|
|
113
|
+
if (v in rootVars || v in nested) {
|
|
114
|
+
return // Skip if already in rootVars or nested to prevent infinite loop
|
|
115
|
+
}
|
|
116
|
+
let val = variables[v]
|
|
117
|
+
if (val || val === '') {
|
|
118
|
+
vars[v] = val || ' '
|
|
119
|
+
} else {
|
|
120
|
+
val = globalStyles.getPropertyValue(v)
|
|
121
|
+
if (val || val === '') {
|
|
122
|
+
vars[v] = val || ' '
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { ...acc, ...vars }
|
|
129
|
+
},
|
|
130
|
+
{}
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
// If no new variables found stop checking, otherwise merge them into nested
|
|
134
|
+
if (Object.keys(parsed).length) {
|
|
135
|
+
nested = { ...nested, ...parsed }
|
|
136
|
+
} else {
|
|
137
|
+
checkingNested = false
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
rootVars = { ...rootVars, ...nested }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return rootVars
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Helper function to deep compare objects, with support for circular references
|
|
148
|
+
function isEqual(objA, objB, visited = new WeakSet()) {
|
|
149
|
+
if (objA === objB) {
|
|
150
|
+
return true
|
|
151
|
+
} // Check for reference equality
|
|
152
|
+
if (objA == null || objB == null) {
|
|
153
|
+
return false
|
|
154
|
+
} // Handle null or undefined
|
|
155
|
+
|
|
156
|
+
if (typeof objA !== 'object' || typeof objB !== 'object') {
|
|
157
|
+
return false // Both must be objects
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Handle circular references
|
|
161
|
+
if (visited.has(objA) || visited.has(objB)) {
|
|
162
|
+
return false
|
|
163
|
+
}
|
|
164
|
+
visited.add(objA)
|
|
165
|
+
visited.add(objB)
|
|
166
|
+
|
|
167
|
+
const keysA = Object.keys(objA)
|
|
168
|
+
const keysB = Object.keys(objB)
|
|
169
|
+
|
|
170
|
+
if (keysA.length !== keysB.length) {
|
|
171
|
+
return false
|
|
172
|
+
} // Different number of keys
|
|
173
|
+
|
|
174
|
+
for (const key of keysA) {
|
|
175
|
+
if (!keysB.includes(key) || !isEqual(objA[key], objB[key], visited)) {
|
|
176
|
+
return false // Keys don't match or values are different
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return true // Objects are equal
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Function to find shared properties
|
|
184
|
+
export function cleanSharedProperties(children) {
|
|
185
|
+
// Extract props from children
|
|
186
|
+
const props = children.map((child) => child.props)
|
|
187
|
+
const shared = {}
|
|
188
|
+
|
|
189
|
+
// Get keys from the first style object
|
|
190
|
+
const keys = Object.keys(props[0])
|
|
191
|
+
|
|
192
|
+
for (const key of keys) {
|
|
193
|
+
// Check if all style objects have the same value for this key
|
|
194
|
+
const values = props.map((style) => style[key])
|
|
195
|
+
|
|
196
|
+
if (values.every((value) => isEqual(value, values[0]))) {
|
|
197
|
+
const [val] = values
|
|
198
|
+
shared[key] = val // Add to shared properties
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return shared // Return the shared properties object
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Converts an array of children objects into a single object with unique keys.
|
|
207
|
+
* @param {Array<any>} children
|
|
208
|
+
* @returns {Record<string, any>} reformatted children object
|
|
209
|
+
*/
|
|
210
|
+
export function reformatChildren(children) {
|
|
211
|
+
const childrenObj = {}
|
|
212
|
+
|
|
213
|
+
const keyCounts = {}
|
|
214
|
+
|
|
215
|
+
children.forEach((child) => {
|
|
216
|
+
let key = child.extend ?? 'Child' // Default key if `extend` is missing
|
|
217
|
+
|
|
218
|
+
// Check for multiple `extends` in the array
|
|
219
|
+
if (Array.isArray(key)) {
|
|
220
|
+
key = child.extend.shift()
|
|
221
|
+
if (!child.extend.length) {
|
|
222
|
+
delete child.extend
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Increment the key count to handle duplicates
|
|
227
|
+
if (!(key in keyCounts)) {
|
|
228
|
+
keyCounts[key] = 0
|
|
229
|
+
}
|
|
230
|
+
keyCounts[key]++
|
|
231
|
+
|
|
232
|
+
const newKey = keyCounts[key] > 1 ? `${key}_${keyCounts[key]}` : key
|
|
233
|
+
|
|
234
|
+
childrenObj[newKey] = { ...child }
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
return childrenObj
|
|
238
|
+
}
|