@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.
Files changed (51) hide show
  1. package/dist/cjs/TitleBar.cjs +97 -0
  2. package/dist/cjs/TitleBar.native.js +109 -0
  3. package/dist/cjs/TitleBar.native.js.map +1 -0
  4. package/dist/cjs/index.cjs +41 -0
  5. package/dist/cjs/index.native.js +44 -0
  6. package/dist/cjs/index.native.js.map +1 -0
  7. package/dist/cjs/isTauri.cjs +30 -0
  8. package/dist/cjs/isTauri.native.js +33 -0
  9. package/dist/cjs/isTauri.native.js.map +1 -0
  10. package/dist/cjs/shortcuts.cjs +70 -0
  11. package/dist/cjs/shortcuts.native.js +73 -0
  12. package/dist/cjs/shortcuts.native.js.map +1 -0
  13. package/dist/cjs/system.cjs +65 -0
  14. package/dist/cjs/system.native.js +68 -0
  15. package/dist/cjs/system.native.js.map +1 -0
  16. package/dist/cjs/window.cjs +81 -0
  17. package/dist/cjs/window.native.js +106 -0
  18. package/dist/cjs/window.native.js.map +1 -0
  19. package/dist/esm/TitleBar.mjs +72 -0
  20. package/dist/esm/TitleBar.mjs.map +1 -0
  21. package/dist/esm/TitleBar.native.js +81 -0
  22. package/dist/esm/TitleBar.native.js.map +1 -0
  23. package/dist/esm/index.js +7 -0
  24. package/dist/esm/index.js.map +1 -0
  25. package/dist/esm/index.mjs +7 -0
  26. package/dist/esm/index.mjs.map +1 -0
  27. package/dist/esm/index.native.js +7 -0
  28. package/dist/esm/index.native.js.map +1 -0
  29. package/dist/esm/isTauri.mjs +5 -0
  30. package/dist/esm/isTauri.mjs.map +1 -0
  31. package/dist/esm/isTauri.native.js +5 -0
  32. package/dist/esm/isTauri.native.js.map +1 -0
  33. package/dist/esm/shortcuts.mjs +32 -0
  34. package/dist/esm/shortcuts.mjs.map +1 -0
  35. package/dist/esm/shortcuts.native.js +32 -0
  36. package/dist/esm/shortcuts.native.js.map +1 -0
  37. package/dist/esm/system.mjs +27 -0
  38. package/dist/esm/system.mjs.map +1 -0
  39. package/dist/esm/system.native.js +27 -0
  40. package/dist/esm/system.native.js.map +1 -0
  41. package/dist/esm/window.mjs +44 -0
  42. package/dist/esm/window.mjs.map +1 -0
  43. package/dist/esm/window.native.js +66 -0
  44. package/dist/esm/window.native.js.map +1 -0
  45. package/package.json +53 -0
  46. package/src/TitleBar.tsx +71 -0
  47. package/src/index.ts +13 -0
  48. package/src/isTauri.ts +12 -0
  49. package/src/shortcuts.ts +37 -0
  50. package/src/system.ts +27 -0
  51. 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
+ }
@@ -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
+ }
@@ -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
+ }