shadcn_phlexcomponents 0.1.14 → 0.1.17

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 (73) 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/components/combobox.rb +2 -6
  39. data/lib/shadcn_phlexcomponents/version.rb +1 -1
  40. metadata +69 -35
  41. /data/app/{javascript → typescript}/controllers/accordion_controller.ts +0 -0
  42. /data/app/{javascript → typescript}/controllers/alert_dialog_controller.ts +0 -0
  43. /data/app/{javascript → typescript}/controllers/avatar_controller.ts +0 -0
  44. /data/app/{javascript → typescript}/controllers/checkbox_controller.ts +0 -0
  45. /data/app/{javascript → typescript}/controllers/collapsible_controller.ts +0 -0
  46. /data/app/{javascript → typescript}/controllers/combobox_controller.ts +0 -0
  47. /data/app/{javascript → typescript}/controllers/command_controller.ts +0 -0
  48. /data/app/{javascript → typescript}/controllers/date_picker_controller.ts +0 -0
  49. /data/app/{javascript → typescript}/controllers/date_range_picker_controller.ts +0 -0
  50. /data/app/{javascript → typescript}/controllers/dialog_controller.ts +0 -0
  51. /data/app/{javascript → typescript}/controllers/dropdown_menu_controller.ts +0 -0
  52. /data/app/{javascript → typescript}/controllers/dropdown_menu_sub_controller.ts +0 -0
  53. /data/app/{javascript → typescript}/controllers/form_field_controller.ts +0 -0
  54. /data/app/{javascript → typescript}/controllers/hover_card_controller.ts +0 -0
  55. /data/app/{javascript → typescript}/controllers/loading_button_controller.ts +0 -0
  56. /data/app/{javascript → typescript}/controllers/popover_controller.ts +0 -0
  57. /data/app/{javascript → typescript}/controllers/progress_controller.ts +0 -0
  58. /data/app/{javascript → typescript}/controllers/radio_group_controller.ts +0 -0
  59. /data/app/{javascript → typescript}/controllers/select_controller.ts +0 -0
  60. /data/app/{javascript → typescript}/controllers/sidebar_controller.ts +0 -0
  61. /data/app/{javascript → typescript}/controllers/sidebar_trigger_controller.ts +0 -0
  62. /data/app/{javascript → typescript}/controllers/slider_controller.ts +0 -0
  63. /data/app/{javascript → typescript}/controllers/switch_controller.ts +0 -0
  64. /data/app/{javascript → typescript}/controllers/tabs_controller.ts +0 -0
  65. /data/app/{javascript → typescript}/controllers/theme_switcher_controller.ts +0 -0
  66. /data/app/{javascript → typescript}/controllers/toast_container_controller.ts +0 -0
  67. /data/app/{javascript → typescript}/controllers/toast_controller.ts +0 -0
  68. /data/app/{javascript → typescript}/controllers/toggle_controller.ts +0 -0
  69. /data/app/{javascript → typescript}/controllers/toggle_group_controller.ts +0 -0
  70. /data/app/{javascript → typescript}/controllers/tooltip_controller.ts +0 -0
  71. /data/app/{javascript → typescript}/shadcn_phlexcomponents.ts +0 -0
  72. /data/app/{javascript → typescript}/utils/command.ts +0 -0
  73. /data/app/{javascript → typescript}/utils/floating_ui.ts +0 -0
@@ -0,0 +1,238 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { useClickOutside } from "stimulus-use";
3
+ import { initFloatingUi } from "../utils/floating_ui";
4
+ import {
5
+ getSameLevelItems,
6
+ focusTrigger,
7
+ hideContent,
8
+ showContent,
9
+ lockScroll,
10
+ unlockScroll,
11
+ getStimulusInstance,
12
+ onClickOutside,
13
+ getNextEnabledIndex,
14
+ getPreviousEnabledIndex,
15
+ focusElement,
16
+ } from "../utils";
17
+ const onKeydown = (controller, event) => {
18
+ const key = event.key;
19
+ if (["Tab", "Enter", " "].includes(key)) event.preventDefault();
20
+ if (key === "Home") {
21
+ controller.focusItemByIndex(null, 0);
22
+ } else if (key === "End") {
23
+ controller.focusItemByIndex(null, controller.items.length - 1);
24
+ } else if (key === "Escape") {
25
+ controller.close();
26
+ }
27
+ };
28
+ const focusItemByIndex = (controller, event = null, index = null) => {
29
+ if (event !== null) {
30
+ const key = event.key;
31
+ if (key === "ArrowUp") {
32
+ controller.items[controller.items.length - 1].focus();
33
+ } else {
34
+ controller.items[0].focus();
35
+ }
36
+ } else if (index !== null) {
37
+ controller.items[index].focus();
38
+ }
39
+ };
40
+ const DropdownMenuController = class extends Controller {
41
+ // targets
42
+ static targets = ["trigger", "contentContainer", "content", "item"];
43
+ // values
44
+ static values = {
45
+ isOpen: Boolean,
46
+ };
47
+ connect() {
48
+ this.closestContentSelector =
49
+ '[data-dropdown-menu-target="content"], [data-dropdown-menu-sub-target="content"]';
50
+ this.items = getSameLevelItems({
51
+ content: this.contentTarget,
52
+ items: this.itemTargets,
53
+ closestContentSelector: this.closestContentSelector,
54
+ });
55
+ useClickOutside(this, {
56
+ element: this.contentTarget,
57
+ dispatchEvent: false,
58
+ });
59
+ this.DOMKeydownListener = this.onDOMKeydown.bind(this);
60
+ }
61
+ toggle(event) {
62
+ if (this.isOpenValue) {
63
+ this.close();
64
+ } else {
65
+ this.open(event);
66
+ }
67
+ }
68
+ open(event) {
69
+ this.isOpenValue = true;
70
+ // Sub menus are not connected to the DOM yet when dropdown menu is connected.
71
+ // So we initialize them here instead of in connect().
72
+ if (this.subMenuControllers === undefined) {
73
+ const subMenuControllers = [];
74
+ const subMenus = Array.from(
75
+ this.contentTarget.querySelectorAll(
76
+ '[data-shadcn-phlexcomponents="dropdown-menu-sub"]',
77
+ ),
78
+ );
79
+ subMenus.forEach((subMenu) => {
80
+ const subMenuController = getStimulusInstance(
81
+ "dropdown-menu-sub",
82
+ subMenu,
83
+ );
84
+ if (subMenuController) {
85
+ subMenuControllers.push(subMenuController);
86
+ }
87
+ });
88
+ this.subMenuControllers = subMenuControllers;
89
+ }
90
+ let elementToFocus = null;
91
+ if (event instanceof KeyboardEvent) {
92
+ const key = event.key;
93
+ if (["ArrowDown", "Enter", " "].includes(key)) {
94
+ elementToFocus = this.items[0];
95
+ }
96
+ } else {
97
+ elementToFocus = this.contentTarget;
98
+ }
99
+ focusElement(elementToFocus);
100
+ }
101
+ close() {
102
+ this.isOpenValue = false;
103
+ this.subMenuControllers.forEach((subMenuController) => {
104
+ subMenuController.closeImmediately();
105
+ });
106
+ }
107
+ focusItem(event) {
108
+ const item = event.currentTarget;
109
+ let items = [];
110
+ const content = item.closest(this.closestContentSelector);
111
+ const isSubMenu =
112
+ content.dataset.shadcnPhlexcomponents === "dropdown-menu-sub-content";
113
+ if (isSubMenu) {
114
+ const subMenu = content.closest(
115
+ '[data-shadcn-phlexcomponents="dropdown-menu-sub"]',
116
+ );
117
+ const subMenuController = this.subMenuControllers.find(
118
+ (subMenuController) => subMenuController.element == subMenu,
119
+ );
120
+ if (subMenuController) {
121
+ items = subMenuController.items;
122
+ }
123
+ } else {
124
+ items = this.items;
125
+ }
126
+ const index = items.indexOf(item);
127
+ if (event instanceof KeyboardEvent) {
128
+ const key = event.key;
129
+ let newIndex = 0;
130
+ if (key === "ArrowUp") {
131
+ newIndex = getPreviousEnabledIndex({
132
+ items,
133
+ currentIndex: index,
134
+ wrapAround: false,
135
+ });
136
+ } else {
137
+ newIndex = getNextEnabledIndex({
138
+ items,
139
+ currentIndex: index,
140
+ wrapAround: false,
141
+ });
142
+ }
143
+ items[newIndex].focus();
144
+ } else {
145
+ // item mouseover event
146
+ items[index].focus();
147
+ }
148
+ // Close submenus on the same level
149
+ const subMenusInContent = Array.from(
150
+ content.querySelectorAll(
151
+ '[data-shadcn-phlexcomponents="dropdown-menu-sub"]',
152
+ ),
153
+ );
154
+ subMenusInContent.forEach((subMenu) => {
155
+ const subMenuController = this.subMenuControllers.find(
156
+ (subMenuController) => subMenuController.element == subMenu,
157
+ );
158
+ if (subMenuController) {
159
+ subMenuController.closeImmediately();
160
+ }
161
+ });
162
+ }
163
+ onItemFocus(event) {
164
+ const item = event.currentTarget;
165
+ item.tabIndex = 0;
166
+ }
167
+ onItemBlur(event) {
168
+ const item = event.currentTarget;
169
+ item.tabIndex = -1;
170
+ }
171
+ focusItemByIndex(event = null, index = null) {
172
+ focusItemByIndex(this, event, index);
173
+ }
174
+ focusContent(event) {
175
+ const item = event.currentTarget;
176
+ const content = item.closest(this.closestContentSelector);
177
+ content.focus();
178
+ }
179
+ select(event) {
180
+ if (event instanceof KeyboardEvent) {
181
+ const key = event.key;
182
+ const item = event.currentTarget || event.target;
183
+ // For rails button_to
184
+ if (item && (key === "Enter" || key === " ")) {
185
+ item.click();
186
+ }
187
+ }
188
+ this.close();
189
+ }
190
+ clickOutside(event) {
191
+ onClickOutside(this, event);
192
+ }
193
+ isOpenValueChanged(isOpen, previousIsOpen) {
194
+ if (isOpen) {
195
+ lockScroll(this.contentTarget.id);
196
+ showContent({
197
+ trigger: this.triggerTarget,
198
+ content: this.contentTarget,
199
+ contentContainer: this.contentContainerTarget,
200
+ setEqualWidth: false,
201
+ });
202
+ this.cleanup = initFloatingUi({
203
+ referenceElement: this.triggerTarget,
204
+ floatingElement: this.contentContainerTarget,
205
+ side: this.contentTarget.dataset.side,
206
+ align: this.contentTarget.dataset.align,
207
+ sideOffset: 4,
208
+ });
209
+ this.setupEventListeners();
210
+ } else {
211
+ unlockScroll(this.contentTarget.id);
212
+ hideContent({
213
+ trigger: this.triggerTarget,
214
+ content: this.contentTarget,
215
+ contentContainer: this.contentContainerTarget,
216
+ });
217
+ if (previousIsOpen) {
218
+ focusTrigger(this.triggerTarget);
219
+ }
220
+ this.cleanupEventListeners();
221
+ }
222
+ }
223
+ disconnect() {
224
+ this.cleanupEventListeners();
225
+ }
226
+ onDOMKeydown(event) {
227
+ if (!this.isOpenValue) return;
228
+ onKeydown(this, event);
229
+ }
230
+ setupEventListeners() {
231
+ document.addEventListener("keydown", this.DOMKeydownListener);
232
+ }
233
+ cleanupEventListeners() {
234
+ if (this.cleanup) this.cleanup();
235
+ document.removeEventListener("keydown", this.DOMKeydownListener);
236
+ }
237
+ };
238
+ export { DropdownMenuController, onKeydown, focusItemByIndex };
@@ -0,0 +1,118 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { initFloatingUi } from "../utils/floating_ui";
3
+ import {
4
+ ON_OPEN_FOCUS_DELAY,
5
+ getSameLevelItems,
6
+ showContent,
7
+ hideContent,
8
+ getStimulusInstance,
9
+ } from "../utils";
10
+ const DropdownMenuSubController = class extends Controller {
11
+ // targets
12
+ static targets = ["trigger", "contentContainer", "content"];
13
+ // values
14
+ static values = {
15
+ isOpen: Boolean,
16
+ };
17
+ connect() {
18
+ this.items = getSameLevelItems({
19
+ content: this.contentTarget,
20
+ items: Array.from(
21
+ this.contentTarget.querySelectorAll(
22
+ '[data-dropdown-menu-target="item"], [data-dropdown-menu-sub-target="trigger"]',
23
+ ),
24
+ ),
25
+ closestContentSelector: '[data-dropdown-menu-sub-target="content"]',
26
+ });
27
+ }
28
+ open(event = null) {
29
+ clearTimeout(this.closeTimeout);
30
+ this.isOpenValue = true;
31
+ setTimeout(() => {
32
+ if (event instanceof KeyboardEvent) {
33
+ const key = event.key;
34
+ if (["ArrowRight", "Enter", " "].includes(key)) {
35
+ this.focusItemByIndex(null, 0);
36
+ }
37
+ }
38
+ }, ON_OPEN_FOCUS_DELAY);
39
+ }
40
+ close() {
41
+ this.closeTimeout = window.setTimeout(() => {
42
+ this.isOpenValue = false;
43
+ }, 250);
44
+ }
45
+ closeOnLeftKeydown() {
46
+ this.closeImmediately();
47
+ this.triggerTarget.focus();
48
+ }
49
+ focusItemByIndex(event, index) {
50
+ if (event) {
51
+ const key = event.key;
52
+ if (key === "ArrowUp") {
53
+ this.items[this.items.length - 1].focus();
54
+ } else {
55
+ this.items[0].focus();
56
+ }
57
+ } else if (index !== null) {
58
+ this.items[index].focus();
59
+ }
60
+ }
61
+ closeParentSubMenu() {
62
+ const parentContent = this.triggerTarget.closest(
63
+ '[data-dropdown-menu-sub-target="content"]',
64
+ );
65
+ if (parentContent) {
66
+ const subMenu = parentContent.closest(
67
+ '[data-shadcn-phlexcomponents="dropdown-menu-sub"]',
68
+ );
69
+ if (subMenu) {
70
+ const subMenuController = getStimulusInstance(
71
+ "dropdown-menu-sub",
72
+ subMenu,
73
+ );
74
+ if (subMenuController) {
75
+ subMenuController.closeImmediately();
76
+ setTimeout(() => {
77
+ subMenuController.triggerTarget.focus();
78
+ }, 100);
79
+ }
80
+ }
81
+ }
82
+ }
83
+ closeImmediately() {
84
+ this.isOpenValue = false;
85
+ }
86
+ isOpenValueChanged(isOpen) {
87
+ if (isOpen) {
88
+ showContent({
89
+ trigger: this.triggerTarget,
90
+ content: this.contentTarget,
91
+ contentContainer: this.contentContainerTarget,
92
+ });
93
+ this.cleanup = initFloatingUi({
94
+ referenceElement: this.triggerTarget,
95
+ floatingElement: this.contentContainerTarget,
96
+ side: this.contentTarget.dataset.side,
97
+ align: this.contentTarget.dataset.align,
98
+ sideOffset: -2,
99
+ });
100
+ } else {
101
+ this.closeTimeout = window.setTimeout(() => {
102
+ hideContent({
103
+ trigger: this.triggerTarget,
104
+ content: this.contentTarget,
105
+ contentContainer: this.contentContainerTarget,
106
+ });
107
+ });
108
+ this.cleanupEventListeners();
109
+ }
110
+ }
111
+ disconnect() {
112
+ this.cleanupEventListeners();
113
+ }
114
+ cleanupEventListeners() {
115
+ if (this.cleanup) this.cleanup();
116
+ }
117
+ };
118
+ export { DropdownMenuSubController };
@@ -0,0 +1,20 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ const FormFieldController = class extends Controller {
3
+ connect() {
4
+ const hintContainer = this.element.querySelector("[data-remove-label]");
5
+ const labelContainer = this.element.querySelector("[data-remove-hint]");
6
+ if (hintContainer) {
7
+ const label = hintContainer.querySelector("label");
8
+ const hint = hintContainer.querySelector("p");
9
+ label.remove();
10
+ hintContainer.replaceWith(hint);
11
+ }
12
+ if (labelContainer) {
13
+ const hint = labelContainer.querySelector("p");
14
+ const label = labelContainer.querySelector("label");
15
+ hint.remove();
16
+ labelContainer.replaceWith(label);
17
+ }
18
+ }
19
+ };
20
+ export { FormFieldController };
@@ -0,0 +1,73 @@
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 HoverCardController = class extends Controller {
6
+ // targets
7
+ static targets = ["trigger", "content", "contentContainer"];
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
+ // for useHover
26
+ mouseEnter() {
27
+ this.open();
28
+ }
29
+ // for useHover
30
+ mouseLeave() {
31
+ this.close();
32
+ }
33
+ isOpenValueChanged(isOpen) {
34
+ if (isOpen) {
35
+ showContent({
36
+ content: this.contentTarget,
37
+ contentContainer: this.contentContainerTarget,
38
+ });
39
+ this.setupEventListeners();
40
+ this.cleanup = initFloatingUi({
41
+ referenceElement: this.triggerTarget,
42
+ floatingElement: this.contentContainerTarget,
43
+ side: this.contentTarget.dataset.side,
44
+ align: this.contentTarget.dataset.align,
45
+ sideOffset: 4,
46
+ });
47
+ } else {
48
+ hideContent({
49
+ content: this.contentTarget,
50
+ contentContainer: this.contentContainerTarget,
51
+ });
52
+ this.cleanupEventListeners();
53
+ }
54
+ }
55
+ disconnect() {
56
+ this.cleanupEventListeners();
57
+ }
58
+ setupEventListeners() {
59
+ document.addEventListener("keydown", this.DOMKeydownListener);
60
+ }
61
+ cleanupEventListeners() {
62
+ if (this.cleanup) this.cleanup();
63
+ document.removeEventListener("keydown", this.DOMKeydownListener);
64
+ }
65
+ onDOMKeydown(event) {
66
+ if (!this.isOpenValue) return;
67
+ const key = event.key;
68
+ if (key === "Escape") {
69
+ this.close();
70
+ }
71
+ }
72
+ };
73
+ export { HoverCardController };
@@ -0,0 +1,14 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ const LoadingButtonController = class extends Controller {
3
+ connect() {
4
+ const el = this.element;
5
+ const form = el.closest("form");
6
+ if (form && form.dataset.turbo === "false") {
7
+ form.addEventListener("submit", () => {
8
+ form.ariaBusy = "true";
9
+ el.disabled = true;
10
+ });
11
+ }
12
+ }
13
+ };
14
+ export { LoadingButtonController };
@@ -0,0 +1,90 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { useClickOutside } from "stimulus-use";
3
+ import { initFloatingUi } from "../utils/floating_ui";
4
+ import {
5
+ focusTrigger,
6
+ getFocusableElements,
7
+ showContent,
8
+ hideContent,
9
+ onClickOutside,
10
+ handleTabNavigation,
11
+ focusElement,
12
+ } from "../utils";
13
+ const PopoverController = class extends Controller {
14
+ // targets
15
+ static targets = ["trigger", "contentContainer", "content"];
16
+ // values
17
+ static values = { isOpen: Boolean };
18
+ connect() {
19
+ this.DOMKeydownListener = this.onDOMKeydown.bind(this);
20
+ useClickOutside(this, {
21
+ element: this.contentTarget,
22
+ dispatchEvent: false,
23
+ });
24
+ }
25
+ toggle() {
26
+ if (this.isOpenValue) {
27
+ this.close();
28
+ } else {
29
+ this.open();
30
+ }
31
+ }
32
+ open() {
33
+ this.isOpenValue = true;
34
+ }
35
+ close() {
36
+ this.isOpenValue = false;
37
+ }
38
+ clickOutside(event) {
39
+ onClickOutside(this, event);
40
+ }
41
+ isOpenValueChanged(isOpen, previousIsOpen) {
42
+ if (isOpen) {
43
+ showContent({
44
+ trigger: this.triggerTarget,
45
+ content: this.contentTarget,
46
+ contentContainer: this.contentContainerTarget,
47
+ });
48
+ this.cleanup = initFloatingUi({
49
+ referenceElement: this.triggerTarget,
50
+ floatingElement: this.contentContainerTarget,
51
+ side: this.contentTarget.dataset.side,
52
+ align: this.contentTarget.dataset.align,
53
+ sideOffset: 4,
54
+ });
55
+ const focusableElements = getFocusableElements(this.contentTarget);
56
+ focusElement(focusableElements[0]);
57
+ this.setupEventListeners();
58
+ } else {
59
+ hideContent({
60
+ trigger: this.triggerTarget,
61
+ content: this.contentTarget,
62
+ contentContainer: this.contentContainerTarget,
63
+ });
64
+ if (previousIsOpen) {
65
+ focusTrigger(this.triggerTarget);
66
+ }
67
+ this.cleanupEventListeners();
68
+ }
69
+ }
70
+ disconnect() {
71
+ this.cleanupEventListeners();
72
+ }
73
+ setupEventListeners() {
74
+ document.addEventListener("keydown", this.DOMKeydownListener);
75
+ }
76
+ cleanupEventListeners() {
77
+ if (this.cleanup) this.cleanup();
78
+ document.removeEventListener("keydown", this.DOMKeydownListener);
79
+ }
80
+ onDOMKeydown(event) {
81
+ if (!this.isOpenValue) return;
82
+ const key = event.key;
83
+ if (key === "Escape") {
84
+ this.close();
85
+ } else if (key === "Tab") {
86
+ handleTabNavigation(this.contentTarget, event);
87
+ }
88
+ }
89
+ };
90
+ export { PopoverController };
@@ -0,0 +1,14 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ const ProgressController = class extends Controller {
3
+ // targets
4
+ static targets = ["indicator"];
5
+ // values
6
+ static values = {
7
+ percent: Number,
8
+ };
9
+ percentValueChanged(value) {
10
+ this.element.setAttribute("aria-valuenow", `${value}`);
11
+ this.indicatorTarget.style.transform = `translateX(-${100 - value}%)`;
12
+ }
13
+ };
14
+ export { ProgressController };
@@ -0,0 +1,80 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ const RadioGroupController = class extends Controller {
3
+ // targets
4
+ static targets = ["item", "input", "indicator"];
5
+ // values
6
+ static values = {
7
+ selected: String,
8
+ };
9
+ connect() {
10
+ if (!this.selectedValue) {
11
+ this.itemTargets[0].tabIndex = 0;
12
+ }
13
+ }
14
+ select(event) {
15
+ const item = event.currentTarget;
16
+ this.selectedValue = item.dataset.value;
17
+ }
18
+ selectItem(event) {
19
+ const focusableItems = this.itemTargets.filter((t) => !t.disabled);
20
+ const item = event.currentTarget;
21
+ const index = focusableItems.indexOf(item);
22
+ const key = event.key;
23
+ let newIndex = 0;
24
+ if (["ArrowUp", "ArrowLeft"].includes(key)) {
25
+ newIndex = index - 1;
26
+ if (newIndex < 0) {
27
+ newIndex = focusableItems.length - 1;
28
+ }
29
+ } else {
30
+ newIndex = index + 1;
31
+ if (newIndex > focusableItems.length - 1) {
32
+ newIndex = 0;
33
+ }
34
+ }
35
+ this.selectedValue = focusableItems[newIndex].dataset.value;
36
+ }
37
+ preventDefault(event) {
38
+ event.preventDefault();
39
+ }
40
+ focusItem() {
41
+ const item = this.itemTargets.find(
42
+ (i) => i.dataset.value === this.selectedValue,
43
+ );
44
+ if (!item) return;
45
+ // Focus first item that is not disabled and allow it to be focused
46
+ if (item.disabled) {
47
+ item.tabIndex = -1;
48
+ const focusableItems = this.itemTargets.filter((t) => !t.disabled);
49
+ if (focusableItems.length > 0) {
50
+ focusableItems[0].focus();
51
+ focusableItems[0].tabIndex = 0;
52
+ }
53
+ } else {
54
+ item.focus();
55
+ }
56
+ }
57
+ selectedValueChanged(value) {
58
+ this.itemTargets.forEach((item) => {
59
+ const input = item.querySelector('[data-radio-group-target="input"]');
60
+ const indicator = item.querySelector(
61
+ '[data-radio-group-target="indicator"]',
62
+ );
63
+ if (value === item.dataset.value) {
64
+ input.checked = true;
65
+ item.tabIndex = 0;
66
+ item.ariaChecked = "true";
67
+ item.dataset.state = "checked";
68
+ indicator.classList.remove("hidden");
69
+ } else {
70
+ input.checked = false;
71
+ item.tabIndex = -1;
72
+ item.ariaChecked = "false";
73
+ item.dataset.state = "unchecked";
74
+ indicator.classList.add("hidden");
75
+ }
76
+ });
77
+ this.focusItem();
78
+ }
79
+ };
80
+ export { RadioGroupController };