react-display-scale-engine 0.1.2 → 0.1.4

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/README.md CHANGED
@@ -1,14 +1,8 @@
1
1
  # react-display-scale-engine
2
2
 
3
- React utilities for maintaining a consistent visual density on Windows displays with elevated OS display scaling, with first-class Material UI overlay support.
3
+ Keep a React app visually consistent on Windows displays that use elevated OS display scaling, while preserving normal browser behavior at standard scale. It is especially useful for MUI overlays such as menus, popovers, poppers, and selects.
4
4
 
5
- The library is split into entry points, so an application only takes the layer it needs:
6
-
7
- ```ts
8
- import { detectDisplayScale } from "react-display-scale-engine/core";
9
- import { DisplayScaleProvider } from "react-display-scale-engine/react";
10
- import { DisplayScaleMenu } from "react-display-scale-engine/mui";
11
- ```
5
+ The engine only applies compensation when it detects the supported display-scale condition. At normal scale, it leaves the app layout and native MUI positioning unchanged.
12
6
 
13
7
  ## Install
14
8
 
@@ -16,11 +10,20 @@ import { DisplayScaleMenu } from "react-display-scale-engine/mui";
16
10
  npm install react-display-scale-engine
17
11
  ```
18
12
 
19
- `react`, `react-dom`, `@mui/material`, `@emotion/react`, `@emotion/styled`, and `@popperjs/core` are peer dependencies. The MUI peers are necessary only for the `/mui` entry point.
13
+ ## Compatibility
20
14
 
21
- ## Quick start
15
+ | Dependency | Supported version | Notes |
16
+ | --- | --- | --- |
17
+ | Node.js | 18 or newer | Required by the package. |
18
+ | React / React DOM | 18 or newer | React 19 is supported and tested. |
19
+ | Material UI | 7.x | Required only when using `…/mui`. MUI 8 is not supported yet. |
20
+ | Emotion | 11 or newer | Required only when using `…/mui`. |
21
+ | `@popperjs/core` | 2 or newer | Required only when using `…/mui`. |
22
+ | TypeScript | Optional | Type declarations are included; the package is verified with TypeScript 5.7. |
22
23
 
23
- Call the optional pre-mount warm-up, then wrap the application once.
24
+ ## Start here
25
+
26
+ Call the optional warm-up before rendering, then add one provider around the app.
24
27
 
25
28
  ```tsx
26
29
  import { primeDisplayScaleCompensation } from "react-display-scale-engine/core";
@@ -29,56 +32,54 @@ import { DisplayScaleProvider } from "react-display-scale-engine/react";
29
32
  primeDisplayScaleCompensation();
30
33
 
31
34
  export function AppRoot() {
32
- return <DisplayScaleProvider><App /></DisplayScaleProvider>;
35
+ return (
36
+ <DisplayScaleProvider>
37
+ <App />
38
+ </DisplayScaleProvider>
39
+ );
33
40
  }
34
41
  ```
35
42
 
36
- Use supplied components for MUI overlays whose placement must account for the compensated coordinate space.
43
+ ## MUI overlays: recommended hook API
37
44
 
38
- ```tsx
39
- import { DisplayScaleMenu } from "react-display-scale-engine/mui";
45
+ Keep using MUI's native components. The hook supplies the compensated props; spread them onto the matching MUI component.
40
46
 
41
- <DisplayScaleMenu anchorEl={anchorEl} open={open} onClose={close}>
42
- <MenuItem onClick={close}>Action</MenuItem>
43
- </DisplayScaleMenu>;
44
- ```
45
-
46
- See [the engine guide](docs/display-scale-engine.md) for policy, limitations, and migration guidance.
47
+ ```tsx
48
+ import { Menu, MenuItem } from "@mui/material";
49
+ import { useMuiDisplayScaleMenu } from "react-display-scale-engine/mui";
47
50
 
48
- ## Development
51
+ function ActionsMenu({ anchorEl, open, close }) {
52
+ const { menuProps } = useMuiDisplayScaleMenu({ anchorEl, open });
49
53
 
50
- ```bash
51
- npm install
52
- npm run check
53
- npm run build
54
- npm pack --dry-run
54
+ return (
55
+ <Menu {...menuProps} onClose={close}>
56
+ <MenuItem onClick={close}>Action</MenuItem>
57
+ </Menu>
58
+ );
59
+ }
55
60
  ```
56
61
 
57
- Public entry points:
62
+ Use the corresponding hook for each MUI component:
58
63
 
59
- | Import | Purpose |
64
+ | MUI component | Engine hook |
60
65
  | --- | --- |
61
- | `react-display-scale-engine` | All public exports |
62
- | `…/core` | Framework-independent detection and compensation math |
63
- | `…/react` | Provider and React hooks |
64
- | `…/mui` | MUI Menu, Popover, Popper, Select, Drawer and token adapters |
65
-
66
- ## Publishing
66
+ | `Menu` | `useMuiDisplayScaleMenu` |
67
+ | `Popover` | `useMuiDisplayScalePopover` |
68
+ | `Popper` | `useMuiDisplayScalePopper` |
69
+ | `Select` | `useMuiDisplayScaleSelect` |
67
70
 
68
- Before first publishing, make sure the `name` in `package.json` is an npm scope you own, then run `npm login`.
71
+ `useMuiDisplayScaleSelect` also handles its internal MUI menu, including the first time the options list opens.
69
72
 
70
- ```bash
71
- npm run release:check
72
- npm run release:dry-run
73
- npm run release:publish -- --otp 123456
74
- ```
73
+ Wrapper components (`DisplayScaleMenu`, `DisplayScalePopover`, `DisplayScalePopper`, and `DisplayScaleSelect`) remain available, but hooks are the preferred API for both new and existing MUI applications.
75
74
 
76
- The package includes a CLI:
75
+ ## Import paths
77
76
 
78
- ```bash
79
- npx rds-engine check
80
- npx rds-engine publish --dry-run
81
- npx rds-engine publish --otp 123456
77
+ ```ts
78
+ import { detectDisplayScale } from "react-display-scale-engine/core";
79
+ import { DisplayScaleProvider } from "react-display-scale-engine/react";
80
+ import { useMuiDisplayScalePopover } from "react-display-scale-engine/mui";
82
81
  ```
83
82
 
84
- `publish` always builds and runs `npm pack --dry-run` first. It never changes the version; use `npm version patch|minor|major` before publishing. When npm account policy requires 2FA for publishing, supply the current authenticator code with `--otp` or use an npm granular access token that has bypass-2FA permission.
83
+ The package root (`react-display-scale-engine`) also exports all public APIs for compatibility. Prefer the explicit paths above for new code.
84
+
85
+ For engine behavior, limitations, and migration examples, see [the guide](docs/display-scale-engine.md).
@@ -1,7 +1,7 @@
1
1
  import { useDisplayScaleCompensation, useDisplayScaleAnchor } from './chunk-F5AFPMQC.js';
2
2
  import { Menu, Popover, Popper, Select } from '@mui/material';
3
3
  import { useTheme } from '@mui/material/styles';
4
- import { useCallback, useLayoutEffect, useRef, useMemo, useId, useState } from 'react';
4
+ import { useCallback, useLayoutEffect, useRef, useMemo, useState, useId } from 'react';
5
5
  import { jsx } from 'react/jsx-runtime';
6
6
 
7
7
  // src/createMuiDisplayScaleComponents.ts
@@ -88,6 +88,7 @@ function resolveOrigins(anchorOrigin, transformOrigin, anchorWidth, anchorHeight
88
88
  function useDisplayScalePopoverPosition({
89
89
  open,
90
90
  overlayRootRef,
91
+ overlayRoot: mountedOverlayRoot,
91
92
  anchorElement,
92
93
  anchorReference,
93
94
  anchorPosition,
@@ -98,7 +99,7 @@ function useDisplayScalePopoverPosition({
98
99
  const { compensation, layoutExtents, rootRef } = useDisplayScaleCompensation();
99
100
  const correctPosition = useCallback(() => {
100
101
  if (!open || !compensation.active || !layoutExtents) return;
101
- const overlayRoot = overlayRootRef.current;
102
+ const overlayRoot = mountedOverlayRoot ?? overlayRootRef.current;
102
103
  const paper = overlayRoot?.querySelector(".MuiPopover-paper");
103
104
  if (!paper) return;
104
105
  const zoom = compensation.zoom > 0 ? compensation.zoom : 1;
@@ -140,6 +141,7 @@ function useDisplayScalePopoverPosition({
140
141
  compensation.zoom,
141
142
  layoutExtents,
142
143
  marginThreshold,
144
+ mountedOverlayRoot,
143
145
  open,
144
146
  overlayRootRef,
145
147
  rootRef,
@@ -160,7 +162,10 @@ function useDisplayScalePopoverPosition({
160
162
  };
161
163
  }, [correctPosition, open]);
162
164
  }
163
- function DisplayScaleMenu({ anchorEl, ...props }) {
165
+ function useMuiDisplayScaleMenu({
166
+ anchorEl,
167
+ ...props
168
+ }) {
164
169
  const displayScaleAnchor = useDisplayScaleAnchor(anchorEl ?? null);
165
170
  const { compensation, rootRef } = useDisplayScaleCompensation();
166
171
  const theme = useTheme();
@@ -194,9 +199,8 @@ function DisplayScaleMenu({ anchorEl, ...props }) {
194
199
  transformOrigin,
195
200
  marginThreshold: props.marginThreshold
196
201
  });
197
- return /* @__PURE__ */ jsx(
198
- Menu,
199
- {
202
+ return {
203
+ menuProps: {
200
204
  ...props,
201
205
  ref: overlayRootRef,
202
206
  anchorEl: displayScaleAnchor,
@@ -205,9 +209,13 @@ function DisplayScaleMenu({ anchorEl, ...props }) {
205
209
  container: rootRef.current ?? document.body,
206
210
  transformOrigin
207
211
  }
208
- );
212
+ };
209
213
  }
210
- function DisplayScalePopover({
214
+ function DisplayScaleMenu(props) {
215
+ const { menuProps } = useMuiDisplayScaleMenu(props);
216
+ return /* @__PURE__ */ jsx(Menu, { ...menuProps });
217
+ }
218
+ function useMuiDisplayScalePopover({
211
219
  anchorEl,
212
220
  ...props
213
221
  }) {
@@ -224,15 +232,18 @@ function DisplayScalePopover({
224
232
  transformOrigin: props.transformOrigin,
225
233
  marginThreshold: props.marginThreshold
226
234
  });
227
- return /* @__PURE__ */ jsx(
228
- Popover,
229
- {
235
+ return {
236
+ popoverProps: {
230
237
  ...props,
231
238
  ref: overlayRootRef,
232
239
  anchorEl: displayScaleAnchor,
233
240
  container: rootRef.current ?? document.body
234
241
  }
235
- );
242
+ };
243
+ }
244
+ function DisplayScalePopover(props) {
245
+ const { popoverProps } = useMuiDisplayScalePopover(props);
246
+ return /* @__PURE__ */ jsx(Popover, { ...popoverProps });
236
247
  }
237
248
  var defaultDisplayScalePopperModifiers = [
238
249
  {
@@ -255,25 +266,31 @@ function scalePopperOffsets(modifiers, zoom) {
255
266
  return { ...modifier, options: { ...options, offset } };
256
267
  });
257
268
  }
258
- function DisplayScalePopper({ anchorEl, ...props }) {
269
+ function useMuiDisplayScalePopper({
270
+ anchorEl,
271
+ ...props
272
+ }) {
259
273
  const displayScaleAnchor = useDisplayScaleAnchor(anchorEl ?? null);
260
274
  const { compensation, rootRef } = useDisplayScaleCompensation();
261
275
  const modifiers = useMemo(
262
276
  () => scalePopperOffsets(props.modifiers ?? defaultDisplayScalePopperModifiers, compensation.zoom),
263
277
  [compensation.zoom, props.modifiers]
264
278
  );
265
- return /* @__PURE__ */ jsx(
266
- Popper,
267
- {
279
+ return {
280
+ popperProps: {
268
281
  ...props,
269
282
  anchorEl: displayScaleAnchor,
270
283
  container: rootRef.current ?? document.body,
271
284
  modifiers,
272
285
  style: { ...props.style, zoom: 1 }
273
286
  }
274
- );
287
+ };
275
288
  }
276
- function DisplayScaleSelect(props) {
289
+ function DisplayScalePopper(props) {
290
+ const { popperProps } = useMuiDisplayScalePopper(props);
291
+ return /* @__PURE__ */ jsx(Popper, { ...popperProps });
292
+ }
293
+ function useMuiDisplayScaleSelect(props) {
277
294
  const {
278
295
  MenuProps,
279
296
  SelectDisplayProps,
@@ -285,10 +302,15 @@ function DisplayScaleSelect(props) {
285
302
  } = props;
286
303
  const { rootRef } = useDisplayScaleCompensation();
287
304
  const menuRootRef = useRef(null);
305
+ const [menuRootElement, setMenuRootElement] = useState(null);
288
306
  const selectId = useId();
289
307
  const [anchorElement, setAnchorElement] = useState(null);
290
308
  const [uncontrolledOpen, setUncontrolledOpen] = useState(Boolean(defaultOpen));
291
309
  const open = openProp ?? uncontrolledOpen;
310
+ const handleMenuRootRef = useCallback((element) => {
311
+ menuRootRef.current = element;
312
+ setMenuRootElement(element);
313
+ }, []);
292
314
  useLayoutEffect(() => {
293
315
  if (!open) {
294
316
  setAnchorElement(null);
@@ -315,6 +337,7 @@ function DisplayScaleSelect(props) {
315
337
  useDisplayScalePopoverPosition({
316
338
  open,
317
339
  overlayRootRef: menuRootRef,
340
+ overlayRoot: menuRootElement,
318
341
  anchorElement,
319
342
  anchorOrigin: { vertical: "bottom", horizontal: "center" },
320
343
  transformOrigin: { vertical: "top", horizontal: "center" }
@@ -325,7 +348,10 @@ function DisplayScaleSelect(props) {
325
348
  return {
326
349
  ...MenuProps,
327
350
  container: rootRef.current ?? document.body,
328
- ref: menuRootRef,
351
+ // Select mounts its internal Menu only after the first open event.
352
+ // Store the mounted root in state so the position hook runs again once
353
+ // MUI has measured and rendered its paper, rather than only on reopen.
354
+ ref: handleMenuRootRef,
329
355
  slotProps: {
330
356
  ...MenuProps?.slotProps,
331
357
  paper: {
@@ -334,10 +360,9 @@ function DisplayScaleSelect(props) {
334
360
  }
335
361
  }
336
362
  };
337
- }, [MenuProps, rootRef]);
338
- return /* @__PURE__ */ jsx(
339
- Select,
340
- {
363
+ }, [MenuProps, handleMenuRootRef, rootRef]);
364
+ return {
365
+ selectProps: {
341
366
  ...selectProps,
342
367
  open,
343
368
  onOpen: handleOpen,
@@ -348,6 +373,24 @@ function DisplayScaleSelect(props) {
348
373
  "data-ep-display-scale-select": selectId
349
374
  }
350
375
  }
376
+ };
377
+ }
378
+ function DisplayScaleSelect(props) {
379
+ const { selectProps } = useMuiDisplayScaleSelect(props);
380
+ return /* @__PURE__ */ jsx(Select, { ...selectProps });
381
+ }
382
+ function useMuiDisplayScaleComponents() {
383
+ const { compensation, overlayStrategy, rootRef } = useDisplayScaleCompensation();
384
+ const getPortalContainer = useCallback(() => {
385
+ if (overlayStrategy === "document-body") return document.body;
386
+ return rootRef.current ?? document.body;
387
+ }, [overlayStrategy, rootRef]);
388
+ return useMemo(
389
+ () => createMuiDisplayScaleComponents({
390
+ getPortalContainer,
391
+ overlayZoom: overlayStrategy === "document-body" ? compensation.zoom : 1
392
+ }),
393
+ [compensation.zoom, getPortalContainer, overlayStrategy]
351
394
  );
352
395
  }
353
396
  function useDisplayScaleTabsIndicator(tabsRef, value) {
@@ -408,6 +451,6 @@ var signinSplitColumnSx = {
408
451
  boxSizing: "border-box"
409
452
  };
410
453
 
411
- export { DisplayScaleMenu, DisplayScalePopover, DisplayScalePopper, DisplayScaleSelect, createMuiDisplayScaleComponents, displayScalePageFillSx, signinSplitColumnSx, signinViewportShellSx, useDisplayScalePopoverPosition, useDisplayScaleTabsIndicator };
412
- //# sourceMappingURL=chunk-OQRISU2D.js.map
413
- //# sourceMappingURL=chunk-OQRISU2D.js.map
454
+ export { DisplayScaleMenu, DisplayScalePopover, DisplayScalePopper, DisplayScaleSelect, createMuiDisplayScaleComponents, displayScalePageFillSx, signinSplitColumnSx, signinViewportShellSx, useDisplayScalePopoverPosition, useDisplayScaleTabsIndicator, useMuiDisplayScaleComponents, useMuiDisplayScaleMenu, useMuiDisplayScalePopover, useMuiDisplayScalePopper, useMuiDisplayScaleSelect };
455
+ //# sourceMappingURL=chunk-47O3TJUJ.js.map
456
+ //# sourceMappingURL=chunk-47O3TJUJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/createMuiDisplayScaleComponents.ts","../src/useDisplayScalePopoverPosition.ts","../src/DisplayScaleMuiOverlays.tsx","../src/DisplayScaleSelect.tsx","../src/useMuiDisplayScaleComponents.ts","../src/useDisplayScaleTabsIndicator.ts","../src/displayScaleLayoutTokens.ts"],"names":["useRef","useCallback","useLayoutEffect","useMemo","jsx"],"mappings":"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","file":"chunk-47O3TJUJ.js","sourcesContent":["import type { Components, Theme } from \"@mui/material/styles\";\n\ntype PortalContainerGetter = () => HTMLElement;\n\ntype MuiDisplayScaleComponentsOptions = {\n getPortalContainer: PortalContainerGetter;\n /**\n * Overlay content is portalled outside the zoomed app root, so it needs the\n * same density correction independently.\n */\n overlayZoom?: number;\n};\n\n/**\n * MUI adapter for a compensated app root.\n *\n * Centralizing these defaults gives every MUI overlay the portal strategy and\n * density correction chosen by the display-scale engine.\n */\nexport function createMuiDisplayScaleComponents({\n getPortalContainer,\n overlayZoom = 1,\n}: MuiDisplayScaleComponentsOptions): Components<Theme> {\n const zoom = overlayZoom > 0 ? overlayZoom : 1;\n const resolveContainerZoom = (container: unknown): number => {\n if (typeof document === \"undefined\") return zoom;\n const resolvedContainer =\n typeof container === \"function\" ? (container as PortalContainerGetter)() : container;\n // MUI does not always expose the resolved portal node through ownerState.\n // Missing means the component will use its default (document.body).\n return resolvedContainer == null || resolvedContainer === document.body ? zoom : 1;\n };\n\n return {\n MuiModal: {\n defaultProps: {\n container: getPortalContainer,\n },\n },\n MuiDialog: {\n defaultProps: {\n container: getPortalContainer,\n },\n styleOverrides: {\n paper: ({ ownerState }) => ({ zoom: resolveContainerZoom(ownerState?.container) }),\n },\n },\n MuiPopover: {\n defaultProps: {\n container: getPortalContainer,\n },\n styleOverrides: {\n paper: ({ ownerState }) => ({ zoom: resolveContainerZoom(ownerState?.container) }),\n },\n },\n MuiPopper: {\n defaultProps: {\n container: getPortalContainer,\n },\n styleOverrides: {\n root: ({ ownerState }) => ({ zoom: resolveContainerZoom(ownerState?.container) }),\n },\n },\n MuiDrawer: {\n defaultProps: {\n // Drawer inherits Modal props. Supplying the portal host here (rather\n // than only through MuiModal) also covers Drawer internals directly.\n container: getPortalContainer,\n },\n styleOverrides: {\n paper: ({ ownerState }) => {\n // Permanent/persistent drawers live inside the compensated root and\n // must not be zoomed a second time. Temporary drawers are portalled\n // to document.body, so their paper needs the overlay density zoom.\n if (ownerState.variant !== \"temporary\") return {};\n\n const isVertical = ownerState.anchor === \"left\" || ownerState.anchor === \"right\";\n return isVertical\n ? {\n zoom,\n height: \"var(--ep-display-scale-overlay-viewport-height, 100dvh)\",\n maxHeight: \"var(--ep-display-scale-overlay-viewport-height, 100dvh)\",\n }\n : {\n zoom,\n width: \"var(--ep-display-scale-overlay-viewport-width, 100dvw)\",\n maxWidth: \"var(--ep-display-scale-overlay-viewport-width, 100dvw)\",\n };\n },\n },\n },\n };\n}\n","import type { PopoverOrigin, PopoverProps } from \"@mui/material\";\nimport { type RefObject, useCallback, useLayoutEffect } from \"react\";\nimport { useDisplayScaleCompensation } from \"./DisplayScaleCompensationProvider\";\n\ntype DisplayScalePopoverPositionOptions = {\n open: boolean;\n overlayRootRef: RefObject<HTMLDivElement | null>;\n /**\n * The mounted overlay root. A ref assignment alone does not trigger a\n * render, so callers with lazily-mounted overlays (notably MUI Select)\n * provide this state value to schedule the first correction after mount.\n */\n overlayRoot?: HTMLDivElement | null;\n anchorElement: HTMLElement | null;\n anchorReference?: PopoverProps[\"anchorReference\"];\n anchorPosition?: PopoverProps[\"anchorPosition\"];\n anchorOrigin?: PopoverProps[\"anchorOrigin\"];\n transformOrigin?: PopoverProps[\"transformOrigin\"];\n marginThreshold?: PopoverProps[\"marginThreshold\"];\n};\n\nfunction resolveOffset(\n origin: number | \"top\" | \"center\" | \"bottom\" | \"left\" | \"right\" | undefined,\n size: number,\n): number {\n if (typeof origin === \"number\") return origin;\n if (origin === \"center\") return size / 2;\n if (origin === \"bottom\" || origin === \"right\") return size;\n return 0;\n}\n\nfunction resolveOrigins(\n anchorOrigin: PopoverOrigin | undefined,\n transformOrigin: PopoverOrigin | undefined,\n anchorWidth: number,\n anchorHeight: number,\n paperWidth: number,\n paperHeight: number,\n): { anchorX: number; anchorY: number; transformX: number; transformY: number } {\n const resolvedAnchorOrigin = anchorOrigin ?? { vertical: \"top\", horizontal: \"left\" };\n const resolvedTransformOrigin = transformOrigin ?? { vertical: \"top\", horizontal: \"left\" };\n\n return {\n anchorX: resolveOffset(resolvedAnchorOrigin.horizontal, anchorWidth),\n anchorY: resolveOffset(resolvedAnchorOrigin.vertical, anchorHeight),\n transformX: resolveOffset(resolvedTransformOrigin.horizontal, paperWidth),\n transformY: resolveOffset(resolvedTransformOrigin.vertical, paperHeight),\n };\n}\n\n/**\n * Replaces only MUI Popover's physical-viewport collision pass. MUI still\n * owns focus, transition and anchor semantics; this writes the final logical\n * top/left inside the compensated portal root.\n */\nexport function useDisplayScalePopoverPosition({\n open,\n overlayRootRef,\n overlayRoot: mountedOverlayRoot,\n anchorElement,\n anchorReference,\n anchorPosition,\n anchorOrigin,\n transformOrigin,\n marginThreshold = 16,\n}: DisplayScalePopoverPositionOptions): void {\n const { compensation, layoutExtents, rootRef } = useDisplayScaleCompensation();\n\n const correctPosition = useCallback(() => {\n if (!open || !compensation.active || !layoutExtents) return;\n\n const overlayRoot = mountedOverlayRoot ?? overlayRootRef.current;\n const paper = overlayRoot?.querySelector<HTMLElement>(\".MuiPopover-paper\");\n if (!paper) return;\n\n const zoom = compensation.zoom > 0 ? compensation.zoom : 1;\n const rootRect = rootRef.current?.getBoundingClientRect();\n const rootLeft = rootRect?.left ?? 0;\n const rootTop = rootRect?.top ?? 0;\n const anchorRect = anchorElement?.getBoundingClientRect();\n const anchorX =\n anchorReference === \"anchorPosition\" && anchorPosition\n ? (anchorPosition.left - rootLeft) / zoom\n : anchorRect\n ? (anchorRect.left - rootLeft) / zoom\n : null;\n const anchorY =\n anchorReference === \"anchorPosition\" && anchorPosition\n ? (anchorPosition.top - rootTop) / zoom\n : anchorRect\n ? (anchorRect.top - rootTop) / zoom\n : null;\n if (anchorX == null || anchorY == null) return;\n\n const paperWidth = paper.offsetWidth;\n const paperHeight = paper.offsetHeight;\n const {\n anchorX: originX,\n anchorY: originY,\n transformX,\n transformY,\n } = resolveOrigins(\n anchorOrigin,\n transformOrigin,\n anchorRect ? anchorRect.width / zoom : 0,\n anchorRect ? anchorRect.height / zoom : 0,\n paperWidth,\n paperHeight,\n );\n const margin = typeof marginThreshold === \"number\" ? marginThreshold : 16;\n const maxLeft = Math.max(margin, layoutExtents.layoutWidthPx - margin - paperWidth);\n const maxTop = Math.max(margin, layoutExtents.layoutHeightPx - margin - paperHeight);\n const left = Math.min(maxLeft, Math.max(margin, anchorX + originX - transformX));\n const top = Math.min(maxTop, Math.max(margin, anchorY + originY - transformY));\n\n paper.style.left = `${Math.round(left)}px`;\n paper.style.top = `${Math.round(top)}px`;\n }, [\n anchorElement,\n anchorOrigin,\n anchorPosition,\n anchorReference,\n compensation.active,\n compensation.zoom,\n layoutExtents,\n marginThreshold,\n mountedOverlayRoot,\n open,\n overlayRootRef,\n rootRef,\n transformOrigin,\n ]);\n\n useLayoutEffect(() => {\n if (!open) return;\n\n const firstFrame = window.requestAnimationFrame(() => {\n correctPosition();\n window.requestAnimationFrame(correctPosition);\n });\n window.addEventListener(\"resize\", correctPosition);\n window.addEventListener(\"scroll\", correctPosition, true);\n\n return () => {\n window.cancelAnimationFrame(firstFrame);\n window.removeEventListener(\"resize\", correctPosition);\n window.removeEventListener(\"scroll\", correctPosition, true);\n };\n }, [correctPosition, open]);\n}\n","import type { MenuProps, PopoverProps, PopperProps } from \"@mui/material\";\nimport { Menu, Popover, Popper } from \"@mui/material\";\nimport { useTheme } from \"@mui/material/styles\";\nimport type {\n OffsetsFunction,\n Options as PopperOffsetOptions,\n} from \"@popperjs/core/lib/modifiers/offset\";\nimport { type ReactElement, useMemo, useRef } from \"react\";\nimport { useDisplayScaleCompensation } from \"./DisplayScaleCompensationProvider\";\nimport { useDisplayScaleAnchor } from \"./useDisplayScaleAnchor\";\nimport { useDisplayScalePopoverPosition } from \"./useDisplayScalePopoverPosition\";\n\ntype NativeAnchorProps = {\n anchorEl?: HTMLElement | null;\n};\n\nexport type DisplayScaleMenuProps = Omit<MenuProps, \"anchorEl\"> & NativeAnchorProps;\n\nexport type MuiDisplayScaleMenuAdapter = {\n /** Spread these props onto MUI's native `<Menu />`. */\n menuProps: MenuProps;\n};\n\n/** Non-wrapper display-scale adapter for MUI's native `<Menu />`. */\nexport function useMuiDisplayScaleMenu({\n anchorEl,\n ...props\n}: DisplayScaleMenuProps): MuiDisplayScaleMenuAdapter {\n const displayScaleAnchor = useDisplayScaleAnchor(anchorEl ?? null);\n const { compensation, rootRef } = useDisplayScaleCompensation();\n const theme = useTheme();\n const overlayRootRef = useRef<HTMLDivElement>(null);\n const horizontalOrigin = theme.direction === \"rtl\" ? \"right\" : \"left\";\n const anchorOrigin = props.anchorOrigin ?? {\n vertical: \"bottom\",\n horizontal: horizontalOrigin,\n };\n const transformOrigin = props.transformOrigin ?? {\n vertical: \"top\",\n horizontal: horizontalOrigin,\n };\n const anchorPosition = useMemo(() => {\n if (props.anchorReference !== \"anchorPosition\" || !props.anchorPosition) {\n return props.anchorPosition;\n }\n const zoom = compensation.zoom > 0 ? compensation.zoom : 1;\n return {\n top: props.anchorPosition.top / zoom,\n left: props.anchorPosition.left / zoom,\n };\n }, [compensation.zoom, props.anchorPosition, props.anchorReference]);\n useDisplayScalePopoverPosition({\n open: props.open,\n overlayRootRef,\n anchorElement: anchorEl ?? null,\n anchorReference: props.anchorReference,\n anchorPosition: props.anchorPosition,\n anchorOrigin,\n transformOrigin,\n marginThreshold: props.marginThreshold,\n });\n\n return {\n menuProps: {\n ...props,\n ref: overlayRootRef,\n anchorEl: displayScaleAnchor,\n anchorPosition,\n anchorOrigin,\n container: rootRef.current ?? document.body,\n transformOrigin,\n },\n };\n}\n\n/** MUI Menu wrapper — prefer `useMuiDisplayScaleMenu` when retaining `<Menu />`. */\nexport function DisplayScaleMenu(props: DisplayScaleMenuProps): ReactElement {\n const { menuProps } = useMuiDisplayScaleMenu(props);\n return <Menu {...menuProps} />;\n}\n\nexport type DisplayScalePopoverProps = Omit<PopoverProps, \"anchorEl\"> & NativeAnchorProps;\n\nexport type MuiDisplayScalePopoverAdapter = {\n /** Spread these props onto MUI's native `<Popover />`. */\n popoverProps: PopoverProps;\n};\n\n/** Non-wrapper display-scale adapter for MUI's native `<Popover />`. */\nexport function useMuiDisplayScalePopover({\n anchorEl,\n ...props\n}: DisplayScalePopoverProps): MuiDisplayScalePopoverAdapter {\n const displayScaleAnchor = useDisplayScaleAnchor(anchorEl ?? null);\n const { rootRef } = useDisplayScaleCompensation();\n const overlayRootRef = useRef<HTMLDivElement>(null);\n useDisplayScalePopoverPosition({\n open: props.open,\n overlayRootRef,\n anchorElement: anchorEl ?? null,\n anchorReference: props.anchorReference,\n anchorPosition: props.anchorPosition,\n anchorOrigin: props.anchorOrigin,\n transformOrigin: props.transformOrigin,\n marginThreshold: props.marginThreshold,\n });\n return {\n popoverProps: {\n ...props,\n ref: overlayRootRef,\n anchorEl: displayScaleAnchor,\n container: rootRef.current ?? document.body,\n },\n };\n}\n\n/** MUI Popover wrapper — prefer `useMuiDisplayScalePopover` when retaining `<Popover />`. */\nexport function DisplayScalePopover(props: DisplayScalePopoverProps): ReactElement {\n const { popoverProps } = useMuiDisplayScalePopover(props);\n return <Popover {...popoverProps} />;\n}\n\nexport type DisplayScalePopperProps = Omit<PopperProps, \"anchorEl\"> & NativeAnchorProps;\n\nexport type MuiDisplayScalePopperAdapter = {\n /** Spread these props onto MUI's native `<Popper />`. */\n popperProps: PopperProps;\n};\n\nconst defaultDisplayScalePopperModifiers: PopperProps[\"modifiers\"] = [\n {\n name: \"offset\",\n options: { offset: [0, 8] },\n },\n];\n\nfunction scalePopperOffsets(\n modifiers: PopperProps[\"modifiers\"],\n zoom: number,\n): PopperProps[\"modifiers\"] {\n if (!modifiers || zoom === 1) return modifiers;\n\n return modifiers.map((modifier) => {\n if (modifier.name !== \"offset\") return modifier;\n\n const options = modifier.options as Partial<PopperOffsetOptions> | undefined;\n const rawOffset = options?.offset;\n if (!rawOffset) return modifier;\n\n const scaleValue = (value: number | null | undefined) => (value == null ? value : value / zoom);\n const offset: PopperOffsetOptions[\"offset\"] =\n typeof rawOffset === \"function\"\n ? (args: Parameters<OffsetsFunction>[0]) => {\n const [skidding, distance] = rawOffset(args);\n return [scaleValue(skidding), scaleValue(distance)];\n }\n : [scaleValue(rawOffset[0]), scaleValue(rawOffset[1])];\n\n return { ...modifier, options: { ...options, offset } };\n });\n}\n\n/** Non-wrapper display-scale adapter for MUI's native `<Popper />`. */\nexport function useMuiDisplayScalePopper({\n anchorEl,\n ...props\n}: DisplayScalePopperProps): MuiDisplayScalePopperAdapter {\n const displayScaleAnchor = useDisplayScaleAnchor(anchorEl ?? null);\n const { compensation, rootRef } = useDisplayScaleCompensation();\n const modifiers = useMemo(\n () =>\n scalePopperOffsets(props.modifiers ?? defaultDisplayScalePopperModifiers, compensation.zoom),\n [compensation.zoom, props.modifiers],\n );\n return {\n popperProps: {\n ...props,\n anchorEl: displayScaleAnchor,\n container: rootRef.current ?? document.body,\n modifiers,\n style: { ...props.style, zoom: 1 },\n },\n };\n}\n\n/** MUI Popper wrapper — prefer `useMuiDisplayScalePopper` when retaining `<Popper />`. */\nexport function DisplayScalePopper(props: DisplayScalePopperProps): ReactElement {\n const { popperProps } = useMuiDisplayScalePopper(props);\n return <Popper {...popperProps} />;\n}\n","import type { MenuProps, SelectProps } from \"@mui/material\";\nimport { Select } from \"@mui/material\";\nimport type { ReactElement, SyntheticEvent } from \"react\";\nimport { useCallback, useId, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport { useDisplayScaleCompensation } from \"./DisplayScaleCompensationProvider\";\nimport { useDisplayScalePopoverPosition } from \"./useDisplayScalePopoverPosition\";\n\nexport type MuiDisplayScaleSelectAdapter<Value = unknown> = {\n /** Spread these props onto MUI's native `<Select />`. */\n selectProps: SelectProps<Value>;\n};\n\n/**\n * MUI Select adapter for the display-scale engine. Select creates its Menu\n * internally, so it cannot use DisplayScaleMenu directly.\n */\nexport function useMuiDisplayScaleSelect<Value = unknown>(\n props: SelectProps<Value>,\n): MuiDisplayScaleSelectAdapter<Value> {\n const {\n MenuProps,\n SelectDisplayProps,\n defaultOpen,\n onClose,\n onOpen,\n open: openProp,\n ...selectProps\n } = props;\n const { rootRef } = useDisplayScaleCompensation();\n const menuRootRef = useRef<HTMLDivElement>(null);\n const [menuRootElement, setMenuRootElement] = useState<HTMLDivElement | null>(null);\n const selectId = useId();\n const [anchorElement, setAnchorElement] = useState<HTMLElement | null>(null);\n const [uncontrolledOpen, setUncontrolledOpen] = useState(Boolean(defaultOpen));\n const open = openProp ?? uncontrolledOpen;\n\n const handleMenuRootRef = useCallback((element: HTMLDivElement | null) => {\n menuRootRef.current = element;\n setMenuRootElement(element);\n }, []);\n\n useLayoutEffect(() => {\n if (!open) {\n setAnchorElement(null);\n return;\n }\n setAnchorElement(\n document.querySelector<HTMLElement>(`[data-ep-display-scale-select=\"${selectId}\"]`),\n );\n }, [open, selectId]);\n\n const handleOpen = useCallback(\n (event: SyntheticEvent) => {\n if (openProp === undefined) setUncontrolledOpen(true);\n onOpen?.(event);\n },\n [onOpen, openProp],\n );\n const handleClose = useCallback(\n (event: SyntheticEvent) => {\n if (openProp === undefined) setUncontrolledOpen(false);\n onClose?.(event);\n },\n [onClose, openProp],\n );\n\n useDisplayScalePopoverPosition({\n open,\n overlayRootRef: menuRootRef,\n overlayRoot: menuRootElement,\n anchorElement,\n anchorOrigin: { vertical: \"bottom\", horizontal: \"center\" },\n transformOrigin: { vertical: \"top\", horizontal: \"center\" },\n });\n\n const menuProps = useMemo((): Partial<MenuProps> => {\n const paperSlot = MenuProps?.slotProps?.paper;\n const resolvedPaperSlot = typeof paperSlot === \"function\" ? undefined : paperSlot;\n\n return {\n ...MenuProps,\n container: rootRef.current ?? document.body,\n // Select mounts its internal Menu only after the first open event.\n // Store the mounted root in state so the position hook runs again once\n // MUI has measured and rendered its paper, rather than only on reopen.\n ref: handleMenuRootRef,\n slotProps: {\n ...MenuProps?.slotProps,\n paper: {\n ...resolvedPaperSlot,\n style: { ...resolvedPaperSlot?.style, zoom: 1 },\n },\n },\n };\n }, [MenuProps, handleMenuRootRef, rootRef]);\n\n return {\n selectProps: {\n ...selectProps,\n open,\n onOpen: handleOpen,\n onClose: handleClose,\n MenuProps: menuProps,\n SelectDisplayProps: {\n ...SelectDisplayProps,\n \"data-ep-display-scale-select\": selectId,\n } as SelectProps<Value>[\"SelectDisplayProps\"],\n },\n };\n}\n\n/** MUI Select wrapper — prefer `useMuiDisplayScaleSelect` when retaining `<Select />`. */\nexport function DisplayScaleSelect<Value = unknown>(props: SelectProps<Value>): ReactElement {\n const { selectProps } = useMuiDisplayScaleSelect(props);\n return <Select<Value> {...selectProps} />;\n}\n","import type { Components, Theme } from \"@mui/material/styles\";\nimport { useCallback, useMemo } from \"react\";\nimport { useDisplayScaleCompensation } from \"./DisplayScaleCompensationProvider\";\nimport { createMuiDisplayScaleComponents } from \"./createMuiDisplayScaleComponents\";\n\n/**\n * React adapter for global MUI overlay defaults. Use inside an existing\n * `DisplayScaleProvider`, then pass the result to `createTheme`.\n */\nexport function useMuiDisplayScaleComponents(): Components<Theme> {\n const { compensation, overlayStrategy, rootRef } = useDisplayScaleCompensation();\n\n const getPortalContainer = useCallback((): HTMLElement => {\n if (overlayStrategy === \"document-body\") return document.body;\n return rootRef.current ?? document.body;\n }, [overlayStrategy, rootRef]);\n\n return useMemo(\n () =>\n createMuiDisplayScaleComponents({\n getPortalContainer,\n overlayZoom: overlayStrategy === \"document-body\" ? compensation.zoom : 1,\n }),\n [compensation.zoom, getPortalContainer, overlayStrategy],\n );\n}\n","import { type RefObject, useLayoutEffect } from \"react\";\nimport { useDisplayScaleCompensation } from \"./DisplayScaleCompensationProvider\";\n\n/**\n * Keeps MUI Tabs' indicator in the compensated root's logical coordinate\n * space. MUI normally uses getBoundingClientRect(), whose values are already\n * zoomed and therefore too small for a logical layout container.\n */\nexport function useDisplayScaleTabsIndicator(\n tabsRef: RefObject<HTMLDivElement | null>,\n value: unknown,\n): void {\n const { compensation } = useDisplayScaleCompensation();\n\n useLayoutEffect(() => {\n if (!compensation.active) return;\n // Re-run after the selected Tabs value changes.\n void value;\n\n const updateIndicator = (): void => {\n const tabs = tabsRef.current;\n const indicator = tabs?.querySelector<HTMLElement>(\".MuiTabs-indicator\");\n const selectedTab = tabs?.querySelector<HTMLElement>(\".MuiTab-root.Mui-selected\");\n if (!tabs || !indicator || !selectedTab) return;\n\n const isRtl = getComputedStyle(tabs).direction === \"rtl\";\n const left = selectedTab.offsetLeft;\n const width = selectedTab.offsetWidth;\n\n indicator.style.width = `${width}px`;\n if (isRtl) {\n indicator.style.left = \"auto\";\n indicator.style.right = `${tabs.offsetWidth - left - width}px`;\n } else {\n indicator.style.right = \"auto\";\n indicator.style.left = `${left}px`;\n }\n };\n\n const frame = window.requestAnimationFrame(updateIndicator);\n const observer = new ResizeObserver(updateIndicator);\n if (tabsRef.current) observer.observe(tabsRef.current);\n window.addEventListener(\"resize\", updateIndicator);\n\n return () => {\n window.cancelAnimationFrame(frame);\n observer.disconnect();\n window.removeEventListener(\"resize\", updateIndicator);\n };\n }, [compensation.active, tabsRef, value]);\n}\n","import type { SxProps, Theme } from \"@mui/material/styles\";\n\n/** Fill the compensated app tree — prefer over raw `100vh` inside `#root`. */\nexport const displayScalePageFillSx: SxProps<Theme> = {\n flex: 1,\n minHeight: 0,\n minWidth: 0,\n width: \"100%\",\n height: \"100%\",\n};\n\n/** Full-viewport auth split layout — fills the compensated app root (not `position: fixed`). */\nexport const signinViewportShellSx: SxProps<Theme> = {\n flex: 1,\n minHeight: 0,\n minWidth: 0,\n width: \"100%\",\n height: \"100%\",\n overflow: \"hidden\",\n boxSizing: \"border-box\",\n};\n\n/** 50/50 desktop columns — avoids flex min-content overflow at OS display scale. */\nexport const signinSplitColumnSx: SxProps<Theme> = {\n flex: { xs: \"0 0 auto\", md: \"1 1 0\" },\n width: { xs: \"100%\", md: \"50%\" },\n maxWidth: { xs: \"100%\", md: \"50%\" },\n minWidth: 0,\n boxSizing: \"border-box\",\n};\n"]}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { DEFAULT_DISPLAY_SCALE_THRESHOLDS, clearDisplayScaleBaseline, detectDisplayScale, evaluateDisplayScale, readDisplayScaleBaseline, recalibrateDisplayScale, resetDisplayScaleEngine, shouldApplyOsScaleCompensation, snapToWindowsOsScale, subscribeDisplayScale, writeDisplayScaleBaseline } from './core.js';
2
2
  export { C as ComputeDisplayScaleCompensationOptions, D as DEFAULT_DISPLAY_SCALE_COMPENSATION_CONFIG, a as DetectDisplayScaleOptions, b as DisplayScaleBaseline, c as DisplayScaleCalibration, d as DisplayScaleCompensation, e as DisplayScaleCompensationConfig, f as DisplayScaleConfidence, g as DisplayScaleDetectionMethod, h as DisplayScaleEvaluation, i as DisplayScaleIssue, j as DisplayScaleLayoutExtents, k as DisplayScaleLogicalViewport, l as DisplayScaleOverlayBoundsOptions, m as DisplayScaleScreen, n as DisplayScaleSnapshot, o as DisplayScaleThresholds, p as DisplayScaleViewport, S as SubscribeDisplayScaleOptions, q as applyDisplayScaleCompensationStyles, r as applyDisplayScaleCompensationToDocument, s as clearDisplayScaleCompensationFromDocument, t as clearDisplayScaleCompensationStyles, u as computeDisplayScaleCompensation, v as getDisplayScaleLogicalViewport, w as getDisplayScaleOverlayMaxHeight, x as measureDisplayScaleLayoutExtents, y as primeDisplayScaleCompensation, z as readDisplayScaleViewportSize, A as supportsCssZoom, B as syncDisplayScaleCompensationMetadata, E as toDisplayScaleLogicalPixels } from './displayScaleLogicalViewport-Ikf-DtkQ.js';
