KeyboardShortcuts
Binds key combinations such as ⌘S or ? to functions on the page, leaving text boxes alone, and lists them.
Demo
| Save | Ctrl+SorCtrl+Enter |
| New item | Alt+N |
| Show help | ? |
| Close | Esc |
Last done: nothing yet; press one of the keys above.
Get it
It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/keyboard_shortcuts.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/keyboard_shortcuts.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/keyboard_shortcuts.qwe
component KeyboardShortcutsDemoLive() { state log = [] let did = (what: str) => { log = take(push(log, what) | reverse, 4) | reverse } <div.demo-stack> <KeyboardShortcuts show={true} bindings={[ { keys: "mod+s, mod+Enter", label: "Save", run: () => { did("saved") } }, { keys: "alt+n", label: "New item", run: () => { did("new item") } }, { keys: "?", label: "Show help", run: () => { did("help") } }, { keys: "Escape", label: "Close", run: () => { did("closed") }, in_fields: true }, { keys: "g", run: () => { did("g") } }, ]}/> <TextField name="kbsc_try" label="A text box: shortcuts other than Escape are left alone in it" placeholder="Type here"/> <p.demo-note>Last done: {len(log) == 0 ? "nothing yet; press one of the keys above" : join(log, ", ")}.</p> </div>}view KeyboardShortcutsDemo() = <div.demo-stack> <KeyboardShortcutsDemoLive/> </div>Source
ui/keyboard_shortcuts.qwe
// KeyboardShortcuts: binds key combinations to functions while it is on the// page, and can list them. "mod" is ⌘ on a Mac and Ctrl elsewhere.//// <KeyboardShortcuts bindings={[// { keys: "mod+s", label: "Save", run: () => { save() } },// { keys: "mod+k", label: "Search", run: () => { searching = true } },// { keys: "?", label: "Show shortcuts", run: () => { help = true } },// { keys: "Escape", label: "Close", run: () => { help = false }, in_fields: true },// ]}/>//// bindings: maps of { keys, label?, run, in_fields? }. keys: modifiers (mod,// ctrl, alt, shift, meta) and a key joined by "+": a letter, a digit, a// character as typed ("?", "/"), or a key's name ("Escape", "Enter",// "ArrowUp", "F2"). Several combos for one action: "mod+s, mod+enter". run():// called with nothing. in_fields: also while typing in a text box (false: keys// typed into fields are left alone). The browser's own action for a key bound// here is prevented. show: lists the bindings that have a label, as a table of// keys (false). label: the list's caption ("Keyboard shortcuts").// keyboard_shortcuts_text(keys, mac) gives how a combo is written ("⌘S", "Ctrl+S").// Inside one of your components, since it takes functions.import ui/theme.qwe use ui_clslet keyboard_shortcuts_css = css`.ui-kbsc { width: 100%; border-collapse: collapse; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-kbsc caption { padding: 0 0 8px; text-align: left; font-weight: 600; }.ui-kbsc td { padding: 7px 0; border-top: 1px solid var(--ui-border); }.ui-kbsc td:last-child { text-align: right; }.ui-kbsc kbd { display: inline-block; min-width: 22px; padding: 2px 6px; border: 1px solid var(--ui-border); border-bottom-width: 2px; border-radius: 5px; background: var(--ui-subtle); color: var(--ui-fg); font: 500 12px/1.4 var(--ui-mono); text-align: center; }.ui-kbsc-or { margin: 0 6px; color: var(--ui-muted); font-size: 12px; }`// A combo as { ctrl, alt, shift, meta, key }, "mod" made meta on a Mac and ctrl elsewhere.fn keyboard_shortcuts_parse(combo: str, mac: bool) -> map { let parts = split(trim(combo), "+") | map((p) => trim(p)) // "mod++" and "+" name the plus key itself. let key = ends_with(trim(combo), "++") || trim(combo) == "+" ? "+" : last(parts) let mods = take(parts, len(parts) - 1) | map((m) => lower(m)) let has = (m) => contains(mods, m) { ctrl: has("ctrl") || (has("mod") && !mac), meta: has("meta") || has("cmd") || (has("mod") && mac), alt: has("alt") || has("option"), shift: has("shift"), key: lower(key) }}// How a combo is written: "⌘⇧S" on a Mac, "Ctrl+Shift+S" elsewhere.fn keyboard_shortcuts_text(combo: str, mac: bool = false) -> str { let c = keyboard_shortcuts_parse(combo, mac) let names = { escape: "Esc", arrowup: "↑", arrowdown: "↓", arrowleft: "←", arrowright: "→", enter: "Enter", " ": "Space", space: "Space" } let k = names[c.key] ?? (len(c.key) == 1 ? upper(c.key) : upper(slice(c.key, 0, 1)) + slice(c.key, 1)) mac ? join([c.ctrl && "⌃", c.alt && "⌥", c.shift && "⇧", c.meta && "⌘", k] | filter((p) => p != false), "") : join([c.ctrl && "Ctrl", c.alt && "Alt", c.shift && "Shift", c.meta && "Meta", k] | filter((p) => p != false), "+")}// Whether a key event matches a combo. Shift is checked for letters and named// keys; for a character as typed ("?") it is part of how it was typed.fn keyboard_shortcuts_match(c: map, e) -> bool { let key = lower(e.key ?? "") let named_key = key == "space" ? " " : key let want = c.key == "space" ? " " : c.key let shift_counts = len(want) > 1 || re.test(want, "^[a-z0-9]$") named_key == want && e.ctrlKey == c.ctrl && e.metaKey == c.meta && e.altKey == c.alt && (!shift_counts || e.shiftKey == c.shift)}component KeyboardShortcuts(bindings: list = [], show: bool = false, label: str = "Keyboard shortcuts", class: str? = null) { style global keyboard_shortcuts_css state mac = false <div class={ui_cls(["ui-kbsc-host", class])} @mount={(_el) => { mac = contains(str(js("navigator.platform") ?? ""), "Mac") let onkey = (e) => { let t = e.target let typing = t != null && (t.isContentEditable == true || contains(["INPUT", "TEXTAREA", "SELECT"], str(t.tagName ?? ""))) let hit = find(bindings, (b) => (!typing || b.in_fields == true) && any(split(b.keys, ","), (k) => trim(k) != "" && keyboard_shortcuts_match(keyboard_shortcuts_parse(k, mac), e))) if hit != null && !e.repeat { e.preventDefault() hit.run() } } js("document.addEventListener", "keydown", onkey) () => js("document.removeEventListener", "keydown", onkey) }}> {show && <table.ui-kbsc> <caption>{label}</caption> <tbody> {for b in bindings | filter((b) => b.label != null) => <tr key={b.keys}> <td>{b.label}</td> <td>{for [i, k] in enumerate(split(b.keys, ",") | map((k) => trim(k)) | filter((k) => k != "")) => [i > 0 && <span.ui-kbsc-or>or</span>, <kbd aria-label={keyboard_shortcuts_text(k, mac)}>{keyboard_shortcuts_text(k, mac)}</kbd>]}</td> </tr>} </tbody> </table>} </div>}Related
DraggableA box dragged anywhere in its container with the pointer or the arrow keys, kept inside it, with on_move(x, y).8 views · 7 downloadsLiveAnnouncerA hidden aria-live region the page writes messages to with live_announcer_say(), politely or at once.6 views · 6 downloadsResizableBoxA box the user resizes by dragging its corner or an edge, within limits, with the keyboard too.5 views · 5 downloadsScrollAreaA box with its own scrollbars and a height limit, fading at the edges where there is more to see.6 views · 7 downloadsShortcutsDialogA dialog listing the page's keyboard shortcuts in groups, opened with ?, with a box to filter them.5 views · 8 downloadsSkipLink"Skip to content": the first link on a page, out of sight until the keyboard reaches it.5 views · 6 downloads
Thread
Thread 0 posts
On KeyboardShortcuts: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.