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.
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Cmdk
2
+ VERSION = '0.1.0'
3
+ end
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: []