mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-13 07:40:59 +00:00
6906f80ff8
The Android shell now reports keyCommands and windowLayout, so the large screen layout no longer assumes an iPad: - The keyboard host hands the shortcuts to the app whenever the shell has the keyCommands feature, whatever the platform. Before it also required iOS, so an Android tablet with a keyboard answered the keys in the page and never listed them in the system's shortcuts. - useWindowFold follows the layout the Android shell reports (feature windowLayout). When a vertical fold separates the window, a foldable half open like a book, MobileShell marks it with data-fold and its place, and the leading pane (the trip's plan, a tab's summary, a list beside its detail) ends at the fold while the trailing one starts there, on either reading direction. A flat fold that does not separate stays one screen. - The split size class starts at 840pt, Android's expanded width, instead of 900pt, so a foldable unfolded on its side (about 841dp) gets two panes like an iPad does. An 11" iPad upright (834pt) stays a single pane. The address screen of the native shell follows the same width. - TrekShell exports NativeWindowLayout for it. Also: the sheet's width and anchor types and the collab board type stay private to their modules, which nothing else imports (knip).
73 lines
2.6 KiB
TypeScript
73 lines
2.6 KiB
TypeScript
import { useEffect, useSyncExternalStore } from 'react';
|
|
|
|
import { hasNativeFeature, useNativeCapabilities } from './capabilities';
|
|
import { reportNativeFailure } from './reportFailure';
|
|
import { TrekShell, type NativeWindowLayout } from './trekShell';
|
|
|
|
/** A fold down the window, in CSS pixels from its left edge: where it begins and ends. */
|
|
export interface WindowFold {
|
|
start: number;
|
|
end: number;
|
|
}
|
|
|
|
/**
|
|
* The fold a two-pane screen should respect: a vertical one that separates the
|
|
* window into two sides, as a foldable half open like a book has, or a device
|
|
* whose two screens meet there. A flat fold that does not separate (a Galaxy
|
|
* Fold opened all the way) is one seamless screen, and a horizontal one runs
|
|
* across both panes rather than between them.
|
|
*/
|
|
export function foldOf(layout: NativeWindowLayout | null): WindowFold | null {
|
|
if (!layout?.hinge || layout.posture === 'none') return null;
|
|
if (layout.orientation !== 'vertical' || !layout.separating) return null;
|
|
const { left, right } = layout.hinge;
|
|
if (!(left > 0 && right >= left && right < layout.width)) return null;
|
|
return { start: left, end: right };
|
|
}
|
|
|
|
let layout: NativeWindowLayout | null = null;
|
|
let listening = false;
|
|
const listeners = new Set<() => void>();
|
|
|
|
function publish(next: NativeWindowLayout): void {
|
|
layout = next;
|
|
for (const listener of listeners) listener();
|
|
}
|
|
|
|
/** Starts following the window layout the Android shell reports (feature windowLayout), once. */
|
|
function follow(): void {
|
|
if (listening || !hasNativeFeature('windowLayout')) return;
|
|
listening = true;
|
|
TrekShell.getWindowLayout().then(publish, reportNativeFailure);
|
|
TrekShell.addListener('windowLayout', publish).catch(reportNativeFailure);
|
|
}
|
|
|
|
function subscribe(listener: () => void): () => void {
|
|
listeners.add(listener);
|
|
return () => listeners.delete(listener);
|
|
}
|
|
|
|
const current = () => layout;
|
|
|
|
/**
|
|
* The fold of the window, while the app's shell reports one (Android, feature
|
|
* windowLayout); null in a browser, on iOS and on a device without one. Two-pane
|
|
* screens end their leading pane at it, so no row runs across the hinge.
|
|
*/
|
|
export function useWindowFold(): WindowFold | null {
|
|
const { caps } = useNativeCapabilities();
|
|
const supported = caps.features.includes('windowLayout');
|
|
useEffect(() => {
|
|
if (supported) follow();
|
|
}, [supported]);
|
|
const reported = useSyncExternalStore(subscribe, current, current);
|
|
return supported ? foldOf(reported) : null;
|
|
}
|
|
|
|
/** Test helper: forget the layout and the subscription. */
|
|
export function _resetWindowFold(): void {
|
|
layout = null;
|
|
listening = false;
|
|
listeners.clear();
|
|
}
|