Skip to content

feat(events): lvt-key modifier matching (Mod+Enter)#138

Merged
adnaan merged 1 commit into
mainfrom
lvt-key-modifiers
Jun 28, 2026
Merged

feat(events): lvt-key modifier matching (Mod+Enter)#138
adnaan merged 1 commit into
mainfrom
lvt-key-modifiers

Conversation

@adnaan

@adnaan adnaan commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

lvt-key modifier matching (Mod/Meta/Ctrl/Shift/Alt + key)

lvt-key matched event.key with 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):

  • Bare filter ("Enter", "Escape", "j", "+") → matches event.key verbatim, regardless of modifiers. Existing bindings are byte-for-byte unchanged (the back-compat path).
  • Combo ("+"-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/Option
    • e.g. lvt-key="Mod+Enter", lvt-key="Shift+ArrowDown". 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 (Mod+Enter won't insert a newline).

Safety

lvt-key is the event filter only; node identity uses data-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. preventDefault and the plain-Enter-doesn't-fire case). Full suite green (772 passed).

Motivation: consumed by livetemplate/prereview to add Cmd/Ctrl+Enter-to-save in its comment composer.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ey41dRwDpRgLtVHZjhYLKt

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
@claude

claude Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review

Overall 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.

@adnaan adnaan merged commit c2c837b into main Jun 28, 2026
6 checks passed
@adnaan adnaan deleted the lvt-key-modifiers branch June 28, 2026 15:59
adnaan added a commit to livetemplate/prereview that referenced this pull request Jun 28, 2026
…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
adnaan added a commit to livetemplate/prereview that referenced this pull request Jun 28, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant