shadcn_phlexcomponents 0.1.11 → 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 (106) 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/stylesheets/date_picker.css +118 -0
  37. data/app/typescript/controllers/accordion_controller.ts +136 -0
  38. data/app/typescript/controllers/alert_dialog_controller.ts +12 -0
  39. data/app/{javascript → typescript}/controllers/avatar_controller.ts +7 -2
  40. data/app/{javascript → typescript}/controllers/checkbox_controller.ts +11 -4
  41. data/app/{javascript → typescript}/controllers/collapsible_controller.ts +12 -5
  42. data/app/typescript/controllers/combobox_controller.ts +376 -0
  43. data/app/typescript/controllers/command_controller.ts +301 -0
  44. data/app/{javascript → typescript}/controllers/date_picker_controller.ts +185 -125
  45. data/app/{javascript → typescript}/controllers/date_range_picker_controller.ts +89 -79
  46. data/app/{javascript → typescript}/controllers/dialog_controller.ts +59 -57
  47. data/app/typescript/controllers/dropdown_menu_controller.ts +309 -0
  48. data/app/{javascript → typescript}/controllers/dropdown_menu_sub_controller.ts +31 -29
  49. data/app/{javascript → typescript}/controllers/form_field_controller.ts +6 -1
  50. data/app/{javascript → typescript}/controllers/hover_card_controller.ts +36 -26
  51. data/app/{javascript → typescript}/controllers/loading_button_controller.ts +6 -1
  52. data/app/{javascript → typescript}/controllers/popover_controller.ts +42 -65
  53. data/app/{javascript → typescript}/controllers/progress_controller.ts +9 -3
  54. data/app/{javascript → typescript}/controllers/radio_group_controller.ts +16 -9
  55. data/app/typescript/controllers/select_controller.ts +341 -0
  56. data/app/{javascript → typescript}/controllers/slider_controller.ts +23 -16
  57. data/app/{javascript → typescript}/controllers/switch_controller.ts +11 -4
  58. data/app/{javascript → typescript}/controllers/tabs_controller.ts +26 -18
  59. data/app/{javascript → typescript}/controllers/theme_switcher_controller.ts +6 -1
  60. data/app/{javascript → typescript}/controllers/toast_container_controller.ts +6 -1
  61. data/app/{javascript → typescript}/controllers/toast_controller.ts +7 -1
  62. data/app/typescript/controllers/toggle_controller.ts +28 -0
  63. data/app/typescript/controllers/toggle_group_controller.ts +28 -0
  64. data/app/{javascript → typescript}/controllers/tooltip_controller.ts +43 -31
  65. data/app/typescript/shadcn_phlexcomponents.ts +61 -0
  66. data/app/typescript/utils/command.ts +544 -0
  67. data/app/typescript/utils/floating_ui.ts +196 -0
  68. data/app/typescript/utils/index.ts +424 -0
  69. data/lib/install/install_shadcn_phlexcomponents.rb +10 -3
  70. data/lib/shadcn_phlexcomponents/alias.rb +3 -0
  71. data/lib/shadcn_phlexcomponents/components/accordion.rb +2 -1
  72. data/lib/shadcn_phlexcomponents/components/alert_dialog.rb +18 -15
  73. data/lib/shadcn_phlexcomponents/components/base.rb +14 -0
  74. data/lib/shadcn_phlexcomponents/components/collapsible.rb +1 -2
  75. data/lib/shadcn_phlexcomponents/components/combobox.rb +87 -57
  76. data/lib/shadcn_phlexcomponents/components/command.rb +77 -47
  77. data/lib/shadcn_phlexcomponents/components/date_picker.rb +25 -81
  78. data/lib/shadcn_phlexcomponents/components/date_range_picker.rb +21 -4
  79. data/lib/shadcn_phlexcomponents/components/dialog.rb +14 -12
  80. data/lib/shadcn_phlexcomponents/components/dropdown_menu.rb +5 -4
  81. data/lib/shadcn_phlexcomponents/components/dropdown_menu_sub.rb +2 -1
  82. data/lib/shadcn_phlexcomponents/components/form/form_combobox.rb +64 -0
  83. data/lib/shadcn_phlexcomponents/components/form.rb +14 -0
  84. data/lib/shadcn_phlexcomponents/components/hover_card.rb +3 -2
  85. data/lib/shadcn_phlexcomponents/components/popover.rb +3 -3
  86. data/lib/shadcn_phlexcomponents/components/select.rb +10 -25
  87. data/lib/shadcn_phlexcomponents/components/sheet.rb +15 -11
  88. data/lib/shadcn_phlexcomponents/components/table.rb +1 -1
  89. data/lib/shadcn_phlexcomponents/components/tabs.rb +1 -1
  90. data/lib/shadcn_phlexcomponents/components/toast_container.rb +1 -1
  91. data/lib/shadcn_phlexcomponents/components/toggle.rb +54 -0
  92. data/lib/shadcn_phlexcomponents/components/tooltip.rb +3 -2
  93. data/lib/shadcn_phlexcomponents/engine.rb +1 -5
  94. data/lib/shadcn_phlexcomponents/version.rb +1 -1
  95. metadata +71 -32
  96. data/app/javascript/controllers/accordion_controller.ts +0 -133
  97. data/app/javascript/controllers/combobox_controller.ts +0 -145
  98. data/app/javascript/controllers/command_controller.ts +0 -129
  99. data/app/javascript/controllers/command_root_controller.ts +0 -355
  100. data/app/javascript/controllers/dropdown_menu_controller.ts +0 -133
  101. data/app/javascript/controllers/dropdown_menu_root_controller.ts +0 -234
  102. data/app/javascript/controllers/select_controller.ts +0 -200
  103. data/app/javascript/shadcn_phlexcomponents.ts +0 -57
  104. data/app/javascript/utils.ts +0 -437
  105. /data/app/{javascript → typescript}/controllers/sidebar_controller.ts +0 -0
  106. /data/app/{javascript → typescript}/controllers/sidebar_trigger_controller.ts +0 -0
