@hanzogui/tauri 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/TitleBar.cjs +97 -0
- package/dist/cjs/TitleBar.native.js +109 -0
- package/dist/cjs/TitleBar.native.js.map +1 -0
- package/dist/cjs/index.cjs +41 -0
- package/dist/cjs/index.native.js +44 -0
- package/dist/cjs/index.native.js.map +1 -0
- package/dist/cjs/isTauri.cjs +30 -0
- package/dist/cjs/isTauri.native.js +33 -0
- package/dist/cjs/isTauri.native.js.map +1 -0
- package/dist/cjs/shortcuts.cjs +70 -0
- package/dist/cjs/shortcuts.native.js +73 -0
- package/dist/cjs/shortcuts.native.js.map +1 -0
- package/dist/cjs/system.cjs +65 -0
- package/dist/cjs/system.native.js +68 -0
- package/dist/cjs/system.native.js.map +1 -0
- package/dist/cjs/window.cjs +81 -0
- package/dist/cjs/window.native.js +106 -0
- package/dist/cjs/window.native.js.map +1 -0
- package/dist/esm/TitleBar.mjs +72 -0
- package/dist/esm/TitleBar.mjs.map +1 -0
- package/dist/esm/TitleBar.native.js +81 -0
- package/dist/esm/TitleBar.native.js.map +1 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/index.mjs +7 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/index.native.js +7 -0
- package/dist/esm/index.native.js.map +1 -0
- package/dist/esm/isTauri.mjs +5 -0
- package/dist/esm/isTauri.mjs.map +1 -0
- package/dist/esm/isTauri.native.js +5 -0
- package/dist/esm/isTauri.native.js.map +1 -0
- package/dist/esm/shortcuts.mjs +32 -0
- package/dist/esm/shortcuts.mjs.map +1 -0
- package/dist/esm/shortcuts.native.js +32 -0
- package/dist/esm/shortcuts.native.js.map +1 -0
- package/dist/esm/system.mjs +27 -0
- package/dist/esm/system.mjs.map +1 -0
- package/dist/esm/system.native.js +27 -0
- package/dist/esm/system.native.js.map +1 -0
- package/dist/esm/window.mjs +44 -0
- package/dist/esm/window.mjs.map +1 -0
- package/dist/esm/window.native.js +66 -0
- package/dist/esm/window.native.js.map +1 -0
- package/package.json +53 -0
- package/src/TitleBar.tsx +71 -0
- package/src/index.ts +13 -0
- package/src/isTauri.ts +12 -0
- package/src/shortcuts.ts +37 -0
- package/src/system.ts +27 -0
- package/src/window.ts +51 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { isTauri } from "./isTauri.native.js";
|
|
3
|
+
async function currentWindow() {
|
|
4
|
+
if (!isTauri()) return null;
|
|
5
|
+
var {
|
|
6
|
+
getCurrentWindow
|
|
7
|
+
} = await import("@tauri-apps/api/window");
|
|
8
|
+
return getCurrentWindow();
|
|
9
|
+
}
|
|
10
|
+
var windowControls = {
|
|
11
|
+
minimize: async function () {
|
|
12
|
+
var _this;
|
|
13
|
+
return (_this = await currentWindow()) === null || _this === void 0 ? void 0 : _this.minimize();
|
|
14
|
+
},
|
|
15
|
+
toggleMaximize: async function () {
|
|
16
|
+
var _this;
|
|
17
|
+
return (_this = await currentWindow()) === null || _this === void 0 ? void 0 : _this.toggleMaximize();
|
|
18
|
+
},
|
|
19
|
+
close: async function () {
|
|
20
|
+
var _this;
|
|
21
|
+
return (_this = await currentWindow()) === null || _this === void 0 ? void 0 : _this.close();
|
|
22
|
+
},
|
|
23
|
+
hide: async function () {
|
|
24
|
+
var _this;
|
|
25
|
+
return (_this = await currentWindow()) === null || _this === void 0 ? void 0 : _this.hide();
|
|
26
|
+
},
|
|
27
|
+
startDragging: async function () {
|
|
28
|
+
var _this;
|
|
29
|
+
return (_this = await currentWindow()) === null || _this === void 0 ? void 0 : _this.startDragging();
|
|
30
|
+
},
|
|
31
|
+
isMaximized: async function () {
|
|
32
|
+
var _this;
|
|
33
|
+
var _isMaximized;
|
|
34
|
+
return (_isMaximized = (_this = await currentWindow()) === null || _this === void 0 ? void 0 : _this.isMaximized()) !== null && _isMaximized !== void 0 ? _isMaximized : false;
|
|
35
|
+
},
|
|
36
|
+
setFullscreen: async function (v) {
|
|
37
|
+
var _this;
|
|
38
|
+
return (_this = await currentWindow()) === null || _this === void 0 ? void 0 : _this.setFullscreen(v);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
function useWindowControls() {
|
|
42
|
+
var [isMaximized, setIsMaximized] = useState(false);
|
|
43
|
+
useEffect(function () {
|
|
44
|
+
if (!isTauri()) return;
|
|
45
|
+
var unlisten;
|
|
46
|
+
var active = true;
|
|
47
|
+
(async function () {
|
|
48
|
+
var win = await currentWindow();
|
|
49
|
+
if (!win || !active) return;
|
|
50
|
+
setIsMaximized(await win.isMaximized());
|
|
51
|
+
unlisten = await win.onResized(async function () {
|
|
52
|
+
setIsMaximized(await win.isMaximized());
|
|
53
|
+
});
|
|
54
|
+
})();
|
|
55
|
+
return function () {
|
|
56
|
+
active = false;
|
|
57
|
+
unlisten === null || unlisten === void 0 ? void 0 : unlisten();
|
|
58
|
+
};
|
|
59
|
+
}, []);
|
|
60
|
+
return {
|
|
61
|
+
...windowControls,
|
|
62
|
+
isMaximized
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export { useWindowControls, windowControls };
|
|
66
|
+
//# sourceMappingURL=window.native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["useEffect","useState","isTauri","currentWindow","getCurrentWindow","windowControls","minimize","_this","toggleMaximize","close","hide","startDragging","isMaximized","_isMaximized","setFullscreen","v"],"sources":["../../src/window.ts"],"sourcesContent":[null],"mappings":"AAAA,SAASA,SAAA,EAAWC,QAAA,QAAgB;AACpC,SAASC,OAAA,QAAe;AAOxB,eAAeC,cAAA,EAAgB;EAC7B,IAAI,CAACD,OAAA,CAAQ,GAAG,OAAO;EACvB;IAAME;EAAE,IAAiB,MAAI,MAAM,yBAAO;EAC1C,OAAOA,gBAAA,CAAiB;AAC1B;AAEO,IAAAC,cAAM;EACXC,QAAA,EAAU,eAAAA,CAAA,EAAa;IACvB,IAAAC,KAAA;IACA,OAAO,CAAAA,KAAA,SAAaJ,aAAM,QAAkB,QAAMI,KAAA,uBAAAA,KAAA,CAAAD,QAAA;EAClD;EACAE,cAAA,EAAe,eAAAA,CAAA,EAAa;IAC5B,IAAAD,KAAA;IACA,QAAAA,KAAe,SAAOJ,aAAsB,YAAc,IAAII,KAAA,UAAe,aAAAA,KAAA,CAAAC,cAAA;EAC/E;EAMOC,KAAA,EAAS,eAAAA,CAAA;IACd,IAAMF,KAAC;IAEP,QAAUA,KAAA,GAAM,MAAAJ,aAAA,gBAAAI,KAAA,uBAAAA,KAAA,CAAAE,KAAA;EACd;EACAC,IAAA,EAAI,eAAAA,CAAA;IACJ,IAAIH,KAAA;IACH,OAAC,CAAAA,KAAY,SAAAJ,aAAA,gBAAAI,KAAA,uBAAAA,KAAA,CAAAG,IAAA;EACZ;EACAC,aAAK,iBAAAA,CAAA,EAAgB;IACrB,IAAAJ,KAAA;IACA,QAAAA,KAAW,SAAMJ,aAAc,YAAY,IAAAI,KAAA,uBAAAA,KAAA,CAAAI,aAAA;EACzC;EAAsCC,WACvC,iBAAAA,CAAA;IACH,IAAGL,KAAA;IACH,IAAAM,YAAa;IACX,QAAAA,YAAS,IAAAN,KAAA,SAAAJ,aAAA,gBAAAI,KAAA,uBAAAA,KAAA,CAAAK,WAAA,gBAAAC,YAAA,cAAAA,YAAA;EACT;EAAWC,aACb,iBAAAA,CAAAC,CAAA;IACF,IAAKR,KAAA;IAEL,OAAS,CAAAA,KAAG,SAAAJ,aAAgB,QAAY,QAAAI,KAAA,uBAAAA,KAAA,CAAAO,aAAA,CAAAC,CAAA;EAC1C","ignoreList":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hanzogui/tauri",
|
|
3
|
+
"version": "8.0.0",
|
|
4
|
+
"description": "Tauri desktop adapter for Hanzo GUI — window controls, global shortcuts, fs/shell bridge so hanzogui components run in a Tauri webview.",
|
|
5
|
+
"files": [
|
|
6
|
+
"src",
|
|
7
|
+
"types",
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"type": "module",
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"main": "dist/cjs",
|
|
13
|
+
"module": "dist/esm",
|
|
14
|
+
"types": "./src",
|
|
15
|
+
"exports": {
|
|
16
|
+
"./package.json": "./package.json",
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./src/index.ts",
|
|
19
|
+
"react-native": "./dist/esm/index.native.js",
|
|
20
|
+
"browser": "./dist/esm/index.mjs",
|
|
21
|
+
"module": "./dist/esm/index.mjs",
|
|
22
|
+
"import": "./dist/esm/index.mjs",
|
|
23
|
+
"require": "./dist/cjs/index.cjs",
|
|
24
|
+
"default": "./dist/esm/index.mjs"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "hanzogui-build --skip-types",
|
|
32
|
+
"watch": "hanzogui-build --skip-types --watch",
|
|
33
|
+
"clean": "hanzogui-build clean",
|
|
34
|
+
"clean:build": "hanzogui-build clean:build"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@tauri-apps/api": "^2.6.0",
|
|
38
|
+
"@tauri-apps/plugin-fs": "^2.3.0",
|
|
39
|
+
"@tauri-apps/plugin-global-shortcut": "^2.2.1",
|
|
40
|
+
"@tauri-apps/plugin-shell": "^2.2.1"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@hanzogui/build": "8.0.0",
|
|
44
|
+
"@hanzo/gui": "8.0.0",
|
|
45
|
+
"react": ">=19"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"@hanzo/gui": "*",
|
|
49
|
+
"react": ">=19"
|
|
50
|
+
},
|
|
51
|
+
"license": "BSD-3-Clause",
|
|
52
|
+
"author": "Hanzo AI <dev@hanzo.ai>"
|
|
53
|
+
}
|
package/src/TitleBar.tsx
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Text, XStack, styled } from '@hanzo/gui'
|
|
2
|
+
import { useWindowControls } from './window'
|
|
3
|
+
|
|
4
|
+
// A frameless-window titlebar built entirely from hanzogui primitives + tokens
|
|
5
|
+
// (true-black `$background`, hairline `$borderColor`). It is the proof that a
|
|
6
|
+
// hanzogui component drives the real Tauri OS window through the bridge. The bar
|
|
7
|
+
// is draggable via Tauri's `data-tauri-drag-region`; the buttons call the
|
|
8
|
+
// window controls. Renders harmlessly on the web (controls no-op off-Tauri).
|
|
9
|
+
|
|
10
|
+
const Control = styled(XStack, {
|
|
11
|
+
width: 46,
|
|
12
|
+
height: 32,
|
|
13
|
+
alignItems: 'center',
|
|
14
|
+
justifyContent: 'center',
|
|
15
|
+
cursor: 'pointer',
|
|
16
|
+
hoverStyle: { backgroundColor: '$color4' },
|
|
17
|
+
pressStyle: { backgroundColor: '$color5' },
|
|
18
|
+
animation: 'quick',
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const Glyph = styled(Text, {
|
|
22
|
+
fontFamily: '$mono',
|
|
23
|
+
fontSize: 13,
|
|
24
|
+
lineHeight: 13,
|
|
25
|
+
color: '$color11',
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export type TitleBarProps = {
|
|
29
|
+
title?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function TitleBar({ title = '' }: TitleBarProps) {
|
|
33
|
+
const { minimize, toggleMaximize, close } = useWindowControls()
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<XStack
|
|
37
|
+
// @ts-expect-error — Tauri drag region is a raw DOM attribute (web/webview)
|
|
38
|
+
data-tauri-drag-region
|
|
39
|
+
height={40}
|
|
40
|
+
alignItems="center"
|
|
41
|
+
backgroundColor="$background"
|
|
42
|
+
borderBottomWidth={1}
|
|
43
|
+
borderBottomColor="$borderColor"
|
|
44
|
+
userSelect="none"
|
|
45
|
+
>
|
|
46
|
+
<Text
|
|
47
|
+
flex={1}
|
|
48
|
+
paddingLeft="$3"
|
|
49
|
+
fontFamily="$body"
|
|
50
|
+
fontSize={13}
|
|
51
|
+
color="$color11"
|
|
52
|
+
// @ts-expect-error — raw DOM attribute so clicks on the title still drag
|
|
53
|
+
data-tauri-drag-region
|
|
54
|
+
>
|
|
55
|
+
{title}
|
|
56
|
+
</Text>
|
|
57
|
+
|
|
58
|
+
<XStack>
|
|
59
|
+
<Control onPress={() => minimize()}>
|
|
60
|
+
<Glyph>—</Glyph>
|
|
61
|
+
</Control>
|
|
62
|
+
<Control onPress={() => toggleMaximize()}>
|
|
63
|
+
<Glyph>▢</Glyph>
|
|
64
|
+
</Control>
|
|
65
|
+
<Control onPress={() => close()} hoverStyle={{ backgroundColor: '$red9' }}>
|
|
66
|
+
<Glyph>✕</Glyph>
|
|
67
|
+
</Control>
|
|
68
|
+
</XStack>
|
|
69
|
+
</XStack>
|
|
70
|
+
)
|
|
71
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// @hanzogui/tauri — the Tauri desktop weld for Hanzo GUI.
|
|
2
|
+
//
|
|
3
|
+
// hanzogui already targets web (Next/Vite) and native (Expo/RN). This adapter
|
|
4
|
+
// adds the third host: a Tauri webview with a native bridge. The same hanzogui
|
|
5
|
+
// component tree renders in the webview; this package gives it OS window
|
|
6
|
+
// controls, global shortcuts, and fs/shell access — all guarded by isTauri() so
|
|
7
|
+
// nothing breaks on the web.
|
|
8
|
+
|
|
9
|
+
export { isTauri } from './isTauri'
|
|
10
|
+
export { windowControls, useWindowControls } from './window'
|
|
11
|
+
export { registerShortcut, unregisterShortcut, useGlobalShortcut } from './shortcuts'
|
|
12
|
+
export { readTextFile, writeTextFile, openExternal } from './system'
|
|
13
|
+
export { TitleBar, type TitleBarProps } from './TitleBar'
|
package/src/isTauri.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when running inside a Tauri webview. Tauri v2 injects
|
|
3
|
+
* `window.__TAURI_INTERNALS__` (and `window.isTauri`) into the page. Every
|
|
4
|
+
* bridge call guards on this so the same hanzogui component runs unchanged on
|
|
5
|
+
* the web — off-Tauri the native calls are no-ops.
|
|
6
|
+
*/
|
|
7
|
+
export function isTauri(): boolean {
|
|
8
|
+
return (
|
|
9
|
+
typeof window !== 'undefined' &&
|
|
10
|
+
(!!(window as any).__TAURI_INTERNALS__ || (window as any).isTauri === true)
|
|
11
|
+
)
|
|
12
|
+
}
|
package/src/shortcuts.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
import { isTauri } from './isTauri'
|
|
3
|
+
|
|
4
|
+
// Global (OS-wide) shortcuts via @tauri-apps/plugin-global-shortcut. Off-Tauri
|
|
5
|
+
// these no-op, so a component can register e.g. ⌘K to summon a CommandPalette
|
|
6
|
+
// and the same code degrades to an in-page key handler on the web.
|
|
7
|
+
|
|
8
|
+
export async function registerShortcut(
|
|
9
|
+
accelerator: string,
|
|
10
|
+
handler: () => void
|
|
11
|
+
): Promise<void> {
|
|
12
|
+
if (!isTauri()) return
|
|
13
|
+
const { register } = await import('@tauri-apps/plugin-global-shortcut')
|
|
14
|
+
await register(accelerator, (event) => {
|
|
15
|
+
if (event.state === 'Pressed') handler()
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function unregisterShortcut(accelerator: string): Promise<void> {
|
|
20
|
+
if (!isTauri()) return
|
|
21
|
+
const { unregister } = await import('@tauri-apps/plugin-global-shortcut')
|
|
22
|
+
await unregister(accelerator)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Register `accelerator` for the lifetime of the component. */
|
|
26
|
+
export function useGlobalShortcut(accelerator: string, handler: () => void) {
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!isTauri()) return
|
|
29
|
+
let cancelled = false
|
|
30
|
+
registerShortcut(accelerator, handler).catch(() => {})
|
|
31
|
+
return () => {
|
|
32
|
+
cancelled = true
|
|
33
|
+
void cancelled
|
|
34
|
+
unregisterShortcut(accelerator).catch(() => {})
|
|
35
|
+
}
|
|
36
|
+
}, [accelerator, handler])
|
|
37
|
+
}
|
package/src/system.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { isTauri } from './isTauri'
|
|
2
|
+
|
|
3
|
+
// Thin fs + shell bridge. Each guards on isTauri() and dynamically imports the
|
|
4
|
+
// Tauri plugin, so the web build stays clean. Off-Tauri, reads/opens reject or
|
|
5
|
+
// fall back to the browser (openExternal uses window.open).
|
|
6
|
+
|
|
7
|
+
export async function readTextFile(path: string): Promise<string> {
|
|
8
|
+
if (!isTauri()) throw new Error('readTextFile is only available under Tauri')
|
|
9
|
+
const { readTextFile } = await import('@tauri-apps/plugin-fs')
|
|
10
|
+
return readTextFile(path)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function writeTextFile(path: string, contents: string): Promise<void> {
|
|
14
|
+
if (!isTauri()) throw new Error('writeTextFile is only available under Tauri')
|
|
15
|
+
const { writeTextFile } = await import('@tauri-apps/plugin-fs')
|
|
16
|
+
return writeTextFile(path, contents)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Open a URL in the user's default browser (Tauri) or a new tab (web). */
|
|
20
|
+
export async function openExternal(url: string): Promise<void> {
|
|
21
|
+
if (!isTauri()) {
|
|
22
|
+
if (typeof window !== 'undefined') window.open(url, '_blank', 'noopener')
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
const { open } = await import('@tauri-apps/plugin-shell')
|
|
26
|
+
await open(url)
|
|
27
|
+
}
|
package/src/window.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { isTauri } from './isTauri'
|
|
3
|
+
|
|
4
|
+
// Window controls for a frameless Tauri window (`decorations: false`). Each call
|
|
5
|
+
// dynamically imports the Tauri API so a web bundle never hard-links it, and
|
|
6
|
+
// no-ops off-Tauri. This is the native bridge that lets a hanzogui titlebar
|
|
7
|
+
// drive the real OS window.
|
|
8
|
+
|
|
9
|
+
async function currentWindow() {
|
|
10
|
+
if (!isTauri()) return null
|
|
11
|
+
const { getCurrentWindow } = await import('@tauri-apps/api/window')
|
|
12
|
+
return getCurrentWindow()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const windowControls = {
|
|
16
|
+
minimize: async () => (await currentWindow())?.minimize(),
|
|
17
|
+
toggleMaximize: async () => (await currentWindow())?.toggleMaximize(),
|
|
18
|
+
close: async () => (await currentWindow())?.close(),
|
|
19
|
+
hide: async () => (await currentWindow())?.hide(),
|
|
20
|
+
startDragging: async () => (await currentWindow())?.startDragging(),
|
|
21
|
+
isMaximized: async () => (await currentWindow())?.isMaximized() ?? false,
|
|
22
|
+
setFullscreen: async (v: boolean) => (await currentWindow())?.setFullscreen(v),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* React hook exposing the window controls plus live maximized state. Bind these
|
|
27
|
+
* to a hanzogui titlebar — see {@link TitleBar}.
|
|
28
|
+
*/
|
|
29
|
+
export function useWindowControls() {
|
|
30
|
+
const [isMaximized, setIsMaximized] = useState(false)
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!isTauri()) return
|
|
34
|
+
let unlisten: (() => void) | undefined
|
|
35
|
+
let active = true
|
|
36
|
+
;(async () => {
|
|
37
|
+
const win = await currentWindow()
|
|
38
|
+
if (!win || !active) return
|
|
39
|
+
setIsMaximized(await win.isMaximized())
|
|
40
|
+
unlisten = await win.onResized(async () => {
|
|
41
|
+
setIsMaximized(await win.isMaximized())
|
|
42
|
+
})
|
|
43
|
+
})()
|
|
44
|
+
return () => {
|
|
45
|
+
active = false
|
|
46
|
+
unlisten?.()
|
|
47
|
+
}
|
|
48
|
+
}, [])
|
|
49
|
+
|
|
50
|
+
return { ...windowControls, isMaximized }
|
|
51
|
+
}
|