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
@@ -0,0 +1,79 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { useHover } from "stimulus-use";
3
+ import { initFloatingUi } from "../utils/floating_ui";
4
+ import { showContent, hideContent } from "../utils";
5
+ const TooltipController = class extends Controller {
6
+ // targets
7
+ static targets = ["trigger", "content", "contentContainer", "arrow"];
8
+ // values
9
+ static values = {
10
+ isOpen: Boolean,
11
+ };
12
+ connect() {
13
+ this.DOMKeydownListener = this.onDOMKeydown.bind(this);
14
+ useHover(this, { element: this.triggerTarget, dispatchEvent: false });
15
+ }
16
+ open() {
17
+ window.clearTimeout(this.closeTimeout);
18
+ this.isOpenValue = true;
19
+ }
20
+ close() {
21
+ this.closeTimeout = window.setTimeout(() => {
22
+ this.isOpenValue = false;
23
+ }, 250);
24
+ }
25
+ closeImmediately() {
26
+ this.isOpenValue = false;
27
+ }
28
+ // for useHover
29
+ mouseEnter() {
30
+ this.open();
31
+ }
32
+ // for useHover
33
+ mouseLeave() {
34
+ this.close();
35
+ }
36
+ isOpenValueChanged(isOpen) {
37
+ if (isOpen) {
38
+ showContent({
39
+ content: this.contentTarget,
40
+ contentContainer: this.contentContainerTarget,
41
+ });
42
+ this.setupEventListeners();
43
+ this.cleanup = initFloatingUi({
44
+ referenceElement: this.triggerTarget,
45
+ floatingElement: this.contentContainerTarget,
46
+ arrowElement: this.arrowTarget,
47
+ side: this.contentTarget.dataset.side,
48
+ align: this.contentTarget.dataset.align,
49
+ sideOffset: 8,
50
+ });
51
+ } else {
52
+ hideContent({
53
+ content: this.contentTarget,
54
+ contentContainer: this.contentContainerTarget,
55
+ });
56
+ this.cleanupEventListeners();
57
+ }
58
+ }
59
+ disconnect() {
60
+ this.cleanupEventListeners();
61
+ }
62
+ onDOMKeydown(event) {
63
+ if (!this.isOpenValue) return;
64
+ const key = event.key;
65
+ if (["Escape", "Enter", " "].includes(key)) {
66
+ event.preventDefault();
67
+ event.stopImmediatePropagation();
68
+ this.closeImmediately();
69
+ }
70
+ }
71
+ setupEventListeners() {
72
+ document.addEventListener("keydown", this.DOMKeydownListener);
73
+ }
74
+ cleanupEventListeners() {
75
+ if (this.cleanup) this.cleanup();
76
+ document.removeEventListener("keydown", this.DOMKeydownListener);
77
+ }
78
+ };
79
+ export { TooltipController };
@@ -0,0 +1,60 @@
1
+ import { AccordionController } from "./controllers/accordion_controller";
2
+ import { AlertDialogController } from "./controllers/alert_dialog_controller";
3
+ import { AvatarController } from "./controllers/avatar_controller";
4
+ import { CheckboxController } from "./controllers/checkbox_controller";
5
+ import { CollapsibleController } from "./controllers/collapsible_controller";
6
+ import { ComboboxController } from "./controllers/combobox_controller";
7
+ import { CommandController } from "./controllers/command_controller";
8
+ import { DatePickerController } from "./controllers/date_picker_controller";
9
+ import { DateRangePickerController } from "./controllers/date_range_picker_controller";
10
+ import { DialogController } from "./controllers/dialog_controller";
11
+ import { DropdownMenuController } from "./controllers/dropdown_menu_controller";
12
+ import { DropdownMenuSubController } from "./controllers/dropdown_menu_sub_controller";
13
+ import { FormFieldController } from "./controllers/form_field_controller";
14
+ import { HoverCardController } from "./controllers/hover_card_controller";
15
+ import { LoadingButtonController } from "./controllers/loading_button_controller";
16
+ import { PopoverController } from "./controllers/popover_controller";
17
+ import { ProgressController } from "./controllers/progress_controller";
18
+ import { RadioGroupController } from "./controllers/radio_group_controller";
19
+ import { SelectController } from "./controllers/select_controller";
20
+ import SidebarController from "./controllers/sidebar_controller";
21
+ import SidebarTriggerController from "./controllers/sidebar_trigger_controller";
22
+ import { SliderController } from "./controllers/slider_controller";
23
+ import { SwitchController } from "./controllers/switch_controller";
24
+ import { TabsController } from "./controllers/tabs_controller";
25
+ import { ThemeSwitcherController } from "./controllers/theme_switcher_controller";
26
+ import { ToastContainerController } from "./controllers/toast_container_controller";
27
+ import { ToastController } from "./controllers/toast_controller";
28
+ import { ToggleController } from "./controllers/toggle_controller";
29
+ import { TooltipController } from "./controllers/tooltip_controller";
30
+ export default {
31
+ accordion: AccordionController,
32
+ "alert-dialog": AlertDialogController,
33
+ avatar: AvatarController,
34
+ checkbox: CheckboxController,
35
+ collapsible: CollapsibleController,
36
+ combobox: ComboboxController,
37
+ command: CommandController,
38
+ "date-picker": DatePickerController,
39
+ "date-range-picker": DateRangePickerController,
40
+ dialog: DialogController,
41
+ "dropdown-menu": DropdownMenuController,
42
+ "dropdown-menu-sub": DropdownMenuSubController,
43
+ "form-field": FormFieldController,
44
+ "hover-card": HoverCardController,
45
+ "loading-button": LoadingButtonController,
46
+ popover: PopoverController,
47
+ progress: ProgressController,
48
+ "radio-group": RadioGroupController,
49
+ select: SelectController,
50
+ sidebar: SidebarController,
51
+ "sidebar-trigger": SidebarTriggerController,
52
+ slider: SliderController,
53
+ switch: SwitchController,
54
+ tabs: TabsController,
55
+ "theme-switcher": ThemeSwitcherController,
56
+ "toast-container": ToastContainerController,
57
+ toast: ToastController,
58
+ toggle: ToggleController,
59
+ tooltip: TooltipController,
60
+ };
@@ -0,0 +1,448 @@
1
+ import { ComboboxController } from "../controllers/combobox_controller";
2
+ import { getNextEnabledIndex, getPreviousEnabledIndex } from ".";
3
+ const scrollToItem = (controller, index) => {
4
+ const item = controller.filteredItems[index];
5
+ const itemRect = item.getBoundingClientRect();
6
+ const listContainerRect =
7
+ controller.listContainerTarget.getBoundingClientRect();
8
+ let newScrollTop = null;
9
+ const maxScrollTop =
10
+ controller.listContainerTarget.scrollHeight -
11
+ controller.listContainerTarget.clientHeight;
12
+ // scroll to bottom
13
+ if (itemRect.bottom - listContainerRect.bottom > 0) {
14
+ if (index === controller.filteredItems.length - 1) {
15
+ newScrollTop = maxScrollTop;
16
+ } else {
17
+ newScrollTop =
18
+ controller.listContainerTarget.scrollTop +
19
+ (itemRect.bottom - listContainerRect.bottom);
20
+ }
21
+ } else if (listContainerRect.top - itemRect.top > 0) {
22
+ // scroll to top
23
+ if (index === 0) {
24
+ newScrollTop = 0;
25
+ } else {
26
+ newScrollTop =
27
+ controller.listContainerTarget.scrollTop -
28
+ (listContainerRect.top - itemRect.top);
29
+ }
30
+ }
31
+ if (newScrollTop !== null) {
32
+ controller.scrollingViaKeyboard = true;
33
+ if (newScrollTop >= 0 && newScrollTop <= maxScrollTop) {
34
+ controller.listContainerTarget.scrollTop = newScrollTop;
35
+ }
36
+ // Clear the flag after scroll settles
37
+ clearTimeout(controller.keyboardScrollTimeout);
38
+ controller.keyboardScrollTimeout = window.setTimeout(() => {
39
+ controller.scrollingViaKeyboard = false;
40
+ }, 200);
41
+ }
42
+ };
43
+ const highlightItem = (controller, event = null, index = null) => {
44
+ if (event !== null) {
45
+ if (event instanceof KeyboardEvent) {
46
+ const key = event.key;
47
+ const item = controller.filteredItems.find(
48
+ (i) => i.dataset.highlighted === "true",
49
+ );
50
+ if (item) {
51
+ const index = controller.filteredItems.indexOf(item);
52
+ let newIndex = 0;
53
+ if (key === "ArrowUp") {
54
+ newIndex = getPreviousEnabledIndex({
55
+ items: controller.filteredItems,
56
+ currentIndex: index,
57
+ filterFn: (item) => item.dataset.disabled === undefined,
58
+ wrapAround: false,
59
+ });
60
+ } else {
61
+ newIndex = getNextEnabledIndex({
62
+ items: controller.filteredItems,
63
+ currentIndex: index,
64
+ filterFn: (item) => item.dataset.disabled === undefined,
65
+ wrapAround: false,
66
+ });
67
+ }
68
+ controller.highlightItemByIndex(newIndex);
69
+ controller.scrollToItem(newIndex);
70
+ } else {
71
+ if (key === "ArrowUp") {
72
+ controller.highlightItemByIndex(controller.filteredItems.length - 1);
73
+ } else {
74
+ controller.highlightItemByIndex(0);
75
+ }
76
+ }
77
+ } else {
78
+ // mouse event
79
+ if (controller.scrollingViaKeyboard) {
80
+ event.stopImmediatePropagation();
81
+ return;
82
+ } else {
83
+ const item = event.currentTarget;
84
+ const index = controller.filteredItems.indexOf(item);
85
+ controller.highlightItemByIndex(index);
86
+ }
87
+ }
88
+ } else if (index !== null) {
89
+ controller.highlightItemByIndex(index);
90
+ }
91
+ };
92
+ const highlightItemByIndex = (controller, index) => {
93
+ controller.filteredItems.forEach((item, i) => {
94
+ if (i === index) {
95
+ item.dataset.highlighted = "true";
96
+ } else {
97
+ item.dataset.highlighted = "false";
98
+ }
99
+ });
100
+ };
101
+ const filteredItemsChanged = (controller, filteredItemIndexes) => {
102
+ if (controller.orderedItems) {
103
+ const filteredItems = filteredItemIndexes.map(
104
+ (i) => controller.orderedItems[i],
105
+ );
106
+ // 1. Toggle visibility of items
107
+ controller.orderedItems.forEach((item) => {
108
+ if (filteredItems.includes(item)) {
109
+ item.ariaHidden = "false";
110
+ item.classList.remove("hidden");
111
+ } else {
112
+ item.ariaHidden = "true";
113
+ item.classList.add("hidden");
114
+ }
115
+ });
116
+ // 2. Get groups based on order of filtered items
117
+ const groupIds = filteredItems.map((item) => item.dataset.groupId);
118
+ const uniqueGroupIds = [...new Set(groupIds)].filter(
119
+ (groupId) => !!groupId,
120
+ );
121
+ const orderedGroups = uniqueGroupIds.map((groupId) => {
122
+ return controller.listTarget.querySelector(
123
+ `[aria-labelledby=${groupId}]`,
124
+ );
125
+ });
126
+ // 3. Append items and groups based on filtered items
127
+ const appendedGroupIds = [];
128
+ filteredItems.forEach((item) => {
129
+ const groupId = item.dataset.groupId;
130
+ if (groupId) {
131
+ const group = orderedGroups.find(
132
+ (g) => g.getAttribute("aria-labelledby") === groupId,
133
+ );
134
+ if (group) {
135
+ group.appendChild(item);
136
+ if (!appendedGroupIds.includes(groupId)) {
137
+ controller.listTarget.appendChild(group);
138
+ appendedGroupIds.push(groupId);
139
+ }
140
+ }
141
+ } else {
142
+ controller.listTarget.appendChild(item);
143
+ }
144
+ });
145
+ // 4. Toggle visibility of groups
146
+ controller.groupTargets.forEach((group) => {
147
+ const itemsCount = group.querySelectorAll(
148
+ `[data-${controller.identifier}-target=item][aria-hidden=false]`,
149
+ ).length;
150
+ if (itemsCount > 0) {
151
+ group.classList.remove("hidden");
152
+ } else {
153
+ group.classList.add("hidden");
154
+ }
155
+ });
156
+ // 5. Move remote items to the end
157
+ const remoteItems = Array.from(
158
+ controller.element.querySelectorAll(
159
+ `[data-shadcn-phlexcomponents="${controller.identifier}-item"][data-remote='true']`,
160
+ ),
161
+ );
162
+ remoteItems.forEach((i) => {
163
+ const isInsideGroup =
164
+ i.parentElement?.dataset?.shadcnPhlexcomponents ===
165
+ `${controller.identifier}-group`;
166
+ if (isInsideGroup) {
167
+ const isRemoteGroup = i.parentElement.dataset.remote === "true";
168
+ // Move group to last
169
+ if (isRemoteGroup) {
170
+ controller.listTarget.appendChild(i.parentElement);
171
+ }
172
+ } else {
173
+ // Move item to last
174
+ controller.listTarget.appendChild(i);
175
+ }
176
+ });
177
+ // 6. Assign filteredItems based on the order it is displayed in the DOM
178
+ controller.filteredItems = Array.from(
179
+ controller.listTarget.querySelectorAll(
180
+ `[data-${controller.identifier}-target=item][aria-hidden=false]`,
181
+ ),
182
+ );
183
+ // 7. Highlight first item
184
+ controller.highlightItemByIndex(0);
185
+ // 8. Toggle visibility of empty
186
+ if (controller.isDirty && !controller.isLoading) {
187
+ if (controller.filteredItems.length > 0) {
188
+ hideEmpty(controller);
189
+ } else {
190
+ showEmpty(controller);
191
+ }
192
+ }
193
+ }
194
+ };
195
+ const setItemsGroupId = (controller) => {
196
+ controller.itemTargets.forEach((item) => {
197
+ const parent = item.parentElement;
198
+ if (parent?.dataset[`${controller.identifier}Target`] === "group") {
199
+ item.dataset.groupId = parent.getAttribute("aria-labelledby");
200
+ }
201
+ });
202
+ };
203
+ const search = (controller, event) => {
204
+ const input = event.target;
205
+ const value = input.value.trim();
206
+ if (value.length > 0) {
207
+ const results = controller.fuse.search(value);
208
+ // Don't show disabled items when filtering
209
+ let filteredItemIndexes = results.map((result) => result.refIndex);
210
+ filteredItemIndexes = filteredItemIndexes.filter((index) => {
211
+ const item = controller.orderedItems[index];
212
+ return item.dataset.disabled === undefined;
213
+ });
214
+ if (controller.searchPath) {
215
+ hideSelectedRemoteItems(controller);
216
+ showLoading(controller);
217
+ hideList(controller);
218
+ hideEmpty(controller);
219
+ controller.filteredItemIndexesValue = filteredItemIndexes;
220
+ performRemoteSearch(controller, value);
221
+ } else {
222
+ controller.filteredItemIndexesValue = filteredItemIndexes;
223
+ }
224
+ } else {
225
+ if (controller.searchPath) {
226
+ showSelectedRemoteItems(controller);
227
+ }
228
+ controller.filteredItemIndexesValue = Array.from(
229
+ { length: controller.orderedItems.length },
230
+ (_, i) => i,
231
+ );
232
+ }
233
+ };
234
+ const performRemoteSearch = async (controller, query) => {
235
+ // Cancel previous request
236
+ if (controller.abortController) {
237
+ controller.abortController.abort();
238
+ }
239
+ // Create new abort controller
240
+ controller.abortController = new AbortController();
241
+ try {
242
+ const response = await fetch(`${controller.searchPath}?q=${query}`, {
243
+ signal: controller.abortController.signal,
244
+ headers: {
245
+ Accept: "application/json",
246
+ "Content-Type": "application/json",
247
+ },
248
+ });
249
+ if (!response.ok) {
250
+ throw new Error(`HTTP error! status: ${response.status}`);
251
+ }
252
+ const data = await response.json();
253
+ renderRemoteResults(controller, data);
254
+ showList(controller);
255
+ } catch (error) {
256
+ if (error instanceof Error && error.name !== "AbortError") {
257
+ console.error("Remote search error:", error);
258
+ showError(controller);
259
+ }
260
+ } finally {
261
+ hideLoading(controller);
262
+ }
263
+ };
264
+ const renderRemoteResults = (controller, data) => {
265
+ console.log("data", data);
266
+ data.forEach((item) => {
267
+ const tempDiv = document.createElement("div");
268
+ tempDiv.innerHTML = item.html;
269
+ const itemEl = tempDiv.firstElementChild;
270
+ itemEl.dataset.remote = "true";
271
+ itemEl.ariaHidden = "false";
272
+ if (controller instanceof ComboboxController) {
273
+ // Don't append same item
274
+ if (controller.selectedValue === itemEl.dataset.value) {
275
+ const item = controller.itemTargets.find(
276
+ (i) => i.dataset.value === controller.selectedValue,
277
+ );
278
+ if (item) {
279
+ item.classList.remove("hidden");
280
+ item.ariaHidden = "false";
281
+ }
282
+ return;
283
+ }
284
+ }
285
+ const group = item.group;
286
+ if (group) {
287
+ const groupEl = controller.groupTargets.find((g) => {
288
+ const label = g.querySelector(
289
+ `[data-shadcn-phlexcomponents="${controller.identifier}-label"]`,
290
+ );
291
+ if (!label) return false;
292
+ return label.textContent === group;
293
+ });
294
+ if (groupEl) {
295
+ groupEl.classList.remove("hidden");
296
+ groupEl.append(itemEl);
297
+ } else {
298
+ const template = controller.element.querySelector("template");
299
+ const clone = template.content.cloneNode(true);
300
+ const groupEl = clone.querySelector(
301
+ `[data-shadcn-phlexcomponents="${controller.identifier}-group"]`,
302
+ );
303
+ const groupId = crypto.randomUUID();
304
+ const label = clone.querySelector(
305
+ `[data-shadcn-phlexcomponents="${controller.identifier}-label"]`,
306
+ );
307
+ label.textContent = group;
308
+ label.id = groupId;
309
+ groupEl.setAttribute("aria-labelledby", groupId);
310
+ groupEl.dataset.remote = "true";
311
+ groupEl.append(itemEl);
312
+ controller.listTarget.append(clone);
313
+ }
314
+ } else {
315
+ controller.listTarget.append(itemEl);
316
+ }
317
+ });
318
+ // Update filtered items for keyboard navigation
319
+ controller.filteredItems = Array.from(
320
+ controller.listTarget.querySelectorAll(
321
+ `[data-${controller.identifier}-target="item"][aria-hidden=false]`,
322
+ ),
323
+ );
324
+ controller.highlightItemByIndex(0);
325
+ console.log("controller.filteredItems", controller.filteredItems);
326
+ if (controller.filteredItems.length > 0) {
327
+ hideEmpty(controller);
328
+ } else {
329
+ showEmpty(controller);
330
+ }
331
+ };
332
+ const clearRemoteResults = (controller) => {
333
+ const remoteGroups = Array.from(
334
+ controller.element.querySelectorAll(
335
+ `[data-shadcn-phlexcomponents="${controller.identifier}-group"][data-remote='true']`,
336
+ ),
337
+ );
338
+ remoteGroups.forEach((g) => {
339
+ const containsSelected = g.querySelector('[aria-selected="true"]');
340
+ if (!containsSelected) {
341
+ g.remove();
342
+ }
343
+ });
344
+ const remoteItems = Array.from(
345
+ controller.element.querySelectorAll(
346
+ `[data-shadcn-phlexcomponents="${controller.identifier}-item"][data-remote='true']:not([aria-selected="true"])`,
347
+ ),
348
+ );
349
+ remoteItems.forEach((i) => i.remove());
350
+ };
351
+ const resetState = (controller) => {
352
+ controller.searchInputTarget.value = "";
353
+ if (controller.searchPath) {
354
+ clearRemoteResults(controller);
355
+ showSelectedRemoteItems(controller);
356
+ }
357
+ controller.filteredItemIndexesValue = Array.from(
358
+ { length: controller.orderedItems.length },
359
+ (_, i) => i,
360
+ );
361
+ };
362
+ const showLoading = (controller) => {
363
+ controller.isLoading = true;
364
+ controller.loadingTarget.classList.remove("hidden");
365
+ };
366
+ const hideLoading = (controller) => {
367
+ controller.isLoading = false;
368
+ controller.loadingTarget.classList.add("hidden");
369
+ };
370
+ const showList = (controller) => {
371
+ controller.listTarget.classList.remove("hidden");
372
+ };
373
+ const hideList = (controller) => {
374
+ controller.listTarget.classList.add("hidden");
375
+ };
376
+ const showError = (controller) => {
377
+ controller.errorTarget.classList.remove("hidden");
378
+ };
379
+ const hideError = (controller) => {
380
+ controller.errorTarget.classList.add("hidden");
381
+ };
382
+ const showEmpty = (controller) => {
383
+ controller.emptyTarget.classList.remove("hidden");
384
+ };
385
+ const hideEmpty = (controller) => {
386
+ controller.emptyTarget.classList.add("hidden");
387
+ };
388
+ const showSelectedRemoteItems = (controller) => {
389
+ const remoteItems = Array.from(
390
+ controller.element.querySelectorAll(
391
+ `[data-shadcn-phlexcomponents="${controller.identifier}-item"][data-remote='true']`,
392
+ ),
393
+ );
394
+ remoteItems.forEach((i) => {
395
+ const isInsideGroup =
396
+ i.parentElement?.dataset?.shadcnPhlexcomponents ===
397
+ `${controller.identifier}-group`;
398
+ if (isInsideGroup) {
399
+ const isRemoteGroup = i.parentElement.dataset.remote === "true";
400
+ if (isRemoteGroup) {
401
+ i.parentElement.classList.remove("hidden");
402
+ }
403
+ }
404
+ i.ariaHidden = "false";
405
+ i.classList.remove("hidden");
406
+ });
407
+ };
408
+ const hideSelectedRemoteItems = (controller) => {
409
+ const remoteItems = Array.from(
410
+ controller.element.querySelectorAll(
411
+ `[data-shadcn-phlexcomponents="${controller.identifier}-item"][data-remote='true']`,
412
+ ),
413
+ );
414
+ remoteItems.forEach((i) => {
415
+ const isInsideGroup =
416
+ i.parentElement?.dataset?.shadcnPhlexcomponents ===
417
+ `${controller.identifier}-group`;
418
+ if (isInsideGroup) {
419
+ const isRemoteGroup = i.parentElement.dataset.remote === "true";
420
+ if (isRemoteGroup) {
421
+ i.parentElement.classList.add("hidden");
422
+ }
423
+ }
424
+ i.ariaHidden = "true";
425
+ i.classList.add("hidden");
426
+ });
427
+ };
428
+ export {
429
+ scrollToItem,
430
+ highlightItem,
431
+ highlightItemByIndex,
432
+ filteredItemsChanged,
433
+ setItemsGroupId,
434
+ search,
435
+ performRemoteSearch,
436
+ clearRemoteResults,
437
+ resetState,
438
+ showLoading,
439
+ hideLoading,
440
+ showList,
441
+ hideList,
442
+ showError,
443
+ hideError,
444
+ showEmpty,
445
+ hideEmpty,
446
+ showSelectedRemoteItems,
447
+ hideSelectedRemoteItems,
448
+ };