@tamagui/create-theme 1.31.4 → 1.32.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/index.js +9 -128
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +4 -117
- package/dist/esm/index.js.map +1 -1
- package/package.json +3 -3
- package/src/ThemeBuilder.tsx +352 -0
- package/src/createTheme.tsx +72 -0
- package/src/helpers.tsx +44 -0
- package/src/index.tsx +4 -174
- package/src/isMinusZero.tsx +3 -0
- package/src/masks.tsx +155 -0
- package/src/tests.tsx +27 -0
- package/src/themeInfo.tsx +26 -0
- package/src/types.tsx +27 -0
- package/types/ThemeBuilder.d.ts +86 -0
- package/types/createTheme.d.ts +15 -0
- package/types/helpers.d.ts +24 -0
- package/types/index.d.ts +4 -37
- package/types/index.d.ts.map +1 -1
- package/types/isMinusZero.d.ts +2 -0
- package/types/masks.d.ts +16 -0
- package/types/tests.d.ts +2 -0
- package/types/themeInfo.d.ts +10 -0
- package/types/types.d.ts +23 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { isMinusZero } from './isMinusZero'
|
|
2
|
+
import { setThemeInfo } from './themeInfo'
|
|
3
|
+
import { CreateThemeOptions, CreateThemePalette, GenericTheme, ThemeMask } from './types'
|
|
4
|
+
|
|
5
|
+
const identityCache = new Map()
|
|
6
|
+
|
|
7
|
+
export function createTheme<
|
|
8
|
+
Definition extends ThemeMask,
|
|
9
|
+
Extras extends GenericTheme = {}
|
|
10
|
+
>(
|
|
11
|
+
palette: CreateThemePalette,
|
|
12
|
+
definition: Definition,
|
|
13
|
+
options?: CreateThemeOptions
|
|
14
|
+
): {
|
|
15
|
+
[key in keyof Definition | keyof Extras]: string
|
|
16
|
+
} {
|
|
17
|
+
const cacheKey = JSON.stringify([palette, definition, options])
|
|
18
|
+
if (identityCache.has(cacheKey)) {
|
|
19
|
+
return identityCache.get(cacheKey)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const theme = {
|
|
23
|
+
...(Object.fromEntries(
|
|
24
|
+
Object.entries(definition).map(([key, offset]) => {
|
|
25
|
+
return [key, getValue(palette, offset)]
|
|
26
|
+
})
|
|
27
|
+
) as any),
|
|
28
|
+
...options?.nonInheritedValues,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setThemeInfo(theme, { palette, definition, options })
|
|
32
|
+
identityCache.set(cacheKey, theme)
|
|
33
|
+
|
|
34
|
+
return theme
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const getValue = (palette: CreateThemePalette, value: string | number) => {
|
|
38
|
+
if (typeof value === 'string') return value
|
|
39
|
+
const max = palette.length - 1
|
|
40
|
+
const isPositive = value === 0 ? !isMinusZero(value) : value >= 0
|
|
41
|
+
const next = isPositive ? value : max + value
|
|
42
|
+
const index = Math.min(Math.max(0, next), max)
|
|
43
|
+
return palette[index]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type SubThemeKeys<ParentKeys, ChildKeys> = `${ParentKeys extends string
|
|
47
|
+
? ParentKeys
|
|
48
|
+
: never}_${ChildKeys extends string ? ChildKeys : never}`
|
|
49
|
+
|
|
50
|
+
type ChildGetter<Name extends string | number | symbol, Theme extends GenericTheme> = (
|
|
51
|
+
name: Name,
|
|
52
|
+
theme: Theme
|
|
53
|
+
) => { [key: string]: Theme }
|
|
54
|
+
|
|
55
|
+
export function addChildren<
|
|
56
|
+
Themes extends { [key: string]: GenericTheme },
|
|
57
|
+
GetChildren extends ChildGetter<keyof Themes, Themes[keyof Themes]>
|
|
58
|
+
>(
|
|
59
|
+
themes: Themes,
|
|
60
|
+
getChildren: GetChildren
|
|
61
|
+
): Themes & {
|
|
62
|
+
[key in SubThemeKeys<keyof Themes, keyof ReturnType<GetChildren>>]: Themes[keyof Themes]
|
|
63
|
+
} {
|
|
64
|
+
const out = { ...themes }
|
|
65
|
+
for (const key in themes) {
|
|
66
|
+
const subThemes = getChildren(key, themes[key])
|
|
67
|
+
for (const sKey in subThemes) {
|
|
68
|
+
out[`${key}_${sKey}`] = subThemes[sKey] as any
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return out as any
|
|
72
|
+
}
|
package/src/helpers.tsx
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
type ObjectType = Record<PropertyKey, unknown>
|
|
2
|
+
|
|
3
|
+
type PickByValue<OBJ_T, VALUE_T> = // From https://stackoverflow.com/a/55153000
|
|
4
|
+
Pick<OBJ_T, { [K in keyof OBJ_T]: OBJ_T[K] extends VALUE_T ? K : never }[keyof OBJ_T]>
|
|
5
|
+
|
|
6
|
+
type ObjectEntries<OBJ_T> = // From https://stackoverflow.com/a/60142095
|
|
7
|
+
{ [K in keyof OBJ_T]: [keyof PickByValue<OBJ_T, OBJ_T[K]>, OBJ_T[K]] }[keyof OBJ_T][]
|
|
8
|
+
|
|
9
|
+
export const objectKeys = <O extends Object>(obj: O) => Object.keys(obj) as Array<keyof O>
|
|
10
|
+
|
|
11
|
+
export function objectEntries<OBJ_T extends ObjectType>(
|
|
12
|
+
obj: OBJ_T
|
|
13
|
+
): ObjectEntries<OBJ_T> {
|
|
14
|
+
return Object.entries(obj) as ObjectEntries<OBJ_T>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type EntriesType =
|
|
18
|
+
| [PropertyKey, unknown][]
|
|
19
|
+
| ReadonlyArray<readonly [PropertyKey, unknown]>
|
|
20
|
+
|
|
21
|
+
// Existing Utils
|
|
22
|
+
type DeepWritable<OBJ_T> = { -readonly [P in keyof OBJ_T]: DeepWritable<OBJ_T[P]> }
|
|
23
|
+
type UnionToIntersection<UNION_T> = // From https://stackoverflow.com/a/50375286
|
|
24
|
+
(UNION_T extends any ? (k: UNION_T) => void : never) extends (k: infer I) => void
|
|
25
|
+
? I
|
|
26
|
+
: never
|
|
27
|
+
|
|
28
|
+
// New Utils
|
|
29
|
+
type UnionObjectFromArrayOfPairs<ARR_T extends EntriesType> =
|
|
30
|
+
DeepWritable<ARR_T> extends (infer R)[]
|
|
31
|
+
? R extends [infer key, infer val]
|
|
32
|
+
? { [prop in key & PropertyKey]: val }
|
|
33
|
+
: never
|
|
34
|
+
: never
|
|
35
|
+
type MergeIntersectingObjects<ObjT> = { [key in keyof ObjT]: ObjT[key] }
|
|
36
|
+
type EntriesToObject<ARR_T extends EntriesType> = MergeIntersectingObjects<
|
|
37
|
+
UnionToIntersection<UnionObjectFromArrayOfPairs<ARR_T>>
|
|
38
|
+
>
|
|
39
|
+
|
|
40
|
+
export function objectFromEntries<ARR_T extends EntriesType>(
|
|
41
|
+
arr: ARR_T
|
|
42
|
+
): EntriesToObject<ARR_T> {
|
|
43
|
+
return Object.fromEntries(arr) as EntriesToObject<ARR_T>
|
|
44
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -1,174 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
export type MaskOptions = {
|
|
7
|
-
palette?: Palette
|
|
8
|
-
override?: Partial<ThemeMask>
|
|
9
|
-
skip?: Partial<ThemeMask>
|
|
10
|
-
strength?: number
|
|
11
|
-
max?: number
|
|
12
|
-
min?: number
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type GenericTheme = { [key: string]: string | Variable }
|
|
16
|
-
|
|
17
|
-
export type CreateMask = <A extends ThemeMask>(template: A, options: MaskOptions) => A
|
|
18
|
-
|
|
19
|
-
const THEME_INFO = new WeakMap<
|
|
20
|
-
any,
|
|
21
|
-
{ palette: Palette; definition: ThemeMask; cache: Map<any, any> }
|
|
22
|
-
>()
|
|
23
|
-
|
|
24
|
-
export function createTheme<
|
|
25
|
-
Definition extends ThemeMask,
|
|
26
|
-
Extras extends Record<string, string> = {}
|
|
27
|
-
>(
|
|
28
|
-
palette: Palette,
|
|
29
|
-
definition: Definition,
|
|
30
|
-
options?: {
|
|
31
|
-
nonInheritedValues?: Extras
|
|
32
|
-
}
|
|
33
|
-
): {
|
|
34
|
-
[key in keyof Definition | keyof Extras]: string
|
|
35
|
-
} {
|
|
36
|
-
const theme = {
|
|
37
|
-
...(Object.fromEntries(
|
|
38
|
-
Object.entries(definition).map(([key, offset]) => {
|
|
39
|
-
return [key, getValue(palette, offset)]
|
|
40
|
-
})
|
|
41
|
-
) as any),
|
|
42
|
-
...options?.nonInheritedValues,
|
|
43
|
-
}
|
|
44
|
-
THEME_INFO.set(theme, { palette, definition, cache: new Map() })
|
|
45
|
-
return theme
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const getValue = (palette: Palette, value: string | number) => {
|
|
49
|
-
if (typeof value === 'string') return value
|
|
50
|
-
const max = palette.length - 1
|
|
51
|
-
const isPositive = value === 0 ? !isMinusZero(value) : value >= 0
|
|
52
|
-
const next = isPositive ? value : max + value
|
|
53
|
-
const index = Math.min(Math.max(0, next), max)
|
|
54
|
-
return palette[index]
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
type SubThemeKeys<ParentKeys, ChildKeys> = `${ParentKeys extends string
|
|
58
|
-
? ParentKeys
|
|
59
|
-
: never}_${ChildKeys extends string ? ChildKeys : never}`
|
|
60
|
-
|
|
61
|
-
type ChildGetter<Name extends string | number | symbol, Theme extends GenericTheme> = (
|
|
62
|
-
name: Name,
|
|
63
|
-
theme: Theme
|
|
64
|
-
) => { [key: string]: Theme }
|
|
65
|
-
|
|
66
|
-
export function addChildren<
|
|
67
|
-
Themes extends { [key: string]: GenericTheme },
|
|
68
|
-
GetChildren extends ChildGetter<keyof Themes, Themes[keyof Themes]>
|
|
69
|
-
>(
|
|
70
|
-
themes: Themes,
|
|
71
|
-
getChildren: GetChildren
|
|
72
|
-
): Themes & {
|
|
73
|
-
[key in SubThemeKeys<keyof Themes, keyof ReturnType<GetChildren>>]: Themes[keyof Themes]
|
|
74
|
-
} {
|
|
75
|
-
const out = { ...themes }
|
|
76
|
-
for (const key in themes) {
|
|
77
|
-
const subThemes = getChildren(key, themes[key])
|
|
78
|
-
for (const sKey in subThemes) {
|
|
79
|
-
out[`${key}_${sKey}`] = subThemes[sKey] as any
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return out as any
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export const skipMask: CreateMask = (template, { skip }) => {
|
|
86
|
-
if (!skip) return template
|
|
87
|
-
return Object.fromEntries(
|
|
88
|
-
Object.entries(template).filter(([k]) => !(k in skip))
|
|
89
|
-
) as typeof template
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export const createShiftMask = ({ inverse }: { inverse?: boolean } = {}) => {
|
|
93
|
-
return ((template, opts) => {
|
|
94
|
-
const { override, max: maxIn, palette, min = 0, strength = 1 } = opts
|
|
95
|
-
const values = Object.entries(template)
|
|
96
|
-
const max = maxIn ?? (palette ? Object.values(palette).length - 1 : Infinity)
|
|
97
|
-
const out = {}
|
|
98
|
-
for (const [key, value] of values) {
|
|
99
|
-
if (typeof value === 'string') continue
|
|
100
|
-
if (typeof override?.[key] === 'number') {
|
|
101
|
-
const overrideShift = override[key] as number
|
|
102
|
-
out[key] = value + overrideShift
|
|
103
|
-
continue
|
|
104
|
-
}
|
|
105
|
-
const isPositive = value === 0 ? !isMinusZero(value) : value >= 0
|
|
106
|
-
const direction = isPositive ? 1 : -1
|
|
107
|
-
const invert = inverse ? -1 : 1
|
|
108
|
-
const next = value + strength * direction * invert
|
|
109
|
-
const clamped = isPositive
|
|
110
|
-
? Math.max(min, Math.min(max, next))
|
|
111
|
-
: Math.min(-min, Math.max(-max, next))
|
|
112
|
-
|
|
113
|
-
out[key] = clamped
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return skipMask(out, opts) as typeof template
|
|
117
|
-
}) as CreateMask
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export const createWeakenMask = () => createShiftMask()
|
|
121
|
-
export const createStrengthenMask = () => createShiftMask({ inverse: true })
|
|
122
|
-
|
|
123
|
-
function isMinusZero(value) {
|
|
124
|
-
return 1 / value === -Infinity
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export function applyMask<Theme extends GenericTheme>(
|
|
128
|
-
theme: Theme,
|
|
129
|
-
mask: CreateMask,
|
|
130
|
-
options: MaskOptions = {}
|
|
131
|
-
): Theme {
|
|
132
|
-
const info = THEME_INFO.get(theme)
|
|
133
|
-
if (!info) {
|
|
134
|
-
throw new Error(
|
|
135
|
-
process.env.NODE_ENV !== 'production'
|
|
136
|
-
? `No info found for theme, you must pass the theme created by createThemeFromPalette directly to extendTheme`
|
|
137
|
-
: `❌ Err2`
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const template = mask(info.definition, {
|
|
142
|
-
palette: info.palette,
|
|
143
|
-
...options,
|
|
144
|
-
})
|
|
145
|
-
const next = createTheme(info.palette, template) as Theme
|
|
146
|
-
|
|
147
|
-
return next
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// --- tests ---
|
|
151
|
-
|
|
152
|
-
if (process.env.NODE_ENV === 'development') {
|
|
153
|
-
const palette = ['0', '1', '2', '3', '-3', '-2', '-1', '-0']
|
|
154
|
-
const template = { bg: 1, fg: -1 }
|
|
155
|
-
|
|
156
|
-
const stongerMask = createStrengthenMask()
|
|
157
|
-
const weakerMask = createWeakenMask()
|
|
158
|
-
|
|
159
|
-
const theme = createTheme(palette, template)
|
|
160
|
-
if (theme.bg !== '1') throw `❌`
|
|
161
|
-
if (theme.fg !== '-1') throw `❌`
|
|
162
|
-
|
|
163
|
-
const str = applyMask(theme, stongerMask)
|
|
164
|
-
if (str.bg !== '0') throw `❌`
|
|
165
|
-
if (str.fg !== '-0') throw `❌`
|
|
166
|
-
|
|
167
|
-
const weak = applyMask(theme, weakerMask)
|
|
168
|
-
if (weak.bg !== '2') throw `❌`
|
|
169
|
-
if (weak.fg !== '-2') throw `❌`
|
|
170
|
-
|
|
171
|
-
const weak2 = applyMask(theme, weakerMask, { strength: 2 })
|
|
172
|
-
if (weak2.bg !== '3') throw `❌`
|
|
173
|
-
if (weak2.fg !== '-3') throw `❌`
|
|
174
|
-
}
|
|
1
|
+
export * from './createTheme'
|
|
2
|
+
export * from './masks'
|
|
3
|
+
export * from './types'
|
|
4
|
+
export * from './ThemeBuilder'
|
package/src/masks.tsx
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { createTheme } from './createTheme'
|
|
2
|
+
import { objectEntries, objectFromEntries } from './helpers'
|
|
3
|
+
import { isMinusZero } from './isMinusZero'
|
|
4
|
+
import { getThemeInfo, setThemeInfo } from './themeInfo'
|
|
5
|
+
import { CreateMask, GenericTheme, MaskFunction, MaskOptions, ThemeMask } from './types'
|
|
6
|
+
|
|
7
|
+
export const createMask = <C extends CreateMask | MaskFunction>(
|
|
8
|
+
createMask: C
|
|
9
|
+
): CreateMask =>
|
|
10
|
+
typeof createMask === 'function'
|
|
11
|
+
? { name: createMask.name || 'unnamed', mask: createMask }
|
|
12
|
+
: createMask
|
|
13
|
+
|
|
14
|
+
export const combineMasks = (...masks: CreateMask[]) => {
|
|
15
|
+
const mask: CreateMask = {
|
|
16
|
+
name: 'combine-mask',
|
|
17
|
+
mask: (template, opts) => {
|
|
18
|
+
let current = template
|
|
19
|
+
for (const mask of masks) {
|
|
20
|
+
current = applyMask(current, mask, opts)
|
|
21
|
+
}
|
|
22
|
+
return current
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
return mask
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const skipMask: CreateMask = {
|
|
29
|
+
name: 'skip-mask',
|
|
30
|
+
mask: (template, { skip }) => {
|
|
31
|
+
if (!skip) return template
|
|
32
|
+
return Object.fromEntries(
|
|
33
|
+
Object.entries(template).filter(([k]) => !(k in skip))
|
|
34
|
+
) as typeof template
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const createIdentityMask = (): CreateMask => ({
|
|
39
|
+
name: 'identity-mask',
|
|
40
|
+
mask: (template) => template,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export const createInverseMask = () => {
|
|
44
|
+
const mask: CreateMask = {
|
|
45
|
+
name: 'inverse-mask',
|
|
46
|
+
mask: (template, opts) => {
|
|
47
|
+
const inversed = objectFromEntries(
|
|
48
|
+
objectEntries(template).map(([k, v]) => [k, -v])
|
|
49
|
+
) as any
|
|
50
|
+
return skipMask.mask(inversed, opts)
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
return mask
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type ShiftMaskOptions = { inverse?: boolean }
|
|
57
|
+
|
|
58
|
+
export const createShiftMask = (
|
|
59
|
+
{ inverse }: ShiftMaskOptions = {},
|
|
60
|
+
defaultOptions?: MaskOptions
|
|
61
|
+
) => {
|
|
62
|
+
const mask: CreateMask = {
|
|
63
|
+
name: 'shift-mask',
|
|
64
|
+
mask: (template, opts) => {
|
|
65
|
+
const {
|
|
66
|
+
override,
|
|
67
|
+
max: maxIn,
|
|
68
|
+
palette,
|
|
69
|
+
min = 0,
|
|
70
|
+
strength = 1,
|
|
71
|
+
} = { ...defaultOptions, ...opts }
|
|
72
|
+
const values = Object.entries(template)
|
|
73
|
+
const max = maxIn ?? (palette ? Object.values(palette).length - 1 : Infinity)
|
|
74
|
+
const out = {}
|
|
75
|
+
for (const [key, value] of values) {
|
|
76
|
+
if (typeof value === 'string') continue
|
|
77
|
+
if (typeof override?.[key] === 'number') {
|
|
78
|
+
const overrideShift = override[key] as number
|
|
79
|
+
out[key] = value + overrideShift
|
|
80
|
+
continue
|
|
81
|
+
} else if (typeof override?.[key] === 'string') {
|
|
82
|
+
out[key] = override[key]
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
const isPositive = value === 0 ? !isMinusZero(value) : value >= 0
|
|
86
|
+
const direction = isPositive ? 1 : -1
|
|
87
|
+
const invert = inverse ? -1 : 1
|
|
88
|
+
const next = value + strength * direction * invert
|
|
89
|
+
const clamped = isPositive
|
|
90
|
+
? Math.max(min, Math.min(max, next))
|
|
91
|
+
: Math.min(-min, Math.max(-max, next))
|
|
92
|
+
|
|
93
|
+
out[key] = clamped
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return skipMask.mask(out, opts) as typeof template
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
return mask
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const createWeakenMask = (defaultOptions?: MaskOptions): CreateMask => ({
|
|
103
|
+
name: 'soften-mask',
|
|
104
|
+
mask: createShiftMask({}, defaultOptions).mask,
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
export const createSoftenMask = createWeakenMask
|
|
108
|
+
|
|
109
|
+
export const createStrengthenMask = (defaultOptions?: MaskOptions): CreateMask => ({
|
|
110
|
+
name: 'strengthen-mask',
|
|
111
|
+
mask: createShiftMask({ inverse: true }, defaultOptions).mask,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
export function applyMask<Theme extends GenericTheme | ThemeMask>(
|
|
115
|
+
theme: Theme,
|
|
116
|
+
mask: CreateMask,
|
|
117
|
+
options: MaskOptions = {}
|
|
118
|
+
): Theme {
|
|
119
|
+
const info = getThemeInfo(theme)
|
|
120
|
+
|
|
121
|
+
if (!info) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
process.env.NODE_ENV !== 'production'
|
|
124
|
+
? `No info found for theme, you must pass the theme created by createThemeFromPalette directly to extendTheme`
|
|
125
|
+
: `❌ Err2`
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const skip = {
|
|
130
|
+
...options.skip,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// skip nonInheritedValues from parent theme
|
|
134
|
+
if (info.options?.nonInheritedValues) {
|
|
135
|
+
for (const key in info.options.nonInheritedValues) {
|
|
136
|
+
skip[key] = 1
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// convert theme back to template first
|
|
141
|
+
const template = mask.mask(info.definition, {
|
|
142
|
+
palette: info.palette,
|
|
143
|
+
...options,
|
|
144
|
+
skip,
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const next = createTheme(info.palette, template) as Theme
|
|
148
|
+
|
|
149
|
+
setThemeInfo(next, {
|
|
150
|
+
definition: template,
|
|
151
|
+
palette: info.palette,
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
return next
|
|
155
|
+
}
|
package/src/tests.tsx
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createTheme } from './createTheme'
|
|
2
|
+
import { applyMask, createStrengthenMask, createWeakenMask } from './masks'
|
|
3
|
+
|
|
4
|
+
// --- tests ---
|
|
5
|
+
if (process.env.NODE_ENV === 'development') {
|
|
6
|
+
const palette = ['0', '1', '2', '3', '-3', '-2', '-1', '-0']
|
|
7
|
+
const template = { bg: 1, fg: -1 }
|
|
8
|
+
|
|
9
|
+
const stongerMask = createStrengthenMask()
|
|
10
|
+
const weakerMask = createWeakenMask()
|
|
11
|
+
|
|
12
|
+
const theme = createTheme(palette, template)
|
|
13
|
+
if (theme.bg !== '1') throw `❌`
|
|
14
|
+
if (theme.fg !== '-1') throw `❌`
|
|
15
|
+
|
|
16
|
+
const str = applyMask(theme, stongerMask)
|
|
17
|
+
if (str.bg !== '0') throw `❌`
|
|
18
|
+
if (str.fg !== '-0') throw `❌`
|
|
19
|
+
|
|
20
|
+
const weak = applyMask(theme, weakerMask)
|
|
21
|
+
if (weak.bg !== '2') throw `❌`
|
|
22
|
+
if (weak.fg !== '-2') throw `❌`
|
|
23
|
+
|
|
24
|
+
const weak2 = applyMask(theme, weakerMask, { strength: 2 })
|
|
25
|
+
if (weak2.bg !== '3') throw `❌`
|
|
26
|
+
if (weak2.fg !== '-3') throw `❌`
|
|
27
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CreateThemeOptions, CreateThemePalette, GenericTheme, ThemeMask } from './types'
|
|
2
|
+
|
|
3
|
+
export type ThemeInfo = {
|
|
4
|
+
palette: CreateThemePalette
|
|
5
|
+
definition: ThemeMask
|
|
6
|
+
options?: CreateThemeOptions
|
|
7
|
+
cache: Map<any, any>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const THEME_INFO = new Map<string, ThemeInfo>()
|
|
11
|
+
|
|
12
|
+
export const getThemeInfo = (theme: GenericTheme | ThemeMask): ThemeInfo | undefined => {
|
|
13
|
+
return THEME_INFO.get(JSON.stringify(theme))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const setThemeInfo = (
|
|
17
|
+
theme: GenericTheme | ThemeMask,
|
|
18
|
+
info: Pick<ThemeInfo, 'palette' | 'definition' | 'options'>
|
|
19
|
+
) => {
|
|
20
|
+
const next = {
|
|
21
|
+
...info,
|
|
22
|
+
cache: new Map(),
|
|
23
|
+
}
|
|
24
|
+
THEME_INFO.set(JSON.stringify(theme), next)
|
|
25
|
+
THEME_INFO.set(JSON.stringify(info.definition), next)
|
|
26
|
+
}
|
package/src/types.tsx
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Variable } from '@tamagui/web'
|
|
2
|
+
|
|
3
|
+
export type CreateThemePalette = (string | Variable)[]
|
|
4
|
+
|
|
5
|
+
export type ThemeMask = Record<string, string | number>
|
|
6
|
+
|
|
7
|
+
export type MaskOptions = {
|
|
8
|
+
palette?: CreateThemePalette
|
|
9
|
+
override?: Partial<ThemeMask>
|
|
10
|
+
skip?: Partial<ThemeMask>
|
|
11
|
+
strength?: number
|
|
12
|
+
max?: number
|
|
13
|
+
min?: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type GenericTheme = { [key: string]: string | Variable }
|
|
17
|
+
|
|
18
|
+
export type MaskFunction = <A extends ThemeMask>(template: A, options: MaskOptions) => A
|
|
19
|
+
|
|
20
|
+
export type CreateMask = {
|
|
21
|
+
name: string
|
|
22
|
+
mask: MaskFunction
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type CreateThemeOptions = {
|
|
26
|
+
nonInheritedValues?: GenericTheme
|
|
27
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Narrow } from '@tamagui/web';
|
|
2
|
+
import { CreateMask, CreateThemeOptions, MaskOptions } from './types';
|
|
3
|
+
export type Palette = string[];
|
|
4
|
+
type GenericTheme = {
|
|
5
|
+
[key: string]: string;
|
|
6
|
+
};
|
|
7
|
+
export type Template = {
|
|
8
|
+
[key: string]: number;
|
|
9
|
+
};
|
|
10
|
+
type ThemeUsingMask<Masks = string> = MaskOptions & {
|
|
11
|
+
mask: Masks;
|
|
12
|
+
};
|
|
13
|
+
type ThemeUsingTemplate = CreateThemeOptions & {
|
|
14
|
+
palette: string;
|
|
15
|
+
template: string;
|
|
16
|
+
};
|
|
17
|
+
type ThemePreDefined = {
|
|
18
|
+
theme: GenericTheme;
|
|
19
|
+
};
|
|
20
|
+
export type Theme<Masks = string> = ThemePreDefined | ThemeUsingTemplate | ThemeUsingMask<Masks>;
|
|
21
|
+
type ThemeWithParent<Masks = string> = Theme<Masks> & {
|
|
22
|
+
parent: string;
|
|
23
|
+
};
|
|
24
|
+
type PaletteDefinitions = {
|
|
25
|
+
[key: string]: Palette;
|
|
26
|
+
};
|
|
27
|
+
type ThemeDefinition<Masks extends string = string> = Theme<Masks> | ThemeWithParent<Masks>[];
|
|
28
|
+
type UnionableString = string & {};
|
|
29
|
+
type ThemeDefinitions<Masks extends string = string> = {
|
|
30
|
+
[key: string]: ThemeDefinition<Masks | UnionableString>;
|
|
31
|
+
};
|
|
32
|
+
type TemplateDefinitions = {
|
|
33
|
+
[key: string]: Template;
|
|
34
|
+
};
|
|
35
|
+
type MaskDefinitions = {
|
|
36
|
+
[key: string]: CreateMask | CreateMask['mask'];
|
|
37
|
+
};
|
|
38
|
+
type ThemeBuilderState = {
|
|
39
|
+
palettes?: PaletteDefinitions;
|
|
40
|
+
templates?: TemplateDefinitions;
|
|
41
|
+
themes?: ThemeDefinitions;
|
|
42
|
+
masks?: MaskDefinitions;
|
|
43
|
+
};
|
|
44
|
+
type ObjectStringKeys<A extends Object | undefined> = A extends Object ? Exclude<keyof A, symbol | number> : never;
|
|
45
|
+
type GetGeneratedThemeFromTemplate<Template> = {
|
|
46
|
+
[key in keyof Template]: string;
|
|
47
|
+
};
|
|
48
|
+
type GetParentTheme<P, Themes extends ThemeDefinitions | undefined> = P extends string ? P extends keyof Themes ? Themes[P] : GetParentName<P> extends keyof Themes ? Themes[GetParentName<P>] : GetParentName<GetParentName<P>> extends keyof Themes ? Themes[GetParentName<GetParentName<P>>] : GetParentName<GetParentName<GetParentName<P>>> extends keyof Themes ? Themes[GetParentName<GetParentName<GetParentName<P>>>] : never : never;
|
|
49
|
+
type GetGeneratedTheme<TD extends any, S extends ThemeBuilderState> = TD extends {
|
|
50
|
+
theme: infer T;
|
|
51
|
+
} ? T : TD extends {
|
|
52
|
+
parent: infer P;
|
|
53
|
+
} ? GetGeneratedTheme<GetParentTheme<P, S['themes']>, S> : TD extends {
|
|
54
|
+
template: infer T;
|
|
55
|
+
} ? T extends keyof S['templates'] ? GetGeneratedThemeFromTemplate<S['templates'][T]> : TD : TD;
|
|
56
|
+
type GetGeneratedThemes<S extends ThemeBuilderState> = {
|
|
57
|
+
[Key in keyof S['themes']]: GetGeneratedTheme<S['themes'][Key], S>;
|
|
58
|
+
};
|
|
59
|
+
type GetParentName<N extends string> = N extends `${infer A}_${infer B}_${infer C}_${infer D}_${string}` ? `${A}_${B}_${C}_${D}` : N extends `${infer A}_${infer B}_${infer C}_${string}` ? `${A}_${B}_${C}` : N extends `${infer A}_${infer B}_${string}` ? `${A}_${B}` : N extends `${infer A}_${string}` ? `${A}` : never;
|
|
60
|
+
declare class ThemeBuilder<State extends ThemeBuilderState> {
|
|
61
|
+
state: State;
|
|
62
|
+
constructor(state: State);
|
|
63
|
+
addPalettes<const P extends PaletteDefinitions>(palettes: P): ThemeBuilder<State & {
|
|
64
|
+
readonly palettes: P;
|
|
65
|
+
}>;
|
|
66
|
+
addTemplates<const T extends TemplateDefinitions>(templates: T): ThemeBuilder<State & {
|
|
67
|
+
templates: T;
|
|
68
|
+
}>;
|
|
69
|
+
addMasks<const T extends MaskDefinitions>(masks: T): ThemeBuilder<State & {
|
|
70
|
+
masks: T;
|
|
71
|
+
}>;
|
|
72
|
+
addThemes<const T extends ThemeDefinitions<ObjectStringKeys<State['masks']>>>(themes: T): ThemeBuilder<State & {
|
|
73
|
+
themes: T;
|
|
74
|
+
}>;
|
|
75
|
+
addChildThemes<CTD extends Narrow<ThemeDefinitions<ObjectStringKeys<State['masks']>>>, const AvoidNestingWithin extends string[] = []>(childThemeDefinition: CTD, options?: {
|
|
76
|
+
avoidNestingWithin?: AvoidNestingWithin;
|
|
77
|
+
}): ThemeBuilder<State & {
|
|
78
|
+
themes: { [key in `${Exclude<keyof NonNullable<State["themes"]>, number | symbol>}_${Exclude<keyof CTD, number | symbol>}`]: CTD & {
|
|
79
|
+
parent: GetParentName<key>;
|
|
80
|
+
}; };
|
|
81
|
+
}>;
|
|
82
|
+
build(): GetGeneratedThemes<State>;
|
|
83
|
+
}
|
|
84
|
+
export declare function createThemeBuilder(): ThemeBuilder<{}>;
|
|
85
|
+
export {};
|
|
86
|
+
//# sourceMappingURL=ThemeBuilder.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CreateThemeOptions, CreateThemePalette, GenericTheme, ThemeMask } from './types';
|
|
2
|
+
export declare function createTheme<Definition extends ThemeMask, Extras extends GenericTheme = {}>(palette: CreateThemePalette, definition: Definition, options?: CreateThemeOptions): {
|
|
3
|
+
[key in keyof Definition | keyof Extras]: string;
|
|
4
|
+
};
|
|
5
|
+
type SubThemeKeys<ParentKeys, ChildKeys> = `${ParentKeys extends string ? ParentKeys : never}_${ChildKeys extends string ? ChildKeys : never}`;
|
|
6
|
+
type ChildGetter<Name extends string | number | symbol, Theme extends GenericTheme> = (name: Name, theme: Theme) => {
|
|
7
|
+
[key: string]: Theme;
|
|
8
|
+
};
|
|
9
|
+
export declare function addChildren<Themes extends {
|
|
10
|
+
[key: string]: GenericTheme;
|
|
11
|
+
}, GetChildren extends ChildGetter<keyof Themes, Themes[keyof Themes]>>(themes: Themes, getChildren: GetChildren): Themes & {
|
|
12
|
+
[key in SubThemeKeys<keyof Themes, keyof ReturnType<GetChildren>>]: Themes[keyof Themes];
|
|
13
|
+
};
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=createTheme.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
type ObjectType = Record<PropertyKey, unknown>;
|
|
2
|
+
type PickByValue<OBJ_T, VALUE_T> = Pick<OBJ_T, {
|
|
3
|
+
[K in keyof OBJ_T]: OBJ_T[K] extends VALUE_T ? K : never;
|
|
4
|
+
}[keyof OBJ_T]>;
|
|
5
|
+
type ObjectEntries<OBJ_T> = {
|
|
6
|
+
[K in keyof OBJ_T]: [keyof PickByValue<OBJ_T, OBJ_T[K]>, OBJ_T[K]];
|
|
7
|
+
}[keyof OBJ_T][];
|
|
8
|
+
export declare const objectKeys: <O extends Object>(obj: O) => (keyof O)[];
|
|
9
|
+
export declare function objectEntries<OBJ_T extends ObjectType>(obj: OBJ_T): ObjectEntries<OBJ_T>;
|
|
10
|
+
type EntriesType = [PropertyKey, unknown][] | ReadonlyArray<readonly [PropertyKey, unknown]>;
|
|
11
|
+
type DeepWritable<OBJ_T> = {
|
|
12
|
+
-readonly [P in keyof OBJ_T]: DeepWritable<OBJ_T[P]>;
|
|
13
|
+
};
|
|
14
|
+
type UnionToIntersection<UNION_T> = (UNION_T extends any ? (k: UNION_T) => void : never) extends (k: infer I) => void ? I : never;
|
|
15
|
+
type UnionObjectFromArrayOfPairs<ARR_T extends EntriesType> = DeepWritable<ARR_T> extends (infer R)[] ? R extends [infer key, infer val] ? {
|
|
16
|
+
[prop in key & PropertyKey]: val;
|
|
17
|
+
} : never : never;
|
|
18
|
+
type MergeIntersectingObjects<ObjT> = {
|
|
19
|
+
[key in keyof ObjT]: ObjT[key];
|
|
20
|
+
};
|
|
21
|
+
type EntriesToObject<ARR_T extends EntriesType> = MergeIntersectingObjects<UnionToIntersection<UnionObjectFromArrayOfPairs<ARR_T>>>;
|
|
22
|
+
export declare function objectFromEntries<ARR_T extends EntriesType>(arr: ARR_T): EntriesToObject<ARR_T>;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=helpers.d.ts.map
|