From 52a261e89f9994f3150b5a1852cca22068ae17ad Mon Sep 17 00:00:00 2001 From: Segun Adebayo Date: Fri, 22 Mar 2024 21:06:42 +0000 Subject: [PATCH] feat: floating panel machine (#1363) * chore: initial scafold * chore: update * chore: improve set up * chore: update styles * chore: rm portal for now * chore: update transitions * feat: implement dragging * chore: get drag and resize to work * feat: support min and max size * chore: preserve * chore: update preserve * feat: complete features * refactor: stage types * feat: track boundary element * feat: complete v1 * style: fix * feat: update examples * feat: keyboard support * docs: add changeset --- .changeset/thin-buses-burn.md | 5 + .xstate/floating-panel.js | 128 ++++++ examples/next-ts/package.json | 3 +- examples/next-ts/pages/floating-panel.tsx | 67 ++++ examples/nuxt-ts/package.json | 1 + examples/nuxt-ts/pages/floating-panel.vue | 63 +++ examples/preact-ts/package.json | 3 +- examples/solid-ts/package.json | 3 +- .../solid-ts/src/pages/floating-panel.tsx | 67 ++++ examples/solid-ts/src/routes.ts | 1 + examples/svelte-ts/package.json | 3 +- examples/vue-ts/package.json | 1 + examples/vue-ts/src/pages/floating-panel.tsx | 74 ++++ examples/vue-ts/src/routes.ts | 1 + packages/core/CHANGELOG.md | 8 +- packages/docs/data/api.json | 2 +- packages/frameworks/solid/CHANGELOG.md | 2 +- packages/frameworks/vue/CHANGELOG.md | 7 +- packages/machines/checkbox/CHANGELOG.md | 4 +- packages/machines/color-picker/CHANGELOG.md | 3 +- packages/machines/combobox/CHANGELOG.md | 18 +- packages/machines/date-picker/CHANGELOG.md | 6 +- packages/machines/dialog/CHANGELOG.md | 4 +- packages/machines/editable/CHANGELOG.md | 2 +- packages/machines/floating-panel/README.md | 19 + packages/machines/floating-panel/package.json | 53 +++ .../src/floating-panel.anatomy.ts | 19 + .../src/floating-panel.connect.ts | 205 ++++++++++ .../floating-panel/src/floating-panel.dom.ts | 15 + .../src/floating-panel.machine.ts | 372 +++++++++++++++++ .../src/floating-panel.props.ts | 30 ++ .../src/floating-panel.types.ts | 183 +++++++++ .../src/get-resize-axis-style.ts | 72 ++++ packages/machines/floating-panel/src/index.ts | 14 + .../machines/floating-panel/tsconfig.json | 7 + packages/machines/hover-card/CHANGELOG.md | 17 +- packages/machines/menu/CHANGELOG.md | 8 +- packages/machines/number-input/CHANGELOG.md | 2 +- packages/machines/pin-input/CHANGELOG.md | 2 +- packages/machines/popover/CHANGELOG.md | 9 +- packages/machines/progress/CHANGELOG.md | 4 +- packages/machines/rating-group/CHANGELOG.md | 4 +- packages/machines/select/CHANGELOG.md | 14 +- packages/machines/slider/CHANGELOG.md | 3 +- packages/machines/splitter/CHANGELOG.md | 4 +- packages/machines/switch/CHANGELOG.md | 4 +- packages/machines/tabs/CHANGELOG.md | 4 +- packages/machines/tags-input/CHANGELOG.md | 4 +- packages/machines/tooltip/CHANGELOG.md | 6 +- packages/machines/tour/CHANGELOG.md | 3 +- packages/utilities/core/CHANGELOG.md | 8 +- packages/utilities/core/src/object.ts | 13 + packages/utilities/dismissable/CHANGELOG.md | 3 +- .../utilities/dismissable/src/layer-stack.ts | 4 +- packages/utilities/element-rect/CHANGELOG.md | 8 +- packages/utilities/popper/CHANGELOG.md | 4 +- .../utilities/rect/src/affine-transform.ts | 177 ++++++++ packages/utilities/rect/src/align.ts | 55 +-- packages/utilities/rect/src/clamp.ts | 26 ++ packages/utilities/rect/src/closest.ts | 19 +- packages/utilities/rect/src/compass.ts | 25 ++ packages/utilities/rect/src/constrain.ts | 18 + packages/utilities/rect/src/contains.ts | 4 +- packages/utilities/rect/src/distance.ts | 7 +- packages/utilities/rect/src/equality.ts | 13 + packages/utilities/rect/src/from-element.ts | 4 +- packages/utilities/rect/src/from-points.ts | 4 +- packages/utilities/rect/src/from-range.ts | 3 +- packages/utilities/rect/src/from-rotation.ts | 4 +- packages/utilities/rect/src/from-window.ts | 3 +- packages/utilities/rect/src/get-polygon.ts | 15 - packages/utilities/rect/src/index.ts | 6 +- packages/utilities/rect/src/intersection.ts | 4 +- packages/utilities/rect/src/operations.ts | 4 +- packages/utilities/rect/src/polygon.ts | 16 +- packages/utilities/rect/src/rect.ts | 46 ++- packages/utilities/rect/src/resize.ts | 106 +++++ packages/utilities/rect/src/types.ts | 53 ++- packages/utilities/rect/src/union.ts | 22 +- packages/utilities/tabbable/CHANGELOG.md | 18 +- pnpm-lock.yaml | 379 ++++-------------- shared/src/controls.ts | 8 + shared/src/routes.ts | 1 + shared/src/style.css | 82 ++++ 84 files changed, 2205 insertions(+), 510 deletions(-) create mode 100644 .changeset/thin-buses-burn.md create mode 100644 .xstate/floating-panel.js create mode 100644 examples/next-ts/pages/floating-panel.tsx create mode 100644 examples/nuxt-ts/pages/floating-panel.vue create mode 100644 examples/solid-ts/src/pages/floating-panel.tsx create mode 100644 examples/vue-ts/src/pages/floating-panel.tsx create mode 100644 packages/machines/floating-panel/README.md create mode 100644 packages/machines/floating-panel/package.json create mode 100644 packages/machines/floating-panel/src/floating-panel.anatomy.ts create mode 100644 packages/machines/floating-panel/src/floating-panel.connect.ts create mode 100644 packages/machines/floating-panel/src/floating-panel.dom.ts create mode 100644 packages/machines/floating-panel/src/floating-panel.machine.ts create mode 100644 packages/machines/floating-panel/src/floating-panel.props.ts create mode 100644 packages/machines/floating-panel/src/floating-panel.types.ts create mode 100644 packages/machines/floating-panel/src/get-resize-axis-style.ts create mode 100644 packages/machines/floating-panel/src/index.ts create mode 100644 packages/machines/floating-panel/tsconfig.json create mode 100644 packages/utilities/rect/src/affine-transform.ts create mode 100644 packages/utilities/rect/src/clamp.ts create mode 100644 packages/utilities/rect/src/compass.ts create mode 100644 packages/utilities/rect/src/constrain.ts create mode 100644 packages/utilities/rect/src/equality.ts delete mode 100644 packages/utilities/rect/src/get-polygon.ts create mode 100644 packages/utilities/rect/src/resize.ts diff --git a/.changeset/thin-buses-burn.md b/.changeset/thin-buses-burn.md new file mode 100644 index 0000000000..0cdb226b4f --- /dev/null +++ b/.changeset/thin-buses-burn.md @@ -0,0 +1,5 @@ +--- +"@zag-js/floating-panel": minor +--- + +Introduce new floating panel machine for draggable and resizable panels diff --git a/.xstate/floating-panel.js b/.xstate/floating-panel.js new file mode 100644 index 0000000000..4ab3d32b87 --- /dev/null +++ b/.xstate/floating-panel.js @@ -0,0 +1,128 @@ +"use strict"; + +var _xstate = require("xstate"); +const { + actions, + createMachine, + assign +} = _xstate; +const { + choose +} = actions; +const fetchMachine = createMachine({ + id: "floating-panel", + initial: ctx.open ? "open" : "closed", + context: { + "!isMaximized": false, + "!isMinimized": false, + "closeOnEsc": false + }, + on: { + UPDATE_CONTEXT: { + actions: "updateContext" + } + }, + states: { + closed: { + tags: ["closed"], + on: { + OPEN: { + target: "open", + actions: ["invokeOnOpen", "setPositionStyle", "setSizeStyle"] + } + } + }, + open: { + tags: ["open"], + activities: ["trackBoundaryRect"], + on: { + DRAG_START: { + cond: "!isMaximized", + target: "open.dragging", + actions: ["setPrevPosition"] + }, + RESIZE_START: { + cond: "!isMinimized", + target: "open.resizing", + actions: ["setPrevSize"] + }, + CLOSE: { + target: "closed", + actions: ["invokeOnClose", "resetRect"] + }, + ESCAPE: { + cond: "closeOnEsc", + target: "closed", + actions: ["invokeOnClose", "resetRect"] + }, + MINIMIZE: { + actions: ["setMinimized", "invokeOnMinimize"] + }, + MAXIMIZE: { + actions: ["setMaximized", "invokeOnMaximize"] + }, + RESTORE: { + actions: ["setRestored"] + }, + MOVE: { + actions: ["setPositionFromKeybord"] + } + } + }, + "open.dragging": { + tags: ["open"], + activities: ["trackPointerMove", "trackBoundaryRect"], + exit: ["clearPrevPosition"], + on: { + DRAG: { + actions: ["setPosition"] + }, + DRAG_END: { + target: "open", + actions: ["invokeOnDragEnd"] + }, + CLOSE: { + target: "closed", + actions: ["invokeOnClose", "resetRect"] + }, + ESCAPE: { + target: "open" + } + } + }, + "open.resizing": { + tags: ["open"], + activities: ["trackPointerMove", "trackBoundaryRect"], + exit: ["clearPrevSize"], + on: { + DRAG: { + actions: ["setSize"] + }, + DRAG_END: { + target: "open", + actions: ["invokeOnResizeEnd"] + }, + CLOSE: { + target: "closed", + actions: ["invokeOnClose", "resetRect"] + }, + ESCAPE: { + target: "open" + } + } + } + } +}, { + actions: { + updateContext: assign((context, event) => { + return { + [event.contextKey]: true + }; + }) + }, + guards: { + "!isMaximized": ctx => ctx["!isMaximized"], + "!isMinimized": ctx => ctx["!isMinimized"], + "closeOnEsc": ctx => ctx["closeOnEsc"] + } +}); \ No newline at end of file diff --git a/examples/next-ts/package.json b/examples/next-ts/package.json index 3742f63779..617ffc97c1 100644 --- a/examples/next-ts/package.json +++ b/examples/next-ts/package.json @@ -38,6 +38,7 @@ "@zag-js/element-size": "workspace:*", "@zag-js/file-upload": "workspace:*", "@zag-js/file-utils": "workspace:*", + "@zag-js/floating-panel": "workspace:*", "@zag-js/focus-scope": "workspace:*", "@zag-js/focus-visible": "workspace:*", "@zag-js/form-utils": "workspace:*", @@ -96,4 +97,4 @@ "typescript": "5.4.2" }, "license": "MIT" -} +} \ No newline at end of file diff --git a/examples/next-ts/pages/floating-panel.tsx b/examples/next-ts/pages/floating-panel.tsx new file mode 100644 index 0000000000..7bd339e874 --- /dev/null +++ b/examples/next-ts/pages/floating-panel.tsx @@ -0,0 +1,67 @@ +import * as floatingPanel from "@zag-js/floating-panel" +import { normalizeProps, useMachine } from "@zag-js/react" +import { floatingPanelControls } from "@zag-js/shared" +import { ArrowDownLeft, Maximize2, Minus, XIcon } from "lucide-react" +import { useId } from "react" +import { StateVisualizer } from "../components/state-visualizer" +import { Toolbar } from "../components/toolbar" +import { useControls } from "../hooks/use-controls" + +export default function Page() { + const controls = useControls(floatingPanelControls) + + const [state, send] = useMachine(floatingPanel.machine({ id: useId() }), { + context: controls.context, + }) + + const api = floatingPanel.connect(state, send, normalizeProps) + + return ( + <> +
+
+ +
+
+
+
+

Floating Panel

+
+ + + + +
+
+
+
+

Some content

+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + ) +} diff --git a/examples/nuxt-ts/package.json b/examples/nuxt-ts/package.json index ca1db2d203..7a6312992a 100644 --- a/examples/nuxt-ts/package.json +++ b/examples/nuxt-ts/package.json @@ -37,6 +37,7 @@ "@zag-js/element-size": "workspace:*", "@zag-js/file-upload": "workspace:*", "@zag-js/file-utils": "workspace:*", + "@zag-js/floating-panel": "workspace:*", "@zag-js/focus-scope": "workspace:*", "@zag-js/focus-visible": "workspace:*", "@zag-js/form-utils": "workspace:*", diff --git a/examples/nuxt-ts/pages/floating-panel.vue b/examples/nuxt-ts/pages/floating-panel.vue new file mode 100644 index 0000000000..43c38fadd7 --- /dev/null +++ b/examples/nuxt-ts/pages/floating-panel.vue @@ -0,0 +1,63 @@ + + + diff --git a/examples/preact-ts/package.json b/examples/preact-ts/package.json index d605305774..820cc77d98 100644 --- a/examples/preact-ts/package.json +++ b/examples/preact-ts/package.json @@ -36,6 +36,7 @@ "@zag-js/element-size": "workspace:*", "@zag-js/file-upload": "workspace:*", "@zag-js/file-utils": "workspace:*", + "@zag-js/floating-panel": "workspace:*", "@zag-js/focus-scope": "workspace:*", "@zag-js/focus-visible": "workspace:*", "@zag-js/form-utils": "workspace:*", @@ -89,4 +90,4 @@ "typescript": "5.4.2", "vite": "5.1.6" } -} +} \ No newline at end of file diff --git a/examples/solid-ts/package.json b/examples/solid-ts/package.json index e989eb9463..42cd14018b 100644 --- a/examples/solid-ts/package.json +++ b/examples/solid-ts/package.json @@ -46,6 +46,7 @@ "@zag-js/element-size": "workspace:*", "@zag-js/file-upload": "workspace:*", "@zag-js/file-utils": "workspace:*", + "@zag-js/floating-panel": "workspace:*", "@zag-js/focus-scope": "workspace:*", "@zag-js/focus-visible": "workspace:*", "@zag-js/form-utils": "workspace:*", @@ -91,4 +92,4 @@ "lucide-solid": "0.359.0", "solid-js": "1.8.15" } -} +} \ No newline at end of file diff --git a/examples/solid-ts/src/pages/floating-panel.tsx b/examples/solid-ts/src/pages/floating-panel.tsx new file mode 100644 index 0000000000..1fe34d6ed7 --- /dev/null +++ b/examples/solid-ts/src/pages/floating-panel.tsx @@ -0,0 +1,67 @@ +import * as floatingPanel from "@zag-js/floating-panel" +import { floatingPanelControls } from "@zag-js/shared" +import { normalizeProps, useMachine } from "@zag-js/solid" +import { ArrowDownLeft, Maximize2, Minus, XIcon } from "lucide-solid" +import { createMemo, createUniqueId } from "solid-js" +import { StateVisualizer } from "../components/state-visualizer" +import { Toolbar } from "../components/toolbar" +import { useControls } from "../hooks/use-controls" + +export default function Page() { + const controls = useControls(floatingPanelControls) + + const [state, send] = useMachine(floatingPanel.machine({ id: createUniqueId() }), { + context: controls.context, + }) + + const api = createMemo(() => floatingPanel.connect(state, send, normalizeProps)) + + return ( + <> +
+
+ +
+
+
+
+

Floating Panel

+
+ + + + +
+
+
+
+

Some content

+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + ) +} diff --git a/examples/solid-ts/src/routes.ts b/examples/solid-ts/src/routes.ts index 358f2be5f9..9c8611c646 100644 --- a/examples/solid-ts/src/routes.ts +++ b/examples/solid-ts/src/routes.ts @@ -4,6 +4,7 @@ import { lazy } from "solid-js" import Home from "./pages/home" export const routes: RouteDefinition[] = [ + { path: "/floating-panel", component: lazy(() => import("./pages/floating-panel")) }, { path: "/tour", component: lazy(() => import("./pages/tour")) }, { path: "/collapsible", component: lazy(() => import("./pages/collapsible")) }, { path: "/clipboard", component: lazy(() => import("./pages/clipboard")) }, diff --git a/examples/svelte-ts/package.json b/examples/svelte-ts/package.json index ebc0717834..8492250dcd 100644 --- a/examples/svelte-ts/package.json +++ b/examples/svelte-ts/package.json @@ -38,6 +38,7 @@ "@zag-js/element-size": "workspace:*", "@zag-js/file-upload": "workspace:*", "@zag-js/file-utils": "workspace:*", + "@zag-js/floating-panel": "workspace:*", "@zag-js/focus-scope": "workspace:*", "@zag-js/focus-visible": "workspace:*", "@zag-js/form-utils": "workspace:*", @@ -94,4 +95,4 @@ "vite": "5.1.6", "vite-tsconfig-paths": "4.3.2" } -} +} \ No newline at end of file diff --git a/examples/vue-ts/package.json b/examples/vue-ts/package.json index 704a17f60f..cc86feea79 100644 --- a/examples/vue-ts/package.json +++ b/examples/vue-ts/package.json @@ -38,6 +38,7 @@ "@zag-js/element-size": "workspace:*", "@zag-js/file-upload": "workspace:*", "@zag-js/file-utils": "workspace:*", + "@zag-js/floating-panel": "workspace:*", "@zag-js/focus-scope": "workspace:*", "@zag-js/focus-visible": "workspace:*", "@zag-js/form-utils": "workspace:*", diff --git a/examples/vue-ts/src/pages/floating-panel.tsx b/examples/vue-ts/src/pages/floating-panel.tsx new file mode 100644 index 0000000000..29f860bbca --- /dev/null +++ b/examples/vue-ts/src/pages/floating-panel.tsx @@ -0,0 +1,74 @@ +import * as floatingPanel from "@zag-js/floating-panel" +import { normalizeProps, useMachine } from "@zag-js/vue" +import { computed, defineComponent, h, Fragment } from "vue" +import { floatingPanelControls } from "@zag-js/shared" +import { StateVisualizer } from "../components/state-visualizer" +import { Toolbar } from "../components/toolbar" +import { ArrowDownLeft, Maximize2, Minus, XIcon } from "lucide-vue-next" +import { useControls } from "../hooks/use-controls" + +export default defineComponent({ + name: "floatingPanel", + setup() { + const controls = useControls(floatingPanelControls) + + const [state, send] = useMachine(floatingPanel.machine({ id: "1" }), { + context: controls.context, + }) + + const apiRef = computed(() => floatingPanel.connect(state.value, send, normalizeProps)) + + return () => { + const api = apiRef.value + + return ( + <> +
+
+ +
+
+
+
+

Floating Panel

+
+ + + + +
+
+
+
+

Some content

+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + ) + } + }, +}) diff --git a/examples/vue-ts/src/routes.ts b/examples/vue-ts/src/routes.ts index baa4ca1b96..6f770b9625 100644 --- a/examples/vue-ts/src/routes.ts +++ b/examples/vue-ts/src/routes.ts @@ -4,6 +4,7 @@ import Home from "./pages/index" export const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ + { path: "/floating-panel", component: () => import("./pages/floating-panel") }, { path: "/tour", component: () => import("./pages/tour") }, { path: "/collapsible", component: () => import("./pages/collapsible") }, { path: "/clipboard", component: () => import("./pages/clipboard") }, diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 93dcf49529..c14f063631 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -522,7 +522,7 @@ date: (a, b) => a.getTime() === b.getTime(), }, }, - ); + ) ``` - [#462](https://github.com/chakra-ui/zag/pull/462) @@ -698,9 +698,9 @@ This helper can be used in inline guards or the gaurds options. ```js - import { guards } from "@zag-js/core"; + import { guards } from "@zag-js/core" - const { isIn } = gaurds; + const { isIn } = gaurds const machine = createMachine({ on: { @@ -712,7 +712,7 @@ open: {}, closed: {}, }, - }); + }) ``` * [`587cbec9`](https://github.com/chakra-ui/zag/commit/587cbec9b32ee9e8faef5ceeefb779231b152018) Thanks diff --git a/packages/docs/data/api.json b/packages/docs/data/api.json index 581c3f0cd2..5ae35c55a9 100644 --- a/packages/docs/data/api.json +++ b/packages/docs/data/api.json @@ -3488,4 +3488,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/frameworks/solid/CHANGELOG.md b/packages/frameworks/solid/CHANGELOG.md index 1b8eb3ba07..6d640058b7 100644 --- a/packages/frameworks/solid/CHANGELOG.md +++ b/packages/frameworks/solid/CHANGELOG.md @@ -566,7 +566,7 @@ max: props.max, min: props.min, })), - }); + }) } ``` diff --git a/packages/frameworks/vue/CHANGELOG.md b/packages/frameworks/vue/CHANGELOG.md index 4dec5e8300..99c6643a5f 100644 --- a/packages/frameworks/vue/CHANGELOG.md +++ b/packages/frameworks/vue/CHANGELOG.md @@ -716,13 +716,10 @@ ```ts // Before - type Ref = - | string - | Vue.Ref - | ((ref: Element | Vue.ComponentPublicInstance | null) => void); + type Ref = string | Vue.Ref | ((ref: Element | Vue.ComponentPublicInstance | null) => void) // After - type Ref = VNodeRef; + type Ref = VNodeRef ``` - Updated dependencies [[`61c11646`](https://github.com/chakra-ui/zag/commit/61c116467c1758bdda7efe1f27d4ed26e7d44624), diff --git a/packages/machines/checkbox/CHANGELOG.md b/packages/machines/checkbox/CHANGELOG.md index 9145cf7289..10fa71cd8e 100644 --- a/packages/machines/checkbox/CHANGELOG.md +++ b/packages/machines/checkbox/CHANGELOG.md @@ -870,7 +870,7 @@ id: "1", checked: true, }), - ); + ) // this will update the checkbox when the `checked` value changes const [state, send] = useMachine(checkbox.machine({ id: "1" }), { @@ -878,7 +878,7 @@ // when this value changes, the checkbox will be checked/unchecked checked: true, }, - }); + }) ``` ### Patch Changes diff --git a/packages/machines/color-picker/CHANGELOG.md b/packages/machines/color-picker/CHANGELOG.md index f1c71195dd..26980a77d6 100644 --- a/packages/machines/color-picker/CHANGELOG.md +++ b/packages/machines/color-picker/CHANGELOG.md @@ -4,7 +4,8 @@ ### Patch Changes -- Updated dependencies [[`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a), [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83)]: +- Updated dependencies [[`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a), + [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83)]: - @zag-js/dismissable@0.39.0 - @zag-js/popper@0.39.0 - @zag-js/anatomy@0.39.0 diff --git a/packages/machines/combobox/CHANGELOG.md b/packages/machines/combobox/CHANGELOG.md index 926308dd6e..d2d16a88e1 100644 --- a/packages/machines/combobox/CHANGELOG.md +++ b/packages/machines/combobox/CHANGELOG.md @@ -4,9 +4,13 @@ ### Patch Changes -- [#1324](https://github.com/chakra-ui/zag/pull/1324) [`ce5b7ce`](https://github.com/chakra-ui/zag/commit/ce5b7ce9ab48cd0dfb750a3fc7c0452912625b6d) Thanks [@TylerAPfledderer](https://github.com/TylerAPfledderer)! - Fixes issue where on load -- if the user set initial `value` to context -- `value` should be used to check whether a - trigger button using `clearTriggerProps` should be visible on the page. -- Updated dependencies [[`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a), [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83)]: +- [#1324](https://github.com/chakra-ui/zag/pull/1324) + [`ce5b7ce`](https://github.com/chakra-ui/zag/commit/ce5b7ce9ab48cd0dfb750a3fc7c0452912625b6d) Thanks + [@TylerAPfledderer](https://github.com/TylerAPfledderer)! - Fixes issue where on load -- if the user set initial + `value` to context -- `value` should be used to check whether a trigger button using `clearTriggerProps` should be + visible on the page. +- Updated dependencies [[`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a), + [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83)]: - @zag-js/dismissable@0.39.0 - @zag-js/popper@0.39.0 - @zag-js/anatomy@0.39.0 @@ -724,12 +728,12 @@ const collection = select.collection({ items: [], itemToString(item) { - return item.label; + return item.label }, itemToValue(item) { - return item.value; + return item.value }, - }); + }) // Pass the collection to the select machine const [state, send] = useMachine( @@ -737,7 +741,7 @@ collection, id: useId(), }), - ); + ) ``` ### Patch Changes diff --git a/packages/machines/date-picker/CHANGELOG.md b/packages/machines/date-picker/CHANGELOG.md index d896a212c8..df25e4cd20 100644 --- a/packages/machines/date-picker/CHANGELOG.md +++ b/packages/machines/date-picker/CHANGELOG.md @@ -4,11 +4,13 @@ ### Minor Changes -- [`6b93232`](https://github.com/chakra-ui/zag/commit/6b932324a776ab66fc34b9dc8673400b60500f1c) Thanks [@segunadebayo](https://github.com/segunadebayo)! - Remove unused `parse` function +- [`6b93232`](https://github.com/chakra-ui/zag/commit/6b932324a776ab66fc34b9dc8673400b60500f1c) Thanks + [@segunadebayo](https://github.com/segunadebayo)! - Remove unused `parse` function ### Patch Changes -- Updated dependencies [[`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a), [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83)]: +- Updated dependencies [[`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a), + [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83)]: - @zag-js/dismissable@0.39.0 - @zag-js/popper@0.39.0 - @zag-js/anatomy@0.39.0 diff --git a/packages/machines/dialog/CHANGELOG.md b/packages/machines/dialog/CHANGELOG.md index d896751a7a..664ed9bdf3 100644 --- a/packages/machines/dialog/CHANGELOG.md +++ b/packages/machines/dialog/CHANGELOG.md @@ -888,7 +888,7 @@ ```jsx // this is will open the dialog initially - const [state, send] = useMachine(dialog.machine({ id: "1", open: true })); + const [state, send] = useMachine(dialog.machine({ id: "1", open: true })) // this will open the dialog when the `open` value changes const [state, send] = useMachine(dialog.machine({ id: "1" }), { @@ -896,7 +896,7 @@ // when this value changes, the dialog will open/close open: true, }, - }); + }) ``` ### Patch Changes diff --git a/packages/machines/editable/CHANGELOG.md b/packages/machines/editable/CHANGELOG.md index bd5b70bfd8..0bd0c25aa3 100644 --- a/packages/machines/editable/CHANGELOG.md +++ b/packages/machines/editable/CHANGELOG.md @@ -1283,7 +1283,7 @@ editable.machine({ placeholder: { edit: "Enter...", preview: "Add name..." }, }), - ); + ) ``` ## 0.1.4 diff --git a/packages/machines/floating-panel/README.md b/packages/machines/floating-panel/README.md new file mode 100644 index 0000000000..16cecc8293 --- /dev/null +++ b/packages/machines/floating-panel/README.md @@ -0,0 +1,19 @@ +# @zag-js/floating-panel + +Core logic for the floating-panel widget implemented as a state machine + +## Installation + +```sh +yarn add @zag-js/floating-panel +# or +npm i @zag-js/floating-panel +``` + +## Contribution + +Yes please! See the [contributing guidelines](https://github.com/chakra-ui/zag/blob/main/CONTRIBUTING.md) for details. + +## Licence + +This project is licensed under the terms of the [MIT license](https://github.com/chakra-ui/zag/blob/main/LICENSE). diff --git a/packages/machines/floating-panel/package.json b/packages/machines/floating-panel/package.json new file mode 100644 index 0000000000..daa7f64ba2 --- /dev/null +++ b/packages/machines/floating-panel/package.json @@ -0,0 +1,53 @@ +{ + "name": "@zag-js/floating-panel", + "version": "0.0.0", + "description": "Core logic for the floating-panel widget implemented as a state machine", + "keywords": [ + "js", + "machine", + "xstate", + "statechart", + "component", + "chakra-ui", + "floating-panel" + ], + "author": "Segun Adebayo ", + "homepage": "https://github.com/chakra-ui/zag#readme", + "license": "MIT", + "main": "src/index.ts", + "repository": "https://github.com/chakra-ui/zag/tree/main/packages/floating-panel", + "sideEffects": false, + "files": [ + "dist", + "src" + ], + "scripts": { + "build": "tsup", + "lint": "eslint src --ext .ts,.tsx", + "typecheck": "tsc --noEmit", + "prepack": "clean-package", + "postpack": "clean-package restore" + }, + "publishConfig": { + "access": "public" + }, + "bugs": { + "url": "https://github.com/chakra-ui/zag/issues" + }, + "dependencies": { + "@zag-js/anatomy": "workspace:*", + "@zag-js/dismissable": "workspace:*", + "@zag-js/core": "workspace:*", + "@zag-js/dom-query": "workspace:*", + "@zag-js/dom-event": "workspace:*", + "@zag-js/utils": "workspace:*", + "@zag-js/numeric-range": "workspace:*", + "@zag-js/rect-utils": "workspace:*", + "@zag-js/popper": "workspace:*", + "@zag-js/types": "workspace:*" + }, + "devDependencies": { + "clean-package": "2.2.0" + }, + "clean-package": "../../../clean-package.config.json" +} diff --git a/packages/machines/floating-panel/src/floating-panel.anatomy.ts b/packages/machines/floating-panel/src/floating-panel.anatomy.ts new file mode 100644 index 0000000000..7da5a078d2 --- /dev/null +++ b/packages/machines/floating-panel/src/floating-panel.anatomy.ts @@ -0,0 +1,19 @@ +import { createAnatomy } from "@zag-js/anatomy" + +export const anatomy = createAnatomy("floating-panel").parts( + "trigger", + "positioner", + "content", + "header", + "body", + "title", + "resizeTrigger", + "dragTrigger", + "minimizeTrigger", + "maximizeTrigger", + "closeTrigger", + "restoreTrigger", + "dock", +) + +export const parts = anatomy.build() diff --git a/packages/machines/floating-panel/src/floating-panel.connect.ts b/packages/machines/floating-panel/src/floating-panel.connect.ts new file mode 100644 index 0000000000..777accf2b8 --- /dev/null +++ b/packages/machines/floating-panel/src/floating-panel.connect.ts @@ -0,0 +1,205 @@ +import { getEventKey, getEventStep, getNativeEvent, type EventKeyMap } from "@zag-js/dom-event" +import { dataAttr, getEventTarget, isSelfEvent } from "@zag-js/dom-query" +import type { NormalizeProps, PropTypes } from "@zag-js/types" +import { parts } from "./floating-panel.anatomy" +import { dom } from "./floating-panel.dom" +import type { MachineApi, ResizeTriggerProps, Send, State } from "./floating-panel.types" +import { getResizeAxisStyle } from "./get-resize-axis-style" + +export function connect(state: State, send: Send, normalize: NormalizeProps): MachineApi { + const isOpen = state.hasTag("open") + const isDragging = state.matches("open.dragging") + const isResizing = state.matches("open.resizing") + + return { + isOpen, + isDragging, + isResizing, + + triggerProps: normalize.button({ + ...parts.trigger.attrs, + type: "button", + disabled: state.context.isDisabled, + id: dom.getTriggerId(state.context), + "data-state": isOpen ? "open" : "closed", + "data-dragging": dataAttr(isDragging), + "aria-controls": dom.getContentId(state.context), + onClick() { + if (state.context.isDisabled) return + send({ type: "OPEN" }) + }, + }), + + positionerProps: normalize.element({ + ...parts.positioner.attrs, + id: dom.getPositionerId(state.context), + style: { + position: "absolute", + top: "var(--y)", + left: "var(--x)", + }, + }), + + contentProps: normalize.element({ + ...parts.content.attrs, + role: "dialog", + tabIndex: 0, + hidden: !isOpen, + id: dom.getContentId(state.context), + "aria-labelledby": dom.getTitleId(state.context), + "data-state": isOpen ? "open" : "closed", + "data-dragging": dataAttr(isDragging), + style: { + position: "relative", + width: "var(--width)", + height: "var(--height)", + overflow: state.context.isMinimized ? "hidden" : undefined, + }, + onKeyDown(event) { + if (!isSelfEvent(getNativeEvent(event))) return + const step = getEventStep(event) * state.context.gridSize + const keyMap: EventKeyMap = { + Escape() { + send("ESCAPE") + }, + ArrowLeft() { + send({ type: "MOVE", direction: "left", step }) + }, + ArrowRight() { + send({ type: "MOVE", direction: "right", step }) + }, + ArrowUp() { + send({ type: "MOVE", direction: "up", step }) + }, + ArrowDown() { + send({ type: "MOVE", direction: "down", step }) + }, + } + + const handler = keyMap[getEventKey(event, state.context)] + + if (handler) { + event.preventDefault() + handler(event) + } + }, + }), + + closeTriggerProps: normalize.button({ + ...parts.closeTrigger.attrs, + disabled: state.context.isDisabled, + "aria-label": "Close Window", + type: "button", + onClick() { + send("CLOSE") + }, + }), + + minimizeTriggerProps: normalize.button({ + ...parts.minimizeTrigger.attrs, + disabled: state.context.isDisabled, + "aria-label": "Minimize Window", + hidden: state.context.isStaged, + type: "button", + onClick() { + send("MINIMIZE") + }, + }), + + maximizeTriggerProps: normalize.button({ + ...parts.maximizeTrigger.attrs, + disabled: state.context.isDisabled, + "aria-label": "Maximize Window", + hidden: state.context.isStaged, + type: "button", + onClick() { + send("MAXIMIZE") + }, + }), + + restoreTriggerProps: normalize.button({ + ...parts.restoreTrigger.attrs, + disabled: state.context.isDisabled, + "aria-label": "Restore Window", + hidden: !state.context.isStaged, + type: "button", + onClick() { + send("RESTORE") + }, + }), + + getResizeTriggerProps(props: ResizeTriggerProps) { + return normalize.element({ + ...parts.resizeTrigger.attrs, + "data-disabled": dataAttr(!state.context.canResize), + "data-axis": props.axis, + onPointerDown(event) { + if (!state.context.canResize || event.button == 2) return + + event.currentTarget.setPointerCapture(event.pointerId) + event.stopPropagation() + + send({ + type: "RESIZE_START", + axis: props.axis, + position: { x: event.clientX, y: event.clientY }, + }) + }, + style: { + position: "absolute", + touchAction: "none", + ...getResizeAxisStyle(props.axis), + }, + }) + }, + + dragTriggerProps: normalize.element({ + ...parts.dragTrigger.attrs, + "data-disabled": dataAttr(!state.context.canDrag), + onPointerDown(event) { + if (!state.context.canDrag || event.button == 2) return + + const target = getEventTarget(getNativeEvent(event)) + + if (target?.closest("button")) { + console.log(target?.closest("button")) + return + } + + event.currentTarget.setPointerCapture(event.pointerId) + event.stopPropagation() + + send({ + type: "DRAG_START", + pointerId: event.pointerId, + position: { x: event.clientX, y: event.clientY }, + }) + }, + onDoubleClick() { + send(state.context.isMaximized ? "RESTORE" : "MAXIMIZE") + }, + style: { + userSelect: "none", + touchAction: "none", + cursor: "move", + }, + }), + + titleProps: normalize.element({ + ...parts.title.attrs, + id: dom.getTitleId(state.context), + }), + + headerProps: normalize.element({ + ...parts.header.attrs, + id: dom.getHeaderId(state.context), + "data-dragging": dataAttr(isDragging), + }), + + bodyProps: normalize.element({ + ...parts.body.attrs, + "data-dragging": dataAttr(isDragging), + hidden: state.context.isMinimized, + }), + } +} diff --git a/packages/machines/floating-panel/src/floating-panel.dom.ts b/packages/machines/floating-panel/src/floating-panel.dom.ts new file mode 100644 index 0000000000..5df0f2bbe1 --- /dev/null +++ b/packages/machines/floating-panel/src/floating-panel.dom.ts @@ -0,0 +1,15 @@ +import { createScope } from "@zag-js/dom-query" +import type { MachineContext as Ctx } from "./floating-panel.types" + +export const dom = createScope({ + getTriggerId: (ctx: Ctx) => `float:${ctx.id}:trigger`, + getPositionerId: (ctx: Ctx) => `float:${ctx.id}:positioner`, + getContentId: (ctx: Ctx) => `float:${ctx.id}:content`, + getTitleId: (ctx: Ctx) => `float:${ctx.id}:title`, + getHeaderId: (ctx: Ctx) => `float:${ctx.id}:header`, + + getTriggerEl: (ctx: Ctx) => dom.getById(ctx, dom.getTriggerId(ctx)), + getPositionerEl: (ctx: Ctx) => dom.getById(ctx, dom.getPositionerId(ctx)), + getContentEl: (ctx: Ctx) => dom.getById(ctx, dom.getContentId(ctx)), + getHeaderEl: (ctx: Ctx) => dom.getById(ctx, dom.getHeaderId(ctx)), +}) diff --git a/packages/machines/floating-panel/src/floating-panel.machine.ts b/packages/machines/floating-panel/src/floating-panel.machine.ts new file mode 100644 index 0000000000..c6362057cb --- /dev/null +++ b/packages/machines/floating-panel/src/floating-panel.machine.ts @@ -0,0 +1,372 @@ +import { createMachine, guards } from "@zag-js/core" +import { addDomEvent, trackPointerMove } from "@zag-js/dom-event" +import { isHTMLElement } from "@zag-js/dom-query" +import { + addPoints, + clampPoint, + clampSize, + constrainRect, + createRect, + getElementRect, + getWindowRect, + isPointEqual, + isSizeEqual, + resizeRect, + subtractPoints, + type Point, + type Rect, + type Size, +} from "@zag-js/rect-utils" +import { compact, invariant, isEqual, match, pick } from "@zag-js/utils" +import { dom } from "./floating-panel.dom" +import type { MachineContext, MachineState, Stage, UserDefinedContext } from "./floating-panel.types" + +const { not } = guards + +export function machine(userContext: UserDefinedContext) { + const ctx = compact(userContext) + return createMachine( + { + id: "floating-panel", + initial: ctx.open ? "open" : "closed", + context: { + size: { width: 320, height: 400 }, + position: { x: 300, y: 100 }, + gridSize: 1, + disabled: false, + resizable: true, + draggable: true, + ...ctx, + stage: undefined, + lastEventPosition: null, + prevPosition: null, + prevSize: null, + boundaryRect: null, + }, + + computed: { + isMaximized: (ctx) => ctx.stage === "maximized", + isMinimized: (ctx) => ctx.stage === "minimized", + isStaged: (ctx) => !!ctx.stage, + isDisabled: (ctx) => !!ctx.disabled, + canResize: (ctx) => (ctx.resizable || !ctx.isDisabled) && !ctx.stage, + canDrag: (ctx) => (ctx.draggable || !ctx.isDisabled) && !ctx.isMaximized, + }, + + watch: { + position: ["setPositionStyle"], + size: ["setSizeStyle"], + }, + + states: { + closed: { + tags: ["closed"], + on: { + OPEN: { + target: "open", + actions: ["invokeOnOpen", "setPositionStyle", "setSizeStyle"], + }, + }, + }, + + open: { + tags: ["open"], + activities: ["trackBoundaryRect"], + on: { + DRAG_START: { + guard: not("isMaximized"), + target: "open.dragging", + actions: ["setPrevPosition"], + }, + RESIZE_START: { + guard: not("isMinimized"), + target: "open.resizing", + actions: ["setPrevSize"], + }, + CLOSE: { + target: "closed", + actions: ["invokeOnClose", "resetRect"], + }, + ESCAPE: { + guard: "closeOnEsc", + target: "closed", + actions: ["invokeOnClose", "resetRect"], + }, + MINIMIZE: { + actions: ["setMinimized", "invokeOnMinimize"], + }, + MAXIMIZE: { + actions: ["setMaximized", "invokeOnMaximize"], + }, + RESTORE: { + actions: ["setRestored"], + }, + MOVE: { + actions: ["setPositionFromKeybord"], + }, + }, + }, + + "open.dragging": { + tags: ["open"], + activities: ["trackPointerMove", "trackBoundaryRect"], + exit: ["clearPrevPosition"], + on: { + DRAG: { + actions: ["setPosition"], + }, + DRAG_END: { + target: "open", + actions: ["invokeOnDragEnd"], + }, + CLOSE: { + target: "closed", + actions: ["invokeOnClose", "resetRect"], + }, + ESCAPE: { + target: "open", + }, + }, + }, + + "open.resizing": { + tags: ["open"], + activities: ["trackPointerMove", "trackBoundaryRect"], + exit: ["clearPrevSize"], + on: { + DRAG: { + actions: ["setSize"], + }, + DRAG_END: { + target: "open", + actions: ["invokeOnResizeEnd"], + }, + CLOSE: { + target: "closed", + actions: ["invokeOnClose", "resetRect"], + }, + ESCAPE: { + target: "open", + }, + }, + }, + }, + }, + { + guards: { + closeOnEsc: (ctx) => !!ctx.closeOnEscape, + isMaximized: (ctx) => ctx.isMaximized, + isMinimized: (ctx) => ctx.isMinimized, + }, + activities: { + trackPointerMove(ctx, _evt, { send }) { + const doc = dom.getDoc(ctx) + return trackPointerMove(doc, { + onPointerMove({ point, event }) { + const { altKey, shiftKey } = event + send({ type: "DRAG", position: point, axis: _evt.axis, altKey, shiftKey }) + }, + onPointerUp() { + send("DRAG_END") + }, + }) + }, + trackBoundaryRect(ctx) { + const win = dom.getWin(ctx) + const el = ctx.getBoundaryEl?.() + + const adjust = (boundary: Rect) => { + const boundaryRect = pick(boundary, ["width", "height", "x", "y"]) + ctx.boundaryRect = boundaryRect + + const res = ctx.isMaximized + ? boundaryRect + : constrainRect( + { + ...ctx.position, + ...ctx.size, + }, + boundaryRect, + ) + + set.size(ctx, pick(res, ["width", "height"])) + set.position(ctx, pick(res, ["x", "y"])) + } + + if (isHTMLElement(el)) { + const exec = () => adjust(getElementRect(el)) + exec() + const obs = new win.ResizeObserver(exec) + obs.observe(el) + return () => obs.disconnect() + } + + const exec = () => adjust(getWindowRect(win)) + exec() + return addDomEvent(win, "resize", exec) + }, + }, + actions: { + setPrevPosition(ctx, evt) { + ctx.prevPosition = { ...ctx.position } + ctx.lastEventPosition = evt.position + }, + clearPrevPosition(ctx) { + if (!ctx.preserveOnClose) ctx.prevPosition = null + ctx.lastEventPosition = null + }, + setPosition(ctx, evt) { + let diff = subtractPoints(evt.position, ctx.lastEventPosition!) + + diff.x = Math.round(diff.x / ctx.gridSize) * ctx.gridSize + diff.y = Math.round(diff.y / ctx.gridSize) * ctx.gridSize + + let position = addPoints(ctx.prevPosition!, diff) + position = clampPoint(position, ctx.size, ctx.boundaryRect!) + + set.position(ctx, position) + }, + setPositionStyle(ctx) { + const el = dom.getPositionerEl(ctx) + el?.style.setProperty("--x", `${ctx.position.x}px`) + el?.style.setProperty("--y", `${ctx.position.y}px`) + }, + resetRect(ctx, _evt, { initialContext }) { + ctx.stage = undefined + if (!ctx.preserveOnClose) { + set.position(ctx, initialContext.position) + set.size(ctx, initialContext.size) + } + }, + setPrevSize(ctx, evt) { + ctx.prevSize = { ...ctx.size } + ctx.prevPosition = { ...ctx.position } + ctx.lastEventPosition = evt.position + }, + clearPrevSize(ctx) { + ctx.prevSize = null + ctx.prevPosition = null + ctx.lastEventPosition = null + }, + setSize(ctx, evt) { + if (!ctx.prevSize || !ctx.prevPosition || !ctx.lastEventPosition) return + + const prevRect = createRect({ ...ctx.prevPosition, ...ctx.prevSize }) + const offset = subtractPoints(evt.position, ctx.lastEventPosition) + + const nextRect = resizeRect(prevRect, offset, evt.axis, { + scalingOriginMode: evt.altKey ? "center" : "extent", + lockAspectRatio: !!ctx.lockAspectRatio || evt.shiftKey, + }) + + let nextSize = pick(nextRect, ["width", "height"]) + let nextPosition = pick(nextRect, ["x", "y"]) + + nextSize = clampSize(nextSize, ctx.minSize, ctx.maxSize) + set.size(ctx, nextSize) + + if (nextPosition) { + const point = clampPoint(nextPosition, nextSize, ctx.boundaryRect!) + set.position(ctx, point) + } + }, + setSizeStyle(ctx) { + const el = dom.getPositionerEl(ctx) + el?.style.setProperty("--width", `${ctx.size.width}px`) + el?.style.setProperty("--height", `${ctx.size.height}px`) + }, + setMaximized(ctx) { + // set max stage + set.stage(ctx, "maximized") + + // save previous + ctx.prevSize = ctx.size + ctx.prevPosition = ctx.position + + // update size + set.position(ctx, { x: 0, y: 0 }) + set.size(ctx, pick(ctx.boundaryRect!, ["height", "width"])) + }, + setMinimized(ctx) { + // set min stage + set.stage(ctx, "minimized") + + // save previous + ctx.prevSize = ctx.size + ctx.prevPosition = ctx.position + + // update size + const headerEl = dom.getHeaderEl(ctx) + if (!headerEl) return + const size = { + ...ctx.size, + height: headerEl?.offsetHeight, + } + set.size(ctx, size) + }, + setRestored(ctx) { + // remove stage + set.stage(ctx, undefined) + + // restore size + if (!ctx.prevSize || !ctx.prevPosition) return + set.size(ctx, ctx.prevSize) + set.position(ctx, ctx.prevPosition) + + // clear previous + ctx.prevSize = null + ctx.prevPosition = null + }, + setPositionFromKeybord(ctx, evt) { + invariant(evt.step == null, "step is required") + + let nextPosition = match(evt.direction, { + left: { x: ctx.position.x - evt.step, y: ctx.position.y }, + right: { x: ctx.position.x + evt.step, y: ctx.position.y }, + up: { x: ctx.position.x, y: ctx.position.y - evt.step }, + down: { x: ctx.position.x, y: ctx.position.y + evt.step }, + }) + + nextPosition = clampPoint(nextPosition, ctx.size, ctx.boundaryRect!) + set.position(ctx, nextPosition) + }, + invokeOnOpen(ctx) { + ctx.onOpenChange?.({ open: true }) + }, + invokeOnClose(ctx) { + ctx.onOpenChange?.({ open: false }) + }, + invokeOnDragEnd(ctx) { + ctx.onPositionChangeEnd?.({ position: ctx.position }) + }, + invokeOnResizeEnd(ctx) { + ctx.onSizeChangeEnd?.({ size: ctx.size }) + }, + invokeOnMinimize(ctx) { + ctx.onStageChange?.({ stage: "minimized" }) + }, + invokeOnMaximize(ctx) { + ctx.onStageChange?.({ stage: "maximized" }) + }, + }, + }, + ) +} + +const set = { + size(ctx: MachineContext, value: Size) { + if (isSizeEqual(ctx.size, value)) return + ctx.size = value + ctx.onSizeChange?.({ size: value }) + }, + position(ctx: MachineContext, value: Point) { + if (isPointEqual(ctx.position, value)) return + ctx.position = value + ctx.onPositionChange?.({ position: value }) + }, + stage(ctx: MachineContext, value: Stage | undefined) { + if (isEqual(ctx.stage, value)) return + ctx.stage = value + ctx.onStageChange?.({ stage: value }) + }, +} diff --git a/packages/machines/floating-panel/src/floating-panel.props.ts b/packages/machines/floating-panel/src/floating-panel.props.ts new file mode 100644 index 0000000000..3121f202d0 --- /dev/null +++ b/packages/machines/floating-panel/src/floating-panel.props.ts @@ -0,0 +1,30 @@ +import { createProps } from "@zag-js/types" +import { createSplitProps } from "@zag-js/utils" +import type { UserDefinedContext } from "./floating-panel.types" + +export const props = createProps()([ + "closeOnEscape", + "dir", + "disabled", + "draggable", + "getBoundaryEl", + "getRootNode", + "gridSize", + "id", + "lockAspectRatio", + "maxSize", + "minSize", + "onPositionChange", + "onPositionChangeEnd", + "onOpenChange", + "onStageChange", + "onSizeChange", + "onSizeChangeEnd", + "open", + "position", + "preserveOnClose", + "resizable", + "size", +]) + +export const splitProps = createSplitProps>(props) diff --git a/packages/machines/floating-panel/src/floating-panel.types.ts b/packages/machines/floating-panel/src/floating-panel.types.ts new file mode 100644 index 0000000000..83bb712a8a --- /dev/null +++ b/packages/machines/floating-panel/src/floating-panel.types.ts @@ -0,0 +1,183 @@ +import type { StateMachine as S } from "@zag-js/core" +import type { Point, RectInit, Size } from "@zag-js/rect-utils" +import type { CommonProperties, DirectionProperty, PropTypes, RequiredBy } from "@zag-js/types" + +export interface PositionChangeDetails { + position: Point +} + +export interface SizeChangeDetails { + size: Size +} + +export interface OpenChangeDetails { + open: boolean +} + +export type Stage = "minimized" | "maximized" + +export interface StageChangeDetails { + stage: Stage | undefined +} + +interface PublicContext extends DirectionProperty, CommonProperties { + /** + * Whether the panel is open + */ + open?: boolean + /** + * Whether the panel is draggable + */ + draggable?: boolean + /** + * Whether the panel is resizable + */ + resizable?: boolean + /** + * The size of the panel + */ + size: Size + /** + * The minimum size of the panel + */ + minSize?: Size + /** + * The maximum size of the panel + */ + maxSize?: Size + /** + * The position of the panel + */ + position: Point + /** + * Whether the panel is locked to its aspect ratio + */ + lockAspectRatio?: boolean + /** + * Whether the panel should close when the escape key is pressed + */ + closeOnEscape?: boolean + /** + * The boundary of the panel. Defaults to the window + */ + getBoundaryEl?(): HTMLElement + /** + * Whether the panel is disabled + */ + disabled?: boolean + /** + * Function called when the position of the panel changes via dragging + */ + onPositionChange?(details: PositionChangeDetails): void + /** + * Function called when the position of the panel changes via dragging ends + */ + onPositionChangeEnd?(details: PositionChangeDetails): void + /** + * Function called when the panel is opened or closed + */ + onOpenChange?(details: OpenChangeDetails): void + /** + * Function called when the size of the panel changes via resizing + */ + onSizeChange?(details: SizeChangeDetails): void + /** + * Function called when the size of the panel changes via resizing ends + */ + onSizeChangeEnd?(details: SizeChangeDetails): void + /** + * Whether the panel size and position should be preserved when it is closed + */ + preserveOnClose?: boolean + /** + * The snap grid for the panel + */ + gridSize: number + /** + * Function called when the stage of the panel changes + */ + onStageChange?(details: StageChangeDetails): void +} + +interface PrivateContext { + /** + * The rect of the boundary + */ + boundaryRect: RectInit | null + /** + * The last position of the mouse event + */ + lastEventPosition: Point | null + /** + * The previous position of the panel before dragging + */ + prevPosition: Point | null + /** + * The previous size of the panel before resizing + */ + prevSize: Size | null + /** + * The stage of the panel + */ + stage?: Stage +} + +type ComputedContext = Readonly<{ + isMaximized: boolean + isMinimized: boolean + isStaged: boolean + isDisabled: boolean + canResize: boolean + canDrag: boolean +}> + +export type UserDefinedContext = RequiredBy + +export interface MachineContext extends PublicContext, PrivateContext, ComputedContext {} + +export interface MachineState { + tags: "open" | "closed" + value: "open" | "open.dragging" | "open.resizing" | "closed" +} + +export type State = S.State + +export type Send = S.Send + +/* ----------------------------------------------------------------------------- + * Component props + * -----------------------------------------------------------------------------*/ + +export type ResizeTriggerAxis = "s" | "w" | "e" | "n" | "sw" | "nw" | "se" | "ne" + +export interface ResizeTriggerProps { + axis: ResizeTriggerAxis +} + +export interface MachineApi { + /** + * Whether the panel is open + */ + isOpen: boolean + /** + * Whether the panel is being dragged + */ + isDragging: boolean + /** + * Whether the panel is being resized + */ + isResizing: boolean + + dragTriggerProps: T["element"] + getResizeTriggerProps(props: ResizeTriggerProps): T["element"] + triggerProps: T["button"] + positionerProps: T["element"] + contentProps: T["element"] + titleProps: T["element"] + headerProps: T["element"] + bodyProps: T["element"] + closeTriggerProps: T["button"] + minimizeTriggerProps: T["button"] + maximizeTriggerProps: T["button"] + restoreTriggerProps: T["button"] +} diff --git a/packages/machines/floating-panel/src/get-resize-axis-style.ts b/packages/machines/floating-panel/src/get-resize-axis-style.ts new file mode 100644 index 0000000000..61ac70fb21 --- /dev/null +++ b/packages/machines/floating-panel/src/get-resize-axis-style.ts @@ -0,0 +1,72 @@ +import type { JSX } from "@zag-js/types" +import type { ResizeTriggerAxis } from "./floating-panel.types" + +export function getResizeAxisStyle(axis: ResizeTriggerAxis): JSX.CSSProperties { + switch (axis) { + case "n": + return { + cursor: "n-resize", + width: "100%", + left: "50%", + translate: "-50%", + } + + case "e": + return { + cursor: "e-resize", + height: "100%", + right: 0, + top: "50%", + translate: "0 -50%", + } + + case "s": + return { + cursor: "s-resize", + width: "100%", + bottom: 0, + left: "50%", + translate: "-50%", + } + + case "w": + return { + cursor: "w-resize", + height: "100%", + left: 0, + top: "50%", + translate: "0 -50%", + } + + case "se": + return { + cursor: "se-resize", + bottom: 0, + right: 0, + } + + case "sw": + return { + cursor: "sw-resize", + bottom: 0, + left: 0, + } + + case "ne": + return { + cursor: "ne-resize", + top: 0, + right: 0, + } + + case "nw": + return { + cursor: "nw-resize", + top: 0, + left: 0, + } + + default: + throw new Error(`Invalid axis: ${axis}`) + } +} diff --git a/packages/machines/floating-panel/src/index.ts b/packages/machines/floating-panel/src/index.ts new file mode 100644 index 0000000000..e6d68aaf39 --- /dev/null +++ b/packages/machines/floating-panel/src/index.ts @@ -0,0 +1,14 @@ +export { anatomy } from "./floating-panel.anatomy" +export { connect } from "./floating-panel.connect" +export { machine } from "./floating-panel.machine" +export * from "./floating-panel.props" +export type { + UserDefinedContext as Context, + PositionChangeDetails, + OpenChangeDetails, + SizeChangeDetails, + ResizeTriggerAxis, + ResizeTriggerProps, + StageChangeDetails, + MachineApi as Api, +} from "./floating-panel.types" diff --git a/packages/machines/floating-panel/tsconfig.json b/packages/machines/floating-panel/tsconfig.json new file mode 100644 index 0000000000..8e781cd154 --- /dev/null +++ b/packages/machines/floating-panel/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.json", + "include": ["src"], + "compilerOptions": { + "tsBuildInfoFile": "node_modules/.cache/.tsbuildinfo" + } +} diff --git a/packages/machines/hover-card/CHANGELOG.md b/packages/machines/hover-card/CHANGELOG.md index 11cc9c93fc..9acf2dc721 100644 --- a/packages/machines/hover-card/CHANGELOG.md +++ b/packages/machines/hover-card/CHANGELOG.md @@ -4,7 +4,8 @@ ### Patch Changes -- Updated dependencies [[`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a), [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83)]: +- Updated dependencies [[`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a), + [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83)]: - @zag-js/dismissable@0.39.0 - @zag-js/popper@0.39.0 - @zag-js/anatomy@0.39.0 @@ -826,7 +827,7 @@ ```jsx // this is will open the dialog initially - const [state, send] = useMachine(dialog.machine({ id: "1", open: true })); + const [state, send] = useMachine(dialog.machine({ id: "1", open: true })) // this will open the dialog when the `open` value changes const [state, send] = useMachine(dialog.machine({ id: "1" }), { @@ -834,7 +835,7 @@ // when this value changes, the dialog will open/close open: true, }, - }); + }) ``` ### Patch Changes @@ -854,7 +855,7 @@ of the popover. This API supports all the positioning options. ```js - api.setPositioning({ placement: "top" }); + api.setPositioning({ placement: "top" }) ``` ### Patch Changes @@ -948,15 +949,15 @@ [@TimKolberger](https://github.com/TimKolberger)! - Add `open` and `close` functions to the connect api: ```ts - import * as hoverCard from "@zag-js/hover-card"; + import * as hoverCard from "@zag-js/hover-card" - const api = hoverCard.connect(state, send, normalizeProps); + const api = hoverCard.connect(state, send, normalizeProps) // call `open` to open the hover card - api.open(); + api.open() // call `close` to close the hover card - api.close(); + api.close() ``` ### Patch Changes diff --git a/packages/machines/menu/CHANGELOG.md b/packages/machines/menu/CHANGELOG.md index a5111a1dab..4aa180289c 100644 --- a/packages/machines/menu/CHANGELOG.md +++ b/packages/machines/menu/CHANGELOG.md @@ -4,11 +4,13 @@ ### Minor Changes -- [`492d152`](https://github.com/chakra-ui/zag/commit/492d152c04ab367e6c4fd80b1a1f1a68bb287e46) Thanks [@segunadebayo](https://github.com/segunadebayo)! - Expose `onEscapeKeyDown` event handler +- [`492d152`](https://github.com/chakra-ui/zag/commit/492d152c04ab367e6c4fd80b1a1f1a68bb287e46) Thanks + [@segunadebayo](https://github.com/segunadebayo)! - Expose `onEscapeKeyDown` event handler ### Patch Changes -- Updated dependencies [[`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a), [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83)]: +- Updated dependencies [[`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a), + [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83)]: - @zag-js/dismissable@0.39.0 - @zag-js/popper@0.39.0 - @zag-js/anatomy@0.39.0 @@ -1084,7 +1086,7 @@ of the popover. This API supports all the positioning options. ```js - api.setPositioning({ placement: "top" }); + api.setPositioning({ placement: "top" }) ``` ### Patch Changes diff --git a/packages/machines/number-input/CHANGELOG.md b/packages/machines/number-input/CHANGELOG.md index 05cf87f4b2..2e47fba97e 100644 --- a/packages/machines/number-input/CHANGELOG.md +++ b/packages/machines/number-input/CHANGELOG.md @@ -1189,7 +1189,7 @@ // details => { value: string, valueAsNumber: number } }, }), - ); + ) ``` - Add `focus()` and `blur()` methods to the machine's `api` diff --git a/packages/machines/pin-input/CHANGELOG.md b/packages/machines/pin-input/CHANGELOG.md index c50b5e642a..39a0f1743a 100644 --- a/packages/machines/pin-input/CHANGELOG.md +++ b/packages/machines/pin-input/CHANGELOG.md @@ -1140,7 +1140,7 @@ define their own patterns to validate against. ```jsx - const [state, send] = useMachine(pinInput.machine({ pattern: "^[0-9.-]+$" })); + const [state, send] = useMachine(pinInput.machine({ pattern: "^[0-9.-]+$" })) ``` Improve form support by exposing `hiddenInputProps`. When the machine is passed a `name` property: diff --git a/packages/machines/popover/CHANGELOG.md b/packages/machines/popover/CHANGELOG.md index 5f93a4375c..192bf83c0e 100644 --- a/packages/machines/popover/CHANGELOG.md +++ b/packages/machines/popover/CHANGELOG.md @@ -4,7 +4,8 @@ ### Patch Changes -- Updated dependencies [[`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a), [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83)]: +- Updated dependencies [[`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a), + [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83)]: - @zag-js/dismissable@0.39.0 - @zag-js/popper@0.39.0 - @zag-js/anatomy@0.39.0 @@ -1027,7 +1028,7 @@ ```jsx // this is will open the dialog initially - const [state, send] = useMachine(dialog.machine({ id: "1", open: true })); + const [state, send] = useMachine(dialog.machine({ id: "1", open: true })) // this will open the dialog when the `open` value changes const [state, send] = useMachine(dialog.machine({ id: "1" }), { @@ -1035,7 +1036,7 @@ // when this value changes, the dialog will open/close open: true, }, - }); + }) ``` ### Patch Changes @@ -1058,7 +1059,7 @@ positioning options. ```js - api.setPositioning({ placement: "top" }); + api.setPositioning({ placement: "top" }) ``` - Updated dependencies [[`fa2ecc8e`](https://github.com/chakra-ui/zag/commit/fa2ecc8ea235b824f45deda10070c321f896886c), diff --git a/packages/machines/progress/CHANGELOG.md b/packages/machines/progress/CHANGELOG.md index 9934682b06..4f344a0807 100644 --- a/packages/machines/progress/CHANGELOG.md +++ b/packages/machines/progress/CHANGELOG.md @@ -4,7 +4,9 @@ ### Patch Changes -- [`a621fe5`](https://github.com/chakra-ui/zag/commit/a621fe5445f938341e761539471e737eaa149da5) Thanks [@segunadebayo](https://github.com/segunadebayo)! - Fix issue where progress throws when value is initially set to `null` +- [`a621fe5`](https://github.com/chakra-ui/zag/commit/a621fe5445f938341e761539471e737eaa149da5) Thanks + [@segunadebayo](https://github.com/segunadebayo)! - Fix issue where progress throws when value is initially set to + `null` - Updated dependencies []: - @zag-js/anatomy@0.39.0 diff --git a/packages/machines/rating-group/CHANGELOG.md b/packages/machines/rating-group/CHANGELOG.md index ea068a11e0..2e062608ab 100644 --- a/packages/machines/rating-group/CHANGELOG.md +++ b/packages/machines/rating-group/CHANGELOG.md @@ -863,10 +863,10 @@ ```js // set the value of the rating-group to 3 - api.setValue(3); + api.setValue(3) // clear the value of the rating-group - api.clearValue(); + api.clearValue() ``` ### Patch Changes diff --git a/packages/machines/select/CHANGELOG.md b/packages/machines/select/CHANGELOG.md index a22b90fa59..4dcd4d439e 100644 --- a/packages/machines/select/CHANGELOG.md +++ b/packages/machines/select/CHANGELOG.md @@ -4,9 +4,11 @@ ### Patch Changes -- [`ce1975a`](https://github.com/chakra-ui/zag/commit/ce1975a7a3335710b8863c370ffc168d108decca) Thanks [@segunadebayo](https://github.com/segunadebayo)! - Fix issue where multiple select doesn't work correctly in forms. +- [`ce1975a`](https://github.com/chakra-ui/zag/commit/ce1975a7a3335710b8863c370ffc168d108decca) Thanks + [@segunadebayo](https://github.com/segunadebayo)! - Fix issue where multiple select doesn't work correctly in forms. -- Updated dependencies [[`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a), [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83)]: +- Updated dependencies [[`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a), + [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83)]: - @zag-js/dismissable@0.39.0 - @zag-js/popper@0.39.0 - @zag-js/anatomy@0.39.0 @@ -826,12 +828,12 @@ const collection = select.collection({ items: [], itemToString(item) { - return item.label; + return item.label }, itemToValue(item) { - return item.value; + return item.value }, - }); + }) // Pass the collection to the select machine const [state, send] = useMachine( @@ -839,7 +841,7 @@ collection, id: useId(), }), - ); + ) ``` ### Patch Changes diff --git a/packages/machines/slider/CHANGELOG.md b/packages/machines/slider/CHANGELOG.md index e21e9fc6b0..61debfcd72 100644 --- a/packages/machines/slider/CHANGELOG.md +++ b/packages/machines/slider/CHANGELOG.md @@ -4,7 +4,8 @@ ### Minor Changes -- [`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a) Thanks [@segunadebayo](https://github.com/segunadebayo)! - Support custom `name` for each slider thumb +- [`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a) Thanks + [@segunadebayo](https://github.com/segunadebayo)! - Support custom `name` for each slider thumb ### Patch Changes diff --git a/packages/machines/splitter/CHANGELOG.md b/packages/machines/splitter/CHANGELOG.md index 5b43203cc4..19c9e5b505 100644 --- a/packages/machines/splitter/CHANGELOG.md +++ b/packages/machines/splitter/CHANGELOG.md @@ -4,7 +4,9 @@ ### Patch Changes -- [#1333](https://github.com/chakra-ui/zag/pull/1333) [`2b594ce`](https://github.com/chakra-ui/zag/commit/2b594ce0481dba747f0aa41853e09c33caf50d90) Thanks [@anubra266](https://github.com/anubra266)! - Add `data-orientation` attribute to splitter panel part +- [#1333](https://github.com/chakra-ui/zag/pull/1333) + [`2b594ce`](https://github.com/chakra-ui/zag/commit/2b594ce0481dba747f0aa41853e09c33caf50d90) Thanks + [@anubra266](https://github.com/anubra266)! - Add `data-orientation` attribute to splitter panel part - Updated dependencies []: - @zag-js/anatomy@0.39.0 diff --git a/packages/machines/switch/CHANGELOG.md b/packages/machines/switch/CHANGELOG.md index b7e1fb6f09..2326f23747 100644 --- a/packages/machines/switch/CHANGELOG.md +++ b/packages/machines/switch/CHANGELOG.md @@ -814,7 +814,7 @@ id: "1", checked: true, }), - ); + ) // this will update the checkbox when the `checked` value changes const [state, send] = useMachine(checkbox.machine({ id: "1" }), { @@ -822,7 +822,7 @@ // when this value changes, the checkbox will be checked/unchecked checked: true, }, - }); + }) ``` ### Patch Changes diff --git a/packages/machines/tabs/CHANGELOG.md b/packages/machines/tabs/CHANGELOG.md index 83a840bfc2..e0b37225e4 100644 --- a/packages/machines/tabs/CHANGELOG.md +++ b/packages/machines/tabs/CHANGELOG.md @@ -4,7 +4,9 @@ ### Patch Changes -- [#1333](https://github.com/chakra-ui/zag/pull/1333) [`2b594ce`](https://github.com/chakra-ui/zag/commit/2b594ce0481dba747f0aa41853e09c33caf50d90) Thanks [@anubra266](https://github.com/anubra266)! - Tabs: Rename `tablist` part to `list` to match naming convention +- [#1333](https://github.com/chakra-ui/zag/pull/1333) + [`2b594ce`](https://github.com/chakra-ui/zag/commit/2b594ce0481dba747f0aa41853e09c33caf50d90) Thanks + [@anubra266](https://github.com/anubra266)! - Tabs: Rename `tablist` part to `list` to match naming convention - Updated dependencies []: - @zag-js/anatomy@0.39.0 diff --git a/packages/machines/tags-input/CHANGELOG.md b/packages/machines/tags-input/CHANGELOG.md index 960763d87e..6b64911eff 100644 --- a/packages/machines/tags-input/CHANGELOG.md +++ b/packages/machines/tags-input/CHANGELOG.md @@ -4,7 +4,9 @@ ### Patch Changes -- [`409caad`](https://github.com/chakra-ui/zag/commit/409caad0332e8893ed3cf9f44c9bc21aa3308e74) Thanks [@segunadebayo](https://github.com/segunadebayo)! - Fix issue where setting `addOnPaste` to `false` and pasting text prevents subsequent tags from being added +- [`409caad`](https://github.com/chakra-ui/zag/commit/409caad0332e8893ed3cf9f44c9bc21aa3308e74) Thanks + [@segunadebayo](https://github.com/segunadebayo)! - Fix issue where setting `addOnPaste` to `false` and pasting text + prevents subsequent tags from being added - Updated dependencies []: - @zag-js/anatomy@0.39.0 diff --git a/packages/machines/tooltip/CHANGELOG.md b/packages/machines/tooltip/CHANGELOG.md index a6005eda1e..d9053758e3 100644 --- a/packages/machines/tooltip/CHANGELOG.md +++ b/packages/machines/tooltip/CHANGELOG.md @@ -851,7 +851,7 @@ ```jsx // this is will open the dialog initially - const [state, send] = useMachine(dialog.machine({ id: "1", open: true })); + const [state, send] = useMachine(dialog.machine({ id: "1", open: true })) // this will open the dialog when the `open` value changes const [state, send] = useMachine(dialog.machine({ id: "1" }), { @@ -859,7 +859,7 @@ // when this value changes, the dialog will open/close open: true, }, - }); + }) ``` ### Patch Changes @@ -879,7 +879,7 @@ of the popover. This API supports all the positioning options. ```js - api.setPositioning({ placement: "top" }); + api.setPositioning({ placement: "top" }) ``` ### Patch Changes diff --git a/packages/machines/tour/CHANGELOG.md b/packages/machines/tour/CHANGELOG.md index fd95c5a015..c07ebcc4f7 100644 --- a/packages/machines/tour/CHANGELOG.md +++ b/packages/machines/tour/CHANGELOG.md @@ -4,7 +4,8 @@ ### Patch Changes -- Updated dependencies [[`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a), [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83)]: +- Updated dependencies [[`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a), + [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83)]: - @zag-js/dismissable@0.39.0 - @zag-js/popper@0.39.0 - @zag-js/anatomy@0.39.0 diff --git a/packages/utilities/core/CHANGELOG.md b/packages/utilities/core/CHANGELOG.md index a2f8b542d4..53c877d811 100644 --- a/packages/utilities/core/CHANGELOG.md +++ b/packages/utilities/core/CHANGELOG.md @@ -105,12 +105,12 @@ const collection = select.collection({ items: [], itemToString(item) { - return item.label; + return item.label }, itemToValue(item) { - return item.value; + return item.value }, - }); + }) // Pass the collection to the select machine const [state, send] = useMachine( @@ -118,7 +118,7 @@ collection, id: useId(), }), - ); + ) ``` ## 0.17.0 diff --git a/packages/utilities/core/src/object.ts b/packages/utilities/core/src/object.ts index 56fb63845f..90d0329a6f 100644 --- a/packages/utilities/core/src/object.ts +++ b/packages/utilities/core/src/object.ts @@ -21,3 +21,16 @@ export function json(value: any) { const isPlainObject = (value: any) => { return value && typeof value === "object" && value.constructor === Object } + +export function pick, K extends keyof T>(obj: T, keys: K[]): Pick { + const filtered: Partial = {} + + for (const key of keys) { + const value = obj[key] + if (value !== undefined) { + filtered[key] = value + } + } + + return filtered as any +} diff --git a/packages/utilities/dismissable/CHANGELOG.md b/packages/utilities/dismissable/CHANGELOG.md index 96cedec439..00305a30a8 100644 --- a/packages/utilities/dismissable/CHANGELOG.md +++ b/packages/utilities/dismissable/CHANGELOG.md @@ -4,7 +4,8 @@ ### Patch Changes -- [`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a) Thanks [@segunadebayo](https://github.com/segunadebayo)! - Use capture phase for escape keydown dismissing +- [`27f9ec0`](https://github.com/chakra-ui/zag/commit/27f9ec0812f19228921158885107ed43d559544a) Thanks + [@segunadebayo](https://github.com/segunadebayo)! - Use capture phase for escape keydown dismissing - Updated dependencies []: - @zag-js/utils@0.39.0 diff --git a/packages/utilities/dismissable/src/layer-stack.ts b/packages/utilities/dismissable/src/layer-stack.ts index 967d6c4f7e..c6a9492277 100644 --- a/packages/utilities/dismissable/src/layer-stack.ts +++ b/packages/utilities/dismissable/src/layer-stack.ts @@ -42,7 +42,8 @@ export const layerStack = { return Array.from(this.branches).some((branch) => contains(branch, target)) }, add(layer: Layer) { - this.layers.push(layer) + const num = this.layers.push(layer) + layer.node.style.setProperty("--stack-index", `${num}`) }, addBranch(node: HTMLElement) { this.branches.push(node) @@ -58,6 +59,7 @@ export const layerStack = { } // remove this layer this.layers.splice(index, 1) + node.style.removeProperty("--stack-index") }, removeBranch(node: HTMLElement) { const index = this.branches.indexOf(node) diff --git a/packages/utilities/element-rect/CHANGELOG.md b/packages/utilities/element-rect/CHANGELOG.md index 5b76c2e81a..cde35ca849 100644 --- a/packages/utilities/element-rect/CHANGELOG.md +++ b/packages/utilities/element-rect/CHANGELOG.md @@ -142,11 +142,11 @@ position or rect. ```js - import { trackElementRect } from "@zag-js/element-rect"; + import { trackElementRect } from "@zag-js/element-rect" - trackElementRect(element, update, { scope: "size" }); // only track size - trackElementRect(element, update, { scope: "position" }); // only track position - trackElementRect(element, update, { scope: "rect" }); // track size and position (default) + trackElementRect(element, update, { scope: "size" }) // only track size + trackElementRect(element, update, { scope: "position" }) // only track position + trackElementRect(element, update, { scope: "rect" }) // track size and position (default) ``` ## 0.2.2 diff --git a/packages/utilities/popper/CHANGELOG.md b/packages/utilities/popper/CHANGELOG.md index 1e07d046b2..6b7e58dae4 100644 --- a/packages/utilities/popper/CHANGELOG.md +++ b/packages/utilities/popper/CHANGELOG.md @@ -4,7 +4,9 @@ ### Patch Changes -- [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83) Thanks [@segunadebayo](https://github.com/segunadebayo)! - Fix issue where `crossAxis` positioning property doesn't work in some cases. +- [`565a7e4`](https://github.com/chakra-ui/zag/commit/565a7e46070edb7bb2a39ed9d065dcaee418db83) Thanks + [@segunadebayo](https://github.com/segunadebayo)! - Fix issue where `crossAxis` positioning property doesn't work in + some cases. - Updated dependencies []: - @zag-js/utils@0.39.0 diff --git a/packages/utilities/rect/src/affine-transform.ts b/packages/utilities/rect/src/affine-transform.ts new file mode 100644 index 0000000000..15c971837a --- /dev/null +++ b/packages/utilities/rect/src/affine-transform.ts @@ -0,0 +1,177 @@ +import type { Point } from "./types" + +export class AffineTransform { + m00: number + m01: number + m02: number + m10: number + m11: number + m12: number + + constructor([m00, m01, m02, m10, m11, m12]: Iterable = [0, 0, 0, 0, 0, 0]) { + this.m00 = m00 + this.m01 = m01 + this.m02 = m02 + this.m10 = m10 + this.m11 = m11 + this.m12 = m12 + } + + applyTo(point: Point): Point { + const { x, y } = point + const { m00, m01, m02, m10, m11, m12 } = this + + return { + x: m00 * x + m01 * y + m02, + y: m10 * x + m11 * y + m12, + } + } + + prepend(other: AffineTransform): AffineTransform { + return new AffineTransform([ + this.m00 * other.m00 + this.m01 * other.m10, // m00 + this.m00 * other.m01 + this.m01 * other.m11, // m01 + this.m00 * other.m02 + this.m01 * other.m12 + this.m02, // m02 + this.m10 * other.m00 + this.m11 * other.m10, // m10 + this.m10 * other.m01 + this.m11 * other.m11, // m11 + this.m10 * other.m02 + this.m11 * other.m12 + this.m12, // m12 + ]) + } + + append(other: AffineTransform): AffineTransform { + return new AffineTransform([ + other.m00 * this.m00 + other.m01 * this.m10, // m00 + other.m00 * this.m01 + other.m01 * this.m11, // m01 + other.m00 * this.m02 + other.m01 * this.m12 + other.m02, // m02 + other.m10 * this.m00 + other.m11 * this.m10, // m10 + other.m10 * this.m01 + other.m11 * this.m11, // m11 + other.m10 * this.m02 + other.m11 * this.m12 + other.m12, // m12 + ]) + } + + get determinant() { + return this.m00 * this.m11 - this.m01 * this.m10 + } + + get isInvertible() { + const det = this.determinant + + return isFinite(det) && isFinite(this.m02) && isFinite(this.m12) && det !== 0 + } + + invert() { + const det = this.determinant + + return new AffineTransform([ + this.m11 / det, // m00 + -this.m01 / det, // m01 + (this.m01 * this.m12 - this.m11 * this.m02) / det, // m02 + -this.m10 / det, // m10 + this.m00 / det, // m11 + (this.m10 * this.m02 - this.m00 * this.m12) / det, // m12 + ]) + } + + get array(): number[] { + return [this.m00, this.m01, this.m02, this.m10, this.m11, this.m12, 0, 0, 1] + } + + get float32Array(): Float32Array { + return new Float32Array(this.array) + } + + // Static + + static get identity(): AffineTransform { + return new AffineTransform([1, 0, 0, 0, 1, 0]) + } + + static rotate(theta: number, origin?: Point): AffineTransform { + const rotation = new AffineTransform([Math.cos(theta), -Math.sin(theta), 0, Math.sin(theta), Math.cos(theta), 0]) + + if (origin && (origin.x !== 0 || origin.y !== 0)) { + return AffineTransform.multiply( + AffineTransform.translate(origin.x, origin.y), + rotation, + AffineTransform.translate(-origin.x, -origin.y), + ) + } + + return rotation + } + + rotate: (typeof AffineTransform)["rotate"] = (...args) => { + return this.prepend(AffineTransform.rotate(...args)) + } + + static scale(sx: number, sy: number = sx, origin: Point = { x: 0, y: 0 }): AffineTransform { + const scale = new AffineTransform([sx, 0, 0, 0, sy, 0]) + + if (origin.x !== 0 || origin.y !== 0) { + return AffineTransform.multiply( + AffineTransform.translate(origin.x, origin.y), + scale, + AffineTransform.translate(-origin.x, -origin.y), + ) + } + + return scale + } + + scale: (typeof AffineTransform)["scale"] = (...args) => { + return this.prepend(AffineTransform.scale(...args)) + } + + static translate(tx: number, ty: number): AffineTransform { + return new AffineTransform([1, 0, tx, 0, 1, ty]) + } + + translate: (typeof AffineTransform)["translate"] = (...args) => { + return this.prepend(AffineTransform.translate(...args)) + } + + static multiply(...[first, ...rest]: AffineTransform[]): AffineTransform { + if (!first) return AffineTransform.identity + return rest.reduce((result, item) => result.prepend(item), first) + } + + get a() { + return this.m00 + } + + get b() { + return this.m10 + } + + get c() { + return this.m01 + } + + get d() { + return this.m11 + } + + get tx() { + return this.m02 + } + + get ty() { + return this.m12 + } + + get scaleComponents(): Point { + return { x: this.a, y: this.d } + } + + get translationComponents(): Point { + return { x: this.tx, y: this.ty } + } + + get skewComponents(): Point { + return { x: this.c, y: this.b } + } + + toString() { + return `matrix(${this.a}, ${this.b}, ${this.c}, ${this.d}, ${this.tx}, ${this.ty})` + } +} diff --git a/packages/utilities/rect/src/align.ts b/packages/utilities/rect/src/align.ts index a620b6d53c..2562af5dcf 100644 --- a/packages/utilities/rect/src/align.ts +++ b/packages/utilities/rect/src/align.ts @@ -1,46 +1,22 @@ -import type { Rect } from "./rect" +import type { AlignOptions, HAlign, Rect, VAlign } from "./types" function hAlign(a: Rect, ref: Rect, h: HAlign): Rect { let x = ref.minX - - if (h === "left-inside") { - x = ref.minX - } - if (h === "left-outside") { - x = ref.minX - ref.width - } - if (h === "right-inside") { - x = ref.maxX - ref.width - } - if (h === "right-outside") { - x = ref.maxX - } - if (h === "center") { - x = ref.midX - ref.width / 2 - } - + if (h === "left-inside") x = ref.minX + if (h === "left-outside") x = ref.minX - ref.width + if (h === "right-inside") x = ref.maxX - ref.width + if (h === "right-outside") x = ref.maxX + if (h === "center") x = ref.midX - ref.width / 2 return { ...a, x } } function vAlign(a: Rect, ref: Rect, v: VAlign): Rect { let y = ref.minY - - if (v === "top-inside") { - y = ref.minY - } - if (v === "top-outside") { - y = ref.minY - a.height - } - if (v === "bottom-inside") { - y = ref.maxY - a.height - } - if (v === "bottom-outside") { - y = ref.maxY - } - if (v === "center") { - y = ref.midY - a.height / 2 - } - + if (v === "top-inside") y = ref.minY + if (v === "top-outside") y = ref.minY - a.height + if (v === "bottom-inside") y = ref.maxY - a.height + if (v === "bottom-outside") y = ref.maxY + if (v === "center") y = ref.midY - a.height / 2 return { ...a, y } } @@ -48,12 +24,3 @@ export function alignRect(a: Rect, ref: Rect, options: AlignOptions): Rect { const { h, v } = options return vAlign(hAlign(a, ref, h), ref, v) } - -export type AlignOptions = { - h: HAlign - v: VAlign -} - -export type HAlign = "left-inside" | "left-outside" | "center" | "right-inside" | "right-outside" - -export type VAlign = "top-inside" | "top-outside" | "center" | "bottom-inside" | "bottom-outside" diff --git a/packages/utilities/rect/src/clamp.ts b/packages/utilities/rect/src/clamp.ts new file mode 100644 index 0000000000..ff6999ef1a --- /dev/null +++ b/packages/utilities/rect/src/clamp.ts @@ -0,0 +1,26 @@ +import type { Point, RectInit, Size } from "./types" + +const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max) + +export const clampPoint = (position: Point, size: Size, boundaryRect: RectInit) => { + const x = clamp(position.x, boundaryRect.x, boundaryRect.x + boundaryRect.width - size.width) + const y = clamp(position.y, boundaryRect.y, boundaryRect.y + boundaryRect.height - size.height) + return { x, y } +} + +const defaultMinSize: Size = { + width: 0, + height: 0, +} + +const defaultMaxSize: Size = { + width: Infinity, + height: Infinity, +} + +export const clampSize = (size: Size, minSize = defaultMinSize, maxSize = defaultMaxSize) => { + return { + width: Math.min(Math.max(size.width, minSize!.width), maxSize!.width), + height: Math.min(Math.max(size.height, minSize!.height), maxSize!.height), + } +} diff --git a/packages/utilities/rect/src/closest.ts b/packages/utilities/rect/src/closest.ts index 753bd30291..deca544874 100644 --- a/packages/utilities/rect/src/closest.ts +++ b/packages/utilities/rect/src/closest.ts @@ -1,6 +1,5 @@ import { distance } from "./distance" -import type { Rect } from "./rect" -import type { Point, RectSide } from "./types" +import type { Point, Rect, RectSide } from "./types" export function closest(...pts: Point[]) { return (a: Point): Point => { @@ -11,18 +10,10 @@ export function closest(...pts: Point[]) { } export function closestSideToRect(ref: Rect, r: Rect): RectSide { - if (r.maxX <= ref.minX) { - return "left" - } - if (r.minX >= ref.maxX) { - return "right" - } - if (r.maxY <= ref.minY) { - return "top" - } - if (r.minY >= ref.maxY) { - return "bottom" - } + if (r.maxX <= ref.minX) return "left" + if (r.minX >= ref.maxX) return "right" + if (r.maxY <= ref.minY) return "top" + if (r.minY >= ref.maxY) return "bottom" return "left" } diff --git a/packages/utilities/rect/src/compass.ts b/packages/utilities/rect/src/compass.ts new file mode 100644 index 0000000000..72d4e37679 --- /dev/null +++ b/packages/utilities/rect/src/compass.ts @@ -0,0 +1,25 @@ +import type { Point } from "./types" + +export type CompassDirection = "n" | "ne" | "e" | "se" | "s" | "sw" | "w" | "nw" + +export const compassDirectionMap: Record = { + n: { x: 0.5, y: 0 }, + ne: { x: 1, y: 0 }, + e: { x: 1, y: 0.5 }, + se: { x: 1, y: 1 }, + s: { x: 0.5, y: 1 }, + sw: { x: 0, y: 1 }, + w: { x: 0, y: 0.5 }, + nw: { x: 0, y: 0 }, +} + +export const oppositeDirectionMap: Record = { + n: "s", + ne: "sw", + e: "w", + se: "nw", + s: "n", + sw: "ne", + w: "e", + nw: "se", +} diff --git a/packages/utilities/rect/src/constrain.ts b/packages/utilities/rect/src/constrain.ts new file mode 100644 index 0000000000..f840ad54bc --- /dev/null +++ b/packages/utilities/rect/src/constrain.ts @@ -0,0 +1,18 @@ +import type { RectInit } from "./types" + +// given a rect and a boundary, return a new rect that is constrained within the boundary +// resize or reposition the rect so that it fits within the boundary +export const constrainRect = (rect: RectInit, boundary: RectInit): RectInit => { + const { x, y, width, height } = rect + const { x: bx, y: by, width: bw, height: bh } = boundary + + const left = Math.max(bx, Math.min(x, bx + bw - width)) + const top = Math.max(by, Math.min(y, by + bh - height)) + + return { + x: left, + y: top, + width: Math.min(width, bw), + height: Math.min(height, bh), + } +} diff --git a/packages/utilities/rect/src/contains.ts b/packages/utilities/rect/src/contains.ts index 2b60139a16..f84dfed498 100644 --- a/packages/utilities/rect/src/contains.ts +++ b/packages/utilities/rect/src/contains.ts @@ -1,5 +1,5 @@ -import { getRectCorners, isRect, type Rect } from "./rect" -import type { Point } from "./types" +import { getRectCorners, isRect } from "./rect" +import type { Point, Rect } from "./types" export function containsPoint(r: Rect, p: Point): boolean { return r.minX <= p.x && p.x <= r.maxX && r.minY <= p.y && p.y <= r.maxY diff --git a/packages/utilities/rect/src/distance.ts b/packages/utilities/rect/src/distance.ts index 394a7e9adf..f87b9b90b0 100644 --- a/packages/utilities/rect/src/distance.ts +++ b/packages/utilities/rect/src/distance.ts @@ -1,8 +1,7 @@ import { intersects } from "./intersection" -import type { Rect } from "./rect" -import type { Point, RectSide } from "./types" +import type { Point, Rect, RectSide } from "./types" -export type DistanceValue = Point & { +export interface DistanceValue extends Point { value: number } @@ -13,8 +12,10 @@ export function distance(a: Point, b: Point = { x: 0, y: 0 }): number { export function distanceFromPoint(r: Rect, p: Point): DistanceValue { let x = 0 let y = 0 + if (p.x < r.x) x = r.x - p.x else if (p.x > r.maxX) x = p.x - r.maxX + if (p.y < r.y) y = r.y - p.y else if (p.y > r.maxY) y = p.y - r.maxY return { x, y, value: distance({ x, y }) } diff --git a/packages/utilities/rect/src/equality.ts b/packages/utilities/rect/src/equality.ts new file mode 100644 index 0000000000..18a3bc27ff --- /dev/null +++ b/packages/utilities/rect/src/equality.ts @@ -0,0 +1,13 @@ +import type { Point, RectInit, Size } from "./types" + +export const isSizeEqual = (a: Size, b: Size) => { + return a.width === b.width && a.height === b.height +} + +export const isPointEqual = (a: Point, b: Point) => { + return a.x === b.x && a.y === b.y +} + +export const isRectEqual = (a: RectInit, b: RectInit) => { + return isPointEqual(a, b) && isSizeEqual(a, b) +} diff --git a/packages/utilities/rect/src/from-element.ts b/packages/utilities/rect/src/from-element.ts index b55913ef0f..edaa71c5b5 100644 --- a/packages/utilities/rect/src/from-element.ts +++ b/packages/utilities/rect/src/from-element.ts @@ -1,6 +1,8 @@ -import { createRect, type Rect } from "./rect" +import { createRect } from "./rect" +import type { Rect } from "./types" const styleCache = new WeakMap() + function getCacheComputedStyle(el: HTMLElement) { if (!styleCache.has(el)) { const win = el.ownerDocument.defaultView || window diff --git a/packages/utilities/rect/src/from-points.ts b/packages/utilities/rect/src/from-points.ts index 88d66cecee..07c55520c4 100644 --- a/packages/utilities/rect/src/from-points.ts +++ b/packages/utilities/rect/src/from-points.ts @@ -1,5 +1,5 @@ -import { createRect, type Rect } from "./rect" -import type { Point } from "./types" +import { createRect } from "./rect" +import type { Point, Rect } from "./types" export function getRectFromPoints(...pts: Point[]): Rect { const xs = pts.map((p) => p.x) diff --git a/packages/utilities/rect/src/from-range.ts b/packages/utilities/rect/src/from-range.ts index 8851d314d3..d31c814110 100644 --- a/packages/utilities/rect/src/from-range.ts +++ b/packages/utilities/rect/src/from-range.ts @@ -1,6 +1,7 @@ -import { createRect, type Rect } from "./rect" +import { createRect } from "./rect" import { getElementRect } from "./from-element" import { union } from "./union" +import type { Rect } from "./types" export function fromRange(range: Range): Rect { let rs: Rect[] = [] diff --git a/packages/utilities/rect/src/from-rotation.ts b/packages/utilities/rect/src/from-rotation.ts index d7e3de6fb8..b488074175 100644 --- a/packages/utilities/rect/src/from-rotation.ts +++ b/packages/utilities/rect/src/from-rotation.ts @@ -1,5 +1,5 @@ -import { createRect, getRectCorners, type Rect } from "./rect" -import type { Point } from "./types" +import { createRect, getRectCorners } from "./rect" +import type { Point, Rect } from "./types" export function toRad(d: number) { return ((d % 360) * Math.PI) / 180 diff --git a/packages/utilities/rect/src/from-window.ts b/packages/utilities/rect/src/from-window.ts index fca3bfc8a3..05ac478ef8 100644 --- a/packages/utilities/rect/src/from-window.ts +++ b/packages/utilities/rect/src/from-window.ts @@ -1,4 +1,5 @@ -import { createRect, type Rect } from "./rect" +import { createRect } from "./rect" +import type { Rect } from "./types" export type WindowRectOptions = { /** diff --git a/packages/utilities/rect/src/get-polygon.ts b/packages/utilities/rect/src/get-polygon.ts deleted file mode 100644 index 578e18f9da..0000000000 --- a/packages/utilities/rect/src/get-polygon.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { createRect, getRectCorners } from "./rect" -import type { RectValue } from "./types" - -export function getElementPolygon(rectValue: RectValue, placement: string) { - const rect = createRect(rectValue) - const { top, right, left, bottom } = getRectCorners(rect) - const [base] = placement.split("-") - - return { - top: [left, top, right, bottom], - right: [top, right, bottom, left], - bottom: [top, left, bottom, right], - left: [right, top, left, bottom], - }[base] -} diff --git a/packages/utilities/rect/src/index.ts b/packages/utilities/rect/src/index.ts index 12654e9c6b..5d24785169 100644 --- a/packages/utilities/rect/src/index.ts +++ b/packages/utilities/rect/src/index.ts @@ -1,16 +1,20 @@ +export * from "./affine-transform" export * from "./align" +export * from "./clamp" export * from "./closest" +export * from "./constrain" export * from "./contains" export * from "./distance" +export * from "./equality" export * from "./from-element" export * from "./from-points" export * from "./from-range" export * from "./from-rotation" export * from "./from-window" -export * from "./get-polygon" export * from "./intersection" export * from "./operations" export * from "./polygon" export * from "./rect" +export * from "./resize" export * from "./types" export * from "./union" diff --git a/packages/utilities/rect/src/intersection.ts b/packages/utilities/rect/src/intersection.ts index d9c8ec5a21..75cc944c57 100644 --- a/packages/utilities/rect/src/intersection.ts +++ b/packages/utilities/rect/src/intersection.ts @@ -1,5 +1,5 @@ -import { createRect, type Rect } from "./rect" -import type { RectSide } from "./types" +import { createRect } from "./rect" +import type { Rect, RectSide } from "./types" /** * Checks if a Rect intersects another Rect diff --git a/packages/utilities/rect/src/operations.ts b/packages/utilities/rect/src/operations.ts index e892768903..8bb010dbcb 100644 --- a/packages/utilities/rect/src/operations.ts +++ b/packages/utilities/rect/src/operations.ts @@ -1,5 +1,5 @@ -import { createRect, type Rect } from "./rect" -import type { Point, RectInset, SymmetricRectInset } from "./types" +import { createRect } from "./rect" +import type { Point, Rect, RectInset, SymmetricRectInset } from "./types" export const isSymmetric = (v: any): v is SymmetricRectInset => "dx" in v || "dy" in v diff --git a/packages/utilities/rect/src/polygon.ts b/packages/utilities/rect/src/polygon.ts index 430716adf6..78af3b2ea6 100644 --- a/packages/utilities/rect/src/polygon.ts +++ b/packages/utilities/rect/src/polygon.ts @@ -1,4 +1,18 @@ -import type { Point } from "./types" +import { createRect, getRectCorners } from "./rect" +import type { Point, RectInit } from "./types" + +export function getElementPolygon(rectValue: RectInit, placement: string) { + const rect = createRect(rectValue) + const { top, right, left, bottom } = getRectCorners(rect) + const [base] = placement.split("-") + + return { + top: [left, top, right, bottom], + right: [top, right, bottom, left], + bottom: [top, left, bottom, right], + left: [right, top, left, bottom], + }[base] +} export function isPointInPolygon(polygon: Point[], point: Point) { const { x, y } = point diff --git a/packages/utilities/rect/src/rect.ts b/packages/utilities/rect/src/rect.ts index 57f9f6687d..1eb2963927 100644 --- a/packages/utilities/rect/src/rect.ts +++ b/packages/utilities/rect/src/rect.ts @@ -1,8 +1,23 @@ -import type { RectEdge, RectValue } from "./types" +import type { Point, Rect, RectEdge, RectInit } from "./types" -const point = (x: number, y: number) => ({ x, y }) +/* ----------------------------------------------------------------------------- + * Point + * -----------------------------------------------------------------------------*/ -export function createRect(r: RectValue) { +export const createPoint = (x: number, y: number) => ({ x, y }) + +export const subtractPoints = (a: Point, b: Point) => createPoint(a.x - b.x, a.y - b.y) +export const addPoints = (a: Point, b: Point) => createPoint(a.x + b.x, a.y + b.y) + +export function isPoint(v: any): v is Point { + return Reflect.has(v, "x") && Reflect.has(v, "y") +} + +/* ----------------------------------------------------------------------------- + * Rect + * -----------------------------------------------------------------------------*/ + +export function createRect(r: RectInit): Rect { const { x, y, width, height } = r const midX = x + width / 2 const midY = y + height / 2 @@ -17,32 +32,27 @@ export function createRect(r: RectValue) { maxY: y + height, midX, midY, - center: point(midX, midY), + center: createPoint(midX, midY), } } -export type Rect = ReturnType - -const hasProp = (obj: any, prop: T): obj is Record => - Object.prototype.hasOwnProperty.call(obj, prop) - export function isRect(v: any): v is Rect { - return hasProp(v, "x") && hasProp(v, "y") && hasProp(v, "width") && hasProp(v, "height") + return Reflect.has(v, "x") && Reflect.has(v, "y") && Reflect.has(v, "width") && Reflect.has(v, "height") } export function getRectCenters(v: Rect) { - const top = point(v.midX, v.minY) - const right = point(v.maxX, v.midY) - const bottom = point(v.midX, v.maxY) - const left = point(v.minX, v.midY) + const top = createPoint(v.midX, v.minY) + const right = createPoint(v.maxX, v.midY) + const bottom = createPoint(v.midX, v.maxY) + const left = createPoint(v.minX, v.midY) return { top, right, bottom, left } } export function getRectCorners(v: Rect) { - const top = point(v.minX, v.minY) - const right = point(v.maxX, v.minY) - const bottom = point(v.maxX, v.maxY) - const left = point(v.minX, v.maxY) + const top = createPoint(v.minX, v.minY) + const right = createPoint(v.maxX, v.minY) + const bottom = createPoint(v.maxX, v.maxY) + const left = createPoint(v.minX, v.maxY) return { top, right, bottom, left } } diff --git a/packages/utilities/rect/src/resize.ts b/packages/utilities/rect/src/resize.ts new file mode 100644 index 0000000000..bdc63b3001 --- /dev/null +++ b/packages/utilities/rect/src/resize.ts @@ -0,0 +1,106 @@ +import { AffineTransform } from "./affine-transform" +import { compassDirectionMap, oppositeDirectionMap, type CompassDirection } from "./compass" +import type { Point, Rect, RectInit, ScalingOptions } from "./types" + +const { sign, abs, min } = Math + +function getRectExtentPoint(rect: Rect, direction: CompassDirection) { + const { minX, minY, maxX, maxY, midX, midY } = rect + const x = direction.includes("w") ? minX : direction.includes("e") ? maxX : midX + const y = direction.includes("n") ? minY : direction.includes("s") ? maxY : midY + return { x, y } +} + +function getOppositeDirection(direction: CompassDirection) { + return oppositeDirectionMap[direction] +} + +export function resizeRect(rect: Rect, offset: Point, direction: CompassDirection, opts: ScalingOptions) { + const { scalingOriginMode, lockAspectRatio } = opts + + const extent = getRectExtentPoint(rect, direction) + + const oppositeDirection = getOppositeDirection(direction) + const oppositeExtent = getRectExtentPoint(rect, oppositeDirection) + + if (scalingOriginMode === "center") { + offset = { x: offset.x * 2, y: offset.y * 2 } + } + + const newExtent = { + x: extent.x + offset.x, + y: extent.y + offset.y, + } + + const multiplier = { + x: compassDirectionMap[direction].x * 2 - 1, + y: compassDirectionMap[direction].y * 2 - 1, + } + + const newSize = { + width: newExtent.x - oppositeExtent.x, + height: newExtent.y - oppositeExtent.y, + } + + const scaleX = (multiplier.x * newSize.width) / rect.width + const scaleY = (multiplier.y * newSize.height) / rect.height + + const largestMagnitude = abs(scaleX) > abs(scaleY) ? scaleX : scaleY + + const scale = lockAspectRatio + ? { x: largestMagnitude, y: largestMagnitude } + : { + x: extent.x === oppositeExtent.x ? 1 : scaleX, + y: extent.y === oppositeExtent.y ? 1 : scaleY, + } + + if (extent.y === oppositeExtent.y) { + scale.y = abs(scale.y) + } else if (sign(scale.y) !== sign(scaleY)) { + scale.y *= -1 + } + + if (extent.x === oppositeExtent.x) { + scale.x = abs(scale.x) + } else if (sign(scale.x) !== sign(scaleX)) { + scale.x *= -1 + } + + switch (scalingOriginMode) { + case "extent": + return transformRect(rect, AffineTransform.scale(scale.x, scale.y, oppositeExtent), false) + case "center": + return transformRect( + rect, + AffineTransform.scale(scale.x, scale.y, { + x: rect.midX, + y: rect.midY, + }), + false, + ) + } +} + +function createRectFromPoints(initialPoint: Point, finalPoint: Point, normalized: boolean = true): RectInit { + if (normalized) { + return { + x: min(finalPoint.x, initialPoint.x), + y: min(finalPoint.y, initialPoint.y), + width: abs(finalPoint.x - initialPoint.x), + height: abs(finalPoint.y - initialPoint.y), + } + } + + return { + x: initialPoint.x, + y: initialPoint.y, + width: finalPoint.x - initialPoint.x, + height: finalPoint.y - initialPoint.y, + } +} + +function transformRect(rect: Rect, transform: AffineTransform, normalized = true): RectInit { + const p1 = transform.applyTo({ x: rect.minX, y: rect.minY }) + const p2 = transform.applyTo({ x: rect.maxX, y: rect.maxY }) + return createRectFromPoints(p1, p2, normalized) +} diff --git a/packages/utilities/rect/src/types.ts b/packages/utilities/rect/src/types.ts index 161c434524..6feb0d2054 100644 --- a/packages/utilities/rect/src/types.ts +++ b/packages/utilities/rect/src/types.ts @@ -1,12 +1,38 @@ -export type Point = { x: number; y: number } +/* ----------------------------------------------------------------------------- + * Basic + * -----------------------------------------------------------------------------*/ -export type RectValue = { +export interface Point { x: number y: number +} + +export interface Size { width: number height: number } +export interface Bounds { + minX: number + midX: number + maxX: number + minY: number + midY: number + maxY: number +} + +export interface CenterPoint { + center: Point +} + +export interface RectInit extends Point, Size {} + +export interface Rect extends Point, Size, Bounds, CenterPoint {} + +/* ----------------------------------------------------------------------------- + * Edge and Side + * -----------------------------------------------------------------------------*/ + export type RectSide = "top" | "right" | "bottom" | "left" export type RectPoint = @@ -42,4 +68,25 @@ export type RectCenters = Record & { export type RectInset = Partial> -export type SymmetricRectInset = { dx?: number; dy?: number } +export interface SymmetricRectInset { + dx?: number + dy?: number +} + +export interface ScalingOptions { + scalingOriginMode: "center" | "extent" + lockAspectRatio: boolean +} + +/* ----------------------------------------------------------------------------- + * Alignment + * -----------------------------------------------------------------------------*/ + +export interface AlignOptions { + h: HAlign + v: VAlign +} + +export type HAlign = "left-inside" | "left-outside" | "center" | "right-inside" | "right-outside" + +export type VAlign = "top-inside" | "top-outside" | "center" | "bottom-inside" | "bottom-outside" diff --git a/packages/utilities/rect/src/union.ts b/packages/utilities/rect/src/union.ts index 298222de91..ede981476c 100644 --- a/packages/utilities/rect/src/union.ts +++ b/packages/utilities/rect/src/union.ts @@ -1,28 +1,16 @@ import { getRectFromPoints } from "./from-points" -import type { Rect } from "./rect" +import type { Rect } from "./types" const { min, max } = Math export function union(...rs: Rect[]): Rect { const pMin = { - x: min.apply( - Math, - rs.map((r) => r.minX), - ), - y: min.apply( - Math, - rs.map((r) => r.minY), - ), + x: min(...rs.map((r) => r.minX)), + y: min(...rs.map((r) => r.minY)), } const pMax = { - x: max.apply( - Math, - rs.map((r) => r.maxX), - ), - y: max.apply( - Math, - rs.map((r) => r.maxY), - ), + x: max(...rs.map((r) => r.maxX)), + y: max(...rs.map((r) => r.maxY)), } return getRectFromPoints(pMin, pMax) } diff --git a/packages/utilities/tabbable/CHANGELOG.md b/packages/utilities/tabbable/CHANGELOG.md index 08ad52b888..53a79d048a 100644 --- a/packages/utilities/tabbable/CHANGELOG.md +++ b/packages/utilities/tabbable/CHANGELOG.md @@ -411,20 +411,16 @@ element in DOM sequence. ```js - import { proxyTabFocus } from "@zag-js/tabbable"; + import { proxyTabFocus } from "@zag-js/tabbable" export function App() { - const referenceRef = useRef(); - const containerRef = useRef(); + const referenceRef = useRef() + const containerRef = useRef() useEffect(() => { - const focusElement = (el) => el.focus({ preventScroll: true }); - return proxyTabFocus( - containerRef.current, - referenceRef.current, - focusElement, - ); - }, []); + const focusElement = (el) => el.focus({ preventScroll: true }) + return proxyTabFocus(containerRef.current, referenceRef.current, focusElement) + }, []) return (
@@ -437,7 +433,7 @@
- ); + ) } ``` diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d1b1050917..67c4365c34 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,6 +225,9 @@ importers: '@zag-js/file-utils': specifier: workspace:* version: link:../../packages/utilities/file-utils + '@zag-js/floating-panel': + specifier: workspace:* + version: link:../../packages/machines/floating-panel '@zag-js/focus-scope': specifier: workspace:* version: link:../../packages/utilities/focus-scope @@ -475,6 +478,9 @@ importers: '@zag-js/file-utils': specifier: workspace:* version: link:../../packages/utilities/file-utils + '@zag-js/floating-panel': + specifier: workspace:* + version: link:../../packages/machines/floating-panel '@zag-js/focus-scope': specifier: workspace:* version: link:../../packages/utilities/focus-scope @@ -713,6 +719,9 @@ importers: '@zag-js/file-utils': specifier: workspace:* version: link:../../packages/utilities/file-utils + '@zag-js/floating-panel': + specifier: workspace:* + version: link:../../packages/machines/floating-panel '@zag-js/focus-scope': specifier: workspace:* version: link:../../packages/utilities/focus-scope @@ -954,6 +963,9 @@ importers: '@zag-js/file-utils': specifier: workspace:* version: link:../../packages/utilities/file-utils + '@zag-js/floating-panel': + specifier: workspace:* + version: link:../../packages/machines/floating-panel '@zag-js/focus-scope': specifier: workspace:* version: link:../../packages/utilities/focus-scope @@ -1183,6 +1195,9 @@ importers: '@zag-js/file-utils': specifier: workspace:* version: link:../../packages/utilities/file-utils + '@zag-js/floating-panel': + specifier: workspace:* + version: link:../../packages/machines/floating-panel '@zag-js/focus-scope': specifier: workspace:* version: link:../../packages/utilities/focus-scope @@ -1430,6 +1445,9 @@ importers: '@zag-js/file-utils': specifier: workspace:* version: link:../../packages/utilities/file-utils + '@zag-js/floating-panel': + specifier: workspace:* + version: link:../../packages/machines/floating-panel '@zag-js/focus-scope': specifier: workspace:* version: link:../../packages/utilities/focus-scope @@ -2165,6 +2183,43 @@ importers: specifier: 2.2.0 version: 2.2.0 + packages/machines/floating-panel: + dependencies: + '@zag-js/anatomy': + specifier: workspace:* + version: link:../../anatomy + '@zag-js/core': + specifier: workspace:* + version: link:../../core + '@zag-js/dismissable': + specifier: workspace:* + version: link:../../utilities/dismissable + '@zag-js/dom-event': + specifier: workspace:* + version: link:../../utilities/dom-event + '@zag-js/dom-query': + specifier: workspace:* + version: link:../../utilities/dom-query + '@zag-js/numeric-range': + specifier: workspace:* + version: link:../../utilities/numeric-range + '@zag-js/popper': + specifier: workspace:* + version: link:../../utilities/popper + '@zag-js/rect-utils': + specifier: workspace:* + version: link:../../utilities/rect + '@zag-js/types': + specifier: workspace:* + version: link:../../types + '@zag-js/utils': + specifier: workspace:* + version: link:../../utilities/core + devDependencies: + clean-package: + specifier: 2.2.0 + version: 2.2.0 + packages/machines/hover-card: dependencies: '@zag-js/anatomy': @@ -3453,7 +3508,7 @@ importers: version: link:../packages/machines/tooltip contentlayer: specifier: 0.3.4 - version: 0.3.4(esbuild@0.20.1) + version: 0.3.4(esbuild@0.20.2) hastscript: specifier: 9.0.0 version: 9.0.0 @@ -3468,7 +3523,7 @@ importers: version: 14.1.3(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0) next-contentlayer: specifier: 0.3.4 - version: 0.3.4(contentlayer@0.3.4)(esbuild@0.20.1)(next@14.1.3)(react-dom@18.2.0)(react@18.2.0) + version: 0.3.4(contentlayer@0.3.4)(esbuild@0.20.2)(next@14.1.3)(react-dom@18.2.0)(react@18.2.0) next-seo: specifier: 6.5.0 version: 6.5.0(next@14.1.3)(react-dom@18.2.0)(react@18.2.0) @@ -4530,10 +4585,10 @@ packages: chalk: 5.3.0 dev: false - /@contentlayer/cli@0.3.4(esbuild@0.20.1): + /@contentlayer/cli@0.3.4(esbuild@0.20.2): resolution: {integrity: sha512-vNDwgLuhYNu+m70NZ3XK9kexKNguuxPXg7Yvzj3B34cEilQjjzSrcTY/i+AIQm9V7uT5GGshx9ukzPf+SmoszQ==} dependencies: - '@contentlayer/core': 0.3.4(esbuild@0.20.1) + '@contentlayer/core': 0.3.4(esbuild@0.20.2) '@contentlayer/utils': 0.3.4 clipanion: 3.2.1(typanion@3.14.0) typanion: 3.14.0 @@ -4544,10 +4599,10 @@ packages: - supports-color dev: false - /@contentlayer/client@0.3.4(esbuild@0.20.1): + /@contentlayer/client@0.3.4(esbuild@0.20.2): resolution: {integrity: sha512-QSlLyc3y4PtdC5lFw0L4wTZUH8BQnv2nk37hNCsPAqGf+dRO7TLAzdc+2/mVIRgK+vSH+pSOzjLsQpFxxXRTZA==} dependencies: - '@contentlayer/core': 0.3.4(esbuild@0.20.1) + '@contentlayer/core': 0.3.4(esbuild@0.20.2) transitivePeerDependencies: - '@effect-ts/otel-node' - esbuild @@ -4555,7 +4610,7 @@ packages: - supports-color dev: false - /@contentlayer/core@0.3.4(esbuild@0.20.1): + /@contentlayer/core@0.3.4(esbuild@0.20.2): resolution: {integrity: sha512-o68oBLwfYZ+2vtgfk1lgHxOl3LoxvRNiUfeQ8IWFWy/L4wnIkKIqLZX01zlRE5IzYM+ZMMN5V0cKQlO7DsyR9g==} peerDependencies: esbuild: 0.17.x || 0.18.x @@ -4569,9 +4624,9 @@ packages: '@contentlayer/utils': 0.3.4 camel-case: 4.1.2 comment-json: 4.2.3 - esbuild: 0.20.1 + esbuild: 0.20.2 gray-matter: 4.0.3 - mdx-bundler: 9.2.1(esbuild@0.20.1) + mdx-bundler: 9.2.1(esbuild@0.20.2) rehype-stringify: 9.0.4 remark-frontmatter: 4.0.1 remark-parse: 10.0.2 @@ -4584,10 +4639,10 @@ packages: - supports-color dev: false - /@contentlayer/source-files@0.3.4(esbuild@0.20.1): + /@contentlayer/source-files@0.3.4(esbuild@0.20.2): resolution: {integrity: sha512-4njyn0OFPu7WY4tAjMxiJgWOKeiHuBOGdQ36EYE03iij/pPPRbiWbL+cmLccYXUFEW58mDwpqROZZm6pnxjRDQ==} dependencies: - '@contentlayer/core': 0.3.4(esbuild@0.20.1) + '@contentlayer/core': 0.3.4(esbuild@0.20.2) '@contentlayer/utils': 0.3.4 chokidar: 3.6.0 fast-glob: 3.3.2 @@ -4605,11 +4660,11 @@ packages: - supports-color dev: false - /@contentlayer/source-remote-files@0.3.4(esbuild@0.20.1): + /@contentlayer/source-remote-files@0.3.4(esbuild@0.20.2): resolution: {integrity: sha512-cyiv4sNUySZvR0uAKlM+kSAELzNd2h2QT1R2e41dRKbwOUVxeLfmGiLugr0aVac6Q3xYcD99dbHyR1xWPV+w9w==} dependencies: - '@contentlayer/core': 0.3.4(esbuild@0.20.1) - '@contentlayer/source-files': 0.3.4(esbuild@0.20.1) + '@contentlayer/core': 0.3.4(esbuild@0.20.2) + '@contentlayer/source-files': 0.3.4(esbuild@0.20.2) '@contentlayer/utils': 0.3.4 transitivePeerDependencies: - '@effect-ts/otel-node' @@ -4821,14 +4876,14 @@ packages: resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==} dev: false - /@esbuild-plugins/node-resolve@0.1.4(esbuild@0.20.1): + /@esbuild-plugins/node-resolve@0.1.4(esbuild@0.20.2): resolution: {integrity: sha512-haFQ0qhxEpqtWWY0kx1Y5oE3sMyO1PcoSiWEPrAw6tm/ZOOLXjSs6Q+v1v9eyuVF0nNt50YEvrcrvENmyoMv5g==} peerDependencies: esbuild: '*' dependencies: '@types/resolve': 1.20.6 debug: 4.3.4 - esbuild: 0.20.1 + esbuild: 0.20.2 escape-string-regexp: 4.0.0 resolve: 1.22.8 transitivePeerDependencies: @@ -4843,22 +4898,12 @@ packages: requiresBuild: true optional: true - /@esbuild/aix-ppc64@0.20.1: - resolution: {integrity: sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - requiresBuild: true - dev: false - optional: true - /@esbuild/aix-ppc64@0.20.2: resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] requiresBuild: true - dev: true optional: true /@esbuild/android-arm64@0.17.19: @@ -4878,22 +4923,12 @@ packages: requiresBuild: true optional: true - /@esbuild/android-arm64@0.20.1: - resolution: {integrity: sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: false - optional: true - /@esbuild/android-arm64@0.20.2: resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} engines: {node: '>=12'} cpu: [arm64] os: [android] requiresBuild: true - dev: true optional: true /@esbuild/android-arm@0.17.19: @@ -4913,22 +4948,12 @@ packages: requiresBuild: true optional: true - /@esbuild/android-arm@0.20.1: - resolution: {integrity: sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true - dev: false - optional: true - /@esbuild/android-arm@0.20.2: resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} engines: {node: '>=12'} cpu: [arm] os: [android] requiresBuild: true - dev: true optional: true /@esbuild/android-x64@0.17.19: @@ -4948,22 +4973,12 @@ packages: requiresBuild: true optional: true - /@esbuild/android-x64@0.20.1: - resolution: {integrity: sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: false - optional: true - /@esbuild/android-x64@0.20.2: resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} engines: {node: '>=12'} cpu: [x64] os: [android] requiresBuild: true - dev: true optional: true /@esbuild/darwin-arm64@0.17.19: @@ -4983,22 +4998,12 @@ packages: requiresBuild: true optional: true - /@esbuild/darwin-arm64@0.20.1: - resolution: {integrity: sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - /@esbuild/darwin-arm64@0.20.2: resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] requiresBuild: true - dev: true optional: true /@esbuild/darwin-x64@0.17.19: @@ -5018,22 +5023,12 @@ packages: requiresBuild: true optional: true - /@esbuild/darwin-x64@0.20.1: - resolution: {integrity: sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - /@esbuild/darwin-x64@0.20.2: resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} engines: {node: '>=12'} cpu: [x64] os: [darwin] requiresBuild: true - dev: true optional: true /@esbuild/freebsd-arm64@0.17.19: @@ -5053,22 +5048,12 @@ packages: requiresBuild: true optional: true - /@esbuild/freebsd-arm64@0.20.1: - resolution: {integrity: sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: false - optional: true - /@esbuild/freebsd-arm64@0.20.2: resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] requiresBuild: true - dev: true optional: true /@esbuild/freebsd-x64@0.17.19: @@ -5088,22 +5073,12 @@ packages: requiresBuild: true optional: true - /@esbuild/freebsd-x64@0.20.1: - resolution: {integrity: sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: false - optional: true - /@esbuild/freebsd-x64@0.20.2: resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] requiresBuild: true - dev: true optional: true /@esbuild/linux-arm64@0.17.19: @@ -5123,22 +5098,12 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-arm64@0.20.1: - resolution: {integrity: sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@esbuild/linux-arm64@0.20.2: resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} engines: {node: '>=12'} cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-arm@0.17.19: @@ -5158,22 +5123,12 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-arm@0.20.1: - resolution: {integrity: sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@esbuild/linux-arm@0.20.2: resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} engines: {node: '>=12'} cpu: [arm] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-ia32@0.17.19: @@ -5193,22 +5148,12 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-ia32@0.20.1: - resolution: {integrity: sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@esbuild/linux-ia32@0.20.2: resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} engines: {node: '>=12'} cpu: [ia32] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-loong64@0.17.19: @@ -5228,22 +5173,12 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-loong64@0.20.1: - resolution: {integrity: sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@esbuild/linux-loong64@0.20.2: resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} engines: {node: '>=12'} cpu: [loong64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-mips64el@0.17.19: @@ -5263,22 +5198,12 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-mips64el@0.20.1: - resolution: {integrity: sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@esbuild/linux-mips64el@0.20.2: resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-ppc64@0.17.19: @@ -5298,22 +5223,12 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-ppc64@0.20.1: - resolution: {integrity: sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@esbuild/linux-ppc64@0.20.2: resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-riscv64@0.17.19: @@ -5333,22 +5248,12 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-riscv64@0.20.1: - resolution: {integrity: sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@esbuild/linux-riscv64@0.20.2: resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-s390x@0.17.19: @@ -5368,22 +5273,12 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-s390x@0.20.1: - resolution: {integrity: sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@esbuild/linux-s390x@0.20.2: resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} engines: {node: '>=12'} cpu: [s390x] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-x64@0.17.19: @@ -5403,22 +5298,12 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-x64@0.20.1: - resolution: {integrity: sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@esbuild/linux-x64@0.20.2: resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} engines: {node: '>=12'} cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/netbsd-x64@0.17.19: @@ -5438,22 +5323,12 @@ packages: requiresBuild: true optional: true - /@esbuild/netbsd-x64@0.20.1: - resolution: {integrity: sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: false - optional: true - /@esbuild/netbsd-x64@0.20.2: resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] requiresBuild: true - dev: true optional: true /@esbuild/openbsd-x64@0.17.19: @@ -5473,22 +5348,12 @@ packages: requiresBuild: true optional: true - /@esbuild/openbsd-x64@0.20.1: - resolution: {integrity: sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: false - optional: true - /@esbuild/openbsd-x64@0.20.2: resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] requiresBuild: true - dev: true optional: true /@esbuild/sunos-x64@0.17.19: @@ -5508,22 +5373,12 @@ packages: requiresBuild: true optional: true - /@esbuild/sunos-x64@0.20.1: - resolution: {integrity: sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: false - optional: true - /@esbuild/sunos-x64@0.20.2: resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} engines: {node: '>=12'} cpu: [x64] os: [sunos] requiresBuild: true - dev: true optional: true /@esbuild/win32-arm64@0.17.19: @@ -5543,22 +5398,12 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-arm64@0.20.1: - resolution: {integrity: sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: false - optional: true - /@esbuild/win32-arm64@0.20.2: resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} engines: {node: '>=12'} cpu: [arm64] os: [win32] requiresBuild: true - dev: true optional: true /@esbuild/win32-ia32@0.17.19: @@ -5578,22 +5423,12 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-ia32@0.20.1: - resolution: {integrity: sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: false - optional: true - /@esbuild/win32-ia32@0.20.2: resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} engines: {node: '>=12'} cpu: [ia32] os: [win32] requiresBuild: true - dev: true optional: true /@esbuild/win32-x64@0.17.19: @@ -5613,22 +5448,12 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-x64@0.20.1: - resolution: {integrity: sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - /@esbuild/win32-x64@0.20.2: resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} engines: {node: '>=12'} cpu: [x64] os: [win32] requiresBuild: true - dev: true optional: true /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): @@ -5879,13 +5704,13 @@ packages: resolution: {integrity: sha512-/AHFqy6OeNHS2NNZGFVRgQh+pnW8iAoV3d1fiO9b2PuQ3HzZpC30MrMrHtq1uOGy1/zcK4uPQEyI31jkM0NNAA==} dev: true - /@mdx-js/esbuild@2.3.0(esbuild@0.20.1): + /@mdx-js/esbuild@2.3.0(esbuild@0.20.2): resolution: {integrity: sha512-r/vsqsM0E+U4Wr0DK+0EfmABE/eg+8ITW4DjvYdh3ve/tK2safaqHArNnaqbOk1DjYGrhxtoXoGaM3BY8fGBTA==} peerDependencies: esbuild: '>=0.11.0' dependencies: '@mdx-js/mdx': 2.3.0 - esbuild: 0.20.1 + esbuild: 0.20.2 node-fetch: 3.3.2 vfile: 5.3.7 transitivePeerDependencies: @@ -10046,17 +9871,17 @@ packages: upper-case: 2.0.2 dev: false - /contentlayer@0.3.4(esbuild@0.20.1): + /contentlayer@0.3.4(esbuild@0.20.2): resolution: {integrity: sha512-FYDdTUFaN4yqep0waswrhcXjmMJnPD5iXDTtxcUCGdklfuIrXM2xLx51xl748cHmGA6IsC+27YZFxU6Ym13QIA==} engines: {node: '>=14.18'} hasBin: true requiresBuild: true dependencies: - '@contentlayer/cli': 0.3.4(esbuild@0.20.1) - '@contentlayer/client': 0.3.4(esbuild@0.20.1) - '@contentlayer/core': 0.3.4(esbuild@0.20.1) - '@contentlayer/source-files': 0.3.4(esbuild@0.20.1) - '@contentlayer/source-remote-files': 0.3.4(esbuild@0.20.1) + '@contentlayer/cli': 0.3.4(esbuild@0.20.2) + '@contentlayer/client': 0.3.4(esbuild@0.20.2) + '@contentlayer/core': 0.3.4(esbuild@0.20.2) + '@contentlayer/source-files': 0.3.4(esbuild@0.20.2) + '@contentlayer/source-remote-files': 0.3.4(esbuild@0.20.2) '@contentlayer/utils': 0.3.4 transitivePeerDependencies: - '@effect-ts/otel-node' @@ -10936,37 +10761,6 @@ packages: '@esbuild/win32-ia32': 0.19.12 '@esbuild/win32-x64': 0.19.12 - /esbuild@0.20.1: - resolution: {integrity: sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/aix-ppc64': 0.20.1 - '@esbuild/android-arm': 0.20.1 - '@esbuild/android-arm64': 0.20.1 - '@esbuild/android-x64': 0.20.1 - '@esbuild/darwin-arm64': 0.20.1 - '@esbuild/darwin-x64': 0.20.1 - '@esbuild/freebsd-arm64': 0.20.1 - '@esbuild/freebsd-x64': 0.20.1 - '@esbuild/linux-arm': 0.20.1 - '@esbuild/linux-arm64': 0.20.1 - '@esbuild/linux-ia32': 0.20.1 - '@esbuild/linux-loong64': 0.20.1 - '@esbuild/linux-mips64el': 0.20.1 - '@esbuild/linux-ppc64': 0.20.1 - '@esbuild/linux-riscv64': 0.20.1 - '@esbuild/linux-s390x': 0.20.1 - '@esbuild/linux-x64': 0.20.1 - '@esbuild/netbsd-x64': 0.20.1 - '@esbuild/openbsd-x64': 0.20.1 - '@esbuild/sunos-x64': 0.20.1 - '@esbuild/win32-arm64': 0.20.1 - '@esbuild/win32-ia32': 0.20.1 - '@esbuild/win32-x64': 0.20.1 - dev: false - /esbuild@0.20.2: resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} engines: {node: '>=12'} @@ -10996,7 +10790,6 @@ packages: '@esbuild/win32-arm64': 0.20.2 '@esbuild/win32-ia32': 0.20.2 '@esbuild/win32-x64': 0.20.2 - dev: true /escalade@3.1.2: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} @@ -14211,17 +14004,17 @@ packages: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} dev: true - /mdx-bundler@9.2.1(esbuild@0.20.1): + /mdx-bundler@9.2.1(esbuild@0.20.2): resolution: {integrity: sha512-hWEEip1KU9MCNqeH2rqwzAZ1pdqPPbfkx9OTJjADqGPQz4t9BO85fhI7AP9gVYrpmfArf9/xJZUN0yBErg/G/Q==} engines: {node: '>=14', npm: '>=6'} peerDependencies: esbuild: 0.* dependencies: '@babel/runtime': 7.24.0 - '@esbuild-plugins/node-resolve': 0.1.4(esbuild@0.20.1) + '@esbuild-plugins/node-resolve': 0.1.4(esbuild@0.20.2) '@fal-works/esbuild-plugin-global-externals': 2.1.2 - '@mdx-js/esbuild': 2.3.0(esbuild@0.20.1) - esbuild: 0.20.1 + '@mdx-js/esbuild': 2.3.0(esbuild@0.20.2) + esbuild: 0.20.2 gray-matter: 4.0.3 remark-frontmatter: 4.0.1 remark-mdx-frontmatter: 1.1.1 @@ -14976,7 +14769,7 @@ packages: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: false - /next-contentlayer@0.3.4(contentlayer@0.3.4)(esbuild@0.20.1)(next@14.1.3)(react-dom@18.2.0)(react@18.2.0): + /next-contentlayer@0.3.4(contentlayer@0.3.4)(esbuild@0.20.2)(next@14.1.3)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-UtUCwgAl159KwfhNaOwyiI7Lg6sdioyKMeh+E7jxx0CJ29JuXGxBEYmCI6+72NxFGIFZKx8lvttbbQhbnYWYSw==} peerDependencies: contentlayer: 0.3.4 @@ -14984,9 +14777,9 @@ packages: react: '*' react-dom: '*' dependencies: - '@contentlayer/core': 0.3.4(esbuild@0.20.1) + '@contentlayer/core': 0.3.4(esbuild@0.20.2) '@contentlayer/utils': 0.3.4 - contentlayer: 0.3.4(esbuild@0.20.1) + contentlayer: 0.3.4(esbuild@0.20.2) next: 14.1.3(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) diff --git a/shared/src/controls.ts b/shared/src/controls.ts index 7e20a97300..5ff8d5f9a4 100644 --- a/shared/src/controls.ts +++ b/shared/src/controls.ts @@ -246,3 +246,11 @@ export const tourControls = defineControls({ closeOnInteractOutside: { type: "boolean", defaultValue: true }, preventInteraction: { type: "boolean", defaultValue: true }, }) + +export const floatingPanelControls = defineControls({ + disabled: { type: "boolean", defaultValue: false }, + resizable: { type: "boolean", defaultValue: true }, + draggable: { type: "boolean", defaultValue: true }, + lockAspectRatio: { type: "boolean", defaultValue: false }, + closeOnEscape: { type: "boolean", defaultValue: true }, +}) diff --git a/shared/src/routes.ts b/shared/src/routes.ts index e135679c0a..3c8e722afa 100644 --- a/shared/src/routes.ts +++ b/shared/src/routes.ts @@ -4,6 +4,7 @@ type RouteData = { } export const routesData: RouteData[] = [ + { label: "Floating Panel", path: "/floating-panel" }, { label: "Tour", path: "/tour" }, { label: "Collapsible", path: "/collapsible" }, { label: "Clipboard", path: "/clipboard" }, diff --git a/shared/src/style.css b/shared/src/style.css index 00b5632063..9f23630b4d 100644 --- a/shared/src/style.css +++ b/shared/src/style.css @@ -26,6 +26,10 @@ ul { padding-inline-start: 0; } +[hidden] { + display: none !important; +} + .pre { background: rgba(0, 0, 0, 0.05); padding: 20px; @@ -1980,6 +1984,84 @@ main [data-testid="scrubber"] { height: 100px; } +/* ----------------------------------------------------------------------------- +* Floating Panel +* -----------------------------------------------------------------------------*/ + +[data-scope="floating-panel"][data-part="content"] { + border: 1px solid #ebebeb; + box-shadow: + rgba(0, 0, 0, 0.28) 0px 16px 18px 0px, + rgba(0, 0, 0, 0.12) 0px 4px 16px 0px; + outline: 0 !important; + background-color: white; + display: flex; + flex-direction: column; +} + +[data-scope="floating-panel"][data-part="body"] { + position: relative; + overflow: auto; + flex: 1 1 auto; + padding-block: 16px; + padding-inline: 16px; + background-color: white; +} + +[data-scope="floating-panel"][data-part="header"] { + padding-block: 4px; + padding-inline: 16px; + background-color: #f5f5f5; + border-bottom: 1px solid #ebebeb; + display: flex; + justify-content: space-between; + align-items: center; +} + +[data-scope="floating-panel"][data-part="header"] button { + width: 24px; + height: 24px; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 14px; + padding: 0; + svg { + width: 1em; + height: 1em; + } +} + +[data-scope="floating-panel"][data-part="trigger-group"] { + display: flex; + align-items: center; + gap: 8px; +} + +[data-scope="floating-panel"][data-part="resize-trigger"] { + background-color: rgba(154, 18, 18, 0.396); + + &[data-axis="n"], + &[data-axis="s"] { + height: 6px; + max-width: 90%; + } + + &[data-axis="e"], + &[data-axis="w"] { + width: 6px; + max-height: 90%; + } + + &[data-axis="ne"], + &[data-axis="nw"], + &[data-axis="se"], + &[data-axis="sw"] { + width: 10px; + height: 10px; + } +} + /* ----------------------------------------------------------------------------- * Page Shell * -----------------------------------------------------------------------------*/