@@ -1,15 +1,18 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import { useClickOutside } from 'stimulus-use'
1
3
  import {
2
- initFloatingUi,
3
- showOverlay,
4
- hideOverlay,
4
+ focusTrigger,
5
+ getFocusableElements,
6
+ showContent,
7
+ hideContent,
5
8
  lockScroll,
6
9
  unlockScroll,
7
- ON_OPEN_FOCUS_DELAY,
8
- getFocusableElements,
10
+ handleTabNavigation,
11
+ focusElement,
9
12
  } from '../utils'
13
+ import { initFloatingUi } from '../utils/floating_ui'
10
14
  import { Calendar, Options } from 'vanilla-calendar-pro'
11
15
  import Inputmask from 'inputmask'
12
- import PopoverController from './popover_controller'
13
16
  import dayjs from 'dayjs'
14
17
  import customParseFormat from 'dayjs/plugin/customParseFormat'
15
18
  import utc from 'dayjs/plugin/utc'
@@ -18,7 +21,8 @@ dayjs.extend(utc)
18
21
 
19
22
  const DAYJS_FORMAT = 'YYYY-MM-DD'
20
23
 
21
- export default class extends PopoverController {
24
+ const DatePickerController = class extends Controller<HTMLElement> {
25
+ // targets
22
26
  static targets = [
23
27
  'trigger',
24
28
  'triggerText',
@@ -28,31 +32,42 @@ export default class extends PopoverController {
28
32
  'hiddenInput',
29
33
  'inputContainer',
30
34
  'calendar',
35
+ 'overlay',
31
36
  ]
32
-
33
- static values = { isOpen: Boolean, date: String }
34
-
37
+ declare readonly triggerTarget: HTMLElement
35
38
  declare readonly triggerTextTarget: HTMLElement
39
+ declare readonly hasTriggerTextTarget: boolean
40
+ declare readonly contentContainerTarget: HTMLElement
41
+ declare readonly contentTarget: HTMLElement
36
42
  declare readonly inputTarget: HTMLInputElement
43
+ declare readonly hasInputTarget: boolean
37
44
  declare readonly hiddenInputTarget: HTMLInputElement
38
45
  declare readonly inputContainerTarget: HTMLElement
39
46
  declare readonly calendarTarget: HTMLElement
40
- declare onClickDateListener: (event: any, self: any) => void
47
+ declare readonly overlayTarget: HTMLElement
48
+
49
+ // values
50
+ static values = { isOpen: Boolean, date: String }
51
+ declare isOpenValue: boolean
52
+ declare dateValue: string
53
+
54
+ // custom properties
41
55
  declare format: string
42
56
  declare mask: boolean
43
- declare dateValue: string
44
57
  declare calendar: Calendar
45
- declare readonly hasInputTarget: boolean
46
- declare readonly hasTriggerTextTarget: boolean
58
+ declare isMobile: boolean
59
+ declare DOMKeydownListener: (event: KeyboardEvent) => void
60
+ declare cleanup: () => void
61
+ declare onClickDateListener: (self: Calendar) => void
47
62
 
48
63
  connect() {
49
- super.connect()
50
- this.onClickDateListener = this.onClickDate.bind(this)
51
64
  this.format = this.element.dataset.format || 'DD/MM/YYYY'
52
65
  this.mask = this.element.dataset.mask === 'true'
53
66
 
54
- const options = this.getOptions()
67
+ this.DOMKeydownListener = this.onDOMKeydown.bind(this)
68
+ this.onClickDateListener = this.onClickDate.bind(this)
55
69
 
70
+ const options = this.getOptions()
56
71
  this.calendar = new Calendar(this.calendarTarget, options)
57
72
  this.calendar.init()
58
73
 
@@ -61,30 +76,50 @@ export default class extends PopoverController {
61
76
  }
62
77
 
63
78
  this.calendarTarget.removeAttribute('tabindex')
79
+
80
+ useClickOutside(this, { element: this.contentTarget, dispatchEvent: false })
81
+ }
82
+
83
+ contentContainerTargetConnected() {
84
+ // Datepicker is shown as a dialog on small screens
85
+ const styles = window.getComputedStyle(this.contentContainerTarget)
86
+ this.isMobile = styles.translate === '-50%'
87
+ }
88
+
89
+ toggle() {
90
+ if (this.isOpenValue) {
91
+ this.close()
92
+ } else {
93
+ this.open()
94
+ }
95
+ }
96
+
97
+ open() {
98
+ this.isOpenValue = true
99
+ }
100
+
101
+ close() {
102
+ this.isOpenValue = false
64
103
  }
65
104
 
66
105
  inputBlur() {
67
106
  let dateDisplay = ''
68
107
  const date = this.calendar.context.selectedDates[0]
69
-
70
108
  if (date) {
71
109
  dateDisplay = dayjs(date).format(this.format)
72
110
  }
73
-
74
111
  this.inputTarget.value = dateDisplay
75
112
  this.inputContainerTarget.dataset.focus = 'false'
76
113
  }
77
114
 
78
115
  inputDate(event: KeyboardEvent) {
79
116
  const value = (event.target as HTMLInputElement).value
80
-
81
117
  if (value.length === 0) {
82
118
  this.calendar.set({
83
119
  selectedDates: [],
84
120
  })
85
121
  this.dateValue = ''
86
122
  }
87
-
88
123
  if (value.length > 0 && dayjs(value, this.format, true).isValid()) {
89
124
  const dayjsDate = dayjs(value, this.format).format(DAYJS_FORMAT)
90
125
  this.calendar.set({
@@ -98,38 +133,23 @@ export default class extends PopoverController {
98
133
  this.inputContainerTarget.dataset.focus = 'true'
99
134
  }
100
135
 
101
- onOpenFocusedElement() {
102
- const focusableElements = getFocusableElements(this.contentTarget)
103
-
104
- const selectedElement = Array.from(focusableElements).find(
105
- (e) => e.ariaSelected,
106
- ) as HTMLElement
107
-
108
- const currentElement = this.contentTarget.querySelector(
109
- '[aria-current]',
110
- ) as HTMLElement
111
-
112
- if (selectedElement) {
113
- return selectedElement
114
- } else if (currentElement) {
115
- const firstElementChild = currentElement.firstElementChild as HTMLElement
116
- return firstElementChild
117
- } else {
118
- return focusableElements[0]
119
- }
136
+ clickOutside(event: MouseEvent) {
137
+ const target = event.target as HTMLElement
138
+ // Let trigger handle state
139
+ if (target === this.triggerTarget) return
140
+ if (this.triggerTarget.contains(target)) return
141
+ if (this.triggerTarget.id === target.getAttribute('for')) return
142
+ this.close()
120
143
  }
121
144
 
122
- referenceElement() {
123
- return this.hasInputTarget ? this.inputTarget : this.triggerTarget
124
- }
125
-
126
- onOpen() {
127
- if (this.isMobile()) {
128
- lockScroll()
129
- showOverlay({ elementId: this.contentTarget.id })
130
- }
145
+ isOpenValueChanged(isOpen: boolean, previousIsOpen: boolean) {
146
+ if (isOpen) {
147
+ showContent({
148
+ trigger: this.triggerTarget,
149
+ content: this.contentTarget,
150
+ contentContainer: this.contentContainerTarget,
151
+ })
131
152
 
132
- setTimeout(() => {
133
153
  // Prevent width from changing when changing to month/year view
134
154
  if (!this.contentTarget.dataset.width) {
135
155
  const contentWidth = this.contentTarget.offsetWidth
@@ -139,32 +159,121 @@ export default class extends PopoverController {
139
159
  this.contentTarget.style.minWidth = `${contentWidth}px`
140
160
  }
141
161
 
142
- if (this.isMobile()) {
143
- // Prevent position from changing when toggling between month/year on mobile
144
- if (!this.contentTarget.dataset.top) {
145
- const rect = this.contentTarget.getBoundingClientRect()
146
- this.contentTarget.dataset.top = `${rect.top}`
147
- this.contentTarget.style.top = `${rect.top}px`
148
- this.contentTarget.classList.remove('-translate-y-1/2', 'top-1/2')
162
+ if (this.isMobile) {
163
+ lockScroll(this.contentTarget.id)
164
+ this.overlayTarget.style.display = ''
165
+ this.overlayTarget.dataset.state = 'open'
166
+ } else {
167
+ this.cleanup = initFloatingUi({
168
+ referenceElement: this.hasInputTarget
169
+ ? this.inputTarget
170
+ : this.triggerTarget,
171
+ floatingElement: this.contentContainerTarget,
172
+ side: this.contentTarget.dataset.side,
173
+ align: this.contentTarget.dataset.align,
174
+ sideOffset: 4,
175
+ })
176
+ }
177
+
178
+ let elementToFocus = null as HTMLElement | null
179
+ const focusableElements = getFocusableElements(this.contentTarget)
180
+
181
+ const selectedElement = Array.from(focusableElements).find(
182
+ (e) => e.ariaSelected,
183
+ ) as HTMLElement
184
+
185
+ const currentElement = this.contentTarget.querySelector(
186
+ '[aria-current]',
187
+ ) as HTMLElement
188
+
189
+ if (selectedElement) {
190
+ elementToFocus = selectedElement
191
+ } else if (currentElement) {
192
+ const firstElementChild =
193
+ currentElement.firstElementChild as HTMLElement
194
+ elementToFocus = firstElementChild
195
+ } else {
196
+ elementToFocus = focusableElements[0]
197
+ }
198
+
199
+ focusElement(elementToFocus)
200
+
201
+ this.setupEventListeners()
202
+ } else {
203
+ hideContent({
204
+ trigger: this.triggerTarget,
205
+ content: this.contentTarget,
206
+ contentContainer: this.contentContainerTarget,
207
+ })
208
+
209
+ if (this.isMobile) {
210
+ unlockScroll(this.contentTarget.id)
211
+ this.overlayTarget.style.display = 'none'
212
+ this.overlayTarget.dataset.state = 'closed'
213
+ }
214
+
215
+ if (previousIsOpen) {
216
+ focusTrigger(this.triggerTarget)
217
+ }
218
+
219
+ this.cleanupEventListeners()
220
+ }
221
+ }
222
+
223
+ dateValueChanged(value: string) {
224
+ if (value && value.length > 0) {
225
+ const dayjsDate = dayjs(value)
226
+ const formattedDate = dayjsDate.format(this.format)
227
+
228
+ if (this.hasInputTarget) this.inputTarget.value = formattedDate
229
+ if (this.hasTriggerTextTarget) {
230
+ this.triggerTarget.dataset.hasValue = 'true'
231
+ this.triggerTextTarget.textContent = formattedDate
232
+ }
233
+
234
+ this.hiddenInputTarget.value = dayjsDate.utc().format()
235
+ } else {
236
+ if (this.hasInputTarget) this.inputTarget.value = ''
237
+
238
+ if (this.hasTriggerTextTarget) {
239
+ this.triggerTarget.dataset.hasValue = 'false'
240
+ if (this.triggerTarget.dataset.placeholder) {
241
+ this.triggerTextTarget.textContent =
242
+ this.triggerTarget.dataset.placeholder
243
+ } else {
244
+ this.triggerTextTarget.textContent = ''
149
245
  }
150
246
  }
151
- }, ON_OPEN_FOCUS_DELAY)
247
+
248
+ this.hiddenInputTarget.value = ''
249
+ }
250
+ }
251
+
252
+ disconnect() {
253
+ this.cleanupEventListeners()
152
254
  }
153
255
 
154
- onClose() {
155
- if (this.isMobile()) {
156
- hideOverlay(this.contentTarget.id)
157
- unlockScroll()
256
+ protected onClickDate(self: Calendar) {
257
+ const date = self.context.selectedDates[0]
258
+
259
+ if (date) {
260
+ this.dateValue = date
261
+ this.close()
262
+ } else {
263
+ this.dateValue = ''
158
264
  }
159
265
  }
160
266
 
161
- // Popover is shown as a dialog on small screens with position: fixed
162
- isMobile() {
163
- const styles = window.getComputedStyle(this.contentTarget)
164
- return styles.position === 'fixed'
267
+ protected setupEventListeners() {
268
+ document.addEventListener('keydown', this.DOMKeydownListener)
269
+ }
270
+
271
+ protected cleanupEventListeners() {
272
+ if (this.cleanup) this.cleanup()
273
+ document.removeEventListener('keydown', this.DOMKeydownListener)
165
274
  }
166
275
 
167
- getOptions() {
276
+ protected getOptions() {
168
277
  let options = {
169
278
  type: 'default',
170
279
  enableJumpToSelectedDate: true,
@@ -184,7 +293,7 @@ export default class extends PopoverController {
184
293
  ...JSON.parse(this.element.dataset.options || ''),
185
294
  }
186
295
  } catch {
187
- options = options
296
+ // noop
188
297
  }
189
298
 
190
299
  if (options.selectedDates && options.selectedDates.length > 0) {
@@ -194,29 +303,15 @@ export default class extends PopoverController {
194
303
  return options
195
304
  }
196
305
 
197
- onDOMKeydown(event: KeyboardEvent) {
306
+ protected onDOMKeydown(event: KeyboardEvent) {
198
307
  if (!this.isOpenValue) return
199
308
 
200
309
  const key = event.key
201
310
 
202
- const focusableElements = getFocusableElements(this.contentTarget)
203
-
204
- const firstElement = focusableElements[0]
205
- const lastElement = focusableElements[focusableElements.length - 1]
206
-
207
311
  if (key === 'Escape') {
208
312
  this.close()
209
313
  } else if (key === 'Tab') {
210
- // If Shift + Tab pressed on first element, go to last element
211
- if (event.shiftKey && document.activeElement === firstElement) {
212
- event.preventDefault()
213
- lastElement.focus()
214
- }
215
- // If Tab pressed on last element, go to first element
216
- else if (!event.shiftKey && document.activeElement === lastElement) {
217
- event.preventDefault()
218
- firstElement.focus()
219
- }
314
+ handleTabNavigation(this.contentTarget, event)
220
315
  } else if (
221
316
  ['ArrowUp', 'ArrowDown', 'ArrowRight', 'ArrowLeft'].includes(key) &&
222
317
  document.activeElement != this.inputTarget
@@ -225,50 +320,15 @@ export default class extends PopoverController {
225
320
  }
226
321
  }
227
322
 
228
- onClickDate(self: Calendar) {
229
- const date = self.context.selectedDates[0]
230
-
231
- if (date) {
232
- this.dateValue = date
233
- this.close()
234
- } else {
235
- this.dateValue = ''
236
- }
237
- }
238
-
239
- setupInputMask() {
240
- const im = new Inputmask(this.format.replace(/[^\/]/g, '9'), {
323
+ protected setupInputMask() {
324
+ const im = new Inputmask(this.format.replace(/[^/]/g, '9'), {
241
325
  showMaskOnHover: false,
242
326
  })
243
327
  im.mask(this.inputTarget)
244
328
  }
329
+ }
245
330
 
246
- dateValueChanged(value: string) {
247
- if (value && value.length > 0) {
248
- const dayjsDate = dayjs(value)
249
- const formattedDate = dayjsDate.format(this.format)
250
-
251
- if (this.hasInputTarget) this.inputTarget.value = formattedDate
252
- if (this.hasTriggerTextTarget) {
253
- this.triggerTarget.dataset.hasValue = 'true'
254
- this.triggerTextTarget.textContent = formattedDate
255
- }
256
-
257
- this.hiddenInputTarget.value = dayjsDate.utc().format()
258
- } else {
259
- if (this.hasInputTarget) this.inputTarget.value = ''
260
-
261
- if (this.hasTriggerTextTarget) {
262
- this.triggerTarget.dataset.hasValue = 'false'
263
- if (this.triggerTarget.dataset.placeholder) {
264
- this.triggerTextTarget.textContent =
265
- this.triggerTarget.dataset.placeholder
266
- } else {
267
- this.triggerTextTarget.textContent = ''
268
- }
269
- }
331
+ type DatePicker = InstanceType<typeof DatePickerController>
270
332
 
271
- this.hiddenInputTarget.value = ''
272
- }
273
- }
274
- }
333
+ export { DatePickerController }
334
+ export type { DatePicker }
@@ -1,5 +1,5 @@
1
1
  import { Calendar, Options } from 'vanilla-calendar-pro'
2
- import DatePickerController from './date_picker_controller'
2
+ import { DatePickerController } from './date_picker_controller'
3
3
  import dayjs from 'dayjs'
4
4
  import customParseFormat from 'dayjs/plugin/customParseFormat'
5
5
  import utc from 'dayjs/plugin/utc'
@@ -9,26 +9,28 @@ dayjs.extend(utc)
9
9
  const DELIMITER = ' - '
10
10
  const DAYJS_FORMAT = 'YYYY-MM-DD'
11
11
 
12
- export default class extends DatePickerController {
12
+ const DateRangePickerController = class extends DatePickerController {
13
+ // targets
13
14
  static targets = [
14
15
  'trigger',
15
16
  'triggerText',
16
- 'contentWrapper',
17
+ 'contentContainer',
17
18
  'content',
18
19
  'input',
19
20
  'hiddenInput',
20
21
  'endHiddenInput',
21
22
  'inputContainer',
22
23
  'calendar',
24
+ 'overlay',
23
25
  ]
26
+ declare readonly endHiddenInputTarget: HTMLInputElement
24
27
 
28
+ // values
25
29
  static values = {
26
30
  isOpen: Boolean,
27
31
  date: String,
28
32
  endDate: String,
29
33
  }
30
-
31
- declare readonly endHiddenInputTarget: HTMLInputElement
32
34
  declare endDateValue: string
33
35
 
34
36
  inputBlur() {
@@ -88,80 +90,6 @@ export default class extends DatePickerController {
88
90
  }
89
91
  }
90
92
 
91
- getOptions() {
92
- let options = {
93
- type: 'multiple',
94
- selectionDatesMode: 'multiple-ranged',
95
- displayMonthsCount: 2,
96
- monthsToSwitch: 1,
97
- displayDatesOutside: false,
98
- enableJumpToSelectedDate: true,
99
- onClickDate: this.onClickDateListener,
100
- } as Options
101
-
102
- const selectedDates = []
103
-
104
- const startDate = this.element.dataset.value
105
- const endDate = this.element.dataset.endValue
106
-
107
- if (startDate && dayjs(startDate).isValid()) {
108
- const date = dayjs(startDate).format(DAYJS_FORMAT)
109
- selectedDates.push(date)
110
- }
111
-
112
- if (endDate && dayjs(endDate).isValid()) {
113
- const date = dayjs(endDate).format(DAYJS_FORMAT)
114
- selectedDates.push(date)
115
- }
116
-
117
- options.selectedDates = selectedDates
118
-
119
- try {
120
- options = {
121
- ...options,
122
- ...JSON.parse(this.element.dataset.options || ''),
123
- }
124
- } catch {
125
- options = options
126
- }
127
-
128
- if (options.selectedDates && options.selectedDates.length > 0) {
129
- this.dateValue = `${options.selectedDates[0]}`
130
- this.endDateValue = `${options.selectedDates[1]}`
131
- }
132
-
133
- return options
134
- }
135
-
136
- onClickDate(self: Calendar) {
137
- const dates = self.context.selectedDates
138
-
139
- if (dates.length > 0) {
140
- const startDate = dates[0]
141
- const endDate = dates[1]
142
-
143
- this.dateValue = startDate
144
-
145
- if (endDate) {
146
- this.endDateValue = endDate
147
- this.close()
148
- } else {
149
- this.endDateValue = ''
150
- }
151
- } else {
152
- this.dateValue = ''
153
- this.endDateValue = ''
154
- }
155
- }
156
-
157
- setupInputMask() {
158
- const pattern = this.format.replace(/[^\/]/g, '9')
159
- const im = new Inputmask(`${pattern}${DELIMITER}${pattern}`, {
160
- showMaskOnHover: false,
161
- })
162
- im.mask(this.inputTarget)
163
- }
164
-
165
93
  dateValueChanged(value: string) {
166
94
  this.onClickDateListener = this.onClickDate.bind(this)
167
95
 
@@ -228,6 +156,7 @@ export default class extends DatePickerController {
228
156
  }
229
157
 
230
158
  if (this.hasInputTarget) this.inputTarget.value = datesDisplay
159
+
231
160
  if (this.hasTriggerTextTarget) {
232
161
  const hasValue = (!!value && value.length > 0) || !!startDate
233
162
  this.triggerTarget.dataset.hasValue = `${hasValue}`
@@ -240,4 +169,85 @@ export default class extends DatePickerController {
240
169
  }
241
170
  }
242
171
  }
172
+
173
+ protected getOptions() {
174
+ let options = {
175
+ type: 'multiple',
176
+ selectionDatesMode: 'multiple-ranged',
177
+ displayMonthsCount: 2,
178
+ monthsToSwitch: 1,
179
+ displayDatesOutside: false,
180
+ enableJumpToSelectedDate: true,
181
+ onClickDate: this.onClickDateListener,
182
+ } as Options
183
+
184
+ const selectedDates = []
185
+
186
+ const startDate = this.element.dataset.value
187
+ const endDate = this.element.dataset.endValue
188
+
189
+ if (startDate && dayjs(startDate).isValid()) {
190
+ const date = dayjs(startDate).format(DAYJS_FORMAT)
191
+ selectedDates.push(date)
192
+ }
193
+
194
+ if (endDate && dayjs(endDate).isValid()) {
195
+ const date = dayjs(endDate).format(DAYJS_FORMAT)
196
+ selectedDates.push(date)
197
+ }
198
+
199
+ options.selectedDates = selectedDates
200
+
201
+ try {
202
+ options = {
203
+ ...options,
204
+ ...JSON.parse(this.element.dataset.options || ''),
205
+ }
206
+ } catch {
207
+ // noop
208
+ }
209
+
210
+ if (options.selectedDates && options.selectedDates.length > 0) {
211
+ this.dateValue = `${options.selectedDates[0]}`
212
+ if (options.selectedDates[1]) {
213
+ this.endDateValue = `${options.selectedDates[1]}`
214
+ }
215
+ }
216
+
217
+ return options
218
+ }
219
+
220
+ protected onClickDate(self: Calendar) {
221
+ const dates = self.context.selectedDates
222
+
223
+ if (dates.length > 0) {
224
+ const startDate = dates[0]
225
+ const endDate = dates[1]
226
+
227
+ this.dateValue = startDate
228
+
229
+ if (endDate) {
230
+ this.endDateValue = endDate
231
+ this.close()
232
+ } else {
233
+ this.endDateValue = ''
234
+ }
235
+ } else {
236
+ this.dateValue = ''
237
+ this.endDateValue = ''
238
+ }
239
+ }
240
+
241
+ protected setupInputMask() {
242
+ const pattern = this.format.replace(/[^\/]/g, '9')
243
+ const im = new Inputmask(`${pattern}${DELIMITER}${pattern}`, {
244
+ showMaskOnHover: false,
245
+ })
246
+ im.mask(this.inputTarget)
247
+ }
243
248
  }
249
+
250
+ type DateRangePicker = InstanceType<typeof DateRangePickerController>
251
+
252
+ export { DateRangePickerController }
253
+ export type { DateRangePicker }