3
3
  export { DisplayScaleCompensationProvider, DisplayScaleOverlay, DisplayScaleOverlayStrategy, DisplayScaleProvider, DisplayScaleProviderProps, DisplayScaleVirtualAnchor, useDisplayScale, useDisplayScaleAnchor, useDisplayScaleCompensation, useDisplayScaleOverlay, useDisplayScalePortalContainer, useLogicalMaxWidth, useLogicalMinWidth } from './react.js';
4
- export { DisplayScaleMenu, DisplayScaleMenuProps, DisplayScalePopover, DisplayScalePopoverProps, DisplayScalePopper, DisplayScalePopperProps, DisplayScaleSelect, createMuiDisplayScaleComponents, displayScalePageFillSx, signinSplitColumnSx, signinViewportShellSx, useDisplayScalePopoverPosition, useDisplayScaleTabsIndicator } from './mui.js';
4
+ export { DisplayScaleMenu, DisplayScaleMenuProps, DisplayScalePopover, DisplayScalePopoverProps, DisplayScalePopper, DisplayScalePopperProps, DisplayScaleSelect, MuiDisplayScaleMenuAdapter, MuiDisplayScalePopoverAdapter, MuiDisplayScalePopperAdapter, MuiDisplayScaleSelectAdapter, createMuiDisplayScaleComponents, displayScalePageFillSx, signinSplitColumnSx, signinViewportShellSx, useDisplayScalePopoverPosition, useDisplayScaleTabsIndicator, useMuiDisplayScaleComponents, useMuiDisplayScaleMenu, useMuiDisplayScalePopover, useMuiDisplayScalePopper, useMuiDisplayScaleSelect } from './mui.js';
5
5
  import 'react';
