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.
- checksums.yaml +4 -4
- data/app/javascript/controllers/accordion_controller.js +107 -0
- data/app/javascript/controllers/alert_dialog_controller.js +7 -0
- data/app/javascript/controllers/avatar_controller.js +14 -0
- data/app/javascript/controllers/checkbox_controller.js +29 -0
- data/app/javascript/controllers/collapsible_controller.js +39 -0
- data/app/javascript/controllers/combobox_controller.js +278 -0
- data/app/javascript/controllers/command_controller.js +207 -0
- data/app/javascript/controllers/date_picker_controller.js +258 -0
- data/app/javascript/controllers/date_range_picker_controller.js +200 -0
- data/app/javascript/controllers/dialog_controller.js +83 -0
- data/app/javascript/controllers/dropdown_menu_controller.js +238 -0
- data/app/javascript/controllers/dropdown_menu_sub_controller.js +118 -0
- data/app/javascript/controllers/form_field_controller.js +20 -0
- data/app/javascript/controllers/hover_card_controller.js +73 -0
- data/app/javascript/controllers/loading_button_controller.js +14 -0
- data/app/javascript/controllers/popover_controller.js +90 -0
- data/app/javascript/controllers/progress_controller.js +14 -0
- data/app/javascript/controllers/radio_group_controller.js +80 -0
- data/app/javascript/controllers/select_controller.js +265 -0
- data/app/javascript/controllers/sidebar_controller.js +29 -0
- data/app/javascript/controllers/sidebar_trigger_controller.js +15 -0
- data/app/javascript/controllers/slider_controller.js +82 -0
- data/app/javascript/controllers/switch_controller.js +26 -0
- data/app/javascript/controllers/tabs_controller.js +66 -0
- data/app/javascript/controllers/theme_switcher_controller.js +32 -0
- data/app/javascript/controllers/toast_container_controller.js +48 -0
- data/app/javascript/controllers/toast_controller.js +22 -0
- data/app/javascript/controllers/toggle_controller.js +20 -0
- data/app/javascript/controllers/toggle_group_controller.js +20 -0
- data/app/javascript/controllers/tooltip_controller.js +79 -0
- data/app/javascript/shadcn_phlexcomponents.js +60 -0
- data/app/javascript/utils/command.js +448 -0
- data/app/javascript/utils/floating_ui.js +160 -0
- data/app/javascript/utils/index.js +288 -0
- data/app/stylesheets/date_picker.css +118 -0
- data/app/typescript/controllers/accordion_controller.ts +136 -0
- data/app/typescript/controllers/alert_dialog_controller.ts +12 -0
- data/app/{javascript → typescript}/controllers/avatar_controller.ts +7 -2
- data/app/{javascript → typescript}/controllers/checkbox_controller.ts +11 -4
- data/app/{javascript → typescript}/controllers/collapsible_controller.ts +12 -5
- data/app/typescript/controllers/combobox_controller.ts +376 -0
- data/app/typescript/controllers/command_controller.ts +301 -0
- data/app/{javascript → typescript}/controllers/date_picker_controller.ts +185 -125
- data/app/{javascript → typescript}/controllers/date_range_picker_controller.ts +89 -79
- data/app/{javascript → typescript}/controllers/dialog_controller.ts +59 -57
- data/app/typescript/controllers/dropdown_menu_controller.ts +309 -0
- data/app/{javascript → typescript}/controllers/dropdown_menu_sub_controller.ts +31 -29
- data/app/{javascript → typescript}/controllers/form_field_controller.ts +6 -1
- data/app/{javascript → typescript}/controllers/hover_card_controller.ts +36 -26
- data/app/{javascript → typescript}/controllers/loading_button_controller.ts +6 -1
- data/app/{javascript → typescript}/controllers/popover_controller.ts +42 -65
- data/app/{javascript → typescript}/controllers/progress_controller.ts +9 -3
- data/app/{javascript → typescript}/controllers/radio_group_controller.ts +16 -9
- data/app/typescript/controllers/select_controller.ts +341 -0
- data/app/{javascript → typescript}/controllers/slider_controller.ts +23 -16
- data/app/{javascript → typescript}/controllers/switch_controller.ts +11 -4
- data/app/{javascript → typescript}/controllers/tabs_controller.ts +26 -18
- data/app/{javascript → typescript}/controllers/theme_switcher_controller.ts +6 -1
- data/app/{javascript → typescript}/controllers/toast_container_controller.ts +6 -1
- data/app/{javascript → typescript}/controllers/toast_controller.ts +7 -1
- data/app/typescript/controllers/toggle_controller.ts +28 -0
- data/app/typescript/controllers/toggle_group_controller.ts +28 -0
- data/app/{javascript → typescript}/controllers/tooltip_controller.ts +43 -31
- data/app/typescript/shadcn_phlexcomponents.ts +61 -0
- data/app/typescript/utils/command.ts +544 -0
- data/app/typescript/utils/floating_ui.ts +196 -0
- data/app/typescript/utils/index.ts +424 -0
- data/lib/install/install_shadcn_phlexcomponents.rb +10 -3
- data/lib/shadcn_phlexcomponents/alias.rb +3 -0
- data/lib/shadcn_phlexcomponents/components/accordion.rb +2 -1
- data/lib/shadcn_phlexcomponents/components/alert_dialog.rb +18 -15
- data/lib/shadcn_phlexcomponents/components/base.rb +14 -0
- data/lib/shadcn_phlexcomponents/components/collapsible.rb +1 -2
- data/lib/shadcn_phlexcomponents/components/combobox.rb +87 -57
- data/lib/shadcn_phlexcomponents/components/command.rb +77 -47
- data/lib/shadcn_phlexcomponents/components/date_picker.rb +25 -81
- data/lib/shadcn_phlexcomponents/components/date_range_picker.rb +21 -4
- data/lib/shadcn_phlexcomponents/components/dialog.rb +14 -12
- data/lib/shadcn_phlexcomponents/components/dropdown_menu.rb +5 -4
- data/lib/shadcn_phlexcomponents/components/dropdown_menu_sub.rb +2 -1
- data/lib/shadcn_phlexcomponents/components/form/form_combobox.rb +64 -0
- data/lib/shadcn_phlexcomponents/components/form.rb +14 -0
- data/lib/shadcn_phlexcomponents/components/hover_card.rb +3 -2
- data/lib/shadcn_phlexcomponents/components/popover.rb +3 -3
- data/lib/shadcn_phlexcomponents/components/select.rb +10 -25
- data/lib/shadcn_phlexcomponents/components/sheet.rb +15 -11
- data/lib/shadcn_phlexcomponents/components/table.rb +1 -1
- data/lib/shadcn_phlexcomponents/components/tabs.rb +1 -1
- data/lib/shadcn_phlexcomponents/components/toast_container.rb +1 -1
- data/lib/shadcn_phlexcomponents/components/toggle.rb +54 -0
- data/lib/shadcn_phlexcomponents/components/tooltip.rb +3 -2
- data/lib/shadcn_phlexcomponents/engine.rb +1 -5
- data/lib/shadcn_phlexcomponents/version.rb +1 -1
- metadata +71 -32
- data/app/javascript/controllers/accordion_controller.ts +0 -133
- data/app/javascript/controllers/combobox_controller.ts +0 -145
- data/app/javascript/controllers/command_controller.ts +0 -129
- data/app/javascript/controllers/command_root_controller.ts +0 -355
- data/app/javascript/controllers/dropdown_menu_controller.ts +0 -133
- data/app/javascript/controllers/dropdown_menu_root_controller.ts +0 -234
- data/app/javascript/controllers/select_controller.ts +0 -200
- data/app/javascript/shadcn_phlexcomponents.ts +0 -57
- data/app/javascript/utils.ts +0 -437
- /data/app/{javascript → typescript}/controllers/sidebar_controller.ts +0 -0
- /data/app/{javascript → typescript}/controllers/sidebar_trigger_controller.ts +0 -0
@@ -1,200 +0,0 @@
|
|
1
|
-
import DropdownMenuRootController from './dropdown_menu_root_controller'
|
2
|
-
|
3
|
-
export default class extends DropdownMenuRootController {
|
4
|
-
static targets = [
|
5
|
-
'trigger',
|
6
|
-
'contentContainer',
|
7
|
-
'content',
|
8
|
-
'item',
|
9
|
-
'triggerText',
|
10
|
-
'group',
|
11
|
-
'label',
|
12
|
-
'select',
|
13
|
-
]
|
14
|
-
|
15
|
-
static values = {
|
16
|
-
isOpen: Boolean,
|
17
|
-
selected: String,
|
18
|
-
setEqualWidth: { type: Boolean, default: true },
|
19
|
-
closestContentSelector: {
|
20
|
-
type: String,
|
21
|
-
default: '[data-select-target="content"]',
|
22
|
-
},
|
23
|
-
}
|
24
|
-
|
25
|
-
declare selectedValue: string
|
26
|
-
declare searchString: string
|
27
|
-
declare searchTimeout: number
|
28
|
-
declare groupTargets: HTMLElement[]
|
29
|
-
declare triggerTextTarget: HTMLElement
|
30
|
-
declare selectTarget: HTMLSelectElement
|
31
|
-
declare itemsInnerText: string[]
|
32
|
-
|
33
|
-
connect() {
|
34
|
-
super.connect()
|
35
|
-
this.itemsInnerText = this.items.map((i) => i.innerText.trim())
|
36
|
-
this.setAriaLabelledby()
|
37
|
-
this.searchString = ''
|
38
|
-
}
|
39
|
-
|
40
|
-
setAriaLabelledby() {
|
41
|
-
this.groupTargets.forEach((g) => {
|
42
|
-
const label = g.querySelector(
|
43
|
-
'[data-select-target="label"]',
|
44
|
-
) as HTMLElement
|
45
|
-
|
46
|
-
if (label) {
|
47
|
-
label.id = g.getAttribute('aria-labelledby') as string
|
48
|
-
}
|
49
|
-
})
|
50
|
-
}
|
51
|
-
|
52
|
-
onOpenFocusedElement(event: MouseEvent | KeyboardEvent) {
|
53
|
-
let itemIndex = null as number | null
|
54
|
-
|
55
|
-
if (this.selectedValue) {
|
56
|
-
const item = this.itemTargets.find(
|
57
|
-
(i) => i.dataset.value === this.selectedValue,
|
58
|
-
)
|
59
|
-
|
60
|
-
if (item && !item.dataset.disabled) {
|
61
|
-
itemIndex = this.items.indexOf(item)
|
62
|
-
}
|
63
|
-
} else {
|
64
|
-
if (event instanceof KeyboardEvent) {
|
65
|
-
const key = event.key
|
66
|
-
|
67
|
-
if (['ArrowDown', 'Enter', ' '].includes(key)) {
|
68
|
-
itemIndex = 0
|
69
|
-
}
|
70
|
-
}
|
71
|
-
}
|
72
|
-
|
73
|
-
if (itemIndex !== null) {
|
74
|
-
return this.items[itemIndex]
|
75
|
-
} else {
|
76
|
-
return this.contentTarget
|
77
|
-
}
|
78
|
-
}
|
79
|
-
|
80
|
-
onSelect(event: MouseEvent | KeyboardEvent) {
|
81
|
-
const item = event.currentTarget as HTMLElement
|
82
|
-
const value = item.dataset.value as string
|
83
|
-
this.selectedValue = value
|
84
|
-
}
|
85
|
-
|
86
|
-
onDOMKeydown(event: KeyboardEvent) {
|
87
|
-
super.onDOMKeydown(event)
|
88
|
-
|
89
|
-
const { key, altKey, ctrlKey, metaKey } = event
|
90
|
-
|
91
|
-
if (
|
92
|
-
key === 'Backspace' ||
|
93
|
-
key === 'Clear' ||
|
94
|
-
(key.length === 1 && key !== ' ' && !altKey && !ctrlKey && !metaKey)
|
95
|
-
) {
|
96
|
-
this.handleSearch(key)
|
97
|
-
}
|
98
|
-
}
|
99
|
-
|
100
|
-
// https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/
|
101
|
-
handleSearch(char: string) {
|
102
|
-
const searchString = this.getSearchString(char)
|
103
|
-
const focusedItem = this.items.find(
|
104
|
-
(item) => document.activeElement === item,
|
105
|
-
)
|
106
|
-
const focusedIndex = focusedItem ? this.items.indexOf(focusedItem) : 0
|
107
|
-
const searchIndex = this.getIndexByLetter(searchString, focusedIndex + 1)
|
108
|
-
|
109
|
-
// if a match was found, go to it
|
110
|
-
if (searchIndex >= 0) {
|
111
|
-
this.focusItemByIndex(null, searchIndex)
|
112
|
-
}
|
113
|
-
// if no matches, clear the timeout and search string
|
114
|
-
else {
|
115
|
-
window.clearTimeout(this.searchTimeout)
|
116
|
-
this.searchString = ''
|
117
|
-
}
|
118
|
-
}
|
119
|
-
|
120
|
-
filterItemsInnerText(items: string[], filter: string) {
|
121
|
-
return items.filter((item) => {
|
122
|
-
const matches = item.toLowerCase().indexOf(filter.toLowerCase()) === 0
|
123
|
-
return matches
|
124
|
-
})
|
125
|
-
}
|
126
|
-
|
127
|
-
getSearchString(char: string) {
|
128
|
-
// reset typing timeout and start new timeout
|
129
|
-
// this allows us to make multiple-letter matches, like a native select
|
130
|
-
if (typeof this.searchTimeout === 'number') {
|
131
|
-
window.clearTimeout(this.searchTimeout)
|
132
|
-
}
|
133
|
-
|
134
|
-
this.searchTimeout = window.setTimeout(() => {
|
135
|
-
this.searchString = ''
|
136
|
-
}, 500)
|
137
|
-
|
138
|
-
// add most recent letter to saved search string
|
139
|
-
this.searchString += char
|
140
|
-
return this.searchString
|
141
|
-
}
|
142
|
-
|
143
|
-
// return the index of an option from an array of options, based on a search string
|
144
|
-
// if the filter is multiple iterations of the same letter (e.g "aaa"), then cycle through first-letter matches
|
145
|
-
getIndexByLetter(filter: string, startIndex: number) {
|
146
|
-
const orderedItems = [
|
147
|
-
...this.itemsInnerText.slice(startIndex),
|
148
|
-
...this.itemsInnerText.slice(0, startIndex),
|
149
|
-
]
|
150
|
-
|
151
|
-
const firstMatch = this.filterItemsInnerText(orderedItems, filter)[0]
|
152
|
-
|
153
|
-
const allSameLetter = (array: string[]) =>
|
154
|
-
array.every((letter) => letter === array[0])
|
155
|
-
|
156
|
-
// first check if there is an exact match for the typed string
|
157
|
-
if (firstMatch) {
|
158
|
-
const index = this.itemsInnerText.indexOf(firstMatch)
|
159
|
-
return index
|
160
|
-
}
|
161
|
-
|
162
|
-
// if the same letter is being repeated, cycle through first-letter matches
|
163
|
-
else if (allSameLetter(filter.split(''))) {
|
164
|
-
const matches = this.filterItemsInnerText(orderedItems, filter[0])
|
165
|
-
const index = this.itemsInnerText.indexOf(matches[0])
|
166
|
-
return index
|
167
|
-
}
|
168
|
-
|
169
|
-
// if no matches, return -1
|
170
|
-
else {
|
171
|
-
return -1
|
172
|
-
}
|
173
|
-
}
|
174
|
-
|
175
|
-
selectedValueChanged(value: string) {
|
176
|
-
const item = this.itemTargets.find((i) => i.dataset.value === value)
|
177
|
-
|
178
|
-
if (item) {
|
179
|
-
this.triggerTextTarget.textContent = item.textContent
|
180
|
-
|
181
|
-
this.itemTargets.forEach((i) => {
|
182
|
-
if (i.dataset.value === value) {
|
183
|
-
i.setAttribute('aria-selected', 'true')
|
184
|
-
} else {
|
185
|
-
i.setAttribute('aria-selected', 'false')
|
186
|
-
}
|
187
|
-
})
|
188
|
-
|
189
|
-
this.selectTarget.value = value
|
190
|
-
}
|
191
|
-
|
192
|
-
this.triggerTarget.dataset.hasValue = `${!!value && value.length > 0}`
|
193
|
-
|
194
|
-
const placeholder = this.triggerTarget.dataset.placeholder
|
195
|
-
|
196
|
-
if (placeholder && this.triggerTarget.dataset.hasValue === 'false') {
|
197
|
-
this.triggerTextTarget.textContent = placeholder
|
198
|
-
}
|
199
|
-
}
|
200
|
-
}
|
@@ -1,57 +0,0 @@
|
|
1
|
-
import AccordionController from './controllers/accordion_controller'
|
2
|
-
import AvatarController from './controllers/avatar_controller'
|
3
|
-
import CheckboxController from './controllers/checkbox_controller'
|
4
|
-
import CollapsibleController from './controllers/collapsible_controller'
|
5
|
-
import ComboboxController from './controllers/combobox_controller'
|
6
|
-
import CommandController from './controllers/command_controller'
|
7
|
-
import DatePickerController from './controllers/date_picker_controller'
|
8
|
-
import DateRangePickerController from './controllers/date_range_picker_controller'
|
9
|
-
import DialogController from './controllers/dialog_controller'
|
10
|
-
import DropdownMenuController from './controllers/dropdown_menu_controller'
|
11
|
-
import DropdownMenuSubController from './controllers/dropdown_menu_sub_controller'
|
12
|
-
import FormFieldController from './controllers/form_field_controller'
|
13
|
-
import HoverCardController from './controllers/hover_card_controller'
|
14
|
-
import LoadingButtonController from './controllers/loading_button_controller'
|
15
|
-
import PopoverController from './controllers/popover_controller'
|
16
|
-
import ProgressController from './controllers/progress_controller'
|
17
|
-
import RadioGroupController from './controllers/radio_group_controller'
|
18
|
-
import SelectController from './controllers/select_controller'
|
19
|
-
import SidebarController from './controllers/sidebar_controller'
|
20
|
-
import SidebarTriggerController from './controllers/sidebar_trigger_controller'
|
21
|
-
import SliderController from './controllers/slider_controller'
|
22
|
-
import SwitchController from './controllers/switch_controller'
|
23
|
-
import TabsController from './controllers/tabs_controller'
|
24
|
-
import ThemeSwitcherController from './controllers/theme_switcher_controller'
|
25
|
-
import ToastContainerController from './controllers/toast_container_controller'
|
26
|
-
import ToastController from './controllers/toast_controller'
|
27
|
-
import TooltipController from './controllers/tooltip_controller'
|
28
|
-
|
29
|
-
export default {
|
30
|
-
accordion: AccordionController,
|
31
|
-
avatar: AvatarController,
|
32
|
-
checkbox: CheckboxController,
|
33
|
-
collapsible: CollapsibleController,
|
34
|
-
combobox: ComboboxController,
|
35
|
-
command: CommandController,
|
36
|
-
'date-picker': DatePickerController,
|
37
|
-
'date-range-picker': DateRangePickerController,
|
38
|
-
dialog: DialogController,
|
39
|
-
'dropdown-menu': DropdownMenuController,
|
40
|
-
'dropdown-menu-sub': DropdownMenuSubController,
|
41
|
-
'form-field': FormFieldController,
|
42
|
-
'hover-card': HoverCardController,
|
43
|
-
'loading-button': LoadingButtonController,
|
44
|
-
popover: PopoverController,
|
45
|
-
progress: ProgressController,
|
46
|
-
'radio-group': RadioGroupController,
|
47
|
-
select: SelectController,
|
48
|
-
sidebar: SidebarController,
|
49
|
-
'sidebar-trigger': SidebarTriggerController,
|
50
|
-
slider: SliderController,
|
51
|
-
switch: SwitchController,
|
52
|
-
tabs: TabsController,
|
53
|
-
'theme-switcher': ThemeSwitcherController,
|
54
|
-
'toast-container': ToastContainerController,
|
55
|
-
toast: ToastController,
|
56
|
-
tooltip: TooltipController,
|
57
|
-
}
|
data/app/javascript/utils.ts
DELETED
@@ -1,437 +0,0 @@
|
|
1
|
-
import {
|
2
|
-
computePosition,
|
3
|
-
flip,
|
4
|
-
shift,
|
5
|
-
offset,
|
6
|
-
autoUpdate,
|
7
|
-
size,
|
8
|
-
Placement,
|
9
|
-
Middleware,
|
10
|
-
arrow,
|
11
|
-
} from '@floating-ui/dom'
|
12
|
-
|
13
|
-
const ANIMATION_OUT_DELAY = 100
|
14
|
-
const ON_OPEN_FOCUS_DELAY = 100
|
15
|
-
const ON_CLOSE_FOCUS_DELAY = 50
|
16
|
-
|
17
|
-
const OPPOSITE_SIDE = {
|
18
|
-
top: 'bottom',
|
19
|
-
right: 'left',
|
20
|
-
bottom: 'top',
|
21
|
-
left: 'right',
|
22
|
-
}
|
23
|
-
|
24
|
-
const ARROW_TRANSFORM_ORIGIN = {
|
25
|
-
top: '',
|
26
|
-
right: '0 0',
|
27
|
-
bottom: 'center 0',
|
28
|
-
left: '100% 0',
|
29
|
-
}
|
30
|
-
|
31
|
-
const ARROW_TRANSFORM = {
|
32
|
-
top: 'translateY(100%)',
|
33
|
-
right: 'translateY(50%) rotate(90deg) translateX(-50%)',
|
34
|
-
bottom: `rotate(180deg)`,
|
35
|
-
left: 'translateY(50%) rotate(-90deg) translateX(50%)',
|
36
|
-
}
|
37
|
-
|
38
|
-
const getScrollbarWidth = () => {
|
39
|
-
// Create a temporary div container and append it into the body
|
40
|
-
const outer = document.createElement('div')
|
41
|
-
outer.style.visibility = 'hidden'
|
42
|
-
outer.style.overflow = 'scroll' // force scrollbars
|
43
|
-
outer.style.width = '100px'
|
44
|
-
outer.style.position = 'absolute'
|
45
|
-
outer.style.top = '-9999px'
|
46
|
-
document.body.appendChild(outer)
|
47
|
-
|
48
|
-
// Create an inner div and place it inside the outer div
|
49
|
-
const inner = document.createElement('div')
|
50
|
-
inner.style.width = '100%'
|
51
|
-
outer.appendChild(inner)
|
52
|
-
|
53
|
-
// Calculate the scrollbar width
|
54
|
-
const scrollbarWidth = outer.offsetWidth - inner.offsetWidth
|
55
|
-
|
56
|
-
// Clean up
|
57
|
-
outer.remove()
|
58
|
-
|
59
|
-
return scrollbarWidth
|
60
|
-
}
|
61
|
-
|
62
|
-
const showOverlay = ({
|
63
|
-
classNames = '',
|
64
|
-
elementId,
|
65
|
-
}: {
|
66
|
-
classNames?: string
|
67
|
-
elementId: string
|
68
|
-
}) => {
|
69
|
-
const element = document.createElement('div')
|
70
|
-
|
71
|
-
let defaultClassNames = [
|
72
|
-
'data-[state=open]:animate-in',
|
73
|
-
'data-[state=closed]:animate-out',
|
74
|
-
'data-[state=closed]:fade-out-0',
|
75
|
-
'data-[state=open]:fade-in-0',
|
76
|
-
'fixed',
|
77
|
-
'inset-0',
|
78
|
-
'z-[48]',
|
79
|
-
'bg-black/50',
|
80
|
-
'pointer-events-auto',
|
81
|
-
]
|
82
|
-
|
83
|
-
defaultClassNames = defaultClassNames.concat(
|
84
|
-
classNames.split(' ').filter((c) => !!c),
|
85
|
-
)
|
86
|
-
|
87
|
-
element.classList.add(...defaultClassNames)
|
88
|
-
element.dataset.state = 'open'
|
89
|
-
element.dataset.shadcnPhlexcomponentsOverlay = elementId
|
90
|
-
element.ariaHidden = 'true'
|
91
|
-
|
92
|
-
document.body.appendChild(element)
|
93
|
-
}
|
94
|
-
|
95
|
-
const hideOverlay = (elementId: string) => {
|
96
|
-
const element = document.querySelector(
|
97
|
-
`[data-shadcn-phlexcomponents-overlay=${elementId}]`,
|
98
|
-
)
|
99
|
-
|
100
|
-
if (element && element instanceof HTMLElement) {
|
101
|
-
element.dataset.state = 'closed'
|
102
|
-
|
103
|
-
setTimeout(() => {
|
104
|
-
element.remove()
|
105
|
-
}, ANIMATION_OUT_DELAY)
|
106
|
-
}
|
107
|
-
}
|
108
|
-
|
109
|
-
const lockScroll = () => {
|
110
|
-
if (window.innerHeight < document.documentElement.scrollHeight) {
|
111
|
-
document.body.dataset.scrollLocked = '1'
|
112
|
-
document.body.classList.add(
|
113
|
-
'data-[scroll-locked]:pointer-events-none',
|
114
|
-
'data-[scroll-locked]:!overflow-hidden',
|
115
|
-
'data-[scroll-locked]:!relative',
|
116
|
-
'data-[scroll-locked]:px-0',
|
117
|
-
'data-[scroll-locked]:pt-0',
|
118
|
-
'data-[scroll-locked]:ml-0',
|
119
|
-
'data-[scroll-locked]:mt-0',
|
120
|
-
)
|
121
|
-
const style = getComputedStyle(document.body)
|
122
|
-
const originalMarginRight = style.marginRight
|
123
|
-
document.body.dataset.marginRight = originalMarginRight
|
124
|
-
document.body.style.marginRight = `${getScrollbarWidth()}px`
|
125
|
-
}
|
126
|
-
}
|
127
|
-
|
128
|
-
const unlockScroll = () => {
|
129
|
-
if (document.body.dataset.scrollLocked) {
|
130
|
-
delete document.body.dataset.scrollLocked
|
131
|
-
document.body.classList.remove(
|
132
|
-
'data-[scroll-locked]:pointer-events-none',
|
133
|
-
'data-[scroll-locked]:!overflow-hidden',
|
134
|
-
'data-[scroll-locked]:!relative',
|
135
|
-
'data-[scroll-locked]:px-0',
|
136
|
-
'data-[scroll-locked]:pt-0',
|
137
|
-
'data-[scroll-locked]:ml-0',
|
138
|
-
'data-[scroll-locked]:mt-0',
|
139
|
-
)
|
140
|
-
|
141
|
-
const originalMarginRight = document.body.dataset.marginRight
|
142
|
-
|
143
|
-
if (originalMarginRight && parseInt(originalMarginRight) === 0) {
|
144
|
-
document.body.style.marginRight = ''
|
145
|
-
} else {
|
146
|
-
document.body.style.marginRight = `${originalMarginRight}`
|
147
|
-
}
|
148
|
-
|
149
|
-
delete document.body.dataset.marginRight
|
150
|
-
}
|
151
|
-
}
|
152
|
-
|
153
|
-
const openWithOverlay = (elementId: string) => {
|
154
|
-
showOverlay({ elementId })
|
155
|
-
lockScroll()
|
156
|
-
}
|
157
|
-
|
158
|
-
const closeWithOverlay = (elementId: string) => {
|
159
|
-
hideOverlay(elementId)
|
160
|
-
unlockScroll()
|
161
|
-
}
|
162
|
-
|
163
|
-
const initFloatingUi = ({
|
164
|
-
referenceElement,
|
165
|
-
floatingElement,
|
166
|
-
side = 'bottom',
|
167
|
-
align = 'center',
|
168
|
-
sideOffset = 0,
|
169
|
-
alignOffset = 0,
|
170
|
-
arrowElement,
|
171
|
-
}: {
|
172
|
-
referenceElement: HTMLElement
|
173
|
-
floatingElement: HTMLElement
|
174
|
-
side?: string
|
175
|
-
align?: string
|
176
|
-
sideOffset?: number
|
177
|
-
alignOffset?: number
|
178
|
-
offsetPx?: number
|
179
|
-
arrowElement?: HTMLElement
|
180
|
-
}) => {
|
181
|
-
let placement = `${side}-${align}`
|
182
|
-
placement = placement.replace(/-center/g, '')
|
183
|
-
|
184
|
-
let arrowHeight = 0,
|
185
|
-
arrowWidth = 0
|
186
|
-
|
187
|
-
if (arrowElement) {
|
188
|
-
const rect = arrowElement.getBoundingClientRect()
|
189
|
-
arrowWidth = rect.width
|
190
|
-
arrowHeight = rect.height
|
191
|
-
}
|
192
|
-
|
193
|
-
const middleware = [
|
194
|
-
transformOrigin({ arrowHeight, arrowWidth }),
|
195
|
-
offset({ mainAxis: sideOffset, alignmentAxis: alignOffset }),
|
196
|
-
size({
|
197
|
-
apply: ({ elements, rects, availableWidth, availableHeight }) => {
|
198
|
-
const { width: anchorWidth, height: anchorHeight } = rects.reference
|
199
|
-
const contentStyle = elements.floating.style
|
200
|
-
contentStyle.setProperty(
|
201
|
-
'--radix-popper-available-width',
|
202
|
-
`${availableWidth}px`,
|
203
|
-
)
|
204
|
-
contentStyle.setProperty(
|
205
|
-
'--radix-popper-available-height',
|
206
|
-
`${availableHeight}px`,
|
207
|
-
)
|
208
|
-
contentStyle.setProperty(
|
209
|
-
'--radix-popper-anchor-width',
|
210
|
-
`${anchorWidth}px`,
|
211
|
-
)
|
212
|
-
contentStyle.setProperty(
|
213
|
-
'--radix-popper-anchor-height',
|
214
|
-
`${anchorHeight}px`,
|
215
|
-
)
|
216
|
-
},
|
217
|
-
}),
|
218
|
-
]
|
219
|
-
|
220
|
-
const flipMiddleware = flip({
|
221
|
-
// Ensure we flip to the perpendicular axis if it doesn't fit
|
222
|
-
// on narrow viewports.
|
223
|
-
crossAxis: 'alignment',
|
224
|
-
fallbackAxisSideDirection: 'end', // or 'start'
|
225
|
-
})
|
226
|
-
const shiftMiddleware = shift()
|
227
|
-
|
228
|
-
// Prioritize flip over shift for edge-aligned placements only.
|
229
|
-
if (placement.includes('-')) {
|
230
|
-
middleware.push(flipMiddleware, shiftMiddleware)
|
231
|
-
} else {
|
232
|
-
middleware.push(shiftMiddleware, flipMiddleware)
|
233
|
-
}
|
234
|
-
|
235
|
-
if (arrowElement) {
|
236
|
-
middleware.push(arrow({ element: arrowElement, padding: 0 }))
|
237
|
-
}
|
238
|
-
|
239
|
-
return autoUpdate(referenceElement, floatingElement, () => {
|
240
|
-
computePosition(referenceElement, floatingElement, {
|
241
|
-
placement: placement as Placement,
|
242
|
-
strategy: 'fixed',
|
243
|
-
middleware,
|
244
|
-
}).then(({ middlewareData, x, y }) => {
|
245
|
-
const arrowX = middlewareData.arrow?.x
|
246
|
-
const arrowY = middlewareData.arrow?.y
|
247
|
-
const cannotCenterArrow = middlewareData.arrow?.centerOffset !== 0
|
248
|
-
|
249
|
-
floatingElement.style.setProperty(
|
250
|
-
'--radix-popper-transform-origin',
|
251
|
-
`${middlewareData.transformOrigin?.x} ${middlewareData.transformOrigin?.y}`,
|
252
|
-
)
|
253
|
-
if (arrowElement) {
|
254
|
-
const baseSide = OPPOSITE_SIDE[side as keyof typeof OPPOSITE_SIDE]
|
255
|
-
|
256
|
-
const arrowStyle = {
|
257
|
-
position: 'absolute',
|
258
|
-
left: arrowX ? `${arrowX}px` : undefined,
|
259
|
-
top: arrowY ? `${arrowY}px` : undefined,
|
260
|
-
[baseSide]: 0,
|
261
|
-
transformOrigin:
|
262
|
-
ARROW_TRANSFORM_ORIGIN[side as keyof typeof ARROW_TRANSFORM_ORIGIN],
|
263
|
-
transform: ARROW_TRANSFORM[side as keyof typeof ARROW_TRANSFORM],
|
264
|
-
visibility: cannotCenterArrow ? 'hidden' : undefined,
|
265
|
-
}
|
266
|
-
|
267
|
-
Object.assign(arrowElement.style, arrowStyle)
|
268
|
-
}
|
269
|
-
Object.assign(floatingElement.style, {
|
270
|
-
left: `${x}px`,
|
271
|
-
top: `${y}px`,
|
272
|
-
})
|
273
|
-
})
|
274
|
-
})
|
275
|
-
}
|
276
|
-
|
277
|
-
const transformOrigin = (options: {
|
278
|
-
arrowWidth: number
|
279
|
-
arrowHeight: number
|
280
|
-
}): Middleware => {
|
281
|
-
return {
|
282
|
-
name: 'transformOrigin',
|
283
|
-
options,
|
284
|
-
fn(data) {
|
285
|
-
const { placement, rects, middlewareData } = data
|
286
|
-
const cannotCenterArrow = middlewareData.arrow?.centerOffset !== 0
|
287
|
-
const isArrowHidden = cannotCenterArrow
|
288
|
-
const arrowWidth = isArrowHidden ? 0 : options.arrowWidth
|
289
|
-
const arrowHeight = isArrowHidden ? 0 : options.arrowHeight
|
290
|
-
|
291
|
-
const [placedSide, placedAlign] = getSideAndAlignFromPlacement(placement)
|
292
|
-
const noArrowAlign = { start: '0%', center: '50%', end: '100%' }[
|
293
|
-
placedAlign
|
294
|
-
] as string
|
295
|
-
|
296
|
-
const arrowXCenter = (middlewareData.arrow?.x ?? 0) + arrowWidth / 2
|
297
|
-
const arrowYCenter = (middlewareData.arrow?.y ?? 0) + arrowHeight / 2
|
298
|
-
|
299
|
-
let x = ''
|
300
|
-
let y = ''
|
301
|
-
|
302
|
-
if (placedSide === 'bottom') {
|
303
|
-
x = isArrowHidden ? noArrowAlign : `${arrowXCenter}px`
|
304
|
-
y = `${-arrowHeight}px`
|
305
|
-
} else if (placedSide === 'top') {
|
306
|
-
x = isArrowHidden ? noArrowAlign : `${arrowXCenter}px`
|
307
|
-
y = `${rects.floating.height + arrowHeight}px`
|
308
|
-
} else if (placedSide === 'right') {
|
309
|
-
x = `${-arrowHeight}px`
|
310
|
-
y = isArrowHidden ? noArrowAlign : `${arrowYCenter}px`
|
311
|
-
} else if (placedSide === 'left') {
|
312
|
-
x = `${rects.floating.width + arrowHeight}px`
|
313
|
-
y = isArrowHidden ? noArrowAlign : `${arrowYCenter}px`
|
314
|
-
}
|
315
|
-
return { data: { x, y } }
|
316
|
-
},
|
317
|
-
}
|
318
|
-
}
|
319
|
-
|
320
|
-
function getSideAndAlignFromPlacement(placement: Placement) {
|
321
|
-
const [side, align = 'center'] = placement.split('-')
|
322
|
-
return [side, align] as const
|
323
|
-
}
|
324
|
-
|
325
|
-
const focusTrigger = (triggerTarget: HTMLElement) => {
|
326
|
-
setTimeout(() => {
|
327
|
-
if (triggerTarget.dataset.asChild === 'false') {
|
328
|
-
const childElement = triggerTarget.firstElementChild as HTMLElement
|
329
|
-
|
330
|
-
if (childElement) {
|
331
|
-
childElement.focus()
|
332
|
-
}
|
333
|
-
} else {
|
334
|
-
triggerTarget.focus()
|
335
|
-
}
|
336
|
-
}, ON_CLOSE_FOCUS_DELAY)
|
337
|
-
}
|
338
|
-
|
339
|
-
const getFocusableElements = (container: HTMLElement) => {
|
340
|
-
return Array.from(
|
341
|
-
container.querySelectorAll(
|
342
|
-
'button, [href], input:not([type="hidden"]), select:not([tabindex="-1"]), textarea, [tabindex]:not([tabindex="-1"])',
|
343
|
-
),
|
344
|
-
) as HTMLElement[]
|
345
|
-
}
|
346
|
-
|
347
|
-
const getSameLevelItems = ({
|
348
|
-
content,
|
349
|
-
items,
|
350
|
-
closestContentSelector,
|
351
|
-
}: {
|
352
|
-
content: HTMLElement
|
353
|
-
items: HTMLElement[]
|
354
|
-
closestContentSelector: string
|
355
|
-
}) => {
|
356
|
-
let sameLevelItems = [] as HTMLElement[]
|
357
|
-
|
358
|
-
items.forEach((i) => {
|
359
|
-
if (
|
360
|
-
i.closest(closestContentSelector) === content &&
|
361
|
-
i.dataset.disabled === undefined
|
362
|
-
) {
|
363
|
-
sameLevelItems.push(i)
|
364
|
-
}
|
365
|
-
})
|
366
|
-
|
367
|
-
return sameLevelItems
|
368
|
-
}
|
369
|
-
|
370
|
-
const showContent = ({
|
371
|
-
trigger,
|
372
|
-
content,
|
373
|
-
contentContainer,
|
374
|
-
setEqualWidth,
|
375
|
-
}: {
|
376
|
-
trigger?: HTMLElement
|
377
|
-
content: HTMLElement
|
378
|
-
contentContainer: HTMLElement
|
379
|
-
setEqualWidth?: boolean
|
380
|
-
}) => {
|
381
|
-
contentContainer.classList.remove('hidden')
|
382
|
-
|
383
|
-
if (trigger) {
|
384
|
-
if (setEqualWidth) {
|
385
|
-
const triggerWidth = trigger.offsetWidth
|
386
|
-
const contentContainerWidth = contentContainer.offsetWidth
|
387
|
-
|
388
|
-
if (contentContainerWidth < triggerWidth) {
|
389
|
-
contentContainer.style.width = `${triggerWidth}px`
|
390
|
-
}
|
391
|
-
}
|
392
|
-
|
393
|
-
trigger.ariaExpanded = 'true'
|
394
|
-
trigger.dataset.state = 'open'
|
395
|
-
}
|
396
|
-
|
397
|
-
content.dataset.state = 'open'
|
398
|
-
}
|
399
|
-
|
400
|
-
const hideContent = ({
|
401
|
-
trigger,
|
402
|
-
content,
|
403
|
-
contentContainer,
|
404
|
-
}: {
|
405
|
-
trigger?: HTMLElement
|
406
|
-
content: HTMLElement
|
407
|
-
contentContainer: HTMLElement
|
408
|
-
}) => {
|
409
|
-
if (trigger) {
|
410
|
-
trigger.ariaExpanded = 'false'
|
411
|
-
trigger.dataset.state = 'closed'
|
412
|
-
}
|
413
|
-
|
414
|
-
content.dataset.state = 'closed'
|
415
|
-
|
416
|
-
setTimeout(() => {
|
417
|
-
contentContainer.classList.add('hidden')
|
418
|
-
}, ANIMATION_OUT_DELAY)
|
419
|
-
}
|
420
|
-
|
421
|
-
export {
|
422
|
-
ANIMATION_OUT_DELAY,
|
423
|
-
ON_CLOSE_FOCUS_DELAY,
|
424
|
-
ON_OPEN_FOCUS_DELAY,
|
425
|
-
showOverlay,
|
426
|
-
hideOverlay,
|
427
|
-
lockScroll,
|
428
|
-
unlockScroll,
|
429
|
-
openWithOverlay,
|
430
|
-
closeWithOverlay,
|
431
|
-
initFloatingUi,
|
432
|
-
focusTrigger,
|
433
|
-
getFocusableElements,
|
434
|
-
getSameLevelItems,
|
435
|
-
showContent,
|
436
|
-
hideContent,
|
437
|
-
}
|
File without changes
|
File without changes
|