@dxos/plugin-simple-layout 0.8.4-main.6fa680abb7 → 0.8.4-main.7996785055
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/{chunk-MDPEKLKR.mjs → chunk-J5FQ32AV.mjs} +302 -209
- package/dist/lib/browser/chunk-J5FQ32AV.mjs.map +7 -0
- package/dist/lib/browser/chunk-XJVW3PRY.mjs +22 -0
- package/dist/lib/browser/chunk-XJVW3PRY.mjs.map +7 -0
- package/dist/lib/browser/close-OT5JOGVY.mjs +34 -0
- package/dist/lib/browser/close-OT5JOGVY.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +23 -22
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/open-4FQ44Z5G.mjs +31 -0
- package/dist/lib/browser/open-4FQ44Z5G.mjs.map +7 -0
- package/dist/lib/browser/operation-handler-OAD7LISD.mjs +16 -0
- package/dist/lib/browser/operation-handler-OAD7LISD.mjs.map +7 -0
- package/dist/lib/browser/{react-root-WVQYY2JA.mjs → react-root-6KIGPLUT.mjs} +2 -2
- package/dist/lib/browser/{react-surface-VLBR37ED.mjs → react-surface-JLIEQGOL.mjs} +2 -2
- package/dist/lib/browser/revert-workspace-DLE265AN.mjs +21 -0
- package/dist/lib/browser/revert-workspace-DLE265AN.mjs.map +7 -0
- package/dist/lib/browser/set-52HGTSH4.mjs +21 -0
- package/dist/lib/browser/set-52HGTSH4.mjs.map +7 -0
- package/dist/lib/browser/set-layout-mode-T6QI3DGU.mjs +11 -0
- package/dist/lib/browser/set-layout-mode-T6QI3DGU.mjs.map +7 -0
- package/dist/lib/browser/switch-workspace-5Y6T4BWJ.mjs +24 -0
- package/dist/lib/browser/switch-workspace-5Y6T4BWJ.mjs.map +7 -0
- package/dist/lib/browser/update-complementary-MX3TTVAB.mjs +31 -0
- package/dist/lib/browser/update-complementary-MX3TTVAB.mjs.map +7 -0
- package/dist/lib/browser/update-dialog-FPAPZXKO.mjs +29 -0
- package/dist/lib/browser/update-dialog-FPAPZXKO.mjs.map +7 -0
- package/dist/lib/browser/update-popover-6V5ZTIYN.mjs +33 -0
- package/dist/lib/browser/update-popover-6V5ZTIYN.mjs.map +7 -0
- package/dist/lib/browser/update-sidebar-WHDKYMV7.mjs +10 -0
- package/dist/lib/browser/update-sidebar-WHDKYMV7.mjs.map +7 -0
- package/dist/lib/browser/{url-handler-RBRONH7S.mjs → url-handler-GUJ3L7Y3.mjs} +24 -12
- package/dist/lib/browser/url-handler-GUJ3L7Y3.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-27K22G6S.mjs +23 -0
- package/dist/lib/node-esm/chunk-27K22G6S.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-DCKASLMP.mjs → chunk-EXNDYZTP.mjs} +302 -209
- package/dist/lib/node-esm/chunk-EXNDYZTP.mjs.map +7 -0
- package/dist/lib/node-esm/close-PEVHREL2.mjs +35 -0
- package/dist/lib/node-esm/close-PEVHREL2.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +23 -22
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/open-LMJY7JCJ.mjs +32 -0
- package/dist/lib/node-esm/open-LMJY7JCJ.mjs.map +7 -0
- package/dist/lib/node-esm/operation-handler-A2DC4WHC.mjs +18 -0
- package/dist/lib/node-esm/operation-handler-A2DC4WHC.mjs.map +7 -0
- package/dist/lib/node-esm/{react-root-XBNDM7BE.mjs → react-root-5SCW2KTH.mjs} +2 -2
- package/dist/lib/node-esm/{react-surface-U5NHA367.mjs → react-surface-WLKB6AET.mjs} +2 -2
- package/dist/lib/node-esm/revert-workspace-XZXT64YA.mjs +22 -0
- package/dist/lib/node-esm/revert-workspace-XZXT64YA.mjs.map +7 -0
- package/dist/lib/node-esm/set-5I6LFH5L.mjs +22 -0
- package/dist/lib/node-esm/set-5I6LFH5L.mjs.map +7 -0
- package/dist/lib/node-esm/set-layout-mode-F5B6QLZM.mjs +13 -0
- package/dist/lib/node-esm/set-layout-mode-F5B6QLZM.mjs.map +7 -0
- package/dist/lib/node-esm/switch-workspace-PB6T2SGY.mjs +25 -0
- package/dist/lib/node-esm/switch-workspace-PB6T2SGY.mjs.map +7 -0
- package/dist/lib/node-esm/update-complementary-FTW423IY.mjs +32 -0
- package/dist/lib/node-esm/update-complementary-FTW423IY.mjs.map +7 -0
- package/dist/lib/node-esm/update-dialog-ID267DCL.mjs +30 -0
- package/dist/lib/node-esm/update-dialog-ID267DCL.mjs.map +7 -0
- package/dist/lib/node-esm/update-popover-RLHU2HF4.mjs +34 -0
- package/dist/lib/node-esm/update-popover-RLHU2HF4.mjs.map +7 -0
- package/dist/lib/node-esm/update-sidebar-BJ7HTNZ4.mjs +12 -0
- package/dist/lib/node-esm/update-sidebar-BJ7HTNZ4.mjs.map +7 -0
- package/dist/lib/node-esm/{url-handler-QSMCH3JB.mjs → url-handler-WMONO2T6.mjs} +24 -12
- package/dist/lib/node-esm/url-handler-WMONO2T6.mjs.map +7 -0
- package/dist/types/src/SimpleLayoutPlugin.d.ts +1 -1
- package/dist/types/src/SimpleLayoutPlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts +1 -1
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/operation-handler/index.d.ts +4 -0
- package/dist/types/src/capabilities/operation-handler/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/operation-handler/operation-handler.d.ts +6 -0
- package/dist/types/src/capabilities/operation-handler/operation-handler.d.ts.map +1 -0
- package/dist/types/src/capabilities/url-handler/url-handler.d.ts.map +1 -1
- package/dist/types/src/components/ContentError.stories.d.ts +6 -1
- 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/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.map +1 -1
- package/dist/types/src/components/MobileLayout/MobileLayout.stories.d.ts.map +1 -1
- package/dist/types/src/components/NavBranch/NavBranch.d.ts.map +1 -1
- package/dist/types/src/components/Popover/Popover.d.ts.map +1 -1
- package/dist/types/src/components/SimpleLayout/AppBar.d.ts +9 -7
- package/dist/types/src/components/SimpleLayout/AppBar.d.ts.map +1 -1
- package/dist/types/src/components/SimpleLayout/AppBar.stories.d.ts +9 -4
- package/dist/types/src/components/SimpleLayout/AppBar.stories.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 +9 -7
- package/dist/types/src/components/SimpleLayout/NavBar.d.ts.map +1 -1
- package/dist/types/src/components/SimpleLayout/NavBar.stories.d.ts +13 -5
- 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 +10 -11
- package/dist/types/src/components/SimpleLayout/SimpleLayout.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +2 -1
- package/dist/types/src/components/index.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 +6 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +29 -29
- package/src/SimpleLayoutPlugin.ts +3 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/operation-handler/index.ts +9 -0
- package/src/capabilities/operation-handler/operation-handler.ts +14 -0
- package/src/capabilities/url-handler/url-handler.ts +15 -3
- package/src/components/DebugOverlay/DebugOverlay.tsx +96 -0
- package/src/components/DebugOverlay/index.ts +5 -0
- package/src/components/Home/Home.tsx +27 -28
- package/src/components/{ContentLoading/ContentLoading.stories.tsx → Loading/Loading.stories.tsx} +4 -4
- package/src/components/{ContentLoading/ContentLoading.tsx → Loading/Loading.tsx} +1 -1
- package/src/components/{ContentLoading → Loading}/index.ts +1 -1
- package/src/components/MobileLayout/MobileLayout.stories.tsx +10 -6
- package/src/components/MobileLayout/MobileLayout.tsx +118 -49
- package/src/components/NavBranch/NavBranch.tsx +27 -30
- package/src/components/Popover/Popover.tsx +2 -12
- package/src/components/SimpleLayout/AppBar.stories.tsx +3 -3
- package/src/components/SimpleLayout/AppBar.tsx +58 -59
- package/src/components/SimpleLayout/Drawer.tsx +6 -6
- package/src/components/SimpleLayout/Main.tsx +15 -19
- package/src/components/SimpleLayout/NavBar.tsx +8 -9
- package/src/components/SimpleLayout/SimpleLayout.stories.tsx +41 -64
- package/src/components/SimpleLayout/SimpleLayout.tsx +29 -30
- package/src/components/index.ts +2 -1
- package/src/hooks/useAppBarProps.ts +6 -6
- package/src/hooks/useDrawerActions.ts +1 -1
- package/src/operations/close.ts +34 -0
- package/src/operations/index.ts +16 -0
- package/src/operations/open.ts +32 -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 +34 -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 +2 -1
- package/dist/lib/browser/chunk-MDPEKLKR.mjs.map +0 -7
- package/dist/lib/browser/operation-resolver-VTZ6HZ4B.mjs +0 -194
- package/dist/lib/browser/operation-resolver-VTZ6HZ4B.mjs.map +0 -7
- package/dist/lib/browser/url-handler-RBRONH7S.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-DCKASLMP.mjs.map +0 -7
- package/dist/lib/node-esm/operation-resolver-R7CQ6ERU.mjs +0 -195
- package/dist/lib/node-esm/operation-resolver-R7CQ6ERU.mjs.map +0 -7
- package/dist/lib/node-esm/url-handler-QSMCH3JB.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/components/ContentLoading/ContentLoading.d.ts +0 -3
- package/dist/types/src/components/ContentLoading/ContentLoading.d.ts.map +0 -1
- package/dist/types/src/components/ContentLoading/ContentLoading.stories.d.ts.map +0 -1
- package/dist/types/src/components/ContentLoading/index.d.ts +0 -2
- package/dist/types/src/components/ContentLoading/index.d.ts.map +0 -1
- package/src/capabilities/operation-resolver/index.ts +0 -10
- package/src/capabilities/operation-resolver/operation-resolver.ts +0 -202
- /package/dist/lib/browser/{react-root-WVQYY2JA.mjs.map → react-root-6KIGPLUT.mjs.map} +0 -0
- /package/dist/lib/browser/{react-surface-VLBR37ED.mjs.map → react-surface-JLIEQGOL.mjs.map} +0 -0
- /package/dist/lib/node-esm/{react-root-XBNDM7BE.mjs.map → react-root-5SCW2KTH.mjs.map} +0 -0
- /package/dist/lib/node-esm/{react-surface-U5NHA367.mjs.map → react-surface-WLKB6AET.mjs.map} +0 -0
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { createContext } from '@radix-ui/react-context';
|
|
6
|
-
import React, { type PropsWithChildren, forwardRef, useEffect, useState } from 'react';
|
|
6
|
+
import React, { type PropsWithChildren, forwardRef, useEffect, useLayoutEffect, useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { addEventListener, combine } from '@dxos/async';
|
|
9
9
|
import { log } from '@dxos/log';
|
|
10
10
|
import { type ThemedClassName } from '@dxos/react-ui';
|
|
11
11
|
import { mx } from '@dxos/ui-theme';
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
import { useDebugLog } from '../DebugOverlay';
|
|
14
14
|
|
|
15
15
|
const MOBILE_LAYOUT_NAME = 'MobileLayout';
|
|
16
16
|
const MOBILE_LAYOUT_ROOT_NAME = 'MobileLayout.Root';
|
|
@@ -42,11 +42,15 @@ type MobileLayoutRootProps = ThemedClassName<
|
|
|
42
42
|
*/
|
|
43
43
|
// TODO(burdon): Should this be ios-only?
|
|
44
44
|
const MobileLayoutRoot = forwardRef<HTMLDivElement, MobileLayoutRootProps>(
|
|
45
|
-
({ classNames, children, transition =
|
|
45
|
+
({ classNames, children, transition = 500, onKeyboardOpenChange, ...props }, forwardedRef) => {
|
|
46
46
|
const { open: keyboardOpen } = useIOSKeyboard();
|
|
47
|
-
useAutoScroll();
|
|
48
|
-
useEffect(() => onKeyboardOpenChange?.(keyboardOpen), [onKeyboardOpenChange, keyboardOpen]);
|
|
49
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]);
|
|
50
54
|
|
|
51
55
|
return (
|
|
52
56
|
<MobileLayoutProvider keyboardOpen={keyboardOpen}>
|
|
@@ -54,10 +58,11 @@ const MobileLayoutRoot = forwardRef<HTMLDivElement, MobileLayoutRootProps>(
|
|
|
54
58
|
{...props}
|
|
55
59
|
role='none'
|
|
56
60
|
style={{
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
height: 'calc(100vh - var(--kb-height, 0px))',
|
|
62
|
+
transition: `height ${keyboardOpen ? 0 : transition}ms ease-out`,
|
|
63
|
+
// transition: `height ${animationDuration}ms ease-out`,
|
|
59
64
|
}}
|
|
60
|
-
className={mx('
|
|
65
|
+
className={mx('fixed top-0 left-0 right-0 grid overflow-hidden', classNames)}
|
|
61
66
|
ref={forwardedRef}
|
|
62
67
|
>
|
|
63
68
|
{children}
|
|
@@ -95,7 +100,7 @@ const MobileLayoutPanel = forwardRef<HTMLDivElement, MobileLayoutPanelProps>(
|
|
|
95
100
|
paddingTop: safe?.top ? 'env(safe-area-inset-top)' : undefined,
|
|
96
101
|
paddingBottom: safe?.bottom ? `calc((1 - var(--kb-open, 0)) * env(safe-area-inset-bottom))` : undefined,
|
|
97
102
|
}}
|
|
98
|
-
className={mx(
|
|
103
|
+
className={mx(classNames)}
|
|
99
104
|
ref={forwardedRef}
|
|
100
105
|
>
|
|
101
106
|
{children}
|
|
@@ -120,42 +125,62 @@ export { useMobileLayout };
|
|
|
120
125
|
export type { MobileLayoutRootProps, MobileLayoutPanelProps };
|
|
121
126
|
|
|
122
127
|
/**
|
|
123
|
-
*
|
|
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.
|
|
124
133
|
*/
|
|
125
134
|
const useAutoScroll = () => {
|
|
135
|
+
// TODO(burdon): Remove debug logging.
|
|
136
|
+
const { dbg } = useDebugLog('useAutoScroll');
|
|
137
|
+
|
|
126
138
|
useEffect(() => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
+
})(),
|
|
157
182
|
);
|
|
158
|
-
}, []);
|
|
183
|
+
}, [dbg]);
|
|
159
184
|
};
|
|
160
185
|
|
|
161
186
|
/**
|
|
@@ -231,6 +256,8 @@ const useLockBodyScroll = (enabled: boolean) => {
|
|
|
231
256
|
type IOSKeyboard = {
|
|
232
257
|
open: boolean;
|
|
233
258
|
height: number;
|
|
259
|
+
/** Native keyboard animation duration in ms, from the iOS keyboard event. */
|
|
260
|
+
duration: number | undefined;
|
|
234
261
|
};
|
|
235
262
|
|
|
236
263
|
/**
|
|
@@ -263,10 +290,13 @@ type IOSKeyboard = {
|
|
|
263
290
|
* Falls back to VisualViewport API on other platforms.
|
|
264
291
|
*/
|
|
265
292
|
const useIOSKeyboard = (): IOSKeyboard => {
|
|
293
|
+
const { dbg } = useDebugLog('useIOSKeyboard');
|
|
294
|
+
|
|
266
295
|
const [open, setOpen] = useState(false);
|
|
267
296
|
const [height, setHeight] = useState(0);
|
|
297
|
+
const [duration, setDuration] = useState<number | undefined>(undefined);
|
|
268
298
|
|
|
269
|
-
// Detect
|
|
299
|
+
// Detect keyboard state.
|
|
270
300
|
useEffect(() => {
|
|
271
301
|
const viewport = window.visualViewport;
|
|
272
302
|
if (!viewport) {
|
|
@@ -276,30 +306,69 @@ const useIOSKeyboard = (): IOSKeyboard => {
|
|
|
276
306
|
// Handler for VisualViewport resize (fallback for non-iOS).
|
|
277
307
|
const initialHeight = viewport.height ?? window.innerHeight;
|
|
278
308
|
|
|
279
|
-
const updateState = (keyboardHeight: number, keyboardOpen: boolean) => {
|
|
309
|
+
const updateState = (keyboardHeight: number, keyboardOpen: boolean, animationDuration?: number) => {
|
|
280
310
|
setOpen(keyboardOpen);
|
|
281
311
|
setHeight(keyboardHeight);
|
|
312
|
+
setDuration(animationDuration);
|
|
282
313
|
|
|
283
314
|
const vvh = initialHeight - keyboardHeight;
|
|
284
315
|
document.documentElement.style.setProperty('--vvh', `${vvh}px`);
|
|
285
316
|
document.documentElement.style.setProperty('--kb-height', `${keyboardHeight}px`);
|
|
286
317
|
document.documentElement.style.setProperty('--kb-open', keyboardOpen ? '1' : '0');
|
|
287
|
-
log.info('viewport size', { initialHeight, vvh, keyboardHeight, keyboardOpen });
|
|
318
|
+
log.info('viewport size', { initialHeight, vvh, keyboardHeight, keyboardOpen, animationDuration });
|
|
288
319
|
};
|
|
289
320
|
|
|
321
|
+
let rafId: number | undefined;
|
|
322
|
+
|
|
290
323
|
return combine(
|
|
291
324
|
// Handler for native iOS keyboard events (from KeyboardObserver.swift).
|
|
292
325
|
addEventListener(
|
|
293
326
|
window,
|
|
294
327
|
'keyboard' as any,
|
|
295
328
|
(event: CustomEvent<{ type: 'show' | 'hide'; height: number; duration: number }>) => {
|
|
296
|
-
const { type, height } = event.detail;
|
|
297
|
-
|
|
298
|
-
|
|
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);
|
|
299
363
|
},
|
|
300
364
|
),
|
|
365
|
+
() => {
|
|
366
|
+
if (rafId !== undefined) {
|
|
367
|
+
cancelAnimationFrame(rafId);
|
|
368
|
+
}
|
|
369
|
+
},
|
|
301
370
|
);
|
|
302
|
-
}, []);
|
|
371
|
+
}, [dbg]);
|
|
303
372
|
|
|
304
|
-
return { open, height };
|
|
373
|
+
return { open, height, duration };
|
|
305
374
|
};
|
|
@@ -8,10 +8,10 @@ import { useOperationInvoker } from '@dxos/app-framework/ui';
|
|
|
8
8
|
import { LayoutOperation } from '@dxos/app-toolkit';
|
|
9
9
|
import { useAppGraph } from '@dxos/app-toolkit/ui';
|
|
10
10
|
import { type Node, useConnections } from '@dxos/plugin-graph';
|
|
11
|
-
import { Avatar, Icon,
|
|
11
|
+
import { Avatar, Icon, ScrollArea, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
12
12
|
import { Card } from '@dxos/react-ui';
|
|
13
13
|
import { Mosaic, type MosaicStackTileComponent } from '@dxos/react-ui-mosaic';
|
|
14
|
-
import {
|
|
14
|
+
import { SearchPanel, useSearchListItem, useSearchListResults } from '@dxos/react-ui-search';
|
|
15
15
|
import { mx } from '@dxos/ui-theme';
|
|
16
16
|
|
|
17
17
|
import { meta } from '../../meta';
|
|
@@ -36,7 +36,10 @@ export const NavBranch = ({ id }: NavBranchProps) => {
|
|
|
36
36
|
|
|
37
37
|
// TODO(wittjosiah): Move alternate-tree nodes to a non-child relation so they don't need filtering.
|
|
38
38
|
const visibleChildren = useMemo(
|
|
39
|
-
() =>
|
|
39
|
+
() =>
|
|
40
|
+
children.filter(
|
|
41
|
+
(node) => node.properties.disposition !== 'alternate-tree' && node.properties.disposition !== 'hidden',
|
|
42
|
+
),
|
|
40
43
|
[children],
|
|
41
44
|
);
|
|
42
45
|
|
|
@@ -46,34 +49,28 @@ export const NavBranch = ({ id }: NavBranchProps) => {
|
|
|
46
49
|
});
|
|
47
50
|
|
|
48
51
|
return (
|
|
49
|
-
<
|
|
50
|
-
<
|
|
51
|
-
<
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
</ScrollArea.Root>
|
|
65
|
-
</Mosaic.Container>
|
|
66
|
-
</SearchList.Content>
|
|
67
|
-
</Panel.Content>
|
|
68
|
-
</Panel.Root>
|
|
69
|
-
</SearchList.Root>
|
|
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>
|
|
70
67
|
);
|
|
71
68
|
};
|
|
72
69
|
|
|
73
70
|
const NavBranchTile: MosaicStackTileComponent<Node.Node> = (props) => {
|
|
74
71
|
const data = props.data;
|
|
75
72
|
const { t } = useTranslation(meta.id);
|
|
76
|
-
const {
|
|
73
|
+
const { invokePromise } = useOperationInvoker();
|
|
77
74
|
const ref = useRef<HTMLDivElement>(null);
|
|
78
75
|
const { selectedValue, registerItem, unregisterItem } = useSearchListItem();
|
|
79
76
|
const isSelected = selectedValue === data.id;
|
|
@@ -81,8 +78,8 @@ const NavBranchTile: MosaicStackTileComponent<Node.Node> = (props) => {
|
|
|
81
78
|
const name = toLocalizedString(data.properties.label, t);
|
|
82
79
|
|
|
83
80
|
const handleSelect = useCallback(
|
|
84
|
-
() =>
|
|
85
|
-
[
|
|
81
|
+
() => void invokePromise(LayoutOperation.Open, { subject: [data.id] }),
|
|
82
|
+
[invokePromise, data.id],
|
|
86
83
|
);
|
|
87
84
|
|
|
88
85
|
// Register this item with the search context.
|
|
@@ -108,17 +105,17 @@ const NavBranchTile: MosaicStackTileComponent<Node.Node> = (props) => {
|
|
|
108
105
|
fullWidth
|
|
109
106
|
tabIndex={-1} // TODO(burdon): Use Mosaic.Focus.
|
|
110
107
|
data-selected={isSelected}
|
|
111
|
-
classNames={mx('dx-focus-ring', isSelected && 'bg-hover-overlay')}
|
|
108
|
+
classNames={mx('dx-focus-ring cursor-pointer', isSelected && 'bg-hover-overlay')}
|
|
112
109
|
onClick={handleSelect}
|
|
113
110
|
>
|
|
114
|
-
<Card.Toolbar
|
|
111
|
+
<Card.Toolbar>
|
|
115
112
|
<Avatar.Root>
|
|
116
113
|
<Avatar.Content
|
|
117
114
|
hue={data.properties.hue}
|
|
118
115
|
icon={data.properties.icon}
|
|
119
116
|
hueVariant='transparent'
|
|
120
117
|
variant='square'
|
|
121
|
-
size={
|
|
118
|
+
size={8}
|
|
122
119
|
fallback={name}
|
|
123
120
|
/>
|
|
124
121
|
<Avatar.Label>{name}</Avatar.Label>
|
|
@@ -6,7 +6,7 @@ import { createContext } from '@radix-ui/react-context';
|
|
|
6
6
|
import React, { type PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { Surface } from '@dxos/app-framework/ui';
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
import { Popover, type PopoverContentInteractOutsideEvent, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
11
11
|
import { Card } from '@dxos/react-ui';
|
|
12
12
|
|
|
@@ -54,16 +54,10 @@ export const PopoverRoot = ({ children }: PropsWithChildren) => {
|
|
|
54
54
|
);
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
// Extracts the subject from popover content if it has one, otherwise returns the content as-is.
|
|
58
|
-
const getPopoverSubject = (content: unknown): unknown =>
|
|
59
|
-
content && typeof content === 'object' && 'subject' in content ? (content as { subject: unknown }).subject : content;
|
|
60
|
-
|
|
61
57
|
export const PopoverContent = () => {
|
|
62
58
|
const { t } = useTranslation(meta.id);
|
|
63
59
|
const { state, updateState } = useSimpleLayoutState();
|
|
64
60
|
const { setOpen } = useLayoutPopoverContext('PopoverContent');
|
|
65
|
-
const handleNavigate = useObjectNavigate(getPopoverSubject(state.popoverContent));
|
|
66
|
-
|
|
67
61
|
const handleClose = useCallback(() => {
|
|
68
62
|
setOpen(false);
|
|
69
63
|
updateState((s) => ({
|
|
@@ -112,11 +106,7 @@ export const PopoverContent = () => {
|
|
|
112
106
|
<Card.Toolbar>
|
|
113
107
|
{/* TODO(wittjosiah): Cleaner way to handle no drag handle in toolbar? */}
|
|
114
108
|
<span />
|
|
115
|
-
{state.popoverTitle ? (
|
|
116
|
-
<Card.Title onClick={handleNavigate}>{toLocalizedString(state.popoverTitle, t)}</Card.Title>
|
|
117
|
-
) : (
|
|
118
|
-
<span />
|
|
119
|
-
)}
|
|
109
|
+
{state.popoverTitle ? <Card.Title>{toLocalizedString(state.popoverTitle, t)}</Card.Title> : <span />}
|
|
120
110
|
<Card.CloseIconButton onClick={handleClose} />
|
|
121
111
|
</Card.Toolbar>
|
|
122
112
|
<Surface.Surface role='card--content' data={state.popoverContent} limit={1} />
|
|
@@ -39,11 +39,11 @@ const buildDefaultActions = (): ActionGraphProps => {
|
|
|
39
39
|
return result;
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
type
|
|
42
|
+
type DefaultStoryProps = Omit<AppBarProps, 'actions'> & {
|
|
43
43
|
actions: ActionGraphProps;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
const DefaultStory = ({ actions: actionsProp, ...props }:
|
|
46
|
+
const DefaultStory = ({ actions: actionsProp, ...props }: DefaultStoryProps) => {
|
|
47
47
|
const actions = useMemo(() => Atom.make(actionsProp).pipe(Atom.keepAlive), [actionsProp]);
|
|
48
48
|
return (
|
|
49
49
|
<MobileLayout.Root>
|
|
@@ -71,7 +71,7 @@ const meta = {
|
|
|
71
71
|
|
|
72
72
|
export default meta;
|
|
73
73
|
|
|
74
|
-
type Story = StoryObj<
|
|
74
|
+
type Story = StoryObj<DefaultStoryProps>;
|
|
75
75
|
|
|
76
76
|
export const Default: Story = {
|
|
77
77
|
tags: ['test'],
|
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
import { type Atom, useAtomValue } from '@effect-atom/atom-react';
|
|
6
6
|
import React, { Fragment } from 'react';
|
|
7
7
|
|
|
8
|
-
import { IconButton, Popover,
|
|
8
|
+
import { IconButton, Popover, Toolbar, useTranslation } from '@dxos/react-ui';
|
|
9
9
|
import { type ActionExecutor, type ActionGraphProps, Menu, useMenuActions } from '@dxos/react-ui-menu';
|
|
10
|
-
import {
|
|
10
|
+
import { composable, composableProps, osTranslations } from '@dxos/ui-theme';
|
|
11
11
|
|
|
12
12
|
import { meta } from '../../meta';
|
|
13
13
|
import { useMobileLayout } from '../MobileLayout';
|
|
14
14
|
|
|
15
15
|
const APP_BAR_NAME = 'SimpleLayout.AppBar';
|
|
16
16
|
|
|
17
|
-
export type AppBarProps =
|
|
17
|
+
export type AppBarProps = {
|
|
18
18
|
/** Title/label to display in the banner. */
|
|
19
19
|
title?: string;
|
|
20
20
|
/** Action graph atom for the dropdown menu. */
|
|
@@ -27,68 +27,67 @@ export type AppBarProps = ThemedClassName<{
|
|
|
27
27
|
onAction?: ActionExecutor;
|
|
28
28
|
/** Callback when back button is clicked. */
|
|
29
29
|
onBack?: () => void;
|
|
30
|
-
}
|
|
30
|
+
};
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* AppBar component that renders a title, optional back button, and actions dropdown.
|
|
34
34
|
*/
|
|
35
|
-
export const AppBar = (
|
|
36
|
-
classNames,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
onBack,
|
|
43
|
-
}: AppBarProps) => {
|
|
44
|
-
const { t } = useTranslation(meta.id);
|
|
45
|
-
const menu = useMenuActions(actions);
|
|
46
|
-
const actionsValue = useAtomValue(actions);
|
|
47
|
-
const hasActions = actionsValue.nodes.length > 0;
|
|
48
|
-
const { keyboardOpen } = useMobileLayout(APP_BAR_NAME);
|
|
35
|
+
export const AppBar = composable<HTMLDivElement, AppBarProps>(
|
|
36
|
+
({ classNames, title, actions, showBackButton, popoverAnchorId, onAction, onBack, ...props }, forwardedRef) => {
|
|
37
|
+
const { t } = useTranslation(meta.id);
|
|
38
|
+
const menuActions = useMenuActions(actions);
|
|
39
|
+
const actionsValue = useAtomValue(actions);
|
|
40
|
+
const hasActions = actionsValue.nodes.length > 0;
|
|
41
|
+
const { keyboardOpen } = useMobileLayout(APP_BAR_NAME);
|
|
49
42
|
|
|
50
|
-
|
|
51
|
-
|
|
43
|
+
// Fall back to app name if no title provided.
|
|
44
|
+
const displayTitle = title ?? t('current app name', { ns: osTranslations });
|
|
52
45
|
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
// Wrap the menu trigger with Popover.Anchor when the popoverAnchorId is set.
|
|
47
|
+
const AnchorRoot = popoverAnchorId ? Popover.Anchor : Fragment;
|
|
55
48
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
49
|
+
return (
|
|
50
|
+
<Toolbar.Root
|
|
51
|
+
{...composableProps(props, {
|
|
52
|
+
role: 'banner',
|
|
53
|
+
classNames: 'grid grid-cols-[var(--dx-rail-size)_1fr_var(--dx-rail-size)] items-center dx-density-fine',
|
|
54
|
+
})}
|
|
55
|
+
ref={forwardedRef}
|
|
56
|
+
>
|
|
57
|
+
{keyboardOpen ? (
|
|
58
|
+
<IconButton variant='ghost' icon='ph--x--regular' iconOnly label={t('done label')} />
|
|
59
|
+
) : showBackButton ? (
|
|
60
|
+
<IconButton
|
|
61
|
+
variant='ghost'
|
|
62
|
+
icon='ph--caret-left--regular'
|
|
63
|
+
iconOnly
|
|
64
|
+
label={t('back label')}
|
|
65
|
+
onClick={onBack}
|
|
66
|
+
/>
|
|
67
|
+
) : (
|
|
68
|
+
<div />
|
|
69
|
+
)}
|
|
70
|
+
<h1 className='text-center truncate font-thin uppercase'>{displayTitle}</h1>
|
|
71
|
+
{hasActions ? (
|
|
72
|
+
<AnchorRoot>
|
|
73
|
+
<Menu.Root {...menuActions} caller={meta.id} onAction={onAction}>
|
|
74
|
+
<Menu.Trigger asChild>
|
|
75
|
+
<IconButton
|
|
76
|
+
variant='ghost'
|
|
77
|
+
icon='ph--dots-three-vertical--regular'
|
|
78
|
+
iconOnly
|
|
79
|
+
label={t('actions menu label')}
|
|
80
|
+
/>
|
|
81
|
+
</Menu.Trigger>
|
|
82
|
+
<Menu.Content />
|
|
83
|
+
</Menu.Root>
|
|
84
|
+
</AnchorRoot>
|
|
85
|
+
) : (
|
|
86
|
+
<span />
|
|
87
|
+
)}
|
|
88
|
+
</Toolbar.Root>
|
|
89
|
+
);
|
|
90
|
+
},
|
|
91
|
+
);
|
|
93
92
|
|
|
94
93
|
AppBar.displayName = APP_BAR_NAME;
|
|
@@ -12,7 +12,7 @@ import { ErrorFallback, Panel } from '@dxos/react-ui';
|
|
|
12
12
|
import { Menu, useMenuActions } from '@dxos/react-ui-menu';
|
|
13
13
|
|
|
14
14
|
import { useCompanions, useDrawerActions, useSimpleLayoutState } from '../../hooks';
|
|
15
|
-
import {
|
|
15
|
+
import { Loading } from '../Loading';
|
|
16
16
|
|
|
17
17
|
const DRAWER_NAME = 'SimpleLayout.Drawer';
|
|
18
18
|
|
|
@@ -23,7 +23,7 @@ export const Drawer = () => {
|
|
|
23
23
|
const { graph } = useAppGraph();
|
|
24
24
|
const { state: layoutState } = useSimpleLayoutState();
|
|
25
25
|
|
|
26
|
-
const placeholder = useMemo(() => <
|
|
26
|
+
const placeholder = useMemo(() => <Loading />, []);
|
|
27
27
|
|
|
28
28
|
// Get all companions for the current active (primary) item.
|
|
29
29
|
const activeId = layoutState.active ?? layoutState.workspace;
|
|
@@ -49,16 +49,16 @@ export const Drawer = () => {
|
|
|
49
49
|
|
|
50
50
|
// Get drawer actions (tabs + toolbar buttons).
|
|
51
51
|
const { actions, onAction } = useDrawerActions(DRAWER_NAME);
|
|
52
|
-
const
|
|
52
|
+
const menuActions = useMenuActions(actions);
|
|
53
53
|
|
|
54
54
|
return (
|
|
55
55
|
<Panel.Root>
|
|
56
56
|
<Panel.Toolbar>
|
|
57
|
-
<Menu.Root {...
|
|
58
|
-
<Menu.Toolbar
|
|
57
|
+
<Menu.Root {...menuActions} alwaysActive onAction={onAction}>
|
|
58
|
+
<Menu.Toolbar />
|
|
59
59
|
</Menu.Root>
|
|
60
60
|
</Panel.Toolbar>
|
|
61
|
-
<Panel.Content
|
|
61
|
+
<Panel.Content>
|
|
62
62
|
<Surface.Surface role='article' data={data} limit={1} fallback={ErrorFallback} placeholder={placeholder} />
|
|
63
63
|
</Panel.Content>
|
|
64
64
|
</Panel.Root>
|