ThemeDefine.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. import { computed, inject, provide, ref, shallowRef, type ComputedRef, type Ref } from "vue";
  2. import { DefaultDarkTheme, DefaultTheme } from "./Theme";
  3. import type { DynamicVarType } from "./ThemeTools";
  4. import { ObjectUtils } from "@imengyu/imengyu-utils";
  5. export const ThemeKey = Symbol("NanaThemeKey");
  6. let defaultSizeUnit = 'rpx';
  7. /**
  8. * 配置默认的尺寸单位
  9. * @param unit
  10. */
  11. export function configDefaultSizeUnit(unit: string) {
  12. defaultSizeUnit = unit;
  13. }
  14. export type ViewStyle = Record<string, any>
  15. export type TextStyle = Record<string, any>
  16. export type ThemeSizeType = number|string;
  17. export type ThemePaddingOrMarginType = number|Array<number>|ThemePaddingMargin|string|string[];
  18. export interface ThemeConfig {
  19. varOverrides: Record<string, any>,
  20. colorConfigs: Record<string, Record<string, string>>,
  21. textConfigs: Record<string, Record<string, string|object>>,
  22. }
  23. function popInjectTheme() : ThemeConfig {
  24. return inject<Ref<ThemeConfig>>(ThemeKey)?.value ?? DefaultTheme;
  25. }
  26. /**
  27. * 在PropDefault回调中使用主题默认值的函数
  28. * @param key
  29. * @param defaultValue
  30. * @returns
  31. */
  32. export function propGetThemeVar<T>(key: string, defaultValue?: T) : T {
  33. const theme = popInjectTheme();
  34. return theme?.varOverrides[key] ?? defaultValue;
  35. }
  36. /**
  37. * 在PropDefault回调中使用主题默认值的函数(回调)
  38. * @param cb 回调函数,参数为获取主题变量的函数和主题配置
  39. * @returns 回调函数的返回值
  40. */
  41. export function propGetThemeVar2(cb: (getVar: (key: string, defaultValue: any) => any, theme: ThemeConfig) => any) {
  42. const theme = popInjectTheme();
  43. return cb((key, defaultValue) => getVar(theme, key, defaultValue), theme);
  44. }
  45. export function provideSomeThemeColor(record: Record<string, any>) {
  46. const topTheme = inject<Ref<ThemeConfig>>(ThemeKey);
  47. const newTheme = computed(() => {
  48. const topThemeValue = topTheme?.value ?? DefaultTheme;
  49. const v = {
  50. ...topThemeValue,
  51. colorConfigs: {
  52. ...topThemeValue.colorConfigs,
  53. ...record
  54. }
  55. } as ThemeConfig;
  56. return v;
  57. });
  58. provide(ThemeKey, newTheme);
  59. }
  60. export function provideSomeThemeVar(record: Record<string, any>) {
  61. const topTheme = inject<Ref<ThemeConfig>>(ThemeKey);
  62. const newTheme = computed(() => {
  63. const topThemeValue = topTheme?.value ?? DefaultTheme;
  64. const v = {
  65. ...topThemeValue,
  66. varOverrides: {
  67. ...topThemeValue.varOverrides,
  68. ...record
  69. }
  70. } as ThemeConfig;
  71. return v;
  72. });
  73. provide(ThemeKey, newTheme);
  74. }
  75. export function getVar<T>(theme : ThemeConfig, key: string, defaultValue: T) : T {
  76. return theme.varOverrides[key] ?? defaultValue;
  77. }
  78. /**
  79. * 主题的组合代码
  80. */
  81. export function useTheme() {
  82. const topTheme = inject<Ref<ThemeConfig>>(ThemeKey);
  83. const theme = computed(() => topTheme?.value ?? DefaultTheme);
  84. function resolveThemeSize(inValue?: ThemeSizeType, defaultValue?: ThemeSizeType) : string|undefined {
  85. const preResolve = resolveSize(inValue);
  86. if (preResolve !== undefined)
  87. return preResolve;
  88. if (defaultValue && !isRealSize(defaultValue))
  89. defaultValue = resolveThemeSize(defaultValue);
  90. if (inValue === undefined)
  91. inValue = defaultValue;
  92. if (inValue === undefined)
  93. return undefined;
  94. if (typeof inValue === 'string') {
  95. const v = getSize(inValue, undefined);
  96. if (v === undefined)
  97. inValue = defaultValue;
  98. else
  99. inValue = v;
  100. }
  101. return resolveSize(inValue);
  102. }
  103. function resolveThemeColor(inValue?: string, defaultValue?: string) : string|undefined {
  104. if (isSpecialColor(inValue))
  105. return inValue;
  106. if (inValue === undefined)
  107. inValue = defaultValue;
  108. else
  109. defaultValue = resolveThemeColor(defaultValue);
  110. if (inValue === undefined)
  111. return undefined;
  112. if (inValue.startsWith('#') || inValue.startsWith('rgb'))
  113. return inValue;
  114. return getColor(inValue, defaultValue);
  115. }
  116. function resolveThemeSizes<T extends Record<string, string|number>>(defaults: T) {
  117. const result : T = {} as T;
  118. for (let key in defaults)
  119. result[key] = resolveThemeSize(key, defaults[key]) as any;
  120. return result;
  121. }
  122. function resolveThemeColors<T extends Record<string, string>>(defaults: T) {
  123. const result : T = {} as T;
  124. for (let key in defaults)
  125. result[key] = resolveThemeColor(key, defaults[key]) as any;
  126. return result;
  127. }
  128. function isSpecialColor(key?: string) {
  129. return key === 'transparent' || key === 'currentColor';
  130. }
  131. function getColor(key: string, defaultValue?: string) {
  132. if (key === undefined)
  133. return defaultValue;
  134. let type = '';
  135. let keyResolve = getVar(key, key);
  136. if (typeof keyResolve === 'string')
  137. key = keyResolve;
  138. if (key.includes('.'))
  139. [type, key] = key.split('.');
  140. if (isSpecialColor(keyResolve))
  141. return keyResolve;
  142. let group = theme.value.colorConfigs[type || 'default'];
  143. if (!group)
  144. group = theme.value.colorConfigs['default'];
  145. return group?.[key] ?? defaultValue;
  146. }
  147. function getSize(key: string, defaultValue?: string|number) {
  148. if (key === undefined)
  149. return defaultValue;
  150. let type = '';
  151. if (key.includes('.'))
  152. [type, key] = key.split('.');
  153. let v: any = undefined;
  154. if (type) {
  155. const group = theme.value.varOverrides[type];
  156. v = group?.[key];
  157. } else
  158. v = theme.value.varOverrides[key];
  159. return resolveSize(v ?? defaultValue);
  160. }
  161. function getText(key: string, defaultValue?: Record<string, string>) {
  162. return theme.value.textConfigs[key]?? defaultValue;
  163. }
  164. function getVar<T>(key: string, defaultValue?: T) : T {
  165. let rs = undefined;
  166. let type = '';
  167. if (key.includes('.'))
  168. [type, key] = key.split('.');
  169. if (type) {
  170. const group = theme.value.varOverrides[type];
  171. rs = group?.[key];
  172. } else
  173. rs = theme.value.varOverrides[key];
  174. return rs ?? defaultValue;
  175. }
  176. function getVars<T extends Record<string, string|number|boolean>>(defaults: T) {
  177. const result : T = {} as T;
  178. for (let key in defaults) {
  179. result[key] = getVar(key, defaults[key]);
  180. }
  181. return result;
  182. }
  183. function getColors<T extends Record<string, string|undefined>>(defaults: T) {
  184. const result : T = {} as T;
  185. for (let key in defaults) {
  186. result[key] = getColor(key, defaults[key]) as any;
  187. }
  188. return result;
  189. }
  190. function useThemeStyle(style: Record<string, any>) {
  191. const mapDynamicVars = new Map<string, DynamicVarType>();
  192. for (const key in style) {
  193. const v = style[key];
  194. if (typeof v === 'object' && typeof v['type'] === 'string')
  195. mapDynamicVars.set(key, v)
  196. }
  197. return computed(() => {
  198. for (const [key, v] of mapDynamicVars) {
  199. switch (v.type) {
  200. case 'var': style[key] = getVar(v.name, v.defaultValue); break;
  201. case 'color': style[key] = resolveThemeColor(v.name, v.defaultValue); break;
  202. case 'size': style[key] = resolveThemeSize(v.name, v.defaultValue); break;
  203. case 'size2': style[key] = `${resolveThemeSize(v.name, v.defaultValue)} ${resolveThemeSize(v.name2, v.defaultValue2)}`; break;
  204. }
  205. }
  206. return style;
  207. })
  208. }
  209. function useThemeStyles<T extends Record<string, Record<string, any>>>
  210. (style: Record<string, Record<string, any>>) : {
  211. [key in keyof T]: ComputedRef<Record<string, any>>
  212. }
  213. {
  214. const result = {} as Record<string, Record<string, any>>;
  215. for (const key in style)
  216. result[key] = useThemeStyle(style[key]);
  217. return result as any;
  218. }
  219. return {
  220. theme,
  221. resolveThemeSizes,
  222. resolveThemeSize,
  223. resolveThemeColors,
  224. resolveThemeColor,
  225. resolveSize,
  226. getSize,
  227. getVar,
  228. getVars,
  229. getText,
  230. getColor,
  231. getColors,
  232. useThemeStyle,
  233. useThemeStyles,
  234. }
  235. }
  236. export type ThemeContext = ReturnType<typeof useTheme>;
  237. function isNumbrSize(inValue: ThemeSizeType|undefined) {
  238. if (inValue == undefined)
  239. return false;
  240. return typeof inValue === 'number' || !isNaN(Number(inValue));
  241. }
  242. function isRealSize(inValue: ThemeSizeType|undefined) {
  243. if (inValue == undefined)
  244. return false;
  245. return typeof inValue === 'number' ||
  246. (typeof inValue === 'string' && (
  247. inValue.startsWith('calc(') || inValue.endsWith('px') || inValue.endsWith('%') ||
  248. inValue.endsWith('em') || inValue.endsWith('vh') || inValue.endsWith('vw')
  249. ));
  250. }
  251. /**
  252. * 数字单位的处理
  253. * @param inValue 输入值
  254. * @param forceUnit 强制单位,默认为defaultSizeUnit即系统配置单位
  255. * @returns
  256. */
  257. export function resolveSize(inValue: ThemeSizeType|undefined, forceUnit = defaultSizeUnit) : string|undefined {
  258. if (inValue == undefined)
  259. return undefined;
  260. if (isNumbrSize(inValue))
  261. return `${inValue}${forceUnit}`;
  262. if (isRealSize(inValue as string))
  263. return inValue as string;
  264. if (inValue == 'fill')
  265. return '100%';
  266. if (inValue == 'half' || inValue == '1/2')
  267. return '50%';
  268. if (inValue == '1/3')
  269. return '33%';
  270. if (inValue == '1/4')
  271. return '25%';
  272. return undefined;
  273. }
  274. export interface ThemePaddingMargin {
  275. l?: number|string,
  276. r?: number|string,
  277. t?: number|string,
  278. b?: number|string,
  279. }
  280. /**
  281. * 配置主题变量,建议在App.vue中调用
  282. * @param cb 回调函数,参数为默认主题配置,返回值为新的主题配置
  283. * @returns 主题配置对象,返回currentTheme为当前主题,可以修改为其他主题对象,但请注意主题对象为shallowRef,
  284. * 直接修改主题对象的属性不会触发主题更新,需要调用currentTheme.value = newTheme新的对象来更新主题。
  285. */
  286. export function configTheme(
  287. /**
  288. * 是否自动匹配系统深色主题
  289. * @default true
  290. */
  291. autoMatchSystemDark?: boolean,
  292. /**
  293. * 回调函数,用于配置修改主题,参数为默认主题配置,返回值为新的主题配置
  294. * @default undefined
  295. * @returns 可以返回新的主题对象或者是传入的对象,第一个为亮色主题,第二个为深色主题
  296. */
  297. cb?: (defaultTheme: ThemeConfig, defaultDarkTheme: ThemeConfig) => [ThemeConfig,ThemeConfig]
  298. ) {
  299. let defaultTheme = ObjectUtils.clone(DefaultTheme);
  300. let defaultDarkTheme = ObjectUtils.clone(DefaultDarkTheme);
  301. const [theme, darkTheme] = cb?.(defaultTheme, defaultDarkTheme) ?? [defaultTheme, defaultDarkTheme];
  302. const currentSystemDark = ref(autoMatchSystemDark !== false && uni.getAppBaseInfo().theme === 'dark');
  303. const currentTheme = shallowRef(currentSystemDark.value ? darkTheme : theme);
  304. provide(ThemeKey, currentTheme);
  305. if (autoMatchSystemDark !== false) {
  306. // 监听系统主题变化
  307. uni.onThemeChange((res) => {
  308. currentSystemDark.value = res.theme === 'dark';
  309. currentTheme.value = currentSystemDark.value ? darkTheme : theme;
  310. });
  311. }
  312. return {
  313. currentTheme,
  314. }
  315. }
  316. /**
  317. * 克隆默认主题,用于自定义主题
  318. * @param cb 回调函数,参数为默认主题配置,返回值为新的主题配置
  319. * @param dark 是否为深色主题,默认false为亮色主题
  320. * @returns 新的主题配置对象
  321. */
  322. export function cloneTheme(dark?: boolean, cb?: (defaultTheme: ThemeConfig) => ThemeConfig) {
  323. return cb?.(ObjectUtils.clone(dark ? DefaultDarkTheme : DefaultTheme)) ?? ObjectUtils.clone(dark ? DefaultDarkTheme : DefaultTheme);
  324. }