hotkeys-rails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0e144f56e44055cc18278c5250a6297519b873bde607c51b24dd5521a40f176e
4
+ data.tar.gz: 601a217fb719cffca22d8869eee811cf2052d3f63d45e5a46f0b94812bc8605d
5
+ SHA512:
6
+ metadata.gz: 72d1774b2efa3e4c85a6fddf37729a91e16c36e455a8d8cd4e5e519d680e20ccb71515e51899f7e7cbe45321ac5baacf190b02770ae995810299060f5c8968ea
7
+ data.tar.gz: a96373911237b3d209f714a9088762c78cc7f7a209b4c16fdd1740d9d5647fbf7f94a5ed61a0c501ed9c4a27582ff1a6e39d7867a82526c419d23937b27bf5d5
data/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.1.0] - 2024-12-02
6
+
7
+ Initial release.
8
+
9
+ ### Added
10
+
11
+ - `hotkey` option for `link_to`, `button_to`, and `button_tag` helpers
12
+ - `hotkey(*keys)` helper for explicit data attribute generation
13
+ - `hotkey_label(*keys)` for platform-aware keyboard shortcut labels
14
+ - `hotkey_hint(*keys)` for rendering `<kbd>` elements
15
+ - Dual binding for `:ctrl` modifier (Ctrl on Windows/Linux, Cmd on Mac)
16
+ - Focus action support via `hotkey(:key, action: :focus)`
17
+ - Rails generator for installing Stimulus controller
18
+ - Demo app for testing with Playwright
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # Hotkeys Rails
2
+
3
+ Keyboard shortcuts for Hotwire apps. No dependencies. No configuration. Just HTML.
4
+
5
+ ## Installation
6
+
7
+ Add to your Gemfile:
8
+
9
+ ```ruby
10
+ gem "hotkeys-rails"
11
+ ```
12
+
13
+ Run:
14
+
15
+ ```sh
16
+ bundle install
17
+ rails generate hotkeys_rails:install
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ```erb
23
+ <%= link_to "Back", root_path, hotkey: :esc %>
24
+ <%= button_to "New Card", cards_path, hotkey: :c %>
25
+ <%= button_tag "Save", hotkey: [:ctrl, :enter] %>
26
+ ```
27
+
28
+ The `:ctrl` modifier binds both Ctrl (Windows/Linux) and Cmd (Mac).
29
+
30
+ ### Visual Hints
31
+
32
+ ```erb
33
+ <%= link_to cards_path, hotkey: :c do %>
34
+ Add a card <%= hotkey_hint(:c) %>
35
+ <% end %>
36
+ ```
37
+
38
+ ### Labels for Tooltips
39
+
40
+ ```erb
41
+ <%= button_tag "Save",
42
+ title: "Save (#{hotkey_label(:ctrl, :enter)})",
43
+ hotkey: [:ctrl, :enter] %>
44
+ ```
45
+
46
+ ### Focus Instead of Click
47
+
48
+ ```erb
49
+ <%= text_field_tag :search, data: hotkey(:f, action: :focus) %>
50
+ ```
51
+
52
+ ### Works with Other Data Attributes
53
+
54
+ ```erb
55
+ <%= link_to "Edit", edit_path,
56
+ hotkey: :e,
57
+ data: { turbo_frame: "modal" } %>
58
+ ```
59
+
60
+ ## Helpers
61
+
62
+ - `hotkey(*keys)` - Returns data attributes for Stimulus controller
63
+ - `hotkey_label(*keys)` - Platform-aware label (⌘ on Mac, Ctrl+ elsewhere)
64
+ - `hotkey_hint(*keys)` - Renders `<kbd>` element with hide-on-touch class
65
+
66
+ ## How It Works
67
+
68
+ This gem overrides `link_to`, `button_to`, and `button_tag` to support the `:hotkey` option. When you use `hotkey: :esc`, it extracts the option and merges the Stimulus data attributes into the element.
69
+
70
+ If you prefer explicit control, use the `hotkey()` helper directly:
71
+
72
+ ```erb
73
+ <%= link_to "Back", root_path, data: hotkey(:esc) %>
74
+ ```
75
+
76
+ ## License
77
+
78
+ MIT
@@ -0,0 +1,15 @@
1
+ require "rails/generators"
2
+
3
+ module HotkeysRails
4
+ class InstallGenerator < Rails::Generators::Base
5
+ source_root File.expand_path("templates", __dir__)
6
+
7
+ def copy_controller
8
+ copy_file "hotkey_controller.js", "app/javascript/controllers/hotkey_controller.js"
9
+ end
10
+
11
+ def copy_stylesheet
12
+ copy_file "hotkey.css", "app/assets/stylesheets/hotkey.css"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ kbd {
2
+ border: 1px solid currentColor;
3
+ border-radius: 0.3em;
4
+ box-shadow: 0 0.1em 0 currentColor;
5
+ font-family: ui-monospace, monospace;
6
+ font-size: 0.8em;
7
+ font-weight: 600;
8
+ opacity: 0.7;
9
+ padding: 0 0.4em;
10
+ }
11
+
12
+ @media (any-hover: none) {
13
+ .hide-on-touch {
14
+ display: none;
15
+ }
16
+ }
@@ -0,0 +1,22 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ click(event) {
5
+ if (this.#shouldHandle(event)) {
6
+ event.preventDefault()
7
+ this.element.click()
8
+ }
9
+ }
10
+
11
+ focus(event) {
12
+ if (this.#shouldHandle(event)) {
13
+ event.preventDefault()
14
+ this.element.focus()
15
+ }
16
+ }
17
+
18
+ #shouldHandle(event) {
19
+ return !event.defaultPrevented &&
20
+ !event.target.closest("input, textarea, [contenteditable]")
21
+ }
22
+ }
@@ -0,0 +1,9 @@
1
+ module HotkeysRails
2
+ class Engine < ::Rails::Engine
3
+ initializer "hotkeys_rails.helper" do
4
+ ActiveSupport.on_load(:action_view) do
5
+ include HotkeysRails::Helper
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,99 @@
1
+ module HotkeysRails
2
+ module Helper
3
+ # Returns data attributes for Stimulus hotkey controller
4
+ #
5
+ # hotkey(:esc)
6
+ # # => { controller: "hotkey", action: "keydown.esc@document->hotkey#click" }
7
+ #
8
+ # hotkey(:ctrl, :enter)
9
+ # # => { controller: "hotkey", action: "keydown.ctrl+enter@document->hotkey#click keydown.meta+enter@document->hotkey#click" }
10
+ #
11
+ def hotkey(*keys, action: :click)
12
+ keys = keys.flatten.map(&:to_s)
13
+
14
+ actions = if keys.include?("ctrl")
15
+ chord = keys.join("+")
16
+ meta_chord = keys.map { |k| k == "ctrl" ? "meta" : k }.join("+")
17
+ "keydown.#{chord}@document->hotkey##{action} keydown.#{meta_chord}@document->hotkey##{action}"
18
+ else
19
+ "keydown.#{keys.join("+")}@document->hotkey##{action}"
20
+ end
21
+
22
+ { controller: "hotkey", action: actions }
23
+ end
24
+
25
+ # Platform-aware label for keyboard shortcuts
26
+ #
27
+ # hotkey_label(:ctrl, :enter)
28
+ # # => "⌘Return" on Mac, "Ctrl+Enter" elsewhere
29
+ #
30
+ def hotkey_label(*keys)
31
+ keys.flatten.map do |key|
32
+ case key.to_s
33
+ when "ctrl" then mac? ? "⌘" : "Ctrl+"
34
+ when "meta" then mac? ? "⌘" : "Win+"
35
+ when "alt" then mac? ? "⌥" : "Alt+"
36
+ when "shift" then mac? ? "⇧" : "Shift+"
37
+ when "enter" then mac? ? "Return" : "Enter"
38
+ when "esc" then "Esc"
39
+ else key.to_s.upcase
40
+ end
41
+ # Mac symbols (⌘⌥⇧) don't need + separator, Windows modifiers do (Ctrl+, Alt+, Shift+)
42
+ # gsub removes trailing + from Mac symbols: "⌘+" -> "⌘"
43
+ end.join.gsub(/[⌘⌥⇧]\+/, &:chop)
44
+ end
45
+
46
+ # Renders <kbd> element with hide-on-touch class
47
+ #
48
+ # hotkey_hint(:ctrl, :s)
49
+ # # => <kbd class="hide-on-touch">⌘S</kbd>
50
+ #
51
+ def hotkey_hint(*keys)
52
+ content_tag(:kbd, hotkey_label(*keys), class: "hide-on-touch")
53
+ end
54
+
55
+ def link_to(name = nil, options = nil, html_options = nil, &block)
56
+ if block_given?
57
+ # link_to(url, html_options) { content }
58
+ html_options = extract_hotkey_option(options)
59
+ super(name, html_options, &block)
60
+ else
61
+ # link_to(name, url, html_options)
62
+ html_options = extract_hotkey_option(html_options)
63
+ super(name, options, html_options)
64
+ end
65
+ end
66
+
67
+ def button_to(name = nil, options = nil, html_options = nil, &block)
68
+ html_options = extract_hotkey_option(html_options)
69
+ super
70
+ end
71
+
72
+ def button_tag(content_or_options = nil, options = nil, &block)
73
+ if content_or_options.is_a?(Hash)
74
+ options = extract_hotkey_option(content_or_options)
75
+ super(nil, options, &block)
76
+ else
77
+ options = extract_hotkey_option(options)
78
+ super(content_or_options, options, &block)
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def extract_hotkey_option(html_options)
85
+ return {} if html_options.nil?
86
+ html_options = html_options.dup
87
+
88
+ if hotkey_keys = html_options.delete(:hotkey)
89
+ html_options[:data] = (html_options[:data] || {}).merge(hotkey(*Array(hotkey_keys)))
90
+ end
91
+
92
+ html_options
93
+ end
94
+
95
+ def mac?
96
+ request.user_agent.to_s.include?("Mac")
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,3 @@
1
+ module HotkeysRails
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,6 @@
1
+ require_relative "hotkeys_rails/version"
2
+ require_relative "hotkeys_rails/helper"
3
+ require_relative "hotkeys_rails/engine" if defined?(Rails::Engine)
4
+
5
+ module HotkeysRails
6
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hotkeys-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kieran Klaassen
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: railties
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.0'
26
+ description: No dependencies. No configuration. Just HTML.
27
+ email:
28
+ - kieran@kieranklaassen.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - CHANGELOG.md
34
+ - README.md
35
+ - lib/generators/hotkeys_rails/install_generator.rb
36
+ - lib/generators/hotkeys_rails/templates/hotkey.css
37
+ - lib/generators/hotkeys_rails/templates/hotkey_controller.js
38
+ - lib/hotkeys_rails.rb
39
+ - lib/hotkeys_rails/engine.rb
40
+ - lib/hotkeys_rails/helper.rb
41
+ - lib/hotkeys_rails/version.rb
42
+ homepage: https://github.com/kieranklaassen/hotkeys-rails
43
+ licenses:
44
+ - MIT
45
+ metadata:
46
+ homepage_uri: https://github.com/kieranklaassen/hotkeys-rails
47
+ source_code_uri: https://github.com/kieranklaassen/hotkeys-rails
48
+ changelog_uri: https://github.com/kieranklaassen/hotkeys-rails/blob/main/CHANGELOG.md
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '3.1'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubygems_version: 3.6.9
64
+ specification_version: 4
65
+ summary: Keyboard shortcuts for Hotwire apps
66
+ test_files: []