@tamagui/web 1.47.8 → 1.48.0

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.
Files changed (55) hide show
  1. package/dist/cjs/createComponent.js +1 -1
  2. package/dist/cjs/createComponent.js.map +1 -1
  3. package/dist/cjs/createTamagui.js +40 -37
  4. package/dist/cjs/createTamagui.js.map +2 -2
  5. package/dist/cjs/createVariable.js +4 -4
  6. package/dist/cjs/createVariable.js.map +1 -1
  7. package/dist/cjs/helpers/ThemeManager.js +1 -20
  8. package/dist/cjs/helpers/ThemeManager.js.map +1 -1
  9. package/dist/cjs/helpers/insertStyleRule.js +119 -18
  10. package/dist/cjs/helpers/insertStyleRule.js.map +1 -1
  11. package/dist/cjs/helpers/propMapper.js +3 -2
  12. package/dist/cjs/helpers/propMapper.js.map +1 -1
  13. package/dist/cjs/helpers/themes.js +31 -12
  14. package/dist/cjs/helpers/themes.js.map +1 -1
  15. package/dist/cjs/hooks/useTheme.js +8 -1
  16. package/dist/cjs/hooks/useTheme.js.map +1 -1
  17. package/dist/esm/createComponent.js +1 -1
  18. package/dist/esm/createComponent.js.map +1 -1
  19. package/dist/esm/createTamagui.js +41 -38
  20. package/dist/esm/createTamagui.js.map +2 -2
  21. package/dist/esm/createVariable.js +4 -4
  22. package/dist/esm/createVariable.js.map +1 -1
  23. package/dist/esm/helpers/ThemeManager.js +2 -21
  24. package/dist/esm/helpers/ThemeManager.js.map +1 -1
  25. package/dist/esm/helpers/insertStyleRule.js +119 -18
  26. package/dist/esm/helpers/insertStyleRule.js.map +1 -1
  27. package/dist/esm/helpers/propMapper.js +3 -2
  28. package/dist/esm/helpers/propMapper.js.map +1 -1
  29. package/dist/esm/helpers/themes.js +29 -11
  30. package/dist/esm/helpers/themes.js.map +1 -1
  31. package/dist/esm/hooks/useTheme.js +8 -1
  32. package/dist/esm/hooks/useTheme.js.map +1 -1
  33. package/package.json +9 -9
  34. package/src/createComponent.tsx +2 -1
  35. package/src/createTamagui.ts +69 -69
  36. package/src/createVariable.ts +5 -4
  37. package/src/helpers/ThemeManager.tsx +1 -19
  38. package/src/helpers/insertStyleRule.tsx +168 -24
  39. package/src/helpers/propMapper.ts +3 -3
  40. package/src/helpers/themes.ts +51 -13
  41. package/src/hooks/useTheme.tsx +10 -1
  42. package/src/types.tsx +10 -0
  43. package/types/createComponent.d.ts.map +1 -1
  44. package/types/createTamagui.d.ts.map +1 -1
  45. package/types/createVariable.d.ts +1 -1
  46. package/types/createVariable.d.ts.map +1 -1
  47. package/types/helpers/ThemeManager.d.ts +0 -1
  48. package/types/helpers/ThemeManager.d.ts.map +1 -1
  49. package/types/helpers/insertStyleRule.d.ts +2 -2
  50. package/types/helpers/insertStyleRule.d.ts.map +1 -1
  51. package/types/helpers/themes.d.ts +3 -2
  52. package/types/helpers/themes.d.ts.map +1 -1
  53. package/types/hooks/useTheme.d.ts.map +1 -1
  54. package/types/types.d.ts +6 -0
  55. package/types/types.d.ts.map +1 -1
@@ -10,16 +10,19 @@ import {
10
10
  scanAllSheets,
11
11
  } from './helpers/insertStyleRule'
12
12
  import { registerCSSVariable, variableToCSS } from './helpers/registerCSSVariable'
13
- import { ensureThemeVariable, proxyThemeToParents } from './helpers/themes'
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
- scanAllSheets()
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
- // dedupe themes to avoid duplicate CSS generation
146
- type DedupedTheme = {
147
- names: string[]
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 themeName in dedupedThemes) {
168
+ for (const { names, theme } of dedupedThemes) {
210
169
  const nextRules = getThemeCSSRules({
211
170
  config: configIn,
212
- themeName,
213
- ...dedupedThemes[themeName],
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
+ }
@@ -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: getThemeUnwrapped(themes[found]),
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 type { RulesToInsert, StyleObject } from '../types'
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
- sheet && updateSheetStyles(sheet)
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
- function updateSheetStyles(sheet: CSSStyleSheet, remove = false) {
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 (!response) {
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 > 20) {
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
- ): readonly [string, CSSStyleRule] | null {
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] === '.' && text[1] === '_') {
167
- return [text.slice(1), rule]
168
- }
169
- if (text.startsWith(':root') && text.includes('._')) {
170
- return [getIdentifierFromTamaguiSelector(text), rule]
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 null
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
- .replace(/(:root)+\s+/, '')
183
- .replace(/:[a-z]+$/, '')
184
- .slice(1)
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, getVariantExtras(styleState))
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
  }
@@ -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 { CreateTamaguiProps } from '../types'
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: any,
31
- themes: CreateTamaguiProps['themes']
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 (numParents && !Reflect.has(target, key)) {
52
- // check parents
53
- for (let i = numParents - 1; i >= 0; i--) {
54
- const parent = themes[parents[i]]
55
- if (!parent) continue
56
- if (Reflect.has(parent, key)) {
57
- return Reflect.get(parent, key)
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(target, key)
99
+ return Reflect.get(tokens, key)
62
100
  },
63
101
  })
64
102
  }
@@ -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 = themeManager?.getValue(keyString)
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,4DA+1B3B;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
+ {"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,EAElB,kBAAkB,EAGnB,MAAM,SAAS,CAAA;AAKhB,wBAAgB,aAAa,CAAC,IAAI,SAAS,kBAAkB,EAC3D,QAAQ,EAAE,IAAI,GACb,kBAAkB,CAAC,IAAI,CAAC,CA4S1B"}
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"}