Files
outline/app/hooks/useKeyDown.ts
Apoorv Mishra 6b286d82b8 Ability to choose publish location for a document (#4582)
* feat: initial base structure

* feat: utils for constructing and flattening collection tree

* feat: basic demo to display tree-like structure with virtualization

* feat: make it searchable

* feat: row component

* fix: handle row selection

* fix: scroll jitter

* fix: popover max-height to eliminate extra scroll

* fix: position scrollbar correctly

* fix: do not sort to maintain correct tree-like view

* feat: footer

* fix: scroll to selected item

* fix: deselect item

* fix: display selected location in footer

* fix: deselect item if any upon search trigger

* fix: create draft without collection

* fix: pass down collectionId to all the nodes

* feat: publish document under selected location

* fix: move the doc post publish in case it is supposed to be a nested doc

* fix: wrap text for selected location

* fix: footer background in dark mode and unused css

* fix: popover height in small devices

* fix: no need to spread

* refactor: remove outline

* refactor: border-radius is common

* refactor: remove active and focus

* fix: do not shrink spacer

* fix: scroll list padding with correctly adjusted scrolling

* refactor: use constants

* fix: use padding in favor of spacer

* refactor: border attrs not needed

* refactor: control title padding and icon size centrally

* fix: rename param

* fix: import path

* fix: refactor styles, avoid magic numbers

* fix: type err

* feat: make rows collapsible

* fix: fully expanded without disclosure upon search

* fix: use modal in place of popover

* fix: collapse descendants

* fix: rename PublishPopover to PublishModal

* fix: adjust collapse icon as part of tree view

* fix: enable keyboard navigation

* not sure why collapse and expand shortcuts are not working

* fix: row expansion and search box focus and blur

* fix: remove css hover, handle it via active prop

* fix: discard tree like view for search results

* fix: minor tweaks

* refactor: no need to pass onPublish

* refactor: remove unnecessary attrs from search component

* fix: publish button text

* fix: reset intial scroll offset to 0 on search

* fix: remove search highlights

* fix: clean up search component

* refactor: search and row collapse

* refactor: PublishLocation

* fix: show emoji or star icon if present

* fix: shift focus only from top item

* fix: leading emoji

* fix: baseline text

* fix: make path tertiary

* fix: do not show path for collections

* fix: path text color upon selection

* fix: deleted collection case

* fix: no results found

* fix: space around slash

* Refinement, some small refactors

* fix: Publish shortcut, use Button action

* Allow new document creation from command menu without active collection

* fix: duplicate

* fix: Unneccessary truncation

* fix: Scroll on expand/collapse
Remove wraparound

* fix: tsc

* fix: Horizontal overflow on PublishLocation
Remove pointless moveTo method

* fix: Missing translation

* Remove method indirection
Show expanded collection icon in tree when expanded

* Shrink font size a point

* Remove feature flag

* fix: Path color contrast in light mode
Remove unused expanded/show attributes

* shrink -> collapse, fix expanded disclosure without items after searching

* Mobile styles

* fix: scroll just into view

Co-authored-by: Tom Moor <tom.moor@gmail.com>
2023-01-23 17:38:24 -08:00

91 lines
2.1 KiB
TypeScript

import * as React from "react";
import isTextInput from "~/utils/isTextInput";
import { isModKey } from "~/utils/keyboard";
type Callback = (event: KeyboardEvent) => void;
export type KeyFilter = ((event: KeyboardEvent) => boolean) | string;
export type Options = {
allowInInput?: boolean;
};
type RegisteredCallback = {
callback: Callback;
options?: Options;
};
// Registered keyboard event callbacks
let callbacks: RegisteredCallback[] = [];
// Track if IME input suggestions are open so we can ignore keydown shortcuts
// in this case, they should never be triggered from mobile keyboards.
let imeOpen = false;
// Based on implementation in react-use
// https://github.com/streamich/react-use/blob/master/src/useKey.ts#L15-L22
const createKeyPredicate = (keyFilter: KeyFilter) =>
typeof keyFilter === "function"
? keyFilter
: typeof keyFilter === "string"
? (event: KeyboardEvent) =>
event.key === keyFilter ||
event.code === `Key${keyFilter.toUpperCase()}`
: keyFilter
? (_event: KeyboardEvent) => true
: (_event: KeyboardEvent) => false;
export default function useKeyDown(
key: KeyFilter,
fn: Callback,
options?: Options
): void {
const predicate = createKeyPredicate(key);
React.useEffect(() => {
const handler = (event: KeyboardEvent) => {
if (predicate(event)) {
fn(event);
}
};
callbacks.push({
callback: handler,
options,
});
return () => {
callbacks = callbacks.filter((cb) => cb.callback !== handler);
};
}, [fn, predicate, options]);
}
window.addEventListener("keydown", (event) => {
if (imeOpen) {
return;
}
// reverse so that the last registered callbacks get executed first
for (const registered of callbacks.reverse()) {
if (event.defaultPrevented === true) {
break;
}
if (
!isTextInput(event.target as HTMLElement) ||
registered.options?.allowInInput ||
isModKey(event)
) {
registered.callback(event);
}
}
});
window.addEventListener("compositionstart", () => {
imeOpen = true;
});
window.addEventListener("compositionend", () => {
imeOpen = false;
});