@primer/react 38.36.0-rc.62d935135 → 38.36.0-rc.a1228eda5

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
@@ -9,6 +9,8 @@
9
9
 
10
10
  ### Patch Changes
11
11
 
12
+ - [#8276](https://github.com/primer/react/pull/8276) [`5069f8a`](https://github.com/primer/react/commit/5069f8a7cb95544ce14122e97d67c8a1279f8a08) Thanks [@copilot-swe-agent](https://github.com/apps/copilot-swe-agent)! - Portal: Render nothing while server rendering and during hydration instead of accessing the DOM during render, fixing server rendering support. Client-only renders are unchanged and still mount portaled content in the same commit.
13
+
12
14
  - [#8260](https://github.com/primer/react/pull/8260) [`24d0831`](https://github.com/primer/react/commit/24d0831d34b13e3b4bac8afcfa2ccd5795cd9a9d) Thanks [@liuliu-dev](https://github.com/liuliu-dev)! - UnderlineNav: Keep item icons visible by default at the medium breakpoint and above
13
15
 
14
16
  ## 38.35.1
@@ -1,43 +1,105 @@
1
1
  import useIsomorphicLayoutEffect from "../utils/useIsomorphicLayoutEffect.js";
2
2
  import { PortalContext } from "./PortalContext.js";
3
3
  import { DEFAULT_PORTAL_CONTAINER_NAME, ensureDefaultPortal, getPortalRoot } from "./portalRoot.js";
4
- import React, { useContext } from "react";
4
+ import { c } from "react-compiler-runtime";
5
+ import React, { useContext, useSyncExternalStore } from "react";
5
6
  import { createPortal } from "react-dom";
6
7
  //#region src/Portal/Portal.tsx
8
+ const subscribe = () => () => {};
9
+ const getSnapshot = () => true;
10
+ const getServerSnapshot = () => false;
11
+ /**
12
+ * Returns `true` only when React is rendering on the client, and not as part of
13
+ * hydrating server-rendered markup.
14
+ *
15
+ * `Portal` needs this because React's server renderer throws when it encounters a
16
+ * portal, and portaled content is never part of the server-rendered markup.
17
+ *
18
+ * `useSyncExternalStore` is used rather than a `useEffect`/`useState` "is mounted"
19
+ * flag because it only defers the portal for the renders that need it: it returns
20
+ * `false` while server rendering and for the hydration pass (keeping hydration
21
+ * consistent with the server markup), but `true` from the very first render of a
22
+ * client-only render. That way client-rendered portals — the overwhelmingly common
23
+ * case for overlays, dialogs and menus — still mount their children in the same
24
+ * commit as the `Portal` itself, so refs into portaled content are populated by the
25
+ * time the parent's layout effects run.
26
+ */
27
+ function useIsClientRender() {
28
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
29
+ }
7
30
  /**
8
31
  * Creates a React Portal, placing all children in a separate physical DOM root node.
9
32
  * @see https://reactjs.org/docs/portals.html
10
33
  */
11
- const Portal = ({ children, onMount, containerName: _containerName }) => {
34
+ const Portal = (t0) => {
35
+ const $ = c(12);
36
+ const { children, onMount, containerName: _containerName } = t0;
12
37
  const { portalContainerName } = useContext(PortalContext);
13
- const elementRef = React.useRef(null);
14
- if (!elementRef.current) {
15
- const div = document.createElement("div");
16
- div.setAttribute("data-component", "Portal");
17
- div.style.position = "relative";
18
- div.style.zIndex = "1";
19
- elementRef.current = div;
20
- }
21
- const element = elementRef.current;
22
- useIsomorphicLayoutEffect(() => {
23
- let containerName = _containerName !== null && _containerName !== void 0 ? _containerName : portalContainerName;
24
- if (containerName === void 0) {
25
- containerName = DEFAULT_PORTAL_CONTAINER_NAME;
26
- ensureDefaultPortal();
27
- }
28
- const parentElement = getPortalRoot(containerName);
29
- if (!parentElement) throw new Error(`Portal container '${containerName}' is not yet registered. Container must be registered with registerPortalRoot before use.`);
30
- parentElement.appendChild(element);
31
- onMount === null || onMount === void 0 || onMount();
32
- return () => {
33
- parentElement.removeChild(element);
38
+ const isClientRender = useIsClientRender();
39
+ const onMountRef = React.useRef(onMount);
40
+ let t1;
41
+ if ($[0] !== onMount) {
42
+ t1 = () => {
43
+ onMountRef.current = onMount;
34
44
  };
35
- }, [
36
- element,
37
- _containerName,
38
- portalContainerName
39
- ]);
40
- return /*#__PURE__*/ createPortal(children, element);
45
+ $[0] = onMount;
46
+ $[1] = t1;
47
+ } else t1 = $[1];
48
+ useIsomorphicLayoutEffect(t1);
49
+ const [element] = React.useState(_temp);
50
+ let t2;
51
+ let t3;
52
+ if ($[2] !== _containerName || $[3] !== element || $[4] !== isClientRender || $[5] !== portalContainerName) {
53
+ t2 = () => {
54
+ var _onMountRef$current;
55
+ if (!element || !isClientRender) return;
56
+ let containerName = _containerName !== null && _containerName !== void 0 ? _containerName : portalContainerName;
57
+ if (containerName === void 0) {
58
+ containerName = DEFAULT_PORTAL_CONTAINER_NAME;
59
+ ensureDefaultPortal();
60
+ }
61
+ const parentElement = getPortalRoot(containerName);
62
+ if (!parentElement) throw new Error(`Portal container '${containerName}' is not yet registered. Container must be registered with registerPortalRoot before use.`);
63
+ parentElement.appendChild(element);
64
+ (_onMountRef$current = onMountRef.current) === null || _onMountRef$current === void 0 || _onMountRef$current.call(onMountRef);
65
+ return () => {
66
+ parentElement.removeChild(element);
67
+ };
68
+ };
69
+ t3 = [
70
+ element,
71
+ isClientRender,
72
+ _containerName,
73
+ portalContainerName
74
+ ];
75
+ $[2] = _containerName;
76
+ $[3] = element;
77
+ $[4] = isClientRender;
78
+ $[5] = portalContainerName;
79
+ $[6] = t2;
80
+ $[7] = t3;
81
+ } else {
82
+ t2 = $[6];
83
+ t3 = $[7];
84
+ }
85
+ useIsomorphicLayoutEffect(t2, t3);
86
+ let t4;
87
+ if ($[8] !== children || $[9] !== element || $[10] !== isClientRender) {
88
+ t4 = element && isClientRender ? /*#__PURE__*/ createPortal(children, element) : null;
89
+ $[8] = children;
90
+ $[9] = element;
91
+ $[10] = isClientRender;
92
+ $[11] = t4;
93
+ } else t4 = $[11];
94
+ return t4;
41
95
  };
96
+ function _temp() {
97
+ if (typeof document === "undefined") return null;
98
+ const div = document.createElement("div");
99
+ div.setAttribute("data-component", "Portal");
100
+ div.style.position = "relative";
101
+ div.style.zIndex = "1";
102
+ return div;
103
+ }
42
104
  //#endregion
43
105
  export { Portal };
@@ -1,68 +1,83 @@
1
+ import { c } from "react-compiler-runtime";
1
2
  import React from "react";
2
3
  import { iterateFocusableElements } from "@primer/behaviors/utils";
3
4
  //#region src/hooks/useMenuInitialFocus.ts
4
5
  const useMenuInitialFocus = (open, containerRef, anchorRef) => {
5
- /**
6
- * We need to pick the first element to focus based on how the menu was opened,
7
- * however, we need to wait for the menu to be open to set focus.
8
- * This is why we use set openingKey in state and have 2 effects
9
- */
6
+ const $ = c(9);
10
7
  const [openingGesture, setOpeningGesture] = React.useState(void 0);
11
- React.useEffect(function inferOpeningKey() {
12
- const anchorElement = anchorRef === null || anchorRef === void 0 ? void 0 : anchorRef.current;
13
- const clickHandler = (event) => {
14
- if (event.detail !== 0) setOpeningGesture("mouse-click");
8
+ let t0;
9
+ let t1;
10
+ if ($[0] !== anchorRef) {
11
+ t0 = function inferOpeningKey() {
12
+ const anchorElement = anchorRef === null || anchorRef === void 0 ? void 0 : anchorRef.current;
13
+ const clickHandler = (event) => {
14
+ if (event.detail !== 0) setOpeningGesture("mouse-click");
15
+ };
16
+ const keydownHandler = (event_0) => {
17
+ if ([
18
+ "Space",
19
+ "Enter",
20
+ "ArrowDown",
21
+ "ArrowUp"
22
+ ].includes(event_0.code)) setOpeningGesture(event_0.code);
23
+ };
24
+ anchorElement === null || anchorElement === void 0 || anchorElement.addEventListener("click", clickHandler);
25
+ anchorElement === null || anchorElement === void 0 || anchorElement.addEventListener("keydown", keydownHandler);
26
+ return () => {
27
+ anchorElement === null || anchorElement === void 0 || anchorElement.removeEventListener("click", clickHandler);
28
+ anchorElement === null || anchorElement === void 0 || anchorElement.removeEventListener("keydown", keydownHandler);
29
+ };
15
30
  };
16
- const keydownHandler = (event) => {
17
- if ([
18
- "Space",
19
- "Enter",
31
+ t1 = [anchorRef];
32
+ $[0] = anchorRef;
33
+ $[1] = t0;
34
+ $[2] = t1;
35
+ } else {
36
+ t0 = $[1];
37
+ t1 = $[2];
38
+ }
39
+ React.useEffect(t0, t1);
40
+ let t2;
41
+ let t3;
42
+ if ($[3] !== anchorRef || $[4] !== containerRef || $[5] !== open || $[6] !== openingGesture) {
43
+ t2 = function moveFocusOnOpen() {
44
+ if (!open || !(containerRef !== null && containerRef !== void 0 && containerRef.current)) return;
45
+ const iterable = iterateFocusableElements(containerRef.current);
46
+ if (openingGesture === "mouse-click") if (anchorRef !== null && anchorRef !== void 0 && anchorRef.current) anchorRef.current.focus();
47
+ else throw new Error("For focus management, please attach anchorRef");
48
+ else if (openingGesture && [
20
49
  "ArrowDown",
21
- "ArrowUp"
22
- ].includes(event.code)) setOpeningGesture(event.code);
23
- };
24
- anchorElement === null || anchorElement === void 0 || anchorElement.addEventListener("click", clickHandler);
25
- anchorElement === null || anchorElement === void 0 || anchorElement.addEventListener("keydown", keydownHandler);
26
- return () => {
27
- anchorElement === null || anchorElement === void 0 || anchorElement.removeEventListener("click", clickHandler);
28
- anchorElement === null || anchorElement === void 0 || anchorElement.removeEventListener("keydown", keydownHandler);
50
+ "Space",
51
+ "Enter"
52
+ ].includes(openingGesture)) {
53
+ const firstElement = iterable.next().value;
54
+ setTimeout(() => firstElement === null || firstElement === void 0 ? void 0 : firstElement.focus());
55
+ } else if ("ArrowUp" === openingGesture) {
56
+ const elements = [...iterable];
57
+ const lastElement = elements[elements.length - 1];
58
+ setTimeout(() => lastElement.focus());
59
+ } else {
60
+ const firstElement_0 = iterable.next().value;
61
+ setTimeout(() => firstElement_0 === null || firstElement_0 === void 0 ? void 0 : firstElement_0.focus());
62
+ }
29
63
  };
30
- }, [anchorRef]);
31
- /**
32
- * Pick the first element to focus based on the key used to open the Menu
33
- * Click: anchor
34
- * ArrowDown | Space | Enter: first element
35
- * ArrowUp: last element
36
- */
37
- React.useEffect(function moveFocusOnOpen() {
38
- if (!open || !(containerRef !== null && containerRef !== void 0 && containerRef.current)) return;
39
- const iterable = iterateFocusableElements(containerRef.current);
40
- if (openingGesture === "mouse-click") if (anchorRef !== null && anchorRef !== void 0 && anchorRef.current) anchorRef.current.focus();
41
- else throw new Error("For focus management, please attach anchorRef");
42
- else if (openingGesture && [
43
- "ArrowDown",
44
- "Space",
45
- "Enter"
46
- ].includes(openingGesture)) {
47
- const firstElement = iterable.next().value;
48
- /** We push imperative focus to the next tick to prevent React's batching */
49
- setTimeout(() => firstElement === null || firstElement === void 0 ? void 0 : firstElement.focus());
50
- } else if ("ArrowUp" === openingGesture) {
51
- const elements = [...iterable];
52
- const lastElement = elements[elements.length - 1];
53
- setTimeout(() => lastElement.focus());
54
- } else {
55
- /** if the menu was not opened with the anchor, we default to the first element
56
- * for example: with keyboard shortcut (see stories/fixtures)
57
- */
58
- const firstElement = iterable.next().value;
59
- setTimeout(() => firstElement === null || firstElement === void 0 ? void 0 : firstElement.focus());
60
- }
61
- }, [
62
- open,
63
- openingGesture,
64
- anchorRef
65
- ]);
64
+ t3 = [
65
+ open,
66
+ openingGesture,
67
+ anchorRef,
68
+ containerRef
69
+ ];
70
+ $[3] = anchorRef;
71
+ $[4] = containerRef;
72
+ $[5] = open;
73
+ $[6] = openingGesture;
74
+ $[7] = t2;
75
+ $[8] = t3;
76
+ } else {
77
+ t2 = $[7];
78
+ t3 = $[8];
79
+ }
80
+ React.useEffect(t2, t3);
66
81
  };
67
82
  //#endregion
68
83
  export { useMenuInitialFocus };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@primer/react",
3
3
  "type": "module",
4
- "version": "38.36.0-rc.62d935135",
4
+ "version": "38.36.0-rc.a1228eda5",
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",