@primer/react 38.19.0-rc.c09ee891a → 38.19.0-rc.f3b0d9cc2

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/CHANGELOG.md CHANGED
@@ -8,6 +8,15 @@
8
8
 
9
9
  - [#7677](https://github.com/primer/react/pull/7677) [`c1a81b1`](https://github.com/primer/react/commit/c1a81b178742ba547b85a3df3ed3c27bcff6b7c5) Thanks [@TylerJDev](https://github.com/TylerJDev)! - AnchoredOverlay: Add Popover API to AnchoredOverlay (behind `primer_react_css_anchor_positioning` feature flag)
10
10
 
11
+ ### Patch Changes
12
+
13
+ - [#7695](https://github.com/primer/react/pull/7695) [`780fc3d`](https://github.com/primer/react/commit/780fc3d7b52fd0f9b63f313af6355398180a0118) Thanks [@mattcosta7](https://github.com/mattcosta7)! - perf(ThemeProvider): Reduce unnecessary renders and effect cascades
14
+
15
+ - Replace `useState` + `useEffect` SSR hydration handoff with `useSyncExternalStore` — eliminates post-hydration re-render
16
+ - Replace `useState` + `useEffect` in `useSystemColorMode` with `useSyncExternalStore` — eliminates effect gap and stale-then-update flicker
17
+ - Cache `getServerHandoff` DOM read + JSON.parse per ID (runs once, not on every call)
18
+ - Memoize context value object to prevent unnecessary re-renders of all consumers
19
+
11
20
  ## 38.18.0
12
21
 
13
22
  ### Minor Changes
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../src/ThemeProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAOzB,eAAO,MAAM,gBAAgB,QAAQ,CAAA;AAKrC,MAAM,MAAM,KAAK,GAAG;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAC,CAAA;AACxC,KAAK,SAAS,GAAG,KAAK,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAA;AACnD,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,MAAM,CAAA;AAElD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,CAAC,EAAE,iBAAiB,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B,CAAA;AA8BD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAsF/E,CAAA;AAED,wBAAgB,QAAQ;YAnHd,KAAK;kBACC,MAAM;gBACR,iBAAiB;wBACT,SAAS;0BACP,MAAM;gBAChB,MAAM;kBACJ,MAAM;kBACN,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC;kBACvD,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;oBAC1C,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;EA4G7D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,UAG1F;AAqGD,eAAe,aAAa,CAAA"}
1
+ {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../src/ThemeProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,eAAO,MAAM,gBAAgB,QAAQ,CAAA;AAKrC,MAAM,MAAM,KAAK,GAAG;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAC,CAAA;AACxC,KAAK,SAAS,GAAG,KAAK,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAA;AACnD,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,MAAM,CAAA;AAElD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,CAAC,EAAE,iBAAiB,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B,CAAA;AA8CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CA6E/E,CAAA;AAED,wBAAgB,QAAQ;YA1Hd,KAAK;kBACC,MAAM;gBACR,iBAAiB;wBACT,SAAS;0BACP,MAAM;gBAChB,MAAM;kBACJ,MAAM;kBACN,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC;kBACvD,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;oBAC1C,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;EAmH7D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,UAG1F;AAqED,eAAe,aAAa,CAAA"}
@@ -1,6 +1,5 @@
1
1
  import { c } from 'react-compiler-runtime';
2
2
  import React from 'react';
3
- import ReactDOM from 'react-dom';
4
3
  import theme from './theme.js';
5
4
  import deepmerge from 'deepmerge';
6
5
  import { useSyncedState } from './hooks/useSyncedState.js';
@@ -20,71 +19,115 @@ const ThemeContext = /*#__PURE__*/React.createContext({
20
19
  });
21
20
 
22
21
  // inspired from __NEXT_DATA__, we use application/json to avoid CSRF policy with inline scripts
22
+ const serverHandoffCache = new Map();
23
+ const emptyHandoff = {};
23
24
  const getServerHandoff = id => {
25
+ if (typeof document === 'undefined') return emptyHandoff;
26
+ const cached = serverHandoffCache.get(id);
27
+ if (cached !== undefined) return cached;
24
28
  try {
25
29
  var _document$getElementB;
26
30
  const serverData = (_document$getElementB = document.getElementById(`__PRIMER_DATA_${id}__`)) === null || _document$getElementB === void 0 ? void 0 : _document$getElementB.textContent;
27
- if (serverData) return JSON.parse(serverData);
31
+ if (serverData) {
32
+ const parsed = JSON.parse(serverData);
33
+ serverHandoffCache.set(id, parsed);
34
+ return parsed;
35
+ }
28
36
  } catch (_error) {
29
37
  // if document/element does not exist or JSON is invalid, suppress error
30
38
  }
31
- return {};
39
+ const empty = {};
40
+ serverHandoffCache.set(id, empty);
41
+ return empty;
32
42
  };
33
- const ThemeProvider = ({
34
- children,
35
- ...props
36
- }) => {
43
+ const emptySubscribe = () => () => {};
44
+ const ThemeProvider = t0 => {
37
45
  var _ref, _props$colorMode, _ref2, _props$dayScheme, _ref3, _props$nightScheme;
38
- // Get fallback values from parent ThemeProvider (if exists)
46
+ const $ = c(42);
47
+ let children;
48
+ let props;
49
+ if ($[0] !== t0) {
50
+ ({
51
+ children,
52
+ ...props
53
+ } = t0);
54
+ $[0] = t0;
55
+ $[1] = children;
56
+ $[2] = props;
57
+ } else {
58
+ children = $[1];
59
+ props = $[2];
60
+ }
39
61
  const {
40
62
  theme: fallbackTheme,
41
63
  colorMode: fallbackColorMode,
42
64
  dayScheme: fallbackDayScheme,
43
65
  nightScheme: fallbackNightScheme
44
66
  } = useTheme();
45
-
46
- // Initialize state
47
67
  const theme$1 = fallbackTheme !== null && fallbackTheme !== void 0 ? fallbackTheme : theme;
48
68
  const uniqueDataId = useId();
49
- const {
50
- resolvedServerColorMode
51
- } = getServerHandoff(uniqueDataId);
52
- const resolvedColorModePassthrough = React.useRef(resolvedServerColorMode);
53
69
  const [colorMode, setColorMode] = useSyncedState((_ref = (_props$colorMode = props.colorMode) !== null && _props$colorMode !== void 0 ? _props$colorMode : fallbackColorMode) !== null && _ref !== void 0 ? _ref : defaultColorMode);
54
70
  const [dayScheme, setDayScheme] = useSyncedState((_ref2 = (_props$dayScheme = props.dayScheme) !== null && _props$dayScheme !== void 0 ? _props$dayScheme : fallbackDayScheme) !== null && _ref2 !== void 0 ? _ref2 : defaultDayScheme);
55
71
  const [nightScheme, setNightScheme] = useSyncedState((_ref3 = (_props$nightScheme = props.nightScheme) !== null && _props$nightScheme !== void 0 ? _props$nightScheme : fallbackNightScheme) !== null && _ref3 !== void 0 ? _ref3 : defaultNightScheme);
56
72
  const systemColorMode = useSystemColorMode();
57
- // eslint-disable-next-line react-hooks/refs
58
- const resolvedColorMode = resolvedColorModePassthrough.current || resolveColorMode(colorMode, systemColorMode);
59
- const colorScheme = chooseColorScheme(resolvedColorMode, dayScheme, nightScheme);
73
+ let t1;
74
+ if ($[3] !== colorMode || $[4] !== systemColorMode) {
75
+ t1 = resolveColorMode(colorMode, systemColorMode);
76
+ $[3] = colorMode;
77
+ $[4] = systemColorMode;
78
+ $[5] = t1;
79
+ } else {
80
+ t1 = $[5];
81
+ }
82
+ const clientColorMode = t1;
83
+ let t2;
84
+ if ($[6] !== clientColorMode) {
85
+ t2 = () => clientColorMode;
86
+ $[6] = clientColorMode;
87
+ $[7] = t2;
88
+ } else {
89
+ t2 = $[7];
90
+ }
91
+ let t3;
92
+ if ($[8] !== clientColorMode || $[9] !== uniqueDataId) {
93
+ t3 = () => {
94
+ var _getServerHandoff$res;
95
+ return (_getServerHandoff$res = getServerHandoff(uniqueDataId).resolvedServerColorMode) !== null && _getServerHandoff$res !== void 0 ? _getServerHandoff$res : clientColorMode;
96
+ };
97
+ $[8] = clientColorMode;
98
+ $[9] = uniqueDataId;
99
+ $[10] = t3;
100
+ } else {
101
+ t3 = $[10];
102
+ }
103
+ const resolvedColorMode = React.useSyncExternalStore(emptySubscribe, t2, t3);
104
+ let t4;
105
+ if ($[11] !== dayScheme || $[12] !== nightScheme || $[13] !== resolvedColorMode) {
106
+ t4 = chooseColorScheme(resolvedColorMode, dayScheme, nightScheme);
107
+ $[11] = dayScheme;
108
+ $[12] = nightScheme;
109
+ $[13] = resolvedColorMode;
110
+ $[14] = t4;
111
+ } else {
112
+ t4 = $[14];
113
+ }
114
+ const colorScheme = t4;
115
+ let t5;
116
+ if ($[15] !== colorScheme || $[16] !== theme$1) {
117
+ t5 = applyColorScheme(theme$1, colorScheme);
118
+ $[15] = colorScheme;
119
+ $[16] = theme$1;
120
+ $[17] = t5;
121
+ } else {
122
+ t5 = $[17];
123
+ }
60
124
  const {
61
125
  resolvedTheme,
62
126
  resolvedColorScheme
63
- } = React.useMemo(() => applyColorScheme(theme$1, colorScheme), [theme$1, colorScheme]);
64
-
65
- // this effect will only run on client
66
- React.useEffect(function updateColorModeAfterServerPassthrough() {
67
- const resolvedColorModeOnClient = resolveColorMode(colorMode, systemColorMode);
68
- if (resolvedColorModePassthrough.current) {
69
- // if the resolved color mode passed on from the server is not the resolved color mode on client, change it!
70
- if (resolvedColorModePassthrough.current !== resolvedColorModeOnClient) {
71
- window.setTimeout(() => {
72
- // use ReactDOM.flushSync to prevent automatic batching of state updates since React 18
73
- // ref: https://github.com/reactwg/react-18/discussions/21
74
- ReactDOM.flushSync(() => {
75
- // override colorMode to whatever is resolved on the client to get a re-render
76
- setColorMode(resolvedColorModeOnClient);
77
- });
78
-
79
- // immediately after that, set the colorMode to what the user passed to respond to system color mode changes
80
- setColorMode(colorMode);
81
- });
82
- }
83
- resolvedColorModePassthrough.current = null;
84
- }
85
- }, [colorMode, systemColorMode, setColorMode]);
86
- return /*#__PURE__*/jsx(ThemeContext.Provider, {
87
- value: {
127
+ } = t5;
128
+ let t6;
129
+ if ($[18] !== colorMode || $[19] !== colorScheme || $[20] !== dayScheme || $[21] !== nightScheme || $[22] !== resolvedColorMode || $[23] !== resolvedColorScheme || $[24] !== resolvedTheme || $[25] !== setColorMode || $[26] !== setDayScheme || $[27] !== setNightScheme) {
130
+ t6 = {
88
131
  theme: resolvedTheme,
89
132
  colorScheme,
90
133
  colorMode,
@@ -95,24 +138,72 @@ const ThemeProvider = ({
95
138
  setColorMode,
96
139
  setDayScheme,
97
140
  setNightScheme
98
- },
99
- children: /*#__PURE__*/jsxs("div", {
100
- "data-color-mode": colorMode === 'auto' ? 'auto' : colorScheme.includes('dark') ? 'dark' : 'light',
141
+ };
142
+ $[18] = colorMode;
143
+ $[19] = colorScheme;
144
+ $[20] = dayScheme;
145
+ $[21] = nightScheme;
146
+ $[22] = resolvedColorMode;
147
+ $[23] = resolvedColorScheme;
148
+ $[24] = resolvedTheme;
149
+ $[25] = setColorMode;
150
+ $[26] = setDayScheme;
151
+ $[27] = setNightScheme;
152
+ $[28] = t6;
153
+ } else {
154
+ t6 = $[28];
155
+ }
156
+ const contextValue = t6;
157
+ const t7 = colorMode === "auto" ? "auto" : colorScheme.includes("dark") ? "dark" : "light";
158
+ let t8;
159
+ if ($[29] !== props.preventSSRMismatch || $[30] !== resolvedColorMode || $[31] !== uniqueDataId) {
160
+ t8 = props.preventSSRMismatch ? /*#__PURE__*/jsx("script", {
161
+ type: "application/json",
162
+ id: `__PRIMER_DATA_${uniqueDataId}__`,
163
+ dangerouslySetInnerHTML: {
164
+ __html: JSON.stringify({
165
+ resolvedServerColorMode: resolvedColorMode
166
+ })
167
+ }
168
+ }) : null;
169
+ $[29] = props.preventSSRMismatch;
170
+ $[30] = resolvedColorMode;
171
+ $[31] = uniqueDataId;
172
+ $[32] = t8;
173
+ } else {
174
+ t8 = $[32];
175
+ }
176
+ let t9;
177
+ if ($[33] !== children || $[34] !== dayScheme || $[35] !== nightScheme || $[36] !== t7 || $[37] !== t8) {
178
+ t9 = /*#__PURE__*/jsxs("div", {
179
+ "data-color-mode": t7,
101
180
  "data-light-theme": dayScheme,
102
181
  "data-dark-theme": nightScheme,
103
- children: [children, props.preventSSRMismatch ? /*#__PURE__*/jsx("script", {
104
- type: "application/json",
105
- id: `__PRIMER_DATA_${uniqueDataId}__`,
106
- dangerouslySetInnerHTML: {
107
- __html: JSON.stringify({
108
- resolvedServerColorMode: resolvedColorMode
109
- })
110
- }
111
- }) : null]
112
- })
113
- });
182
+ children: [children, t8]
183
+ });
184
+ $[33] = children;
185
+ $[34] = dayScheme;
186
+ $[35] = nightScheme;
187
+ $[36] = t7;
188
+ $[37] = t8;
189
+ $[38] = t9;
190
+ } else {
191
+ t9 = $[38];
192
+ }
193
+ let t10;
194
+ if ($[39] !== contextValue || $[40] !== t9) {
195
+ t10 = /*#__PURE__*/jsx(ThemeContext.Provider, {
196
+ value: contextValue,
197
+ children: t9
198
+ });
199
+ $[39] = contextValue;
200
+ $[40] = t9;
201
+ $[41] = t10;
202
+ } else {
203
+ t10 = $[41];
204
+ }
205
+ return t10;
114
206
  };
115
- ThemeProvider.displayName = "ThemeProvider";
116
207
  function useTheme() {
117
208
  return React.useContext(ThemeContext);
118
209
  }
@@ -124,57 +215,25 @@ function useColorSchemeVar(values, fallback) {
124
215
  const colorScheme = t0 === undefined ? "" : t0;
125
216
  return (_values$colorScheme = values[colorScheme]) !== null && _values$colorScheme !== void 0 ? _values$colorScheme : fallback;
126
217
  }
218
+ function subscribeToSystemColorMode(callback) {
219
+ var _window, _window$matchMedia;
220
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
221
+ const media = (_window = window) === null || _window === void 0 ? void 0 : (_window$matchMedia = _window.matchMedia) === null || _window$matchMedia === void 0 ? void 0 : _window$matchMedia.call(_window, '(prefers-color-scheme: dark)');
222
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
223
+ media === null || media === void 0 ? void 0 : media.addEventListener('change', callback);
224
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
225
+ return () => media === null || media === void 0 ? void 0 : media.removeEventListener('change', callback);
226
+ }
127
227
  function useSystemColorMode() {
128
- const $ = c(2);
129
- const [systemColorMode, setSystemColorMode] = React.useState(getSystemColorMode);
130
- let t0;
131
- let t1;
132
- if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
133
- t0 = () => {
134
- var _window, _window$matchMedia;
135
- const media = (_window = window) === null || _window === void 0 ? void 0 : (_window$matchMedia = _window.matchMedia) === null || _window$matchMedia === void 0 ? void 0 : _window$matchMedia.call(_window, "(prefers-color-scheme: dark)");
136
- const matchesMediaToColorMode = function matchesMediaToColorMode(matches) {
137
- return matches ? "night" : "day";
138
- };
139
- const handleChange = function handleChange(event) {
140
- const isNight = event.matches;
141
- setSystemColorMode(matchesMediaToColorMode(isNight));
142
- };
143
- if (media) {
144
- const isNight_0 = media.matches;
145
- setSystemColorMode(matchesMediaToColorMode(isNight_0));
146
- if (media.addEventListener !== undefined) {
147
- media.addEventListener("change", handleChange);
148
- return function cleanup() {
149
- media.removeEventListener("change", handleChange);
150
- };
151
- } else {
152
- if (media.addListener !== undefined) {
153
- media.addListener(handleChange);
154
- return function cleanup() {
155
- media.removeListener(handleChange);
156
- };
157
- }
158
- }
159
- }
160
- };
161
- t1 = [];
162
- $[0] = t0;
163
- $[1] = t1;
164
- } else {
165
- t0 = $[0];
166
- t1 = $[1];
167
- }
168
- React.useEffect(t0, t1);
169
- return systemColorMode;
228
+ return React.useSyncExternalStore(subscribeToSystemColorMode, getSystemColorMode, _temp);
229
+ }
230
+ function _temp() {
231
+ return "day";
170
232
  }
171
233
  function getSystemColorMode() {
172
- var _window$matchMedia2, _window2, _window$matchMedia2$c;
234
+ var _window2, _window2$matchMedia, _window2$matchMedia$c;
173
235
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
174
- if (typeof window !== 'undefined' && (_window$matchMedia2 = (_window2 = window).matchMedia) !== null && _window$matchMedia2 !== void 0 && (_window$matchMedia2$c = _window$matchMedia2.call(_window2, '(prefers-color-scheme: dark)')) !== null && _window$matchMedia2$c !== void 0 && _window$matchMedia2$c.matches) {
175
- return 'night';
176
- }
177
- return 'day';
236
+ return (_window2 = window) !== null && _window2 !== void 0 && (_window2$matchMedia = _window2.matchMedia) !== null && _window2$matchMedia !== void 0 && (_window2$matchMedia$c = _window2$matchMedia.call(_window2, '(prefers-color-scheme: dark)')) !== null && _window2$matchMedia$c !== void 0 && _window2$matchMedia$c.matches ? 'night' : 'day';
178
237
  }
179
238
  function resolveColorMode(colorMode, systemColorMode) {
180
239
  switch (colorMode) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@primer/react",
3
3
  "type": "module",
4
- "version": "38.19.0-rc.c09ee891a",
4
+ "version": "38.19.0-rc.f3b0d9cc2",
5
5
  "description": "An implementation of GitHub's Primer Design System using React",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",