6
6
  import '@mui/material/styles';
7
7
  import '@mui/material';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import './chunk-SLNYXBHU.js';
2
2
  export { useDisplayScaleOverlay, useLogicalMaxWidth, useLogicalMinWidth } from './chunk-64EBQUAW.js';
3
3
  export { getDisplayScaleLogicalViewport, getDisplayScaleOverlayMaxHeight, toDisplayScaleLogicalPixels } from './chunk-U3ZWHUJG.js';
4
- export { DisplayScaleMenu, DisplayScalePopover, DisplayScalePopper, DisplayScaleSelect, createMuiDisplayScaleComponents, displayScalePageFillSx, signinSplitColumnSx, signinViewportShellSx, useDisplayScalePopoverPosition, useDisplayScaleTabsIndicator } from './chunk-OQRISU2D.js';
4
+ export { DisplayScaleMenu, DisplayScalePopover, DisplayScalePopper, DisplayScaleSelect, createMuiDisplayScaleComponents, displayScalePageFillSx, signinSplitColumnSx, signinViewportShellSx, useDisplayScalePopoverPosition, useDisplayScaleTabsIndicator, useMuiDisplayScaleComponents, useMuiDisplayScaleMenu, useMuiDisplayScalePopover, useMuiDisplayScalePopper, useMuiDisplayScaleSelect } from './chunk-47O3TJUJ.js';
5
5
  export { DisplayScaleCompensationProvider, DisplayScaleProvider, useDisplayScale, useDisplayScaleAnchor, useDisplayScaleCompensation, useDisplayScalePortalContainer } from './chunk-F5AFPMQC.js';
6
6
  export { DEFAULT_DISPLAY_SCALE_COMPENSATION_CONFIG, DEFAULT_DISPLAY_SCALE_THRESHOLDS, applyDisplayScaleCompensationStyles, applyDisplayScaleCompensationToDocument, clearDisplayScaleBaseline, clearDisplayScaleCompensationFromDocument, clearDisplayScaleCompensationStyles, computeDisplayScaleCompensation, detectDisplayScale, evaluateDisplayScale, measureDisplayScaleLayoutExtents, primeDisplayScaleCompensation, readDisplayScaleBaseline, readDisplayScaleViewportSize, recalibrateDisplayScale, resetDisplayScaleEngine, shouldApplyOsScaleCompensation, snapToWindowsOsScale, subscribeDisplayScale, supportsCssZoom, syncDisplayScaleCompensationMetadata, writeDisplayScaleBaseline } from './chunk-VWJNHWMD.js';
7
7
  //# sourceMappingURL=index.js.map
package/dist/mui.d.ts CHANGED
@@ -23,24 +23,60 @@ type NativeAnchorProps = {
23
23
  anchorEl?: HTMLElement | null;
24
24
  };
25
25
  type DisplayScaleMenuProps = Omit<MenuProps, "anchorEl"> & NativeAnchorProps;
26
- /** MUI Menu with an anchor adapted to the active portal strategy. */
27
- declare function DisplayScaleMenu({ anchorEl, ...props }: DisplayScaleMenuProps): ReactElement;
26
+ type MuiDisplayScaleMenuAdapter = {
27
+ /** Spread these props onto MUI's native `<Menu />`. */
28
+ menuProps: MenuProps;
29
+ };
30
+ /** Non-wrapper display-scale adapter for MUI's native `<Menu />`. */
31
+ declare function useMuiDisplayScaleMenu({ anchorEl, ...props }: DisplayScaleMenuProps): MuiDisplayScaleMenuAdapter;
32
+ /** MUI Menu wrapper — prefer `useMuiDisplayScaleMenu` when retaining `<Menu />`. */
33
+ declare function DisplayScaleMenu(props: DisplayScaleMenuProps): ReactElement;
28
34
  type DisplayScalePopoverProps = Omit<PopoverProps, "anchorEl"> & NativeAnchorProps;
