-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Feat/add close all tabs #2863
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feat/add close all tabs #2863
Changes from all commits
b31f625
f189eab
f2bda61
efe2ecd
dc418ee
4b7af9e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -124,7 +124,108 @@ export default function Contextmenu(content, options) { | |
| for (let $el of children) $el.tabIndex = "0"; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the currently focused menu item, or null if focus is | ||
| * elsewhere (e.g. nothing focused yet, or focus left the menu). | ||
| * @returns {HTMLElement|null} | ||
| */ | ||
| function getFocusedItem() { | ||
| const items = [...$el.children]; | ||
| const index = items.indexOf(document.activeElement); | ||
| return index === -1 ? null : document.activeElement; | ||
| } | ||
|
|
||
| /** | ||
| * Moves focus to the next/previous menu item, wrapping around at the | ||
| * ends. `direction` is +1 for next, -1 for previous. | ||
| * @param {number} direction | ||
| */ | ||
| function moveFocus(direction) { | ||
| const items = [...$el.children]; | ||
| if (!items.length) return; | ||
|
|
||
| const currentIndex = items.indexOf(document.activeElement); | ||
| let nextIndex; | ||
| if (currentIndex === -1) { | ||
| nextIndex = direction > 0 ? 0 : items.length - 1; | ||
| } else { | ||
| nextIndex = (currentIndex + direction + items.length) % items.length; | ||
| } | ||
| items[nextIndex]?.focus(); | ||
| } | ||
|
|
||
| /** | ||
| * Turns a keyboard activation (Enter/Space on a focused menu item) into | ||
| * a real click event, so both selection patterns this component | ||
| * supports handle it identically to a pointer click: | ||
| * - the `items`/`onselect` array form, whose routing lives in the | ||
| * `onclick` handler above | ||
| * - a consumer's own click listener attached directly to $el, as used | ||
| * by menus built with the `innerHTML` option | ||
| * | ||
| * The dispatched event is marked with `keyboardActivated` so consumers | ||
| * that filter out synthetic `detail === 0` ghost clicks (e.g. to ignore | ||
| * the click that follows a touch-based long press) can still recognize | ||
| * and allow this one through. | ||
| * @param {HTMLElement} $item | ||
| */ | ||
| function activateItem($item) { | ||
| if (!$item || !$el.contains($item)) return; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Disabled items remain keyboard-actionable. Navigation includes every child, including hr separators and .disabled rows. Programmatically dispatching a click bypasses CSS pointer-events: none, allowing disabled commands such as Save, File Info, or Encoding to execute. Restrict navigation and activation to enabled actionable rows. |
||
| const clickEvent = new MouseEvent("click", { | ||
| bubbles: true, | ||
| cancelable: true, | ||
| view: window, | ||
| }); | ||
| Object.defineProperty(clickEvent, "keyboardActivated", { | ||
| value: true, | ||
| }); | ||
| $item.dispatchEvent(clickEvent); | ||
| } | ||
|
|
||
| /** | ||
| * Keyboard support for the menu: Enter/Space activates the focused | ||
| * item, Up/Down arrows move focus between items (wrapping at the | ||
| * ends), Home/End jump to the first/last item, and Escape closes the | ||
| * menu and returns focus to the toggler. | ||
| * @param {KeyboardEvent} e | ||
| */ | ||
| function onMenuKeydown(e) { | ||
| switch (e.key) { | ||
| case "Enter": | ||
| case " ": | ||
| case "Spacebar": | ||
| e.preventDefault(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nested controls lose keyboard behavior. The shared keydown handler intercepts Space/Enter from any descendant. When the file menu’s read-only checkbox has focus, getFocusedItem() returns null, but preventDefault() still blocks the checkbox from toggling. Handle activation only when focus is on a direct menu row, or ignore events originating from interactive descendants. |
||
| activateItem(getFocusedItem()); | ||
| break; | ||
| case "ArrowDown": | ||
| case "Down": | ||
| e.preventDefault(); | ||
| moveFocus(1); | ||
| break; | ||
| case "ArrowUp": | ||
| case "Up": | ||
| e.preventDefault(); | ||
| moveFocus(-1); | ||
| break; | ||
| case "Home": | ||
| e.preventDefault(); | ||
| $el.firstElementChild?.focus(); | ||
| break; | ||
| case "End": | ||
| e.preventDefault(); | ||
| $el.lastElementChild?.focus(); | ||
| break; | ||
| case "Escape": | ||
| case "Esc": | ||
| e.preventDefault(); | ||
| hide(); | ||
| options.toggler?.focus?.(); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| function destroy() { | ||
| $el.removeEventListener("keydown", onMenuKeydown); | ||
| $el.remove(); | ||
| $mask.remove(); | ||
| options.toggler?.removeEventListener("click", toggle); | ||
|
|
@@ -134,6 +235,8 @@ export default function Contextmenu(content, options) { | |
| options.toggler.addEventListener("click", toggle); | ||
| } | ||
|
|
||
| $el.addEventListener("keydown", onMenuKeydown); | ||
|
|
||
| $el.hide = hide; | ||
| $el.show = show; | ||
| $el.destroy = destroy; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keyboard navigation treats every direct child as an action, including separators and entries marked
disabled. Arrow, Home, and End keys can focus<hr>elements, while Enter or Space can dispatch a click on a disabled entry. Existing menu handlers do not check the disabled state before running its command, so keyboard users can trigger actions that the menu presents as unavailable. Navigation and activation should be limited to enabled action items.Knowledge Base Used: