1- import {
1+ import React , {
22 forwardRef ,
3- useEffect ,
3+ useLayoutEffect ,
44 useMemo ,
55 useRef ,
66 useState ,
@@ -20,6 +20,47 @@ const ERROR_CORRECTION_LEVEL_MAP: Record<
2020 H : qrcodegen . QrCode . Ecc . HIGH ,
2121} as const ;
2222
23+ type SizedSvgProps = {
24+ width ?: number | string ;
25+ height ?: number | string ;
26+ viewBox ?: string ;
27+ } ;
28+
29+ function isSizedElement (
30+ node : React . ReactNode
31+ ) : node is React . ReactElement < SizedSvgProps > {
32+ return React . isValidElement ( node ) ;
33+ }
34+
35+ function parseNumber ( val : unknown ) : number | null {
36+ if ( typeof val === "number" ) return val ;
37+ if ( typeof val === "string" ) {
38+ const n = parseFloat ( val ) ;
39+ return Number . isFinite ( n ) ? n : null ;
40+ }
41+ return null ;
42+ }
43+
44+ function getIntrinsicSize (
45+ node : React . ReactNode
46+ ) : { w : number ; h : number } | null {
47+ if ( ! isSizedElement ( node ) ) return null ;
48+
49+ const wAttr = parseNumber ( node . props . width ) ;
50+ const hAttr = parseNumber ( node . props . height ) ;
51+ if ( wAttr && hAttr ) return { w : wAttr , h : hAttr } ;
52+
53+ const vb = node . props . viewBox ;
54+ if ( typeof vb === "string" ) {
55+ const nums = vb . trim ( ) . split ( / \s + / ) . map ( parseFloat ) ;
56+ if ( nums . length === 4 && nums . every ( Number . isFinite ) ) {
57+ const [ , , w , h ] = nums ;
58+ return { w, h } ;
59+ }
60+ }
61+ return null ;
62+ }
63+
2364export interface ReactQRProps extends PropsWithChildren {
2465 value : string ;
2566 size ?: number ;
@@ -36,8 +77,8 @@ export const ReactQR = forwardRef<SVGSVGElement, ReactQRProps>(
3677 size = 128 ,
3778 errorCorrectionLevel = "L" ,
3879 margin = 4 ,
39- foregroundColor = "#000000 " ,
40- backgroundColor = "#ffffff " ,
80+ foregroundColor = "#000 " ,
81+ backgroundColor = "#fff " ,
4182 children,
4283 } ,
4384 ref
@@ -57,20 +98,28 @@ export const ReactQR = forwardRef<SVGSVGElement, ReactQRProps>(
5798 } , [ value , errorCorrectionLevel ] ) ;
5899
59100 const qrSize = qr ?. size ?? 0 ;
60- const cellSize = qrSize > 0 ? ( size - margin * 2 ) / qrSize : 0 ;
101+ const cellSize = qrSize ? ( size - margin * 2 ) / qrSize : 0 ;
61102
62- const [ { w , h } , setOverlay ] = useState ( { w : 0 , h : 0 } ) ;
103+ const staticSize = useMemo ( ( ) => getIntrinsicSize ( children ) , [ children ] ) ;
63104
64- useEffect ( ( ) => {
65- if ( children && childrenSvgRef . current ) {
66- const { width, height } = childrenSvgRef . current . getBBox ( ) ;
67- setOverlay ( { w : width , h : height } ) ;
68- }
69- } , [ children ] ) ;
105+ const [ { w, h } , setOverlay ] = useState ( staticSize ?? { w : 0 , h : 0 } ) ;
106+
107+ useLayoutEffect ( ( ) => {
108+ if ( ! children || staticSize || ! childrenSvgRef . current ) return ;
109+ const { width, height } = childrenSvgRef . current . getBBox ( ) ;
110+ setOverlay ( { w : width , h : height } ) ;
111+ } , [ children , staticSize ] ) ;
70112
71113 const mask = useMemo ( ( ) => {
72- if ( ! children || ! cellSize ) {
73- return { sx : - 1 , sy : - 1 , ex : - 1 , ey : - 1 , fx : 0 , fy : 0 } ;
114+ if ( ! children || ! cellSize || ! w || ! h ) {
115+ return {
116+ sx : - 1 ,
117+ sy : - 1 ,
118+ ex : - 1 ,
119+ ey : - 1 ,
120+ fx : margin ,
121+ fy : margin ,
122+ } ;
74123 }
75124
76125 const startXf = ( qrSize - w / cellSize ) / 2 ;
@@ -116,24 +165,24 @@ export const ReactQR = forwardRef<SVGSVGElement, ReactQRProps>(
116165 height = { size }
117166 viewBox = { `0 0 ${ size } ${ size } ` }
118167 xmlns = "http://www.w3.org/2000/svg"
119- xmlnsXlink = "http://www.w3.org/1999/xlink"
120168 preserveAspectRatio = "xMidYMid meet"
121169 >
122170 < path d = { `M0,0h${ size } v${ size } h-${ size } z` } fill = { backgroundColor } />
123171 < path d = { qrPath } fill = { foregroundColor } shapeRendering = "crispEdges" />
124- { children ? (
172+ { children && (
125173 < svg
126174 ref = { childrenSvgRef }
127175 x = { mask . fx }
128176 y = { mask . fy }
129177 width = { w || 1 }
130178 height = { h || 1 }
179+ style = { { width : w || 1 , height : h || 1 } }
131180 viewBox = { `0 0 ${ w } ${ h } ` }
132181 pointerEvents = "none"
133182 >
134183 { children }
135184 </ svg >
136- ) : null }
185+ ) }
137186 </ svg >
138187 ) ;
139188 }
0 commit comments