shadcn_phlexcomponents 0.1.14 → 0.1.16

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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/app/javascript/controllers/accordion_controller.js +107 -0
  3. data/app/javascript/controllers/alert_dialog_controller.js +7 -0
  4. data/app/javascript/controllers/avatar_controller.js +14 -0
  5. data/app/javascript/controllers/checkbox_controller.js +29 -0
  6. data/app/javascript/controllers/collapsible_controller.js +39 -0
  7. data/app/javascript/controllers/combobox_controller.js +278 -0
  8. data/app/javascript/controllers/command_controller.js +207 -0
  9. data/app/javascript/controllers/date_picker_controller.js +258 -0
  10. data/app/javascript/controllers/date_range_picker_controller.js +200 -0
  11. data/app/javascript/controllers/dialog_controller.js +83 -0
  12. data/app/javascript/controllers/dropdown_menu_controller.js +238 -0
  13. data/app/javascript/controllers/dropdown_menu_sub_controller.js +118 -0
  14. data/app/javascript/controllers/form_field_controller.js +20 -0
  15. data/app/javascript/controllers/hover_card_controller.js +73 -0
  16. data/app/javascript/controllers/loading_button_controller.js +14 -0
  17. data/app/javascript/controllers/popover_controller.js +90 -0
  18. data/app/javascript/controllers/progress_controller.js +14 -0
  19. data/app/javascript/controllers/radio_group_controller.js +80 -0
  20. data/app/javascript/controllers/select_controller.js +265 -0
  21. data/app/javascript/controllers/sidebar_controller.js +29 -0
  22. data/app/javascript/controllers/sidebar_trigger_controller.js +15 -0
  23. data/app/javascript/controllers/slider_controller.js +82 -0
  24. data/app/javascript/controllers/switch_controller.js +26 -0
  25. data/app/javascript/controllers/tabs_controller.js +66 -0
  26. data/app/javascript/controllers/theme_switcher_controller.js +32 -0
  27. data/app/javascript/controllers/toast_container_controller.js +48 -0
  28. data/app/javascript/controllers/toast_controller.js +22 -0
  29. data/app/javascript/controllers/toggle_controller.js +20 -0
  30. data/app/javascript/controllers/toggle_group_controller.js +20 -0
  31. data/app/javascript/controllers/tooltip_controller.js +79 -0
  32. data/app/javascript/shadcn_phlexcomponents.js +60 -0
  33. data/app/javascript/utils/command.js +448 -0
  34. data/app/javascript/utils/floating_ui.js +160 -0
  35. data/app/javascript/utils/index.js +288 -0
  36. data/app/{javascript → typescript}/utils/index.ts +7 -0
  37. data/lib/install/install_shadcn_phlexcomponents.rb +10 -3
  38. data/lib/shadcn_phlexcomponents/version.rb +1 -1
  39. metadata +69 -35
  40. /data/app/{javascript → typescript}/controllers/accordion_controller.ts +0 -0
  41. /data/app/{javascript → typescript}/controllers/alert_dialog_controller.ts +0 -0
  42. /data/app/{javascript → typescript}/controllers/avatar_controller.ts +0 -0
  43. /data/app/{javascript → typescript}/controllers/checkbox_controller.ts +0 -0
  44. /data/app/{javascript → typescript}/controllers/collapsible_controller.ts +0 -0
  45. /data/app/{javascript → typescript}/controllers/combobox_controller.ts +0 -0
  46. /data/app/{javascript → typescript}/controllers/command_controller.ts +0 -0
  47. /data/app/{javascript → typescript}/controllers/date_picker_controller.ts +0 -0
  48. /data/app/{javascript → typescript}/controllers/date_range_picker_controller.ts +0 -0
  49. /data/app/{javascript → typescript}/controllers/dialog_controller.ts +0 -0
  50. /data/app/{javascript → typescript}/controllers/dropdown_menu_controller.ts +0 -0
  51. /data/app/{javascript → typescript}/controllers/dropdown_menu_sub_controller.ts +0 -0
  52. /data/app/{javascript → typescript}/controllers/form_field_controller.ts +0 -0
  53. /data/app/{javascript → typescript}/controllers/hover_card_controller.ts +0 -0
  54. /data/app/{javascript → typescript}/controllers/loading_button_controller.ts +0 -0
  55. /data/app/{javascript → typescript}/controllers/popover_controller.ts +0 -0
  56. /data/app/{javascript → typescript}/controllers/progress_controller.ts +0 -0
  57. /data/app/{javascript → typescript}/controllers/radio_group_controller.ts +0 -0
  58. /data/app/{javascript → typescript}/controllers/select_controller.ts +0 -0
  59. /data/app/{javascript → typescript}/controllers/sidebar_controller.ts +0 -0
  60. /data/app/{javascript → typescript}/controllers/sidebar_trigger_controller.ts +0 -0
  61. /data/app/{javascript → typescript}/controllers/slider_controller.ts +0 -0
  62. /data/app/{javascript → typescript}/controllers/switch_controller.ts +0 -0
  63. /data/app/{javascript → typescript}/controllers/tabs_controller.ts +0 -0
  64. /data/app/{javascript → typescript}/controllers/theme_switcher_controller.ts +0 -0
  65. /data/app/{javascript → typescript}/controllers/toast_container_controller.ts +0 -0
  66. /data/app/{javascript → typescript}/controllers/toast_controller.ts +0 -0
  67. /data/app/{javascript → typescript}/controllers/toggle_controller.ts +0 -0
  68. /data/app/{javascript → typescript}/controllers/toggle_group_controller.ts +0 -0
  69. /data/app/{javascript → typescript}/controllers/tooltip_controller.ts +0 -0
  70. /data/app/{javascript → typescript}/shadcn_phlexcomponents.ts +0 -0
  71. /data/app/{javascript → typescript}/utils/command.ts +0 -0
  72. /data/app/{javascript → typescript}/utils/floating_ui.ts +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e6ead95ebdeef8eac55b2ee6101b2fbdb7fb90c7d12f8f489bdb2f45ad507915
