mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-09 01:45:00 +00:00
* fix(ci): skip unrelated required checks cleanly Matrix jobs had to start no-op runners to preserve interpolated required-check names. Static named jobs let GitHub report genuine skipped conclusions while keeping those names stable. * fix(ci): gate changes to install patching script * fix(ci): route cross-package test contracts narrowly * fix(ci): keep routed contracts merge-blocking * fix(server): exclude nested test utilities from builds * fix(ci): cover app-owned native and browser domains * refactor(ci): align tests with runtime domains Own browser specs, desktop specs, and Electron-only app code by stable directories so PR routing does not depend on filename conventions. Keep desktop integration coverage inside the required desktop check. * fix(ci): keep routing and desktop suites isolated The routing regression must run before dependency installation, and desktop Vitest must not collect Playwright-owned specs. * fix(ci): isolate desktop e2e runtime * perf(test): isolate Playwright workers * perf(test): parallelize Playwright within specs * fix(test): keep Playwright file scheduling * fix(test): share dynamic Metro fixture * fix(test): preserve own-host restart fixtures * fix(ci): preserve routed contracts after rebase * Stabilize Playwright wheel-scroll assertions
212 lines
5.5 KiB
TypeScript
212 lines
5.5 KiB
TypeScript
import {
|
|
type ChordState,
|
|
matchesKeyboardShortcutContext,
|
|
type KeyboardShortcutInput,
|
|
type ParsedShortcutBinding,
|
|
} from "../../keyboard/keyboard-shortcuts";
|
|
import type { KeyCombo } from "../../keyboard/shortcut-string";
|
|
|
|
export interface BrowserShortcutPrefix {
|
|
alt: boolean;
|
|
code: string;
|
|
codeFallback?: true;
|
|
control: boolean;
|
|
editable?: false;
|
|
key?: string;
|
|
meta: boolean;
|
|
repeat?: false;
|
|
shift: boolean;
|
|
shiftedKey?: string;
|
|
}
|
|
|
|
export interface BrowserShortcutInput extends KeyboardShortcutInput {
|
|
browserId: string;
|
|
}
|
|
|
|
export interface BrowserKeyboardPolicy {
|
|
menuPrefixes: BrowserShortcutPrefix[];
|
|
prefixes: BrowserShortcutPrefix[];
|
|
}
|
|
|
|
interface BrowserShortcutPolicyInput {
|
|
bindings: readonly ParsedShortcutBinding[];
|
|
chordState?: ChordState;
|
|
isMac: boolean;
|
|
isDesktop: boolean;
|
|
}
|
|
|
|
export function shouldPublishBrowserShortcutPolicy(input: {
|
|
isBrowserInput: boolean;
|
|
nextChordState: ChordState;
|
|
previousChordState: ChordState;
|
|
}): boolean {
|
|
return (
|
|
input.isBrowserInput || (input.previousChordState.step > 0 && input.nextChordState.step === 0)
|
|
);
|
|
}
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
}
|
|
|
|
export function parseBrowserShortcutInput(value: unknown): BrowserShortcutInput | null {
|
|
if (!isRecord(value)) {
|
|
return null;
|
|
}
|
|
const { browserId, code, key } = value;
|
|
if (typeof browserId !== "string" || browserId.length === 0) {
|
|
return null;
|
|
}
|
|
if (typeof code !== "string" || typeof key !== "string") {
|
|
return null;
|
|
}
|
|
if (
|
|
typeof value.alt !== "boolean" ||
|
|
typeof value.control !== "boolean" ||
|
|
typeof value.meta !== "boolean" ||
|
|
typeof value.shift !== "boolean" ||
|
|
(value.repeat !== undefined && typeof value.repeat !== "boolean")
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
browserId,
|
|
key,
|
|
code,
|
|
altKey: value.alt,
|
|
ctrlKey: value.control,
|
|
metaKey: value.meta,
|
|
shiftKey: value.shift,
|
|
repeat: value.repeat ?? false,
|
|
};
|
|
}
|
|
|
|
function prefixFromCombo(
|
|
combo: KeyCombo,
|
|
isMac: boolean,
|
|
editable: false | undefined,
|
|
): BrowserShortcutPrefix | null {
|
|
const prefix: BrowserShortcutPrefix = {
|
|
alt: combo.alt === true,
|
|
code: combo.code,
|
|
control: combo.ctrl === true || (!isMac && combo.mod === true),
|
|
meta: combo.meta === true || (isMac && combo.mod === true),
|
|
shift: combo.shift === true,
|
|
};
|
|
if (combo.codeFallback === true) {
|
|
prefix.codeFallback = true;
|
|
}
|
|
if (editable === false) {
|
|
prefix.editable = false;
|
|
}
|
|
if (combo.key) {
|
|
prefix.key = combo.key;
|
|
}
|
|
if (combo.repeat === false) {
|
|
prefix.repeat = false;
|
|
}
|
|
if (combo.shiftedKey) {
|
|
prefix.shiftedKey = combo.shiftedKey;
|
|
}
|
|
return prefix.meta || prefix.control || prefix.alt ? prefix : null;
|
|
}
|
|
|
|
function isBrowserNativeNavigationPrefix(prefix: BrowserShortcutPrefix, isMac: boolean): boolean {
|
|
return (
|
|
isMac &&
|
|
prefix.meta &&
|
|
!prefix.control &&
|
|
!prefix.alt &&
|
|
!prefix.shift &&
|
|
(prefix.code === "BracketLeft" || prefix.code === "BracketRight")
|
|
);
|
|
}
|
|
|
|
function canCrossBrowserBoundary(binding: ParsedShortcutBinding, isMac: boolean): boolean {
|
|
return binding.parsedChord.every((combo) => {
|
|
const prefix = prefixFromCombo(combo, isMac, binding.when?.editable);
|
|
return prefix !== null && !isBrowserNativeNavigationPrefix(prefix, isMac);
|
|
});
|
|
}
|
|
|
|
function prefixKey(prefix: BrowserShortcutPrefix): string {
|
|
return [
|
|
prefix.code,
|
|
prefix.key ?? "",
|
|
prefix.shiftedKey ?? "",
|
|
prefix.codeFallback ?? "",
|
|
prefix.editable ?? "",
|
|
prefix.control,
|
|
prefix.meta,
|
|
prefix.alt,
|
|
prefix.shift,
|
|
prefix.repeat ?? "",
|
|
].join(":");
|
|
}
|
|
|
|
function buildBrowserShortcutPrefixes(input: BrowserShortcutPolicyInput): BrowserShortcutPrefix[] {
|
|
const prefixes = new Map<string, BrowserShortcutPrefix>();
|
|
const context = {
|
|
isMac: input.isMac,
|
|
isDesktop: input.isDesktop,
|
|
focusScope: "browser" as const,
|
|
commandCenterOpen: false,
|
|
};
|
|
|
|
const candidates =
|
|
input.chordState && input.chordState.step > 0
|
|
? input.chordState.candidateIndices
|
|
: input.bindings.map((_, index) => index);
|
|
const step = input.chordState?.step ?? 0;
|
|
|
|
for (const index of candidates) {
|
|
const binding = input.bindings[index];
|
|
if (!binding) {
|
|
continue;
|
|
}
|
|
if (!matchesKeyboardShortcutContext(binding.when, context)) {
|
|
continue;
|
|
}
|
|
if (!canCrossBrowserBoundary(binding, input.isMac)) {
|
|
continue;
|
|
}
|
|
const combo = binding.parsedChord[step];
|
|
if (!combo) {
|
|
continue;
|
|
}
|
|
const prefix = prefixFromCombo(combo, input.isMac, binding.when?.editable);
|
|
if (!prefix) {
|
|
continue;
|
|
}
|
|
prefixes.set(prefixKey(prefix), prefix);
|
|
}
|
|
|
|
return [...prefixes.values()];
|
|
}
|
|
|
|
export function buildBrowserKeyboardPolicy(
|
|
input: BrowserShortcutPolicyInput,
|
|
): BrowserKeyboardPolicy {
|
|
const idlePrefixes = buildBrowserShortcutPrefixes({ ...input, chordState: undefined });
|
|
const prefixes =
|
|
input.chordState && input.chordState.step > 0
|
|
? buildBrowserShortcutPrefixes(input)
|
|
: idlePrefixes;
|
|
const menuPrefixes = [...idlePrefixes];
|
|
if (!input.isMac) {
|
|
const closeWindowGuard: BrowserShortcutPrefix = {
|
|
alt: false,
|
|
code: "KeyW",
|
|
control: true,
|
|
key: "w",
|
|
meta: false,
|
|
shift: false,
|
|
};
|
|
if (!menuPrefixes.some((prefix) => prefixKey(prefix) === prefixKey(closeWindowGuard))) {
|
|
menuPrefixes.push(closeWindowGuard);
|
|
}
|
|
}
|
|
return { menuPrefixes, prefixes };
|
|
}
|