feat(events): lvt-key modifier matching (Mod+Enter)#138
Conversation
lvt-key matched event.key with exact equality, so a chord like Cmd/Ctrl+
Enter was indistinguishable from a plain Enter — making "submit on
Cmd+Enter while a textarea keeps Enter for newlines" impossible to express.
Add modifier matching via a shared keyFilterMatches() helper used by both
keydown/keyup sites (delegated element bindings + window bindings):
- A bare filter ("Enter", "Escape", "j", "+") matches event.key verbatim,
regardless of modifiers — existing bindings are byte-for-byte unchanged.
- A "+"-joined combo's final segment is the key; leading segments are
required modifiers: Mod (metaKey||ctrlKey, the platform command key),
Meta/Cmd, Ctrl/Control, Shift, Alt/Option. Matching is lenient about
extra modifiers; an unknown token never matches.
- A combo match calls preventDefault() so the chord isn't also typed into
the focused field (e.g. Mod+Enter doesn't insert a newline).
lvt-key is the event filter only; node identity uses data-lvt-key, so the
new values don't affect morphdom reconciliation.
10 new tests (8 unit + 2 integration through the delegator). Full suite green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ey41dRwDpRgLtVHZjhYLKt
ReviewOverall this is well-structured and the test coverage is solid. One behavioural concern and one minor gap. preventDefault() is unconditional for all combo matches The PR intentionally calls preventDefault() on every matched combo so Mod+Enter does not also insert a newline. That makes sense for that specific case, but the same logic fires for combos like Shift+ArrowDown or Alt+ArrowLeft, where developers may want the default browser action to still happen (text selection, history navigation, etc.) alongside the custom handler. The risk is that lvt-key="Shift+ArrowDown" silently loses text-selection behaviour in adjacent focused fields. Worth documenting or offering an opt-out attribute like lvt-key-prevent="false". Mod++ is unrepresentable "Mod++".split("+") produces ["Mod", "", ""], so key = "" never matches. The docs acknowledge "+" only works as a bare filter, but it may be surprising that Shift++ cannot be expressed. Low priority, but worth documenting. Everything else looks good Back-compat path (bare key verbatim match, no preventDefault) is correct and tested. Both keydown match sites are updated consistently. The default: return { matched: false, combo: true } path for unknown modifiers is safe since no preventDefault is called on a non-match. mod.toLowerCase() for modifier names with a case-preserved key segment is the right call. |
…p h-scroll P3 of the UI overhaul — the read/write loop. - Auto-grow composer: `.composer textarea` gets `field-sizing: content` + `max-height`, so a multi-paragraph comment is fully visible while typing instead of scrolling inside a cramped 2-line box. min-height is the empty floor and the graceful fallback where field-sizing is unsupported. - Cmd/Ctrl+Enter saves: the composer form carries `lvt-on:keydown="addComment" lvt-key="Mod+Enter"`, so the chord saves from inside the textarea (plain Enter stays free for newlines). Relies on the client's new modifier matching (livetemplate/client#138), re-vendored here via `make sync-client`. Help-overlay row + keymap doc updated. - Desktop horizontal code scroll: under `@media (min-width:900px)`, long code lines scroll horizontally (`overflow-x:auto`, `.content white-space:pre`, `button.line width:max-content`) instead of wrapping mid-token. Mobile keeps wrapping — the documented iOS-Safari focus-scroll fix is small-screen only. Tests: TestE2E_CmdEnterSavesComment saves a comment via Ctrl+Enter end-to-end (pins the client patch); the @media split is pinned both ways — desktop white-space:pre in TestE2E_CodeScrollResetsOnFileSwitch, mobile pre-wrap in TestE2E_MobileScrollBounds. Full unit + browser e2e green; make tmpl-check golden delta = only the composer form's new keydown attributes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ey41dRwDpRgLtVHZjhYLKt
…p h-scroll (#68) P3 of the UI overhaul — the read/write loop. - Auto-grow composer: `.composer textarea` gets `field-sizing: content` + `max-height`, so a multi-paragraph comment is fully visible while typing instead of scrolling inside a cramped 2-line box. min-height is the empty floor and the graceful fallback where field-sizing is unsupported. - Cmd/Ctrl+Enter saves: the composer form carries `lvt-on:keydown="addComment" lvt-key="Mod+Enter"`, so the chord saves from inside the textarea (plain Enter stays free for newlines). Relies on the client's new modifier matching (livetemplate/client#138), re-vendored here via `make sync-client`. Help-overlay row + keymap doc updated. - Desktop horizontal code scroll: under `@media (min-width:900px)`, long code lines scroll horizontally (`overflow-x:auto`, `.content white-space:pre`, `button.line width:max-content`) instead of wrapping mid-token. Mobile keeps wrapping — the documented iOS-Safari focus-scroll fix is small-screen only. Tests: TestE2E_CmdEnterSavesComment saves a comment via Ctrl+Enter end-to-end (pins the client patch); the @media split is pinned both ways — desktop white-space:pre in TestE2E_CodeScrollResetsOnFileSwitch, mobile pre-wrap in TestE2E_MobileScrollBounds. Full unit + browser e2e green; make tmpl-check golden delta = only the composer form's new keydown attributes. Claude-Session: https://claude.ai/code/session_01Ey41dRwDpRgLtVHZjhYLKt Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lvt-keymodifier matching (Mod/Meta/Ctrl/Shift/Alt + key)lvt-keymatchedevent.keywith exact equality, so a chord like Cmd/Ctrl+Enter was indistinguishable from a plain Enter. That makes a common pattern impossible to express: submit on Cmd+Enter while the textarea keeps plain Enter for newlines.What changed
A shared
keyFilterMatches(e, filter)helper, used by both keydown/keyup match sites (delegated element bindings + window bindings):"Enter","Escape","j","+") → matchesevent.keyverbatim, regardless of modifiers. Existing bindings are byte-for-byte unchanged (the back-compat path)."+"-joined) → final segment is the key; leading segments are required modifiers:Mod=metaKey || ctrlKey(the platform command key: ⌘ on macOS, Ctrl elsewhere)Meta/Cmd,Ctrl/Control,Shift,Alt/Optionlvt-key="Mod+Enter",lvt-key="Shift+ArrowDown". Lenient about extra modifiers; an unknown token never matches.preventDefault()so the chord isn't also typed into the focused field (Mod+Enter won't insert a newline).Safety
lvt-keyis the event filter only; node identity usesdata-lvt-key, so the new values don't affect morphdom reconciliation. Back-compat is covered by an explicit test (a bare"Enter"filter still matches a modified Enter).Tests
10 new (8 unit on
keyFilterMatches+ 2 integration through the delegator, incl.preventDefaultand the plain-Enter-doesn't-fire case). Full suite green (772 passed).🤖 Generated with Claude Code
https://claude.ai/code/session_01Ey41dRwDpRgLtVHZjhYLKt