@navikt/ds-react 5.10.4 → 5.11.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.
@@ -1,19 +1,57 @@
1
- /* https://github.com/mui/material-ui/blob/master/packages/mui-base/src/TextareaAutosize/TextareaAutosize.js */
1
+ /* https://github.com/mui/material-ui/blob/master/packages/mui-base/src/TextareaAutosize/TextareaAutosize.tsx */
2
2
  import React, { forwardRef, useEffect, useMemo, useRef, useState } from "react";
3
3
  import ReactDOM from "react-dom";
4
4
  import { debounce, mergeRefs, useClientLayoutEffect } from "../util";
5
5
 
6
+ type State = {
7
+ outerHeightStyle: number;
8
+ overflow?: boolean | undefined;
9
+ };
10
+
11
+ const updateState = (
12
+ prevState: State,
13
+ newState: State,
14
+ renders: React.MutableRefObject<number>
15
+ ) => {
16
+ const { outerHeightStyle, overflow } = newState;
17
+ // Need a large enough difference to update the height.
18
+ // This prevents infinite rendering loop.
19
+ if (
20
+ renders.current < 20 &&
21
+ ((outerHeightStyle > 0 &&
22
+ Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1) ||
23
+ prevState.overflow !== overflow)
24
+ ) {
25
+ renders.current += 1;
26
+ return {
27
+ overflow,
28
+ outerHeightStyle,
29
+ };
30
+ }
31
+ if (process.env.NODE_ENV !== "production") {
32
+ if (renders.current === 20) {
33
+ console.error(
34
+ [
35
+ "Textarea: Too many re-renders. The layout is unstable.",
36
+ "TextareaAutosize limits the number of renders to prevent an infinite loop.",
37
+ ].join("\n")
38
+ );
39
+ }
40
+ }
41
+ return prevState;
42
+ };
43
+
6
44
  /**
7
- * https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerDocument.ts
8
- * https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerWindow.ts
45
+ * https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerDocument/ownerDocument.ts
46
+ * https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerWindow/ownerWindow.ts
9
47
  */
10
- const ownerWindow = (node: Node | null): Window => {
48
+ const ownerWindow = (node: Node | undefined): Window => {
11
49
  const doc = (node && node.ownerDocument) || document;
12
50
  return doc.defaultView || window;
13
51
  };
14
52
 
15
- function getStyleValue(computedStyle, property) {
16
- return parseInt(computedStyle[property], 10) || 0;
53
+ function getStyleValue(value: string) {
54
+ return parseInt(value, 10) || 0;
17
55
  }
18
56
 
19
57
  interface TextareaAutosizeProps
@@ -30,34 +68,46 @@ interface TextareaAutosizeProps
30
68
  * @default 1
31
69
  */
32
70
  minRows?: number;
71
+ /**
72
+ * If true, textarea will never get `overflow:hidden`
73
+ */
74
+ autoScrollbar?: boolean;
33
75
  }
34
76
 
35
77
  const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
36
78
  (
37
- { className, onChange, maxRows, minRows = 1, style, value, ...other },
79
+ {
80
+ className,
81
+ onChange,
82
+ maxRows,
83
+ minRows = 1,
84
+ autoScrollbar,
85
+ style,
86
+ value,
87
+ ...other
88
+ }: TextareaAutosizeProps,
38
89
  ref
39
90
  ) => {
40
91
  const { current: isControlled } = useRef(value != null);
41
- const inputRef = useRef<HTMLTextAreaElement | null>(null);
92
+ const inputRef = useRef<HTMLTextAreaElement>(null);
42
93
  const handleRef = useMemo(() => mergeRefs([inputRef, ref]), [ref]);
43
- const shadowRef = useRef<HTMLTextAreaElement | null>(null);
94
+ const shadowRef = useRef<HTMLTextAreaElement>(null);
44
95
  const renders = useRef(0);
45
- const [state, setState] = useState<any>({});
96
+ const [state, setState] = useState<State>({ outerHeightStyle: 0 });
46
97
 
47
98
  const getUpdatedState = React.useCallback(() => {
48
- if (!inputRef.current || !shadowRef.current) return;
49
- const input = inputRef.current;
99
+ const input = inputRef.current!;
50
100
  const containerWindow = ownerWindow(input);
51
101
  const computedStyle = containerWindow.getComputedStyle(input);
52
102
 
53
103
  // If input's width is shrunk and it's not visible, don't sync height.
54
104
  if (computedStyle.width === "0px") {
55
- return;
105
+ return { outerHeightStyle: 0 };
56
106
  }
57
107
 
58
- const inputShallow = shadowRef.current;
108
+ const inputShallow = shadowRef.current!;
59
109
  inputShallow.style.width = computedStyle.width;
60
- inputShallow.value = input.value || other?.placeholder || "x";
110
+ inputShallow.value = input.value || other.placeholder || "x";
61
111
  if (inputShallow.value.slice(-1) === "\n") {
62
112
  // Certain fonts which overflow the line height will cause the textarea
63
113
  // to report a different scrollHeight depending on whether the last line
@@ -65,13 +115,13 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
65
115
  inputShallow.value += " ";
66
116
  }
67
117
 
68
- const boxSizing = computedStyle["box-sizing"];
118
+ const boxSizing = computedStyle.boxSizing;
69
119
  const padding =
70
- getStyleValue(computedStyle, "padding-bottom") +
71
- getStyleValue(computedStyle, "padding-top");
120
+ getStyleValue(computedStyle.paddingBottom) +
121
+ getStyleValue(computedStyle.paddingTop);
72
122
  const border =
73
- getStyleValue(computedStyle, "border-bottom-width") +
74
- getStyleValue(computedStyle, "border-top-width");
123
+ getStyleValue(computedStyle.borderBottomWidth) +
124
+ getStyleValue(computedStyle.borderTopWidth);
75
125
 
76
126
  // The height of the inner content
77
127
  const innerHeight = inputShallow.scrollHeight - padding;
@@ -97,7 +147,7 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
97
147
  const overflow = Math.abs(outerHeight - innerHeight) <= 1;
98
148
 
99
149
  return { outerHeightStyle, overflow };
100
- }, [maxRows, minRows, other?.placeholder]);
150
+ }, [maxRows, minRows, other.placeholder]);
101
151
 
