command_deck 0.3.1 → 0.3.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 990b9baead655bf1d768061f4df0d19491dd85b4834e9f6989b7a6a81a866ca1
4
- data.tar.gz: 5864197d0b142338c5183f0a2fbd8ba4a0952fe20d011fbd88c9922305a561ce
3
+ metadata.gz: ebceefafcd11660d8efdd358ad30b1d779b236d1cd135286b9fdb79871dd11bf
4
+ data.tar.gz: 9d4bffc3981b9959dfd491d4d17a5f1e8e89465f54c461d20ffa63eb9b62328a
5
5
  SHA512:
6
- metadata.gz: 1c7c1d4813248bb98accc8a5068c981e03ea5230ba7bb34de805edff11e860978c93d7e8d8885f0bce954b4b03db0409e92e1fe90b79e73c9e5ec98e770056c4
7
- data.tar.gz: 0bb4c80779c05f94eefbc66e6f9182e64004f2c991b137b270c7d0a97f55d2a6ea80faf29ce0029f857881965187ec92ef1ad7be1818d6b2b5746533b07c07ce
6
+ metadata.gz: 9ea6c387e844f38d2cbdf0772b53979df7c93be42b8626f7be1120e61e9a052ae42fa0bb53bab411af1572723ce6824743137f16e1bf20ce040951c06ec7eda2
7
+ data.tar.gz: 11969e5bff8030f91e81cd4a6ddf466fea747d35843da09661377e17f835d847b3dc9c185be941da7ca63597f88877965e6748c4b38214ec71dadfa8588dccf8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## [0.3.2] - 2025-10-15
4
+
5
+ ### Added
6
+
7
+ - **Auto-populate feature**: New `auto_populate: true` option for params that automatically fills text/number inputs with values from selector meta data
8
+ - **Dark mode support**: Panel selector now properly respects dark theme settings
9
+ - **Form accessibility improvements**: All form inputs now have proper `id`, `name`, `for`, and `autocomplete` attributes for better accessibility and browser compatibility
10
+
11
+ ### Fixed
12
+
13
+ - **Dropdown text overflow**: Long labels in selectors are now properly truncated (50 chars for action params, 35 chars for panel selector) with full text shown on hover
14
+ - **Select2 conflict prevention**: Added `data-select-type="default"` to prevent third-party select enhancement libraries from interfering with Command Deck selectors
15
+ - **CSS cascade issues**: Fixed dark mode styles not applying due to CSS specificity problems
16
+
17
+ ### Changed
18
+
19
+ - **DRY code improvements**: Extracted label truncation logic into shared `truncateLabel()` utility function in `core/dom.js`
20
+ - Better hint display: "Current:" hints now only show for boolean values (ON/OFF badges), while string values are populated directly in inputs
21
+
3
22
  ## [0.3.1] - 2025-10-10
4
23
 
5
24
  ### Fixed
data/README.md CHANGED
@@ -70,6 +70,26 @@ module CommandDeck::Panels
70
70
  { chosen: p[:color] }
71
71
  end
72
72
  end
73
+
74
+ action 'Update Setting', key: 'utils.update_setting' do
75
+ # Selector with current values in meta
76
+ param :setting_name, :selector,
77
+ collection: -> {
78
+ # Example: fetch settings with current values
79
+ [
80
+ { label: 'Max Users (100)', value: 'max_users', meta: { value: 100 } },
81
+ { label: 'Timeout (30)', value: 'timeout', meta: { value: 30 } }
82
+ ]
83
+ }
84
+
85
+ # Auto-populate this input with the value from meta
86
+ param :new_value, :string, required: true, auto_populate: true
87
+
88
+ perform do |p, _ctx|
89
+ # Update the setting with new value
90
+ { ok: true, setting: p[:setting_name], new_value: p[:new_value] }
91
+ end
92
+ end
73
93
  end
74
94
  end
75
95
  end
@@ -134,6 +154,7 @@ Common param options:
134
154
 
135
155
  - `label:` String – UI label. Defaults to a humanized `name`.
136
156
  - `required:` true/false – disables Run button until filled. Default: false.
157
+ - `auto_populate:` true/false – for text/number inputs, automatically populate with value from previous selector's `meta: { value: ... }`. Default: false.
137
158
 
138
159
  Selector-specific options:
139
160
 
@@ -65,7 +65,8 @@ module CommandDeck
65
65
  name: param[:name],
66
66
  type: param[:type].to_s,
67
67
  label: param[:label] || param[:name].to_s.tr("_", " ").capitalize,
68
- required: param.key?(:required) ? param[:required] : false
68
+ required: param.key?(:required) ? param[:required] : false,
69
+ auto_populate: param.key?(:auto_populate) ? param[:auto_populate] : false
69
70
  }
70
71
  end
71
72
 
@@ -56,8 +56,7 @@
56
56
  #command-deck-panel.cd-theme-dark .cd-menu-item,
57
57
  #command-deck-panel.cd-theme-dark .cd-form input[type="text"],
58
58
  #command-deck-panel.cd-theme-dark .cd-form input[type="number"],
59
- #command-deck-panel.cd-theme-dark .cd-form select,
60
- #command-deck-panel.cd-theme-dark #cd-panel-selector select {
59
+ #command-deck-panel.cd-theme-dark .cd-form select {
61
60
  background: #111; /* near-black */
62
61
  color: #eee;
63
62
  border-color: #333;
@@ -144,6 +143,22 @@
144
143
  border: 1px solid #ddd;
145
144
  border-radius: 6px;
146
145
  background: #fff;
146
+ max-width: 200px;
147
+ overflow: hidden;
148
+ text-overflow: ellipsis;
149
+ white-space: nowrap;
150
+ }
151
+
152
+ #cd-panel-selector select option {
153
+ overflow: hidden;
154
+ text-overflow: ellipsis;
155
+ white-space: nowrap;
156
+ }
157
+
158
+ #command-deck-panel.cd-theme-dark #cd-panel-selector select {
159
+ background: #111;
160
+ color: #eee;
161
+ border-color: #333;
147
162
  }
148
163
 
149
164
  #cd-settings-panel {
@@ -247,6 +262,20 @@
247
262
  border-radius: 6px;
248
263
  }
249
264
 
265
+ .cd-form select {
266
+ max-width: 100%;
267
+ overflow: hidden;
268
+ text-overflow: ellipsis;
269
+ white-space: nowrap;
270
+ }
271
+
272
+ .cd-form select option {
273
+ overflow: hidden;
274
+ text-overflow: ellipsis;
275
+ white-space: nowrap;
276
+ max-width: 100%;
277
+ }
278
+
250
279
  .cd-actions-row {
251
280
  display: flex;
252
281
  justify-content: flex-end;
@@ -33,3 +33,8 @@ export function el(tag, attrs, children) {
33
33
  export function jsonPretty(obj) {
34
34
  try { return JSON.stringify(obj, null, 2); } catch(_) { return String(obj); }
35
35
  }
36
+
37
+ export function truncateLabel(text, maxLength = 50) {
38
+ const str = String(text);
39
+ return str.length > maxLength ? str.substring(0, maxLength) + '...' : str;
40
+ }
@@ -1,4 +1,4 @@
1
- import { el } from '../../core/dom.js';
1
+ import { el, truncateLabel } from '../../core/dom.js';
2
2
  import { store } from '../../core/store.js';
3
3
 
4
4
  export class ActionForm {
@@ -22,26 +22,30 @@ export class ActionForm {
22
22
  const inputs = {};
23
23
  const paramsMeta = {};
24
24
 
25
- (this.action.params || []).forEach(p => {
25
+ (this.action.params || []).forEach((p, paramIndex) => {
26
+ const inputId = `cd-input-${this.action.key}-${p.name}-${paramIndex}`;
26
27
  const labelChildren = [p.label || p.name];
27
28
  if (p.required === true) {
28
29
  labelChildren.push(el('span', { class: 'cd-required', title: 'Required' }, ['*']));
29
30
  }
30
- const label = el('label', null, labelChildren);
31
+ const label = el('label', { for: inputId }, labelChildren);
31
32
  let input;
32
33
  if (p.type === 'boolean') {
33
- input = el('input', { type: 'checkbox' });
34
+ input = el('input', { type: 'checkbox', id: inputId, name: p.name, autocomplete: 'off' });
34
35
  } else if (p.type === 'integer') {
35
- input = el('input', { type: 'number', step: '1' });
36
+ input = el('input', { type: 'number', step: '1', id: inputId, name: p.name, autocomplete: 'off' });
36
37
  } else if (p.type === 'selector' || (p.choices && p.choices.length)) {
37
- input = el('select');
38
+ input = el('select', { 'data-select-type': 'default', id: inputId, name: p.name, autocomplete: 'off' });
38
39
  if (p.include_blank) {
39
40
  input.appendChild(el('option', { value: '' }, ['']));
40
41
  }
41
42
  (p.choices || []).forEach(ch => {
42
43
  const attrs = { 'data-val': JSON.stringify(ch.value) };
43
44
  if (ch.meta != null) attrs['data-meta'] = JSON.stringify(ch.meta);
44
- const opt = el('option', attrs, [ch.label != null ? String(ch.label) : String(ch.value)]);
45
+ const fullLabel = ch.label != null ? String(ch.label) : String(ch.value);
46
+ const displayLabel = truncateLabel(fullLabel, 50);
47
+ attrs['title'] = fullLabel;
48
+ const opt = el('option', attrs, [displayLabel]);
45
49
  input.appendChild(opt);
46
50
  });
47
51
 
@@ -65,7 +69,7 @@ export class ActionForm {
65
69
  }
66
70
  }
67
71
  } else {
68
- input = el('input', { type: 'text' });
72
+ input = el('input', { type: 'text', id: inputId, name: p.name, autocomplete: 'off' });
69
73
  }
70
74
  if (p.required === true && input) {
71
75
  input.setAttribute('aria-required', 'true');
@@ -94,15 +98,43 @@ export class ActionForm {
94
98
  }
95
99
  } catch(_) { hint.textContent = ''; }
96
100
  };
101
+
102
+ const populateRelatedInput = () => {
103
+ const sel = input.options[input.selectedIndex];
104
+ if (!sel) return;
105
+ const metaRaw = sel.getAttribute('data-meta');
106
+ if (!metaRaw) return;
107
+ try {
108
+ const meta = JSON.parse(metaRaw);
109
+ if (meta.value != null && typeof meta.enabled !== 'boolean') {
110
+ // Find the next string/number input after this selector
111
+ const currentParamIndex = (this.action.params || []).findIndex(param => param.name === p.name);
112
+ if (currentParamIndex >= 0 && currentParamIndex < (this.action.params || []).length - 1) {
113
+ const nextParam = this.action.params[currentParamIndex + 1];
114
+ // Only auto-populate if the next param has auto_populate explicitly enabled (defaults to false)
115
+ const shouldAutoPopulate = nextParam.auto_populate === true;
116
+ const nextInput = inputs[nextParam.name];
117
+ if (shouldAutoPopulate && nextInput && (nextInput.type === 'text' || nextInput.type === 'number')) {
118
+ nextInput.value = String(meta.value);
119
+ }
120
+ }
121
+ }
122
+ } catch(_) { }
123
+ };
124
+
97
125
  input.addEventListener('change', () => {
98
126
  const sel = input.options[input.selectedIndex];
99
127
  const raw = sel ? sel.getAttribute('data-val') : '';
100
128
  const selKey = `sel:${this.getCurrentPanelKey() || ''}:${this.action.key}:${p.name}`;
101
129
  store.set(selKey, raw || '');
102
130
  updateHint();
131
+ populateRelatedInput();
103
132
  validate();
104
133
  });
105
- setTimeout(updateHint, 0);
134
+ setTimeout(() => {
135
+ updateHint();
136
+ populateRelatedInput();
137
+ }, 0);
106
138
  form.appendChild(hint);
107
139
  }
108
140
 
@@ -1,10 +1,15 @@
1
- import { el } from '../core/dom.js';
1
+ import { el, truncateLabel } from '../core/dom.js';
2
2
 
3
3
  export class PanelSelector {
4
4
  constructor(onSelect) {
5
5
  this.onSelect = onSelect;
6
6
  this.root = el('div', { id: 'cd-panel-selector' });
7
- this.select = el('select');
7
+ this.select = el('select', {
8
+ 'data-select-type': 'default',
9
+ id: 'cd-panel-select',
10
+ name: 'panel',
11
+ autocomplete: 'off'
12
+ });
8
13
  this.root.appendChild(this.select);
9
14
 
10
15
  this.select.addEventListener('change', () => {
@@ -19,8 +24,10 @@ export class PanelSelector {
19
24
  // panels: [{ key, title, owner, group }]
20
25
  this.select.textContent = '';
21
26
  (panels || []).forEach(p => {
22
- const label = [p.group, p.title].filter(Boolean).join(' • ');
23
- const opt = el('option', { value: p.key }, [label || p.title || p.key]);
27
+ const fullLabel = [p.group, p.title].filter(Boolean).join(' • ');
28
+ const label = fullLabel || p.title || p.key;
29
+ const displayLabel = truncateLabel(label, 35);
30
+ const opt = el('option', { value: p.key, title: label }, [displayLabel]);
24
31
  this.select.appendChild(opt);
25
32
  });
26
33
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CommandDeck
4
- VERSION = "0.3.1"
4
+ VERSION = "0.3.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: command_deck
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - crowrojas
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-10-10 00:00:00.000000000 Z
11
+ date: 2025-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack