@fluentui/react-positioning 9.23.2 → 9.23.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/CHANGELOG.md +21 -2
- package/lib/hooks/useSafeZoneArea/SafeZoneArea.js +51 -110
- package/lib/hooks/useSafeZoneArea/SafeZoneArea.js.map +1 -1
- package/lib/hooks/useSafeZoneArea/SafeZoneArea.styles.js +2 -2
- package/lib/hooks/useSafeZoneArea/SafeZoneArea.styles.js.map +1 -1
- package/lib/hooks/useSafeZoneArea/SafeZoneArea.styles.raw.js +2 -2
- package/lib/hooks/useSafeZoneArea/SafeZoneArea.styles.raw.js.map +1 -1
- package/lib/hooks/useSafeZoneArea/computeOutsideClipPath.js +3 -6
- package/lib/hooks/useSafeZoneArea/computeOutsideClipPath.js.map +1 -1
- package/lib/hooks/useSafeZoneArea/getSafeZonePoints.js +72 -0
- package/lib/hooks/useSafeZoneArea/getSafeZonePoints.js.map +1 -0
- package/lib/usePositioningSlideDirection.js +32 -23
- package/lib/usePositioningSlideDirection.js.map +1 -1
- package/lib-commonjs/hooks/useSafeZoneArea/SafeZoneArea.cjs +53 -121
- package/lib-commonjs/hooks/useSafeZoneArea/SafeZoneArea.cjs.map +1 -1
- package/lib-commonjs/hooks/useSafeZoneArea/SafeZoneArea.styles.cjs +2 -2
- package/lib-commonjs/hooks/useSafeZoneArea/SafeZoneArea.styles.cjs.map +1 -1
- package/lib-commonjs/hooks/useSafeZoneArea/SafeZoneArea.styles.raw.cjs +2 -2
- package/lib-commonjs/hooks/useSafeZoneArea/SafeZoneArea.styles.raw.cjs.map +1 -1
- package/lib-commonjs/hooks/useSafeZoneArea/computeOutsideClipPath.cjs +2 -5
- package/lib-commonjs/hooks/useSafeZoneArea/computeOutsideClipPath.cjs.map +1 -1
- package/lib-commonjs/hooks/useSafeZoneArea/getSafeZonePoints.cjs +82 -0
- package/lib-commonjs/hooks/useSafeZoneArea/getSafeZonePoints.cjs.map +1 -0
- package/lib-commonjs/usePositioningSlideDirection.cjs +32 -23
- package/lib-commonjs/usePositioningSlideDirection.cjs.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
# Change Log - @fluentui/react-positioning
|
|
2
2
|
|
|
3
|
-
<!-- This log was last generated on Mon,
|
|
3
|
+
<!-- This log was last generated on Mon, 19 Oct 2026 14:49:10 GMT and should not be manually modified. -->
|
|
4
4
|
|
|
5
5
|
<!-- Start content -->
|
|
6
6
|
|
|
7
|
+
## [9.23.4](https://github.com/microsoft/fluentui/tree/@fluentui/react-positioning_v9.23.4)
|
|
8
|
+
|
|
9
|
+
Mon, 19 Oct 2026 14:49:10 GMT
|
|
10
|
+
[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/react-positioning_v9.23.3..@fluentui/react-positioning_v9.23.4)
|
|
11
|
+
|
|
12
|
+
### Patches
|
|
13
|
+
|
|
14
|
+
- fix: register positioning slide-direction CSS properties once per document ([PR #36833](https://github.com/microsoft/fluentui/pull/36833) by bernardo.sunderhus@gmail.com)
|
|
15
|
+
- Bump @fluentui/react-utilities to v9.27.0 ([commit](https://github.com/microsoft/fluentui/commit/undefined) by beachball)
|
|
16
|
+
|
|
17
|
+
## [9.23.3](https://github.com/microsoft/fluentui/tree/@fluentui/react-positioning_v9.23.3)
|
|
18
|
+
|
|
19
|
+
Wed, 23 Sep 2026 09:04:33 GMT
|
|
20
|
+
[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/react-positioning_v9.23.2..@fluentui/react-positioning_v9.23.3)
|
|
21
|
+
|
|
22
|
+
### Patches
|
|
23
|
+
|
|
24
|
+
- fix: improve safe zone hit testing and avoid rerenders during pointer movement ([PR #36708](https://github.com/microsoft/fluentui/pull/36708) by jakubmiskech@microsoft.com)
|
|
25
|
+
|
|
7
26
|
## [9.23.2](https://github.com/microsoft/fluentui/tree/@fluentui/react-positioning_v9.23.2)
|
|
8
27
|
|
|
9
|
-
|
|
28
|
+
Tue, 22 Sep 2026 11:25:39 GMT
|
|
10
29
|
[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/react-positioning_v9.23.1..@fluentui/react-positioning_v9.23.2)
|
|
11
30
|
|
|
12
31
|
### Patches
|
|
@@ -8,34 +8,13 @@ import { getMouseAnchor } from "./getMouseAnchor.js";
|
|
|
8
8
|
import { pointsToSvgPath } from "./pointsToSvgPath.js";
|
|
9
9
|
import { useStyles } from "./SafeZoneArea.styles.js";
|
|
10
10
|
import { computeOutsideClipPath } from "./computeOutsideClipPath.js";
|
|
11
|
-
|
|
12
|
-
const EMPTY_RECT = {
|
|
13
|
-
top: 0,
|
|
14
|
-
right: 0,
|
|
15
|
-
bottom: 0,
|
|
16
|
-
left: 0,
|
|
17
|
-
width: 0,
|
|
18
|
-
height: 0,
|
|
19
|
-
x: 0,
|
|
20
|
-
y: 0,
|
|
21
|
-
toJSON () {
|
|
22
|
-
return '';
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
export function isSameRect(a, b) {
|
|
26
|
-
return a.top === b.top && a.right === b.right && a.bottom === b.bottom && a.left === b.left && a.width === b.width && a.height === b.height;
|
|
27
|
-
}
|
|
28
|
-
export function isSameCoordinates(a, b) {
|
|
29
|
-
return a[0] === b[0] && a[1] === b[1];
|
|
30
|
-
}
|
|
11
|
+
import { getSafeZonePoints } from "./getSafeZonePoints.js";
|
|
31
12
|
// ---
|
|
32
13
|
/**
|
|
33
14
|
* A component that renders a safe zone area with SVG shapes. Uses `useSyncExternalStore` to manage its active state
|
|
34
15
|
* to avoid causing re-renders in `useSafeZoneArea()` as the hook might be used in host components like `Menu`.
|
|
35
16
|
*
|
|
36
|
-
* Draws
|
|
37
|
-
* - a triangle that points to the target element which is an actual safe zone
|
|
38
|
-
* - a rectangle for a clip path that clips out the target element
|
|
17
|
+
* Draws a polygon from the mouse to the facing edges of the container and clips out the target element.
|
|
39
18
|
*
|
|
40
19
|
* @internal
|
|
41
20
|
*/ export const SafeZoneArea = /*#__PURE__*/ React.memo((props)=>{
|
|
@@ -44,114 +23,76 @@ export function isSameCoordinates(a, b) {
|
|
|
44
23
|
const styles = useStyles();
|
|
45
24
|
const active = useSyncExternalStore(stateStore.subscribe, stateStore.isActive);
|
|
46
25
|
const svgRef = React.useRef(null);
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
mouseCoordinates: [
|
|
51
|
-
0,
|
|
52
|
-
0
|
|
53
|
-
]
|
|
54
|
-
}));
|
|
26
|
+
const safeZoneRef = React.useRef(null);
|
|
27
|
+
const clipPathRef = React.useRef(null);
|
|
28
|
+
const rectDebugRef = React.useRef(null);
|
|
55
29
|
React.useImperativeHandle(props.imperativeRef, ()=>({
|
|
56
|
-
updateSVG (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
30
|
+
updateSVG ({ containerRect, targetRect, mouseCoordinates }) {
|
|
31
|
+
var _safeZoneRef_current, _clipPathRef_current, _rectDebugRef_current;
|
|
32
|
+
const topOffset = Math.min(targetRect.top, containerRect.top);
|
|
33
|
+
const leftOffset = Math.min(targetRect.left, containerRect.left);
|
|
34
|
+
const bottomOffset = Math.max(targetRect.bottom, containerRect.bottom);
|
|
35
|
+
const rightOffset = Math.max(targetRect.right, containerRect.right);
|
|
36
|
+
const containerCorners = getRectCorners(containerRect, [
|
|
37
|
+
leftOffset,
|
|
38
|
+
topOffset
|
|
39
|
+
]);
|
|
40
|
+
const targetCorners = getRectCorners(targetRect, [
|
|
41
|
+
leftOffset,
|
|
42
|
+
topOffset
|
|
43
|
+
]);
|
|
44
|
+
// SVG coordinates are relative to its top-left corner.
|
|
45
|
+
const relativeMouseCoordinates = [
|
|
46
|
+
mouseCoordinates[0] - leftOffset,
|
|
47
|
+
mouseCoordinates[1] - topOffset
|
|
48
|
+
];
|
|
49
|
+
const mouseAnchor = getMouseAnchor(containerCorners.topLeft, containerCorners.bottomRight, relativeMouseCoordinates);
|
|
50
|
+
const svgWidth = rightOffset - leftOffset;
|
|
51
|
+
const svgHeight = bottomOffset - topOffset;
|
|
52
|
+
const clipPath = computeOutsideClipPath(svgWidth, svgHeight, {
|
|
53
|
+
x: targetCorners.topLeft[0],
|
|
54
|
+
y: targetCorners.topLeft[1],
|
|
55
|
+
width: targetRect.width,
|
|
56
|
+
height: targetRect.height
|
|
64
57
|
});
|
|
58
|
+
if (svgRef.current) {
|
|
59
|
+
svgRef.current.style.width = `${svgWidth}px`;
|
|
60
|
+
svgRef.current.style.height = `${svgHeight}px`;
|
|
61
|
+
svgRef.current.style.transform = `translate(${leftOffset}px, ${topOffset}px)`;
|
|
62
|
+
}
|
|
63
|
+
const safeZonePoints = getSafeZonePoints(mouseAnchor, containerCorners);
|
|
64
|
+
(_safeZoneRef_current = safeZoneRef.current) === null || _safeZoneRef_current === void 0 ? void 0 : _safeZoneRef_current.setAttribute('d', safeZonePoints.length > 0 ? pointsToSvgPath(safeZonePoints) : '');
|
|
65
|
+
(_clipPathRef_current = clipPathRef.current) === null || _clipPathRef_current === void 0 ? void 0 : _clipPathRef_current.setAttribute('d', clipPath);
|
|
66
|
+
(_rectDebugRef_current = rectDebugRef.current) === null || _rectDebugRef_current === void 0 ? void 0 : _rectDebugRef_current.setAttribute('d', clipPath);
|
|
65
67
|
}
|
|
66
68
|
}), []);
|
|
67
|
-
const { containerRect, targetRect, mouseCoordinates } = state;
|
|
68
|
-
const topOffset = Math.min(targetRect.top, containerRect.top);
|
|
69
|
-
const leftOffset = Math.min(targetRect.left, containerRect.left);
|
|
70
|
-
const bottomOffset = Math.max(targetRect.bottom, containerRect.bottom);
|
|
71
|
-
const rightOffset = Math.max(targetRect.right, containerRect.right);
|
|
72
|
-
// ---
|
|
73
|
-
const containerCorners = getRectCorners(containerRect, [
|
|
74
|
-
leftOffset,
|
|
75
|
-
topOffset
|
|
76
|
-
]);
|
|
77
|
-
const targetCorners = getRectCorners(targetRect, [
|
|
78
|
-
leftOffset,
|
|
79
|
-
topOffset
|
|
80
|
-
]);
|
|
81
|
-
// Heads up!
|
|
82
|
-
// The SVG coordinate system starts at the top-left corner of the SVG element,
|
|
83
|
-
// so we need to adjust the mouse coordinates relative to the SVG's top-left corner.
|
|
84
|
-
const relativeMouseCoordinates = [
|
|
85
|
-
mouseCoordinates[0] - leftOffset,
|
|
86
|
-
mouseCoordinates[1] - topOffset
|
|
87
|
-
];
|
|
88
|
-
const mouseAnchor = getMouseAnchor(containerCorners.topLeft, containerCorners.bottomRight, relativeMouseCoordinates);
|
|
89
|
-
const triangleA = [
|
|
90
|
-
mouseAnchor,
|
|
91
|
-
containerCorners.topLeft,
|
|
92
|
-
containerCorners.topRight
|
|
93
|
-
];
|
|
94
|
-
const triangleB = [
|
|
95
|
-
mouseAnchor,
|
|
96
|
-
containerCorners.topRight,
|
|
97
|
-
containerCorners.bottomRight
|
|
98
|
-
];
|
|
99
|
-
const triangleC = [
|
|
100
|
-
mouseAnchor,
|
|
101
|
-
containerCorners.bottomRight,
|
|
102
|
-
containerCorners.bottomLeft
|
|
103
|
-
];
|
|
104
|
-
const triangleD = [
|
|
105
|
-
mouseAnchor,
|
|
106
|
-
containerCorners.bottomLeft,
|
|
107
|
-
containerCorners.topLeft
|
|
108
|
-
];
|
|
109
|
-
const svgWidth = rightOffset - leftOffset;
|
|
110
|
-
const svgHeight = bottomOffset - topOffset;
|
|
111
|
-
const clipPath = computeOutsideClipPath(svgWidth, svgHeight, {
|
|
112
|
-
x: targetCorners.topLeft[0],
|
|
113
|
-
y: targetCorners.topLeft[1],
|
|
114
|
-
width: targetRect.width,
|
|
115
|
-
height: targetRect.height
|
|
116
|
-
}, {
|
|
117
|
-
x: containerCorners.topLeft[0],
|
|
118
|
-
y: containerCorners.topLeft[1],
|
|
119
|
-
width: containerRect.width,
|
|
120
|
-
height: containerRect.height
|
|
121
|
-
});
|
|
122
69
|
return /*#__PURE__*/ React.createElement("div", {
|
|
123
70
|
className: mergeClasses(styles.wrapper, active && styles.wrapperActive),
|
|
124
71
|
"data-safe-zone": ""
|
|
125
|
-
},
|
|
72
|
+
}, /*#__PURE__*/ React.createElement("svg", {
|
|
126
73
|
"aria-hidden": true,
|
|
127
74
|
className: styles.svg,
|
|
128
75
|
xmlns: "http://www.w3.org/2000/svg",
|
|
129
76
|
ref: svgRef,
|
|
130
77
|
style: {
|
|
131
|
-
width:
|
|
132
|
-
height:
|
|
133
|
-
transform:
|
|
78
|
+
width: 0,
|
|
79
|
+
height: 0,
|
|
80
|
+
transform: 'translate(0px, 0px)'
|
|
134
81
|
}
|
|
135
82
|
}, /*#__PURE__*/ React.createElement("g", {
|
|
136
|
-
className: mergeClasses(styles.
|
|
83
|
+
className: mergeClasses(styles.safeZone, debug && styles.safeZoneDebug),
|
|
137
84
|
clipPath: `url(#${clipPathId})`,
|
|
138
85
|
onMouseEnter: onMouseEnter,
|
|
139
86
|
onMouseMove: onMouseMove,
|
|
140
87
|
onMouseLeave: onMouseLeave
|
|
141
88
|
}, /*#__PURE__*/ React.createElement("path", {
|
|
142
|
-
|
|
143
|
-
}), /*#__PURE__*/ React.createElement("path", {
|
|
144
|
-
d: pointsToSvgPath(triangleB)
|
|
145
|
-
}), /*#__PURE__*/ React.createElement("path", {
|
|
146
|
-
d: pointsToSvgPath(triangleC)
|
|
147
|
-
}), /*#__PURE__*/ React.createElement("path", {
|
|
148
|
-
d: pointsToSvgPath(triangleD)
|
|
89
|
+
ref: safeZoneRef
|
|
149
90
|
})), /*#__PURE__*/ React.createElement("clipPath", {
|
|
150
91
|
id: clipPathId
|
|
151
92
|
}, /*#__PURE__*/ React.createElement("path", {
|
|
152
|
-
|
|
93
|
+
ref: clipPathRef
|
|
153
94
|
})), debug && /*#__PURE__*/ React.createElement("path", {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}))
|
|
95
|
+
ref: rectDebugRef,
|
|
96
|
+
className: styles.rectDebug
|
|
97
|
+
})));
|
|
157
98
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useSafeZoneArea/SafeZoneArea.tsx"],"sourcesContent":["'use client';\n\nimport { mergeClasses } from '@griffel/react';\nimport { useId } from '@fluentui/react-utilities';\nimport type { JSXElement } from '@fluentui/react-utilities';\nimport * as React from 'react';\nimport { useSyncExternalStore } from 'use-sync-external-store/shim';\n\nimport type { createSafeZoneAreaStateStore } from './createSafeZoneAreaStateStore';\nimport { getRectCorners } from './getRectCorners';\nimport { getMouseAnchor } from './getMouseAnchor';\nimport { pointsToSvgPath } from './pointsToSvgPath';\nimport { useStyles } from './SafeZoneArea.styles';\nimport type { Point } from './types';\nimport { computeOutsideClipPath } from './computeOutsideClipPath';\n\nexport type SafeZoneAreaImperativeHandle = {\n updateSVG: (options: { containerRect: DOMRect; targetRect: DOMRect; mouseCoordinates: Point }) => void;\n};\n\nexport type SafeZoneAreaProps = {\n /** Enables debug mode: makes drawn shapes visible. */\n debug: boolean;\n\n /** A reference to the SafeZoneArea imperative handle. */\n imperativeRef: React.Ref<SafeZoneAreaImperativeHandle>;\n\n // eslint-disable-next-line @nx/workspace-consistent-callback-type\n onMouseEnter: (e: React.MouseEvent) => void;\n // eslint-disable-next-line @nx/workspace-consistent-callback-type\n onMouseMove: (e: React.MouseEvent) => void;\n // eslint-disable-next-line @nx/workspace-consistent-callback-type\n onMouseLeave: (e: React.MouseEvent) => void;\n\n stateStore: ReturnType<typeof createSafeZoneAreaStateStore>;\n};\n\n/**\n * @internal\n */\ntype SafeZoneAreaState = {\n containerRect: DOMRect;\n targetRect: DOMRect;\n mouseCoordinates: Point;\n};\n\n// ---\n\nconst EMPTY_RECT: DOMRect = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n width: 0,\n height: 0,\n x: 0,\n y: 0,\n toJSON() {\n return '';\n },\n};\n\nexport function isSameRect(a: DOMRect, b: DOMRect): boolean {\n return (\n a.top === b.top &&\n a.right === b.right &&\n a.bottom === b.bottom &&\n a.left === b.left &&\n a.width === b.width &&\n a.height === b.height\n );\n}\n\nexport function isSameCoordinates(a: Point, b: Point): boolean {\n return a[0] === b[0] && a[1] === b[1];\n}\n\n// ---\n\n/**\n * A component that renders a safe zone area with SVG shapes. Uses `useSyncExternalStore` to manage its active state\n * to avoid causing re-renders in `useSafeZoneArea()` as the hook might be used in host components like `Menu`.\n *\n * Draws two shapes:\n * - a triangle that points to the target element which is an actual safe zone\n * - a rectangle for a clip path that clips out the target element\n *\n * @internal\n */\nexport const SafeZoneArea = React.memo((props: SafeZoneAreaProps): JSXElement => {\n const { debug, onMouseEnter, onMouseMove, onMouseLeave, stateStore } = props;\n\n const clipPathId = useId();\n const styles = useStyles();\n\n const active = useSyncExternalStore(stateStore.subscribe, stateStore.isActive);\n const svgRef = React.useRef<SVGSVGElement>(null);\n\n const [state, setState] = React.useState<SafeZoneAreaState>(() => ({\n containerRect: EMPTY_RECT,\n targetRect: EMPTY_RECT,\n mouseCoordinates: [0, 0],\n }));\n\n React.useImperativeHandle(\n props.imperativeRef,\n () => ({\n updateSVG(newState) {\n setState(prevState => {\n // Heads up!\n // A small optimization to avoid unnecessary re-renders\n if (\n isSameRect(prevState.containerRect, newState.containerRect) &&\n isSameRect(prevState.targetRect, newState.targetRect) &&\n isSameCoordinates(prevState.mouseCoordinates, newState.mouseCoordinates)\n ) {\n return prevState;\n }\n\n return newState;\n });\n },\n }),\n [],\n );\n\n const { containerRect, targetRect, mouseCoordinates } = state;\n\n const topOffset = Math.min(targetRect.top, containerRect.top);\n const leftOffset = Math.min(targetRect.left, containerRect.left);\n const bottomOffset = Math.max(targetRect.bottom, containerRect.bottom);\n const rightOffset = Math.max(targetRect.right, containerRect.right);\n\n // ---\n\n const containerCorners = getRectCorners(containerRect, [leftOffset, topOffset]);\n const targetCorners = getRectCorners(targetRect, [leftOffset, topOffset]);\n\n // Heads up!\n // The SVG coordinate system starts at the top-left corner of the SVG element,\n // so we need to adjust the mouse coordinates relative to the SVG's top-left corner.\n const relativeMouseCoordinates: Point = [mouseCoordinates[0] - leftOffset, mouseCoordinates[1] - topOffset];\n const mouseAnchor = getMouseAnchor(containerCorners.topLeft, containerCorners.bottomRight, relativeMouseCoordinates);\n\n const triangleA = [mouseAnchor, containerCorners.topLeft, containerCorners.topRight];\n const triangleB = [mouseAnchor, containerCorners.topRight, containerCorners.bottomRight];\n const triangleC = [mouseAnchor, containerCorners.bottomRight, containerCorners.bottomLeft];\n const triangleD = [mouseAnchor, containerCorners.bottomLeft, containerCorners.topLeft];\n\n const svgWidth = rightOffset - leftOffset;\n const svgHeight = bottomOffset - topOffset;\n\n const clipPath = computeOutsideClipPath(\n svgWidth,\n svgHeight,\n {\n x: targetCorners.topLeft[0],\n y: targetCorners.topLeft[1],\n width: targetRect.width,\n height: targetRect.height,\n },\n {\n x: containerCorners.topLeft[0],\n y: containerCorners.topLeft[1],\n width: containerRect.width,\n height: containerRect.height,\n },\n );\n\n return (\n <div className={mergeClasses(styles.wrapper, active && styles.wrapperActive)} data-safe-zone=\"\">\n {active ? (\n <svg\n aria-hidden\n className={styles.svg}\n xmlns=\"http://www.w3.org/2000/svg\"\n ref={svgRef}\n style={{\n width: `${svgWidth}px`,\n height: `${svgHeight}px`,\n transform: `translate(${leftOffset}px, ${topOffset}px)`,\n }}\n >\n <g\n className={mergeClasses(styles.triangle, debug && styles.triangleDebug)}\n clipPath={`url(#${clipPathId})`}\n onMouseEnter={onMouseEnter}\n onMouseMove={onMouseMove}\n onMouseLeave={onMouseLeave}\n >\n <path d={pointsToSvgPath(triangleA)} />\n <path d={pointsToSvgPath(triangleB)} />\n <path d={pointsToSvgPath(triangleC)} />\n <path d={pointsToSvgPath(triangleD)} />\n </g>\n\n <clipPath id={clipPathId}>\n <path d={clipPath} />\n </clipPath>\n\n {debug && <path className={styles.rectDebug} d={clipPath} />}\n </svg>\n ) : null}\n </div>\n );\n});\n"],"names":["mergeClasses","useId","React","useSyncExternalStore","getRectCorners","getMouseAnchor","pointsToSvgPath","useStyles","computeOutsideClipPath","EMPTY_RECT","top","right","bottom","left","width","height","x","y","toJSON","isSameRect","a","b","isSameCoordinates","SafeZoneArea","memo","props","debug","onMouseEnter","onMouseMove","onMouseLeave","stateStore","clipPathId","styles","active","subscribe","isActive","svgRef","useRef","state","setState","useState","containerRect","targetRect","mouseCoordinates","useImperativeHandle","imperativeRef","updateSVG","newState","prevState","topOffset","Math","min","leftOffset","bottomOffset","max","rightOffset","containerCorners","targetCorners","relativeMouseCoordinates","mouseAnchor","topLeft","bottomRight","triangleA","topRight","triangleB","triangleC","bottomLeft","triangleD","svgWidth","svgHeight","clipPath","div","className","wrapper","wrapperActive","data-safe-zone","svg","aria-hidden","xmlns","ref","style","transform","g","triangle","triangleDebug","path","d","id","rectDebug"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useSafeZoneArea/SafeZoneArea.tsx"],"sourcesContent":["'use client';\n\nimport { mergeClasses } from '@griffel/react';\nimport { useId } from '@fluentui/react-utilities';\nimport type { JSXElement } from '@fluentui/react-utilities';\nimport * as React from 'react';\nimport { useSyncExternalStore } from 'use-sync-external-store/shim';\n\nimport type { createSafeZoneAreaStateStore } from './createSafeZoneAreaStateStore';\nimport { getRectCorners } from './getRectCorners';\nimport { getMouseAnchor } from './getMouseAnchor';\nimport { pointsToSvgPath } from './pointsToSvgPath';\nimport { useStyles } from './SafeZoneArea.styles';\nimport type { Point } from './types';\nimport { computeOutsideClipPath } from './computeOutsideClipPath';\nimport { getSafeZonePoints } from './getSafeZonePoints';\n\nexport type SafeZoneAreaImperativeHandle = {\n updateSVG: (options: { containerRect: DOMRect; targetRect: DOMRect; mouseCoordinates: Point }) => void;\n};\n\nexport type SafeZoneAreaProps = {\n /** Enables debug mode: makes drawn shapes visible. */\n debug: boolean;\n\n /** A reference to the SafeZoneArea imperative handle. */\n imperativeRef: React.Ref<SafeZoneAreaImperativeHandle>;\n\n // eslint-disable-next-line @nx/workspace-consistent-callback-type\n onMouseEnter: (e: React.MouseEvent) => void;\n // eslint-disable-next-line @nx/workspace-consistent-callback-type\n onMouseMove: (e: React.MouseEvent) => void;\n // eslint-disable-next-line @nx/workspace-consistent-callback-type\n onMouseLeave: (e: React.MouseEvent) => void;\n\n stateStore: ReturnType<typeof createSafeZoneAreaStateStore>;\n};\n\n// ---\n\n/**\n * A component that renders a safe zone area with SVG shapes. Uses `useSyncExternalStore` to manage its active state\n * to avoid causing re-renders in `useSafeZoneArea()` as the hook might be used in host components like `Menu`.\n *\n * Draws a polygon from the mouse to the facing edges of the container and clips out the target element.\n *\n * @internal\n */\nexport const SafeZoneArea = React.memo((props: SafeZoneAreaProps): JSXElement => {\n const { debug, onMouseEnter, onMouseMove, onMouseLeave, stateStore } = props;\n\n const clipPathId = useId();\n const styles = useStyles();\n\n const active = useSyncExternalStore(stateStore.subscribe, stateStore.isActive);\n const svgRef = React.useRef<SVGSVGElement>(null);\n const safeZoneRef = React.useRef<SVGPathElement>(null);\n const clipPathRef = React.useRef<SVGPathElement>(null);\n const rectDebugRef = React.useRef<SVGPathElement>(null);\n\n React.useImperativeHandle(\n props.imperativeRef,\n () => ({\n updateSVG({ containerRect, targetRect, mouseCoordinates }) {\n const topOffset = Math.min(targetRect.top, containerRect.top);\n const leftOffset = Math.min(targetRect.left, containerRect.left);\n const bottomOffset = Math.max(targetRect.bottom, containerRect.bottom);\n const rightOffset = Math.max(targetRect.right, containerRect.right);\n\n const containerCorners = getRectCorners(containerRect, [leftOffset, topOffset]);\n const targetCorners = getRectCorners(targetRect, [leftOffset, topOffset]);\n\n // SVG coordinates are relative to its top-left corner.\n const relativeMouseCoordinates: Point = [mouseCoordinates[0] - leftOffset, mouseCoordinates[1] - topOffset];\n const mouseAnchor = getMouseAnchor(\n containerCorners.topLeft,\n containerCorners.bottomRight,\n relativeMouseCoordinates,\n );\n\n const svgWidth = rightOffset - leftOffset;\n const svgHeight = bottomOffset - topOffset;\n const clipPath = computeOutsideClipPath(svgWidth, svgHeight, {\n x: targetCorners.topLeft[0],\n y: targetCorners.topLeft[1],\n width: targetRect.width,\n height: targetRect.height,\n });\n\n if (svgRef.current) {\n svgRef.current.style.width = `${svgWidth}px`;\n svgRef.current.style.height = `${svgHeight}px`;\n svgRef.current.style.transform = `translate(${leftOffset}px, ${topOffset}px)`;\n }\n\n const safeZonePoints = getSafeZonePoints(mouseAnchor, containerCorners);\n safeZoneRef.current?.setAttribute('d', safeZonePoints.length > 0 ? pointsToSvgPath(safeZonePoints) : '');\n clipPathRef.current?.setAttribute('d', clipPath);\n rectDebugRef.current?.setAttribute('d', clipPath);\n },\n }),\n [],\n );\n\n return (\n <div className={mergeClasses(styles.wrapper, active && styles.wrapperActive)} data-safe-zone=\"\">\n <svg\n aria-hidden\n className={styles.svg}\n xmlns=\"http://www.w3.org/2000/svg\"\n ref={svgRef}\n style={{ width: 0, height: 0, transform: 'translate(0px, 0px)' }}\n >\n <g\n className={mergeClasses(styles.safeZone, debug && styles.safeZoneDebug)}\n clipPath={`url(#${clipPathId})`}\n onMouseEnter={onMouseEnter}\n onMouseMove={onMouseMove}\n onMouseLeave={onMouseLeave}\n >\n <path ref={safeZoneRef} />\n </g>\n\n <clipPath id={clipPathId}>\n <path ref={clipPathRef} />\n </clipPath>\n\n {debug && <path ref={rectDebugRef} className={styles.rectDebug} />}\n </svg>\n </div>\n );\n});\n"],"names":["mergeClasses","useId","React","useSyncExternalStore","getRectCorners","getMouseAnchor","pointsToSvgPath","useStyles","computeOutsideClipPath","getSafeZonePoints","SafeZoneArea","memo","props","debug","onMouseEnter","onMouseMove","onMouseLeave","stateStore","clipPathId","styles","active","subscribe","isActive","svgRef","useRef","safeZoneRef","clipPathRef","rectDebugRef","useImperativeHandle","imperativeRef","updateSVG","containerRect","targetRect","mouseCoordinates","topOffset","Math","min","top","leftOffset","left","bottomOffset","max","bottom","rightOffset","right","containerCorners","targetCorners","relativeMouseCoordinates","mouseAnchor","topLeft","bottomRight","svgWidth","svgHeight","clipPath","x","y","width","height","current","style","transform","safeZonePoints","setAttribute","length","div","className","wrapper","wrapperActive","data-safe-zone","svg","aria-hidden","xmlns","ref","g","safeZone","safeZoneDebug","path","id","rectDebug"],"mappings":"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"}
|
|
@@ -19,10 +19,10 @@ export const useStyles = /*#__PURE__*/__styles({
|
|
|
19
19
|
Bhzewxz: "f15twtuk",
|
|
20
20
|
oyh7mz: ["f1vgc2s3", "f1e31b4d"]
|
|
21
21
|
},
|
|
22
|
-
|
|
22
|
+
safeZone: {
|
|
23
23
|
Bkecrkj: "f1cguypg"
|
|
24
24
|
},
|
|
25
|
-
|
|
25
|
+
safeZoneDebug: {
|
|
26
26
|
Bceei9c: "f7116n6",
|
|
27
27
|
Bkfmm31: "f1xab38x"
|
|
28
28
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["__styles","tokens","useStyles","wrapper","mc9l5x","Bqenvij","a9b677","Bkecrkj","wrapperActive","svg","Bkfmm31","qhf8xq","Bhzewxz","oyh7mz","
|
|
1
|
+
{"version":3,"names":["__styles","tokens","useStyles","wrapper","mc9l5x","Bqenvij","a9b677","Bkecrkj","wrapperActive","svg","Bkfmm31","qhf8xq","Bhzewxz","oyh7mz","safeZone","safeZoneDebug","Bceei9c","rectDebug","d"],"sources":["SafeZoneArea.styles.js"],"sourcesContent":["'use client';\nimport { makeStyles } from \"@griffel/react\";\nimport { tokens } from \"@fluentui/react-theme\";\nexport const useStyles = makeStyles({\n wrapper: {\n display: 'none',\n height: 0,\n width: 0,\n pointerEvents: 'none'\n },\n wrapperActive: {\n display: 'block'\n },\n svg: {\n fill: 'transparent',\n pointerEvents: 'none',\n position: 'fixed',\n top: 0,\n left: 0\n },\n safeZone: {\n pointerEvents: 'auto'\n },\n safeZoneDebug: {\n cursor: 'crosshair',\n fill: `color-mix(in srgb, ${tokens.colorPaletteGreenBackground3} 20%, transparent)`\n },\n rectDebug: {\n fill: `color-mix(in srgb, ${tokens.colorPaletteRedBackground3} 20%, transparent)`\n }\n});\n"],"mappings":"AAAA,YAAY;;AACZ,SAAAA,QAAA,QAA2B,gBAAgB;AAC3C,SAASC,MAAM,QAAQ,uBAAuB;AAC9C,OAAO,MAAMC,SAAS,gBAAGF,QAAA;EAAAG,OAAA;IAAAC,MAAA;IAAAC,OAAA;IAAAC,MAAA;IAAAC,OAAA;EAAA;EAAAC,aAAA;IAAAJ,MAAA;EAAA;EAAAK,GAAA;IAAAC,OAAA;IAAAH,OAAA;IAAAI,MAAA;IAAAC,OAAA;IAAAC,MAAA;EAAA;EAAAC,QAAA;IAAAP,OAAA;EAAA;EAAAQ,aAAA;IAAAC,OAAA;IAAAN,OAAA;EAAA;EAAAO,SAAA;IAAAP,OAAA;EAAA;AAAA;EAAAQ,CAAA;AAAA,CA2BxB,CAAC","ignoreList":[]}
|
|
@@ -18,10 +18,10 @@ export const useStyles = makeStyles({
|
|
|
18
18
|
top: 0,
|
|
19
19
|
left: 0
|
|
20
20
|
},
|
|
21
|
-
|
|
21
|
+
safeZone: {
|
|
22
22
|
pointerEvents: 'auto'
|
|
23
23
|
},
|
|
24
|
-
|
|
24
|
+
safeZoneDebug: {
|
|
25
25
|
cursor: 'crosshair',
|
|
26
26
|
fill: `color-mix(in srgb, ${tokens.colorPaletteGreenBackground3} 20%, transparent)`
|
|
27
27
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useSafeZoneArea/SafeZoneArea.styles.ts"],"sourcesContent":["'use client';\n\nimport { makeStyles } from '@griffel/react';\nimport { tokens } from '@fluentui/react-theme';\n\nexport const useStyles = makeStyles({\n wrapper: {\n display: 'none',\n height: 0,\n width: 0,\n pointerEvents: 'none',\n },\n wrapperActive: {\n display: 'block',\n },\n svg: {\n fill: 'transparent',\n pointerEvents: 'none',\n position: 'fixed',\n top: 0,\n left: 0,\n },\n
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useSafeZoneArea/SafeZoneArea.styles.ts"],"sourcesContent":["'use client';\n\nimport { makeStyles } from '@griffel/react';\nimport { tokens } from '@fluentui/react-theme';\n\nexport const useStyles = makeStyles({\n wrapper: {\n display: 'none',\n height: 0,\n width: 0,\n pointerEvents: 'none',\n },\n wrapperActive: {\n display: 'block',\n },\n svg: {\n fill: 'transparent',\n pointerEvents: 'none',\n position: 'fixed',\n top: 0,\n left: 0,\n },\n safeZone: {\n pointerEvents: 'auto',\n },\n safeZoneDebug: {\n cursor: 'crosshair',\n fill: `color-mix(in srgb, ${tokens.colorPaletteGreenBackground3} 20%, transparent)`,\n },\n rectDebug: {\n fill: `color-mix(in srgb, ${tokens.colorPaletteRedBackground3} 20%, transparent)`,\n },\n});\n"],"names":["makeStyles","tokens","useStyles","wrapper","display","height","width","pointerEvents","wrapperActive","svg","fill","position","top","left","safeZone","safeZoneDebug","cursor","colorPaletteGreenBackground3","rectDebug","colorPaletteRedBackground3"],"mappings":"AAAA;AAEA,SAASA,UAAU,QAAQ,iBAAiB;AAC5C,SAASC,MAAM,QAAQ,wBAAwB;AAE/C,OAAO,MAAMC,YAAYF,WAAW;IAClCG,SAAS;QACPC,SAAS;QACTC,QAAQ;QACRC,OAAO;QACPC,eAAe;IACjB;IACAC,eAAe;QACbJ,SAAS;IACX;IACAK,KAAK;QACHC,MAAM;QACNH,eAAe;QACfI,UAAU;QACVC,KAAK;QACLC,MAAM;IACR;IACAC,UAAU;QACRP,eAAe;IACjB;IACAQ,eAAe;QACbC,QAAQ;QACRN,MAAM,CAAC,mBAAmB,EAAET,OAAOgB,4BAA4B,CAAC,kBAAkB,CAAC;IACrF;IACAC,WAAW;QACTR,MAAM,CAAC,mBAAmB,EAAET,OAAOkB,0BAA0B,CAAC,kBAAkB,CAAC;IACnF;AACF,GAAG"}
|
|
@@ -13,15 +13,12 @@ function drawRectangle(rect) {
|
|
|
13
13
|
return pathData;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Computes a clip path that covers the area outside
|
|
16
|
+
* Computes a clip path that covers the area outside a rectangle.
|
|
17
17
|
*
|
|
18
18
|
* @internal
|
|
19
|
-
*/ export function computeOutsideClipPath(svgWidth, svgHeight, targetRect
|
|
19
|
+
*/ export function computeOutsideClipPath(svgWidth, svgHeight, targetRect) {
|
|
20
20
|
let pathData = `M 0,0 H ${svgWidth} V ${svgHeight} H 0 Z `;
|
|
21
|
-
//
|
|
22
|
-
// The trick is to draw each rectangle in the counterclockwise direction
|
|
23
|
-
// which creates a "hole" in the main path
|
|
21
|
+
// Draw the rectangle counterclockwise to create a hole in the main path.
|
|
24
22
|
pathData += drawRectangle(targetRect);
|
|
25
|
-
pathData += drawRectangle(containerRect);
|
|
26
23
|
return pathData;
|
|
27
24
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useSafeZoneArea/computeOutsideClipPath.ts"],"sourcesContent":["function drawRectangle(rect: { x: number; y: number; width: number; height: number }): string {\n if (rect.width <= 0 || rect.height <= 0) {\n return '';\n }\n\n let pathData = '';\n\n // Creates a subpath moving in counterclockwise direction to create a hole\n\n pathData += `M ${rect.x},${rect.y} `;\n pathData += `V ${rect.y + rect.height} `; // Down to bottom-left\n pathData += `H ${rect.x + rect.width} `; // Right to bottom-right\n pathData += `V ${rect.y} `; // Up to top-right\n pathData += `H ${rect.x} `; // Left to top-left (closing)\n pathData += `Z `; // Close path\n\n return pathData;\n}\n\n/**\n * Computes a clip path that covers the area outside
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useSafeZoneArea/computeOutsideClipPath.ts"],"sourcesContent":["function drawRectangle(rect: { x: number; y: number; width: number; height: number }): string {\n if (rect.width <= 0 || rect.height <= 0) {\n return '';\n }\n\n let pathData = '';\n\n // Creates a subpath moving in counterclockwise direction to create a hole\n\n pathData += `M ${rect.x},${rect.y} `;\n pathData += `V ${rect.y + rect.height} `; // Down to bottom-left\n pathData += `H ${rect.x + rect.width} `; // Right to bottom-right\n pathData += `V ${rect.y} `; // Up to top-right\n pathData += `H ${rect.x} `; // Left to top-left (closing)\n pathData += `Z `; // Close path\n\n return pathData;\n}\n\n/**\n * Computes a clip path that covers the area outside a rectangle.\n *\n * @internal\n */\nexport function computeOutsideClipPath(\n svgWidth: number,\n svgHeight: number,\n targetRect: { x: number; y: number; width: number; height: number },\n): string {\n let pathData = `M 0,0 H ${svgWidth} V ${svgHeight} H 0 Z `;\n\n // Draw the rectangle counterclockwise to create a hole in the main path.\n\n pathData += drawRectangle(targetRect);\n\n return pathData;\n}\n"],"names":["drawRectangle","rect","width","height","pathData","x","y","computeOutsideClipPath","svgWidth","svgHeight","targetRect"],"mappings":"AAAA,SAASA,cAAcC,IAA6D;IAClF,IAAIA,KAAKC,KAAK,IAAI,KAAKD,KAAKE,MAAM,IAAI,GAAG;QACvC,OAAO;IACT;IAEA,IAAIC,WAAW;IAEf,0EAA0E;IAE1EA,YAAY,CAAC,EAAE,EAAEH,KAAKI,CAAC,CAAC,CAAC,EAAEJ,KAAKK,CAAC,CAAC,CAAC,CAAC;IACpCF,YAAY,CAAC,EAAE,EAAEH,KAAKK,CAAC,GAAGL,KAAKE,MAAM,CAAC,CAAC,CAAC,EAAE,sBAAsB;IAChEC,YAAY,CAAC,EAAE,EAAEH,KAAKI,CAAC,GAAGJ,KAAKC,KAAK,CAAC,CAAC,CAAC,EAAE,wBAAwB;IACjEE,YAAY,CAAC,EAAE,EAAEH,KAAKK,CAAC,CAAC,CAAC,CAAC,EAAE,kBAAkB;IAC9CF,YAAY,CAAC,EAAE,EAAEH,KAAKI,CAAC,CAAC,CAAC,CAAC,EAAE,6BAA6B;IACzDD,YAAY,CAAC,EAAE,CAAC,EAAE,aAAa;IAE/B,OAAOA;AACT;AAEA;;;;CAIC,GACD,OAAO,SAASG,uBACdC,QAAgB,EAChBC,SAAiB,EACjBC,UAAmE;IAEnE,IAAIN,WAAW,CAAC,QAAQ,EAAEI,SAAS,GAAG,EAAEC,UAAU,OAAO,CAAC;IAE1D,yEAAyE;IAEzEL,YAAYJ,cAAcU;IAE1B,OAAON;AACT"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the mouse anchor and the container corners facing it.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/ export function getSafeZonePoints(mouseAnchor, containerCorners) {
|
|
6
|
+
const { topLeft, topRight, bottomRight, bottomLeft } = containerCorners;
|
|
7
|
+
const isAbove = mouseAnchor[1] < topLeft[1];
|
|
8
|
+
const isBelow = mouseAnchor[1] > bottomLeft[1];
|
|
9
|
+
const isLeft = mouseAnchor[0] < topLeft[0];
|
|
10
|
+
const isRight = mouseAnchor[0] > topRight[0];
|
|
11
|
+
if (isAbove) {
|
|
12
|
+
if (isLeft) {
|
|
13
|
+
return [
|
|
14
|
+
mouseAnchor,
|
|
15
|
+
bottomLeft,
|
|
16
|
+
topLeft,
|
|
17
|
+
topRight
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
if (isRight) {
|
|
21
|
+
return [
|
|
22
|
+
mouseAnchor,
|
|
23
|
+
topLeft,
|
|
24
|
+
topRight,
|
|
25
|
+
bottomRight
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
return [
|
|
29
|
+
mouseAnchor,
|
|
30
|
+
topLeft,
|
|
31
|
+
topRight
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
if (isBelow) {
|
|
35
|
+
if (isLeft) {
|
|
36
|
+
return [
|
|
37
|
+
mouseAnchor,
|
|
38
|
+
topLeft,
|
|
39
|
+
bottomLeft,
|
|
40
|
+
bottomRight
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
if (isRight) {
|
|
44
|
+
return [
|
|
45
|
+
mouseAnchor,
|
|
46
|
+
topRight,
|
|
47
|
+
bottomRight,
|
|
48
|
+
bottomLeft
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
return [
|
|
52
|
+
mouseAnchor,
|
|
53
|
+
bottomRight,
|
|
54
|
+
bottomLeft
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
if (isLeft) {
|
|
58
|
+
return [
|
|
59
|
+
mouseAnchor,
|
|
60
|
+
bottomLeft,
|
|
61
|
+
topLeft
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
if (isRight) {
|
|
65
|
+
return [
|
|
66
|
+
mouseAnchor,
|
|
67
|
+
topRight,
|
|
68
|
+
bottomRight
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useSafeZoneArea/getSafeZonePoints.ts"],"sourcesContent":["import type { Point } from './types';\n\ntype RectCorners = Record<'topLeft' | 'topRight' | 'bottomRight' | 'bottomLeft', Point>;\n\n/**\n * Returns the mouse anchor and the container corners facing it.\n *\n * @internal\n */\nexport function getSafeZonePoints(mouseAnchor: Point, containerCorners: RectCorners): Point[] {\n const { topLeft, topRight, bottomRight, bottomLeft } = containerCorners;\n const isAbove = mouseAnchor[1] < topLeft[1];\n const isBelow = mouseAnchor[1] > bottomLeft[1];\n const isLeft = mouseAnchor[0] < topLeft[0];\n const isRight = mouseAnchor[0] > topRight[0];\n\n if (isAbove) {\n if (isLeft) {\n return [mouseAnchor, bottomLeft, topLeft, topRight];\n }\n\n if (isRight) {\n return [mouseAnchor, topLeft, topRight, bottomRight];\n }\n\n return [mouseAnchor, topLeft, topRight];\n }\n\n if (isBelow) {\n if (isLeft) {\n return [mouseAnchor, topLeft, bottomLeft, bottomRight];\n }\n\n if (isRight) {\n return [mouseAnchor, topRight, bottomRight, bottomLeft];\n }\n\n return [mouseAnchor, bottomRight, bottomLeft];\n }\n\n if (isLeft) {\n return [mouseAnchor, bottomLeft, topLeft];\n }\n\n if (isRight) {\n return [mouseAnchor, topRight, bottomRight];\n }\n\n return [];\n}\n"],"names":["getSafeZonePoints","mouseAnchor","containerCorners","topLeft","topRight","bottomRight","bottomLeft","isAbove","isBelow","isLeft","isRight"],"mappings":"AAIA;;;;CAIC,GACD,OAAO,SAASA,kBAAkBC,WAAkB,EAAEC,gBAA6B;IACjF,MAAM,EAAEC,OAAO,EAAEC,QAAQ,EAAEC,WAAW,EAAEC,UAAU,EAAE,GAAGJ;IACvD,MAAMK,UAAUN,WAAW,CAAC,EAAE,GAAGE,OAAO,CAAC,EAAE;IAC3C,MAAMK,UAAUP,WAAW,CAAC,EAAE,GAAGK,UAAU,CAAC,EAAE;IAC9C,MAAMG,SAASR,WAAW,CAAC,EAAE,GAAGE,OAAO,CAAC,EAAE;IAC1C,MAAMO,UAAUT,WAAW,CAAC,EAAE,GAAGG,QAAQ,CAAC,EAAE;IAE5C,IAAIG,SAAS;QACX,IAAIE,QAAQ;YACV,OAAO;gBAACR;gBAAaK;gBAAYH;gBAASC;aAAS;QACrD;QAEA,IAAIM,SAAS;YACX,OAAO;gBAACT;gBAAaE;gBAASC;gBAAUC;aAAY;QACtD;QAEA,OAAO;YAACJ;YAAaE;YAASC;SAAS;IACzC;IAEA,IAAII,SAAS;QACX,IAAIC,QAAQ;YACV,OAAO;gBAACR;gBAAaE;gBAASG;gBAAYD;aAAY;QACxD;QAEA,IAAIK,SAAS;YACX,OAAO;gBAACT;gBAAaG;gBAAUC;gBAAaC;aAAW;QACzD;QAEA,OAAO;YAACL;YAAaI;YAAaC;SAAW;IAC/C;IAEA,IAAIG,QAAQ;QACV,OAAO;YAACR;YAAaK;YAAYH;SAAQ;IAC3C;IAEA,IAAIO,SAAS;QACX,OAAO;YAACT;YAAaG;YAAUC;SAAY;IAC7C;IAEA,OAAO,EAAE;AACX"}
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { useEventCallback, isHTMLElement } from "@fluentui/react-utilities";
|
|
4
4
|
import { POSITIONING_SLIDE_DIRECTION_VAR_X, POSITIONING_SLIDE_DIRECTION_VAR_Y } from "./constants.js";
|
|
5
|
+
const registeredProperties = new WeakMap();
|
|
6
|
+
const slideDirectionProperties = [
|
|
7
|
+
POSITIONING_SLIDE_DIRECTION_VAR_X,
|
|
8
|
+
POSITIONING_SLIDE_DIRECTION_VAR_Y
|
|
9
|
+
];
|
|
5
10
|
/**
|
|
6
11
|
* Returns the slide direction unit vectors for a given Floating UI placement.
|
|
7
12
|
* Values are -1, 0, or 1, representing the direction the element slides in from.
|
|
@@ -46,30 +51,34 @@ import { POSITIONING_SLIDE_DIRECTION_VAR_X, POSITIONING_SLIDE_DIRECTION_VAR_Y }
|
|
|
46
51
|
element.style.setProperty(POSITIONING_SLIDE_DIRECTION_VAR_X, `${x}px`);
|
|
47
52
|
element.style.setProperty(POSITIONING_SLIDE_DIRECTION_VAR_Y, `${y}px`);
|
|
48
53
|
});
|
|
49
|
-
// Register the CSS custom properties so they can be interpolated during animations.
|
|
50
|
-
// CSS.registerProperty is idempotent — the try/catch handles the case where
|
|
51
|
-
// properties are already registered.
|
|
52
54
|
React.useEffect(()=>{
|
|
53
|
-
var
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
55
|
+
var _targetDocument_defaultView;
|
|
56
|
+
const css = targetDocument === null || targetDocument === void 0 ? void 0 : (_targetDocument_defaultView = targetDocument.defaultView) === null || _targetDocument_defaultView === void 0 ? void 0 : _targetDocument_defaultView.CSS;
|
|
57
|
+
if (!targetDocument || !(css === null || css === void 0 ? void 0 : css.registerProperty)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
let properties = registeredProperties.get(targetDocument);
|
|
61
|
+
if (!properties) {
|
|
62
|
+
properties = new Set();
|
|
63
|
+
registeredProperties.set(targetDocument, properties);
|
|
64
|
+
}
|
|
65
|
+
for (const name of slideDirectionProperties){
|
|
66
|
+
if (properties.has(name)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
css.registerProperty({
|
|
71
|
+
name,
|
|
72
|
+
syntax: '<length>',
|
|
73
|
+
inherits: false,
|
|
74
|
+
initialValue: '0px'
|
|
75
|
+
});
|
|
76
|
+
properties.add(name);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error && typeof error === 'object' && 'name' in error && error.name === 'InvalidModificationError') {
|
|
79
|
+
properties.add(name);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
73
82
|
}
|
|
74
83
|
}, [
|
|
75
84
|
targetDocument
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/usePositioningSlideDirection.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useEventCallback, isHTMLElement } from '@fluentui/react-utilities';\nimport type { PositioningProps } from './types';\nimport { POSITIONING_SLIDE_DIRECTION_VAR_X, POSITIONING_SLIDE_DIRECTION_VAR_Y } from './constants';\n\n/**\n * Returns the slide direction unit vectors for a given Floating UI placement.\n * Values are -1, 0, or 1, representing the direction the element slides in from.\n */\nexport function getPlacementSlideDirections(placement: string): { x: number; y: number } {\n const side = placement.split('-')[0];\n // Default to sliding down from the top side\n let x = 0;\n let y = 1;\n\n if (side === 'right') {\n x = -1;\n y = 0;\n } else if (side === 'bottom') {\n x = 0;\n y = -1;\n } else if (side === 'left') {\n x = 1;\n y = 0;\n }\n\n return { x, y };\n}\n\ntype UsePositioningSlideDirectionOptions = {\n /** The target document for CSS.registerProperty. */\n targetDocument: Document | undefined;\n /** The user's original onPositioningEnd callback, if any. */\n onPositioningEnd?: PositioningProps['onPositioningEnd'];\n};\n\n/**\n * A hook that manages CSS custom properties for slide direction based on positioning placement.\n *\n * It wraps the `onPositioningEnd` callback to set `--fui-positioning-slide-direction-x` and\n * `--fui-positioning-slide-direction-y` CSS custom properties on the positioned element,\n * and registers them via `CSS.registerProperty` to avoid properties propagation down to a DOM tree.\n *\n * @returns The wrapped `onPositioningEnd` handler to pass to the positioning config.\n */\nexport function usePositioningSlideDirection(\n options: UsePositioningSlideDirectionOptions,\n): NonNullable<PositioningProps['onPositioningEnd']> {\n const { targetDocument, onPositioningEnd } = options;\n\n const handlePositionEnd: NonNullable<PositioningProps['onPositioningEnd']> = useEventCallback(e => {\n onPositioningEnd?.(e);\n\n const element = e.target;\n const placement = e.detail.placement;\n\n if (!isHTMLElement(element)) {\n return;\n }\n\n const { x, y } = getPlacementSlideDirections(placement);\n\n element.style.setProperty(POSITIONING_SLIDE_DIRECTION_VAR_X, `${x}px`);\n element.style.setProperty(POSITIONING_SLIDE_DIRECTION_VAR_Y, `${y}px`);\n });\n\n
|
|
1
|
+
{"version":3,"sources":["../src/usePositioningSlideDirection.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useEventCallback, isHTMLElement } from '@fluentui/react-utilities';\nimport type { PositioningProps } from './types';\nimport { POSITIONING_SLIDE_DIRECTION_VAR_X, POSITIONING_SLIDE_DIRECTION_VAR_Y } from './constants';\n\nconst registeredProperties = new WeakMap<Document, Set<string>>();\nconst slideDirectionProperties = [POSITIONING_SLIDE_DIRECTION_VAR_X, POSITIONING_SLIDE_DIRECTION_VAR_Y];\n\n/**\n * Returns the slide direction unit vectors for a given Floating UI placement.\n * Values are -1, 0, or 1, representing the direction the element slides in from.\n */\nexport function getPlacementSlideDirections(placement: string): { x: number; y: number } {\n const side = placement.split('-')[0];\n // Default to sliding down from the top side\n let x = 0;\n let y = 1;\n\n if (side === 'right') {\n x = -1;\n y = 0;\n } else if (side === 'bottom') {\n x = 0;\n y = -1;\n } else if (side === 'left') {\n x = 1;\n y = 0;\n }\n\n return { x, y };\n}\n\ntype UsePositioningSlideDirectionOptions = {\n /** The target document for CSS.registerProperty. */\n targetDocument: Document | undefined;\n /** The user's original onPositioningEnd callback, if any. */\n onPositioningEnd?: PositioningProps['onPositioningEnd'];\n};\n\n/**\n * A hook that manages CSS custom properties for slide direction based on positioning placement.\n *\n * It wraps the `onPositioningEnd` callback to set `--fui-positioning-slide-direction-x` and\n * `--fui-positioning-slide-direction-y` CSS custom properties on the positioned element,\n * and registers them via `CSS.registerProperty` to avoid properties propagation down to a DOM tree.\n *\n * @returns The wrapped `onPositioningEnd` handler to pass to the positioning config.\n */\nexport function usePositioningSlideDirection(\n options: UsePositioningSlideDirectionOptions,\n): NonNullable<PositioningProps['onPositioningEnd']> {\n const { targetDocument, onPositioningEnd } = options;\n\n const handlePositionEnd: NonNullable<PositioningProps['onPositioningEnd']> = useEventCallback(e => {\n onPositioningEnd?.(e);\n\n const element = e.target;\n const placement = e.detail.placement;\n\n if (!isHTMLElement(element)) {\n return;\n }\n\n const { x, y } = getPlacementSlideDirections(placement);\n\n element.style.setProperty(POSITIONING_SLIDE_DIRECTION_VAR_X, `${x}px`);\n element.style.setProperty(POSITIONING_SLIDE_DIRECTION_VAR_Y, `${y}px`);\n });\n\n React.useEffect(() => {\n const css = targetDocument?.defaultView?.CSS;\n if (!targetDocument || !css?.registerProperty) {\n return;\n }\n let properties = registeredProperties.get(targetDocument);\n if (!properties) {\n properties = new Set<string>();\n registeredProperties.set(targetDocument, properties);\n }\n for (const name of slideDirectionProperties) {\n if (properties.has(name)) {\n continue;\n }\n try {\n css.registerProperty({ name, syntax: '<length>', inherits: false, initialValue: '0px' });\n properties.add(name);\n } catch (error) {\n if (error && typeof error === 'object' && 'name' in error && error.name === 'InvalidModificationError') {\n properties.add(name);\n }\n }\n }\n }, [targetDocument]);\n\n return handlePositionEnd;\n}\n"],"names":["React","useEventCallback","isHTMLElement","POSITIONING_SLIDE_DIRECTION_VAR_X","POSITIONING_SLIDE_DIRECTION_VAR_Y","registeredProperties","WeakMap","slideDirectionProperties","getPlacementSlideDirections","placement","side","split","x","y","usePositioningSlideDirection","options","targetDocument","onPositioningEnd","handlePositionEnd","e","element","target","detail","style","setProperty","useEffect","css","defaultView","CSS","registerProperty","properties","get","Set","set","name","has","syntax","inherits","initialValue","add","error"],"mappings":"AAAA;AAEA,YAAYA,WAAW,QAAQ;AAC/B,SAASC,gBAAgB,EAAEC,aAAa,QAAQ,4BAA4B;AAE5E,SAASC,iCAAiC,EAAEC,iCAAiC,QAAQ,iBAAc;AAEnG,MAAMC,uBAAuB,IAAIC;AACjC,MAAMC,2BAA2B;IAACJ;IAAmCC;CAAkC;AAEvG;;;CAGC,GACD,OAAO,SAASI,4BAA4BC,SAAiB;IAC3D,MAAMC,OAAOD,UAAUE,KAAK,CAAC,IAAI,CAAC,EAAE;IACpC,4CAA4C;IAC5C,IAAIC,IAAI;IACR,IAAIC,IAAI;IAER,IAAIH,SAAS,SAAS;QACpBE,IAAI,CAAC;QACLC,IAAI;IACN,OAAO,IAAIH,SAAS,UAAU;QAC5BE,IAAI;QACJC,IAAI,CAAC;IACP,OAAO,IAAIH,SAAS,QAAQ;QAC1BE,IAAI;QACJC,IAAI;IACN;IAEA,OAAO;QAAED;QAAGC;IAAE;AAChB;AASA;;;;;;;;CAQC,GACD,OAAO,SAASC,6BACdC,OAA4C;IAE5C,MAAM,EAAEC,cAAc,EAAEC,gBAAgB,EAAE,GAAGF;IAE7C,MAAMG,oBAAuEjB,iBAAiBkB,CAAAA;QAC5FF,6BAAAA,uCAAAA,iBAAmBE;QAEnB,MAAMC,UAAUD,EAAEE,MAAM;QACxB,MAAMZ,YAAYU,EAAEG,MAAM,CAACb,SAAS;QAEpC,IAAI,CAACP,cAAckB,UAAU;YAC3B;QACF;QAEA,MAAM,EAAER,CAAC,EAAEC,CAAC,EAAE,GAAGL,4BAA4BC;QAE7CW,QAAQG,KAAK,CAACC,WAAW,CAACrB,mCAAmC,GAAGS,EAAE,EAAE,CAAC;QACrEQ,QAAQG,KAAK,CAACC,WAAW,CAACpB,mCAAmC,GAAGS,EAAE,EAAE,CAAC;IACvE;IAEAb,MAAMyB,SAAS,CAAC;YACFT;QAAZ,MAAMU,MAAMV,2BAAAA,sCAAAA,8BAAAA,eAAgBW,WAAW,cAA3BX,kDAAAA,4BAA6BY,GAAG;QAC5C,IAAI,CAACZ,kBAAkB,EAACU,gBAAAA,0BAAAA,IAAKG,gBAAgB,GAAE;YAC7C;QACF;QACA,IAAIC,aAAazB,qBAAqB0B,GAAG,CAACf;QAC1C,IAAI,CAACc,YAAY;YACfA,aAAa,IAAIE;YACjB3B,qBAAqB4B,GAAG,CAACjB,gBAAgBc;QAC3C;QACA,KAAK,MAAMI,QAAQ3B,yBAA0B;YAC3C,IAAIuB,WAAWK,GAAG,CAACD,OAAO;gBACxB;YACF;YACA,IAAI;gBACFR,IAAIG,gBAAgB,CAAC;oBAAEK;oBAAME,QAAQ;oBAAYC,UAAU;oBAAOC,cAAc;gBAAM;gBACtFR,WAAWS,GAAG,CAACL;YACjB,EAAE,OAAOM,OAAO;gBACd,IAAIA,SAAS,OAAOA,UAAU,YAAY,UAAUA,SAASA,MAAMN,IAAI,KAAK,4BAA4B;oBACtGJ,WAAWS,GAAG,CAACL;gBACjB;YACF;QACF;IACF,GAAG;QAAClB;KAAe;IAEnB,OAAOE;AACT"}
|
|
@@ -3,21 +3,10 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
get: all[name]
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
_export(exports, {
|
|
13
|
-
SafeZoneArea: function() {
|
|
6
|
+
Object.defineProperty(exports, "SafeZoneArea", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function() {
|
|
14
9
|
return SafeZoneArea;
|
|
15
|
-
},
|
|
16
|
-
isSameCoordinates: function() {
|
|
17
|
-
return isSameCoordinates;
|
|
18
|
-
},
|
|
19
|
-
isSameRect: function() {
|
|
20
|
-
return isSameRect;
|
|
21
10
|
}
|
|
22
11
|
});
|
|
23
12
|
const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildcard");
|
|
@@ -30,140 +19,83 @@ const _getMouseAnchor = require("./getMouseAnchor.cjs");
|
|
|
30
19
|
const _pointsToSvgPath = require("./pointsToSvgPath.cjs");
|
|
31
20
|
const _SafeZoneAreastyles = require("./SafeZoneArea.styles.cjs");
|
|
32
21
|
const _computeOutsideClipPath = require("./computeOutsideClipPath.cjs");
|
|
33
|
-
|
|
34
|
-
const EMPTY_RECT = {
|
|
35
|
-
top: 0,
|
|
36
|
-
right: 0,
|
|
37
|
-
bottom: 0,
|
|
38
|
-
left: 0,
|
|
39
|
-
width: 0,
|
|
40
|
-
height: 0,
|
|
41
|
-
x: 0,
|
|
42
|
-
y: 0,
|
|
43
|
-
toJSON () {
|
|
44
|
-
return '';
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
function isSameRect(a, b) {
|
|
48
|
-
return a.top === b.top && a.right === b.right && a.bottom === b.bottom && a.left === b.left && a.width === b.width && a.height === b.height;
|
|
49
|
-
}
|
|
50
|
-
function isSameCoordinates(a, b) {
|
|
51
|
-
return a[0] === b[0] && a[1] === b[1];
|
|
52
|
-
}
|
|
22
|
+
const _getSafeZonePoints = require("./getSafeZonePoints.cjs");
|
|
53
23
|
const SafeZoneArea = /*#__PURE__*/ _react1.memo((props)=>{
|
|
54
24
|
const { debug, onMouseEnter, onMouseMove, onMouseLeave, stateStore } = props;
|
|
55
25
|
const clipPathId = (0, _reactutilities.useId)();
|
|
56
26
|
const styles = (0, _SafeZoneAreastyles.useStyles)();
|
|
57
27
|
const active = (0, _shim.useSyncExternalStore)(stateStore.subscribe, stateStore.isActive);
|
|
58
28
|
const svgRef = _react1.useRef(null);
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
mouseCoordinates: [
|
|
63
|
-
0,
|
|
64
|
-
0
|
|
65
|
-
]
|
|
66
|
-
}));
|
|
29
|
+
const safeZoneRef = _react1.useRef(null);
|
|
30
|
+
const clipPathRef = _react1.useRef(null);
|
|
31
|
+
const rectDebugRef = _react1.useRef(null);
|
|
67
32
|
_react1.useImperativeHandle(props.imperativeRef, ()=>({
|
|
68
|
-
updateSVG (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
33
|
+
updateSVG ({ containerRect, targetRect, mouseCoordinates }) {
|
|
34
|
+
var _safeZoneRef_current, _clipPathRef_current, _rectDebugRef_current;
|
|
35
|
+
const topOffset = Math.min(targetRect.top, containerRect.top);
|
|
36
|
+
const leftOffset = Math.min(targetRect.left, containerRect.left);
|
|
37
|
+
const bottomOffset = Math.max(targetRect.bottom, containerRect.bottom);
|
|
38
|
+
const rightOffset = Math.max(targetRect.right, containerRect.right);
|
|
39
|
+
const containerCorners = (0, _getRectCorners.getRectCorners)(containerRect, [
|
|
40
|
+
leftOffset,
|
|
41
|
+
topOffset
|
|
42
|
+
]);
|
|
43
|
+
const targetCorners = (0, _getRectCorners.getRectCorners)(targetRect, [
|
|
44
|
+
leftOffset,
|
|
45
|
+
topOffset
|
|
46
|
+
]);
|
|
47
|
+
// SVG coordinates are relative to its top-left corner.
|
|
48
|
+
const relativeMouseCoordinates = [
|
|
49
|
+
mouseCoordinates[0] - leftOffset,
|
|
50
|
+
mouseCoordinates[1] - topOffset
|
|
51
|
+
];
|
|
52
|
+
const mouseAnchor = (0, _getMouseAnchor.getMouseAnchor)(containerCorners.topLeft, containerCorners.bottomRight, relativeMouseCoordinates);
|
|
53
|
+
const svgWidth = rightOffset - leftOffset;
|
|
54
|
+
const svgHeight = bottomOffset - topOffset;
|
|
55
|
+
const clipPath = (0, _computeOutsideClipPath.computeOutsideClipPath)(svgWidth, svgHeight, {
|
|
56
|
+
x: targetCorners.topLeft[0],
|
|
57
|
+
y: targetCorners.topLeft[1],
|
|
58
|
+
width: targetRect.width,
|
|
59
|
+
height: targetRect.height
|
|
76
60
|
});
|
|
61
|
+
if (svgRef.current) {
|
|
62
|
+
svgRef.current.style.width = `${svgWidth}px`;
|
|
63
|
+
svgRef.current.style.height = `${svgHeight}px`;
|
|
64
|
+
svgRef.current.style.transform = `translate(${leftOffset}px, ${topOffset}px)`;
|
|
65
|
+
}
|
|
66
|
+
const safeZonePoints = (0, _getSafeZonePoints.getSafeZonePoints)(mouseAnchor, containerCorners);
|
|
67
|
+
(_safeZoneRef_current = safeZoneRef.current) === null || _safeZoneRef_current === void 0 ? void 0 : _safeZoneRef_current.setAttribute('d', safeZonePoints.length > 0 ? (0, _pointsToSvgPath.pointsToSvgPath)(safeZonePoints) : '');
|
|
68
|
+
(_clipPathRef_current = clipPathRef.current) === null || _clipPathRef_current === void 0 ? void 0 : _clipPathRef_current.setAttribute('d', clipPath);
|
|
69
|
+
(_rectDebugRef_current = rectDebugRef.current) === null || _rectDebugRef_current === void 0 ? void 0 : _rectDebugRef_current.setAttribute('d', clipPath);
|
|
77
70
|
}
|
|
78
71
|
}), []);
|
|
79
|
-
const { containerRect, targetRect, mouseCoordinates } = state;
|
|
80
|
-
const topOffset = Math.min(targetRect.top, containerRect.top);
|
|
81
|
-
const leftOffset = Math.min(targetRect.left, containerRect.left);
|
|
82
|
-
const bottomOffset = Math.max(targetRect.bottom, containerRect.bottom);
|
|
83
|
-
const rightOffset = Math.max(targetRect.right, containerRect.right);
|
|
84
|
-
// ---
|
|
85
|
-
const containerCorners = (0, _getRectCorners.getRectCorners)(containerRect, [
|
|
86
|
-
leftOffset,
|
|
87
|
-
topOffset
|
|
88
|
-
]);
|
|
89
|
-
const targetCorners = (0, _getRectCorners.getRectCorners)(targetRect, [
|
|
90
|
-
leftOffset,
|
|
91
|
-
topOffset
|
|
92
|
-
]);
|
|
93
|
-
// Heads up!
|
|
94
|
-
// The SVG coordinate system starts at the top-left corner of the SVG element,
|
|
95
|
-
// so we need to adjust the mouse coordinates relative to the SVG's top-left corner.
|
|
96
|
-
const relativeMouseCoordinates = [
|
|
97
|
-
mouseCoordinates[0] - leftOffset,
|
|
98
|
-
mouseCoordinates[1] - topOffset
|
|
99
|
-
];
|
|
100
|
-
const mouseAnchor = (0, _getMouseAnchor.getMouseAnchor)(containerCorners.topLeft, containerCorners.bottomRight, relativeMouseCoordinates);
|
|
101
|
-
const triangleA = [
|
|
102
|
-
mouseAnchor,
|
|
103
|
-
containerCorners.topLeft,
|
|
104
|
-
containerCorners.topRight
|
|
105
|
-
];
|
|
106
|
-
const triangleB = [
|
|
107
|
-
mouseAnchor,
|
|
108
|
-
containerCorners.topRight,
|
|
109
|
-
containerCorners.bottomRight
|
|
110
|
-
];
|
|
111
|
-
const triangleC = [
|
|
112
|
-
mouseAnchor,
|
|
113
|
-
containerCorners.bottomRight,
|
|
114
|
-
containerCorners.bottomLeft
|
|
115
|
-
];
|
|
116
|
-
const triangleD = [
|
|
117
|
-
mouseAnchor,
|
|
118
|
-
containerCorners.bottomLeft,
|
|
119
|
-
containerCorners.topLeft
|
|
120
|
-
];
|
|
121
|
-
const svgWidth = rightOffset - leftOffset;
|
|
122
|
-
const svgHeight = bottomOffset - topOffset;
|
|
123
|
-
const clipPath = (0, _computeOutsideClipPath.computeOutsideClipPath)(svgWidth, svgHeight, {
|
|
124
|
-
x: targetCorners.topLeft[0],
|
|
125
|
-
y: targetCorners.topLeft[1],
|
|
126
|
-
width: targetRect.width,
|
|
127
|
-
height: targetRect.height
|
|
128
|
-
}, {
|
|
129
|
-
x: containerCorners.topLeft[0],
|
|
130
|
-
y: containerCorners.topLeft[1],
|
|
131
|
-
width: containerRect.width,
|
|
132
|
-
height: containerRect.height
|
|
133
|
-
});
|
|
134
72
|
return /*#__PURE__*/ _react1.createElement("div", {
|
|
135
73
|
className: (0, _react.mergeClasses)(styles.wrapper, active && styles.wrapperActive),
|
|
136
74
|
"data-safe-zone": ""
|
|
137
|
-
},
|
|
75
|
+
}, /*#__PURE__*/ _react1.createElement("svg", {
|
|
138
76
|
"aria-hidden": true,
|
|
139
77
|
className: styles.svg,
|
|
140
78
|
xmlns: "http://www.w3.org/2000/svg",
|
|
141
79
|
ref: svgRef,
|
|
142
80
|
style: {
|
|
143
|
-
width:
|
|
144
|
-
height:
|
|
145
|
-
transform:
|
|
81
|
+
width: 0,
|
|
82
|
+
height: 0,
|
|
83
|
+
transform: 'translate(0px, 0px)'
|
|
146
84
|
}
|
|
147
85
|
}, /*#__PURE__*/ _react1.createElement("g", {
|
|
148
|
-
className: (0, _react.mergeClasses)(styles.
|
|
86
|
+
className: (0, _react.mergeClasses)(styles.safeZone, debug && styles.safeZoneDebug),
|
|
149
87
|
clipPath: `url(#${clipPathId})`,
|
|
150
88
|
onMouseEnter: onMouseEnter,
|
|
151
89
|
onMouseMove: onMouseMove,
|
|
152
90
|
onMouseLeave: onMouseLeave
|
|
153
91
|
}, /*#__PURE__*/ _react1.createElement("path", {
|
|
154
|
-
|
|
155
|
-
}), /*#__PURE__*/ _react1.createElement("path", {
|
|
156
|
-
d: (0, _pointsToSvgPath.pointsToSvgPath)(triangleB)
|
|
157
|
-
}), /*#__PURE__*/ _react1.createElement("path", {
|
|
158
|
-
d: (0, _pointsToSvgPath.pointsToSvgPath)(triangleC)
|
|
159
|
-
}), /*#__PURE__*/ _react1.createElement("path", {
|
|
160
|
-
d: (0, _pointsToSvgPath.pointsToSvgPath)(triangleD)
|
|
92
|
+
ref: safeZoneRef
|
|
161
93
|
})), /*#__PURE__*/ _react1.createElement("clipPath", {
|
|
162
94
|
id: clipPathId
|
|
163
95
|
}, /*#__PURE__*/ _react1.createElement("path", {
|
|
164
|
-
|
|
96
|
+
ref: clipPathRef
|
|
165
97
|
})), debug && /*#__PURE__*/ _react1.createElement("path", {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}))
|
|
98
|
+
ref: rectDebugRef,
|
|
99
|
+
className: styles.rectDebug
|
|
100
|
+
})));
|
|
169
101
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useSafeZoneArea/SafeZoneArea.tsx"],"sourcesContent":["'use client';\n\nimport { mergeClasses } from '@griffel/react';\nimport { useId } from '@fluentui/react-utilities';\nimport type { JSXElement } from '@fluentui/react-utilities';\nimport * as React from 'react';\nimport { useSyncExternalStore } from 'use-sync-external-store/shim';\n\nimport type { createSafeZoneAreaStateStore } from './createSafeZoneAreaStateStore';\nimport { getRectCorners } from './getRectCorners';\nimport { getMouseAnchor } from './getMouseAnchor';\nimport { pointsToSvgPath } from './pointsToSvgPath';\nimport { useStyles } from './SafeZoneArea.styles';\nimport type { Point } from './types';\nimport { computeOutsideClipPath } from './computeOutsideClipPath';\n\nexport type SafeZoneAreaImperativeHandle = {\n updateSVG: (options: { containerRect: DOMRect; targetRect: DOMRect; mouseCoordinates: Point }) => void;\n};\n\nexport type SafeZoneAreaProps = {\n /** Enables debug mode: makes drawn shapes visible. */\n debug: boolean;\n\n /** A reference to the SafeZoneArea imperative handle. */\n imperativeRef: React.Ref<SafeZoneAreaImperativeHandle>;\n\n // eslint-disable-next-line @nx/workspace-consistent-callback-type\n onMouseEnter: (e: React.MouseEvent) => void;\n // eslint-disable-next-line @nx/workspace-consistent-callback-type\n onMouseMove: (e: React.MouseEvent) => void;\n // eslint-disable-next-line @nx/workspace-consistent-callback-type\n onMouseLeave: (e: React.MouseEvent) => void;\n\n stateStore: ReturnType<typeof createSafeZoneAreaStateStore>;\n};\n\n/**\n * @internal\n */\ntype SafeZoneAreaState = {\n containerRect: DOMRect;\n targetRect: DOMRect;\n mouseCoordinates: Point;\n};\n\n// ---\n\nconst EMPTY_RECT: DOMRect = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n width: 0,\n height: 0,\n x: 0,\n y: 0,\n toJSON() {\n return '';\n },\n};\n\nexport function isSameRect(a: DOMRect, b: DOMRect): boolean {\n return (\n a.top === b.top &&\n a.right === b.right &&\n a.bottom === b.bottom &&\n a.left === b.left &&\n a.width === b.width &&\n a.height === b.height\n );\n}\n\nexport function isSameCoordinates(a: Point, b: Point): boolean {\n return a[0] === b[0] && a[1] === b[1];\n}\n\n// ---\n\n/**\n * A component that renders a safe zone area with SVG shapes. Uses `useSyncExternalStore` to manage its active state\n * to avoid causing re-renders in `useSafeZoneArea()` as the hook might be used in host components like `Menu`.\n *\n * Draws two shapes:\n * - a triangle that points to the target element which is an actual safe zone\n * - a rectangle for a clip path that clips out the target element\n *\n * @internal\n */\nexport const SafeZoneArea = React.memo((props: SafeZoneAreaProps): JSXElement => {\n const { debug, onMouseEnter, onMouseMove, onMouseLeave, stateStore } = props;\n\n const clipPathId = useId();\n const styles = useStyles();\n\n const active = useSyncExternalStore(stateStore.subscribe, stateStore.isActive);\n const svgRef = React.useRef<SVGSVGElement>(null);\n\n const [state, setState] = React.useState<SafeZoneAreaState>(() => ({\n containerRect: EMPTY_RECT,\n targetRect: EMPTY_RECT,\n mouseCoordinates: [0, 0],\n }));\n\n React.useImperativeHandle(\n props.imperativeRef,\n () => ({\n updateSVG(newState) {\n setState(prevState => {\n // Heads up!\n // A small optimization to avoid unnecessary re-renders\n if (\n isSameRect(prevState.containerRect, newState.containerRect) &&\n isSameRect(prevState.targetRect, newState.targetRect) &&\n isSameCoordinates(prevState.mouseCoordinates, newState.mouseCoordinates)\n ) {\n return prevState;\n }\n\n return newState;\n });\n },\n }),\n [],\n );\n\n const { containerRect, targetRect, mouseCoordinates } = state;\n\n const topOffset = Math.min(targetRect.top, containerRect.top);\n const leftOffset = Math.min(targetRect.left, containerRect.left);\n const bottomOffset = Math.max(targetRect.bottom, containerRect.bottom);\n const rightOffset = Math.max(targetRect.right, containerRect.right);\n\n // ---\n\n const containerCorners = getRectCorners(containerRect, [leftOffset, topOffset]);\n const targetCorners = getRectCorners(targetRect, [leftOffset, topOffset]);\n\n // Heads up!\n // The SVG coordinate system starts at the top-left corner of the SVG element,\n // so we need to adjust the mouse coordinates relative to the SVG's top-left corner.\n const relativeMouseCoordinates: Point = [mouseCoordinates[0] - leftOffset, mouseCoordinates[1] - topOffset];\n const mouseAnchor = getMouseAnchor(containerCorners.topLeft, containerCorners.bottomRight, relativeMouseCoordinates);\n\n const triangleA = [mouseAnchor, containerCorners.topLeft, containerCorners.topRight];\n const triangleB = [mouseAnchor, containerCorners.topRight, containerCorners.bottomRight];\n const triangleC = [mouseAnchor, containerCorners.bottomRight, containerCorners.bottomLeft];\n const triangleD = [mouseAnchor, containerCorners.bottomLeft, containerCorners.topLeft];\n\n const svgWidth = rightOffset - leftOffset;\n const svgHeight = bottomOffset - topOffset;\n\n const clipPath = computeOutsideClipPath(\n svgWidth,\n svgHeight,\n {\n x: targetCorners.topLeft[0],\n y: targetCorners.topLeft[1],\n width: targetRect.width,\n height: targetRect.height,\n },\n {\n x: containerCorners.topLeft[0],\n y: containerCorners.topLeft[1],\n width: containerRect.width,\n height: containerRect.height,\n },\n );\n\n return (\n <div className={mergeClasses(styles.wrapper, active && styles.wrapperActive)} data-safe-zone=\"\">\n {active ? (\n <svg\n aria-hidden\n className={styles.svg}\n xmlns=\"http://www.w3.org/2000/svg\"\n ref={svgRef}\n style={{\n width: `${svgWidth}px`,\n height: `${svgHeight}px`,\n transform: `translate(${leftOffset}px, ${topOffset}px)`,\n }}\n >\n <g\n className={mergeClasses(styles.triangle, debug && styles.triangleDebug)}\n clipPath={`url(#${clipPathId})`}\n onMouseEnter={onMouseEnter}\n onMouseMove={onMouseMove}\n onMouseLeave={onMouseLeave}\n >\n <path d={pointsToSvgPath(triangleA)} />\n <path d={pointsToSvgPath(triangleB)} />\n <path d={pointsToSvgPath(triangleC)} />\n <path d={pointsToSvgPath(triangleD)} />\n </g>\n\n <clipPath id={clipPathId}>\n <path d={clipPath} />\n </clipPath>\n\n {debug && <path className={styles.rectDebug} d={clipPath} />}\n </svg>\n ) : null}\n </div>\n );\n});\n"],"names":["mergeClasses","useId","React","useSyncExternalStore","getRectCorners","getMouseAnchor","pointsToSvgPath","useStyles","computeOutsideClipPath","EMPTY_RECT","top","right","bottom","left","width","height","x","y","toJSON","isSameRect","a","b","isSameCoordinates","SafeZoneArea","memo","props","debug","onMouseEnter","onMouseMove","onMouseLeave","stateStore","clipPathId","styles","active","subscribe","isActive","svgRef","useRef","state","setState","useState","containerRect","targetRect","mouseCoordinates","useImperativeHandle","imperativeRef","updateSVG","newState","prevState","topOffset","Math","min","leftOffset","bottomOffset","max","rightOffset","containerCorners","targetCorners","relativeMouseCoordinates","mouseAnchor","topLeft","bottomRight","triangleA","topRight","triangleB","triangleC","bottomLeft","triangleD","svgWidth","svgHeight","clipPath","div","className","wrapper","wrapperActive","data-safe-zone","svg","aria-hidden","xmlns","ref","style","transform","g","triangle","triangleDebug","path","d","id","rectDebug"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useSafeZoneArea/SafeZoneArea.tsx"],"sourcesContent":["'use client';\n\nimport { mergeClasses } from '@griffel/react';\nimport { useId } from '@fluentui/react-utilities';\nimport type { JSXElement } from '@fluentui/react-utilities';\nimport * as React from 'react';\nimport { useSyncExternalStore } from 'use-sync-external-store/shim';\n\nimport type { createSafeZoneAreaStateStore } from './createSafeZoneAreaStateStore';\nimport { getRectCorners } from './getRectCorners';\nimport { getMouseAnchor } from './getMouseAnchor';\nimport { pointsToSvgPath } from './pointsToSvgPath';\nimport { useStyles } from './SafeZoneArea.styles';\nimport type { Point } from './types';\nimport { computeOutsideClipPath } from './computeOutsideClipPath';\nimport { getSafeZonePoints } from './getSafeZonePoints';\n\nexport type SafeZoneAreaImperativeHandle = {\n updateSVG: (options: { containerRect: DOMRect; targetRect: DOMRect; mouseCoordinates: Point }) => void;\n};\n\nexport type SafeZoneAreaProps = {\n /** Enables debug mode: makes drawn shapes visible. */\n debug: boolean;\n\n /** A reference to the SafeZoneArea imperative handle. */\n imperativeRef: React.Ref<SafeZoneAreaImperativeHandle>;\n\n // eslint-disable-next-line @nx/workspace-consistent-callback-type\n onMouseEnter: (e: React.MouseEvent) => void;\n // eslint-disable-next-line @nx/workspace-consistent-callback-type\n onMouseMove: (e: React.MouseEvent) => void;\n // eslint-disable-next-line @nx/workspace-consistent-callback-type\n onMouseLeave: (e: React.MouseEvent) => void;\n\n stateStore: ReturnType<typeof createSafeZoneAreaStateStore>;\n};\n\n// ---\n\n/**\n * A component that renders a safe zone area with SVG shapes. Uses `useSyncExternalStore` to manage its active state\n * to avoid causing re-renders in `useSafeZoneArea()` as the hook might be used in host components like `Menu`.\n *\n * Draws a polygon from the mouse to the facing edges of the container and clips out the target element.\n *\n * @internal\n */\nexport const SafeZoneArea = React.memo((props: SafeZoneAreaProps): JSXElement => {\n const { debug, onMouseEnter, onMouseMove, onMouseLeave, stateStore } = props;\n\n const clipPathId = useId();\n const styles = useStyles();\n\n const active = useSyncExternalStore(stateStore.subscribe, stateStore.isActive);\n const svgRef = React.useRef<SVGSVGElement>(null);\n const safeZoneRef = React.useRef<SVGPathElement>(null);\n const clipPathRef = React.useRef<SVGPathElement>(null);\n const rectDebugRef = React.useRef<SVGPathElement>(null);\n\n React.useImperativeHandle(\n props.imperativeRef,\n () => ({\n updateSVG({ containerRect, targetRect, mouseCoordinates }) {\n const topOffset = Math.min(targetRect.top, containerRect.top);\n const leftOffset = Math.min(targetRect.left, containerRect.left);\n const bottomOffset = Math.max(targetRect.bottom, containerRect.bottom);\n const rightOffset = Math.max(targetRect.right, containerRect.right);\n\n const containerCorners = getRectCorners(containerRect, [leftOffset, topOffset]);\n const targetCorners = getRectCorners(targetRect, [leftOffset, topOffset]);\n\n // SVG coordinates are relative to its top-left corner.\n const relativeMouseCoordinates: Point = [mouseCoordinates[0] - leftOffset, mouseCoordinates[1] - topOffset];\n const mouseAnchor = getMouseAnchor(\n containerCorners.topLeft,\n containerCorners.bottomRight,\n relativeMouseCoordinates,\n );\n\n const svgWidth = rightOffset - leftOffset;\n const svgHeight = bottomOffset - topOffset;\n const clipPath = computeOutsideClipPath(svgWidth, svgHeight, {\n x: targetCorners.topLeft[0],\n y: targetCorners.topLeft[1],\n width: targetRect.width,\n height: targetRect.height,\n });\n\n if (svgRef.current) {\n svgRef.current.style.width = `${svgWidth}px`;\n svgRef.current.style.height = `${svgHeight}px`;\n svgRef.current.style.transform = `translate(${leftOffset}px, ${topOffset}px)`;\n }\n\n const safeZonePoints = getSafeZonePoints(mouseAnchor, containerCorners);\n safeZoneRef.current?.setAttribute('d', safeZonePoints.length > 0 ? pointsToSvgPath(safeZonePoints) : '');\n clipPathRef.current?.setAttribute('d', clipPath);\n rectDebugRef.current?.setAttribute('d', clipPath);\n },\n }),\n [],\n );\n\n return (\n <div className={mergeClasses(styles.wrapper, active && styles.wrapperActive)} data-safe-zone=\"\">\n <svg\n aria-hidden\n className={styles.svg}\n xmlns=\"http://www.w3.org/2000/svg\"\n ref={svgRef}\n style={{ width: 0, height: 0, transform: 'translate(0px, 0px)' }}\n >\n <g\n className={mergeClasses(styles.safeZone, debug && styles.safeZoneDebug)}\n clipPath={`url(#${clipPathId})`}\n onMouseEnter={onMouseEnter}\n onMouseMove={onMouseMove}\n onMouseLeave={onMouseLeave}\n >\n <path ref={safeZoneRef} />\n </g>\n\n <clipPath id={clipPathId}>\n <path ref={clipPathRef} />\n </clipPath>\n\n {debug && <path ref={rectDebugRef} className={styles.rectDebug} />}\n </svg>\n </div>\n );\n});\n"],"names":["mergeClasses","useId","React","useSyncExternalStore","getRectCorners","getMouseAnchor","pointsToSvgPath","useStyles","computeOutsideClipPath","getSafeZonePoints","SafeZoneArea","memo","props","debug","onMouseEnter","onMouseMove","onMouseLeave","stateStore","clipPathId","styles","active","subscribe","isActive","svgRef","useRef","safeZoneRef","clipPathRef","rectDebugRef","useImperativeHandle","imperativeRef","updateSVG","containerRect","targetRect","mouseCoordinates","topOffset","Math","min","top","leftOffset","left","bottomOffset","max","bottom","rightOffset","right","containerCorners","targetCorners","relativeMouseCoordinates","mouseAnchor","topLeft","bottomRight","svgWidth","svgHeight","clipPath","x","y","width","height","current","style","transform","safeZonePoints","setAttribute","length","div","className","wrapper","wrapperActive","data-safe-zone","svg","aria-hidden","xmlns","ref","g","safeZone","safeZoneDebug","path","id","rectDebug"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["SafeZoneArea.styles.js"],"sourcesContent":["'use client';\nimport { makeStyles } from \"@griffel/react\";\nimport { tokens } from \"@fluentui/react-theme\";\nexport const useStyles = makeStyles({\n wrapper: {\n display: 'none',\n height: 0,\n width: 0,\n pointerEvents: 'none'\n },\n wrapperActive: {\n display: 'block'\n },\n svg: {\n fill: 'transparent',\n pointerEvents: 'none',\n position: 'fixed',\n top: 0,\n left: 0\n },\n
|
|
1
|
+
{"version":3,"sources":["SafeZoneArea.styles.js"],"sourcesContent":["'use client';\nimport { makeStyles } from \"@griffel/react\";\nimport { tokens } from \"@fluentui/react-theme\";\nexport const useStyles = makeStyles({\n wrapper: {\n display: 'none',\n height: 0,\n width: 0,\n pointerEvents: 'none'\n },\n wrapperActive: {\n display: 'block'\n },\n svg: {\n fill: 'transparent',\n pointerEvents: 'none',\n position: 'fixed',\n top: 0,\n left: 0\n },\n safeZone: {\n pointerEvents: 'auto'\n },\n safeZoneDebug: {\n cursor: 'crosshair',\n fill: `color-mix(in srgb, ${tokens.colorPaletteGreenBackground3} 20%, transparent)`\n },\n rectDebug: {\n fill: `color-mix(in srgb, ${tokens.colorPaletteRedBackground3} 20%, transparent)`\n }\n});\n"],"names":["__styles","tokens","useStyles","wrapper","mc9l5x","Bqenvij","a9b677","Bkecrkj","wrapperActive","svg","Bkfmm31","qhf8xq","Bhzewxz","oyh7mz","safeZone","safeZoneDebug","Bceei9c","rectDebug","d"],"mappings":"AAAA,YAAY;;;;;+BAGCE,SAAS;;;;;;uBAFK,gBAAgB;AAEpC,kBAAe,WAAA,OAAGF,eAAA,EAAA;IAAAG,OAAA,EAAA;QAAAC,MAAA,EAAA;QAAAC,OAAA,EAAA;QAAAC,MAAA,EAAA;QAAAC,OAAA,EAAA;IAAA;IAAAC,aAAA,EAAA;QAAAJ,MAAA,EAAA;IAAA;IAAAK,GAAA,EAAA;QAAAC,OAAA,EAAA;QAAAH,OAAA,EAAA;QAAAI,MAAA,EAAA;QAAAC,OAAA,EAAA;QAAAC,MAAA,EAAA;YAAA;YAAA;SAAA;IAAA;IAAAC,QAAA,EAAA;QAAAP,OAAA,EAAA;IAAA;IAAAQ,aAAA,EAAA;QAAAC,OAAA,EAAA;QAAAN,OAAA,EAAA;IAAA;IAAAO,SAAA,EAAA;QAAAP,OAAA,EAAA;IAAA;AAAA,GAAA;IAAAQ,CAAA,EAAA;QAAA;QAAA;QAAA;QAAA;QAAA;QAAA;QAAA;QAAA;QAAA;QAAA;QAAA;QAAA;QAAA;QAAA;KAAA;AAAA,CA2BxB,CAAC"}
|
|
@@ -28,10 +28,10 @@ const useStyles = (0, _react.makeStyles)({
|
|
|
28
28
|
top: 0,
|
|
29
29
|
left: 0
|
|
30
30
|
},
|
|
31
|
-
|
|
31
|
+
safeZone: {
|
|
32
32
|
pointerEvents: 'auto'
|
|
33
33
|
},
|
|
34
|
-
|
|
34
|
+
safeZoneDebug: {
|
|
35
35
|
cursor: 'crosshair',
|
|
36
36
|
fill: `color-mix(in srgb, ${_reacttheme.tokens.colorPaletteGreenBackground3} 20%, transparent)`
|
|
37
37
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useSafeZoneArea/SafeZoneArea.styles.ts"],"sourcesContent":["'use client';\n\nimport { makeStyles } from '@griffel/react';\nimport { tokens } from '@fluentui/react-theme';\n\nexport const useStyles = makeStyles({\n wrapper: {\n display: 'none',\n height: 0,\n width: 0,\n pointerEvents: 'none',\n },\n wrapperActive: {\n display: 'block',\n },\n svg: {\n fill: 'transparent',\n pointerEvents: 'none',\n position: 'fixed',\n top: 0,\n left: 0,\n },\n
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useSafeZoneArea/SafeZoneArea.styles.ts"],"sourcesContent":["'use client';\n\nimport { makeStyles } from '@griffel/react';\nimport { tokens } from '@fluentui/react-theme';\n\nexport const useStyles = makeStyles({\n wrapper: {\n display: 'none',\n height: 0,\n width: 0,\n pointerEvents: 'none',\n },\n wrapperActive: {\n display: 'block',\n },\n svg: {\n fill: 'transparent',\n pointerEvents: 'none',\n position: 'fixed',\n top: 0,\n left: 0,\n },\n safeZone: {\n pointerEvents: 'auto',\n },\n safeZoneDebug: {\n cursor: 'crosshair',\n fill: `color-mix(in srgb, ${tokens.colorPaletteGreenBackground3} 20%, transparent)`,\n },\n rectDebug: {\n fill: `color-mix(in srgb, ${tokens.colorPaletteRedBackground3} 20%, transparent)`,\n },\n});\n"],"names":["makeStyles","tokens","useStyles","wrapper","display","height","width","pointerEvents","wrapperActive","svg","fill","position","top","left","safeZone","safeZoneDebug","cursor","colorPaletteGreenBackground3","rectDebug","colorPaletteRedBackground3"],"mappings":"AAAA;;;;;+BAKaE;;;;;;uBAHc,iBAAiB;4BACrB,wBAAwB;AAExC,sBAAkBF,iBAAAA,EAAW;IAClCG,SAAS;QACPC,SAAS;QACTC,QAAQ;QACRC,OAAO;QACPC,eAAe;IACjB;IACAC,eAAe;QACbJ,SAAS;IACX;IACAK,KAAK;QACHC,MAAM;QACNH,eAAe;QACfI,UAAU;QACVC,KAAK;QACLC,MAAM;IACR;IACAC,UAAU;QACRP,eAAe;IACjB;IACAQ,eAAe;QACbC,QAAQ;QACRN,MAAM,CAAC,mBAAmB,EAAET,kBAAAA,CAAOgB,4BAA4B,CAAC,kBAAkB,CAAC;IACrF;IACAC,WAAW;QACTR,MAAM,CAAC,mBAAmB,EAAET,kBAAAA,CAAOkB,0BAA0B,CAAC,kBAAkB,CAAC;IACnF;AACF,GAAG"}
|
|
@@ -22,12 +22,9 @@ function drawRectangle(rect) {
|
|
|
22
22
|
pathData += `Z `; // Close path
|
|
23
23
|
return pathData;
|
|
24
24
|
}
|
|
25
|
-
function computeOutsideClipPath(svgWidth, svgHeight, targetRect
|
|
25
|
+
function computeOutsideClipPath(svgWidth, svgHeight, targetRect) {
|
|
26
26
|
let pathData = `M 0,0 H ${svgWidth} V ${svgHeight} H 0 Z `;
|
|
27
|
-
//
|
|
28
|
-
// The trick is to draw each rectangle in the counterclockwise direction
|
|
29
|
-
// which creates a "hole" in the main path
|
|
27
|
+
// Draw the rectangle counterclockwise to create a hole in the main path.
|
|
30
28
|
pathData += drawRectangle(targetRect);
|
|
31
|
-
pathData += drawRectangle(containerRect);
|
|
32
29
|
return pathData;
|
|
33
30
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useSafeZoneArea/computeOutsideClipPath.ts"],"sourcesContent":["function drawRectangle(rect: { x: number; y: number; width: number; height: number }): string {\n if (rect.width <= 0 || rect.height <= 0) {\n return '';\n }\n\n let pathData = '';\n\n // Creates a subpath moving in counterclockwise direction to create a hole\n\n pathData += `M ${rect.x},${rect.y} `;\n pathData += `V ${rect.y + rect.height} `; // Down to bottom-left\n pathData += `H ${rect.x + rect.width} `; // Right to bottom-right\n pathData += `V ${rect.y} `; // Up to top-right\n pathData += `H ${rect.x} `; // Left to top-left (closing)\n pathData += `Z `; // Close path\n\n return pathData;\n}\n\n/**\n * Computes a clip path that covers the area outside
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useSafeZoneArea/computeOutsideClipPath.ts"],"sourcesContent":["function drawRectangle(rect: { x: number; y: number; width: number; height: number }): string {\n if (rect.width <= 0 || rect.height <= 0) {\n return '';\n }\n\n let pathData = '';\n\n // Creates a subpath moving in counterclockwise direction to create a hole\n\n pathData += `M ${rect.x},${rect.y} `;\n pathData += `V ${rect.y + rect.height} `; // Down to bottom-left\n pathData += `H ${rect.x + rect.width} `; // Right to bottom-right\n pathData += `V ${rect.y} `; // Up to top-right\n pathData += `H ${rect.x} `; // Left to top-left (closing)\n pathData += `Z `; // Close path\n\n return pathData;\n}\n\n/**\n * Computes a clip path that covers the area outside a rectangle.\n *\n * @internal\n */\nexport function computeOutsideClipPath(\n svgWidth: number,\n svgHeight: number,\n targetRect: { x: number; y: number; width: number; height: number },\n): string {\n let pathData = `M 0,0 H ${svgWidth} V ${svgHeight} H 0 Z `;\n\n // Draw the rectangle counterclockwise to create a hole in the main path.\n\n pathData += drawRectangle(targetRect);\n\n return pathData;\n}\n"],"names":["drawRectangle","rect","width","height","pathData","x","y","computeOutsideClipPath","svgWidth","svgHeight","targetRect"],"mappings":";;;;;;;;;;AAAA,SAASA,cAAcC,IAA6D;IAClF,IAAIA,KAAKC,KAAK,IAAI,KAAKD,KAAKE,MAAM,IAAI,GAAG;QACvC,OAAO;IACT;IAEA,IAAIC,WAAW;IAEf,0EAA0E;IAE1EA,YAAY,CAAC,EAAE,EAAEH,KAAKI,CAAC,CAAC,CAAC,EAAEJ,KAAKK,CAAC,CAAC,CAAC,CAAC;IACpCF,YAAY,CAAC,EAAE,EAAEH,KAAKK,CAAC,GAAGL,KAAKE,MAAM,CAAC,CAAC,CAAC,EAAE,sBAAsB;IAChEC,YAAY,CAAC,EAAE,EAAEH,KAAKI,CAAC,GAAGJ,KAAKC,KAAK,CAAC,CAAC,CAAC,EAAE,wBAAwB;IACjEE,YAAY,CAAC,EAAE,EAAEH,KAAKK,CAAC,CAAC,CAAC,CAAC,EAAE,kBAAkB;IAC9CF,YAAY,CAAC,EAAE,EAAEH,KAAKI,CAAC,CAAC,CAAC,CAAC,EAAE,6BAA6B;IACzDD,YAAY,CAAC,EAAE,CAAC,EAAE,aAAa;IAE/B,OAAOA;AACT;AAOO,SAASG,uBACdC,QAAgB,EAChBC,SAAiB,EACjBC,UAAmE;IAEnE,IAAIN,WAAW,CAAC,QAAQ,EAAEI,SAAS,GAAG,EAAEC,UAAU,OAAO,CAAC;IAE1D,yEAAyE;IAEzEL,YAAYJ,cAAcU;IAE1B,OAAON;AACT"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the mouse anchor and the container corners facing it.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/ "use strict";
|
|
6
|
+
Object.defineProperty(exports, "__esModule", {
|
|
7
|
+
value: true
|
|
8
|
+
});
|
|
9
|
+
Object.defineProperty(exports, "getSafeZonePoints", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
get: function() {
|
|
12
|
+
return getSafeZonePoints;
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
function getSafeZonePoints(mouseAnchor, containerCorners) {
|
|
16
|
+
const { topLeft, topRight, bottomRight, bottomLeft } = containerCorners;
|
|
17
|
+
const isAbove = mouseAnchor[1] < topLeft[1];
|
|
18
|
+
const isBelow = mouseAnchor[1] > bottomLeft[1];
|
|
19
|
+
const isLeft = mouseAnchor[0] < topLeft[0];
|
|
20
|
+
const isRight = mouseAnchor[0] > topRight[0];
|
|
21
|
+
if (isAbove) {
|
|
22
|
+
if (isLeft) {
|
|
23
|
+
return [
|
|
24
|
+
mouseAnchor,
|
|
25
|
+
bottomLeft,
|
|
26
|
+
topLeft,
|
|
27
|
+
topRight
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
if (isRight) {
|
|
31
|
+
return [
|
|
32
|
+
mouseAnchor,
|
|
33
|
+
topLeft,
|
|
34
|
+
topRight,
|
|
35
|
+
bottomRight
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
return [
|
|
39
|
+
mouseAnchor,
|
|
40
|
+
topLeft,
|
|
41
|
+
topRight
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
if (isBelow) {
|
|
45
|
+
if (isLeft) {
|
|
46
|
+
return [
|
|
47
|
+
mouseAnchor,
|
|
48
|
+
topLeft,
|
|
49
|
+
bottomLeft,
|
|
50
|
+
bottomRight
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
if (isRight) {
|
|
54
|
+
return [
|
|
55
|
+
mouseAnchor,
|
|
56
|
+
topRight,
|
|
57
|
+
bottomRight,
|
|
58
|
+
bottomLeft
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
return [
|
|
62
|
+
mouseAnchor,
|
|
63
|
+
bottomRight,
|
|
64
|
+
bottomLeft
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
if (isLeft) {
|
|
68
|
+
return [
|
|
69
|
+
mouseAnchor,
|
|
70
|
+
bottomLeft,
|
|
71
|
+
topLeft
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
if (isRight) {
|
|
75
|
+
return [
|
|
76
|
+
mouseAnchor,
|
|
77
|
+
topRight,
|
|
78
|
+
bottomRight
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useSafeZoneArea/getSafeZonePoints.ts"],"sourcesContent":["import type { Point } from './types';\n\ntype RectCorners = Record<'topLeft' | 'topRight' | 'bottomRight' | 'bottomLeft', Point>;\n\n/**\n * Returns the mouse anchor and the container corners facing it.\n *\n * @internal\n */\nexport function getSafeZonePoints(mouseAnchor: Point, containerCorners: RectCorners): Point[] {\n const { topLeft, topRight, bottomRight, bottomLeft } = containerCorners;\n const isAbove = mouseAnchor[1] < topLeft[1];\n const isBelow = mouseAnchor[1] > bottomLeft[1];\n const isLeft = mouseAnchor[0] < topLeft[0];\n const isRight = mouseAnchor[0] > topRight[0];\n\n if (isAbove) {\n if (isLeft) {\n return [mouseAnchor, bottomLeft, topLeft, topRight];\n }\n\n if (isRight) {\n return [mouseAnchor, topLeft, topRight, bottomRight];\n }\n\n return [mouseAnchor, topLeft, topRight];\n }\n\n if (isBelow) {\n if (isLeft) {\n return [mouseAnchor, topLeft, bottomLeft, bottomRight];\n }\n\n if (isRight) {\n return [mouseAnchor, topRight, bottomRight, bottomLeft];\n }\n\n return [mouseAnchor, bottomRight, bottomLeft];\n }\n\n if (isLeft) {\n return [mouseAnchor, bottomLeft, topLeft];\n }\n\n if (isRight) {\n return [mouseAnchor, topRight, bottomRight];\n }\n\n return [];\n}\n"],"names":["getSafeZonePoints","mouseAnchor","containerCorners","topLeft","topRight","bottomRight","bottomLeft","isAbove","isBelow","isLeft","isRight"],"mappings":"AAIA;;;;CAIC,GACD;;;;+BAAgBA;;;;;;AAAT,2BAA2BC,WAAkB,EAAEC,gBAA6B;IACjF,MAAM,EAAEC,OAAO,EAAEC,QAAQ,EAAEC,WAAW,EAAEC,UAAU,EAAE,GAAGJ;IACvD,MAAMK,UAAUN,WAAW,CAAC,EAAE,GAAGE,OAAO,CAAC,EAAE;IAC3C,MAAMK,UAAUP,WAAW,CAAC,EAAE,GAAGK,UAAU,CAAC,EAAE;IAC9C,MAAMG,SAASR,WAAW,CAAC,EAAE,GAAGE,OAAO,CAAC,EAAE;IAC1C,MAAMO,UAAUT,WAAW,CAAC,EAAE,GAAGG,QAAQ,CAAC,EAAE;IAE5C,IAAIG,SAAS;QACX,IAAIE,QAAQ;YACV,OAAO;gBAACR;gBAAaK;gBAAYH;gBAASC;aAAS;QACrD;QAEA,IAAIM,SAAS;YACX,OAAO;gBAACT;gBAAaE;gBAASC;gBAAUC;aAAY;QACtD;QAEA,OAAO;YAACJ;YAAaE;YAASC;SAAS;IACzC;IAEA,IAAII,SAAS;QACX,IAAIC,QAAQ;YACV,OAAO;gBAACR;gBAAaE;gBAASG;gBAAYD;aAAY;QACxD;QAEA,IAAIK,SAAS;YACX,OAAO;gBAACT;gBAAaG;gBAAUC;gBAAaC;aAAW;QACzD;QAEA,OAAO;YAACL;YAAaI;YAAaC;SAAW;IAC/C;IAEA,IAAIG,QAAQ;QACV,OAAO;YAACR;YAAaK;YAAYH;SAAQ;IAC3C;IAEA,IAAIO,SAAS;QACX,OAAO;YAACT;YAAaG;YAAUC;SAAY;IAC7C;IAEA,OAAO,EAAE;AACX"}
|
|
@@ -21,6 +21,11 @@ const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildc
|
|
|
21
21
|
const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));
|
|
22
22
|
const _reactutilities = require("@fluentui/react-utilities");
|
|
23
23
|
const _constants = require("./constants.cjs");
|
|
24
|
+
const registeredProperties = new WeakMap();
|
|
25
|
+
const slideDirectionProperties = [
|
|
26
|
+
_constants.POSITIONING_SLIDE_DIRECTION_VAR_X,
|
|
27
|
+
_constants.POSITIONING_SLIDE_DIRECTION_VAR_Y
|
|
28
|
+
];
|
|
24
29
|
function getPlacementSlideDirections(placement) {
|
|
25
30
|
const side = placement.split('-')[0];
|
|
26
31
|
// Default to sliding down from the top side
|
|
@@ -54,30 +59,34 @@ function usePositioningSlideDirection(options) {
|
|
|
54
59
|
element.style.setProperty(_constants.POSITIONING_SLIDE_DIRECTION_VAR_X, `${x}px`);
|
|
55
60
|
element.style.setProperty(_constants.POSITIONING_SLIDE_DIRECTION_VAR_Y, `${y}px`);
|
|
56
61
|
});
|
|
57
|
-
// Register the CSS custom properties so they can be interpolated during animations.
|
|
58
|
-
// CSS.registerProperty is idempotent — the try/catch handles the case where
|
|
59
|
-
// properties are already registered.
|
|
60
62
|
_react.useEffect(()=>{
|
|
61
|
-
var
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
63
|
+
var _targetDocument_defaultView;
|
|
64
|
+
const css = targetDocument === null || targetDocument === void 0 ? void 0 : (_targetDocument_defaultView = targetDocument.defaultView) === null || _targetDocument_defaultView === void 0 ? void 0 : _targetDocument_defaultView.CSS;
|
|
65
|
+
if (!targetDocument || !(css === null || css === void 0 ? void 0 : css.registerProperty)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
let properties = registeredProperties.get(targetDocument);
|
|
69
|
+
if (!properties) {
|
|
70
|
+
properties = new Set();
|
|
71
|
+
registeredProperties.set(targetDocument, properties);
|
|
72
|
+
}
|
|
73
|
+
for (const name of slideDirectionProperties){
|
|
74
|
+
if (properties.has(name)) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
css.registerProperty({
|
|
79
|
+
name,
|
|
80
|
+
syntax: '<length>',
|
|
81
|
+
inherits: false,
|
|
82
|
+
initialValue: '0px'
|
|
83
|
+
});
|
|
84
|
+
properties.add(name);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error && typeof error === 'object' && 'name' in error && error.name === 'InvalidModificationError') {
|
|
87
|
+
properties.add(name);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
81
90
|
}
|
|
82
91
|
}, [
|
|
83
92
|
targetDocument
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/usePositioningSlideDirection.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useEventCallback, isHTMLElement } from '@fluentui/react-utilities';\nimport type { PositioningProps } from './types';\nimport { POSITIONING_SLIDE_DIRECTION_VAR_X, POSITIONING_SLIDE_DIRECTION_VAR_Y } from './constants';\n\n/**\n * Returns the slide direction unit vectors for a given Floating UI placement.\n * Values are -1, 0, or 1, representing the direction the element slides in from.\n */\nexport function getPlacementSlideDirections(placement: string): { x: number; y: number } {\n const side = placement.split('-')[0];\n // Default to sliding down from the top side\n let x = 0;\n let y = 1;\n\n if (side === 'right') {\n x = -1;\n y = 0;\n } else if (side === 'bottom') {\n x = 0;\n y = -1;\n } else if (side === 'left') {\n x = 1;\n y = 0;\n }\n\n return { x, y };\n}\n\ntype UsePositioningSlideDirectionOptions = {\n /** The target document for CSS.registerProperty. */\n targetDocument: Document | undefined;\n /** The user's original onPositioningEnd callback, if any. */\n onPositioningEnd?: PositioningProps['onPositioningEnd'];\n};\n\n/**\n * A hook that manages CSS custom properties for slide direction based on positioning placement.\n *\n * It wraps the `onPositioningEnd` callback to set `--fui-positioning-slide-direction-x` and\n * `--fui-positioning-slide-direction-y` CSS custom properties on the positioned element,\n * and registers them via `CSS.registerProperty` to avoid properties propagation down to a DOM tree.\n *\n * @returns The wrapped `onPositioningEnd` handler to pass to the positioning config.\n */\nexport function usePositioningSlideDirection(\n options: UsePositioningSlideDirectionOptions,\n): NonNullable<PositioningProps['onPositioningEnd']> {\n const { targetDocument, onPositioningEnd } = options;\n\n const handlePositionEnd: NonNullable<PositioningProps['onPositioningEnd']> = useEventCallback(e => {\n onPositioningEnd?.(e);\n\n const element = e.target;\n const placement = e.detail.placement;\n\n if (!isHTMLElement(element)) {\n return;\n }\n\n const { x, y } = getPlacementSlideDirections(placement);\n\n element.style.setProperty(POSITIONING_SLIDE_DIRECTION_VAR_X, `${x}px`);\n element.style.setProperty(POSITIONING_SLIDE_DIRECTION_VAR_Y, `${y}px`);\n });\n\n
|
|
1
|
+
{"version":3,"sources":["../src/usePositioningSlideDirection.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useEventCallback, isHTMLElement } from '@fluentui/react-utilities';\nimport type { PositioningProps } from './types';\nimport { POSITIONING_SLIDE_DIRECTION_VAR_X, POSITIONING_SLIDE_DIRECTION_VAR_Y } from './constants';\n\nconst registeredProperties = new WeakMap<Document, Set<string>>();\nconst slideDirectionProperties = [POSITIONING_SLIDE_DIRECTION_VAR_X, POSITIONING_SLIDE_DIRECTION_VAR_Y];\n\n/**\n * Returns the slide direction unit vectors for a given Floating UI placement.\n * Values are -1, 0, or 1, representing the direction the element slides in from.\n */\nexport function getPlacementSlideDirections(placement: string): { x: number; y: number } {\n const side = placement.split('-')[0];\n // Default to sliding down from the top side\n let x = 0;\n let y = 1;\n\n if (side === 'right') {\n x = -1;\n y = 0;\n } else if (side === 'bottom') {\n x = 0;\n y = -1;\n } else if (side === 'left') {\n x = 1;\n y = 0;\n }\n\n return { x, y };\n}\n\ntype UsePositioningSlideDirectionOptions = {\n /** The target document for CSS.registerProperty. */\n targetDocument: Document | undefined;\n /** The user's original onPositioningEnd callback, if any. */\n onPositioningEnd?: PositioningProps['onPositioningEnd'];\n};\n\n/**\n * A hook that manages CSS custom properties for slide direction based on positioning placement.\n *\n * It wraps the `onPositioningEnd` callback to set `--fui-positioning-slide-direction-x` and\n * `--fui-positioning-slide-direction-y` CSS custom properties on the positioned element,\n * and registers them via `CSS.registerProperty` to avoid properties propagation down to a DOM tree.\n *\n * @returns The wrapped `onPositioningEnd` handler to pass to the positioning config.\n */\nexport function usePositioningSlideDirection(\n options: UsePositioningSlideDirectionOptions,\n): NonNullable<PositioningProps['onPositioningEnd']> {\n const { targetDocument, onPositioningEnd } = options;\n\n const handlePositionEnd: NonNullable<PositioningProps['onPositioningEnd']> = useEventCallback(e => {\n onPositioningEnd?.(e);\n\n const element = e.target;\n const placement = e.detail.placement;\n\n if (!isHTMLElement(element)) {\n return;\n }\n\n const { x, y } = getPlacementSlideDirections(placement);\n\n element.style.setProperty(POSITIONING_SLIDE_DIRECTION_VAR_X, `${x}px`);\n element.style.setProperty(POSITIONING_SLIDE_DIRECTION_VAR_Y, `${y}px`);\n });\n\n React.useEffect(() => {\n const css = targetDocument?.defaultView?.CSS;\n if (!targetDocument || !css?.registerProperty) {\n return;\n }\n let properties = registeredProperties.get(targetDocument);\n if (!properties) {\n properties = new Set<string>();\n registeredProperties.set(targetDocument, properties);\n }\n for (const name of slideDirectionProperties) {\n if (properties.has(name)) {\n continue;\n }\n try {\n css.registerProperty({ name, syntax: '<length>', inherits: false, initialValue: '0px' });\n properties.add(name);\n } catch (error) {\n if (error && typeof error === 'object' && 'name' in error && error.name === 'InvalidModificationError') {\n properties.add(name);\n }\n }\n }\n }, [targetDocument]);\n\n return handlePositionEnd;\n}\n"],"names":["React","useEventCallback","isHTMLElement","POSITIONING_SLIDE_DIRECTION_VAR_X","POSITIONING_SLIDE_DIRECTION_VAR_Y","registeredProperties","WeakMap","slideDirectionProperties","getPlacementSlideDirections","placement","side","split","x","y","usePositioningSlideDirection","options","targetDocument","onPositioningEnd","handlePositionEnd","e","element","target","detail","style","setProperty","useEffect","css","defaultView","CSS","registerProperty","properties","get","Set","set","name","has","syntax","inherits","initialValue","add","error"],"mappings":"AAAA;;;;;;;;;;;;IAcgBQ,2BAAAA;;;IAoCAM,4BAAAA;;;;;iEAhDO,QAAQ;gCACiB,4BAA4B;2BAES,iBAAc;AAEnG,MAAMT,uBAAuB,IAAIC;AACjC,MAAMC,2BAA2B;IAACJ,4CAAAA;IAAmCC,4CAAAA;CAAkC;AAMhG,qCAAqCK,SAAiB;IAC3D,MAAMC,OAAOD,UAAUE,KAAK,CAAC,IAAI,CAAC,EAAE;IACpC,4CAA4C;IAC5C,IAAIC,IAAI;IACR,IAAIC,IAAI;IAER,IAAIH,SAAS,SAAS;QACpBE,IAAI,CAAC;QACLC,IAAI;IACN,OAAO,IAAIH,SAAS,UAAU;QAC5BE,IAAI;QACJC,IAAI,CAAC;IACP,OAAO,IAAIH,SAAS,QAAQ;QAC1BE,IAAI;QACJC,IAAI;IACN;IAEA,OAAO;QAAED;QAAGC;IAAE;AAChB;AAkBO,sCACLE,OAA4C;IAE5C,MAAM,EAAEC,cAAc,EAAEC,gBAAgB,EAAE,GAAGF;IAE7C,MAAMG,oBAAuEjB,oCAAAA,EAAiBkB,CAAAA;QAC5FF,qBAAAA,QAAAA,qBAAAA,KAAAA,IAAAA,KAAAA,IAAAA,iBAAmBE;QAEnB,MAAMC,UAAUD,EAAEE,MAAM;QACxB,MAAMZ,YAAYU,EAAEG,MAAM,CAACb,SAAS;QAEpC,IAAI,KAACP,6BAAAA,EAAckB,UAAU;YAC3B;QACF;QAEA,MAAM,EAAER,CAAC,EAAEC,CAAC,EAAE,GAAGL,4BAA4BC;QAE7CW,QAAQG,KAAK,CAACC,WAAW,CAACrB,4CAAAA,EAAmC,GAAGS,EAAE,EAAE,CAAC;QACrEQ,QAAQG,KAAK,CAACC,WAAW,CAACpB,4CAAAA,EAAmC,GAAGS,EAAE,EAAE,CAAC;IACvE;IAEAb,OAAMyB,SAAS,CAAC;YACFT;QAAZ,MAAMU,MAAMV,mBAAAA,QAAAA,mBAAAA,KAAAA,IAAAA,KAAAA,IAAAA,CAAAA,8BAAAA,eAAgBW,WAAAA,AAAW,MAAA,QAA3BX,gCAAAA,KAAAA,IAAAA,KAAAA,IAAAA,4BAA6BY,GAAG;QAC5C,IAAI,CAACZ,kBAAkB,EAACU,QAAAA,QAAAA,QAAAA,KAAAA,IAAAA,KAAAA,IAAAA,IAAKG,gBAAAA,AAAgB,GAAE;YAC7C;QACF;QACA,IAAIC,aAAazB,qBAAqB0B,GAAG,CAACf;QAC1C,IAAI,CAACc,YAAY;YACfA,aAAa,IAAIE;YACjB3B,qBAAqB4B,GAAG,CAACjB,gBAAgBc;QAC3C;QACA,KAAK,MAAMI,QAAQ3B,yBAA0B;YAC3C,IAAIuB,WAAWK,GAAG,CAACD,OAAO;gBACxB;YACF;YACA,IAAI;gBACFR,IAAIG,gBAAgB,CAAC;oBAAEK;oBAAME,QAAQ;oBAAYC,UAAU;oBAAOC,cAAc;gBAAM;gBACtFR,WAAWS,GAAG,CAACL;YACjB,EAAE,OAAOM,OAAO;gBACd,IAAIA,SAAS,OAAOA,UAAU,YAAY,UAAUA,SAASA,MAAMN,IAAI,KAAK,4BAA4B;oBACtGJ,WAAWS,GAAG,CAACL;gBACjB;YACF;QACF;IACF,GAAG;QAAClB;KAAe;IAEnB,OAAOE;AACT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluentui/react-positioning",
|
|
3
|
-
"version": "9.23.
|
|
3
|
+
"version": "9.23.4",
|
|
4
4
|
"description": "A react wrapper around Popper.js for Fluent UI",
|
|
5
5
|
"main": "lib-commonjs/index.cjs",
|
|
6
6
|
"module": "lib/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"@floating-ui/dom": "^1.6.12",
|
|
17
17
|
"@fluentui/react-shared-contexts": "^9.26.4",
|
|
18
18
|
"@fluentui/react-theme": "^9.2.2",
|
|
19
|
-
"@fluentui/react-utilities": "^9.
|
|
19
|
+
"@fluentui/react-utilities": "^9.27.0",
|
|
20
20
|
"@griffel/react": "^1.5.32",
|
|
21
21
|
"@swc/helpers": "^0.5.1",
|
|
22
22
|
"use-sync-external-store": "^1.4.0"
|