4
- data.tar.gz: 6f067373fd86d04f7e2478b639c3d5021a265931b648a6729869d1c9bb472bee
3
+ metadata.gz: dfa235b75ee12df9314407a533502fbda6e54d16292eec4c2b9c3ed401d2344f
4
+ data.tar.gz: 67c348141c2bc1fbc3ea4a29058b6bdc75e8c729bdffc2c4de6c73b4ec7d86e3
5
5
  SHA512:
6
- metadata.gz: c9ca6aacc79c59cc008ca03043616c4483dcc65c9858b0134d61ab0d53497fda9e8f2be85f05e9b1d030fc74af0524f725524d3a6830f79333fd14894188cb89
7
- data.tar.gz: e1d3d10691bef9568c16af2e2f23f1a7bccce7b512e72a6ef1a53e6db3ce85a95fdefaadbf86bcd9ecbe07f9f61a423cda24aa412ad60d849a99d4efc3501653
6
+ metadata.gz: eb9166677cafed3bc87dae8206d584f0702bd66cfd6675987d70cda32eeb8ecaf0f1c62651d2206ca4dfcd57bad060d4c716ee7431f7abe9f2a0a62062ea53b4
7
+ data.tar.gz: 3dc09e749fe056cbf582c0eb8accb97347da9ba506992d5e471fb181f5cbe47249bd970b25b85f97a87e1e7d98e47f61a212c16965486b52b3563332b218bbbe
@@ -0,0 +1,107 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import {
3
+ showContent,
4
+ hideContent,
5
+ getNextEnabledIndex,
6
+ getPreviousEnabledIndex,
7
+ } from "../utils";
8
+ const AccordionController = class extends Controller {
9
+ // targets
10
+ static targets = ["item", "trigger", "content"];
11
+ // values
12
+ static values = { openItems: Array };
13
+ connect() {
14
+ this.multiple = this.element.dataset.multiple === "true";
15
+ }
16
+ contentTargetConnected(content) {
17
+ setTimeout(() => {
18
+ this.setContentHeight(content);
19
+ }, 100);
20
+ }
21
+ toggle(event) {
22
+ const trigger = event.currentTarget;
23
+ const item = this.itemTargets.find((item) => {
24
+ return item.contains(trigger);
25
+ });
26
+ if (!item) return;
27
+ const value = item.dataset.value;
28
+ const isOpen = this.openItemsValue.includes(value);
29
+ if (isOpen) {
30
+ this.openItemsValue = this.openItemsValue.filter((v) => v !== value);
31
+ } else {
32
+ if (this.multiple) {
33
+ this.openItemsValue = [...this.openItemsValue, value];
34
+ } else {
35
+ this.openItemsValue = [value];
36
+ }
37
+ }
38
+ }
39
+ focusTrigger(event) {
40
+ const trigger = event.currentTarget;
41
+ const key = event.key;
42
+ const focusableTriggers = this.triggerTargets.filter(
43
+ (trigger) => !trigger.disabled,
44
+ );
45
+ const index = focusableTriggers.indexOf(trigger);
46
+ let newIndex = 0;
47
+ if (key === "ArrowUp") {
48
+ newIndex = getPreviousEnabledIndex({
49
+ items: focusableTriggers,
50
+ currentIndex: index,
51
+ wrapAround: true,
52
+ });
53
+ } else {
54
+ newIndex = getNextEnabledIndex({
55
+ items: focusableTriggers,
56
+ currentIndex: index,
57
+ wrapAround: true,
58
+ });
59
+ }
60
+ focusableTriggers[newIndex].focus();
61
+ }
62
+ openItemsValueChanged(openItems) {
63
+ this.itemTargets.forEach((item) => {
64
+ const itemValue = item.dataset.value;
65
+ const trigger = this.triggerTargets.find((trigger) =>
66
+ item.contains(trigger),
67
+ );
68
+ const content = this.contentTargets.find((content) =>
69
+ item.contains(content),
70
+ );
71
+ if (openItems.includes(itemValue)) {
72
+ showContent({
73
+ trigger,
74
+ content: content,
75
+ contentContainer: content,
76
+ });
77
+ } else {
78
+ hideContent({
79
+ trigger,
80
+ content: content,
81
+ contentContainer: content,
82
+ });
83
+ }
84
+ });
85
+ }
86
+ setContentHeight(element) {
87
+ const height =
88
+ this.getContentHeight(element) || element.getBoundingClientRect().height;
89
+ element.style.setProperty(
90
+ "--radix-accordion-content-height",
91
+ `${height}px`,
92
+ );
93
+ }
94
+ getContentHeight(el) {
95
+ const clone = el.cloneNode(true);
96
+ Object.assign(clone.style, {
97
+ display: "block",
98
+ position: "absolute",
99
+ visibility: "hidden",
100
+ });
101
+ document.body.appendChild(clone);
102
+ const height = clone.getBoundingClientRect().height;
103
+ document.body.removeChild(clone);
104
+ return height;
105
+ }
106
+ };
107
+ export { AccordionController };
@@ -0,0 +1,7 @@
1
+ import { DialogController } from "./dialog_controller";
2
+ const AlertDialogController = class extends DialogController {
3
+ onDOMClick() {
4
+ return;
5
+ }
6
+ };
7
+ export { AlertDialogController };
@@ -0,0 +1,14 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ const AvatarController = class extends Controller {
3
+ // targets
4
+ static targets = ["image", "fallback"];
5
+ connect() {
6
+ this.imageTarget.onerror = () => {
7
+ if (this.hasFallbackTarget) {
8
+ this.fallbackTarget.classList.remove("hidden");
9
+ }
10
+ this.imageTarget.classList.add("hidden");
11
+ };
12
+ }
13
+ };
14
+ export { AvatarController };
@@ -0,0 +1,29 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ const CheckboxController = class extends Controller {
3
+ // targets
4
+ static targets = ["input", "indicator"];
5
+ // values
6
+ static values = {
7
+ isChecked: Boolean,
8
+ };
9
+ toggle() {
10
+ this.isCheckedValue = !this.isCheckedValue;
11
+ }
12
+ preventDefault(event) {
13
+ event.preventDefault();
14
+ }
15
+ isCheckedValueChanged(isChecked) {
16
+ if (isChecked) {
17
+ this.element.ariaChecked = "true";
18
+ this.element.dataset.state = "checked";
19
+ this.inputTarget.checked = true;
20
+ this.indicatorTarget.classList.remove("hidden");
21
+ } else {
22
+ this.element.ariaChecked = "false";
23
+ this.element.dataset.state = "unchecked";
24
+ this.inputTarget.checked = false;
25
+ this.indicatorTarget.classList.add("hidden");
26
+ }
27
+ }
28
+ };
29
+ export { CheckboxController };
@@ -0,0 +1,39 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { hideContent, showContent } from "../utils";
3
+ const CollapsibleController = class extends Controller {
4
+ // targets
5
+ static targets = ["trigger", "content"];
6
+ // values
7
+ static values = {
8
+ isOpen: Boolean,
9
+ };
10
+ toggle() {
11
+ if (this.isOpenValue) {
12
+ this.close();
13
+ } else {
14
+ this.open();
15
+ }
16
+ }
17
+ open() {
18
+ this.isOpenValue = true;
19
+ }
20
+ close() {
21
+ this.isOpenValue = false;
22
+ }
23
+ isOpenValueChanged(isOpen) {
24
+ if (isOpen) {
25
+ showContent({
26
+ trigger: this.triggerTarget,
27
+ content: this.contentTarget,
28
+ contentContainer: this.contentTarget,
29
+ });
30
+ } else {
31
+ hideContent({
32
+ trigger: this.triggerTarget,
33
+ content: this.contentTarget,
34
+ contentContainer: this.contentTarget,
35
+ });
36
+ }
37
+ }
38
+ };
39
+ export { CollapsibleController };
@@ -0,0 +1,278 @@
1
+ import {
2
+ ON_OPEN_FOCUS_DELAY,
3
+ lockScroll,
4
+ showContent,
5
+ unlockScroll,
6
+ hideContent,
7
+ focusTrigger,
8
+ setGroupLabelsId,
9
+ onClickOutside,
10
+ } from "../utils";
11
+ import { initFloatingUi } from "../utils/floating_ui";
12
+ import { Controller } from "@hotwired/stimulus";
13
+ import Fuse from "fuse.js";
14
+ import {
15
+ scrollToItem,
16
+ highlightItem,
17
+ highlightItemByIndex,
18
+ filteredItemsChanged,
19
+ setItemsGroupId,
20
+ search,
21
+ clearRemoteResults,
22
+ resetState,
23
+ } from "../utils/command";
24
+ import { useClickOutside, useDebounce } from "stimulus-use";
25
+ const ComboboxController = class extends Controller {
26
+ // targets
27
+ static targets = [
28
+ "trigger",
29
+ "triggerText",
30
+ "contentContainer",
31
+ "content",
32
+ "item",
33
+ "group",
34
+ "hiddenInput",
35
+ "searchInput",
36
+ "list",
37
+ "listContainer",
38
+ "empty",
39
+ "loading",
40
+ "error",
41
+ ];
42
+ // values
43
+ static values = {
44
+ isOpen: Boolean,
45
+ selected: String,
46
+ filteredItemIndexes: Array,
47
+ };
48
+ static debounces = ["search"];
49
+ connect() {
50
+ this.orderedItems = [...this.itemTargets];
51
+ this.itemsInnerText = this.itemTargets.map((i) => i.innerText.trim());
52
+ this.fuse = new Fuse(this.itemsInnerText);
53
+ this.filteredItemIndexesValue = Array.from(
54
+ { length: this.itemTargets.length },
55
+ (_, i) => i,
56
+ );
57
+ this.isLoading = false;
58
+ this.filteredItems = this.itemTargets;
59
+ this.isDirty = false;
60
+ this.searchPath = this.element.dataset.searchPath;
61
+ setGroupLabelsId(this);
62
+ setItemsGroupId(this);
63
+ useDebounce(this);
64
+ useClickOutside(this, {
65
+ element: this.contentTarget,
66
+ dispatchEvent: false,
67
+ });
68
+ this.DOMKeydownListener = this.onDOMKeydown.bind(this);
69
+ }
70
+ toggle() {
71
+ if (this.isOpenValue) {
72
+ this.close();
73
+ } else {
74
+ this.open();
75
+ }
76
+ }
77
+ open() {
78
+ this.isOpenValue = true;
79
+ setTimeout(() => {
80
+ this.searchInputTarget.focus();
81
+ let index = 0;
82
+ console.log("this.selectedValue", this.selectedValue);
83
+ if (this.selectedValue) {
84
+ const item = this.filteredItems.find(
85
+ (i) => i.dataset.value === this.selectedValue,
86
+ );
87
+ if (item && !item.dataset.disabled) {
88
+ index = this.filteredItems.indexOf(item);
89
+ }
90
+ }
91
+ this.highlightItemByIndex(index);
92
+ }, ON_OPEN_FOCUS_DELAY);
93
+ }
94
+ close() {
95
+ this.isOpenValue = false;
96
+ resetState(this);
97
+ }
98
+ scrollToItem(index) {
99
+ scrollToItem(this, index);
100
+ }
101
+ highlightItem(event = null, index = null) {
102
+ highlightItem(this, event, index);
103
+ }
104
+ highlightItemByIndex(index) {
105
+ highlightItemByIndex(this, index);
106
+ }
107
+ select(event) {
108
+ let item = undefined;
109
+ if (event instanceof KeyboardEvent) {
110
+ item = this.filteredItems.find((i) => i.dataset.highlighted === "true");
111
+ } else {
112
+ // mouse event
113
+ item = event.currentTarget;
114
+ }
115
+ if (item) {
116
+ this.selectedValue = item.dataset.value;
117
+ // setTimeout is needed for selectedValueChanged to finish executing
118
+ setTimeout(() => {
119
+ this.close();
120
+ }, 100);
121
+ }
122
+ }
123
+ inputKeydown(event) {
124
+ if (event.key === " " && this.searchInputTarget.value.length === 0) {
125
+ event.preventDefault();
126
+ }
127
+ this.hideError();
128
+ this.showList();
129
+ }
130
+ search(event) {
131
+ this.isDirty = true;
132
+ clearRemoteResults(this);
133
+ search(this, event);
134
+ }
135
+ clickOutside(event) {
136
+ onClickOutside(this, event);
137
+ }
138
+ selectedValueChanged(value) {
139
+ const item = this.itemTargets.find((i) => i.dataset.value === value);
140
+ if (item) {
141
+ this.triggerTextTarget.textContent = item.textContent;
142
+ this.itemTargets.forEach((i) => {
143
+ if (i.dataset.value === value) {
144
+ i.setAttribute("aria-selected", "true");
145
+ } else {
146
+ i.setAttribute("aria-selected", "false");
147
+ }
148
+ });
149
+ this.hiddenInputTarget.value = value;
150
+ }
151
+ this.triggerTarget.dataset.hasValue = `${!!value && value.length > 0}`;
152
+ const placeholder = this.triggerTarget.dataset.placeholder;
153
+ if (placeholder && this.triggerTarget.dataset.hasValue === "false") {
154
+ this.triggerTextTarget.textContent = placeholder;
155
+ }
156
+ }
157
+ isOpenValueChanged(isOpen, previousIsOpen) {
158
+ if (isOpen) {
159
+ lockScroll(this.contentTarget.id);
160
+ showContent({
161
+ trigger: this.triggerTarget,
162
+ content: this.contentTarget,
163
+ contentContainer: this.contentContainerTarget,
164
+ setEqualWidth: true,
165
+ });
166
+ this.cleanup = initFloatingUi({
167
+ referenceElement: this.triggerTarget,
168
+ floatingElement: this.contentContainerTarget,
169
+ side: this.contentTarget.dataset.side,
170
+ align: this.contentTarget.dataset.align,
171
+ sideOffset: 4,
172
+ });
173
+ this.setupEventListeners();
174
+ } else {
175
+ unlockScroll(this.contentTarget.id);
176
+ hideContent({
177
+ trigger: this.triggerTarget,
178
+ content: this.contentTarget,
179
+ contentContainer: this.contentContainerTarget,
180
+ });
181
+ if (previousIsOpen) {
182
+ focusTrigger(this.triggerTarget);
183
+ }
184
+ this.cleanupEventListeners();
185
+ }
186
+ }
187
+ filteredItemIndexesValueChanged(filteredItemIndexes) {
188
+ filteredItemsChanged(this, filteredItemIndexes);
189
+ }
190
+ disconnect() {
191
+ this.cleanupEventListeners();
192
+ resetState(this);
193
+ }
194
+ showLoading() {
195
+ this.isLoading = true;
196
+ this.loadingTarget.classList.remove("hidden");
197
+ }
198
+ hideLoading() {
199
+ this.isLoading = false;
200
+ this.loadingTarget.classList.add("hidden");
201
+ }
202
+ showList() {
203
+ this.listTarget.classList.remove("hidden");
204
+ }
205
+ hideList() {
206
+ this.listTarget.classList.add("hidden");
207
+ }
208
+ showError() {
209
+ this.errorTarget.classList.remove("hidden");
210
+ }
211
+ hideError() {
212
+ this.errorTarget.classList.add("hidden");
213
+ }
214
+ showEmpty() {
215
+ this.emptyTarget.classList.remove("hidden");
216
+ }
217
+ hideEmpty() {
218
+ this.emptyTarget.classList.add("hidden");
219
+ }
220
+ showSelectedRemoteItems() {
221
+ const remoteItems = Array.from(
222
+ this.element.querySelectorAll(
223
+ `[data-shadcn-phlexcomponents="${this.identifier}-item"][data-remote='true']`,
224
+ ),
225
+ );
226
+ remoteItems.forEach((i) => {
227
+ const isInsideGroup =
228
+ i.parentElement?.dataset?.shadcnPhlexcomponents ===
229
+ `${this.identifier}-group`;
230
+ if (isInsideGroup) {
231
+ const isRemoteGroup = i.parentElement.dataset.remote === "true";
232
+ if (isRemoteGroup) {
233
+ i.parentElement.classList.remove("hidden");
234
+ }
235
+ }
236
+ i.ariaHidden = "false";
237
+ i.classList.remove("hidden");
238
+ });
239
+ }
240
+ hideSelectedRemoteItems() {
241
+ const remoteItems = Array.from(
242
+ this.element.querySelectorAll(
243
+ `[data-shadcn-phlexcomponents="${this.identifier}-item"][data-remote='true']`,
244
+ ),
245
+ );
246
+ remoteItems.forEach((i) => {
247
+ const isInsideGroup =
248
+ i.parentElement?.dataset?.shadcnPhlexcomponents ===
249
+ `${this.identifier}-group`;
250
+ if (isInsideGroup) {
251
+ const isRemoteGroup = i.parentElement.dataset.remote === "true";
252
+ if (isRemoteGroup) {
253
+ i.parentElement.classList.add("hidden");
254
+ }
255
+ }
256
+ i.ariaHidden = "true";
257
+ i.classList.add("hidden");
258
+ });
259
+ }
260
+ setupEventListeners() {
261
+ document.addEventListener("keydown", this.DOMKeydownListener);
262
+ }
263
+ cleanupEventListeners() {
264
+ document.removeEventListener("keydown", this.DOMKeydownListener);
265
+ if (this.abortController) {
266
+ this.abortController.abort();
267
+ }
268
+ }
269
+ onDOMKeydown(event) {
270
+ if (!this.isOpenValue) return;
271
+ const key = event.key;
272
+ if (["Tab", "Enter"].includes(key)) event.preventDefault();
273
+ if (key === "Escape") {
274
+ this.close();
275
+ }
276
+ }
277
+ };
278
+ export { ComboboxController };
@@ -0,0 +1,207 @@
1
+ import hotkeys from "hotkeys-js";
2
+ import { Controller } from "@hotwired/stimulus";
3
+ import {
4
+ showContent,
5
+ hideContent,
6
+ focusTrigger,
7
+ ON_OPEN_FOCUS_DELAY,
8
+ setGroupLabelsId,
9
+ } from "../utils";
10
+ import {
11
+ scrollToItem,
12
+ highlightItem,
13
+ highlightItemByIndex,
14
+ filteredItemsChanged,
15
+ setItemsGroupId,
16
+ search,
17
+ clearRemoteResults,
18
+ resetState,
19
+ hideError,
20
+ showList,
21
+ } from "../utils/command";
22
+ import { useDebounce, useClickOutside } from "stimulus-use";
23
+ import Fuse from "fuse.js";
24
+ const CommandController = class extends Controller {
25
+ // targets
26
+ static targets = [
27
+ "trigger",
28
+ "content",
29
+ "overlay",
30
+ "item",
31
+ "group",
32
+ "searchInput",
33
+ "list",
34
+ "listContainer",
35
+ "empty",
36
+ "modifierKey",
37
+ "loading",
38
+ "error",
39
+ ];
40
+ // values
41
+ static values = {
42
+ isOpen: Boolean,
43
+ filteredItemIndexes: Array,
44
+ searchUrl: String,
45
+ };
46
+ static debounces = ["search"];
47
+ connect() {
48
+ this.orderedItems = [...this.itemTargets];
49
+ this.itemsInnerText = this.orderedItems.map((i) => i.innerText.trim());
50
+ this.fuse = new Fuse(this.itemsInnerText);
51
+ this.filteredItemIndexesValue = Array.from(
52
+ { length: this.itemTargets.length },
53
+ (_, i) => i,
54
+ );
55
+ this.isLoading = false;
56
+ this.filteredItems = this.itemTargets;
57
+ this.isDirty = false;
58
+ this.searchPath = this.element.dataset.searchPath;
59
+ setGroupLabelsId(this);
60
+ setItemsGroupId(this);
61
+ useDebounce(this);
62
+ useClickOutside(this, {
63
+ element: this.contentTarget,
64
+ dispatchEvent: false,
65
+ });
66
+ this.hotkeyListener = this.onHotkeyPressed.bind(this);
67
+ this.DOMKeydownListener = this.onDOMKeydown.bind(this);
68
+ this.setupHotkeys();
69
+ this.replaceModifierKeyIcon();
70
+ }
71
+ open() {
72
+ this.isOpenValue = true;
73
+ this.highlightItemByIndex(0);
74
+ setTimeout(() => {
75
+ this.searchInputTarget.focus();
76
+ }, ON_OPEN_FOCUS_DELAY);
77
+ }
78
+ close() {
79
+ this.isOpenValue = false;
80
+ resetState(this);
81
+ }
82
+ scrollToItem(index) {
83
+ scrollToItem(this, index);
84
+ }
85
+ highlightItem(event = null, index = null) {
86
+ highlightItem(this, event, index);
87
+ }
88
+ highlightItemByIndex(index) {
89
+ highlightItemByIndex(this, index);
90
+ }
91
+ select(event) {
92
+ let value = null;
93
+ if (event instanceof KeyboardEvent) {
94
+ const item = this.filteredItems.find(
95
+ (i) => i.dataset.highlighted === "true",
96
+ );
97
+ if (item) {
98
+ value = item.dataset.value;
99
+ }
100
+ } else {
101
+ // mouse event
102
+ const item = event.currentTarget;
103
+ value = item.dataset.value;
104
+ }
105
+ if (value) {
106
+ window.Turbo.visit(value);
107
+ this.close();
108
+ }
109
+ }
110
+ inputKeydown(event) {
111
+ if (event.key === " " && this.searchInputTarget.value.length === 0) {
112
+ event.preventDefault();
113
+ }
114
+ hideError(this);
115
+ showList(this);
116
+ }
117
+ search(event) {
118
+ this.isDirty = true;
119
+ clearRemoteResults(this);
120
+ search(this, event);
121
+ }
122
+ clickOutside() {
123
+ this.close();
124
+ }
125
+ isOpenValueChanged(isOpen, previousIsOpen) {
126
+ if (isOpen) {
127
+ showContent({
128
+ trigger: this.triggerTarget,
129
+ content: this.contentTarget,
130
+ contentContainer: this.contentTarget,
131
+ overlay: this.overlayTarget,
132
+ });
133
+ this.setupEventListeners();
134
+ } else {
135
+ hideContent({
136
+ trigger: this.triggerTarget,
137
+ content: this.contentTarget,
138
+ contentContainer: this.contentTarget,
139
+ overlay: this.overlayTarget,
140
+ });
141
+ if (previousIsOpen) {
142
+ focusTrigger(this.triggerTarget);
143
+ }
144
+ this.cleanupEventListeners();
145
+ }
146
+ }
147
+ filteredItemIndexesValueChanged(filteredItemIndexes) {
148
+ filteredItemsChanged(this, filteredItemIndexes);
149
+ }
150
+ disconnect() {
151
+ this.cleanupEventListeners();
152
+ resetState(this);
153
+ if (this.keybinds) {
154
+ hotkeys.unbind(this.keybinds);
155
+ }
156
+ }
157
+ setupHotkeys() {
158
+ const modifierKey = this.element.dataset.modifierKey;
159
+ const shortcutKey = this.element.dataset.shortcutKey;
160
+ let keybinds = "";
161
+ if (modifierKey && shortcutKey) {
162
+ keybinds = `${modifierKey}+${shortcutKey}`;
163
+ if (modifierKey === "ctrl") {
164
+ keybinds += `,cmd-${shortcutKey}`;
165
+ }
166
+ } else if (shortcutKey) {
167
+ keybinds = shortcutKey;
168
+ }
169
+ this.keybinds = keybinds;
170
+ hotkeys(keybinds, this.hotkeyListener);
171
+ }
172
+ onHotkeyPressed(event) {
173
+ event.preventDefault();
174
+ this.open();
175
+ }
176
+ replaceModifierKeyIcon() {
177
+ if (this.hasModifierKeyTarget && this.isMac()) {
178
+ this.modifierKeyTarget.innerHTML = "⌘";
179
+ }
180
+ }
181
+ isMac() {
182
+ const navigator = window.navigator;
183
+ if (navigator.userAgentData) {
184
+ return navigator.userAgentData.platform === "macOS";
185
+ }
186
+ // Fallback to traditional methods
187
+ return navigator.platform.toUpperCase().indexOf("MAC") >= 0;
188
+ }
189
+ onDOMKeydown(event) {
190
+ if (!this.isOpenValue) return;
191
+ const key = event.key;
192
+ if (["Tab", "Enter"].includes(key)) event.preventDefault();
193
+ if (key === "Escape") {
194
+ this.close();
195
+ }
196
+ }
197
+ setupEventListeners() {
198
+ document.addEventListener("keydown", this.DOMKeydownListener);
199
+ }
200
+ cleanupEventListeners() {
201
+ document.removeEventListener("keydown", this.DOMKeydownListener);
202
+ if (this.abortController) {
203
+ this.abortController.abort();
204
+ }
205
+ }
206
+ };
207
+ export { CommandController };