@tamagui/web 1.47.9 → 1.48.1
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/dist/cjs/createComponent.js +1 -1
- package/dist/cjs/createComponent.js.map +1 -1
- package/dist/cjs/createTamagui.js +40 -37
- package/dist/cjs/createTamagui.js.map +2 -2
- package/dist/cjs/createVariable.js +4 -4
- package/dist/cjs/createVariable.js.map +1 -1
- package/dist/cjs/helpers/ThemeManager.js +1 -20
- package/dist/cjs/helpers/ThemeManager.js.map +1 -1
- package/dist/cjs/helpers/insertStyleRule.js +119 -18
- package/dist/cjs/helpers/insertStyleRule.js.map +1 -1
- package/dist/cjs/helpers/propMapper.js +3 -2
- package/dist/cjs/helpers/propMapper.js.map +1 -1
- package/dist/cjs/helpers/themes.js +31 -12
- package/dist/cjs/helpers/themes.js.map +1 -1
- package/dist/cjs/hooks/useTheme.js +8 -1
- package/dist/cjs/hooks/useTheme.js.map +1 -1
- package/dist/esm/createComponent.js +1 -1
- package/dist/esm/createComponent.js.map +1 -1
- package/dist/esm/createTamagui.js +41 -38
- package/dist/esm/createTamagui.js.map +2 -2
- package/dist/esm/createVariable.js +4 -4
- package/dist/esm/createVariable.js.map +1 -1
- package/dist/esm/helpers/ThemeManager.js +2 -21
- package/dist/esm/helpers/ThemeManager.js.map +1 -1
- package/dist/esm/helpers/insertStyleRule.js +119 -18
- package/dist/esm/helpers/insertStyleRule.js.map +1 -1
- package/dist/esm/helpers/propMapper.js +3 -2
- package/dist/esm/helpers/propMapper.js.map +1 -1
- package/dist/esm/helpers/themes.js +29 -11
- package/dist/esm/helpers/themes.js.map +1 -1
- package/dist/esm/hooks/useTheme.js +8 -1
- package/dist/esm/hooks/useTheme.js.map +1 -1
- package/package.json +9 -9
- package/src/createComponent.tsx +2 -1
- package/src/createTamagui.ts +69 -69
- package/src/createVariable.ts +5 -4
- package/src/helpers/ThemeManager.tsx +1 -19
- package/src/helpers/insertStyleRule.tsx +168 -24
- package/src/helpers/propMapper.ts +3 -3
- package/src/helpers/themes.ts +51 -13
- package/src/hooks/useTheme.tsx +10 -1
- package/src/types.tsx +10 -0
- package/types/createComponent.d.ts.map +1 -1
- package/types/createTamagui.d.ts.map +1 -1
- package/types/createVariable.d.ts +1 -1
- package/types/createVariable.d.ts.map +1 -1
- package/types/helpers/ThemeManager.d.ts +0 -1
- package/types/helpers/ThemeManager.d.ts.map +1 -1
- package/types/helpers/insertStyleRule.d.ts +2 -2
- package/types/helpers/insertStyleRule.d.ts.map +1 -1
- package/types/helpers/themes.d.ts +3 -2
- package/types/helpers/themes.d.ts.map +1 -1
- package/types/hooks/useTheme.d.ts.map +1 -1
- package/types/types.d.ts +6 -0
- package/types/types.d.ts.map +1 -1
package/src/createTamagui.ts
CHANGED
|
@@ -10,16 +10,19 @@ import {
|
|
|
10
10
|
scanAllSheets,
|
|
11
11
|
} from './helpers/insertStyleRule'
|
|
12
12
|
import { registerCSSVariable, variableToCSS } from './helpers/registerCSSVariable'
|
|
13
|
-
import { ensureThemeVariable,
|
|
13
|
+
import { ensureThemeVariable, proxyThemesToParents } from './helpers/themes'
|
|
14
14
|
import { configureMedia } from './hooks/useMedia'
|
|
15
15
|
import { parseFont, registerFontVariables } from './insertFont'
|
|
16
16
|
import { Tamagui } from './Tamagui'
|
|
17
17
|
import {
|
|
18
18
|
CreateTamaguiProps,
|
|
19
|
+
DedupedTheme,
|
|
20
|
+
DedupedThemes,
|
|
19
21
|
GetCSS,
|
|
20
22
|
InferTamaguiConfig,
|
|
21
23
|
TamaguiInternalConfig,
|
|
22
24
|
ThemeParsed,
|
|
25
|
+
ThemesLikeObject,
|
|
23
26
|
} from './types'
|
|
24
27
|
|
|
25
28
|
// config is re-run by the @tamagui/static, dont double validate
|
|
@@ -44,7 +47,16 @@ export function createTamagui<Conf extends CreateTamaguiProps>(
|
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
|
|
50
|
+
// faster $lookups
|
|
51
|
+
const tokensParsed: any = Object.fromEntries(
|
|
52
|
+
Object.entries(configIn.tokens).map(([k, v]) => {
|
|
53
|
+
const val = Object.fromEntries(Object.entries(v).map(([k, v]) => [`$${k}`, v]))
|
|
54
|
+
return [k, val]
|
|
55
|
+
})
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const noThemes = Object.keys(configIn.themes).length === 0
|
|
59
|
+
const foundThemes = scanAllSheets(noThemes, tokensParsed)
|
|
48
60
|
listenForSheetChanges()
|
|
49
61
|
|
|
50
62
|
const fontTokens = Object.fromEntries(
|
|
@@ -71,7 +83,6 @@ export function createTamagui<Conf extends CreateTamaguiProps>(
|
|
|
71
83
|
const specificTokens = {}
|
|
72
84
|
|
|
73
85
|
const themeConfig = (() => {
|
|
74
|
-
const themes = { ...configIn.themes }
|
|
75
86
|
const cssRuleSets: string[] = []
|
|
76
87
|
|
|
77
88
|
if (process.env.TAMAGUI_DOES_SSR_CSS !== 'true') {
|
|
@@ -142,61 +153,9 @@ export function createTamagui<Conf extends CreateTamaguiProps>(
|
|
|
142
153
|
}
|
|
143
154
|
}
|
|
144
155
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
theme: ThemeParsed
|
|
149
|
-
}
|
|
150
|
-
const dedupedThemes: {
|
|
151
|
-
[key: string]: DedupedTheme
|
|
152
|
-
} = {}
|
|
153
|
-
const existing = new Map<string, DedupedTheme>()
|
|
154
|
-
|
|
155
|
-
// first, de-dupe and parse them
|
|
156
|
-
for (const themeName in themes) {
|
|
157
|
-
// forces us to separate the dark/light themes (otherwise we generate bad t_light prefix selectors)
|
|
158
|
-
const darkOrLightSpecificPrefix = themeName.startsWith('dark')
|
|
159
|
-
? 'dark'
|
|
160
|
-
: themeName.startsWith('light')
|
|
161
|
-
? 'light'
|
|
162
|
-
: ''
|
|
163
|
-
|
|
164
|
-
const rawTheme = themes[themeName] as ThemeParsed
|
|
165
|
-
|
|
166
|
-
// dont force referential equality but may need something more consistent than JSON.stringify
|
|
167
|
-
// separate between dark/light
|
|
168
|
-
const key = darkOrLightSpecificPrefix + JSON.stringify(rawTheme)
|
|
169
|
-
|
|
170
|
-
// if existing, avoid
|
|
171
|
-
if (existing.has(key)) {
|
|
172
|
-
const e = existing.get(key)!
|
|
173
|
-
themes[themeName] = e.theme
|
|
174
|
-
e.names.push(themeName)
|
|
175
|
-
continue
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// ensure each theme object unique for dedupe
|
|
179
|
-
const theme = { ...rawTheme }
|
|
180
|
-
// parse into variables
|
|
181
|
-
for (const key in theme) {
|
|
182
|
-
// make sure properly names theme variables
|
|
183
|
-
ensureThemeVariable(theme, key)
|
|
184
|
-
}
|
|
185
|
-
themes[themeName] = theme
|
|
186
|
-
|
|
187
|
-
// set deduped
|
|
188
|
-
dedupedThemes[themeName] = {
|
|
189
|
-
names: [themeName],
|
|
190
|
-
theme,
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
existing.set(key, dedupedThemes[themeName])
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// proxy upwards to get parent variables (themes are subset going down)
|
|
197
|
-
for (const themeName in themes) {
|
|
198
|
-
themes[themeName] = proxyThemeToParents(themeName, themes[themeName], themes)
|
|
199
|
-
}
|
|
156
|
+
const themesIn = { ...configIn.themes } as ThemesLikeObject
|
|
157
|
+
const dedupedThemes = foundThemes ?? getThemesDeduped(themesIn)
|
|
158
|
+
const themes = proxyThemesToParents(dedupedThemes, tokensParsed)
|
|
200
159
|
|
|
201
160
|
return {
|
|
202
161
|
themes,
|
|
@@ -206,11 +165,12 @@ export function createTamagui<Conf extends CreateTamaguiProps>(
|
|
|
206
165
|
let themeRuleSets: string[] = []
|
|
207
166
|
|
|
208
167
|
if (isWeb) {
|
|
209
|
-
for (const
|
|
168
|
+
for (const { names, theme } of dedupedThemes) {
|
|
210
169
|
const nextRules = getThemeCSSRules({
|
|
211
170
|
config: configIn,
|
|
212
|
-
themeName,
|
|
213
|
-
|
|
171
|
+
themeName: names[0],
|
|
172
|
+
names,
|
|
173
|
+
theme,
|
|
214
174
|
})
|
|
215
175
|
themeRuleSets = [...themeRuleSets, ...nextRules]
|
|
216
176
|
}
|
|
@@ -221,14 +181,6 @@ export function createTamagui<Conf extends CreateTamaguiProps>(
|
|
|
221
181
|
}
|
|
222
182
|
})()
|
|
223
183
|
|
|
224
|
-
// faster $lookups
|
|
225
|
-
const tokensParsed: any = Object.fromEntries(
|
|
226
|
-
Object.entries(configIn.tokens).map(([k, v]) => {
|
|
227
|
-
const val = Object.fromEntries(Object.entries(v).map(([k, v]) => [`$${k}`, v]))
|
|
228
|
-
return [k, val]
|
|
229
|
-
})
|
|
230
|
-
)
|
|
231
|
-
|
|
232
184
|
const shorthands = configIn.shorthands || {}
|
|
233
185
|
|
|
234
186
|
let lastCSSInsertedRulesIndex = -1
|
|
@@ -328,3 +280,51 @@ ${runtimeStyles}`
|
|
|
328
280
|
|
|
329
281
|
return config as any
|
|
330
282
|
}
|
|
283
|
+
|
|
284
|
+
// dedupes the themes if given them via JS config
|
|
285
|
+
function getThemesDeduped(themes: ThemesLikeObject): DedupedThemes {
|
|
286
|
+
const dedupedThemes: DedupedThemes = []
|
|
287
|
+
const existing = new Map<string, DedupedTheme>()
|
|
288
|
+
|
|
289
|
+
// first, de-dupe and parse them
|
|
290
|
+
for (const themeName in themes) {
|
|
291
|
+
// forces us to separate the dark/light themes (otherwise we generate bad t_light prefix selectors)
|
|
292
|
+
const darkOrLightSpecificPrefix = themeName.startsWith('dark')
|
|
293
|
+
? 'dark'
|
|
294
|
+
: themeName.startsWith('light')
|
|
295
|
+
? 'light'
|
|
296
|
+
: ''
|
|
297
|
+
|
|
298
|
+
const rawTheme = themes[themeName]
|
|
299
|
+
|
|
300
|
+
// dont force referential equality but may need something more consistent than JSON.stringify
|
|
301
|
+
// separate between dark/light
|
|
302
|
+
const key = darkOrLightSpecificPrefix + JSON.stringify(rawTheme)
|
|
303
|
+
|
|
304
|
+
// if existing, avoid
|
|
305
|
+
if (existing.has(key)) {
|
|
306
|
+
const e = existing.get(key)!
|
|
307
|
+
e.names.push(themeName)
|
|
308
|
+
continue
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ensure each theme object unique for dedupe
|
|
312
|
+
// is ThemeParsed because we call ensureThemeVariable
|
|
313
|
+
const theme = { ...rawTheme } as any as ThemeParsed
|
|
314
|
+
// parse into variables
|
|
315
|
+
for (const key in theme) {
|
|
316
|
+
// make sure properly names theme variables
|
|
317
|
+
ensureThemeVariable(theme, key)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// set deduped
|
|
321
|
+
const deduped: DedupedTheme = {
|
|
322
|
+
names: [themeName],
|
|
323
|
+
theme,
|
|
324
|
+
}
|
|
325
|
+
dedupedThemes.push(deduped)
|
|
326
|
+
existing.set(key, deduped)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return dedupedThemes
|
|
330
|
+
}
|
package/src/createVariable.ts
CHANGED
|
@@ -22,16 +22,17 @@ export type MakeVariable<A = any> = A extends string | number ? Variable<A> : A
|
|
|
22
22
|
|
|
23
23
|
type VariableIn<A = any> = Pick<Variable<A>, 'key' | 'name' | 'val'>
|
|
24
24
|
export const createVariable = <A extends string | number | Variable = any>(
|
|
25
|
-
props: VariableIn<A
|
|
25
|
+
props: VariableIn<A>,
|
|
26
|
+
skipHash = false
|
|
26
27
|
): Variable<A> => {
|
|
27
|
-
if (isVariable(props)) return props
|
|
28
|
+
if (!skipHash && isVariable(props)) return props
|
|
28
29
|
const { key, name, val } = props
|
|
29
30
|
return {
|
|
30
31
|
[IS_VAR]: true,
|
|
31
32
|
key: key!,
|
|
32
|
-
name: simpleHash(name, 40),
|
|
33
|
+
name: skipHash ? '' : simpleHash(name, 40),
|
|
33
34
|
val: val as any,
|
|
34
|
-
variable: isWeb ? createCSSVariable(name) : '',
|
|
35
|
+
variable: isWeb ? (skipHash ? `var(--${name})` : createCSSVariable(name)) : '',
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -154,24 +154,6 @@ export class ThemeManager {
|
|
|
154
154
|
return this._allKeys
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
// gets value going up to parents
|
|
158
|
-
getValue(key: string, state: ThemeManagerState = this.state) {
|
|
159
|
-
if (!key) return
|
|
160
|
-
let theme = state.theme
|
|
161
|
-
let manager = this as ThemeManager | null
|
|
162
|
-
while (theme && manager) {
|
|
163
|
-
if (key in theme) {
|
|
164
|
-
return theme[key]
|
|
165
|
-
}
|
|
166
|
-
manager = manager.parentManager
|
|
167
|
-
theme = manager?.state.theme
|
|
168
|
-
}
|
|
169
|
-
const tokens = getTokens()
|
|
170
|
-
if (key in tokens.color) {
|
|
171
|
-
return tokens.color[key]
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
157
|
notify(forced = false) {
|
|
176
158
|
this.themeListeners.forEach((cb) => cb(this.state.name, this, forced))
|
|
177
159
|
}
|
|
@@ -325,7 +307,7 @@ function getState(
|
|
|
325
307
|
if (found) {
|
|
326
308
|
result = {
|
|
327
309
|
name: found,
|
|
328
|
-
theme:
|
|
310
|
+
theme: themes[found],
|
|
329
311
|
className: getNextThemeClassName(found),
|
|
330
312
|
parentName,
|
|
331
313
|
componentName,
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { isClient } from '@tamagui/constants'
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { getConfig } from '../config'
|
|
4
|
+
import { Variable, createVariable } from '../createVariable'
|
|
5
|
+
import type {
|
|
6
|
+
DedupedTheme,
|
|
7
|
+
DedupedThemes,
|
|
8
|
+
RulesToInsert,
|
|
9
|
+
ThemeParsed,
|
|
10
|
+
Tokens,
|
|
11
|
+
TokensParsed,
|
|
12
|
+
} from '../types'
|
|
13
|
+
import { ensureThemeVariable } from './themes'
|
|
4
14
|
|
|
5
15
|
const allSelectors: Record<string, string> = {}
|
|
6
16
|
const allRules: Record<string, string> = {}
|
|
@@ -63,16 +73,26 @@ export function listenForSheetChanges() {
|
|
|
63
73
|
|
|
64
74
|
let lastScannedSheets: Set<CSSStyleSheet> | null = null
|
|
65
75
|
|
|
66
|
-
export function scanAllSheets(
|
|
76
|
+
export function scanAllSheets(
|
|
77
|
+
collectThemes = false,
|
|
78
|
+
tokens?: TokensParsed
|
|
79
|
+
): DedupedThemes | undefined {
|
|
67
80
|
if (process.env.NODE_ENV === 'test') return
|
|
68
81
|
if (!isClient) return
|
|
69
82
|
|
|
83
|
+
let themes: DedupedThemes | undefined
|
|
84
|
+
|
|
70
85
|
const sheets = document.styleSheets || []
|
|
71
86
|
const prev = lastScannedSheets
|
|
72
87
|
const current = new Set(sheets as any as CSSStyleSheet[])
|
|
73
88
|
if (document.styleSheets) {
|
|
74
89
|
for (const sheet of current) {
|
|
75
|
-
|
|
90
|
+
if (sheet) {
|
|
91
|
+
const out = updateSheetStyles(sheet, false, collectThemes, tokens)
|
|
92
|
+
if (out) {
|
|
93
|
+
themes = out
|
|
94
|
+
}
|
|
95
|
+
}
|
|
76
96
|
}
|
|
77
97
|
lastScannedSheets = current
|
|
78
98
|
}
|
|
@@ -84,6 +104,8 @@ export function scanAllSheets() {
|
|
|
84
104
|
}
|
|
85
105
|
}
|
|
86
106
|
}
|
|
107
|
+
|
|
108
|
+
return themes
|
|
87
109
|
}
|
|
88
110
|
|
|
89
111
|
function track(id: string, remove = false) {
|
|
@@ -92,7 +114,15 @@ function track(id: string, remove = false) {
|
|
|
92
114
|
return next
|
|
93
115
|
}
|
|
94
116
|
|
|
95
|
-
|
|
117
|
+
const bailAfterEnv = process.env.TAMAGUI_BAIL_AFTER_SCANNING_X_CSS_RULES
|
|
118
|
+
const bailAfter = bailAfterEnv ? +bailAfterEnv : 250
|
|
119
|
+
|
|
120
|
+
function updateSheetStyles(
|
|
121
|
+
sheet: CSSStyleSheet,
|
|
122
|
+
remove = false,
|
|
123
|
+
collectThemes = false,
|
|
124
|
+
tokens?: TokensParsed
|
|
125
|
+
): DedupedThemes | undefined {
|
|
96
126
|
// avoid errors on cross origin sheets
|
|
97
127
|
// https://stackoverflow.com/questions/49993633/uncaught-domexception-failed-to-read-the-cssrules-property
|
|
98
128
|
let rules: CSSRuleList
|
|
@@ -105,8 +135,8 @@ function updateSheetStyles(sheet: CSSStyleSheet, remove = false) {
|
|
|
105
135
|
return
|
|
106
136
|
}
|
|
107
137
|
|
|
108
|
-
const firstSelector = getTamaguiSelector(rules[0])?.[0]
|
|
109
|
-
const lastSelector = getTamaguiSelector(rules[rules.length - 1])?.[0]
|
|
138
|
+
const firstSelector = getTamaguiSelector(rules[0], collectThemes)?.[0]
|
|
139
|
+
const lastSelector = getTamaguiSelector(rules[rules.length - 1], collectThemes)?.[0]
|
|
110
140
|
const cacheKey = `${rules.length}${firstSelector}${lastSelector}`
|
|
111
141
|
const lastScanned = scannedCache.get(sheet)
|
|
112
142
|
|
|
@@ -120,22 +150,36 @@ function updateSheetStyles(sheet: CSSStyleSheet, remove = false) {
|
|
|
120
150
|
const len = rules.length
|
|
121
151
|
let fails = 0
|
|
122
152
|
|
|
153
|
+
let dedupedThemes: DedupedThemes | undefined
|
|
154
|
+
|
|
123
155
|
for (let i = 0; i < len; i++) {
|
|
124
156
|
const rule = rules[i]
|
|
125
157
|
if (!(rule instanceof CSSStyleRule)) continue
|
|
126
158
|
|
|
127
|
-
const response = getTamaguiSelector(rule)
|
|
159
|
+
const response = getTamaguiSelector(rule, collectThemes)
|
|
128
160
|
|
|
129
|
-
if (
|
|
161
|
+
if (response) {
|
|
162
|
+
// reset to 0 on any success as eg every other theme scan we get empty
|
|
163
|
+
fails = 0
|
|
164
|
+
} else {
|
|
130
165
|
fails++
|
|
131
|
-
if (fails >
|
|
166
|
+
if (fails > bailAfter) {
|
|
132
167
|
// conservatively bail out of non-tamagui sheets
|
|
133
168
|
return
|
|
134
169
|
}
|
|
135
170
|
continue
|
|
136
171
|
}
|
|
137
172
|
|
|
138
|
-
const [identifier, cssRule] = response
|
|
173
|
+
const [identifier, cssRule, isTheme] = response
|
|
174
|
+
|
|
175
|
+
if (isTheme) {
|
|
176
|
+
const deduped = addThemesFromCSS(cssRule, tokens)
|
|
177
|
+
if (deduped) {
|
|
178
|
+
dedupedThemes ||= []
|
|
179
|
+
dedupedThemes.push(deduped)
|
|
180
|
+
}
|
|
181
|
+
continue
|
|
182
|
+
}
|
|
139
183
|
|
|
140
184
|
// track references
|
|
141
185
|
const total = track(identifier, remove)
|
|
@@ -156,32 +200,132 @@ function updateSheetStyles(sheet: CSSStyleSheet, remove = false) {
|
|
|
156
200
|
}
|
|
157
201
|
|
|
158
202
|
scannedCache.set(sheet, cacheKey)
|
|
203
|
+
|
|
204
|
+
return dedupedThemes
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let colorVarToVal: Record<string, string>
|
|
208
|
+
let rootComputedStyle: CSSStyleDeclaration | null = null
|
|
209
|
+
|
|
210
|
+
function addThemesFromCSS(cssStyleRule: CSSStyleRule, tokens?: TokensParsed) {
|
|
211
|
+
const selectors = cssStyleRule.selectorText.split(',')
|
|
212
|
+
|
|
213
|
+
if (!selectors.length) return
|
|
214
|
+
|
|
215
|
+
if (tokens && !colorVarToVal) {
|
|
216
|
+
colorVarToVal = {}
|
|
217
|
+
for (const key in tokens.color) {
|
|
218
|
+
const token = tokens.color[key]
|
|
219
|
+
colorVarToVal[token.name] = token.val
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const rulesWithBraces = (cssStyleRule.cssText || '')
|
|
224
|
+
.slice(cssStyleRule.selectorText.length + 2, -1)
|
|
225
|
+
.trim()
|
|
226
|
+
const rules = rulesWithBraces.split(';')
|
|
227
|
+
|
|
228
|
+
// get theme object parsed
|
|
229
|
+
const values: ThemeParsed = {}
|
|
230
|
+
// build values first
|
|
231
|
+
for (const rule of rules) {
|
|
232
|
+
const sepI = rule.indexOf(':')
|
|
233
|
+
if (sepI === -1) continue
|
|
234
|
+
const key = rule.slice(rule.indexOf('--') + 2, sepI)
|
|
235
|
+
const val = rule.slice(sepI + 2)
|
|
236
|
+
let value: string
|
|
237
|
+
if (val[3] === '(') {
|
|
238
|
+
// var()
|
|
239
|
+
const varName = val.slice(6, -1)
|
|
240
|
+
const tokenVal = colorVarToVal[varName]
|
|
241
|
+
// either hydrate it from tokens directly or from computed style on body if no token
|
|
242
|
+
if (tokenVal) {
|
|
243
|
+
value = tokenVal
|
|
244
|
+
} else {
|
|
245
|
+
rootComputedStyle ||= getComputedStyle(document.body)
|
|
246
|
+
value = rootComputedStyle.getPropertyValue('--' + varName)
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
value = val
|
|
250
|
+
}
|
|
251
|
+
values[key] = createVariable(
|
|
252
|
+
{
|
|
253
|
+
key,
|
|
254
|
+
name: key,
|
|
255
|
+
val: value,
|
|
256
|
+
},
|
|
257
|
+
true
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const dedupedEntry: DedupedTheme = {
|
|
262
|
+
names: [],
|
|
263
|
+
theme: values,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// loop selectors and build deduped
|
|
267
|
+
for (const selector of selectors) {
|
|
268
|
+
let scheme = selector.includes('t_dark')
|
|
269
|
+
? 'dark'
|
|
270
|
+
: selector.includes('t_light')
|
|
271
|
+
? 'light'
|
|
272
|
+
: ''
|
|
273
|
+
let name = selector.slice(selector.lastIndexOf('.t_') + 3)
|
|
274
|
+
|
|
275
|
+
if (name.startsWith(scheme)) {
|
|
276
|
+
// we have some hardcoded for component themes t_light_name
|
|
277
|
+
name = name.slice(scheme.length + 1)
|
|
278
|
+
}
|
|
279
|
+
// for base dark and light
|
|
280
|
+
if (scheme === name) {
|
|
281
|
+
scheme = ''
|
|
282
|
+
}
|
|
283
|
+
const themeName = `${scheme}${scheme && name ? '_' : ''}${name}`
|
|
284
|
+
|
|
285
|
+
if (dedupedEntry.names.includes(themeName)) {
|
|
286
|
+
continue
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
dedupedEntry.names.push(themeName)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return dedupedEntry
|
|
159
293
|
}
|
|
160
294
|
|
|
161
295
|
function getTamaguiSelector(
|
|
162
|
-
rule: CSSRule | null
|
|
163
|
-
|
|
296
|
+
rule: CSSRule | null,
|
|
297
|
+
collectThemes = false
|
|
298
|
+
): readonly [string, CSSStyleRule] | [string, CSSStyleRule, true] | undefined {
|
|
164
299
|
if (rule instanceof CSSStyleRule) {
|
|
165
300
|
const text = rule.selectorText
|
|
166
|
-
if (text[0] === '
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
301
|
+
if (text[0] === ':' && text[1] === 'r') {
|
|
302
|
+
if (text.startsWith(':root ._')) {
|
|
303
|
+
return [getIdentifierFromTamaguiSelector(text), rule]
|
|
304
|
+
}
|
|
305
|
+
if (collectThemes) {
|
|
306
|
+
if (text.startsWith(':root.t_') || text.startsWith(':root .t_')) {
|
|
307
|
+
return [
|
|
308
|
+
text.slice(0, 20), // just used as uid
|
|
309
|
+
rule,
|
|
310
|
+
true,
|
|
311
|
+
]
|
|
312
|
+
}
|
|
313
|
+
}
|
|
171
314
|
}
|
|
172
315
|
} else if (rule instanceof CSSMediaRule) {
|
|
173
316
|
// tamagui only ever inserts 1 rule per media
|
|
174
|
-
if (rule.cssRules.length > 1) return
|
|
317
|
+
if (rule.cssRules.length > 1) return
|
|
175
318
|
return getTamaguiSelector(rule.cssRules[0])
|
|
176
319
|
}
|
|
177
|
-
return null
|
|
178
320
|
}
|
|
179
321
|
|
|
180
|
-
const getIdentifierFromTamaguiSelector = (selector: string) =>
|
|
181
|
-
selector
|
|
182
|
-
|
|
183
|
-
.replace(/:[a-z]+$/, '')
|
|
184
|
-
|
|
322
|
+
const getIdentifierFromTamaguiSelector = (selector: string) => {
|
|
323
|
+
let res = selector.slice(8)
|
|
324
|
+
if (selector.includes(':')) {
|
|
325
|
+
return res.replace(/:[a-z]+$/, '')
|
|
326
|
+
}
|
|
327
|
+
return res
|
|
328
|
+
}
|
|
185
329
|
|
|
186
330
|
const sheet = isClient
|
|
187
331
|
? document.head.appendChild(document.createElement('style')).sheet
|
|
@@ -120,13 +120,13 @@ const resolveVariants: StyleResolver = (
|
|
|
120
120
|
|
|
121
121
|
if (typeof variantValue === 'function') {
|
|
122
122
|
const fn = variantValue as VariantSpreadFunction<any>
|
|
123
|
-
|
|
124
|
-
variantValue = fn(value,
|
|
123
|
+
const extras = getVariantExtras(styleState)
|
|
124
|
+
variantValue = fn(value, extras)
|
|
125
125
|
|
|
126
126
|
if (process.env.NODE_ENV === 'development' && debug === 'verbose') {
|
|
127
127
|
console.groupCollapsed(' expanded functional variant', key)
|
|
128
128
|
// rome-ignore lint/nursery/noConsoleLog: <explanation>
|
|
129
|
-
console.log({ fn, variantValue })
|
|
129
|
+
console.log({ fn, variantValue, extras })
|
|
130
130
|
console.groupEnd()
|
|
131
131
|
}
|
|
132
132
|
}
|
package/src/helpers/themes.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
import { getConfig } from '../config'
|
|
1
2
|
import { createVariable, isVariable } from '../createVariable'
|
|
2
3
|
import { GetThemeUnwrapped } from '../hooks/getThemeUnwrapped'
|
|
3
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
CreateTamaguiProps,
|
|
6
|
+
DedupedThemes,
|
|
7
|
+
ThemeParsed,
|
|
8
|
+
Tokens,
|
|
9
|
+
TokensParsed,
|
|
10
|
+
} from '../types'
|
|
4
11
|
|
|
5
12
|
// mutates, freeze after
|
|
6
13
|
// shared by createTamagui so extracted here
|
|
@@ -25,13 +32,44 @@ export function ensureThemeVariable(theme: any, key: string) {
|
|
|
25
32
|
}
|
|
26
33
|
}
|
|
27
34
|
|
|
35
|
+
const themesRaw: Record<string, ThemeParsed> = {}
|
|
36
|
+
|
|
37
|
+
// this seems expensive but its necessary to do two loops unless we want to refactor a variety of things again
|
|
38
|
+
// not *too* much work but not a big cost doing the two loops
|
|
39
|
+
export function proxyThemesToParents(
|
|
40
|
+
dedupedThemes: DedupedThemes,
|
|
41
|
+
tokens: Tokens
|
|
42
|
+
): Record<string, ThemeParsed> {
|
|
43
|
+
// fill it in so we can look it up next
|
|
44
|
+
for (const { names, theme } of dedupedThemes) {
|
|
45
|
+
for (const name of names) {
|
|
46
|
+
themesRaw[name] = theme
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const themes: Record<string, ThemeParsed> = {}
|
|
51
|
+
// now go back and re-fill it in
|
|
52
|
+
// we do have to call this once per non-duplicated theme!
|
|
53
|
+
// because they could have different parent chains
|
|
54
|
+
// despite being the same theme
|
|
55
|
+
|
|
56
|
+
for (const { names, theme } of dedupedThemes) {
|
|
57
|
+
for (const themeName of names) {
|
|
58
|
+
const proxiedTheme = proxyThemeToParents(themeName, theme, tokens)
|
|
59
|
+
themes[themeName] = proxiedTheme
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return themes
|
|
64
|
+
}
|
|
65
|
+
|
|
28
66
|
export function proxyThemeToParents(
|
|
29
67
|
themeName: string,
|
|
30
|
-
theme:
|
|
31
|
-
|
|
68
|
+
theme: ThemeParsed,
|
|
69
|
+
tokens: TokensParsed = getConfig().tokensParsed
|
|
32
70
|
) {
|
|
33
|
-
// we could test if this is better as just a straight object spread or fancier proxy
|
|
34
71
|
const cur: string[] = []
|
|
72
|
+
|
|
35
73
|
// if theme is dark_blue_alt1_Button
|
|
36
74
|
// this will be the parent names in order: ['dark', 'dark_blue', 'dark_blue_alt1"]
|
|
37
75
|
const parents = themeName
|
|
@@ -44,21 +82,21 @@ export function proxyThemeToParents(
|
|
|
44
82
|
|
|
45
83
|
const numParents = parents.length
|
|
46
84
|
|
|
85
|
+
// TODO maybe faster to just object spread? needs profiling on native + web
|
|
47
86
|
// proxy fallback values to parent theme values
|
|
48
87
|
return new Proxy(theme, {
|
|
49
88
|
get(target, key) {
|
|
50
89
|
if (key === GetThemeUnwrapped) return theme
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
90
|
+
if (Reflect.has(target, key)) return Reflect.get(target, key)
|
|
91
|
+
// check parents
|
|
92
|
+
for (let i = numParents - 1; i >= 0; i--) {
|
|
93
|
+
const parent = themesRaw[parents[i]]
|
|
94
|
+
if (!parent) continue
|
|
95
|
+
if (Reflect.has(parent, key)) {
|
|
96
|
+
return Reflect.get(parent, key)
|
|
59
97
|
}
|
|
60
98
|
}
|
|
61
|
-
return Reflect.get(
|
|
99
|
+
return Reflect.get(tokens, key)
|
|
62
100
|
},
|
|
63
101
|
})
|
|
64
102
|
}
|
package/src/hooks/useTheme.tsx
CHANGED
|
@@ -61,6 +61,13 @@ export const useThemeWithState = (
|
|
|
61
61
|
const { themeManager, state } = changedThemeState
|
|
62
62
|
const { theme, name, className } = state
|
|
63
63
|
if (!theme) {
|
|
64
|
+
if (process.env.NODE_ENV === 'develpoment') {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`No theme given props ${JSON.stringify(props)} in themes: ${Object.keys(
|
|
67
|
+
getConfig().themes
|
|
68
|
+
)}`
|
|
69
|
+
)
|
|
70
|
+
}
|
|
64
71
|
throw `❌`
|
|
65
72
|
}
|
|
66
73
|
|
|
@@ -109,8 +116,10 @@ export function getThemeProxied(
|
|
|
109
116
|
if (typeof key === 'string' && keys) {
|
|
110
117
|
// auto convert variables to plain
|
|
111
118
|
const keyString = key[0] === '$' ? key.slice(1) : key
|
|
112
|
-
const val =
|
|
119
|
+
const val = theme[keyString]
|
|
113
120
|
if (val && typeof val === 'object') {
|
|
121
|
+
// TODO this could definitely be done better by at the very minimum
|
|
122
|
+
// proxying it up front and just having a listener here
|
|
114
123
|
return new Proxy(val as any, {
|
|
115
124
|
// when they touch the actual value we only track it
|
|
116
125
|
// if its a variable (web), its ignored!
|
package/src/types.tsx
CHANGED
|
@@ -2112,3 +2112,13 @@ export type GetRef<C> = C extends TamaguiComponent<any, infer Ref>
|
|
|
2112
2112
|
) extends Ref<infer T> | string | undefined
|
|
2113
2113
|
? T
|
|
2114
2114
|
: unknown
|
|
2115
|
+
|
|
2116
|
+
export type ThemesLikeObject = Record<string, Record<string, string>>
|
|
2117
|
+
|
|
2118
|
+
// dedupe themes to avoid duplicate CSS generation
|
|
2119
|
+
export type DedupedTheme = {
|
|
2120
|
+
names: string[]
|
|
2121
|
+
theme: ThemeParsed
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
export type DedupedThemes = DedupedTheme[]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createComponent.d.ts","sourceRoot":"","sources":["../src/createComponent.tsx"],"names":[],"mappings":"AAIA,OAAO,KAWN,MAAM,OAAO,CAAA;AAgBd,OAAO,EACL,SAAS,EACT,cAAc,EACd,UAAU,EACV,WAAW,EACX,YAAY,EACZ,gBAAgB,EAEhB,qBAAqB,EACrB,cAAc,EAIf,MAAM,SAAS,CAAA;AAUhB,eAAO,MAAM,qBAAqB,EAAE,qBAMnC,CAAA;AAwBD,eAAO,MAAM,QAAQ,eAAsB,CAAA;AAqB3C,wBAAgB,eAAe,CAC7B,kBAAkB,SAAS,MAAM,GAAG,EAAE,EACtC,GAAG,GAAG,cAAc,EACpB,SAAS,GAAG,KAAK,EACjB,YAAY,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"createComponent.d.ts","sourceRoot":"","sources":["../src/createComponent.tsx"],"names":[],"mappings":"AAIA,OAAO,KAWN,MAAM,OAAO,CAAA;AAgBd,OAAO,EACL,SAAS,EACT,cAAc,EACd,UAAU,EACV,WAAW,EACX,YAAY,EACZ,gBAAgB,EAEhB,qBAAqB,EACrB,cAAc,EAIf,MAAM,SAAS,CAAA;AAUhB,eAAO,MAAM,qBAAqB,EAAE,qBAMnC,CAAA;AAwBD,eAAO,MAAM,QAAQ,eAAsB,CAAA;AAqB3C,wBAAgB,eAAe,CAC7B,kBAAkB,SAAS,MAAM,GAAG,EAAE,EACtC,GAAG,GAAG,cAAc,EACpB,SAAS,GAAG,KAAK,EACjB,YAAY,EAAE,YAAY,4DAg2B3B;AAGD,wBAAgB,QAAQ,CAAC,KAAK,EAAE;IAAE,QAAQ,CAAC,EAAE,GAAG,CAAA;CAAE,OAEjD;AAMD,eAAO,MAAM,MAAM,0DA8CjB,CAAA;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IAC5B,SAAS,CAAC,EAAE,cAAc,CAAA;IAC1B,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC3B,KAAK,CAAC,EAAE,SAAS,CAAA;CAClB,CAAA;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,mBAkGxD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createTamagui.d.ts","sourceRoot":"","sources":["../src/createTamagui.ts"],"names":[],"mappings":"AAgBA,OAAO,EACL,kBAAkB,
|
|
1
|
+
{"version":3,"file":"createTamagui.d.ts","sourceRoot":"","sources":["../src/createTamagui.ts"],"names":[],"mappings":"AAgBA,OAAO,EACL,kBAAkB,EAIlB,kBAAkB,EAInB,MAAM,SAAS,CAAA;AAKhB,wBAAgB,aAAa,CAAC,IAAI,SAAS,kBAAkB,EAC3D,QAAQ,EAAE,IAAI,GACb,kBAAkB,CAAC,IAAI,CAAC,CAyP1B"}
|