Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/__test__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,42 @@ describe('`style` is passed to rendered nodes and merged correctly', () => {
expect(container.firstChild).toMatchSnapshot();
});
});

describe('Device Pixel Ratio Detection', () => {
test('QRCodeCanvas reacts to devicePixelRatio changes', async () => {
// Import act from testing library
const { act } = await import('@testing-library/react');

// Mock window.devicePixelRatio
const originalDevicePixelRatio = window.devicePixelRatio;
Object.defineProperty(window, 'devicePixelRatio', {
writable: true,
value: 1,
});

const {rerender} = render(<QRCodeCanvas {...BASIC_PROPS} />);

// Simulate zoom change by changing devicePixelRatio and triggering resize
Object.defineProperty(window, 'devicePixelRatio', {
writable: true,
value: 2,
});

// Trigger resize event to simulate zoom wrapped in act
await act(async () => {
window.dispatchEvent(new Event('resize'));
});

// Re-render to trigger effect
rerender(<QRCodeCanvas {...BASIC_PROPS} />);

// The component should handle the change gracefully
expect(window.devicePixelRatio).toBe(2);

// Restore original value
Object.defineProperty(window, 'devicePixelRatio', {
writable: true,
value: originalDevicePixelRatio,
});
});
});
36 changes: 34 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,11 @@ const QRCodeCanvas = React.forwardRef<HTMLCanvasElement, QRPropsCanvas>(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [isImgLoaded, setIsImageLoaded] = React.useState(false);

// Track devicePixelRatio to re-render when zoom changes
const [devicePixelRatio, setDevicePixelRatio] = React.useState(
() => (typeof window !== 'undefined' ? window.devicePixelRatio : 1) || 1
);

const {margin, cells, numCells, calculatedImageSettings} = useQRCode({
value,
level,
Expand All @@ -395,6 +400,23 @@ const QRCodeCanvas = React.forwardRef<HTMLCanvasElement, QRPropsCanvas>(
size,
});

// Listen for resize events to detect zoom changes
React.useEffect(() => {
if (typeof window === 'undefined') return;

const handleResize = () => {
const newPixelRatio = window.devicePixelRatio || 1;
if (newPixelRatio !== devicePixelRatio) {
setDevicePixelRatio(newPixelRatio);
}
};

window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, [devicePixelRatio]);

React.useEffect(() => {
// Always update the canvas. It's cheap enough and we want to be correct
// with the current state.
Expand Down Expand Up @@ -428,7 +450,7 @@ const QRCodeCanvas = React.forwardRef<HTMLCanvasElement, QRPropsCanvas>(
// matches the number of cells. This avoids rounding issues, but does
// result in some potentially unwanted single pixel issues between
// blocks, only in environments that don't support Path2D.
const pixelRatio = window.devicePixelRatio || 1;
const pixelRatio = devicePixelRatio;
canvas.height = canvas.width = size * pixelRatio;
const scale = (size / numCells) * pixelRatio;
ctx.scale(scale, scale);
Expand Down Expand Up @@ -465,7 +487,17 @@ const QRCodeCanvas = React.forwardRef<HTMLCanvasElement, QRPropsCanvas>(
);
}
}
});
}, [
cells,
numCells,
size,
bgColor,
fgColor,
margin,
calculatedImageSettings,
isImgLoaded,
devicePixelRatio,
]);

// Ensure we mark image loaded as false here so we trigger updating the
// canvas in our other effect.
Expand Down