Skip to content

Commit d70fd3f

Browse files
committed
fix: floating panel
1 parent bd4d8b9 commit d70fd3f

File tree

5 files changed

+91
-20
lines changed

5 files changed

+91
-20
lines changed

.changeset/brave-lamps-attack.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@zag-js/floating-panel": patch
3+
---
4+
5+
- Set default strategy to `fixed`
6+
- Implement controlled open/close state
7+
- Constraint the mouse movement to the boundary rect

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ All notable changes to this project will be documented in this file.
66
77
## [Unreleased]
88

9+
## [1.9.3](./#1.9.3) - 2025-04-10
10+
11+
### Fixed
12+
13+
- **Floating Panel**
14+
15+
- Change default strategy from `absolute` to `fixed` to improve positioning consistency
16+
- Implement controlled open/close state
17+
- Constraint the mouse movement to the boundary rect to prevent content from being moved outside completely
18+
919
## [1.9.2](./#1.9.2) - 2025-04-10
1020

1121
### Fixed

packages/docs/data/api.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1810,7 +1810,7 @@
18101810
"strategy": {
18111811
"type": "\"absolute\" | \"fixed\"",
18121812
"description": "The strategy to use for positioning",
1813-
"defaultValue": "\"absolute\""
1813+
"defaultValue": "\"fixed\""
18141814
},
18151815
"allowOverflow": {
18161816
"type": "boolean",

packages/machines/floating-panel/src/floating-panel.machine.ts

Lines changed: 72 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ import {
1515
type Size,
1616
} from "@zag-js/rect-utils"
1717
import { subscribe } from "@zag-js/store"
18-
import { ensureProps, invariant, match, pick } from "@zag-js/utils"
18+
import { clampValue, ensureProps, invariant, match, pick } from "@zag-js/utils"
1919
import * as dom from "./floating-panel.dom"
2020
import { panelStack } from "./floating-panel.store"
2121
import type { FloatingPanelSchema, IntlTranslations, Stage } from "./floating-panel.types"
2222

23-
const { not } = createGuards<FloatingPanelSchema>()
23+
const { not, and } = createGuards<FloatingPanelSchema>()
2424

2525
const defaultTranslations: IntlTranslations = {
2626
minimize: "Minimize window",
@@ -32,7 +32,7 @@ export const machine = createMachine<FloatingPanelSchema>({
3232
props({ props }) {
3333
ensureProps(props, ["id"], "floating-panel")
3434
return {
35-
strategy: "absolute",
35+
strategy: "fixed",
3636
gridSize: 1,
3737
defaultSize: { width: 320, height: 240 },
3838
defaultPosition: { x: 300, y: 100 },
@@ -140,10 +140,20 @@ export const machine = createMachine<FloatingPanelSchema>({
140140
closed: {
141141
tags: ["closed"],
142142
on: {
143-
OPEN: {
143+
"CONTROLLED.OPEN": {
144144
target: "open",
145-
actions: ["invokeOnOpen", "setAnchorPosition", "setPositionStyle", "setSizeStyle", "focusContentEl"],
145+
actions: ["setAnchorPosition", "setPositionStyle", "setSizeStyle", "focusContentEl"],
146146
},
147+
OPEN: [
148+
{
149+
guard: "isOpenControlled",
150+
actions: ["invokeOnOpen"],
151+
},
152+
{
153+
target: "open",
154+
actions: ["invokeOnOpen", "setAnchorPosition", "setPositionStyle", "setSizeStyle", "focusContentEl"],
155+
},
156+
],
147157
},
148158
},
149159

@@ -162,15 +172,32 @@ export const machine = createMachine<FloatingPanelSchema>({
162172
target: "open.resizing",
163173
actions: ["setPrevSize"],
164174
},
165-
CLOSE: {
166-
target: "closed",
167-
actions: ["invokeOnClose", "resetRect", "focusTriggerEl"],
168-
},
169-
ESCAPE: {
170-
guard: "closeOnEsc",
175+
"CONTROLLED.CLOSE": {
171176
target: "closed",
172-
actions: ["invokeOnClose", "resetRect", "focusTriggerEl"],
177+
actions: ["resetRect", "focusTriggerEl"],
173178
},
179+
CLOSE: [
180+
{
181+
guard: "isOpenControlled",
182+
target: "closed",
183+
actions: ["invokeOnClose"],
184+
},
185+
{
186+
target: "closed",
187+
actions: ["invokeOnClose", "resetRect", "focusTriggerEl"],
188+
},
189+
],
190+
ESCAPE: [
191+
{
192+
guard: and("isOpenControlled", "closeOnEsc"),
193+
actions: ["invokeOnClose"],
194+
},
195+
{
196+
guard: "closeOnEsc",
197+
target: "closed",
198+
actions: ["invokeOnClose", "resetRect", "focusTriggerEl"],
199+
},
200+
],
174201
MINIMIZE: {
175202
actions: ["setMinimized"],
176203
},
@@ -198,10 +225,21 @@ export const machine = createMachine<FloatingPanelSchema>({
198225
target: "open",
199226
actions: ["invokeOnDragEnd"],
200227
},
201-
CLOSE: {
228+
"CONTROLLED.CLOSE": {
202229
target: "closed",
203-
actions: ["invokeOnClose", "resetRect"],
230+
actions: ["resetRect"],
204231
},
232+
CLOSE: [
233+
{
234+
guard: "isOpenControlled",
235+
target: "closed",
236+
actions: ["invokeOnClose"],
237+
},
238+
{
239+
target: "closed",
240+
actions: ["invokeOnClose", "resetRect"],
241+
},
242+
],
205243
ESCAPE: {
206244
target: "open",
207245
},
@@ -220,10 +258,21 @@ export const machine = createMachine<FloatingPanelSchema>({
220258
target: "open",
221259
actions: ["invokeOnResizeEnd"],
222260
},
223-
CLOSE: {
261+
"CONTROLLED.CLOSE": {
224262
target: "closed",
225-
actions: ["invokeOnClose", "resetRect"],
263+
actions: ["resetRect"],
226264
},
265+
CLOSE: [
266+
{
267+
guard: "isOpenControlled",
268+
target: "closed",
269+
actions: ["invokeOnClose"],
270+
},
271+
{
272+
target: "closed",
273+
actions: ["invokeOnClose", "resetRect"],
274+
},
275+
],
227276
ESCAPE: {
228277
target: "open",
229278
},
@@ -236,15 +285,20 @@ export const machine = createMachine<FloatingPanelSchema>({
236285
closeOnEsc: ({ prop }) => !!prop("closeOnEscape"),
237286
isMaximized: ({ context }) => context.get("stage") === "maximized",
238287
isMinimized: ({ context }) => context.get("stage") === "minimized",
288+
isOpenControlled: ({ prop }) => prop("open") != undefined,
239289
},
240290

241291
effects: {
242-
trackPointerMove({ scope, send, event: evt }) {
292+
trackPointerMove({ scope, send, event: evt, prop }) {
243293
const doc = scope.getDoc()
294+
const boundaryEl = prop("getBoundaryEl")?.()
295+
const boundaryRect = dom.getBoundaryRect(scope, boundaryEl, false)
244296
return trackPointerMove(doc, {
245297
onPointerMove({ point, event }) {
246298
const { altKey, shiftKey } = event
247-
send({ type: "DRAG", position: point, axis: evt.axis, altKey, shiftKey })
299+
let x = clampValue(point.x, boundaryRect.x, boundaryRect.x + boundaryRect.width)
300+
let y = clampValue(point.y, boundaryRect.y, boundaryRect.y + boundaryRect.height)
301+
send({ type: "DRAG", position: { x, y }, axis: evt.axis, altKey, shiftKey })
248302
},
249303
onPointerUp() {
250304
send({ type: "DRAG_END" })

packages/machines/floating-panel/src/floating-panel.types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export interface FloatingPanelProps extends DirectionProperty, CommonProperties
5858
translations?: IntlTranslations | undefined
5959
/**
6060
* The strategy to use for positioning
61-
* @default "absolute"
61+
* @default "fixed"
6262
*/
6363
strategy?: "absolute" | "fixed" | undefined
6464
/**

0 commit comments

Comments
 (0)