29
- /** MUI Popover with an anchor adapted to the active portal strategy. */
30
- declare function DisplayScalePopover({ anchorEl, ...props }: DisplayScalePopoverProps): ReactElement;
35
+ type MuiDisplayScalePopoverAdapter = {
36
+ /** Spread these props onto MUI's native `<Popover />`. */
37
+ popoverProps: PopoverProps;
38
+ };
39
+ /** Non-wrapper display-scale adapter for MUI's native `<Popover />`. */
40
+ declare function useMuiDisplayScalePopover({ anchorEl, ...props }: DisplayScalePopoverProps): MuiDisplayScalePopoverAdapter;
41
+ /** MUI Popover wrapper — prefer `useMuiDisplayScalePopover` when retaining `<Popover />`. */
42
+ declare function DisplayScalePopover(props: DisplayScalePopoverProps): ReactElement;
31
43
  type DisplayScalePopperProps = Omit<PopperProps, "anchorEl"> & NativeAnchorProps;
32
- /** MUI Popper with an anchor adapted to the active portal strategy. */
33
- declare function DisplayScalePopper({ anchorEl, ...props }: DisplayScalePopperProps): ReactElement;
44
+ type MuiDisplayScalePopperAdapter = {
45
+ /** Spread these props onto MUI's native `<Popper />`. */
46
+ popperProps: PopperProps;
47
+ };
48
+ /** Non-wrapper display-scale adapter for MUI's native `<Popper />`. */
49
+ declare function useMuiDisplayScalePopper({ anchorEl, ...props }: DisplayScalePopperProps): MuiDisplayScalePopperAdapter;
50
+ /** MUI Popper wrapper — prefer `useMuiDisplayScalePopper` when retaining `<Popper />`. */
51
+ declare function DisplayScalePopper(props: DisplayScalePopperProps): ReactElement;
34
52
 
53
+ type MuiDisplayScaleSelectAdapter<Value = unknown> = {
54
+ /** Spread these props onto MUI's native `<Select />`. */
55
+ selectProps: SelectProps<Value>;
56
+ };
35
57
  /**
36
58
  * MUI Select adapter for the display-scale engine. Select creates its Menu
37
59
  * internally, so it cannot use DisplayScaleMenu directly.
38
60
  */
61
+ declare function useMuiDisplayScaleSelect<Value = unknown>(props: SelectProps<Value>): MuiDisplayScaleSelectAdapter<Value>;
62
+ /** MUI Select wrapper — prefer `useMuiDisplayScaleSelect` when retaining `<Select />`. */
39
63
  declare function DisplayScaleSelect<Value = unknown>(props: SelectProps<Value>): ReactElement;
40
64
 
