@open-mercato/ui 0.6.7-develop.6594.1.5581f765f4 → 0.6.7-develop.6597.1.b8f069b7fe
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/.turbo/turbo-build.log +1 -1
- package/dist/backend/DataTable.js +22 -1
- package/dist/backend/DataTable.js.map +2 -2
- package/dist/backend/utils/viewport.js +9 -0
- package/dist/backend/utils/viewport.js.map +7 -0
- package/package.json +3 -3
- package/src/backend/DataTable.tsx +27 -1
- package/src/backend/utils/__tests__/viewport.test.ts +26 -0
- package/src/backend/utils/viewport.ts +18 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
function computeMenuViewportShiftX(rect, viewportWidth, margin = 8) {
|
|
2
|
+
if (rect.left < margin) return Math.ceil(margin - rect.left);
|
|
3
|
+
if (rect.right > viewportWidth - margin) return Math.floor(viewportWidth - margin - rect.right);
|
|
4
|
+
return 0;
|
|
5
|
+
}
|
|
6
|
+
export {
|
|
7
|
+
computeMenuViewportShiftX
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=viewport.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/backend/utils/viewport.ts"],
|
|
4
|
+
"sourcesContent": ["// Horizontal shift (px) needed to keep an overlay fully inside the viewport.\n// Positive shifts right (overlay bleeds off the left edge), negative shifts left\n// (bleeds off the right edge), 0 when it already fits. Keeps a small margin.\n//\n// The two branches are mutually exclusive for any overlay narrower than the\n// viewport (minus the margins): clearing the left edge cannot then push the\n// right edge off-screen. Callers that could exceed the viewport width MUST cap\n// the overlay (e.g. `max-w-[calc(100vw-1rem)]`) \u2014 for a genuinely too-wide\n// overlay this falls back to left-aligning it, which is the sane default.\nexport function computeMenuViewportShiftX(\n rect: { left: number; right: number },\n viewportWidth: number,\n margin = 8,\n): number {\n if (rect.left < margin) return Math.ceil(margin - rect.left)\n if (rect.right > viewportWidth - margin) return Math.floor(viewportWidth - margin - rect.right)\n return 0\n}\n"],
|
|
5
|
+
"mappings": "AASO,SAAS,0BACd,MACA,eACA,SAAS,GACD;AACR,MAAI,KAAK,OAAO,OAAQ,QAAO,KAAK,KAAK,SAAS,KAAK,IAAI;AAC3D,MAAI,KAAK,QAAQ,gBAAgB,OAAQ,QAAO,KAAK,MAAM,gBAAgB,SAAS,KAAK,KAAK;AAC9F,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/ui",
|
|
3
|
-
"version": "0.6.7-develop.
|
|
3
|
+
"version": "0.6.7-develop.6597.1.b8f069b7fe",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -155,13 +155,13 @@
|
|
|
155
155
|
"remark-gfm": "^4.0.1"
|
|
156
156
|
},
|
|
157
157
|
"peerDependencies": {
|
|
158
|
-
"@open-mercato/shared": "0.6.7-develop.
|
|
158
|
+
"@open-mercato/shared": "0.6.7-develop.6597.1.b8f069b7fe",
|
|
159
159
|
"react": ">=18.0.0",
|
|
160
160
|
"react-dom": ">=18.0.0",
|
|
161
161
|
"react-is": ">=18.0.0"
|
|
162
162
|
},
|
|
163
163
|
"devDependencies": {
|
|
164
|
-
"@open-mercato/shared": "0.6.7-develop.
|
|
164
|
+
"@open-mercato/shared": "0.6.7-develop.6597.1.b8f069b7fe",
|
|
165
165
|
"@testing-library/dom": "^10.4.1",
|
|
166
166
|
"@testing-library/jest-dom": "^6.9.1",
|
|
167
167
|
"@testing-library/react": "^16.3.1",
|
|
@@ -36,6 +36,7 @@ import { apiCall, withScopedApiRequestHeaders } from './utils/apiCall'
|
|
|
36
36
|
import { buildOptimisticLockHeader } from './utils/optimisticLock'
|
|
37
37
|
import { useGuardedMutation } from './injection/useGuardedMutation'
|
|
38
38
|
import { raiseCrudError } from './utils/serverErrors'
|
|
39
|
+
import { computeMenuViewportShiftX } from './utils/viewport'
|
|
39
40
|
import { PerspectiveSidebar } from './PerspectiveSidebar'
|
|
40
41
|
import { Popover, PopoverTrigger, PopoverContent } from '../primitives/popover'
|
|
41
42
|
import { formatWithPublicDateFormat, normalizeDateFormatPattern } from '../primitives/date-format'
|
|
@@ -707,9 +708,33 @@ function ExportMenu({ config, sections }: { config: DataTableExportConfig; secti
|
|
|
707
708
|
const disabled = Boolean(config.disabled)
|
|
708
709
|
const hasSections = sections.length > 0
|
|
709
710
|
const [open, setOpen] = React.useState(false)
|
|
711
|
+
const [menuOffsetX, setMenuOffsetX] = React.useState(0)
|
|
710
712
|
const buttonRef = React.useRef<HTMLButtonElement>(null)
|
|
711
713
|
const menuRef = React.useRef<HTMLDivElement>(null)
|
|
712
714
|
|
|
715
|
+
// Keep the menu inside the viewport on narrow screens: the trigger can sit
|
|
716
|
+
// near the left edge, and a `right-0` menu would otherwise render off-screen.
|
|
717
|
+
// Measure the untransformed rect and shift it back on-screen, re-measuring on
|
|
718
|
+
// resize/orientation change while the menu stays open.
|
|
719
|
+
React.useLayoutEffect(() => {
|
|
720
|
+
if (!open) {
|
|
721
|
+
setMenuOffsetX(0)
|
|
722
|
+
return
|
|
723
|
+
}
|
|
724
|
+
const measure = () => {
|
|
725
|
+
const el = menuRef.current
|
|
726
|
+
if (!el || typeof window === 'undefined') return
|
|
727
|
+
const previousTransform = el.style.transform
|
|
728
|
+
el.style.transform = 'none'
|
|
729
|
+
const rect = el.getBoundingClientRect()
|
|
730
|
+
el.style.transform = previousTransform
|
|
731
|
+
setMenuOffsetX(computeMenuViewportShiftX(rect, window.innerWidth))
|
|
732
|
+
}
|
|
733
|
+
measure()
|
|
734
|
+
window.addEventListener('resize', measure)
|
|
735
|
+
return () => window.removeEventListener('resize', measure)
|
|
736
|
+
}, [open])
|
|
737
|
+
|
|
713
738
|
React.useEffect(() => {
|
|
714
739
|
if (!open || !hasSections) return
|
|
715
740
|
const onDocClick = (event: MouseEvent) => {
|
|
@@ -790,7 +815,8 @@ function ExportMenu({ config, sections }: { config: DataTableExportConfig; secti
|
|
|
790
815
|
<div
|
|
791
816
|
ref={menuRef}
|
|
792
817
|
role="menu"
|
|
793
|
-
className="absolute right-0 mt-2 w-60 rounded-md border bg-background py-2 shadow z-dropdown"
|
|
818
|
+
className="absolute right-0 mt-2 w-60 max-w-[calc(100vw-1rem)] rounded-md border bg-background py-2 shadow z-dropdown"
|
|
819
|
+
style={menuOffsetX ? { transform: `translateX(${menuOffsetX}px)` } : undefined}
|
|
794
820
|
>
|
|
795
821
|
{sections.map((section, idx) => (
|
|
796
822
|
<div key={section.key} className={idx > 0 ? 'mt-2 border-t pt-3' : ''}>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { computeMenuViewportShiftX } from '../viewport'
|
|
2
|
+
|
|
3
|
+
describe('computeMenuViewportShiftX', () => {
|
|
4
|
+
const viewport = 390
|
|
5
|
+
|
|
6
|
+
it('returns 0 when the overlay already fits within the viewport', () => {
|
|
7
|
+
expect(computeMenuViewportShiftX({ left: 100, right: 340 }, viewport)).toBe(0)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('shifts right when the overlay bleeds off the left edge', () => {
|
|
11
|
+
// Export dropdown reproduction: left:-38 on a narrow phone.
|
|
12
|
+
expect(computeMenuViewportShiftX({ left: -38, right: 202 }, viewport)).toBe(46)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('shifts left (negative) when the overlay bleeds off the right edge', () => {
|
|
16
|
+
expect(computeMenuViewportShiftX({ left: 200, right: 440 }, viewport)).toBe(-58)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('respects a custom margin', () => {
|
|
20
|
+
expect(computeMenuViewportShiftX({ left: 2, right: 200 }, viewport, 16)).toBe(14)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('leaves overlays flush to the margin untouched', () => {
|
|
24
|
+
expect(computeMenuViewportShiftX({ left: 8, right: 382 }, viewport)).toBe(0)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Horizontal shift (px) needed to keep an overlay fully inside the viewport.
|
|
2
|
+
// Positive shifts right (overlay bleeds off the left edge), negative shifts left
|
|
3
|
+
// (bleeds off the right edge), 0 when it already fits. Keeps a small margin.
|
|
4
|
+
//
|
|
5
|
+
// The two branches are mutually exclusive for any overlay narrower than the
|
|
6
|
+
// viewport (minus the margins): clearing the left edge cannot then push the
|
|
7
|
+
// right edge off-screen. Callers that could exceed the viewport width MUST cap
|
|
8
|
+
// the overlay (e.g. `max-w-[calc(100vw-1rem)]`) — for a genuinely too-wide
|
|
9
|
+
// overlay this falls back to left-aligning it, which is the sane default.
|
|
10
|
+
export function computeMenuViewportShiftX(
|
|
11
|
+
rect: { left: number; right: number },
|
|
12
|
+
viewportWidth: number,
|
|
13
|
+
margin = 8,
|
|
14
|
+
): number {
|
|
15
|
+
if (rect.left < margin) return Math.ceil(margin - rect.left)
|
|
16
|
+
if (rect.right > viewportWidth - margin) return Math.floor(viewportWidth - margin - rect.right)
|
|
17
|
+
return 0
|
|
18
|
+
}
|