102
152
  const syncHeight = React.useCallback(() => {
103
153
  const newState = getUpdatedState();
@@ -106,85 +156,52 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
106
156
  return;
107
157
  }
108
158
 
109
- setState((prevState) => {
110
- return updateState(prevState, newState);
111
- });
159
+ setState((prevState) => updateState(prevState, newState, renders));
112
160
  }, [getUpdatedState]);
113
161
 
114
- const updateState = (prevState, newState) => {
115
- const { outerHeightStyle, overflow } = newState;
116
- // Need a large enough difference to update the height.
117
- // This prevents infinite rendering loop.
118
- if (
119
- renders.current < 20 &&
120
- ((outerHeightStyle > 0 &&
121
- Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1) ||
122
- prevState.overflow !== overflow)
123
- ) {
124
- renders.current += 1;
125
- return {
126
- overflow,
127
- outerHeightStyle,
128
- };
129
- }
130
- if (process.env.NODE_ENV !== "production") {
131
- if (renders.current === 20) {
132
- console.error(
133
- [
134
- "Textarea: Too many re-renders. The layout is unstable.",
135
- "TextareaAutosize limits the number of renders to prevent an infinite loop.",
136
- ].join("\n")
137
- );
138
- }
139
- }
140
- return prevState;
141
- };
142
-
143
- const withFlushSync = () => {
144
- const newState = getUpdatedState();
162
+ useClientLayoutEffect(() => {
163
+ const syncHeightWithFlushSync = () => {
164
+ const newState = getUpdatedState();
145
165
 
146
- if (isEmpty(newState)) {
147
- return;
148
- }
166
+ if (isEmpty(newState)) {
167
+ return;
168
+ }
149
169
 
150
- // In React 18, state updates in a ResizeObserver's callback are happening after the paint which causes flickering
151
- // when doing some visual updates in it. Using flushSync ensures that the dom will be painted after the states updates happen
152
- // Related issue - https://github.com/facebook/react/issues/24331
153
- ReactDOM.flushSync(() => {
154
- setState((prevState) => {
155
- return updateState(prevState, newState);
170
+ // In React 18, state updates in a ResizeObserver's callback are happening after
171
+ // the paint, this leads to an infinite rendering.
172
+ //
173
+ // Using flushSync ensures that the states is updated before the next pain.
174
+ // Related issue - https://github.com/facebook/react/issues/24331
175
+ ReactDOM.flushSync(() => {
176
+ setState((prevState) => updateState(prevState, newState, renders));
156
177
  });
157
- });
158
- };
178
+ };
159
179
 
160
- React.useEffect(() => {
161
- const handleResize = debounce(() => {
180
+ const handleResize = () => {
162
181
  renders.current = 0;
182
+ syncHeightWithFlushSync();
183
+ };
163
184
 
164
- if (inputRef.current) {
165
- withFlushSync();
166
- }
167
- });
168
- let resizeObserver: ResizeObserver;
169
-
185
+ const debounceHandleResize = debounce(handleResize);
170
186
  const input = inputRef.current!;
171
187
  const containerWindow = ownerWindow(input);
172
188
 
173
- containerWindow.addEventListener("resize", handleResize);
189
+ containerWindow.addEventListener("resize", debounceHandleResize);
174
190
 
191
+ let resizeObserver: ResizeObserver;
175
192
  if (typeof ResizeObserver !== "undefined") {
176
193
  resizeObserver = new ResizeObserver(handleResize);
177
194
  resizeObserver.observe(input);
178
195
  }
179
196
 
180
197
  return () => {
181
- handleResize.clear();
182
- containerWindow.removeEventListener("resize", handleResize);
198
+ debounceHandleResize.clear();
199
+ containerWindow.removeEventListener("resize", debounceHandleResize);
183
200
  if (resizeObserver) {
184
201
  resizeObserver.disconnect();
185
202
  }
186
203
  };
187
- });
204
+ }, [getUpdatedState]);
188
205
 
189
206
  useClientLayoutEffect(() => {
190
207
  syncHeight();
@@ -194,7 +211,7 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
194
211
  renders.current = 0;
195
212
  }, [value]);
196
213
 
197
- const handleChange = (event) => {
214
+ const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
198
215
  renders.current = 0;
199
216
 
200
217
  if (!isControlled) {
@@ -218,7 +235,7 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
218
235
  height: state.outerHeightStyle,
219
236
  // Need a large enough difference to allow scrolling.
220
237
  // This prevents infinite rendering loop.
221
- ...(state.overflow ? { overflow: "hidden" } : {}),
238
+ overflow: state.overflow && !autoScrollbar ? "hidden" : undefined,
222
239
  ...style,
223
240
  }}
224
241
  {...other}
@@ -250,12 +267,12 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
250
267
  }
251
268
  );
252
269
 
253
- function isEmpty(obj: any) {
270
+ function isEmpty(obj: State) {
254
271
  return (
255
272
  obj === undefined ||
256
273
  obj === null ||
257
274
  Object.keys(obj).length === 0 ||
258
- (obj?.outerHeightStyle === 0 && !obj?.overflow)
275
+ (obj.outerHeightStyle === 0 && !obj.overflow)
259
276
  );
260
277
  }
261
278