@franzzemen/brokenstock-shell 0.4.1 → 0.4.2
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.
|
@@ -1865,6 +1865,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
|
|
|
1865
1865
|
args: [{ providedIn: 'root' }]
|
|
1866
1866
|
}], ctorParameters: () => [] });
|
|
1867
1867
|
|
|
1868
|
+
/**
|
|
1869
|
+
* Elements that mean "the user is typing", where an unmodified shortcut must not fire.
|
|
1870
|
+
*
|
|
1871
|
+
* Kept as a set of tag names plus a `contenteditable` check rather than a
|
|
1872
|
+
* selector string, so it is greppable and so the reasoning stays attached to it.
|
|
1873
|
+
*/
|
|
1874
|
+
const TEXT_ENTRY_TAGS = new Set(['INPUT', 'TEXTAREA', 'SELECT']);
|
|
1868
1875
|
class KeyboardShortcutService {
|
|
1869
1876
|
#groups;
|
|
1870
1877
|
#activeGroupId;
|
|
@@ -1880,6 +1887,18 @@ class KeyboardShortcutService {
|
|
|
1880
1887
|
this.#focusService = inject(TerminalFocusService);
|
|
1881
1888
|
this.helpVisible = signal(false, /* @ts-ignore */
|
|
1882
1889
|
...(ngDevMode ? [{ debugName: "helpVisible" }] : /* istanbul ignore next */ []));
|
|
1890
|
+
/**
|
|
1891
|
+
* True while unmodified shortcuts are being suppressed — the user is typing,
|
|
1892
|
+
* or a dialog is open.
|
|
1893
|
+
*
|
|
1894
|
+
* Exposed because a screen binding BARE keys needs to tell the user whether the
|
|
1895
|
+
* next keystroke acts or types, and that indicator must read the service's own
|
|
1896
|
+
* dispatch state. A screen keeping its own boolean will eventually disagree
|
|
1897
|
+
* with the service, and an indicator that lies about whether a keystroke sells
|
|
1898
|
+
* a thousand shares is worse than no indicator at all.
|
|
1899
|
+
*/
|
|
1900
|
+
this.suppressed = signal(false, /* @ts-ignore */
|
|
1901
|
+
...(ngDevMode ? [{ debugName: "suppressed" }] : /* istanbul ignore next */ []));
|
|
1883
1902
|
this.activeShortcuts = computed(() => {
|
|
1884
1903
|
const groupId = this.#activeGroupId();
|
|
1885
1904
|
if (!groupId)
|
|
@@ -1894,11 +1913,19 @@ class KeyboardShortcutService {
|
|
|
1894
1913
|
this.#document.addEventListener('keydown', (event) => {
|
|
1895
1914
|
this.#handleKeydown(event);
|
|
1896
1915
|
});
|
|
1916
|
+
// Focus moves are what change `suppressed`, and they are frequent — kept
|
|
1917
|
+
// out of the zone, with the signal written directly. A signal write marks
|
|
1918
|
+
// its consumers dirty on its own; it does not need a change-detection pass
|
|
1919
|
+
// of its own to be scheduled here.
|
|
1920
|
+
const refresh = () => this.suppressed.set(this.#isSuppressed());
|
|
1921
|
+
this.#document.addEventListener('focusin', refresh, true);
|
|
1922
|
+
this.#document.addEventListener('focusout', refresh, true);
|
|
1897
1923
|
});
|
|
1898
1924
|
}
|
|
1899
1925
|
registerGroup(groupId, shortcuts) {
|
|
1900
1926
|
this.#groups.set(groupId, shortcuts);
|
|
1901
1927
|
this.#activeGroupId.set(groupId);
|
|
1928
|
+
this.suppressed.set(this.#isSuppressed());
|
|
1902
1929
|
}
|
|
1903
1930
|
unregisterGroup(groupId) {
|
|
1904
1931
|
this.#groups.delete(groupId);
|
|
@@ -1910,17 +1937,61 @@ class KeyboardShortcutService {
|
|
|
1910
1937
|
toggleHelp() {
|
|
1911
1938
|
this.helpVisible.update(v => !v);
|
|
1912
1939
|
}
|
|
1940
|
+
/**
|
|
1941
|
+
* Would an unmodified shortcut be ignored right now?
|
|
1942
|
+
*
|
|
1943
|
+
* Two cases: a text-entry element has focus, or a dialog is open. Alt
|
|
1944
|
+
* combinations are exempt — they do not collide with typing, which is why
|
|
1945
|
+
* every binding in the app before this guard existed used one.
|
|
1946
|
+
*/
|
|
1947
|
+
#isSuppressed() {
|
|
1948
|
+
const active = this.#document.activeElement;
|
|
1949
|
+
if (active) {
|
|
1950
|
+
if (TEXT_ENTRY_TAGS.has(active.tagName))
|
|
1951
|
+
return true;
|
|
1952
|
+
if (active.isContentEditable)
|
|
1953
|
+
return true;
|
|
1954
|
+
}
|
|
1955
|
+
// Angular Material puts every dialog in this container. A shortcut firing
|
|
1956
|
+
// behind an open dialog acts on a screen the user cannot currently see.
|
|
1957
|
+
return this.#document.querySelector('.cdk-overlay-container .cdk-overlay-pane') !== null;
|
|
1958
|
+
}
|
|
1959
|
+
#matches(shortcut, event) {
|
|
1960
|
+
// The physical key when `code` is given, the produced character otherwise.
|
|
1961
|
+
const hit = shortcut.code ? shortcut.code === event.code : shortcut.key === event.key;
|
|
1962
|
+
if (!hit)
|
|
1963
|
+
return false;
|
|
1964
|
+
if (shortcut.modifier === 'Alt')
|
|
1965
|
+
return event.altKey;
|
|
1966
|
+
if (shortcut.modifier === 'Shift') {
|
|
1967
|
+
return event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey;
|
|
1968
|
+
}
|
|
1969
|
+
// Unmodified. `ctrl`/`alt`/`meta` are excluded because the page still
|
|
1970
|
+
// receives a keystroke carrying the digit when the browser handles
|
|
1971
|
+
// `ctrl`+1 as a tab switch — without this, switching tabs would also fire
|
|
1972
|
+
// whatever a bare `1` is bound to.
|
|
1973
|
+
return !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey;
|
|
1974
|
+
}
|
|
1913
1975
|
#handleKeydown(event) {
|
|
1914
1976
|
const activeId = this.#activeGroupId();
|
|
1915
1977
|
if (activeId) {
|
|
1916
1978
|
const shortcuts = this.#groups.get(activeId);
|
|
1917
1979
|
if (shortcuts) {
|
|
1918
|
-
const match = shortcuts.find(s =>
|
|
1919
|
-
if (s.modifier === 'Alt')
|
|
1920
|
-
return event.altKey && s.key === event.key;
|
|
1921
|
-
return !event.altKey && !event.ctrlKey && !event.metaKey && s.key === event.key;
|
|
1922
|
-
});
|
|
1980
|
+
const match = shortcuts.find(s => this.#matches(s, event));
|
|
1923
1981
|
if (match) {
|
|
1982
|
+
const alt = match.modifier === 'Alt';
|
|
1983
|
+
// Alt bindings are exempt from both guards below, so nothing that
|
|
1984
|
+
// existed before them changes behaviour: Alt does not collide with
|
|
1985
|
+
// typing, and holding Alt+ArrowDown to walk a list is wanted.
|
|
1986
|
+
if (!alt) {
|
|
1987
|
+
// A key held down repeats at roughly thirty a second. On a screen
|
|
1988
|
+
// where a bare key places an order, that is fifteen orders in half a
|
|
1989
|
+
// second, and a synchronous action path will run every one of them.
|
|
1990
|
+
if (event.repeat)
|
|
1991
|
+
return false;
|
|
1992
|
+
if (this.#isSuppressed())
|
|
1993
|
+
return false;
|
|
1994
|
+
}
|
|
1924
1995
|
event.preventDefault();
|
|
1925
1996
|
this.#zone.run(() => match.action());
|
|
1926
1997
|
return true;
|