65
+ /**
66
+ * React adapter for global MUI overlay defaults. Use inside an existing
67
+ * `DisplayScaleProvider`, then pass the result to `createTheme`.
68
+ */
69
+ declare function useMuiDisplayScaleComponents(): Components<Theme>;
70
+
41
71
  type DisplayScalePopoverPositionOptions = {
42
72
  open: boolean;
43
73
  overlayRootRef: RefObject<HTMLDivElement | null>;
74
+ /**
75
+ * The mounted overlay root. A ref assignment alone does not trigger a
76
+ * render, so callers with lazily-mounted overlays (notably MUI Select)
77
+ * provide this state value to schedule the first correction after mount.
78
+ */
79
+ overlayRoot?: HTMLDivElement | null;
44
80
  anchorElement: HTMLElement | null;
45
81
  anchorReference?: PopoverProps["anchorReference"];
46
82
  anchorPosition?: PopoverProps["anchorPosition"];
@@ -53,7 +89,7 @@ type DisplayScalePopoverPositionOptions = {
53
89
  * owns focus, transition and anchor semantics; this writes the final logical
54
90
  * top/left inside the compensated portal root.
55
91
  */
56
- declare function useDisplayScalePopoverPosition({ open, overlayRootRef, anchorElement, anchorReference, anchorPosition, anchorOrigin, transformOrigin, marginThreshold, }: DisplayScalePopoverPositionOptions): void;
92
+ declare function useDisplayScalePopoverPosition({ open, overlayRootRef, overlayRoot: mountedOverlayRoot, anchorElement, anchorReference, anchorPosition, anchorOrigin, transformOrigin, marginThreshold, }: DisplayScalePopoverPositionOptions): void;
57
93
 
58
94
  /**
59
95
  * Keeps MUI Tabs' indicator in the compensated root's logical coordinate
@@ -69,4 +105,4 @@ declare const signinViewportShellSx: SxProps<Theme>;
69
105
  /** 50/50 desktop columns — avoids flex min-content overflow at OS display scale. */
70
106
  declare const signinSplitColumnSx: SxProps<Theme>;
71
107
 
72
- export { DisplayScaleMenu, type DisplayScaleMenuProps, DisplayScalePopover, type DisplayScalePopoverProps, DisplayScalePopper, type DisplayScalePopperProps, DisplayScaleSelect, createMuiDisplayScaleComponents, displayScalePageFillSx, signinSplitColumnSx, signinViewportShellSx, useDisplayScalePopoverPosition, useDisplayScaleTabsIndicator };
108
+ export { DisplayScaleMenu, type DisplayScaleMenuProps, DisplayScalePopover, type DisplayScalePopoverProps, DisplayScalePopper, type DisplayScalePopperProps, DisplayScaleSelect, type MuiDisplayScaleMenuAdapter, type MuiDisplayScalePopoverAdapter, type MuiDisplayScalePopperAdapter, type MuiDisplayScaleSelectAdapter, createMuiDisplayScaleComponents, displayScalePageFillSx, signinSplitColumnSx, signinViewportShellSx, useDisplayScalePopoverPosition, useDisplayScaleTabsIndicator, useMuiDisplayScaleComponents, useMuiDisplayScaleMenu, useMuiDisplayScalePopover, useMuiDisplayScalePopper, useMuiDisplayScaleSelect };
package/dist/mui.js CHANGED
@@ -1,4 +1,4 @@
1
- export { DisplayScaleMenu, DisplayScalePopover, DisplayScalePopper, DisplayScaleSelect, createMuiDisplayScaleComponents, displayScalePageFillSx, signinSplitColumnSx, signinViewportShellSx, useDisplayScalePopoverPosition, useDisplayScaleTabsIndicator } from './chunk-OQRISU2D.js';
1
+ export { DisplayScaleMenu, DisplayScalePopover, DisplayScalePopper, DisplayScaleSelect, createMuiDisplayScaleComponents, displayScalePageFillSx, signinSplitColumnSx, signinViewportShellSx, useDisplayScalePopoverPosition, useDisplayScaleTabsIndicator, useMuiDisplayScaleComponents, useMuiDisplayScaleMenu, useMuiDisplayScalePopover, useMuiDisplayScalePopper, useMuiDisplayScaleSelect } from './chunk-47O3TJUJ.js';
2
2
  import './chunk-F5AFPMQC.js';
3
3
  import './chunk-VWJNHWMD.js';
4
4
  //# sourceMappingURL=mui.js.map
@@ -1,208 +1,238 @@
1
- # Display-scale engine
1
+ # Display Scale Engine guide
2
2
 
3
- ## Purpose
3
+ `react-display-scale-engine` is an optional visual-density policy for React applications.
4
+ It targets Windows desktop users whose operating-system display scale is above 100%—for example
5
+ 125% or 150%—when a product requires roughly the same dashboard density as Windows 100%.
4
6
 
5
- The display-scale engine provides an optional **visual-density lock** for Windows desktop browsers.
6
- When Windows display scale is 125%, 150%, or another supported tier, the app can render toward a
7
- 100% visual-density target while retaining an expanded logical layout area.
7
+ It is not a generic replacement for responsive design and it does not force browser zoom.
8
8
 
9
- This is a product policy, not a browser requirement. A conventional responsive website should not
10
- enable this behavior unless matching 100% visual density is an explicit requirement.
9
+ ## What it solves
11
10
 
12
- ## Scope and limitations
11
+ At Windows 150%, a browser usually reports a higher `devicePixelRatio`. A conventional web app
12
+ then renders typography, spacing, and MUI components visibly larger. That is normally correct and
13
+ accessible browser behavior, but dense desktop dashboards sometimes require a consistent visual
14
+ density across managed workstations.
13
15
 
14
- - The engine targets Windows desktop and is disabled for coarse-pointer and compact viewports.
15
- - Browser page zoom and OS display scale are not perfectly distinguishable in a normal browser.
16
- `window.devicePixelRatio` is an effective value influenced by both.
17
- - The session baseline assumes browser page zoom is 100% when first stored. If this is not true,
18
- call `recalibrateDisplayScale()` after the user resets page zoom to 100%.
19
- - Native shells such as Electron can provide a more authoritative OS scale factor. A pure web app
20
- cannot.
16
+ Applying CSS `zoom` to an app root corrects that density, but creates a second coordinate system.
17
+ Anchored overlays such as MUI `Menu`, `Popover`, `Popper`, `Select`, `Dialog`, and temporary
18
+ `Drawer` can drift, clip, or calculate a wrong viewport height. This engine manages the root
19
+ compensation and the overlay geometry together.
21
20
 
22
- ## Architecture
21
+ ## When it has an effect
22
+
23
+ | Environment | Engine result |
24
+ | --- | --- |
25
+ | Windows desktop at 100% display scale | Inactive: no compensation wrapper and no visual change. |
26
+ | Windows desktop at 125%, 150%, and supported higher tiers | Active: the app root is compensated toward the configured target density. |
27
+ | macOS / Linux | Inactive by default. |
28
+ | Touch/coarse-pointer or compact viewport | Inactive by default. |
29
+ | Browser zoom differs from the recorded baseline | Best-effort detection; use calibration for a strict policy. |
30
+
31
+ When inactive, `DisplayScaleProvider` supplies context to hooks only. It does not insert a layout
32
+ container or apply visual scaling, so a normal display scale preserves the host app's layout.
33
+
34
+ ## Engine model
23
35
 
24
36
  ```text
25
- display-scale core
26
- detection + calibration + compensation policy
27
- |
28
- v
29
- React provider
30
- compensated root (visible portal host) + logical viewport context
31
- |
32
- +-- clipped app-content layer
33
- +-- document-body MUI portal host
34
- +-- overlay adapter
37
+ Browser metrics + session baseline
38
+
39
+
40
+ /core detection
41
+ policy + compensation
42
+
43
+
44
+ /react DisplayScaleProvider
45
+ active: compensated root + logical viewport
46
+ inactive: context only
47
+
48
+
49
+ /mui integration
50
+ corrected anchor + portal + final overlay placement
35
51
  ```
36
52
 
37
- The code is intentionally divided so that the core calculation modules do not depend on React or
38
- MUI. The React-facing API is exported from `@/shared/display-scale` today and can be moved into
39
- an internal package without changing feature call sites.
53
+ A web browser cannot perfectly separate OS display scale from browser page zoom. The engine uses
54
+ `devicePixelRatio`, `visualViewport`, and a session baseline to form a best-effort estimate. For a
55
+ strict product policy, provide an explicit action after users reset browser zoom to 100%.
40
56
 
41
- ## Public API
57
+ ## Best-practice quick start
42
58
 
43
- ```tsx
44
- import {
45
- DisplayScaleProvider,
46
- useDisplayScale,
47
- useDisplayScaleOverlay,
48
- } from "@/shared/display-scale";
49
-
50
- <DisplayScaleProvider
51
- config={{
52
- windowsOnly: true,
53
- targetOsScale: 1,
54
- minOsScaleToCompensate: 1.05,
55
- requireExplicitCalibration: true,
56
- }}
57
- >
58
- <App />
59
- </DisplayScaleProvider>;
59
+ Use the subpath imports below. The root package export exists only for compatibility.
60
+
61
+ ### 1. Install
62
+
63
+ ```bash
64
+ npm install react-display-scale-engine
60
65
  ```
61
66
 
62
- ### Core utilities
63
-
64
- - `detectDisplayScale()`: returns current DPR, estimated OS scale, estimated browser zoom and
65
- confidence.
66
- - `recalibrateDisplayScale()`: replaces the current session baseline when browser zoom is known
67
- to be 100% and marks it as explicit calibration.
68
- - `computeDisplayScaleCompensation()`: resolves whether compensation is active and the target
69
- root zoom.
70
- - `getDisplayScaleLogicalViewport()`: returns browser viewport and compensated-root viewport.
71
- - `toDisplayScaleLogicalPixels()`: converts visible CSS-pixel geometry to root coordinates.
72
-
73
- ### React utilities
74
-
75
- - `DisplayScaleProvider`: package-friendly name for the existing root provider.
76
- - `useDisplayScale()`: subscribes to a scale snapshot.
77
- - `useDisplayScaleCompensation()`: exposes the root and resolved compensation.
78
- - `useDisplayScaleOverlay()`: obtains the portal container, logical viewport and overlay bounds
79
- helpers, including `toOverlayContentPixels()` for a custom density-zoomed dimension.
80
- - `useDisplayScaleAnchor()`: converts a native anchor to a MUI-compatible virtual anchor in the
81
- compensated portal coordinate space.
82
- - `useLogicalMinWidth()` / `useLogicalMaxWidth()`: evaluate app-shell breakpoints against the
83
- compensated root width.
84
- - `createMuiDisplayScaleComponents()`: optional MUI adapter that centralizes portal defaults for
85
- Dialog, Menu/Popover and Popper.
86
-
87
- ## Overlay integration
88
-
89
- MUI overlays must share the same coordinate system as their anchor. The theme adapter keeps legacy
90
- MUI portals on `document.body` with density correction. For anchored `Menu`, `Popover` and
91
- `Popper`, use the provided wrappers: they move only that overlay into the compensated root and
92
- adapt the anchor rectangle relative to the engine root centrally. This avoids the `top`/`left`
93
- drift caused by applying CSS zoom to an absolutely positioned body portal. The adapter also handles temporary MUI Drawers: the
94
- portal host, density zoom, and inverse viewport dimension are set globally. Permanent and
95
- persistent Drawers remain inside the compensated root and are deliberately untouched.
96
-
97
- The compensated root is intentionally a visible portal host. The app content sits in a clipped
98
- child layer, so a portalled overlay is bounded by the real viewport rather than an invisible
99
- `overflow: hidden` root rectangle.
100
-
101
-
102
- Use `overlayStrategy="compensated-root"` only for an app that has verified every portal and
103
- anchor in logical root coordinates. `document-body` is preferred for CSS zoom roots.
104
-
105
- Do not divide a Popper or Menu height by `compensation.zoom` in feature code. Use the overlay
106
- adapter instead; it uses visible pixels for placement and converts only an overlay content
107
- dimension when its density zoom requires it:
67
+ ### 2. Prime once before React renders
68
+
69
+ In the application entry file, before `createRoot`:
108
70
 
109
71
  ```tsx
110
- const { getMaxHeightBelowAnchor } = useDisplayScaleOverlay();
72
+ import { primeDisplayScaleCompensation } from "react-display-scale-engine/core";
111
73
 
112
- const maxHeight = getMaxHeightBelowAnchor(anchorElement, {
113
- offset: 8,
114
- viewportInset: 16,
115
- });
74
+ primeDisplayScaleCompensation();
75
+ ```
76
+
77
+ ### 3. Add exactly one provider at the application root
78
+
79
+ ```tsx
80
+ import { DisplayScaleProvider } from "react-display-scale-engine/react";
81
+
82
+ export function App() {
83
+ return (
84
+ <DisplayScaleProvider>
85
+ <AppContent />
86
+ </DisplayScaleProvider>
87
+ );
88
+ }
116
89
  ```
117
90
 
118
- For MUI `Menu`, `Popover` and `Popper`, adapt the anchor before passing it to the component:
91
+ Do not add a provider around individual features, dialogs, or pages.
92
+
93
+ ### 4. Add the MUI theme adapter once
94
+
95
+ Call this hook inside `DisplayScaleProvider`, then merge its result into the existing MUI theme.
119
96
 
120
97
  ```tsx
121
- const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
122
- const displayScaleAnchor = useDisplayScaleAnchor(anchorEl);
98
+ import { CssBaseline, ThemeProvider, createTheme } from "@mui/material";
99
+ import { useMemo } from "react";
100
+ import { useMuiDisplayScaleComponents } from "react-display-scale-engine/mui";
101
+
102
+ function AppThemeProvider({ children }: { children: React.ReactNode }) {
103
+ const components = useMuiDisplayScaleComponents();
104
+ const theme = useMemo(() => createTheme({ components }), [components]);
105
+
106
+ return (
107
+ <ThemeProvider theme={theme}>
108
+ <CssBaseline />
109
+ {children}
110
+ </ThemeProvider>
111
+ );
112
+ }
113
+ ```
114
+
115
+ For an existing theme, use `createTheme(baseTheme, { components })`; do not create a second theme
116
+ provider only for this engine.
123
117
 
124
- <Menu anchorEl={displayScaleAnchor} open={Boolean(anchorEl)} />;
118
+ ### 5. Use hook adapters for anchored MUI components
119
+
120
+ This is the recommended API for new code. Your app retains native MUI components and supplies
121
+ engine-managed props through the matching hook.
122
+
123
+ ```tsx
124
+ import { Menu, MenuItem } from "@mui/material";
125
+ import { useState } from "react";
126
+ import { useMuiDisplayScaleMenu } from "react-display-scale-engine/mui";
127
+
128
+ function ActionsMenu() {
129
+ const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
130
+ const open = Boolean(anchorEl);
131
+ const { menuProps } = useMuiDisplayScaleMenu({ anchorEl, open });
132
+
133
+ return (
134
+ <Menu {...menuProps} onClose={() => setAnchorEl(null)}>
135
+ <MenuItem onClick={() => setAnchorEl(null)}>Action</MenuItem>
136
+ </Menu>
137
+ );
138
+ }
125
139
  ```
126
140
 
127
- For new code, prefer the MUI wrappers so the conversion cannot be skipped:
141
+ | Native MUI component | Engine hook |
142
+ | --- | --- |
143
+ | `Menu` | `useMuiDisplayScaleMenu` |
144
+ | `Popover` | `useMuiDisplayScalePopover` |
145
+ | `Popper` | `useMuiDisplayScalePopper` |
146
+ | `Select` | `useMuiDisplayScaleSelect` |
147
+
148
+ The hook owns display-scale-specific props only: corrected anchor geometry, portal container,
149
+ placement correction, zoom-aware modifiers, and internal overlay refs. Keep business state,
150
+ children, item rendering, `onClose`, and `onChange` in the application.
151
+
152
+ ## Wrapper API
153
+
154
+ For a fast migration, wrappers remain available:
128
155
 
129
156
  ```tsx
130
- import { DisplayScaleMenu, DisplayScalePopover, DisplayScalePopper } from "@/shared/display-scale";
157
+ import { DisplayScaleMenu } from "react-display-scale-engine/mui";
131
158
 
132
- <DisplayScaleMenu anchorEl={anchorEl} open={Boolean(anchorEl)} />;
159
+ <DisplayScaleMenu anchorEl={anchorEl} open={open} onClose={handleClose}>
160
+ <MenuItem>Action</MenuItem>
161
+ </DisplayScaleMenu>;
133
162
  ```
134
163
 
135
- The wrappers accept a native `HTMLElement` anchor, and also support a Menu using
136
- `anchorReference="anchorPosition"`. Use the lower-level hook only for components that require a
137
- custom virtual anchor.
164
+ `DisplayScaleMenu`, `DisplayScalePopover`, `DisplayScalePopper`, and `DisplayScaleSelect` use the
165
+ same hook adapters internally. Hook adapters and wrappers therefore have equivalent placement and
166
+ scaling behavior. Prefer hooks for new code; use wrappers when migrating a feature quickly.
167
+
168
+ ## Custom overlay content
169
+
170
+ For a custom panel or scrollable list, use the logical overlay helper instead of dividing values
171
+ by `zoom` manually:
138
172
 
139
- For engine wrappers, MUI still performs its initial placement. The engine then replaces only the
140
- final collision clamp with a logical-viewport calculation, preserving MUI focus handling,
141
- transitions and origin semantics. `Popper` is intentionally separate because it uses Popper.js
142
- modifiers rather than MUI Popover's collision implementation.
173
+ ```tsx
174
+ import { useDisplayScaleOverlay } from "react-display-scale-engine/react";
175
+
176
+ const { getMaxHeightBelowAnchor } = useDisplayScaleOverlay();
177
+ const maxHeight = getMaxHeightBelowAnchor(anchorElement, {
178
+ offset: 8,
179
+ viewportInset: 16,
180
+ });
181
+ ```
143
182
 
144
- For custom positioning:
183
+ Apply `maxHeight` to the scrollable surface, usually with `overflowY: "auto"` and `minHeight: 0`.
145
184
 
146
- 1. Read the anchor rectangle with `getBoundingClientRect()`.
147
- 2. Let `useDisplayScaleOverlay()` convert geometry only when the active portal strategy needs it.
148
- 3. Keep the anchor, portal host and overlay in the same strategy.
149
- 4. Give scrollable content `minHeight: 0` and put `overflowY: "auto"` on the list surface.
150
- 5. Prefer Popper modifiers (`offset`, `flip`, `preventOverflow`) over hard-coded
151
- `position: fixed`, `top` or `right`.
185
+ ## Responsive behavior
152
186
 
153
- ## Responsive guidance
187
+ Density and responsive layout are different concerns. Browser media queries still use the raw CSS
188
+ viewport. When an application-shell breakpoint must stay stable between Windows 100% and 150%, use
189
+ the logical breakpoint hooks:
154
190
 
155
- Visual density and responsive behavior are separate:
191
+ ```tsx
192
+ import { useLogicalMinWidth } from "react-display-scale-engine/react";
156
193
 
157
- - **Density** controls perceived font, component and spacing size.
158
- - **Responsive behavior** controls breakpoints, columns and navigation variants.
159
- - **Overlay geometry** controls portals and viewport bounds.
194
+ const isDesktop = useLogicalMinWidth(900);
195
+ ```
160
196
 
161
- Browser-level media queries still observe the browser CSS viewport. Features that must preserve the
162
- same breakpoint behavior at 100% and 150% should use container queries within the compensated root
163
- or `useLogicalMinWidth()` / `useLogicalMaxWidth()` for JavaScript app-shell decisions. Do not
164
- assume root `zoom` changes `window.matchMedia()`.
197
+ Do not use the engine to make a mobile layout behave as desktop.
165
198
 
166
- ## Migration plan for another React app
199
+ ## Calibration and diagnostics
167
200
 
168
- 1. Copy or package only `displayScaleCompensation`, `detectDisplayScale`, baseline storage,
169
- types and logical viewport utilities as the core layer.
170
- 2. Add `DisplayScaleProvider` around the new app shell.
171
- 3. Configure the app's portal system with `createMuiDisplayScaleComponents()`.
172
- 4. Migrate overlays first, then fixed-position pages, then breakpoint-sensitive layouts.
173
- 5. For strict density lock, set `requireExplicitCalibration: true` and call
174
- `recalibrateDisplayScale()` after the app confirms that browser zoom is 100%.
201
+ The default automatic baseline is suitable for most applications. For strict calibration, call this
202
+ only when browser zoom is known to be 100%:
175
203
 
176
- Suggested future package structure:
204
+ ```tsx
205
+ import { recalibrateDisplayScale } from "react-display-scale-engine/core";
177
206
 
178
- ```text
179
- @envidi/display-scale-core
180
- @envidi/display-scale-react
181
- @envidi/display-scale-mui
207
+ recalibrateDisplayScale();
182
208
  ```
183
209
 
184
- The MUI package should remain optional; core must not import MUI.
210
+ `useDisplayScale()` exposes the current estimate, confidence, browser zoom, and OS scale for a
211
+ diagnostics or support screen. Avoid changing feature layout directly from raw scale values.
185
212
 
186
- ## Verification matrix
213
+ ## Rules that prevent overlay bugs
187
214
 
188
- Run the following with browser page zoom at 100%, then repeat at 80%, 125% and 150%:
215
+ 1. Use one provider for one application root.
216
+ 2. Import by responsibility: `/core`, `/react`, and `/mui`.
217
+ 3. Use an engine hook or wrapper for every anchored MUI overlay; do not manually divide coordinates.
218
+ 4. Keep anchor, portal, and overlay under the same engine strategy.
219
+ 5. Avoid hard-coded `position: fixed`, `top`, or `left` as an overlay repair.
220
+ 6. Test Windows 100%, 125%, and 150% with browser zoom at 100%.
189
221
 
190
- | Windows display scale | Required result |
191
- | --- | --- |
192
- | 100% | No compensation; baseline visual layout |
193
- | 125% | Target visual density; no overlay clipping |
194
- | 150% | Target visual density; no overlay drift or viewport overflow |
195
- | Multiple monitors | Re-read scale after the window moves; recalibrate if confidence is insufficient |
196
-
197
- Check at minimum: app shell, sidebar animation, dialogs, menus, Popper lists, fixed controls,
198
- virtualized or scrollable lists, responsive grids and sign-in.
199
-
200
- ## Current known work
201
-
202
- - Factory selector uses `useDisplayScaleOverlay()` for viewport-bounded list height.
203
- - MUI portals are globally routed to `document.body` by default; the theme adapter applies the
204
- matching overlay density there, avoiding coordinate drift from the compensated root.
205
- - The app shell and sidebar profile already use logical breakpoints. Dashboard grids and other
206
- feature-level breakpoint consumers remain a staged migration.
207
- - The engine currently uses best-effort browser-zoom detection; explicit calibration UX remains a
208
- follow-up before enabling strict density lock across other apps.
222
+ ## Compatibility and release checks
223
+
224
+ - `/core` is framework-independent.
225
+ - `/react` requires React 18 or later.
226
+ - `/mui` is tested with MUI 7 (`>=7 <8`) and Emotion peers.
227
+ - The root export is a compatibility entry; prefer subpaths for new application code.
228
+
229
+ Before publishing, run:
230
+
231
+ ```bash
232
+ npm run check
233
+ npm run release:dry-run
234
+ ```
235
+
236
+ `npm run check` type-checks, builds distributable artifacts, and runs a DOM consumer test. It
237
+ renders root and subpath APIs, wrappers, and native MUI components receiving hook adapter props.
238
+ This catches React Context duplication and normal-scale wrapper regressions before publishing.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-display-scale-engine",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "React and Material UI utilities for consistent Windows display-scale rendering.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -40,7 +40,8 @@
40
40
  "scripts": {
41
41
  "build": "tsup",
42
42
  "typecheck": "tsc --noEmit",
43
- "check": "npm run typecheck && npm run build",
43
+ "check": "npm run typecheck && npm run build && npm run test:consumer",
44
+ "test:consumer": "node ./scripts/verify-consumer-runtime.mjs",
44
45
  "pack:check": "npm pack --dry-run",
45
46
  "release:check": "node ./bin/rds-engine.mjs check",
46
47
  "release:dry-run": "node ./bin/rds-engine.mjs publish --dry-run",
@@ -58,7 +59,7 @@
58
59
  "peerDependencies": {
59
60
  "@emotion/react": ">=11.0.0",
60
61
  "@emotion/styled": ">=11.0.0",
61
- "@mui/material": ">=5.0.0",
62
+ "@mui/material": ">=7.0.0 <8.0.0",
62
63
  "@popperjs/core": ">=2.0.0",
63
64
  "react": ">=18.0.0",
64
65
  "react-dom": ">=18.0.0"
@@ -71,6 +72,7 @@
71
72
  "@types/node": "^22.10.0",
72
73
  "@types/react": "^19.0.0",
73
74
  "@types/react-dom": "^19.0.0",
75
+ "jsdom": "^26.1.0",
74
76
  "react": "^19.0.0",
75
77
  "react-dom": "^19.0.0",
76
78
  "tsup": "^8.5.1",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/createMuiDisplayScaleComponents.ts","../src/useDisplayScalePopoverPosition.ts","../src/DisplayScaleMuiOverlays.tsx","../src/DisplayScaleSelect.tsx","../src/useDisplayScaleTabsIndicator.ts","../src/displayScaleLayoutTokens.ts"],"names":["useRef","useLayoutEffect","useCallback","useMemo","jsx"],"mappings":"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","file":"chunk-OQRISU2D.js","sourcesContent":["import type { Components, Theme } from \"@mui/material/styles\";\n\ntype PortalContainerGetter = () => HTMLElement;\n\ntype MuiDisplayScaleComponentsOptions = {\n getPortalContainer: PortalContainerGetter;\n /**\n * Overlay content is portalled outside the zoomed app root, so it needs the\n * same density correction independently.\n */\n overlayZoom?: number;\n};\n\n/**\n * MUI adapter for a compensated app root.\n *\n * Centralizing these defaults gives every MUI overlay the portal strategy and\n * density correction chosen by the display-scale engine.\n */\nexport function createMuiDisplayScaleComponents({\n getPortalContainer,\n overlayZoom = 1,\n}: MuiDisplayScaleComponentsOptions): Components<Theme> {\n const zoom = overlayZoom > 0 ? overlayZoom : 1;\n const resolveContainerZoom = (container: unknown): number => {\n if (typeof document === \"undefined\") return zoom;\n const resolvedContainer =\n typeof container === \"function\" ? (container as PortalContainerGetter)() : container;\n // MUI does not always expose the resolved portal node through ownerState.\n // Missing means the component will use its default (document.body).\n return resolvedContainer == null || resolvedContainer === document.body ? zoom : 1;\n };\n\n return {\n MuiModal: {\n defaultProps: {\n container: getPortalContainer,\n },\n },\n MuiDialog: {\n defaultProps: {\n container: getPortalContainer,\n },\n styleOverrides: {\n paper: ({ ownerState }) => ({ zoom: resolveContainerZoom(ownerState?.container) }),\n },\n },\n MuiPopover: {\n defaultProps: {\n container: getPortalContainer,\n },\n styleOverrides: {\n paper: ({ ownerState }) => ({ zoom: resolveContainerZoom(ownerState?.container) }),\n },\n },\n MuiPopper: {\n defaultProps: {\n container: getPortalContainer,\n },\n styleOverrides: {\n root: ({ ownerState }) => ({ zoom: resolveContainerZoom(ownerState?.container) }),\n },\n },\n MuiDrawer: {\n defaultProps: {\n // Drawer inherits Modal props. Supplying the portal host here (rather\n // than only through MuiModal) also covers Drawer internals directly.\n container: getPortalContainer,\n },\n styleOverrides: {\n paper: ({ ownerState }) => {\n // Permanent/persistent drawers live inside the compensated root and\n // must not be zoomed a second time. Temporary drawers are portalled\n // to document.body, so their paper needs the overlay density zoom.\n if (ownerState.variant !== \"temporary\") return {};\n\n const isVertical = ownerState.anchor === \"left\" || ownerState.anchor === \"right\";\n return isVertical\n ? {\n zoom,\n height: \"var(--ep-display-scale-overlay-viewport-height, 100dvh)\",\n maxHeight: \"var(--ep-display-scale-overlay-viewport-height, 100dvh)\",\n }\n : {\n zoom,\n width: \"var(--ep-display-scale-overlay-viewport-width, 100dvw)\",\n maxWidth: \"var(--ep-display-scale-overlay-viewport-width, 100dvw)\",\n };\n },\n },\n },\n };\n}\n","import type { PopoverOrigin, PopoverProps } from \"@mui/material\";\nimport { type RefObject, useCallback, useLayoutEffect } from \"react\";\nimport { useDisplayScaleCompensation } from \"./DisplayScaleCompensationProvider\";\n\ntype DisplayScalePopoverPositionOptions = {\n open: boolean;\n overlayRootRef: RefObject<HTMLDivElement | null>;\n anchorElement: HTMLElement | null;\n anchorReference?: PopoverProps[\"anchorReference\"];\n anchorPosition?: PopoverProps[\"anchorPosition\"];\n anchorOrigin?: PopoverProps[\"anchorOrigin\"];\n transformOrigin?: PopoverProps[\"transformOrigin\"];\n marginThreshold?: PopoverProps[\"marginThreshold\"];\n};\n\nfunction resolveOffset(\n origin: number | \"top\" | \"center\" | \"bottom\" | \"left\" | \"right\" | undefined,\n size: number,\n): number {\n if (typeof origin === \"number\") return origin;\n if (origin === \"center\") return size / 2;\n if (origin === \"bottom\" || origin === \"right\") return size;\n return 0;\n}\n\nfunction resolveOrigins(\n anchorOrigin: PopoverOrigin | undefined,\n transformOrigin: PopoverOrigin | undefined,\n anchorWidth: number,\n anchorHeight: number,\n paperWidth: number,\n paperHeight: number,\n): { anchorX: number; anchorY: number; transformX: number; transformY: number } {\n const resolvedAnchorOrigin = anchorOrigin ?? { vertical: \"top\", horizontal: \"left\" };\n const resolvedTransformOrigin = transformOrigin ?? { vertical: \"top\", horizontal: \"left\" };\n\n return {\n anchorX: resolveOffset(resolvedAnchorOrigin.horizontal, anchorWidth),\n anchorY: resolveOffset(resolvedAnchorOrigin.vertical, anchorHeight),\n transformX: resolveOffset(resolvedTransformOrigin.horizontal, paperWidth),\n transformY: resolveOffset(resolvedTransformOrigin.vertical, paperHeight),\n };\n}\n\n/**\n * Replaces only MUI Popover's physical-viewport collision pass. MUI still\n * owns focus, transition and anchor semantics; this writes the final logical\n * top/left inside the compensated portal root.\n */\nexport function useDisplayScalePopoverPosition({\n open,\n overlayRootRef,\n anchorElement,\n anchorReference,\n anchorPosition,\n anchorOrigin,\n transformOrigin,\n marginThreshold = 16,\n}: DisplayScalePopoverPositionOptions): void {\n const { compensation, layoutExtents, rootRef } = useDisplayScaleCompensation();\n\n const correctPosition = useCallback(() => {\n if (!open || !compensation.active || !layoutExtents) return;\n\n const overlayRoot = overlayRootRef.current;\n const paper = overlayRoot?.querySelector<HTMLElement>(\".MuiPopover-paper\");\n if (!paper) return;\n\n const zoom = compensation.zoom > 0 ? compensation.zoom : 1;\n const rootRect = rootRef.current?.getBoundingClientRect();\n const rootLeft = rootRect?.left ?? 0;\n const rootTop = rootRect?.top ?? 0;\n const anchorRect = anchorElement?.getBoundingClientRect();\n const anchorX =\n anchorReference === \"anchorPosition\" && anchorPosition\n ? (anchorPosition.left - rootLeft) / zoom\n : anchorRect\n ? (anchorRect.left - rootLeft) / zoom\n : null;\n const anchorY =\n anchorReference === \"anchorPosition\" && anchorPosition\n ? (anchorPosition.top - rootTop) / zoom\n : anchorRect\n ? (anchorRect.top - rootTop) / zoom\n : null;\n if (anchorX == null || anchorY == null) return;\n\n const paperWidth = paper.offsetWidth;\n const paperHeight = paper.offsetHeight;\n const {\n anchorX: originX,\n anchorY: originY,\n transformX,\n transformY,\n } = resolveOrigins(\n anchorOrigin,\n transformOrigin,\n anchorRect ? anchorRect.width / zoom : 0,\n anchorRect ? anchorRect.height / zoom : 0,\n paperWidth,\n paperHeight,\n );\n const margin = typeof marginThreshold === \"number\" ? marginThreshold : 16;\n const maxLeft = Math.max(margin, layoutExtents.layoutWidthPx - margin - paperWidth);\n const maxTop = Math.max(margin, layoutExtents.layoutHeightPx - margin - paperHeight);\n const left = Math.min(maxLeft, Math.max(margin, anchorX + originX - transformX));\n const top = Math.min(maxTop, Math.max(margin, anchorY + originY - transformY));\n\n paper.style.left = `${Math.round(left)}px`;\n paper.style.top = `${Math.round(top)}px`;\n }, [\n anchorElement,\n anchorOrigin,\n anchorPosition,\n anchorReference,\n compensation.active,\n compensation.zoom,\n layoutExtents,\n marginThreshold,\n open,\n overlayRootRef,\n rootRef,\n transformOrigin,\n ]);\n\n useLayoutEffect(() => {\n if (!open) return;\n\n const firstFrame = window.requestAnimationFrame(() => {\n correctPosition();\n window.requestAnimationFrame(correctPosition);\n });\n window.addEventListener(\"resize\", correctPosition);\n window.addEventListener(\"scroll\", correctPosition, true);\n\n return () => {\n window.cancelAnimationFrame(firstFrame);\n window.removeEventListener(\"resize\", correctPosition);\n window.removeEventListener(\"scroll\", correctPosition, true);\n };\n }, [correctPosition, open]);\n}\n","import type { MenuProps, PopoverProps, PopperProps } from \"@mui/material\";\nimport { Menu, Popover, Popper } from \"@mui/material\";\nimport { useTheme } from \"@mui/material/styles\";\nimport type {\n OffsetsFunction,\n Options as PopperOffsetOptions,\n} from \"@popperjs/core/lib/modifiers/offset\";\nimport { type ReactElement, useMemo, useRef } from \"react\";\nimport { useDisplayScaleCompensation } from \"./DisplayScaleCompensationProvider\";\nimport { useDisplayScaleAnchor } from \"./useDisplayScaleAnchor\";\nimport { useDisplayScalePopoverPosition } from \"./useDisplayScalePopoverPosition\";\n\ntype NativeAnchorProps = {\n anchorEl?: HTMLElement | null;\n};\n\nexport type DisplayScaleMenuProps = Omit<MenuProps, \"anchorEl\"> & NativeAnchorProps;\n\n/** MUI Menu with an anchor adapted to the active portal strategy. */\nexport function DisplayScaleMenu({ anchorEl, ...props }: DisplayScaleMenuProps): ReactElement {\n const displayScaleAnchor = useDisplayScaleAnchor(anchorEl ?? null);\n const { compensation, rootRef } = useDisplayScaleCompensation();\n const theme = useTheme();\n const overlayRootRef = useRef<HTMLDivElement>(null);\n const horizontalOrigin = theme.direction === \"rtl\" ? \"right\" : \"left\";\n const anchorOrigin = props.anchorOrigin ?? {\n vertical: \"bottom\",\n horizontal: horizontalOrigin,\n };\n const transformOrigin = props.transformOrigin ?? {\n vertical: \"top\",\n horizontal: horizontalOrigin,\n };\n const anchorPosition = useMemo(() => {\n if (props.anchorReference !== \"anchorPosition\" || !props.anchorPosition) {\n return props.anchorPosition;\n }\n const zoom = compensation.zoom > 0 ? compensation.zoom : 1;\n return {\n top: props.anchorPosition.top / zoom,\n left: props.anchorPosition.left / zoom,\n };\n }, [compensation.zoom, props.anchorPosition, props.anchorReference]);\n useDisplayScalePopoverPosition({\n open: props.open,\n overlayRootRef,\n anchorElement: anchorEl ?? null,\n anchorReference: props.anchorReference,\n anchorPosition: props.anchorPosition,\n anchorOrigin,\n transformOrigin,\n marginThreshold: props.marginThreshold,\n });\n\n return (\n <Menu\n {...props}\n ref={overlayRootRef}\n anchorEl={displayScaleAnchor}\n anchorPosition={anchorPosition}\n anchorOrigin={anchorOrigin}\n container={rootRef.current ?? document.body}\n transformOrigin={transformOrigin}\n />\n );\n}\n\nexport type DisplayScalePopoverProps = Omit<PopoverProps, \"anchorEl\"> & NativeAnchorProps;\n\n/** MUI Popover with an anchor adapted to the active portal strategy. */\nexport function DisplayScalePopover({\n anchorEl,\n ...props\n}: DisplayScalePopoverProps): ReactElement {\n const displayScaleAnchor = useDisplayScaleAnchor(anchorEl ?? null);\n const { rootRef } = useDisplayScaleCompensation();\n const overlayRootRef = useRef<HTMLDivElement>(null);\n useDisplayScalePopoverPosition({\n open: props.open,\n overlayRootRef,\n anchorElement: anchorEl ?? null,\n anchorReference: props.anchorReference,\n anchorPosition: props.anchorPosition,\n anchorOrigin: props.anchorOrigin,\n transformOrigin: props.transformOrigin,\n marginThreshold: props.marginThreshold,\n });\n return (\n <Popover\n {...props}\n ref={overlayRootRef}\n anchorEl={displayScaleAnchor}\n container={rootRef.current ?? document.body}\n />\n );\n}\n\nexport type DisplayScalePopperProps = Omit<PopperProps, \"anchorEl\"> & NativeAnchorProps;\n\nconst defaultDisplayScalePopperModifiers: PopperProps[\"modifiers\"] = [\n {\n name: \"offset\",\n options: { offset: [0, 8] },\n },\n];\n\nfunction scalePopperOffsets(\n modifiers: PopperProps[\"modifiers\"],\n zoom: number,\n): PopperProps[\"modifiers\"] {\n if (!modifiers || zoom === 1) return modifiers;\n\n return modifiers.map((modifier) => {\n if (modifier.name !== \"offset\") return modifier;\n\n const options = modifier.options as Partial<PopperOffsetOptions> | undefined;\n const rawOffset = options?.offset;\n if (!rawOffset) return modifier;\n\n const scaleValue = (value: number | null | undefined) => (value == null ? value : value / zoom);\n const offset: PopperOffsetOptions[\"offset\"] =\n typeof rawOffset === \"function\"\n ? (args: Parameters<OffsetsFunction>[0]) => {\n const [skidding, distance] = rawOffset(args);\n return [scaleValue(skidding), scaleValue(distance)];\n }\n : [scaleValue(rawOffset[0]), scaleValue(rawOffset[1])];\n\n return { ...modifier, options: { ...options, offset } };\n });\n}\n\n/** MUI Popper with an anchor adapted to the active portal strategy. */\nexport function DisplayScalePopper({ anchorEl, ...props }: DisplayScalePopperProps): ReactElement {\n const displayScaleAnchor = useDisplayScaleAnchor(anchorEl ?? null);\n const { compensation, rootRef } = useDisplayScaleCompensation();\n const modifiers = useMemo(\n () =>\n scalePopperOffsets(props.modifiers ?? defaultDisplayScalePopperModifiers, compensation.zoom),\n [compensation.zoom, props.modifiers],\n );\n return (\n <Popper\n {...props}\n anchorEl={displayScaleAnchor}\n container={rootRef.current ?? document.body}\n modifiers={modifiers}\n style={{ ...props.style, zoom: 1 }}\n />\n );\n}\n","import type { MenuProps, SelectProps } from \"@mui/material\";\nimport { Select } from \"@mui/material\";\nimport type { ReactElement, SyntheticEvent } from \"react\";\nimport { useCallback, useId, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport { useDisplayScaleCompensation } from \"./DisplayScaleCompensationProvider\";\nimport { useDisplayScalePopoverPosition } from \"./useDisplayScalePopoverPosition\";\n\n/**\n * MUI Select adapter for the display-scale engine. Select creates its Menu\n * internally, so it cannot use DisplayScaleMenu directly.\n */\nexport function DisplayScaleSelect<Value = unknown>(props: SelectProps<Value>): ReactElement {\n const {\n MenuProps,\n SelectDisplayProps,\n defaultOpen,\n onClose,\n onOpen,\n open: openProp,\n ...selectProps\n } = props;\n const { rootRef } = useDisplayScaleCompensation();\n const menuRootRef = useRef<HTMLDivElement>(null);\n const selectId = useId();\n const [anchorElement, setAnchorElement] = useState<HTMLElement | null>(null);\n const [uncontrolledOpen, setUncontrolledOpen] = useState(Boolean(defaultOpen));\n const open = openProp ?? uncontrolledOpen;\n\n useLayoutEffect(() => {\n if (!open) {\n setAnchorElement(null);\n return;\n }\n setAnchorElement(\n document.querySelector<HTMLElement>(`[data-ep-display-scale-select=\"${selectId}\"]`),\n );\n }, [open, selectId]);\n\n const handleOpen = useCallback(\n (event: SyntheticEvent) => {\n if (openProp === undefined) setUncontrolledOpen(true);\n onOpen?.(event);\n },\n [onOpen, openProp],\n );\n const handleClose = useCallback(\n (event: SyntheticEvent) => {\n if (openProp === undefined) setUncontrolledOpen(false);\n onClose?.(event);\n },\n [onClose, openProp],\n );\n\n useDisplayScalePopoverPosition({\n open,\n overlayRootRef: menuRootRef,\n anchorElement,\n anchorOrigin: { vertical: \"bottom\", horizontal: \"center\" },\n transformOrigin: { vertical: \"top\", horizontal: \"center\" },\n });\n\n const menuProps = useMemo((): Partial<MenuProps> => {\n const paperSlot = MenuProps?.slotProps?.paper;\n const resolvedPaperSlot = typeof paperSlot === \"function\" ? undefined : paperSlot;\n\n return {\n ...MenuProps,\n container: rootRef.current ?? document.body,\n ref: menuRootRef,\n slotProps: {\n ...MenuProps?.slotProps,\n paper: {\n ...resolvedPaperSlot,\n style: { ...resolvedPaperSlot?.style, zoom: 1 },\n },\n },\n };\n }, [MenuProps, rootRef]);\n\n return (\n <Select<Value>\n {...selectProps}\n open={open}\n onOpen={handleOpen}\n onClose={handleClose}\n MenuProps={menuProps}\n SelectDisplayProps={\n {\n ...SelectDisplayProps,\n \"data-ep-display-scale-select\": selectId,\n } as SelectProps<Value>[\"SelectDisplayProps\"]\n }\n />\n );\n}\n","import { type RefObject, useLayoutEffect } from \"react\";\nimport { useDisplayScaleCompensation } from \"./DisplayScaleCompensationProvider\";\n\n/**\n * Keeps MUI Tabs' indicator in the compensated root's logical coordinate\n * space. MUI normally uses getBoundingClientRect(), whose values are already\n * zoomed and therefore too small for a logical layout container.\n */\nexport function useDisplayScaleTabsIndicator(\n tabsRef: RefObject<HTMLDivElement | null>,\n value: unknown,\n): void {\n const { compensation } = useDisplayScaleCompensation();\n\n useLayoutEffect(() => {\n if (!compensation.active) return;\n // Re-run after the selected Tabs value changes.\n void value;\n\n const updateIndicator = (): void => {\n const tabs = tabsRef.current;\n const indicator = tabs?.querySelector<HTMLElement>(\".MuiTabs-indicator\");\n const selectedTab = tabs?.querySelector<HTMLElement>(\".MuiTab-root.Mui-selected\");\n if (!tabs || !indicator || !selectedTab) return;\n\n const isRtl = getComputedStyle(tabs).direction === \"rtl\";\n const left = selectedTab.offsetLeft;\n const width = selectedTab.offsetWidth;\n\n indicator.style.width = `${width}px`;\n if (isRtl) {\n indicator.style.left = \"auto\";\n indicator.style.right = `${tabs.offsetWidth - left - width}px`;\n } else {\n indicator.style.right = \"auto\";\n indicator.style.left = `${left}px`;\n }\n };\n\n const frame = window.requestAnimationFrame(updateIndicator);\n const observer = new ResizeObserver(updateIndicator);\n if (tabsRef.current) observer.observe(tabsRef.current);\n window.addEventListener(\"resize\", updateIndicator);\n\n return () => {\n window.cancelAnimationFrame(frame);\n observer.disconnect();\n window.removeEventListener(\"resize\", updateIndicator);\n };\n }, [compensation.active, tabsRef, value]);\n}\n","import type { SxProps, Theme } from \"@mui/material/styles\";\n\n/** Fill the compensated app tree — prefer over raw `100vh` inside `#root`. */\nexport const displayScalePageFillSx: SxProps<Theme> = {\n flex: 1,\n minHeight: 0,\n minWidth: 0,\n width: \"100%\",\n height: \"100%\",\n};\n\n/** Full-viewport auth split layout — fills the compensated app root (not `position: fixed`). */\nexport const signinViewportShellSx: SxProps<Theme> = {\n flex: 1,\n minHeight: 0,\n minWidth: 0,\n width: \"100%\",\n height: \"100%\",\n overflow: \"hidden\",\n boxSizing: \"border-box\",\n};\n\n/** 50/50 desktop columns — avoids flex min-content overflow at OS display scale. */\nexport const signinSplitColumnSx: SxProps<Theme> = {\n flex: { xs: \"0 0 auto\", md: \"1 1 0\" },\n width: { xs: \"100%\", md: \"50%\" },\n maxWidth: { xs: \"100%\", md: \"50%\" },\n minWidth: 0,\n boxSizing: \"border-box\",\n};\n"]}