phlex-cmdk 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 +7 -0
- data/CHANGELOG.md +22 -0
- data/LICENSE.md +26 -0
- data/README.md +398 -0
- data/assets/css/cmdk_themes.css +428 -0
- data/assets/js/cmdk.js +1015 -0
- data/assets/js/cmdk_controller.js +110 -0
- data/lib/cmdk/base.rb +24 -0
- data/lib/cmdk/dialog.rb +35 -0
- data/lib/cmdk/empty.rb +15 -0
- data/lib/cmdk/footer.rb +30 -0
- data/lib/cmdk/group.rb +46 -0
- data/lib/cmdk/input.rb +34 -0
- data/lib/cmdk/item.rb +54 -0
- data/lib/cmdk/list.rb +17 -0
- data/lib/cmdk/loading.rb +25 -0
- data/lib/cmdk/root.rb +62 -0
- data/lib/cmdk/separator.rb +16 -0
- data/lib/cmdk/version.rb +3 -0
- data/lib/cmdk.rb +40 -0
- data/lib/phlex-cmdk.rb +1 -0
- metadata +87 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional Stimulus base controller for phlex-cmdk. The gem's runtime is
|
|
3
|
+
* framework-free; this thin layer adds idiomatic Hotwire ergonomics on top.
|
|
4
|
+
*
|
|
5
|
+
* Attach to the root (or any ancestor of one):
|
|
6
|
+
*
|
|
7
|
+
* Cmdk::Root(data: { controller: 'cmdk' }) do ... end
|
|
8
|
+
*
|
|
9
|
+
* Extend it and override the hooks you care about:
|
|
10
|
+
*
|
|
11
|
+
* import CmdkController from 'phlex-cmdk/controller' // or the copied file
|
|
12
|
+
*
|
|
13
|
+
* export default class extends CmdkController {
|
|
14
|
+
* itemSelected({ detail: { value } }) { this.runCommand(value) }
|
|
15
|
+
* scopeChanged({ detail: { scope, query } }) {
|
|
16
|
+
* if (scope === 'user') this.frameTarget.src = `/search/users?q=${query}`
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* Requires `@hotwired/stimulus` (peer) and the runtime resolvable as the bare
|
|
21
|
+
* specifier `cmdk` (importmap: `pin 'cmdk', to: 'cmdk.js'`; bundlers: alias).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { Controller } from '@hotwired/stimulus'
|
|
25
|
+
import Cmdk from 'cmdk'
|
|
26
|
+
|
|
27
|
+
export default class CmdkController extends Controller {
|
|
28
|
+
#listeners = []
|
|
29
|
+
|
|
30
|
+
connect() {
|
|
31
|
+
Cmdk.scan(this.element)
|
|
32
|
+
this.#listeners = [
|
|
33
|
+
['cmdk-item-select', (event) => this.itemSelected(event)],
|
|
34
|
+
['cmdk-value-change', (event) => this.valueChanged(event)],
|
|
35
|
+
['cmdk-search-change', (event) => this.searchChanged(event)],
|
|
36
|
+
['cmdk-scope-change', (event) => this.scopeChanged(event)],
|
|
37
|
+
]
|
|
38
|
+
for (const [type, listener] of this.#listeners) this.element.addEventListener(type, listener)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
disconnect() {
|
|
42
|
+
for (const [type, listener] of this.#listeners) this.element.removeEventListener(type, listener)
|
|
43
|
+
this.#listeners = []
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The [cmdk-root] this controller manages. */
|
|
47
|
+
get root() {
|
|
48
|
+
return this.element.matches('[cmdk-root]') ? this.element : this.element.querySelector('[cmdk-root]')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Snapshot of { search, scope, picker, query, value, filtered }. */
|
|
52
|
+
get state() {
|
|
53
|
+
return Cmdk.getState(this.root)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── Overridable event hooks (CustomEvents, payload in event.detail) ──
|
|
57
|
+
|
|
58
|
+
/** An item was chosen via click or Enter. Cancelable: event.preventDefault(). */
|
|
59
|
+
itemSelected(event) {}
|
|
60
|
+
|
|
61
|
+
/** The highlighted item changed. */
|
|
62
|
+
valueChanged(event) {}
|
|
63
|
+
|
|
64
|
+
/** The search query changed; detail carries { search, scope, query }. */
|
|
65
|
+
searchChanged(event) {}
|
|
66
|
+
|
|
67
|
+
/** A search scope was entered or left. */
|
|
68
|
+
scopeChanged(event) {}
|
|
69
|
+
|
|
70
|
+
// ── Actions / imperative API ──
|
|
71
|
+
|
|
72
|
+
/** Open the surrounding or contained <dialog cmdk-dialog>. */
|
|
73
|
+
open() {
|
|
74
|
+
Cmdk.openDialog(this.#dialog)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
close() {
|
|
78
|
+
Cmdk.closeDialog(this.#dialog)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
toggle() {
|
|
82
|
+
this.#dialog?.open ? this.close() : this.open()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
setSearch(query) {
|
|
86
|
+
Cmdk.setSearch(this.root, query)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setValue(value) {
|
|
90
|
+
Cmdk.setValue(this.root, value)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Enter a scope programmatically. Works as a Stimulus action with params:
|
|
95
|
+
* <button data-action="cmdk#enterScope" data-cmdk-scope-param="user">
|
|
96
|
+
* or called directly: this.enterScope('user')
|
|
97
|
+
*/
|
|
98
|
+
enterScope(scopeOrEvent) {
|
|
99
|
+
const scope = typeof scopeOrEvent === 'string' ? scopeOrEvent : scopeOrEvent?.params?.scope
|
|
100
|
+
if (scope) Cmdk.enterScope(this.root, scope)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
exitScope() {
|
|
104
|
+
Cmdk.exitScope(this.root)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get #dialog() {
|
|
108
|
+
return this.element.closest('dialog[cmdk-dialog]') || this.element.querySelector('dialog[cmdk-dialog]')
|
|
109
|
+
}
|
|
110
|
+
}
|
data/lib/cmdk/base.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Cmdk
|
|
2
|
+
# Shared base for all cmdk components.
|
|
3
|
+
class Base < ::Phlex::HTML
|
|
4
|
+
# Inline equivalent of cmdk's srOnlyStyles, used for the accessible label.
|
|
5
|
+
SR_ONLY_STYLE = 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;' \
|
|
6
|
+
'overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0'.freeze
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
# Merge user-supplied attributes into the component's defaults.
|
|
11
|
+
# User values win, except `class` and `style` which are concatenated
|
|
12
|
+
# and `data` hashes which are merged.
|
|
13
|
+
def merged(defaults, overrides)
|
|
14
|
+
defaults.merge(overrides) do |key, default, override|
|
|
15
|
+
case key
|
|
16
|
+
when :class then [default, override].compact.join(' ')
|
|
17
|
+
when :style then [default.to_s.chomp(';'), override].compact.join(';')
|
|
18
|
+
when :data then default.is_a?(Hash) && override.is_a?(Hash) ? default.merge(override) : override
|
|
19
|
+
else override
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/cmdk/dialog.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Cmdk
|
|
2
|
+
# Command menu inside a native <dialog>. Port of `<Command.Dialog>`.
|
|
3
|
+
#
|
|
4
|
+
# Replaces the Radix dialog with a native modal dialog: Escape closes it
|
|
5
|
+
# natively, clicking the backdrop closes it via the runtime, and the
|
|
6
|
+
# backdrop is styled with `dialog[cmdk-dialog]::backdrop` instead of
|
|
7
|
+
# `[cmdk-overlay]`. Accepts every `Cmdk::Root` option plus:
|
|
8
|
+
#
|
|
9
|
+
# - `open:` render the page with the dialog already open (runtime calls showModal)
|
|
10
|
+
# - `hotkey:` a key like "k" — the runtime toggles the dialog on cmd/ctrl+key
|
|
11
|
+
class Dialog < Base
|
|
12
|
+
def initialize(open: false, hotkey: nil, label: nil, default_value: nil, should_filter: true,
|
|
13
|
+
loop: false, vim_bindings: true, disable_pointer_selection: false,
|
|
14
|
+
dialog_attributes: {}, **attributes)
|
|
15
|
+
@open = open
|
|
16
|
+
@hotkey = hotkey
|
|
17
|
+
@dialog_attributes = dialog_attributes
|
|
18
|
+
@root_options = {
|
|
19
|
+
label: label, default_value: default_value, should_filter: should_filter, loop: loop,
|
|
20
|
+
vim_bindings: vim_bindings, disable_pointer_selection: disable_pointer_selection
|
|
21
|
+
}
|
|
22
|
+
@attributes = attributes
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def view_template(&block)
|
|
26
|
+
data = {}
|
|
27
|
+
data[:cmdk_open] = '' if @open
|
|
28
|
+
data[:cmdk_dialog_hotkey] = @hotkey if @hotkey
|
|
29
|
+
|
|
30
|
+
dialog(**merged({ 'cmdk-dialog' => '', aria_label: @root_options[:label], data: data }, @dialog_attributes)) do
|
|
31
|
+
render Root.new(**@root_options, **@attributes), &block
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/cmdk/empty.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Cmdk
|
|
2
|
+
# Shown automatically when there are no results. Port of `<Command.Empty>`.
|
|
3
|
+
# Rendered hidden (inline display:none, so theme CSS cannot override it);
|
|
4
|
+
# the runtime toggles it based on the filtered result count.
|
|
5
|
+
class Empty < Base
|
|
6
|
+
def initialize(**attributes)
|
|
7
|
+
@attributes = attributes
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def view_template(&block)
|
|
11
|
+
defaults = { 'cmdk-empty' => '', role: 'presentation', style: 'display:none' }
|
|
12
|
+
div(**merged(defaults, @attributes)) { block ? block.call : nil }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/cmdk/footer.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Cmdk
|
|
2
|
+
# Palette footer, as seen in Raycast-style command menus (an extension over
|
|
3
|
+
# the React API). Render any content; include an element with the
|
|
4
|
+
# `cmdk-footer-hint` attribute (or pass no block to get one) and the runtime
|
|
5
|
+
# fills it with the selected item's `hint:` text and `kbd:` keys:
|
|
6
|
+
#
|
|
7
|
+
# Cmdk::Item(hint: 'Open in New Tab', kbd: '⌘ ↵') { 'Figma' }
|
|
8
|
+
#
|
|
9
|
+
# Cmdk::Footer() do
|
|
10
|
+
# span { '🚀' }
|
|
11
|
+
# div('cmdk-footer-hint' => '')
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# The hint container gets `data-empty` when the selected item has no hint.
|
|
15
|
+
class Footer < Base
|
|
16
|
+
def initialize(**attributes)
|
|
17
|
+
@attributes = attributes
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def view_template(&block)
|
|
21
|
+
div(**merged({ 'cmdk-footer' => '' }, @attributes)) do
|
|
22
|
+
if block
|
|
23
|
+
block.call
|
|
24
|
+
else
|
|
25
|
+
div('cmdk-footer-hint' => '')
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/cmdk/group.rb
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'securerandom'
|
|
2
|
+
|
|
3
|
+
module Cmdk
|
|
4
|
+
# Groups items together with an optional heading. Port of `<Command.Group>`.
|
|
5
|
+
# Provide `value:` when there is no heading (it is used for sorting groups);
|
|
6
|
+
# with a string heading the value is inferred from it, matching React cmdk.
|
|
7
|
+
class Group < Base
|
|
8
|
+
def initialize(heading: nil, value: nil, force_mount: false, scope: nil, scope_only: false,
|
|
9
|
+
server_filtered: false, **attributes)
|
|
10
|
+
@heading = heading
|
|
11
|
+
@value = value
|
|
12
|
+
@force_mount = force_mount
|
|
13
|
+
@scope = scope
|
|
14
|
+
@scope_only = scope_only
|
|
15
|
+
@server_filtered = server_filtered
|
|
16
|
+
@attributes = attributes
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def view_template(&block)
|
|
20
|
+
heading_id = @heading ? "cmdk-heading-#{SecureRandom.hex(4)}" : nil
|
|
21
|
+
|
|
22
|
+
div(**merged(group_attributes, @attributes)) do
|
|
23
|
+
if @heading
|
|
24
|
+
div('cmdk-group-heading' => '', aria_hidden: 'true', id: heading_id) { @heading }
|
|
25
|
+
end
|
|
26
|
+
div('cmdk-group-items' => '', role: 'group', aria_labelledby: heading_id) do
|
|
27
|
+
block ? block.call : nil
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def group_attributes
|
|
35
|
+
data = {}
|
|
36
|
+
value = @value || (@heading if @heading.is_a?(String))
|
|
37
|
+
data[:value] = value if value
|
|
38
|
+
data[:cmdk_force_mount] = '' if @force_mount
|
|
39
|
+
data[:cmdk_scope] = @scope if @scope
|
|
40
|
+
data[:cmdk_scope_only] = '' if @scope_only
|
|
41
|
+
data[:cmdk_server_filtered] = '' if @server_filtered
|
|
42
|
+
|
|
43
|
+
{ 'cmdk-group' => '', role: 'presentation', data: data }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/cmdk/input.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Cmdk
|
|
2
|
+
# Command menu search input. Port of `<Command.Input>` from cmdk.
|
|
3
|
+
# `id`, `aria-controls`, `aria-labelledby` and `aria-activedescendant`
|
|
4
|
+
# are wired up by the JS runtime at init.
|
|
5
|
+
class Input < Base
|
|
6
|
+
# `enterkeyhint:` labels the mobile keyboard's action key (pass nil to omit).
|
|
7
|
+
def initialize(value: nil, enterkeyhint: 'go', **attributes)
|
|
8
|
+
@value = value
|
|
9
|
+
@enterkeyhint = enterkeyhint
|
|
10
|
+
@attributes = attributes
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def view_template
|
|
14
|
+
input(**merged(input_attributes, @attributes))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def input_attributes
|
|
20
|
+
{
|
|
21
|
+
'cmdk-input' => '',
|
|
22
|
+
type: 'text',
|
|
23
|
+
value: @value,
|
|
24
|
+
autocomplete: 'off',
|
|
25
|
+
autocorrect: 'off',
|
|
26
|
+
spellcheck: 'false',
|
|
27
|
+
role: 'combobox',
|
|
28
|
+
aria_autocomplete: 'list',
|
|
29
|
+
aria_expanded: 'true',
|
|
30
|
+
enterkeyhint: @enterkeyhint
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/cmdk/item.rb
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Cmdk
|
|
2
|
+
# Command menu item. Port of `<Command.Item>` from cmdk.
|
|
3
|
+
#
|
|
4
|
+
# Like the React version, `value` is inferred from the rendered text content
|
|
5
|
+
# when not given (the runtime assigns `data-value` at init). `onSelect`
|
|
6
|
+
# becomes a bubbling `cmdk-item-select` CustomEvent with `detail.value`.
|
|
7
|
+
# `href:` is a Turbo-friendly extension: the runtime visits the URL on select.
|
|
8
|
+
class Item < Base
|
|
9
|
+
def initialize(value: nil, keywords: nil, disabled: false, force_mount: false, href: nil,
|
|
10
|
+
scope: nil, scope_only: false, enters_scope: nil, server_filtered: false,
|
|
11
|
+
hint: nil, kbd: nil, **attributes)
|
|
12
|
+
@value = value
|
|
13
|
+
@keywords = keywords
|
|
14
|
+
@disabled = disabled
|
|
15
|
+
@force_mount = force_mount
|
|
16
|
+
@href = href
|
|
17
|
+
@scope = scope
|
|
18
|
+
@scope_only = scope_only
|
|
19
|
+
@enters_scope = enters_scope
|
|
20
|
+
@server_filtered = server_filtered
|
|
21
|
+
@hint = hint
|
|
22
|
+
@kbd = kbd
|
|
23
|
+
@attributes = attributes
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def view_template(&block)
|
|
27
|
+
div(**merged(item_attributes, @attributes)) { block ? block.call : nil }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def item_attributes
|
|
33
|
+
data = { disabled: @disabled.to_s, selected: 'false' }
|
|
34
|
+
data[:value] = @value if @value
|
|
35
|
+
data[:cmdk_keywords] = Array(@keywords).join(' ') if @keywords
|
|
36
|
+
data[:cmdk_force_mount] = '' if @force_mount
|
|
37
|
+
data[:href] = @href if @href
|
|
38
|
+
data[:cmdk_scope] = @scope if @scope
|
|
39
|
+
data[:cmdk_scope_only] = '' if @scope_only
|
|
40
|
+
data[:cmdk_enters_scope] = @enters_scope if @enters_scope
|
|
41
|
+
data[:cmdk_server_filtered] = '' if @server_filtered
|
|
42
|
+
data[:cmdk_hint] = @hint if @hint
|
|
43
|
+
data[:cmdk_kbd] = @kbd if @kbd
|
|
44
|
+
|
|
45
|
+
{
|
|
46
|
+
'cmdk-item' => '',
|
|
47
|
+
role: 'option',
|
|
48
|
+
aria_disabled: @disabled.to_s,
|
|
49
|
+
aria_selected: 'false',
|
|
50
|
+
data: data
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/cmdk/list.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Cmdk
|
|
2
|
+
# Contains items, groups and separators. Port of `<Command.List>` from cmdk.
|
|
3
|
+
# The JS runtime keeps the `--cmdk-list-height` CSS variable updated on this
|
|
4
|
+
# element so the list height can be animated.
|
|
5
|
+
class List < Base
|
|
6
|
+
def initialize(label: 'Suggestions', **attributes)
|
|
7
|
+
@label = label
|
|
8
|
+
@attributes = attributes
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def view_template(&block)
|
|
12
|
+
div(**merged({ 'cmdk-list' => '', role: 'listbox', tabindex: '-1', aria_label: @label }, @attributes)) do
|
|
13
|
+
div('cmdk-list-sizer' => '') { block ? block.call : nil }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/cmdk/loading.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Cmdk
|
|
2
|
+
# Loading indicator for asynchronous items. Port of `<Command.Loading>`.
|
|
3
|
+
# Conditionally render this yourself while loading, as in React cmdk.
|
|
4
|
+
class Loading < Base
|
|
5
|
+
def initialize(progress: nil, label: 'Loading...', **attributes)
|
|
6
|
+
@progress = progress
|
|
7
|
+
@label = label
|
|
8
|
+
@attributes = attributes
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def view_template(&block)
|
|
12
|
+
defaults = {
|
|
13
|
+
'cmdk-loading' => '',
|
|
14
|
+
role: 'progressbar',
|
|
15
|
+
aria_valuenow: @progress,
|
|
16
|
+
aria_valuemin: '0',
|
|
17
|
+
aria_valuemax: '100',
|
|
18
|
+
aria_label: @label
|
|
19
|
+
}
|
|
20
|
+
div(**merged(defaults, @attributes)) do
|
|
21
|
+
div(aria_hidden: 'true') { block ? block.call : nil }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/cmdk/root.rb
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Cmdk
|
|
2
|
+
# Command menu root. Port of `<Command>` from cmdk.
|
|
3
|
+
#
|
|
4
|
+
# React props map to data attributes consumed by the JS runtime:
|
|
5
|
+
# shouldFilter, loop, vimBindings, disablePointerSelection, defaultValue.
|
|
6
|
+
# `onValueChange` becomes a bubbling `cmdk-value-change` CustomEvent;
|
|
7
|
+
# a custom `filter` can be registered via `Cmdk.setFilter(rootEl, fn)` in JS.
|
|
8
|
+
#
|
|
9
|
+
# `scopes:` enables scoped search (an extension over the React API): pass
|
|
10
|
+
# scope names (`scopes: %w[user doc]`). Typing the scope-picker prefix
|
|
11
|
+
# ("/" by default, override with `scope_picker: ':'`, disable with
|
|
12
|
+
# `scope_picker: false`) suggests `Cmdk::Item(enters_scope:)` items;
|
|
13
|
+
# committing one — or typing the name out ("/user ") — pins the scope as a
|
|
14
|
+
# pill before the input. The rest of the input is then matched only against
|
|
15
|
+
# items/groups tagged with the same `scope:`. Pass `active_scope:` to
|
|
16
|
+
# server-render an already-pinned scope.
|
|
17
|
+
class Root < Base
|
|
18
|
+
def initialize(label: nil, default_value: nil, should_filter: true, loop: false,
|
|
19
|
+
vim_bindings: true, disable_pointer_selection: false, scopes: nil,
|
|
20
|
+
scope_picker: nil, active_scope: nil, **attributes)
|
|
21
|
+
@label = label
|
|
22
|
+
@default_value = default_value
|
|
23
|
+
@should_filter = should_filter
|
|
24
|
+
@loop = loop
|
|
25
|
+
@vim_bindings = vim_bindings
|
|
26
|
+
@disable_pointer_selection = disable_pointer_selection
|
|
27
|
+
@scopes = scopes
|
|
28
|
+
@scope_picker = scope_picker
|
|
29
|
+
@active_scope = active_scope
|
|
30
|
+
@attributes = attributes
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def view_template(&block)
|
|
34
|
+
div(**merged(root_attributes, @attributes)) do
|
|
35
|
+
label('cmdk-label' => '', style: SR_ONLY_STYLE) { @label }
|
|
36
|
+
yield_content(&block)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def yield_content(&block)
|
|
43
|
+
block ? block.call : nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def root_attributes
|
|
47
|
+
data = {}
|
|
48
|
+
data[:cmdk_should_filter] = 'false' unless @should_filter
|
|
49
|
+
data[:cmdk_loop] = '' if @loop
|
|
50
|
+
data[:cmdk_vim_bindings] = 'false' unless @vim_bindings
|
|
51
|
+
data[:cmdk_disable_pointer_selection] = '' if @disable_pointer_selection
|
|
52
|
+
data[:cmdk_default_value] = @default_value if @default_value
|
|
53
|
+
data[:cmdk_scopes] = Array(@scopes).join(' ') if @scopes
|
|
54
|
+
data[:cmdk_active_scope] = @active_scope if @active_scope
|
|
55
|
+
unless @scope_picker.nil?
|
|
56
|
+
data[:cmdk_scope_picker] = @scope_picker == false ? 'false' : @scope_picker
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
{ 'cmdk-root' => '', tabindex: '-1', data: data }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Cmdk
|
|
2
|
+
# Visual and semantic separator. Port of `<Command.Separator>`.
|
|
3
|
+
# Hidden by the runtime while a search query is present, unless `always_render:`.
|
|
4
|
+
class Separator < Base
|
|
5
|
+
def initialize(always_render: false, **attributes)
|
|
6
|
+
@always_render = always_render
|
|
7
|
+
@attributes = attributes
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def view_template
|
|
11
|
+
defaults = { 'cmdk-separator' => '', role: 'separator' }
|
|
12
|
+
defaults[:data] = { cmdk_always_render: '' } if @always_render
|
|
13
|
+
div(**merged(defaults, @attributes))
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/cmdk/version.rb
ADDED
data/lib/cmdk.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'date' # phlex 2.4 references Date in attribute generation without requiring it
|
|
2
|
+
require 'phlex'
|
|
3
|
+
|
|
4
|
+
# Phlex port of the cmdk React command menu. Renders the same markup contract
|
|
5
|
+
# (cmdk-* attributes, ARIA roles) as the React package; runtime behavior is
|
|
6
|
+
# provided by assets/js/cmdk.js.
|
|
7
|
+
module Cmdk
|
|
8
|
+
extend Phlex::Kit
|
|
9
|
+
|
|
10
|
+
# Absolute path to the JS runtime, for serving or copying into asset pipelines.
|
|
11
|
+
def self.javascript_path
|
|
12
|
+
File.expand_path('../assets/js/cmdk.js', __dir__)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Absolute path to the optional themes stylesheet (cmdk-vercel, cmdk-linear,
|
|
16
|
+
# cmdk-raycast). Plain dependency-free CSS — serve it, copy it, or import it
|
|
17
|
+
# into a Tailwind build.
|
|
18
|
+
def self.stylesheet_path
|
|
19
|
+
File.expand_path('../assets/css/cmdk_themes.css', __dir__)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Absolute path to the optional Stimulus base controller (requires
|
|
23
|
+
# @hotwired/stimulus and the runtime pinned/aliased as `cmdk`).
|
|
24
|
+
def self.stimulus_controller_path
|
|
25
|
+
File.expand_path('../assets/js/cmdk_controller.js', __dir__)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
require_relative 'cmdk/version'
|
|
30
|
+
require_relative 'cmdk/base'
|
|
31
|
+
require_relative 'cmdk/root'
|
|
32
|
+
require_relative 'cmdk/input'
|
|
33
|
+
require_relative 'cmdk/list'
|
|
34
|
+
require_relative 'cmdk/item'
|
|
35
|
+
require_relative 'cmdk/group'
|
|
36
|
+
require_relative 'cmdk/separator'
|
|
37
|
+
require_relative 'cmdk/empty'
|
|
38
|
+
require_relative 'cmdk/loading'
|
|
39
|
+
require_relative 'cmdk/footer'
|
|
40
|
+
require_relative 'cmdk/dialog'
|
data/lib/phlex-cmdk.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require_relative 'cmdk'
|
metadata
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: phlex-cmdk
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Leon Gieser
|
|
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: phlex
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '3'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '2.0'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '3'
|
|
32
|
+
description: Phlex components and a dependency-free JS runtime providing feature parity
|
|
33
|
+
with the cmdk React command menu, plus scoped search, footer hints and ready-made
|
|
34
|
+
themes. Agnostic about styling (CSS, SCSS or Tailwind) and JS wiring (plain listeners
|
|
35
|
+
or Stimulus); progressively enhances with Turbo when present.
|
|
36
|
+
email:
|
|
37
|
+
- leon.gieser@gmail.com
|
|
38
|
+
executables: []
|
|
39
|
+
extensions: []
|
|
40
|
+
extra_rdoc_files: []
|
|
41
|
+
files:
|
|
42
|
+
- CHANGELOG.md
|
|
43
|
+
- LICENSE.md
|
|
44
|
+
- README.md
|
|
45
|
+
- assets/css/cmdk_themes.css
|
|
46
|
+
- assets/js/cmdk.js
|
|
47
|
+
- assets/js/cmdk_controller.js
|
|
48
|
+
- lib/cmdk.rb
|
|
49
|
+
- lib/cmdk/base.rb
|
|
50
|
+
- lib/cmdk/dialog.rb
|
|
51
|
+
- lib/cmdk/empty.rb
|
|
52
|
+
- lib/cmdk/footer.rb
|
|
53
|
+
- lib/cmdk/group.rb
|
|
54
|
+
- lib/cmdk/input.rb
|
|
55
|
+
- lib/cmdk/item.rb
|
|
56
|
+
- lib/cmdk/list.rb
|
|
57
|
+
- lib/cmdk/loading.rb
|
|
58
|
+
- lib/cmdk/root.rb
|
|
59
|
+
- lib/cmdk/separator.rb
|
|
60
|
+
- lib/cmdk/version.rb
|
|
61
|
+
- lib/phlex-cmdk.rb
|
|
62
|
+
homepage: https://github.com/leongieser/phlex-cmdk
|
|
63
|
+
licenses:
|
|
64
|
+
- MIT
|
|
65
|
+
metadata:
|
|
66
|
+
homepage_uri: https://github.com/leongieser/phlex-cmdk
|
|
67
|
+
source_code_uri: https://github.com/leongieser/phlex-cmdk
|
|
68
|
+
changelog_uri: https://github.com/leongieser/phlex-cmdk/blob/main/CHANGELOG.md
|
|
69
|
+
rubygems_mfa_required: 'true'
|
|
70
|
+
rdoc_options: []
|
|
71
|
+
require_paths:
|
|
72
|
+
- lib
|
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '3.2'
|
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
requirements: []
|
|
84
|
+
rubygems_version: 4.0.10
|
|
85
|
+
specification_version: 4
|
|
86
|
+
summary: 'Fast, composable command menu for Phlex: a port of cmdk (React).'
|
|
87
|
+
test_files: []
|