@@ -7,7 +7,7 @@ import "prismjs/themes/prism-coy.css";
7
7
const Container = styled . div `
8
8
width: 100%;
9
9
height: 100%;
10
- resize: both ;
10
+ resize: none ;
11
11
overflow: auto;
12
12
border: ButtonBorder 1px solid;
13
13
border-radius: 4px;
@@ -18,13 +18,14 @@ const Container = styled.div`
18
18
19
19
const Content = styled . div `
20
20
margin: 8px;
21
+ font: 14px monospace;
21
22
position: relative;
22
23
overflow: hidden;
23
24
` ;
24
25
25
26
const StyledPre = styled . pre `
26
27
position: absolute;
27
- font-size: 14px ;
28
+ font: inherit ;
28
29
width: 100%;
29
30
height: 100%;
30
31
resize: none;
@@ -34,7 +35,8 @@ const StyledPre = styled.pre`
34
35
35
36
const StyledTextarea = styled . textarea `
36
37
position: absolute;
37
- font-size: 14px;
38
+ overflow: hidden;
39
+ font: inherit;
38
40
width: 100%;
39
41
height: 100%;
40
42
padding: 0;
@@ -47,53 +49,87 @@ const StyledTextarea = styled.textarea`
47
49
caret-color: CanvasText;
48
50
` ;
49
51
52
+ const TextMeasure = styled . div `
53
+ font: inherit;
54
+ position: absolute;
55
+ visibility: hidden;
56
+ ` ;
57
+
50
58
type Props = React . TextareaHTMLAttributes < HTMLTextAreaElement > ;
51
59
52
60
const TextArea : React . FC < Props > = ( props ) => {
53
61
const highlightContainer = React . useRef < HTMLDivElement > ( null ) ;
54
62
const container = React . useRef < HTMLDivElement > ( null ) ;
55
63
const content = React . useRef < HTMLDivElement > ( null ) ;
56
64
const textarea = React . useRef < HTMLTextAreaElement > ( null ) ;
65
+ const measure = React . useRef < HTMLDivElement > ( null ) ;
57
66
const [ json , setJson ] = React . useState ( props . value as string ) ;
58
67
59
- React . useEffect ( ( ) => {
60
- highlight ( json ) ;
61
- } , [ ] ) ;
62
-
63
68
const onChange = React . useCallback (
64
69
( e : React . ChangeEvent < HTMLTextAreaElement > ) => {
65
70
setJson ( e . target . value ) ;
66
- highlight ( e . target . value ) ;
67
- autoResize ( ) ;
68
71
props . onChange ?.( e ) ;
69
72
} ,
70
73
[ ] ,
71
74
) ;
72
75
73
- const highlight = React . useCallback ( ( source : string ) => {
74
- if ( ! highlightContainer . current ) {
75
- return ;
76
+ const [ charWidth , charHeight ] = React . useMemo ( ( ) => {
77
+ if ( ! measure . current ) {
78
+ return [ 0 , 0 ] ;
76
79
}
80
+ const { width, height } = measure . current . getBoundingClientRect ( ) ;
81
+ return [ width , height ] ;
82
+ } , [ measure . current ] ) ;
77
83
78
- const highlighted = Prism . highlight ( source , Prism . languages . json , "json" ) ;
79
- highlightContainer . current . innerHTML = highlighted ;
80
- autoResize ( ) ;
81
- } , [ ] ) ;
84
+ const [ cols , rows ] = React . useMemo ( ( ) => {
85
+ const lines = json . split ( "\n" ) ;
86
+ const cols = Math . max ( ...lines . map ( ( line ) => line . length ) ) ;
87
+ const rows = lines . length ;
88
+ return [ cols , rows ] ;
89
+ } , [ json ] ) ;
82
90
83
91
const autoResize = React . useCallback ( ( ) => {
84
- if ( ! textarea . current || ! content . current ) {
92
+ if ( ! textarea . current || ! content . current || ! container . current ) {
85
93
return ;
86
94
}
87
95
88
- // shrink scroll area to get the correct scroll area
89
- content . current . style . width = "0" ;
90
- content . current . style . height = "0" ;
96
+ // resize textarea
97
+ const parentWidth = container . current . getBoundingClientRect ( ) . width - 16 ;
98
+ content . current . style . width = `${ Math . max (
99
+ parentWidth ,
100
+ charWidth * cols ,
101
+ ) } px`;
102
+ content . current . style . height = `${ charHeight * rows } px` ;
103
+ } , [
104
+ textarea . current ,
105
+ content . current ,
106
+ container . current ,
107
+ charWidth ,
108
+ charHeight ,
109
+ cols ,
110
+ rows ,
111
+ ] ) ;
91
112
92
- const { scrollHeight, scrollWidth } = textarea . current ;
93
- console . log ( scrollHeight , scrollWidth ) ;
94
- content . current . style . width = `${ scrollWidth } px` ;
95
- content . current . style . height = `${ scrollHeight } px` ;
96
- } , [ ] ) ;
113
+ React . useEffect ( ( ) => {
114
+ if ( ! highlightContainer . current ) {
115
+ return ;
116
+ }
117
+ const highlighted = Prism . highlight ( json , Prism . languages . json , "json" ) ;
118
+ highlightContainer . current . innerHTML = highlighted ;
119
+ autoResize ( ) ;
120
+ } , [ json ] ) ;
121
+
122
+ React . useEffect ( ( ) => {
123
+ setJson ( props . value as string ) ;
124
+ } , [ props . value ] ) ;
125
+
126
+ React . useEffect ( ( ) => {
127
+ autoResize ( ) ;
128
+ window . addEventListener ( "resize" , autoResize ) ;
129
+ return ( ) => {
130
+ window . removeEventListener ( "resize" , autoResize ) ;
131
+ } ;
132
+ } , [ autoResize ] ) ;
97
133
98
134
return (
99
135
< Container ref = { container } >
@@ -104,11 +140,14 @@ const TextArea: React.FC<Props> = (props) => {
104
140
< StyledTextarea
105
141
{ ...props }
106
142
wrap = "off"
143
+ cols = { cols }
144
+ rows = { rows }
107
145
ref = { textarea }
108
146
onChange = { onChange }
109
147
spellCheck = { false }
110
148
value = { json }
111
149
/>
150
+ < TextMeasure ref = { measure } > @</ TextMeasure >
112
151
</ Content >
113
152
</ Container >
114
153
) ;
0 commit comments