From 9d267f3c3bd11924f8579f40e908a141c2014c36 Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Sat, 8 Jul 2023 10:11:08 -0600 Subject: [PATCH 01/25] Add keyboard manager types --- packages/core/src/Types.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/core/src/Types.ts b/packages/core/src/Types.ts index 5141153c..7135ff68 100644 --- a/packages/core/src/Types.ts +++ b/packages/core/src/Types.ts @@ -64,3 +64,16 @@ export interface OlliFieldDef { * Interface describing how a visualization adapter should be created */ export type VisAdapter = (spec: T) => Promise; + +export type KeyboardAction = { + action: () => void; + title?: string; + keyDescription?: string; + description?: string; + force?: boolean; + caseSensitive?: boolean; +} + +export type KeyRegistration = { + key: string; +} & KeyboardAction; From fc97227b43844fbbab8bbda5981229f3f2a2ba75 Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Sat, 8 Jul 2023 10:11:24 -0600 Subject: [PATCH 02/25] Create KeyboardManager class --- packages/core/src/Runtime/KeyboardManager.ts | 129 +++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 packages/core/src/Runtime/KeyboardManager.ts diff --git a/packages/core/src/Runtime/KeyboardManager.ts b/packages/core/src/Runtime/KeyboardManager.ts new file mode 100644 index 00000000..34d1f322 --- /dev/null +++ b/packages/core/src/Runtime/KeyboardManager.ts @@ -0,0 +1,129 @@ +import type { KeyboardAction, KeyRegistration } from "../Types"; + +export const keyboardEventToString = (e: KeyboardEvent) => { + return `${e.altKey ? "Alt+" : ""}${e.ctrlKey ? "Ctrl+" : ""}${ + e.shiftKey ? "Shift+" : "" + }${e.key}`; +}; + +/** + * The KeyboardManager handles adding new keyboard events and controls, + * and displaying a help documentation modal + */ +export class KeyboardManager { + private actions: { + [event: string]: KeyboardAction; + }; + private target: HTMLElement; + private helpModal: HTMLDialogElement | null; + + constructor(target: HTMLElement) { + this.target = target; + this.target.addEventListener('keydown', (event) => { + this.handleEvents(event); + }); + if (!this.target.hasAttribute("tabIndex")) { + this.target.setAttribute("tabIndex", "0"); + } + this.helpModal = null; + } + + private handleEvents(e: KeyboardEvent): void { + const keyPress = keyboardEventToString(e) + if (keyPress in this.actions) { + this.actions[keyPress].action(); + e.preventDefault(); + } else if (keyPress.toUpperCase() in this.actions) { + this.actions[keyPress.toUpperCase()].action(); + e.stopPropagation(); + e.preventDefault(); + } + } + + public addAction({ + key, + action, + caseSensitive, + description, + force, + keyDescription, + title, + }: KeyRegistration): void { + const checkKey = caseSensitive ? key : key.toUpperCase(); + if (!force && checkKey in this.actions) { + return; + } + this.actions[checkKey] = { + title, + description, + action, + keyDescription + }; + } + + public addActions(keyList: KeyRegistration[]): void { + keyList.forEach((key: KeyRegistration) => { + this.addAction(key); + }) + } + + /** + * Build a help dialog + */ + public generateHelpDialog() { + const dialog = document.createElement("dialog"); + + const closeButton = document.createElement("button"); + closeButton.textContent = "X"; + closeButton.ariaLabel = "Close"; + closeButton.style.position = "absolute"; + closeButton.style.top = "10px"; + closeButton.style.right = "10px"; + closeButton.addEventListener("click", () => { + dialog.close(); + }); + dialog.appendChild(closeButton); + + const heading = "Keyboard Manager"; + const h1 = document.createElement("h1"); + h1.textContent = heading; + dialog.setAttribute("aria-live", heading); + dialog.appendChild(h1); + + const table = document.createElement("table"); + const tbody = document.createElement("tbody"); + Object.entries(this.actions).forEach(([keystroke, details]) => { + const tr = document.createElement("tr"); + const th = document.createElement("th"); + th.scope = "row"; + th.textContent = details.title; + tr.appendChild(th); + + const td1 = document.createElement("td"); + td1.textContent = details.keyDescription ?? keystroke; + tr.appendChild(td1); + + const td2 = document.createElement("td"); + td2.textContent = details.description; + tr.appendChild(td2); + + tbody.appendChild(tr); + }); + table.appendChild(tbody); + + dialog.appendChild(table); + return dialog; + } + + /** + * Launch help dialog + */ + public launchHelpDialog() { + if (this.helpModal === null) { + this.helpModal = this.generateHelpDialog(); + document.body.appendChild(this.helpModal); + } + this.helpModal.showModal(); + this.helpModal.focus(); + } +} \ No newline at end of file From 07e4e2796acb8da7a37ab54cfc88750a7d539b3d Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Sat, 8 Jul 2023 10:12:14 -0600 Subject: [PATCH 03/25] Use KeyboardManager instead of handleKeyEvent --- .../core/src/Runtime/OlliRuntimeTreeItem.ts | 121 +++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/packages/core/src/Runtime/OlliRuntimeTreeItem.ts b/packages/core/src/Runtime/OlliRuntimeTreeItem.ts index acb048a3..a773a17f 100644 --- a/packages/core/src/Runtime/OlliRuntimeTreeItem.ts +++ b/packages/core/src/Runtime/OlliRuntimeTreeItem.ts @@ -10,6 +10,7 @@ import { openSelectionDialog, openTableDialog } from '../Render/Dialog'; import { ElaboratedOlliNode } from '../Structure/Types'; +import { KeyboardManager } from './KeyboardManager'; import { OlliRuntime } from './OlliRuntime'; /* @@ -29,6 +30,7 @@ export class OlliRuntimeTreeItem { olliNode: ElaboratedOlliNode; isExpandable: boolean; inGroup: boolean; + keyboardManager: KeyboardManager; parent?: OlliRuntimeTreeItem; children: OlliRuntimeTreeItem[]; @@ -65,7 +67,124 @@ export class OlliRuntimeTreeItem { init() { this.domNode.tabIndex = -1; - this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); + // this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); + this.keyboardManager = new KeyboardManager(this.domNode); + this.keyboardManager.addActions([ + { + key: 'Enter', + title: 'Expand and collapse the current layer of the tree', + action: () => { + if (this.isExpandable) { + if (this.isExpanded()) { + this.tree.collapseTreeItem(this); + } else { + this.tree.expandTreeItem(this); + } + } + } + }, + { + key: ' ', + title: 'Expand and collapse the current layer of the tree', + action: () => { + if (this.isExpandable) { + if (this.isExpanded()) { + this.tree.collapseTreeItem(this); + } else { + this.tree.expandTreeItem(this); + } + } + } + }, + { + key: 'ArrowDown', + title: 'Focus on the next layer of the tree', + action: () => { + if (this.children.length > 0 && this.isExpandable) { + this.tree.setFocusToNextLayer(this); + } + } + }, + { + key: 'ArrowUp', + title: 'Focus on the previous layer of the tree', + action: () => { + if (this.inGroup) { + this.tree.setFocusToParentItem(this); + } + } + }, + { + key: 'Escape', + title: 'Focus on the previous layer of the tree', + action: () => { + if (this.inGroup) { + this.tree.setFocusToParentItem(this); + } + } + }, + { + key: 'ArrowLeft', + title: 'Focus on the previous child element of the tree', + action: () => { + this.tree.setFocusToPreviousItem(this); + } + }, + { + key: 'ArrowRight', + title: 'Focus on the next child element of the tree', + action: () => { + this.tree.setFocusToNextItem(this); + } + }, + { + key: 'Home', + title: 'Focus top of the tree', + action: () => { + if (this.parent) { + this.tree.setFocusToFirstInLayer(this); + } + } + }, + { + key: 'x', + title: 'Navigate to the x-axis of the grapg', + action: () => { + this.tree.focusOnNodeType('xAxis', this); + } + }, + { + key: 'y', + title: 'Navigate to the y-axis of the graph', + action: () => { + this.tree.focusOnNodeType('yAxis', this); + } + }, + { + key: 'l', + title: 'Navigate to the legend of the graph', + action: () => { + this.tree.focusOnNodeType('legend', this); + } + }, + { + key: 't', + title: 'Open table dialog', + action: () => { + if ('predicate' in this.olliNode || this.olliNode.nodeType === 'root') { + openTableDialog(this.olliNode, this.tree); + } + } + }, + { + key: 'f', + title: 'Open selection dialog', + action: () => { + openSelectionDialog(this.tree); + } + }, + ]) + this.domNode.addEventListener('click', this.handleClick.bind(this)); this.domNode.addEventListener('focus', this.handleFocus.bind(this)); this.domNode.addEventListener('blur', this.handleBlur.bind(this)); From 625bbf763099d0fe8656e325eabaee21f1e40e1f Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Sat, 8 Jul 2023 10:26:28 -0600 Subject: [PATCH 04/25] Add key action to show help modal --- packages/core/src/Runtime/OlliRuntimeTreeItem.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/core/src/Runtime/OlliRuntimeTreeItem.ts b/packages/core/src/Runtime/OlliRuntimeTreeItem.ts index a773a17f..bea4b901 100644 --- a/packages/core/src/Runtime/OlliRuntimeTreeItem.ts +++ b/packages/core/src/Runtime/OlliRuntimeTreeItem.ts @@ -70,6 +70,13 @@ export class OlliRuntimeTreeItem { // this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); this.keyboardManager = new KeyboardManager(this.domNode); this.keyboardManager.addActions([ + { + key: 'h', + title: 'Display help documentation modal', + action: () => { + this.keyboardManager.launchHelpDialog(); + } + }, { key: 'Enter', title: 'Expand and collapse the current layer of the tree', From f8414ef248f9151c1207968381390dab4dc484d7 Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Mon, 10 Jul 2023 01:06:51 -0600 Subject: [PATCH 05/25] Add key description for spacebar action --- packages/core/src/Runtime/OlliRuntimeTreeItem.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/Runtime/OlliRuntimeTreeItem.ts b/packages/core/src/Runtime/OlliRuntimeTreeItem.ts index bea4b901..7765decf 100644 --- a/packages/core/src/Runtime/OlliRuntimeTreeItem.ts +++ b/packages/core/src/Runtime/OlliRuntimeTreeItem.ts @@ -92,6 +92,7 @@ export class OlliRuntimeTreeItem { }, { key: ' ', + keyDescription: 'Space', title: 'Expand and collapse the current layer of the tree', action: () => { if (this.isExpandable) { From 18e4e33a4de04532fe390834d57b62c1b46f038b Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Mon, 10 Jul 2023 01:07:20 -0600 Subject: [PATCH 06/25] Set initial actions value in KeyboardManager --- packages/core/src/Runtime/KeyboardManager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/Runtime/KeyboardManager.ts b/packages/core/src/Runtime/KeyboardManager.ts index 34d1f322..5a182d65 100644 --- a/packages/core/src/Runtime/KeyboardManager.ts +++ b/packages/core/src/Runtime/KeyboardManager.ts @@ -26,6 +26,7 @@ export class KeyboardManager { this.target.setAttribute("tabIndex", "0"); } this.helpModal = null; + this.actions = {}; } private handleEvents(e: KeyboardEvent): void { From 5b3ab6b7346ea265817e6c1fb303c69cbc1b378e Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Mon, 10 Jul 2023 01:07:33 -0600 Subject: [PATCH 07/25] Rename help dialog modal --- packages/core/src/Runtime/KeyboardManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/Runtime/KeyboardManager.ts b/packages/core/src/Runtime/KeyboardManager.ts index 5a182d65..e8598bc0 100644 --- a/packages/core/src/Runtime/KeyboardManager.ts +++ b/packages/core/src/Runtime/KeyboardManager.ts @@ -85,7 +85,7 @@ export class KeyboardManager { }); dialog.appendChild(closeButton); - const heading = "Keyboard Manager"; + const heading = "Olli Help Menu"; const h1 = document.createElement("h1"); h1.textContent = heading; dialog.setAttribute("aria-live", heading); From 4a33b6dceaf33fefac9a593c091df77884cc47fa Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Mon, 10 Jul 2023 01:08:12 -0600 Subject: [PATCH 08/25] Add element to open help under initial tree item --- packages/core/src/Runtime/OlliRuntime.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/core/src/Runtime/OlliRuntime.ts b/packages/core/src/Runtime/OlliRuntime.ts index ae0eef6f..d4aa5bc5 100644 --- a/packages/core/src/Runtime/OlliRuntime.ts +++ b/packages/core/src/Runtime/OlliRuntime.ts @@ -80,6 +80,11 @@ export class OlliRuntime { this.rootTreeItem = this.treeItems[0]; this.rootTreeItem.domNode.tabIndex = 0; + + const helpComment = "Press 'h' to open the Olli Help Menu for a list of controls."; + const helpElement = document.createElement("em"); + helpElement.textContent = helpComment; + this.rootDomNode.appendChild(helpElement); } renderTreeDescription() { From 11b6e12b2f2d0fa034c555cf32348a37e6e044df Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Mon, 10 Jul 2023 01:14:39 -0600 Subject: [PATCH 09/25] Remove dead code --- packages/core/src/Runtime/OlliRuntimeTreeItem.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/Runtime/OlliRuntimeTreeItem.ts b/packages/core/src/Runtime/OlliRuntimeTreeItem.ts index 7765decf..5c7f7672 100644 --- a/packages/core/src/Runtime/OlliRuntimeTreeItem.ts +++ b/packages/core/src/Runtime/OlliRuntimeTreeItem.ts @@ -67,7 +67,6 @@ export class OlliRuntimeTreeItem { init() { this.domNode.tabIndex = -1; - // this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); this.keyboardManager = new KeyboardManager(this.domNode); this.keyboardManager.addActions([ { From ef24d257786910e3988881ea4290fa4d36e01f16 Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Mon, 10 Jul 2023 01:23:40 -0600 Subject: [PATCH 10/25] Right align control descriptions --- packages/core/src/Runtime/KeyboardManager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/Runtime/KeyboardManager.ts b/packages/core/src/Runtime/KeyboardManager.ts index e8598bc0..f413719f 100644 --- a/packages/core/src/Runtime/KeyboardManager.ts +++ b/packages/core/src/Runtime/KeyboardManager.ts @@ -96,6 +96,7 @@ export class KeyboardManager { Object.entries(this.actions).forEach(([keystroke, details]) => { const tr = document.createElement("tr"); const th = document.createElement("th"); + th.style.textAlign = 'left'; th.scope = "row"; th.textContent = details.title; tr.appendChild(th); From fee21bf26fcc0e6060a107b998c620e346d6f5c1 Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Thu, 13 Jul 2023 18:56:03 -0600 Subject: [PATCH 11/25] Remove help modal HTML element --- packages/core/src/Runtime/OlliRuntime.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/core/src/Runtime/OlliRuntime.ts b/packages/core/src/Runtime/OlliRuntime.ts index 09bbf0fc..6f6558fd 100644 --- a/packages/core/src/Runtime/OlliRuntime.ts +++ b/packages/core/src/Runtime/OlliRuntime.ts @@ -78,11 +78,6 @@ export class OlliRuntime { this.rootTreeItem = this.treeItems[0]; this.rootTreeItem.domNode.tabIndex = 0; - - const helpComment = "Press 'h' to open the Olli Help Menu for a list of controls."; - const helpElement = document.createElement("em"); - helpElement.textContent = helpComment; - this.rootDomNode.appendChild(helpElement); } renderTreeDescription() { From db310c1d998ae55afb90255843cc5592ce8d45ed Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Thu, 13 Jul 2023 19:00:51 -0600 Subject: [PATCH 12/25] Move KeyboardManager types to class file --- packages/core/src/Runtime/KeyboardManager.ts | 13 ++++++++++++- packages/core/src/Types.ts | 13 ------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/core/src/Runtime/KeyboardManager.ts b/packages/core/src/Runtime/KeyboardManager.ts index f413719f..2b3574e6 100644 --- a/packages/core/src/Runtime/KeyboardManager.ts +++ b/packages/core/src/Runtime/KeyboardManager.ts @@ -1,4 +1,15 @@ -import type { KeyboardAction, KeyRegistration } from "../Types"; +export type KeyboardAction = { + action: () => void; + title?: string; + keyDescription?: string; + description?: string; + force?: boolean; + caseSensitive?: boolean; +} + +export type KeyRegistration = { + key: string; +} & KeyboardAction; export const keyboardEventToString = (e: KeyboardEvent) => { return `${e.altKey ? "Alt+" : ""}${e.ctrlKey ? "Ctrl+" : ""}${ diff --git a/packages/core/src/Types.ts b/packages/core/src/Types.ts index 7135ff68..5141153c 100644 --- a/packages/core/src/Types.ts +++ b/packages/core/src/Types.ts @@ -64,16 +64,3 @@ export interface OlliFieldDef { * Interface describing how a visualization adapter should be created */ export type VisAdapter = (spec: T) => Promise; - -export type KeyboardAction = { - action: () => void; - title?: string; - keyDescription?: string; - description?: string; - force?: boolean; - caseSensitive?: boolean; -} - -export type KeyRegistration = { - key: string; -} & KeyboardAction; From bace8f76f888905067e6c90a19ed2e0fc867827c Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Sat, 15 Jul 2023 07:39:13 -0600 Subject: [PATCH 13/25] Initialize KeyboardManager in KeyboardManager.ts --- packages/core/src/Runtime/KeyboardManager.ts | 137 ++++++++++++++++++- 1 file changed, 136 insertions(+), 1 deletion(-) diff --git a/packages/core/src/Runtime/KeyboardManager.ts b/packages/core/src/Runtime/KeyboardManager.ts index 2b3574e6..3a2b4970 100644 --- a/packages/core/src/Runtime/KeyboardManager.ts +++ b/packages/core/src/Runtime/KeyboardManager.ts @@ -1,3 +1,7 @@ +import { openSelectionDialog, openTableDialog } from "../Render/Dialog"; +import { OlliRuntime } from "./OlliRuntime"; +import { OlliRuntimeTreeItem } from "./OlliRuntimeTreeItem"; + export type KeyboardAction = { action: () => void; title?: string; @@ -139,4 +143,135 @@ export class KeyboardManager { this.helpModal.showModal(); this.helpModal.focus(); } -} \ No newline at end of file +} + +export const initKeyboardManager = (olliInstance: OlliRuntime) => { + + const kb = new KeyboardManager(olliInstance.rootDomNode); + const lastFocusedTreeItem: OlliRuntimeTreeItem = olliInstance.lastFocusedTreeItem; + kb.addActions([ + { + key: 'h', + title: 'Display help documentation modal', + action: () => { + kb.launchHelpDialog(); + } + }, + { + key: 'Enter', + title: 'Expand and collapse the current layer of the tree', + action: () => { + if (lastFocusedTreeItem.isExpandable) { + if (lastFocusedTreeItem.isExpanded()) { + olliInstance.collapseTreeItem(lastFocusedTreeItem); + } else { + olliInstance.expandTreeItem(lastFocusedTreeItem); + } + } + } + }, + { + key: ' ', + keyDescription: 'Space', + title: 'Expand and collapse the current layer of the tree', + action: () => { + if (olliInstance.lastFocusedTreeItem.isExpandable) { + if (olliInstance.lastFocusedTreeItem.isExpanded()) { + olliInstance.collapseTreeItem(lastFocusedTreeItem); + } else { + olliInstance.expandTreeItem(lastFocusedTreeItem); + } + } + } + }, + { + key: 'ArrowDown', + title: 'Focus on the next layer of the tree', + action: () => { + if (lastFocusedTreeItem.children.length > 0 && lastFocusedTreeItem.isExpandable) { + olliInstance.setFocusToNextLayer(lastFocusedTreeItem); + } + } + }, + { + key: 'ArrowUp', + title: 'Focus on the previous layer of the tree', + action: () => { + if (lastFocusedTreeItem.inGroup) { + olliInstance.setFocusToParentItem(lastFocusedTreeItem); + } + } + }, + { + key: 'Escape', + title: 'Focus on the previous layer of the tree', + action: () => { + if (lastFocusedTreeItem.inGroup) { + olliInstance.setFocusToParentItem(lastFocusedTreeItem); + } + } + }, + { + key: 'ArrowLeft', + title: 'Focus on the previous child element of the tree', + action: () => { + olliInstance.setFocusToPreviousItem(lastFocusedTreeItem); + } + }, + { + key: 'ArrowRight', + title: 'Focus on the next child element of the tree', + action: () => { + olliInstance.setFocusToNextItem(lastFocusedTreeItem); + } + }, + { + key: 'Home', + title: 'Focus top of the tree', + action: () => { + if (lastFocusedTreeItem.parent) { + olliInstance.setFocusToFirstInLayer(lastFocusedTreeItem); + } + } + }, + { + key: 'x', + title: 'Navigate to the x-axis of the grapg', + action: () => { + olliInstance.focusOnNodeType('xAxis', this); + } + }, + { + key: 'y', + title: 'Navigate to the y-axis of the graph', + action: () => { + olliInstance.focusOnNodeType('yAxis', this); + } + }, + { + key: 'l', + title: 'Navigate to the legend of the graph', + action: () => { + olliInstance.focusOnNodeType('legend', this); + } + }, + { + key: 't', + title: 'Open table dialog', + action: () => { + if ('predicate' in lastFocusedTreeItem.olliNode || lastFocusedTreeItem.olliNode.nodeType === 'root') { + openTableDialog(lastFocusedTreeItem.olliNode, olliInstance); + } + } + }, + { + key: 'f', + title: 'Open selection dialog', + action: () => { + openSelectionDialog(olliInstance); + } + }, + ]) + + return kb; +} From 9a0e542ab61e9235a44840a498ac53daaeafbe4a Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Sat, 15 Jul 2023 07:40:12 -0600 Subject: [PATCH 14/25] Delete KeyboardManager from OlliRuntimeTreeItem.ts --- .../core/src/Runtime/OlliRuntimeTreeItem.ts | 125 ------------------ 1 file changed, 125 deletions(-) diff --git a/packages/core/src/Runtime/OlliRuntimeTreeItem.ts b/packages/core/src/Runtime/OlliRuntimeTreeItem.ts index b2dfec7c..ee1f05bc 100644 --- a/packages/core/src/Runtime/OlliRuntimeTreeItem.ts +++ b/packages/core/src/Runtime/OlliRuntimeTreeItem.ts @@ -67,131 +67,6 @@ export class OlliRuntimeTreeItem { init() { this.domNode.tabIndex = -1; - this.keyboardManager = new KeyboardManager(this.domNode); - this.keyboardManager.addActions([ - { - key: 'h', - title: 'Display help documentation modal', - action: () => { - this.keyboardManager.launchHelpDialog(); - } - }, - { - key: 'Enter', - title: 'Expand and collapse the current layer of the tree', - action: () => { - if (this.isExpandable) { - if (this.isExpanded()) { - this.tree.collapseTreeItem(this); - } else { - this.tree.expandTreeItem(this); - } - } - } - }, - { - key: ' ', - keyDescription: 'Space', - title: 'Expand and collapse the current layer of the tree', - action: () => { - if (this.isExpandable) { - if (this.isExpanded()) { - this.tree.collapseTreeItem(this); - } else { - this.tree.expandTreeItem(this); - } - } - } - }, - { - key: 'ArrowDown', - title: 'Focus on the next layer of the tree', - action: () => { - if (this.children.length > 0 && this.isExpandable) { - this.tree.setFocusToNextLayer(this); - } - } - }, - { - key: 'ArrowUp', - title: 'Focus on the previous layer of the tree', - action: () => { - if (this.inGroup) { - this.tree.setFocusToParentItem(this); - } - } - }, - { - key: 'Escape', - title: 'Focus on the previous layer of the tree', - action: () => { - if (this.inGroup) { - this.tree.setFocusToParentItem(this); - } - } - }, - { - key: 'ArrowLeft', - title: 'Focus on the previous child element of the tree', - action: () => { - this.tree.setFocusToPreviousItem(this); - } - }, - { - key: 'ArrowRight', - title: 'Focus on the next child element of the tree', - action: () => { - this.tree.setFocusToNextItem(this); - } - }, - { - key: 'Home', - title: 'Focus top of the tree', - action: () => { - if (this.parent) { - this.tree.setFocusToFirstInLayer(this); - } - } - }, - { - key: 'x', - title: 'Navigate to the x-axis of the grapg', - action: () => { - this.tree.focusOnNodeType('xAxis', this); - } - }, - { - key: 'y', - title: 'Navigate to the y-axis of the graph', - action: () => { - this.tree.focusOnNodeType('yAxis', this); - } - }, - { - key: 'l', - title: 'Navigate to the legend of the graph', - action: () => { - this.tree.focusOnNodeType('legend', this); - } - }, - { - key: 't', - title: 'Open table dialog', - action: () => { - if ('predicate' in this.olliNode || this.olliNode.nodeType === 'root') { - openTableDialog(this.olliNode, this.tree); - } - } - }, - { - key: 'f', - title: 'Open selection dialog', - action: () => { - openSelectionDialog(this.tree); - } - }, - ]) - this.domNode.addEventListener('click', this.handleClick.bind(this)); this.domNode.addEventListener('focus', this.handleFocus.bind(this)); this.domNode.addEventListener('blur', this.handleBlur.bind(this)); From 5c6618fa85fb7aa5085ab358c31e1b1cf9a43a38 Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Sat, 15 Jul 2023 07:40:30 -0600 Subject: [PATCH 15/25] Add KeyboardManager to OlliGlobalState --- packages/core/src/util/globalState.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/util/globalState.ts b/packages/core/src/util/globalState.ts index d3178dae..c7b5e1fc 100644 --- a/packages/core/src/util/globalState.ts +++ b/packages/core/src/util/globalState.ts @@ -1,3 +1,4 @@ +import { KeyboardManager, initKeyboardManager } from '../Runtime/KeyboardManager'; import { OlliRuntime } from '../Runtime/OlliRuntime'; import { nodeIsTextInput } from './events'; @@ -5,6 +6,7 @@ export interface OlliGlobalState { keyListenerAttached: boolean; lastVisitedInstance: OlliRuntime; instancesOnPage: OlliRuntime[]; + keyboardManager: KeyboardManager; } export const getOlliGlobalState = (): OlliGlobalState => { @@ -71,6 +73,6 @@ export const updateGlobalStateOnInitialRender = (t: OlliRuntime) => { } } }); - setOlliGlobalState({ keyListenerAttached: true }); + setOlliGlobalState({ keyListenerAttached: true, keyboardManager: initKeyboardManager(t) }); } }; From 8ab9cf23652e7a1d35a03305154e4fe1c1b9d989 Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Wed, 2 Aug 2023 16:58:55 -0400 Subject: [PATCH 16/25] Update KeyboardAction type --- packages/core/src/Runtime/KeyboardManager.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core/src/Runtime/KeyboardManager.ts b/packages/core/src/Runtime/KeyboardManager.ts index 3a2b4970..9f99932c 100644 --- a/packages/core/src/Runtime/KeyboardManager.ts +++ b/packages/core/src/Runtime/KeyboardManager.ts @@ -3,12 +3,16 @@ import { OlliRuntime } from "./OlliRuntime"; import { OlliRuntimeTreeItem } from "./OlliRuntimeTreeItem"; export type KeyboardAction = { - action: () => void; + action: (treeItem: OlliRuntimeTreeItem) => void; title?: string; keyDescription?: string; description?: string; force?: boolean; caseSensitive?: boolean; + shiftKey?: boolean; + ctrlKey?: boolean; + altKey?: boolean; + metaKey?: boolean; } export type KeyRegistration = { From e895554ea79e81c4710c7c545b27b712e8ce44e3 Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Wed, 2 Aug 2023 16:59:17 -0400 Subject: [PATCH 17/25] Update checkKeys function --- packages/core/src/Runtime/KeyboardManager.ts | 24 ++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/core/src/Runtime/KeyboardManager.ts b/packages/core/src/Runtime/KeyboardManager.ts index 9f99932c..5dc3d76c 100644 --- a/packages/core/src/Runtime/KeyboardManager.ts +++ b/packages/core/src/Runtime/KeyboardManager.ts @@ -19,10 +19,26 @@ export type KeyRegistration = { key: string; } & KeyboardAction; -export const keyboardEventToString = (e: KeyboardEvent) => { - return `${e.altKey ? "Alt+" : ""}${e.ctrlKey ? "Ctrl+" : ""}${ - e.shiftKey ? "Shift+" : "" - }${e.key}`; +export const checkKeys = (e: KeyboardEvent, action: KeyboardAction) => { + let hasAlt: boolean = true; + let hasShift: boolean = true; + let hasCtrl: boolean = true; + let hasMeta: boolean = true; + + if (action.altKey) { + hasAlt = e.altKey === action.altKey; + } + if (action.shiftKey) { + hasShift = e.shiftKey === action.shiftKey; + } + if (action.ctrlKey) { + hasCtrl = e.ctrlKey === action.ctrlKey; + } + if (action.metaKey) { + hasMeta = e.metaKey === action.metaKey; + } + + return hasAlt && hasCtrl && hasMeta && hasShift; }; /** From a764ef4e2b4603e43c9ac0960d364b2ee0e809e6 Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Wed, 2 Aug 2023 16:59:50 -0400 Subject: [PATCH 18/25] Add extra keys to addAction parameters --- packages/core/src/Runtime/KeyboardManager.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/src/Runtime/KeyboardManager.ts b/packages/core/src/Runtime/KeyboardManager.ts index 5dc3d76c..28c3e78b 100644 --- a/packages/core/src/Runtime/KeyboardManager.ts +++ b/packages/core/src/Runtime/KeyboardManager.ts @@ -84,6 +84,10 @@ export class KeyboardManager { force, keyDescription, title, + shiftKey, + ctrlKey, + altKey, + metaKey, }: KeyRegistration): void { const checkKey = caseSensitive ? key : key.toUpperCase(); if (!force && checkKey in this.actions) { From 1cb726a6b1bf72aedfa3057aec11463e8d82624b Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Wed, 2 Aug 2023 17:00:33 -0400 Subject: [PATCH 19/25] Update handleEvents function usages --- packages/core/src/Runtime/KeyboardManager.ts | 21 ++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/core/src/Runtime/KeyboardManager.ts b/packages/core/src/Runtime/KeyboardManager.ts index 28c3e78b..18c07a1e 100644 --- a/packages/core/src/Runtime/KeyboardManager.ts +++ b/packages/core/src/Runtime/KeyboardManager.ts @@ -64,13 +64,18 @@ export class KeyboardManager { this.actions = {}; } - private handleEvents(e: KeyboardEvent): void { - const keyPress = keyboardEventToString(e) + public handleEvents(e: KeyboardEvent, tree: OlliRuntimeTreeItem): void { + const keyPress = e.key; + let keyboardAction: KeyboardAction; + if (keyPress in this.actions) { - this.actions[keyPress].action(); - e.preventDefault(); + keyboardAction = this.actions[keyPress]; } else if (keyPress.toUpperCase() in this.actions) { - this.actions[keyPress.toUpperCase()].action(); + keyboardAction = this.actions[keyPress.toUpperCase()]; + } + + if (checkKeys(e, keyboardAction)) { + keyboardAction.action(tree); e.stopPropagation(); e.preventDefault(); } @@ -97,7 +102,11 @@ export class KeyboardManager { title, description, action, - keyDescription + keyDescription, + shiftKey, + ctrlKey, + altKey, + metaKey, }; } From 8307ccc36ec39c148dae06c5ddf4aafc5f50eee2 Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Wed, 2 Aug 2023 17:01:11 -0400 Subject: [PATCH 20/25] Update initial actions --- packages/core/src/Runtime/KeyboardManager.ts | 108 +++++++++---------- 1 file changed, 52 insertions(+), 56 deletions(-) diff --git a/packages/core/src/Runtime/KeyboardManager.ts b/packages/core/src/Runtime/KeyboardManager.ts index 18c07a1e..dd78a0fe 100644 --- a/packages/core/src/Runtime/KeyboardManager.ts +++ b/packages/core/src/Runtime/KeyboardManager.ts @@ -54,9 +54,6 @@ export class KeyboardManager { constructor(target: HTMLElement) { this.target = target; - this.target.addEventListener('keydown', (event) => { - this.handleEvents(event); - }); if (!this.target.hasAttribute("tabIndex")) { this.target.setAttribute("tabIndex", "0"); } @@ -181,128 +178,127 @@ export class KeyboardManager { export const initKeyboardManager = (olliInstance: OlliRuntime) => { const kb = new KeyboardManager(olliInstance.rootDomNode); - const lastFocusedTreeItem: OlliRuntimeTreeItem = olliInstance.lastFocusedTreeItem; kb.addActions([ { key: 'h', title: 'Display help documentation modal', action: () => { kb.launchHelpDialog(); - } + }, }, { key: 'Enter', title: 'Expand and collapse the current layer of the tree', - action: () => { - if (lastFocusedTreeItem.isExpandable) { - if (lastFocusedTreeItem.isExpanded()) { - olliInstance.collapseTreeItem(lastFocusedTreeItem); + action: (treeItem) => { + if (treeItem.isExpandable) { + if (treeItem.isExpanded()) { + treeItem.tree.collapseTreeItem(treeItem); } else { - olliInstance.expandTreeItem(lastFocusedTreeItem); + treeItem.tree.expandTreeItem(treeItem); } } - } + }, }, { key: ' ', keyDescription: 'Space', title: 'Expand and collapse the current layer of the tree', - action: () => { - if (olliInstance.lastFocusedTreeItem.isExpandable) { - if (olliInstance.lastFocusedTreeItem.isExpanded()) { - olliInstance.collapseTreeItem(lastFocusedTreeItem); + action: (treeItem) => { + if (treeItem.tree.lastFocusedTreeItem.isExpandable) { + if (treeItem.tree.lastFocusedTreeItem.isExpanded()) { + treeItem.tree.collapseTreeItem(treeItem); } else { - olliInstance.expandTreeItem(lastFocusedTreeItem); + treeItem.tree.expandTreeItem(treeItem); } } - } + }, }, { key: 'ArrowDown', title: 'Focus on the next layer of the tree', - action: () => { - if (lastFocusedTreeItem.children.length > 0 && lastFocusedTreeItem.isExpandable) { - olliInstance.setFocusToNextLayer(lastFocusedTreeItem); + action: (treeItem) => { + if (treeItem.children.length > 0 && treeItem.isExpandable) { + treeItem.tree.setFocusToNextLayer(treeItem); } - } + }, }, { key: 'ArrowUp', title: 'Focus on the previous layer of the tree', - action: () => { - if (lastFocusedTreeItem.inGroup) { - olliInstance.setFocusToParentItem(lastFocusedTreeItem); + action: (treeItem) => { + if (treeItem.inGroup) { + treeItem.tree.setFocusToParentItem(treeItem); } - } + }, }, { key: 'Escape', title: 'Focus on the previous layer of the tree', - action: () => { - if (lastFocusedTreeItem.inGroup) { - olliInstance.setFocusToParentItem(lastFocusedTreeItem); + action: (treeItem) => { + if (treeItem.inGroup) { + treeItem.tree.setFocusToParentItem(treeItem); } - } + }, }, { key: 'ArrowLeft', title: 'Focus on the previous child element of the tree', - action: () => { - olliInstance.setFocusToPreviousItem(lastFocusedTreeItem); - } + action: (treeItem) => { + treeItem.tree.setFocusToPreviousItem(treeItem); + }, }, { key: 'ArrowRight', title: 'Focus on the next child element of the tree', - action: () => { - olliInstance.setFocusToNextItem(lastFocusedTreeItem); - } + action: (treeItem) => { + treeItem.tree.setFocusToNextItem(treeItem); + }, }, { key: 'Home', title: 'Focus top of the tree', - action: () => { - if (lastFocusedTreeItem.parent) { - olliInstance.setFocusToFirstInLayer(lastFocusedTreeItem); + action: (treeItem) => { + if (treeItem.parent) { + treeItem.tree.setFocusToFirstInLayer(treeItem); } - } + }, }, { key: 'x', - title: 'Navigate to the x-axis of the grapg', - action: () => { - olliInstance.focusOnNodeType('xAxis', this); - } + title: 'Navigate to the x-axis of the graph', + action: (treeItem) => { + treeItem.tree.focusOnNodeType('xAxis', treeItem); + }, }, { key: 'y', title: 'Navigate to the y-axis of the graph', - action: () => { - olliInstance.focusOnNodeType('yAxis', this); - } + action: (treeItem) => { + treeItem.tree.focusOnNodeType('yAxis', treeItem); + }, }, { key: 'l', title: 'Navigate to the legend of the graph', - action: () => { - olliInstance.focusOnNodeType('legend', this); - } + action: (treeItem) => { + treeItem.tree.focusOnNodeType('legend', treeItem); + }, }, { key: 't', title: 'Open table dialog', - action: () => { - if ('predicate' in lastFocusedTreeItem.olliNode || lastFocusedTreeItem.olliNode.nodeType === 'root') { - openTableDialog(lastFocusedTreeItem.olliNode, olliInstance); + action: (treeItem) => { + if ('predicate' in treeItem.olliNode || treeItem.olliNode.nodeType === 'root') { + openTableDialog(treeItem.olliNode, treeItem.tree); } - } + }, }, { key: 'f', title: 'Open selection dialog', - action: () => { - openSelectionDialog(olliInstance); - } + action: (treeItem) => { + openSelectionDialog(treeItem.tree); + }, }, ]) From 513f884bf7dd88e08bddd47bba0899dbae97cd16 Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Wed, 2 Aug 2023 17:01:35 -0400 Subject: [PATCH 21/25] Call KeyboardManager handleEvents from tree items --- packages/core/src/Runtime/KeyboardManager.ts | 4 +- .../core/src/Runtime/OlliRuntimeTreeItem.ts | 74 ++----------------- 2 files changed, 7 insertions(+), 71 deletions(-) diff --git a/packages/core/src/Runtime/KeyboardManager.ts b/packages/core/src/Runtime/KeyboardManager.ts index dd78a0fe..711dc6c8 100644 --- a/packages/core/src/Runtime/KeyboardManager.ts +++ b/packages/core/src/Runtime/KeyboardManager.ts @@ -73,8 +73,8 @@ export class KeyboardManager { if (checkKeys(e, keyboardAction)) { keyboardAction.action(tree); - e.stopPropagation(); - e.preventDefault(); + e.stopPropagation(); + e.preventDefault(); } } diff --git a/packages/core/src/Runtime/OlliRuntimeTreeItem.ts b/packages/core/src/Runtime/OlliRuntimeTreeItem.ts index ee1f05bc..eaeb5d87 100644 --- a/packages/core/src/Runtime/OlliRuntimeTreeItem.ts +++ b/packages/core/src/Runtime/OlliRuntimeTreeItem.ts @@ -10,6 +10,7 @@ import { openSelectionDialog, openTableDialog, openTargetedNavigationDialog } from '../Render/Dialog'; import { ElaboratedOlliNode } from '../Structure/Types'; +import { getOlliGlobalState } from '../util/globalState'; import { KeyboardManager } from './KeyboardManager'; import { OlliRuntime } from './OlliRuntime'; @@ -67,6 +68,7 @@ export class OlliRuntimeTreeItem { init() { this.domNode.tabIndex = -1; + this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)) this.domNode.addEventListener('click', this.handleClick.bind(this)); this.domNode.addEventListener('focus', this.handleFocus.bind(this)); this.domNode.addEventListener('blur', this.handleBlur.bind(this)); @@ -84,80 +86,14 @@ export class OlliRuntimeTreeItem { return false; } - /* EVENT HANDLERS */ + // /* EVENT HANDLERS */ handleKeydown(event: KeyboardEvent) { + const { keyboardManager } = getOlliGlobalState(); if (event.altKey || event.ctrlKey || event.metaKey) { return; } - this.checkBaseKeys(event); - } - - checkBaseKeys(event: KeyboardEvent) { - switch (event.key) { - case 'Enter': - case ' ': - if (this.isExpandable) { - if (this.isExpanded()) { - this.tree.collapseTreeItem(this); - } else { - this.tree.expandTreeItem(this); - } - } - break; - case 'ArrowDown': - if (this.children.length > 0 && this.isExpandable) { - this.tree.setFocusToNextLayer(this); - } - break; - case 'Escape': - case 'ArrowUp': - if (this.inGroup) { - this.tree.setFocusToParentItem(this); - } - break; - case 'ArrowLeft': - this.tree.setFocusToPreviousItem(this); - break; - case 'ArrowRight': - this.tree.setFocusToNextItem(this); - break; - case 'Home': - if (this.parent) { - this.tree.setFocusToFirstInLayer(this); - } - break; - - case 'End': - if (this.parent) { - this.tree.setFocusToLastInLayer(this); - } - break; - case 'x': - this.tree.focusOnNodeType('xAxis', this); - break; - case 'y': - this.tree.focusOnNodeType('yAxis', this); - break; - case 'l': - this.tree.focusOnNodeType('legend', this); - break; - case 't': - if ('predicate' in this.olliNode || this.olliNode.nodeType === 'root') { - openTableDialog(this.olliNode, this.tree); - } - break; - case 'f': - openSelectionDialog(this.tree); - break; - case 'r': - openTargetedNavigationDialog(this.tree); - break; - default: - // return to avoid preventing default event action - return; - } - + keyboardManager.handleEvents(event, this); event.stopPropagation(); event.preventDefault(); } From ddc179fb9821959a80c5ed3fc6256a88c35fbbee Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Thu, 3 Aug 2023 00:09:22 -0400 Subject: [PATCH 22/25] Simplify checkKeys function --- packages/core/src/Runtime/KeyboardManager.ts | 29 ++++++++------------ 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/packages/core/src/Runtime/KeyboardManager.ts b/packages/core/src/Runtime/KeyboardManager.ts index 711dc6c8..99b86667 100644 --- a/packages/core/src/Runtime/KeyboardManager.ts +++ b/packages/core/src/Runtime/KeyboardManager.ts @@ -20,25 +20,20 @@ export type KeyRegistration = { } & KeyboardAction; export const checkKeys = (e: KeyboardEvent, action: KeyboardAction) => { - let hasAlt: boolean = true; - let hasShift: boolean = true; - let hasCtrl: boolean = true; - let hasMeta: boolean = true; - - if (action.altKey) { - hasAlt = e.altKey === action.altKey; - } - if (action.shiftKey) { - hasShift = e.shiftKey === action.shiftKey; - } - if (action.ctrlKey) { - hasCtrl = e.ctrlKey === action.ctrlKey; - } - if (action.metaKey) { - hasMeta = e.metaKey === action.metaKey; + let hasKeys = { + altKey: true, + shiftKey: true, + ctrlKEy: true, + metaKey: true, } - return hasAlt && hasCtrl && hasMeta && hasShift; + Object.keys(hasKeys).forEach((key: string) => { + if (action[key]) { + hasKeys[key] = e[key] === action[key]; + } + }) + + return Object.keys(hasKeys).every((key: string) => hasKeys[key]); }; /** From b9b1fefb7f800de67790f5e78ae16910fa030bcd Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Thu, 3 Aug 2023 00:42:00 -0400 Subject: [PATCH 23/25] Render help table in separate file --- packages/core/src/Render/Help/index.ts | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 packages/core/src/Render/Help/index.ts diff --git a/packages/core/src/Render/Help/index.ts b/packages/core/src/Render/Help/index.ts new file mode 100644 index 00000000..67a2494e --- /dev/null +++ b/packages/core/src/Render/Help/index.ts @@ -0,0 +1,31 @@ +import { KeyboardManager } from "../../Runtime/KeyboardManager"; + + /** + * Build a help dialog + */ + export function renderHelpDialog(keyboardManager: KeyboardManager): HTMLElement { + const table = document.createElement("table"); + const tbody = document.createElement("tbody"); + + Object.entries(keyboardManager.getActions()).forEach(([keystroke, details]) => { + const tr = document.createElement("tr"); + const th = document.createElement("th"); + th.style.textAlign = 'left'; + th.scope = "row"; + th.textContent = details.keyDescription ?? keystroke; + tr.appendChild(th); + + const tdKey = document.createElement("td"); + tdKey.textContent = details.description; + tr.appendChild(tdKey); + + const tdTitle = document.createElement("td"); + tdTitle.textContent = details.title; + tr.appendChild(tdTitle); + + tbody.appendChild(tr); + }); + table.appendChild(tbody); + + return table; + } \ No newline at end of file From e8ad01efc28ebfda3f91cf7328b8ed31626295f0 Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Thu, 3 Aug 2023 00:42:42 -0400 Subject: [PATCH 24/25] Add openHelpDialog function --- packages/core/src/Render/Dialog/index.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/core/src/Render/Dialog/index.ts b/packages/core/src/Render/Dialog/index.ts index f97fd4ae..e259591c 100644 --- a/packages/core/src/Render/Dialog/index.ts +++ b/packages/core/src/Render/Dialog/index.ts @@ -1,12 +1,13 @@ import { predicateToDescription } from '../../Customization'; import { ElaboratedOlliNode } from '../../Structure/Types'; -import { OlliSpec } from '../../Types'; import { selectionTest } from '../../util/selection'; import { renderTable } from '../Table'; import { OlliRuntime } from '../../Runtime/OlliRuntime'; import './dialog.css'; import { makeSelectionMenu } from './selectionMenu'; import { makeTargetedNavMenu } from './targetedNavMenu'; +import { renderHelpDialog } from '../Help'; +import { getOlliGlobalState } from '../../util/globalState'; export function makeDialog( tree: OlliRuntime, @@ -94,6 +95,14 @@ function openDialog(dialog: HTMLElement, renderContainer: HTMLElement) { }); } +export function openHelpDialog(tree: OlliRuntime) { + const { keyboardManager } = getOlliGlobalState(); + const table = renderHelpDialog(keyboardManager); + const dialog = makeDialog(tree, 'Olli Help Menu', 'Below are the controls to navigate the Olli tree.', table); + + openDialog(dialog, tree.renderContainer) +} + export function openTableDialog(olliNode: ElaboratedOlliNode, tree: OlliRuntime) { const olliSpec = tree.olliSpec; const table = renderTable( From 9ebbe6b7678290a57f02af125baca22abde7f89e Mon Sep 17 00:00:00 2001 From: Matt Blanco Date: Thu, 3 Aug 2023 00:43:06 -0400 Subject: [PATCH 25/25] Move dialog rendering outside KeyboardManager --- packages/core/src/Runtime/KeyboardManager.ts | 67 ++------------------ 1 file changed, 7 insertions(+), 60 deletions(-) diff --git a/packages/core/src/Runtime/KeyboardManager.ts b/packages/core/src/Runtime/KeyboardManager.ts index 99b86667..7c932b98 100644 --- a/packages/core/src/Runtime/KeyboardManager.ts +++ b/packages/core/src/Runtime/KeyboardManager.ts @@ -1,4 +1,4 @@ -import { openSelectionDialog, openTableDialog } from "../Render/Dialog"; +import { openHelpDialog, openSelectionDialog, openTableDialog } from "../Render/Dialog"; import { OlliRuntime } from "./OlliRuntime"; import { OlliRuntimeTreeItem } from "./OlliRuntimeTreeItem"; @@ -109,76 +109,23 @@ export class KeyboardManager { } /** - * Build a help dialog - */ - public generateHelpDialog() { - const dialog = document.createElement("dialog"); - - const closeButton = document.createElement("button"); - closeButton.textContent = "X"; - closeButton.ariaLabel = "Close"; - closeButton.style.position = "absolute"; - closeButton.style.top = "10px"; - closeButton.style.right = "10px"; - closeButton.addEventListener("click", () => { - dialog.close(); - }); - dialog.appendChild(closeButton); - - const heading = "Olli Help Menu"; - const h1 = document.createElement("h1"); - h1.textContent = heading; - dialog.setAttribute("aria-live", heading); - dialog.appendChild(h1); - - const table = document.createElement("table"); - const tbody = document.createElement("tbody"); - Object.entries(this.actions).forEach(([keystroke, details]) => { - const tr = document.createElement("tr"); - const th = document.createElement("th"); - th.style.textAlign = 'left'; - th.scope = "row"; - th.textContent = details.title; - tr.appendChild(th); - - const td1 = document.createElement("td"); - td1.textContent = details.keyDescription ?? keystroke; - tr.appendChild(td1); - - const td2 = document.createElement("td"); - td2.textContent = details.description; - tr.appendChild(td2); - - tbody.appendChild(tr); - }); - table.appendChild(tbody); - - dialog.appendChild(table); - return dialog; - } - - /** - * Launch help dialog + * Return list of possible actions */ - public launchHelpDialog() { - if (this.helpModal === null) { - this.helpModal = this.generateHelpDialog(); - document.body.appendChild(this.helpModal); - } - this.helpModal.showModal(); - this.helpModal.focus(); + public getActions(): { [event: string]: KeyboardAction; } { + return this.actions; } } export const initKeyboardManager = (olliInstance: OlliRuntime) => { const kb = new KeyboardManager(olliInstance.rootDomNode); + kb.addActions([ { key: 'h', title: 'Display help documentation modal', - action: () => { - kb.launchHelpDialog(); + action: (treeItem) => { + openHelpDialog(treeItem.tree); }, }, {