@dxos/plugin-simple-layout 0.8.4-main.9735255 → 0.8.4-main.bcb3aa67d6
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/lib/browser/index.mjs +44 -64
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +44 -63
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/SimpleLayoutPlugin.d.ts +1 -1
- package/dist/types/src/SimpleLayoutPlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/app-graph-builder.d.ts +6 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +21 -6
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
- package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
- package/dist/types/src/capabilities/{react-root/react-root.d.ts → react-root.d.ts} +1 -1
- package/dist/types/src/capabilities/react-root.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts +5 -0
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
- package/dist/types/src/capabilities/{spotlight-dismiss/spotlight-dismiss.d.ts → spotlight-dismiss.d.ts} +1 -1
- package/dist/types/src/capabilities/spotlight-dismiss.d.ts.map +1 -0
- package/dist/types/src/capabilities/{state/state.d.ts → state.d.ts} +2 -2
- package/dist/types/src/capabilities/state.d.ts.map +1 -0
- package/dist/types/src/capabilities/url-handler.d.ts +12 -0
- package/dist/types/src/capabilities/url-handler.d.ts.map +1 -0
- package/dist/types/src/components/ContentError.stories.d.ts +26 -21
- package/dist/types/src/components/ContentError.stories.d.ts.map +1 -1
- package/dist/types/src/components/DebugOverlay/DebugOverlay.d.ts +19 -0
- package/dist/types/src/components/DebugOverlay/DebugOverlay.d.ts.map +1 -0
- package/dist/types/src/components/DebugOverlay/index.d.ts +2 -0
- package/dist/types/src/components/DebugOverlay/index.d.ts.map +1 -0
- package/dist/types/src/components/Home/Home.d.ts.map +1 -1
- package/dist/types/src/components/Loading/Loading.d.ts +3 -0
- package/dist/types/src/components/Loading/Loading.d.ts.map +1 -0
- package/dist/types/src/components/{ContentLoading.stories.d.ts → Loading/Loading.stories.d.ts} +1 -1
- package/dist/types/src/components/Loading/Loading.stories.d.ts.map +1 -0
- package/dist/types/src/components/Loading/index.d.ts +2 -0
- package/dist/types/src/components/Loading/index.d.ts.map +1 -0
- package/dist/types/src/components/MobileLayout/MobileLayout.d.ts +35 -0
- package/dist/types/src/components/MobileLayout/MobileLayout.d.ts.map +1 -0
- package/dist/types/src/components/MobileLayout/MobileLayout.stories.d.ts +7 -0
- package/dist/types/src/components/MobileLayout/MobileLayout.stories.d.ts.map +1 -0
- package/dist/types/src/components/MobileLayout/index.d.ts +2 -0
- package/dist/types/src/components/MobileLayout/index.d.ts.map +1 -0
- package/dist/types/src/components/NavBranch/NavBranch.d.ts +11 -0
- package/dist/types/src/components/NavBranch/NavBranch.d.ts.map +1 -0
- package/dist/types/src/components/NavBranch/index.d.ts +2 -0
- package/dist/types/src/components/NavBranch/index.d.ts.map +1 -0
- package/dist/types/src/components/Popover/Popover.d.ts.map +1 -1
- package/dist/types/src/components/SimpleLayout/AppBar.d.ts +28 -0
- package/dist/types/src/components/SimpleLayout/AppBar.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/AppBar.stories.d.ts +54 -0
- package/dist/types/src/components/SimpleLayout/AppBar.stories.d.ts.map +1 -0
- package/dist/types/src/components/SimpleLayout/Drawer.d.ts +1 -1
- package/dist/types/src/components/SimpleLayout/Drawer.d.ts.map +1 -1
- package/dist/types/src/components/SimpleLayout/Main.d.ts.map +1 -1
- package/dist/types/src/components/SimpleLayout/NavBar.d.ts +17 -8
- package/dist/types/src/components/SimpleLayout/NavBar.d.ts.map +1 -1
- package/dist/types/src/components/SimpleLayout/NavBar.stories.d.ts +35 -25
- package/dist/types/src/components/SimpleLayout/NavBar.stories.d.ts.map +1 -1
- package/dist/types/src/components/SimpleLayout/SimpleLayout.d.ts.map +1 -1
- package/dist/types/src/components/SimpleLayout/SimpleLayout.stories.d.ts +26 -25
- package/dist/types/src/components/SimpleLayout/SimpleLayout.stories.d.ts.map +1 -1
- package/dist/types/src/components/SimpleLayout/index.d.ts +3 -0
- package/dist/types/src/components/SimpleLayout/index.d.ts.map +1 -1
- package/dist/types/src/components/hooks.d.ts +4 -2
- package/dist/types/src/components/hooks.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +4 -2
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/hooks/actions.d.ts +19 -0
- package/dist/types/src/hooks/actions.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +4 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAppBarProps.d.ts +7 -0
- package/dist/types/src/hooks/useAppBarProps.d.ts.map +1 -0
- package/dist/types/src/hooks/useCompanions.d.ts +5 -1
- package/dist/types/src/hooks/useCompanions.d.ts.map +1 -1
- package/dist/types/src/hooks/useDrawerActions.d.ts +13 -0
- package/dist/types/src/hooks/useDrawerActions.d.ts.map +1 -0
- package/dist/types/src/hooks/useNavbarActions.d.ts +14 -0
- package/dist/types/src/hooks/useNavbarActions.d.ts.map +1 -0
- package/dist/types/src/hooks/useSimpleLayoutState.d.ts +3 -3
- package/dist/types/src/hooks/useSimpleLayoutState.d.ts.map +1 -1
- package/dist/types/src/operations/close.d.ts +5 -0
- package/dist/types/src/operations/close.d.ts.map +1 -0
- package/dist/types/src/operations/index.d.ts +3 -0
- package/dist/types/src/operations/index.d.ts.map +1 -0
- package/dist/types/src/operations/open.d.ts +5 -0
- package/dist/types/src/operations/open.d.ts.map +1 -0
- package/dist/types/src/operations/revert-workspace.d.ts +5 -0
- package/dist/types/src/operations/revert-workspace.d.ts.map +1 -0
- package/dist/types/src/operations/set-layout-mode.d.ts +5 -0
- package/dist/types/src/operations/set-layout-mode.d.ts.map +1 -0
- package/dist/types/src/operations/set.d.ts +5 -0
- package/dist/types/src/operations/set.d.ts.map +1 -0
- package/dist/types/src/operations/state-access.d.ts +8 -0
- package/dist/types/src/operations/state-access.d.ts.map +1 -0
- package/dist/types/src/operations/switch-workspace.d.ts +5 -0
- package/dist/types/src/operations/switch-workspace.d.ts.map +1 -0
- package/dist/types/src/operations/update-complementary.d.ts +5 -0
- package/dist/types/src/operations/update-complementary.d.ts.map +1 -0
- package/dist/types/src/operations/update-dialog.d.ts +5 -0
- package/dist/types/src/operations/update-dialog.d.ts.map +1 -0
- package/dist/types/src/operations/update-popover.d.ts +5 -0
- package/dist/types/src/operations/update-popover.d.ts.map +1 -0
- package/dist/types/src/operations/update-sidebar.d.ts +5 -0
- package/dist/types/src/operations/update-sidebar.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +26 -19
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/capabilities.d.ts +7 -6
- package/dist/types/src/types/capabilities.d.ts.map +1 -1
- package/dist/types/src/types/events.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +46 -30
- package/src/SimpleLayoutPlugin.ts +24 -13
- package/src/capabilities/app-graph-builder.ts +21 -0
- package/src/capabilities/index.ts +13 -6
- package/src/capabilities/operation-handler.ts +14 -0
- package/src/capabilities/{react-root/react-root.tsx → react-root.tsx} +4 -4
- package/src/capabilities/react-surface.tsx +51 -0
- package/src/capabilities/{spotlight-dismiss/spotlight-dismiss.ts → spotlight-dismiss.ts} +2 -2
- package/src/capabilities/{state/state.tsx → state.tsx} +6 -5
- package/src/capabilities/url-handler.ts +161 -0
- package/src/components/ContentError.stories.tsx +8 -7
- package/src/components/DebugOverlay/DebugOverlay.tsx +96 -0
- package/src/components/DebugOverlay/index.ts +5 -0
- package/src/components/Dialog/Dialog.tsx +6 -6
- package/src/components/Home/Home.tsx +50 -43
- package/src/components/{ContentLoading.stories.tsx → Loading/Loading.stories.tsx} +5 -5
- package/src/components/{ContentLoading.tsx → Loading/Loading.tsx} +2 -2
- package/src/components/Loading/index.ts +5 -0
- package/src/components/MobileLayout/MobileLayout.stories.tsx +133 -0
- package/src/components/MobileLayout/MobileLayout.tsx +374 -0
- package/src/components/MobileLayout/index.ts +5 -0
- package/src/components/NavBranch/NavBranch.tsx +127 -0
- package/src/components/{Workspace → NavBranch}/index.ts +1 -1
- package/src/components/Popover/Popover.tsx +9 -9
- package/src/components/SimpleLayout/AppBar.stories.tsx +144 -0
- package/src/components/SimpleLayout/AppBar.tsx +93 -0
- package/src/components/SimpleLayout/Drawer.tsx +27 -82
- package/src/components/SimpleLayout/Main.tsx +40 -34
- package/src/components/SimpleLayout/NavBar.stories.tsx +131 -23
- package/src/components/SimpleLayout/NavBar.tsx +18 -51
- package/src/components/SimpleLayout/SimpleLayout.stories.tsx +45 -57
- package/src/components/SimpleLayout/SimpleLayout.tsx +40 -22
- package/src/components/SimpleLayout/index.ts +3 -0
- package/src/components/hooks.ts +9 -9
- package/src/components/index.ts +4 -2
- package/src/hooks/actions.ts +84 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useAppBarProps.ts +115 -0
- package/src/hooks/useCompanions.ts +8 -5
- package/src/hooks/useDrawerActions.ts +100 -0
- package/src/hooks/useNavbarActions.ts +87 -0
- package/src/hooks/useSimpleLayoutState.ts +5 -5
- package/src/meta.ts +1 -1
- package/src/operations/close.ts +34 -0
- package/src/operations/index.ts +16 -0
- package/src/operations/open.ts +63 -0
- package/src/operations/revert-workspace.ts +22 -0
- package/src/operations/set-layout-mode.ts +12 -0
- package/src/operations/set.ts +23 -0
- package/src/operations/state-access.ts +19 -0
- package/src/operations/switch-workspace.ts +26 -0
- package/src/operations/update-complementary.ts +35 -0
- package/src/operations/update-dialog.ts +28 -0
- package/src/operations/update-popover.ts +35 -0
- package/src/operations/update-sidebar.ts +12 -0
- package/src/translations.ts +21 -19
- package/src/types/capabilities.ts +12 -8
- package/src/types/events.ts +3 -2
- package/dist/lib/browser/chunk-LR3EE3VB.mjs +0 -789
- package/dist/lib/browser/chunk-LR3EE3VB.mjs.map +0 -7
- package/dist/lib/browser/chunk-P77G4YTR.mjs +0 -29
- package/dist/lib/browser/chunk-P77G4YTR.mjs.map +0 -7
- package/dist/lib/browser/operation-resolver-775UYAC2.mjs +0 -203
- package/dist/lib/browser/operation-resolver-775UYAC2.mjs.map +0 -7
- package/dist/lib/browser/react-root-KM55OMGJ.mjs +0 -21
- package/dist/lib/browser/react-root-KM55OMGJ.mjs.map +0 -7
- package/dist/lib/browser/react-surface-BABGAWGY.mjs +0 -39
- package/dist/lib/browser/react-surface-BABGAWGY.mjs.map +0 -7
- package/dist/lib/browser/spotlight-dismiss-VSNOPETH.mjs +0 -66
- package/dist/lib/browser/spotlight-dismiss-VSNOPETH.mjs.map +0 -7
- package/dist/lib/browser/state-OUFTC2KV.mjs +0 -47
- package/dist/lib/browser/state-OUFTC2KV.mjs.map +0 -7
- package/dist/lib/browser/url-handler-DOUFQIAC.mjs +0 -54
- package/dist/lib/browser/url-handler-DOUFQIAC.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-F5TEKVJG.mjs +0 -31
- package/dist/lib/node-esm/chunk-F5TEKVJG.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HB2B3LLG.mjs +0 -790
- package/dist/lib/node-esm/chunk-HB2B3LLG.mjs.map +0 -7
- package/dist/lib/node-esm/operation-resolver-LDNYS3DI.mjs +0 -204
- package/dist/lib/node-esm/operation-resolver-LDNYS3DI.mjs.map +0 -7
- package/dist/lib/node-esm/react-root-36UYFEEB.mjs +0 -22
- package/dist/lib/node-esm/react-root-36UYFEEB.mjs.map +0 -7
- package/dist/lib/node-esm/react-surface-CGHFVWU3.mjs +0 -40
- package/dist/lib/node-esm/react-surface-CGHFVWU3.mjs.map +0 -7
- package/dist/lib/node-esm/spotlight-dismiss-L5PCWIJG.mjs +0 -68
- package/dist/lib/node-esm/spotlight-dismiss-L5PCWIJG.mjs.map +0 -7
- package/dist/lib/node-esm/state-Q2ZA26W5.mjs +0 -48
- package/dist/lib/node-esm/state-Q2ZA26W5.mjs.map +0 -7
- package/dist/lib/node-esm/url-handler-DVAZZEUO.mjs +0 -55
- package/dist/lib/node-esm/url-handler-DVAZZEUO.mjs.map +0 -7
- package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
- package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
- package/dist/types/src/capabilities/react-root/index.d.ts +0 -6
- package/dist/types/src/capabilities/react-root/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/react-root/react-root.d.ts.map +0 -1
- package/dist/types/src/capabilities/react-surface/index.d.ts +0 -3
- package/dist/types/src/capabilities/react-surface/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts +0 -5
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +0 -1
- package/dist/types/src/capabilities/spotlight-dismiss/index.d.ts +0 -3
- package/dist/types/src/capabilities/spotlight-dismiss/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/spotlight-dismiss/spotlight-dismiss.d.ts.map +0 -1
- package/dist/types/src/capabilities/state/index.d.ts +0 -13
- package/dist/types/src/capabilities/state/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/state/state.d.ts.map +0 -1
- package/dist/types/src/capabilities/url-handler/index.d.ts +0 -3
- package/dist/types/src/capabilities/url-handler/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/url-handler/url-handler.d.ts +0 -10
- package/dist/types/src/capabilities/url-handler/url-handler.d.ts.map +0 -1
- package/dist/types/src/components/ContentError.d.ts +0 -5
- package/dist/types/src/components/ContentError.d.ts.map +0 -1
- package/dist/types/src/components/ContentLoading.d.ts +0 -3
- package/dist/types/src/components/ContentLoading.d.ts.map +0 -1
- package/dist/types/src/components/ContentLoading.stories.d.ts.map +0 -1
- package/dist/types/src/components/SimpleLayout/Banner.d.ts +0 -8
- package/dist/types/src/components/SimpleLayout/Banner.d.ts.map +0 -1
- package/dist/types/src/components/Workspace/Workspace.d.ts +0 -9
- package/dist/types/src/components/Workspace/Workspace.d.ts.map +0 -1
- package/dist/types/src/components/Workspace/index.d.ts +0 -2
- package/dist/types/src/components/Workspace/index.d.ts.map +0 -1
- package/src/capabilities/operation-resolver/index.ts +0 -10
- package/src/capabilities/operation-resolver/operation-resolver.ts +0 -215
- package/src/capabilities/react-root/index.ts +0 -7
- package/src/capabilities/react-surface/index.ts +0 -7
- package/src/capabilities/react-surface/react-surface.tsx +0 -40
- package/src/capabilities/spotlight-dismiss/index.ts +0 -7
- package/src/capabilities/state/index.ts +0 -9
- package/src/capabilities/url-handler/index.ts +0 -7
- package/src/capabilities/url-handler/url-handler.ts +0 -80
- package/src/components/ContentError.tsx +0 -23
- package/src/components/SimpleLayout/Banner.tsx +0 -113
- package/src/components/Workspace/Workspace.tsx +0 -115
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { createContext } from '@radix-ui/react-context';
|
|
6
|
+
import React, { type PropsWithChildren, forwardRef, useEffect, useLayoutEffect, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { addEventListener, combine } from '@dxos/async';
|
|
9
|
+
import { log } from '@dxos/log';
|
|
10
|
+
import { type ThemedClassName } from '@dxos/react-ui';
|
|
11
|
+
import { mx } from '@dxos/ui-theme';
|
|
12
|
+
|
|
13
|
+
import { useDebugLog } from '../DebugOverlay';
|
|
14
|
+
|
|
15
|
+
const MOBILE_LAYOUT_NAME = 'MobileLayout';
|
|
16
|
+
const MOBILE_LAYOUT_ROOT_NAME = 'MobileLayout.Root';
|
|
17
|
+
const MOBILE_LAYOUT_PANEL_NAME = 'MobileLayout.Panel';
|
|
18
|
+
|
|
19
|
+
//
|
|
20
|
+
// Context
|
|
21
|
+
//
|
|
22
|
+
|
|
23
|
+
type MobileLayoutContextValue = {
|
|
24
|
+
keyboardOpen: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const [MobileLayoutProvider, useMobileLayout] = createContext<MobileLayoutContextValue>(MOBILE_LAYOUT_NAME);
|
|
28
|
+
|
|
29
|
+
//
|
|
30
|
+
// Root
|
|
31
|
+
//
|
|
32
|
+
|
|
33
|
+
type MobileLayoutRootProps = ThemedClassName<
|
|
34
|
+
PropsWithChildren<{
|
|
35
|
+
transition?: number;
|
|
36
|
+
onKeyboardOpenChange?: (nextState: boolean) => void;
|
|
37
|
+
}>
|
|
38
|
+
>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Mobile layout root container that handles iOS keyboard detection.
|
|
42
|
+
*/
|
|
43
|
+
// TODO(burdon): Should this be ios-only?
|
|
44
|
+
const MobileLayoutRoot = forwardRef<HTMLDivElement, MobileLayoutRootProps>(
|
|
45
|
+
({ classNames, children, transition = 500, onKeyboardOpenChange, ...props }, forwardedRef) => {
|
|
46
|
+
const { open: keyboardOpen } = useIOSKeyboard();
|
|
47
|
+
useLockBodyScroll(keyboardOpen);
|
|
48
|
+
useAutoScroll();
|
|
49
|
+
|
|
50
|
+
// Fire synchronously after DOM mutation (before paint) so SimpleLayout's Splitter mode
|
|
51
|
+
// change is batched into the same paint as the keyboard open state change, preventing
|
|
52
|
+
// intermediate render frames from showing an un-adjusted layout.
|
|
53
|
+
useLayoutEffect(() => onKeyboardOpenChange?.(keyboardOpen), [keyboardOpen, onKeyboardOpenChange]);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<MobileLayoutProvider keyboardOpen={keyboardOpen}>
|
|
57
|
+
<div
|
|
58
|
+
{...props}
|
|
59
|
+
role='none'
|
|
60
|
+
style={{
|
|
61
|
+
height: 'calc(100vh - var(--kb-height, 0px))',
|
|
62
|
+
transition: `height ${keyboardOpen ? 0 : transition}ms ease-out`,
|
|
63
|
+
// transition: `height ${animationDuration}ms ease-out`,
|
|
64
|
+
}}
|
|
65
|
+
className={mx('fixed top-0 left-0 right-0 grid overflow-hidden', classNames)}
|
|
66
|
+
ref={forwardedRef}
|
|
67
|
+
>
|
|
68
|
+
{children}
|
|
69
|
+
</div>
|
|
70
|
+
</MobileLayoutProvider>
|
|
71
|
+
);
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
MobileLayoutRoot.displayName = MOBILE_LAYOUT_ROOT_NAME;
|
|
76
|
+
|
|
77
|
+
//
|
|
78
|
+
// Panel
|
|
79
|
+
//
|
|
80
|
+
|
|
81
|
+
type MobileLayoutPanelProps = ThemedClassName<
|
|
82
|
+
PropsWithChildren<{
|
|
83
|
+
safe?: {
|
|
84
|
+
top: boolean;
|
|
85
|
+
bottom: boolean;
|
|
86
|
+
};
|
|
87
|
+
}>
|
|
88
|
+
>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Mobile layout panel that applies safe area insets.
|
|
92
|
+
*/
|
|
93
|
+
const MobileLayoutPanel = forwardRef<HTMLDivElement, MobileLayoutPanelProps>(
|
|
94
|
+
({ classNames, children, safe, ...props }, forwardedRef) => {
|
|
95
|
+
return (
|
|
96
|
+
<div
|
|
97
|
+
{...props}
|
|
98
|
+
role='none'
|
|
99
|
+
style={{
|
|
100
|
+
paddingTop: safe?.top ? 'env(safe-area-inset-top)' : undefined,
|
|
101
|
+
paddingBottom: safe?.bottom ? `calc((1 - var(--kb-open, 0)) * env(safe-area-inset-bottom))` : undefined,
|
|
102
|
+
}}
|
|
103
|
+
className={mx(classNames)}
|
|
104
|
+
ref={forwardedRef}
|
|
105
|
+
>
|
|
106
|
+
{children}
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
MobileLayoutPanel.displayName = MOBILE_LAYOUT_PANEL_NAME;
|
|
113
|
+
|
|
114
|
+
//
|
|
115
|
+
// Exports
|
|
116
|
+
//
|
|
117
|
+
|
|
118
|
+
export const MobileLayout = {
|
|
119
|
+
Root: MobileLayoutRoot,
|
|
120
|
+
Panel: MobileLayoutPanel,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export { useMobileLayout };
|
|
124
|
+
|
|
125
|
+
export type { MobileLayoutRootProps, MobileLayoutPanelProps };
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Prevents iOS (WKWebView) from shifting the layout when the keyboard appears.
|
|
129
|
+
*
|
|
130
|
+
* Scroll events and window.scrollY stay at 0 in this WKWebView setup — the shift is
|
|
131
|
+
* caused by the browser's scroll-into-view for the focused input. We keep a window
|
|
132
|
+
* scroll reset as belt-and-suspenders, and also monitor container scroll events.
|
|
133
|
+
*/
|
|
134
|
+
const useAutoScroll = () => {
|
|
135
|
+
// TODO(burdon): Remove debug logging.
|
|
136
|
+
const { dbg } = useDebugLog('useAutoScroll');
|
|
137
|
+
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
const resetScroll = () => {
|
|
140
|
+
if (window.scrollX !== 0 || window.scrollY !== 0) {
|
|
141
|
+
window.scrollTo(0, 0);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const detectContainerScroll = (event: Event) => {
|
|
146
|
+
const el = event.target as HTMLElement;
|
|
147
|
+
if (el === document.documentElement || el === document.body) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
dbg(`scroll: ${el.tagName}.${Array.from(el.classList).slice(0, 2).join('.')} top=${el.scrollTop.toFixed(0)}`);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return combine(
|
|
155
|
+
addEventListener(window, 'scroll', resetScroll),
|
|
156
|
+
window.visualViewport ? addEventListener(window.visualViewport, 'scroll' as any, resetScroll) : () => {},
|
|
157
|
+
|
|
158
|
+
// TODO(burdon): Remove debug logging.
|
|
159
|
+
addEventListener(document, 'scroll', detectContainerScroll as EventListener, { capture: true } as any),
|
|
160
|
+
|
|
161
|
+
// Prevent focus-triggered scroll-into-view on inputs.
|
|
162
|
+
(() => {
|
|
163
|
+
let focusingWithPreventScroll = false;
|
|
164
|
+
return addEventListener(
|
|
165
|
+
document,
|
|
166
|
+
'focus',
|
|
167
|
+
(event: FocusEvent) => {
|
|
168
|
+
if (focusingWithPreventScroll) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const target = event.target as HTMLElement;
|
|
173
|
+
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
|
|
174
|
+
focusingWithPreventScroll = true;
|
|
175
|
+
target.focus({ preventScroll: true });
|
|
176
|
+
focusingWithPreventScroll = false;
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
{ capture: true },
|
|
180
|
+
);
|
|
181
|
+
})(),
|
|
182
|
+
);
|
|
183
|
+
}, [dbg]);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Prevent iOS Safari viewport scroll when enabled.
|
|
188
|
+
* Setting overflow:hidden doesn't work on iOS, so we must preventDefault on touchmove events.
|
|
189
|
+
* Only allows scrolling if the target is within a scrollable container.
|
|
190
|
+
*/
|
|
191
|
+
const useLockBodyScroll = (enabled: boolean) => {
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
if (!enabled) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const isScrollable = (el: HTMLElement | null, axis: 'x' | 'y'): boolean => {
|
|
198
|
+
while (el && el !== document.body) {
|
|
199
|
+
const style = getComputedStyle(el);
|
|
200
|
+
if (axis === 'y') {
|
|
201
|
+
const overflow = style.overflowY;
|
|
202
|
+
if ((overflow === 'auto' || overflow === 'scroll') && el.scrollHeight > el.clientHeight) {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
const overflow = style.overflowX;
|
|
207
|
+
if ((overflow === 'auto' || overflow === 'scroll') && el.scrollWidth > el.clientWidth) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
el = el.parentElement;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return false;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
let touchStartX = 0;
|
|
219
|
+
let touchStartY = 0;
|
|
220
|
+
|
|
221
|
+
return combine(
|
|
222
|
+
// Record initial touch position.
|
|
223
|
+
addEventListener(
|
|
224
|
+
document,
|
|
225
|
+
'touchstart',
|
|
226
|
+
(event: TouchEvent) => {
|
|
227
|
+
const touch = event.touches[0];
|
|
228
|
+
touchStartX = touch.clientX;
|
|
229
|
+
touchStartY = touch.clientY;
|
|
230
|
+
},
|
|
231
|
+
{ passive: true },
|
|
232
|
+
),
|
|
233
|
+
|
|
234
|
+
// Prevent scrolling the viewport.
|
|
235
|
+
addEventListener(
|
|
236
|
+
document,
|
|
237
|
+
'touchmove',
|
|
238
|
+
(event: TouchEvent) => {
|
|
239
|
+
const touch = event.touches[0];
|
|
240
|
+
const dx = Math.abs(touch.clientX - touchStartX);
|
|
241
|
+
const dy = Math.abs(touch.clientY - touchStartY);
|
|
242
|
+
if (!isScrollable(event.target as HTMLElement, dx > dy ? 'x' : 'y')) {
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
{ passive: false },
|
|
247
|
+
),
|
|
248
|
+
);
|
|
249
|
+
}, [enabled]);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
//
|
|
253
|
+
// Hooks
|
|
254
|
+
//
|
|
255
|
+
|
|
256
|
+
type IOSKeyboard = {
|
|
257
|
+
open: boolean;
|
|
258
|
+
height: number;
|
|
259
|
+
/** Native keyboard animation duration in ms, from the iOS keyboard event. */
|
|
260
|
+
duration: number | undefined;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Mobile container that handles iOS keyboard layout adjustments.
|
|
265
|
+
*
|
|
266
|
+
* Uses two strategies for keyboard detection:
|
|
267
|
+
* 1. Tauri iOS: Native keyboard plugin for reliable height/animation events.
|
|
268
|
+
* 2. Web/PWA: visualViewport API as fallback.
|
|
269
|
+
*
|
|
270
|
+
* iPhone (portrait, points)
|
|
271
|
+
* - Without predictive bar: ~291 pt
|
|
272
|
+
* - With predictive bar: ~335 pt
|
|
273
|
+
* - With accessory view: ~380–420 pt
|
|
274
|
+
*
|
|
275
|
+
* Example:
|
|
276
|
+
* - Viewport: 874 (entire screen)
|
|
277
|
+
* - SafeArea: 96 (62+34)
|
|
278
|
+
* - Main: 778
|
|
279
|
+
* - Keyboard: 318; 413 (incl. Input Accessory View)
|
|
280
|
+
*
|
|
281
|
+
* CSS Variables set on document.documentElement:
|
|
282
|
+
* --vvh: Visual viewport height (use as container height).
|
|
283
|
+
* --kb-height: Keyboard height in pixels.
|
|
284
|
+
* --kb-open: 1 when keyboard is open, 0 when closed.
|
|
285
|
+
*
|
|
286
|
+
* NOTE: By default when an input is selected on iOS the Input Accessory View is shown above the keyboard.
|
|
287
|
+
* This can be disabled by setting the `inputAccessoryView` property to `false`.
|
|
288
|
+
*
|
|
289
|
+
* On iOS (Tauri), listens for 'keyboard' CustomEvents dispatched by the native KeyboardObserver.swift.
|
|
290
|
+
* Falls back to VisualViewport API on other platforms.
|
|
291
|
+
*/
|
|
292
|
+
const useIOSKeyboard = (): IOSKeyboard => {
|
|
293
|
+
const { dbg } = useDebugLog('useIOSKeyboard');
|
|
294
|
+
|
|
295
|
+
const [open, setOpen] = useState(false);
|
|
296
|
+
const [height, setHeight] = useState(0);
|
|
297
|
+
const [duration, setDuration] = useState<number | undefined>(undefined);
|
|
298
|
+
|
|
299
|
+
// Detect keyboard state.
|
|
300
|
+
useEffect(() => {
|
|
301
|
+
const viewport = window.visualViewport;
|
|
302
|
+
if (!viewport) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Handler for VisualViewport resize (fallback for non-iOS).
|
|
307
|
+
const initialHeight = viewport.height ?? window.innerHeight;
|
|
308
|
+
|
|
309
|
+
const updateState = (keyboardHeight: number, keyboardOpen: boolean, animationDuration?: number) => {
|
|
310
|
+
setOpen(keyboardOpen);
|
|
311
|
+
setHeight(keyboardHeight);
|
|
312
|
+
setDuration(animationDuration);
|
|
313
|
+
|
|
314
|
+
const vvh = initialHeight - keyboardHeight;
|
|
315
|
+
document.documentElement.style.setProperty('--vvh', `${vvh}px`);
|
|
316
|
+
document.documentElement.style.setProperty('--kb-height', `${keyboardHeight}px`);
|
|
317
|
+
document.documentElement.style.setProperty('--kb-open', keyboardOpen ? '1' : '0');
|
|
318
|
+
log.info('viewport size', { initialHeight, vvh, keyboardHeight, keyboardOpen, animationDuration });
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
let rafId: number | undefined;
|
|
322
|
+
|
|
323
|
+
return combine(
|
|
324
|
+
// Handler for native iOS keyboard events (from KeyboardObserver.swift).
|
|
325
|
+
addEventListener(
|
|
326
|
+
window,
|
|
327
|
+
'keyboard' as any,
|
|
328
|
+
(event: CustomEvent<{ type: 'show' | 'hide'; height: number; duration: number }>) => {
|
|
329
|
+
const { type, height, duration } = event.detail;
|
|
330
|
+
// iOS KeyboardObserver.swift sends duration in seconds (e.g., 0.25). Convert to ms.
|
|
331
|
+
const durationMs = duration < 1 ? duration * 1000 : duration;
|
|
332
|
+
|
|
333
|
+
// TODO(burdon): Remove debug logging.
|
|
334
|
+
const vp = window.visualViewport;
|
|
335
|
+
dbg(
|
|
336
|
+
`kb:${type} h=${height} dur=${duration} scrollY=${window.scrollY} vpOffset=${vp?.offsetTop?.toFixed(0) ?? '?'}`,
|
|
337
|
+
);
|
|
338
|
+
log.info('keyboard event', { type, height, duration });
|
|
339
|
+
|
|
340
|
+
updateState(height, type === 'show', durationMs);
|
|
341
|
+
|
|
342
|
+
// RAF loop: monitor visualViewport.offsetTop and window.scrollY every frame.
|
|
343
|
+
// TODO(burdon): Remove debug logging.
|
|
344
|
+
const end = performance.now() + durationMs + 300;
|
|
345
|
+
let prevOffsetTop = vp?.offsetTop ?? 0;
|
|
346
|
+
let prevScrollY = window.scrollY;
|
|
347
|
+
const monitorFrame = () => {
|
|
348
|
+
const offsetTop = vp?.offsetTop ?? 0;
|
|
349
|
+
const scrollY = window.scrollY;
|
|
350
|
+
if (offsetTop !== prevOffsetTop || scrollY !== prevScrollY) {
|
|
351
|
+
dbg(`Δ vpOffset=${offsetTop.toFixed(0)} scrollY=${scrollY.toFixed(0)}`);
|
|
352
|
+
prevOffsetTop = offsetTop;
|
|
353
|
+
prevScrollY = scrollY;
|
|
354
|
+
}
|
|
355
|
+
if (scrollY !== 0) {
|
|
356
|
+
window.scrollTo(0, 0);
|
|
357
|
+
}
|
|
358
|
+
if (performance.now() < end) {
|
|
359
|
+
rafId = requestAnimationFrame(monitorFrame);
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
rafId = requestAnimationFrame(monitorFrame);
|
|
363
|
+
},
|
|
364
|
+
),
|
|
365
|
+
() => {
|
|
366
|
+
if (rafId !== undefined) {
|
|
367
|
+
cancelAnimationFrame(rafId);
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
);
|
|
371
|
+
}, [dbg]);
|
|
372
|
+
|
|
373
|
+
return { open, height, duration };
|
|
374
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
6
|
+
|
|
7
|
+
import { useOperationInvoker } from '@dxos/app-framework/ui';
|
|
8
|
+
import { LayoutOperation } from '@dxos/app-toolkit';
|
|
9
|
+
import { useAppGraph } from '@dxos/app-toolkit/ui';
|
|
10
|
+
import { type Node, useConnections } from '@dxos/plugin-graph';
|
|
11
|
+
import { Avatar, Icon, ScrollArea, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
12
|
+
import { Card } from '@dxos/react-ui';
|
|
13
|
+
import { Mosaic, type MosaicStackTileComponent } from '@dxos/react-ui-mosaic';
|
|
14
|
+
import { SearchPanel, useSearchListItem, useSearchListResults } from '@dxos/react-ui-search';
|
|
15
|
+
import { mx } from '@dxos/ui-theme';
|
|
16
|
+
|
|
17
|
+
import { meta } from '#meta';
|
|
18
|
+
import { useExpandPath } from '../hooks';
|
|
19
|
+
|
|
20
|
+
export type NavBranchProps = {
|
|
21
|
+
id: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Renders the children of a graph branch node as a searchable mosaic list.
|
|
26
|
+
* Used for any node with `role: 'branch'` or a workspace disposition, including
|
|
27
|
+
* spaces, collection sections, type sections, and schema nodes.
|
|
28
|
+
*/
|
|
29
|
+
export const NavBranch = ({ id }: NavBranchProps) => {
|
|
30
|
+
const { t } = useTranslation(meta.id);
|
|
31
|
+
const { graph } = useAppGraph();
|
|
32
|
+
|
|
33
|
+
useExpandPath(id);
|
|
34
|
+
|
|
35
|
+
const children = useConnections(graph, id, 'child');
|
|
36
|
+
|
|
37
|
+
// TODO(wittjosiah): Move alternate-tree nodes to a non-child relation so they don't need filtering.
|
|
38
|
+
const visibleChildren = useMemo(
|
|
39
|
+
() =>
|
|
40
|
+
children.filter(
|
|
41
|
+
(node) => node.properties.disposition !== 'alternate-tree' && node.properties.disposition !== 'hidden',
|
|
42
|
+
),
|
|
43
|
+
[children],
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const { results, handleSearch } = useSearchListResults({
|
|
47
|
+
items: visibleChildren,
|
|
48
|
+
extract: (child) => toLocalizedString(child.properties.label, t),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<SearchPanel onSearch={handleSearch}>
|
|
53
|
+
<Mosaic.Container asChild>
|
|
54
|
+
<ScrollArea.Root centered padding thin>
|
|
55
|
+
<ScrollArea.Viewport>
|
|
56
|
+
<Mosaic.Stack
|
|
57
|
+
classNames='gap-1'
|
|
58
|
+
draggable={false}
|
|
59
|
+
items={results}
|
|
60
|
+
getId={(item) => item.id}
|
|
61
|
+
Tile={NavBranchTile}
|
|
62
|
+
/>
|
|
63
|
+
</ScrollArea.Viewport>
|
|
64
|
+
</ScrollArea.Root>
|
|
65
|
+
</Mosaic.Container>
|
|
66
|
+
</SearchPanel>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const NavBranchTile: MosaicStackTileComponent<Node.Node> = (props) => {
|
|
71
|
+
const data = props.data;
|
|
72
|
+
const { t } = useTranslation(meta.id);
|
|
73
|
+
const { invokePromise } = useOperationInvoker();
|
|
74
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
75
|
+
const { selectedValue, registerItem, unregisterItem } = useSearchListItem();
|
|
76
|
+
const isSelected = selectedValue === data.id;
|
|
77
|
+
|
|
78
|
+
const name = toLocalizedString(data.properties.label, t);
|
|
79
|
+
|
|
80
|
+
const handleSelect = useCallback(
|
|
81
|
+
() => void invokePromise(LayoutOperation.Open, { subject: [data.id] }),
|
|
82
|
+
[invokePromise, data.id],
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Register this item with the search context.
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (ref.current) {
|
|
88
|
+
registerItem(data.id, ref.current, handleSelect);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return () => unregisterItem(data.id);
|
|
92
|
+
}, [data.id, handleSelect, registerItem, unregisterItem]);
|
|
93
|
+
|
|
94
|
+
// Scroll into view when selected.
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (isSelected && ref.current) {
|
|
97
|
+
ref.current.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
98
|
+
}
|
|
99
|
+
}, [isSelected]);
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<Card.Root
|
|
103
|
+
ref={ref}
|
|
104
|
+
role='button'
|
|
105
|
+
fullWidth
|
|
106
|
+
tabIndex={-1} // TODO(burdon): Use Mosaic.Focus.
|
|
107
|
+
data-selected={isSelected}
|
|
108
|
+
classNames={mx('dx-focus-ring cursor-pointer', isSelected && 'bg-hover-overlay')}
|
|
109
|
+
onClick={handleSelect}
|
|
110
|
+
>
|
|
111
|
+
<Card.Toolbar>
|
|
112
|
+
<Avatar.Root>
|
|
113
|
+
<Avatar.Content
|
|
114
|
+
hue={data.properties.hue}
|
|
115
|
+
icon={data.properties.icon}
|
|
116
|
+
hueVariant='transparent'
|
|
117
|
+
variant='square'
|
|
118
|
+
size={8}
|
|
119
|
+
fallback={name}
|
|
120
|
+
/>
|
|
121
|
+
<Avatar.Label>{name}</Avatar.Label>
|
|
122
|
+
<Icon icon='ph--caret-right--regular' />
|
|
123
|
+
</Avatar.Root>
|
|
124
|
+
</Card.Toolbar>
|
|
125
|
+
</Card.Root>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
import { createContext } from '@radix-ui/react-context';
|
|
6
6
|
import React, { type PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import { Surface } from '@dxos/app-framework/
|
|
8
|
+
import { Surface } from '@dxos/app-framework/ui';
|
|
9
|
+
|
|
9
10
|
import { Popover, type PopoverContentInteractOutsideEvent, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
10
|
-
import { Card } from '@dxos/react-ui
|
|
11
|
+
import { Card } from '@dxos/react-ui';
|
|
11
12
|
|
|
12
|
-
import { useSimpleLayoutState } from '
|
|
13
|
-
import { meta } from '
|
|
13
|
+
import { useSimpleLayoutState } from '#hooks';
|
|
14
|
+
import { meta } from '#meta';
|
|
14
15
|
|
|
15
16
|
const DEBOUNCE_DELAY = 40;
|
|
16
17
|
|
|
@@ -57,7 +58,6 @@ export const PopoverContent = () => {
|
|
|
57
58
|
const { t } = useTranslation(meta.id);
|
|
58
59
|
const { state, updateState } = useSimpleLayoutState();
|
|
59
60
|
const { setOpen } = useLayoutPopoverContext('PopoverContent');
|
|
60
|
-
|
|
61
61
|
const handleClose = useCallback(() => {
|
|
62
62
|
setOpen(false);
|
|
63
63
|
updateState((s) => ({
|
|
@@ -100,18 +100,18 @@ export const PopoverContent = () => {
|
|
|
100
100
|
onEscapeKeyDown={handleInteractOutside}
|
|
101
101
|
>
|
|
102
102
|
<Popover.Viewport>
|
|
103
|
+
{state.popoverKind === 'base' && <Surface.Surface role='popover' data={state.popoverContent} limit={1} />}
|
|
103
104
|
{state.popoverKind === 'card' && (
|
|
104
|
-
<Card.Root>
|
|
105
|
+
<Card.Root border={false} classNames='dx-card-popover'>
|
|
105
106
|
<Card.Toolbar>
|
|
106
107
|
{/* TODO(wittjosiah): Cleaner way to handle no drag handle in toolbar? */}
|
|
107
108
|
<span />
|
|
108
109
|
{state.popoverTitle ? <Card.Title>{toLocalizedString(state.popoverTitle, t)}</Card.Title> : <span />}
|
|
109
|
-
<Card.
|
|
110
|
+
<Card.CloseIconButton onClick={handleClose} />
|
|
110
111
|
</Card.Toolbar>
|
|
111
|
-
<Surface role='card--content' data={state.popoverContent} limit={1} />
|
|
112
|
+
<Surface.Surface role='card--content' data={state.popoverContent} limit={1} />
|
|
112
113
|
</Card.Root>
|
|
113
114
|
)}
|
|
114
|
-
{state.popoverKind === 'base' && <Surface role='popover' data={state.popoverContent} limit={1} />}
|
|
115
115
|
</Popover.Viewport>
|
|
116
116
|
<Popover.Arrow />
|
|
117
117
|
</Popover.Content>
|