@thewhileloop/whileui 4.2.1 → 4.2.3

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.
@@ -79,7 +79,7 @@ export function Sheet({ open, onOpenChange, children, maxHeight = 'full', maxWid
79
79
  widthStyle,
80
80
  frostedSurface.surfaceStyle,
81
81
  ];
82
- return (_jsx(OverlayHost, { open: mounted, animationType: "none", onRequestClose: onClose, children: _jsxs(View, { className: "flex-1 justify-end", children: [_jsx(Animated.View, { style: [
82
+ return (_jsx(OverlayHost, { open: mounted, animationType: "none", onRequestClose: open ? onClose : undefined, children: _jsxs(View, { className: "flex-1 justify-end", children: [_jsx(Animated.View, { style: [
83
83
  StyleSheet.absoluteFill,
84
84
  { backgroundColor: frosted ? 'transparent' : colors.overlay },
85
85
  backdropStyle,
@@ -125,5 +125,5 @@ export function DrawerMenu({ visible, onClose, sections, activeKey, onSelect, he
125
125
  frostedSurface.surfaceStyle,
126
126
  style,
127
127
  ], className: drawerPanelClassName(frosted, className), children: [frostedSurface.overlay, _jsxs(View, { className: "flex-1 pb-8", style: { paddingTop: contentTopPadding }, children: [header && _jsx(View, { className: "px-5 pb-4 mb-2 border-b border-border", children: header }), _jsx(DrawerSectionList, { sections: sections, activeKey: activeKey, onSelect: handleItemPress, interaction: interaction, visual: visual }), footer && _jsx(View, { className: "px-5 py-4 mt-auto border-t border-border", children: footer })] })] })] }));
128
- return (_jsx(OverlayHost, { open: mounted, animationType: "none", onRequestClose: onClose, children: drawerTree }));
128
+ return (_jsx(OverlayHost, { open: mounted, animationType: "none", onRequestClose: visible ? onClose : undefined, children: drawerTree }));
129
129
  }
@@ -1,8 +1,7 @@
1
1
  import { type ThemeColors } from './theme-contract';
2
2
  export type { ThemeColors } from './theme-contract';
3
3
  export declare function useThemeColors(): ThemeColors;
4
- /** Icon colors derived from theme. For @expo/vector-icons which need hex values. */
5
- export declare function useIconColors(): {
4
+ export interface IconColors {
6
5
  foreground: string;
7
6
  muted: string;
8
7
  primary: string;
@@ -12,6 +11,8 @@ export declare function useIconColors(): {
12
11
  success: string;
13
12
  info: string;
14
13
  destructive: string;
15
- };
14
+ }
15
+ /** Icon colors derived from theme. For @expo/vector-icons which need hex values. */
16
+ export declare function useIconColors(): IconColors;
16
17
  /** Alias for useThemeColors. Returns RN-safe color strings (hex/rgb/rgba) for RefreshControl, LinearGradient, charts. */
17
18
  export declare const useThemeTokens: typeof useThemeColors;
@@ -41,10 +41,29 @@ function resolveThemeToken(cssValue, appValue) {
41
41
  }
42
42
  return parseThemeColor(cssValue) ?? parseThemeColor(appValue);
43
43
  }
44
+ // Every themed component calls this hook on every render, and parsing 104 colour
45
+ // strings plus deriving the palette cost about 0.16 ms per call in Node, several times
46
+ // that under Hermes. The inputs only change with the theme, so the result is derived
47
+ // once per distinct snapshot and shared: the same object comes back until the CSS
48
+ // variables change, which also keeps it stable for downstream memoisation.
49
+ const resolvedThemeColors = new Map();
50
+ const RESOLVED_THEME_LIMIT = 8;
44
51
  export function useThemeColors() {
45
52
  const { theme } = useUniwind();
46
53
  const values = useCSSVariable(THEME_TOKEN_VARIABLES);
47
54
  const scheme = theme === 'dark' ? 'dark' : 'light';
55
+ const key = `${scheme}\u0001${values.join('\u0001')}`;
56
+ const cached = resolvedThemeColors.get(key);
57
+ if (cached)
58
+ return cached;
59
+ const resolved = resolveThemeColors(values, scheme);
60
+ if (resolvedThemeColors.size >= RESOLVED_THEME_LIMIT) {
61
+ resolvedThemeColors.delete(resolvedThemeColors.keys().next().value);
62
+ }
63
+ resolvedThemeColors.set(key, resolved);
64
+ return resolved;
65
+ }
66
+ function resolveThemeColors(values, scheme) {
48
67
  const fallbackPalette = scheme === 'dark' ? WHILEUI_DARK_THEME_COLORS : WHILEUI_LIGHT_THEME_COLORS;
49
68
  const defined = {};
50
69
  let index = 0;
@@ -61,10 +80,14 @@ export function useThemeColors() {
61
80
  const derived = deriveThemeColors(base, scheme);
62
81
  return { ...derived, ...defined, placeholder: derived.placeholder };
63
82
  }
83
+ const iconColorsByTheme = new WeakMap();
64
84
  /** Icon colors derived from theme. For @expo/vector-icons which need hex values. */
65
85
  export function useIconColors() {
66
86
  const c = useThemeColors();
67
- return {
87
+ const cached = iconColorsByTheme.get(c);
88
+ if (cached)
89
+ return cached;
90
+ const icons = {
68
91
  foreground: c.foreground,
69
92
  muted: c.mutedForeground,
70
93
  primary: c.primary,
@@ -75,6 +98,8 @@ export function useIconColors() {
75
98
  info: c.info,
76
99
  destructive: c.destructive,
77
100
  };
101
+ iconColorsByTheme.set(c, icons);
102
+ return icons;
78
103
  }
79
104
  /** Alias for useThemeColors. Returns RN-safe color strings (hex/rgb/rgba) for RefreshControl, LinearGradient, charts. */
80
105
  export const useThemeTokens = useThemeColors;
@@ -28,11 +28,24 @@ function resolveVisualToken(spec, value) {
28
28
  }
29
29
  return parseRatio(value, spec.fallback, spec.min ?? 0, spec.max ?? 1);
30
30
  }
31
+ // Resolved once per distinct CSS snapshot and shared, for the same reason as
32
+ // `useThemeColors`: 83 parses per component per render add up on a list screen, and a
33
+ // stable object lets callers memoise on it.
34
+ const resolvedVisualTokens = new Map();
35
+ const RESOLVED_VISUAL_LIMIT = 8;
31
36
  export function useVisualTokens() {
32
37
  const values = useCSSVariable(VISUAL_TOKEN_VARIABLES);
38
+ const key = values.join('\u0001');
39
+ const cached = resolvedVisualTokens.get(key);
40
+ if (cached)
41
+ return cached;
33
42
  const resolved = {};
34
43
  VISUAL_TOKEN_SPECS.forEach((spec, index) => {
35
44
  resolved[spec.key] = resolveVisualToken(spec, values[index]);
36
45
  });
46
+ if (resolvedVisualTokens.size >= RESOLVED_VISUAL_LIMIT) {
47
+ resolvedVisualTokens.delete(resolvedVisualTokens.keys().next().value);
48
+ }
49
+ resolvedVisualTokens.set(key, resolved);
37
50
  return resolved;
38
51
  }
package/dist/metro.cjs CHANGED
@@ -12,6 +12,11 @@ const { withUniwindConfig } = require('uniwind/metro');
12
12
  const NEEDS_REAL_REACT_NATIVE = ['expo-asset', 'expo-font', '@expo/vector-icons'];
13
13
 
14
14
  function withWhileUIMetro(config, options) {
15
+ if (!options || typeof options.cssEntryFile !== 'string') {
16
+ throw new Error(
17
+ "withWhileUIMetro(config, options): pass Uniwind's options, at least { cssEntryFile: './global.css' }"
18
+ );
19
+ }
15
20
  const { exemptFromUniwindRewrite = [], ...uniwindOptions } = options;
16
21
  const wrapped = withUniwindConfig(config, uniwindOptions);
17
22
  const exempt = [...NEEDS_REAL_REACT_NATIVE, ...exemptFromUniwindRewrite];
@@ -19,7 +24,8 @@ function withWhileUIMetro(config, options) {
19
24
 
20
25
  wrapped.resolver.resolveRequest = (context, moduleName, platform) => {
21
26
  if (moduleName === 'react-native') {
22
- const origin = context.originModulePath ?? '';
27
+ // Metro reports Windows paths with backslashes; match either separator.
28
+ const origin = (context.originModulePath ?? '').replace(/\\/g, '/');
23
29
  if (exempt.some((pkg) => origin.includes(`node_modules/${pkg}/`))) {
24
30
  return context.resolveRequest(context, moduleName, platform);
25
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thewhileloop/whileui",
3
- "version": "4.2.1",
3
+ "version": "4.2.3",
4
4
  "description": "WhileUI Native — Copy-paste components for React Native. You own the code.",
5
5
  "license": "MIT",
6
6
  "repository": {