@hadoku/task-ui-components 0.0.0-stage → 4.20.4
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.
- package/LICENSE +21 -0
- package/README.md +917 -2
- package/dist/app-header.css +288 -0
- package/dist/bento.css +143 -0
- package/dist/button.css +227 -0
- package/dist/components/AppHeader.d.ts +61 -0
- package/dist/components/AppHeader.d.ts.map +1 -0
- package/dist/components/AppHeader.js +23 -0
- package/dist/components/AppHeader.js.map +1 -0
- package/dist/components/BentoCard.d.ts +23 -0
- package/dist/components/BentoCard.d.ts.map +1 -0
- package/dist/components/BentoCard.js +49 -0
- package/dist/components/BentoCard.js.map +1 -0
- package/dist/components/BentoGrid.d.ts +24 -0
- package/dist/components/BentoGrid.d.ts.map +1 -0
- package/dist/components/BentoGrid.js +22 -0
- package/dist/components/BentoGrid.js.map +1 -0
- package/dist/components/Button.d.ts +28 -0
- package/dist/components/Button.d.ts.map +1 -0
- package/dist/components/Button.js +51 -0
- package/dist/components/Button.js.map +1 -0
- package/dist/components/ConnectedSettings.d.ts +53 -0
- package/dist/components/ConnectedSettings.d.ts.map +1 -0
- package/dist/components/ConnectedSettings.js +368 -0
- package/dist/components/ConnectedSettings.js.map +1 -0
- package/dist/components/ConnectedThemePicker.d.ts +52 -0
- package/dist/components/ConnectedThemePicker.d.ts.map +1 -0
- package/dist/components/ConnectedThemePicker.js +32 -0
- package/dist/components/ConnectedThemePicker.js.map +1 -0
- package/dist/components/ContextMenu.d.ts +22 -0
- package/dist/components/ContextMenu.d.ts.map +1 -0
- package/dist/components/ContextMenu.js +23 -0
- package/dist/components/ContextMenu.js.map +1 -0
- package/dist/components/HadokuThemePicker.d.ts +27 -0
- package/dist/components/HadokuThemePicker.d.ts.map +1 -0
- package/dist/components/HadokuThemePicker.js +19 -0
- package/dist/components/HadokuThemePicker.js.map +1 -0
- package/dist/components/LoadingSkeleton.d.ts +14 -0
- package/dist/components/LoadingSkeleton.d.ts.map +1 -0
- package/dist/components/LoadingSkeleton.js +62 -0
- package/dist/components/LoadingSkeleton.js.map +1 -0
- package/dist/components/Modal.d.ts +39 -0
- package/dist/components/Modal.d.ts.map +1 -0
- package/dist/components/Modal.js +106 -0
- package/dist/components/Modal.js.map +1 -0
- package/dist/components/NumberInput.d.ts +26 -0
- package/dist/components/NumberInput.d.ts.map +1 -0
- package/dist/components/NumberInput.js +131 -0
- package/dist/components/NumberInput.js.map +1 -0
- package/dist/components/Select.d.ts +52 -0
- package/dist/components/Select.d.ts.map +1 -0
- package/dist/components/Select.js +281 -0
- package/dist/components/Select.js.map +1 -0
- package/dist/components/SettingsModal.d.ts +69 -0
- package/dist/components/SettingsModal.d.ts.map +1 -0
- package/dist/components/SettingsModal.js +141 -0
- package/dist/components/SettingsModal.js.map +1 -0
- package/dist/components/ThemeIcons.d.ts +26 -0
- package/dist/components/ThemeIcons.d.ts.map +1 -0
- package/dist/components/ThemeIcons.js +74 -0
- package/dist/components/ThemeIcons.js.map +1 -0
- package/dist/components/ThemePicker.d.ts +28 -0
- package/dist/components/ThemePicker.d.ts.map +1 -0
- package/dist/components/ThemePicker.js +101 -0
- package/dist/components/ThemePicker.js.map +1 -0
- package/dist/components/Toast.d.ts +13 -0
- package/dist/components/Toast.d.ts.map +1 -0
- package/dist/components/Toast.js +70 -0
- package/dist/components/Toast.js.map +1 -0
- package/dist/components/Toaster.d.ts +19 -0
- package/dist/components/Toaster.d.ts.map +1 -0
- package/dist/components/Toaster.js +19 -0
- package/dist/components/Toaster.js.map +1 -0
- package/dist/components/Tooltip.d.ts +49 -0
- package/dist/components/Tooltip.d.ts.map +1 -0
- package/dist/components/Tooltip.js +143 -0
- package/dist/components/Tooltip.js.map +1 -0
- package/dist/context-menu.css +11 -0
- package/dist/controls.css +137 -0
- package/dist/hooks/useReducedMotion.d.ts +7 -0
- package/dist/hooks/useReducedMotion.d.ts.map +1 -0
- package/dist/hooks/useReducedMotion.js +25 -0
- package/dist/hooks/useReducedMotion.js.map +1 -0
- package/dist/hooks/useToast.d.ts +18 -0
- package/dist/hooks/useToast.d.ts.map +1 -0
- package/dist/hooks/useToast.js +28 -0
- package/dist/hooks/useToast.js.map +1 -0
- package/dist/icon-btn.css +64 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +75 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/menuPlacement.d.ts +16 -0
- package/dist/lib/menuPlacement.d.ts.map +1 -0
- package/dist/lib/menuPlacement.js +19 -0
- package/dist/lib/menuPlacement.js.map +1 -0
- package/dist/lib/selectMenus.d.ts +30 -0
- package/dist/lib/selectMenus.d.ts.map +1 -0
- package/dist/lib/selectMenus.js +236 -0
- package/dist/lib/selectMenus.js.map +1 -0
- package/dist/lib/settingsClient.d.ts +144 -0
- package/dist/lib/settingsClient.d.ts.map +1 -0
- package/dist/lib/settingsClient.js +157 -0
- package/dist/lib/settingsClient.js.map +1 -0
- package/dist/lib/themeScope.d.ts +19 -0
- package/dist/lib/themeScope.d.ts.map +1 -0
- package/dist/lib/themeScope.js +10 -0
- package/dist/lib/themeScope.js.map +1 -0
- package/dist/lib/titleTooltips.d.ts +38 -0
- package/dist/lib/titleTooltips.d.ts.map +1 -0
- package/dist/lib/titleTooltips.js +166 -0
- package/dist/lib/titleTooltips.js.map +1 -0
- package/dist/lib/tooltipPlacement.d.ts +14 -0
- package/dist/lib/tooltipPlacement.d.ts.map +1 -0
- package/dist/lib/tooltipPlacement.js +34 -0
- package/dist/lib/tooltipPlacement.js.map +1 -0
- package/dist/modal.css +129 -0
- package/dist/number-input.css +131 -0
- package/dist/scrollbar.css +165 -0
- package/dist/select.css +251 -0
- package/dist/styles.css +1866 -0
- package/dist/task-ui-components.css +1866 -0
- package/dist/theme-picker.css +219 -0
- package/dist/toaster.css +159 -0
- package/dist/tooltip.css +103 -0
- package/dist/types/index.d.ts +37 -0
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +60 -4
- package/src/app-header.css +288 -0
- package/src/bento.css +143 -0
- package/src/button.css +227 -0
- package/src/components/AppHeader.tsx +87 -0
- package/src/components/BentoCard.tsx +72 -0
- package/src/components/BentoGrid.tsx +48 -0
- package/src/components/Button.tsx +81 -0
- package/src/components/ConnectedSettings.tsx +545 -0
- package/src/components/ConnectedThemePicker.tsx +79 -0
- package/src/components/ContextMenu.tsx +48 -0
- package/src/components/HadokuThemePicker.tsx +39 -0
- package/src/components/LoadingSkeleton.tsx +84 -0
- package/src/components/Modal.tsx +171 -0
- package/src/components/NumberInput.tsx +161 -0
- package/src/components/Select.tsx +359 -0
- package/src/components/SettingsModal.tsx +235 -0
- package/src/components/ThemeIcons.tsx +127 -0
- package/src/components/ThemePicker.tsx +149 -0
- package/src/components/Toast.tsx +92 -0
- package/src/components/Toaster.tsx +43 -0
- package/src/components/Tooltip.tsx +212 -0
- package/src/context-menu.css +11 -0
- package/src/controls.css +137 -0
- package/src/hooks/useReducedMotion.ts +36 -0
- package/src/hooks/useToast.ts +57 -0
- package/src/icon-btn.css +64 -0
- package/src/index.ts +153 -0
- package/src/lib/menuPlacement.ts +31 -0
- package/src/lib/selectMenus.ts +303 -0
- package/src/lib/settingsClient.ts +425 -0
- package/src/lib/themeScope.ts +23 -0
- package/src/lib/titleTooltips.ts +236 -0
- package/src/lib/tooltipPlacement.ts +59 -0
- package/src/modal.css +129 -0
- package/src/number-input.css +131 -0
- package/src/scrollbar.css +165 -0
- package/src/select.css +251 -0
- package/src/theme-picker.css +219 -0
- package/src/toaster.css +159 -0
- package/src/tooltip.css +103 -0
- package/src/types/index.ts +43 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The themed list for every native <select> on a page, with no change to the
|
|
3
|
+
* markup.
|
|
4
|
+
*
|
|
5
|
+
* import { installSelectMenus } from '@wolffm/task-ui-components/select-menus'
|
|
6
|
+
* import '@wolffm/task-ui-components/select.css'
|
|
7
|
+
* installSelectMenus()
|
|
8
|
+
*
|
|
9
|
+
* hadoku_site calls this once from its shell, which covers every
|
|
10
|
+
* micro-frontend; an app does not call it itself. Framework-free and
|
|
11
|
+
* React-free, like installTitleTooltips, for the same reason.
|
|
12
|
+
*
|
|
13
|
+
* A native select's closed box takes CSS, but its open list is drawn by the
|
|
14
|
+
* OS and takes almost none. So a mouse press or an opening key on the select
|
|
15
|
+
* is intercepted (preventDefault on mousedown is what stops the OS list) and
|
|
16
|
+
* the same list <Select> renders opens instead, built from the select's own
|
|
17
|
+
* <option>s and <optgroup>s at that moment.
|
|
18
|
+
*
|
|
19
|
+
* The <select> stays the source of truth. Choosing sets its selectedIndex and
|
|
20
|
+
* fires `input` and `change` exactly as the OS list would, so every framework
|
|
21
|
+
* (React's onChange included) and every plain listener sees an ordinary
|
|
22
|
+
* change, and a form still submits the select's value.
|
|
23
|
+
*
|
|
24
|
+
* Left native on purpose:
|
|
25
|
+
* - touch: a phone's own picker is the better control there
|
|
26
|
+
* - `multiple` or `size > 1`, which render as an inline list, not a popup
|
|
27
|
+
* - anything under `data-native-select`
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { themeScopeOf } from './themeScope'
|
|
31
|
+
import { placeMenu } from './menuPlacement'
|
|
32
|
+
|
|
33
|
+
const TYPEAHEAD_RESET_MS = 600
|
|
34
|
+
const LIST_ID = 'hdk-select-menu'
|
|
35
|
+
// `check` from the @wolffm/themes registry (Lucide circle-check), inlined so
|
|
36
|
+
// this entry does not pull the whole registry into the shell.
|
|
37
|
+
const CHECK_SVG =
|
|
38
|
+
'<svg class="hdk-icon hdk-select__check" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10" /><path d="m9 12 2 2 4-4" /></svg>'
|
|
39
|
+
|
|
40
|
+
const OPEN_KEYS = new Set(['ArrowDown', 'ArrowUp', ' ', 'F4'])
|
|
41
|
+
|
|
42
|
+
let uninstallCurrent: (() => void) | null = null
|
|
43
|
+
|
|
44
|
+
function eligible(el: Element | null): el is HTMLSelectElement {
|
|
45
|
+
return (
|
|
46
|
+
el instanceof HTMLSelectElement &&
|
|
47
|
+
!el.multiple &&
|
|
48
|
+
el.size <= 1 &&
|
|
49
|
+
!el.disabled &&
|
|
50
|
+
!el.closest('[data-native-select]')
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function installSelectMenus(): () => void {
|
|
55
|
+
if (uninstallCurrent) return uninstallCurrent
|
|
56
|
+
|
|
57
|
+
let lastPointer = 'mouse'
|
|
58
|
+
let select: HTMLSelectElement | null = null
|
|
59
|
+
let list: HTMLUListElement | null = null
|
|
60
|
+
let options: HTMLOptionElement[] = []
|
|
61
|
+
let items: HTMLLIElement[] = []
|
|
62
|
+
let active = -1
|
|
63
|
+
const typeahead = { text: '', at: 0 }
|
|
64
|
+
|
|
65
|
+
function setActive(i: number) {
|
|
66
|
+
if (items[active]) items[active].classList.remove('hdk-select__option--active')
|
|
67
|
+
active = i
|
|
68
|
+
const item = items[i]
|
|
69
|
+
if (!item || !list) return
|
|
70
|
+
item.classList.add('hdk-select__option--active')
|
|
71
|
+
list.setAttribute('aria-activedescendant', item.id)
|
|
72
|
+
item.scrollIntoView({ block: 'nearest' })
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function step(from: number, dir: 1 | -1) {
|
|
76
|
+
for (let i = 1; i <= options.length; i++) {
|
|
77
|
+
const next = (from + dir * i + options.length) % options.length
|
|
78
|
+
if (!isOff(next)) return next
|
|
79
|
+
}
|
|
80
|
+
return from
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const edge = (dir: 1 | -1) => step(dir === 1 ? -1 : options.length, dir)
|
|
84
|
+
|
|
85
|
+
function matchTypeahead(key: string) {
|
|
86
|
+
const now = Date.now()
|
|
87
|
+
typeahead.text = now - typeahead.at > TYPEAHEAD_RESET_MS ? key : typeahead.text + key
|
|
88
|
+
typeahead.at = now
|
|
89
|
+
const needle = typeahead.text.toLowerCase()
|
|
90
|
+
const start = Math.max(active, 0)
|
|
91
|
+
for (let i = 0; i < options.length; i++) {
|
|
92
|
+
// A single repeated key cycles; a longer prefix stays on the current match.
|
|
93
|
+
const idx = (start + (typeahead.text.length === 1 ? i + 1 : i)) % options.length
|
|
94
|
+
const o = options[idx]
|
|
95
|
+
if (!isOff(idx) && labelOf(o).toLowerCase().startsWith(needle)) return idx
|
|
96
|
+
}
|
|
97
|
+
return -1
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const labelOf = (o: HTMLOptionElement) => o.label || o.text
|
|
101
|
+
// A disabled <optgroup> disables its options too.
|
|
102
|
+
const isOff = (i: number) =>
|
|
103
|
+
options[i].disabled ||
|
|
104
|
+
(options[i].parentElement as HTMLOptGroupElement | null)?.disabled === true
|
|
105
|
+
|
|
106
|
+
function place() {
|
|
107
|
+
if (!select || !list) return
|
|
108
|
+
if (!select.isConnected) {
|
|
109
|
+
close(false)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
const p = placeMenu(select.getBoundingClientRect(), list.scrollHeight)
|
|
113
|
+
list.style.left = `${p.left}px`
|
|
114
|
+
list.style.minWidth = `${p.width}px`
|
|
115
|
+
list.style.top = p.top === undefined ? '' : `${p.top}px`
|
|
116
|
+
list.style.bottom = p.bottom === undefined ? '' : `${p.bottom}px`
|
|
117
|
+
list.style.maxHeight = `${p.maxHeight}px`
|
|
118
|
+
list.style.visibility = ''
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function open(el: HTMLSelectElement, start?: 'last') {
|
|
122
|
+
close(false)
|
|
123
|
+
const scope = themeScopeOf(el)
|
|
124
|
+
if (!scope) return
|
|
125
|
+
select = el
|
|
126
|
+
options = Array.from(el.options).filter(o => !o.hidden)
|
|
127
|
+
if (options.length === 0) return
|
|
128
|
+
|
|
129
|
+
list = document.createElement('ul')
|
|
130
|
+
list.id = LIST_ID
|
|
131
|
+
list.setAttribute('role', 'listbox')
|
|
132
|
+
list.tabIndex = -1
|
|
133
|
+
list.className = 'hdk-select__menu hdk-scroll hdk-scroll--thin'
|
|
134
|
+
const name = el.getAttribute('aria-label') ?? el.labels?.[0]?.textContent?.trim()
|
|
135
|
+
if (name) list.setAttribute('aria-label', name)
|
|
136
|
+
// The list is portalled, so it cannot inherit the select's type size.
|
|
137
|
+
const cs = getComputedStyle(el)
|
|
138
|
+
list.style.fontSize = cs.fontSize
|
|
139
|
+
list.style.fontFamily = cs.fontFamily
|
|
140
|
+
list.style.visibility = 'hidden'
|
|
141
|
+
|
|
142
|
+
let group: HTMLOptGroupElement | null = null
|
|
143
|
+
items = options.map((o, i) => {
|
|
144
|
+
const g = o.parentElement instanceof HTMLOptGroupElement ? o.parentElement : null
|
|
145
|
+
if (g && g !== group) {
|
|
146
|
+
const head = document.createElement('li')
|
|
147
|
+
head.setAttribute('role', 'presentation')
|
|
148
|
+
head.className = 'hdk-select__group'
|
|
149
|
+
head.textContent = g.label
|
|
150
|
+
list?.append(head)
|
|
151
|
+
}
|
|
152
|
+
group = g
|
|
153
|
+
const item = document.createElement('li')
|
|
154
|
+
item.id = `${LIST_ID}-${i}`
|
|
155
|
+
item.setAttribute('role', 'option')
|
|
156
|
+
const selected = o.index === el.selectedIndex
|
|
157
|
+
item.setAttribute('aria-selected', String(selected))
|
|
158
|
+
if (o.disabled || g?.disabled) item.setAttribute('aria-disabled', 'true')
|
|
159
|
+
item.className = `hdk-select__option${selected ? ' hdk-select__option--selected' : ''}`
|
|
160
|
+
const text = document.createElement('span')
|
|
161
|
+
text.className = 'hdk-select__option-label'
|
|
162
|
+
text.textContent = labelOf(o)
|
|
163
|
+
const wrap = document.createElement('span')
|
|
164
|
+
wrap.className = 'hdk-select__option-text'
|
|
165
|
+
wrap.append(text)
|
|
166
|
+
item.append(wrap)
|
|
167
|
+
if (selected) item.insertAdjacentHTML('beforeend', CHECK_SVG)
|
|
168
|
+
item.addEventListener('pointermove', () => {
|
|
169
|
+
if (!isOff(i) && i !== active) setActive(i)
|
|
170
|
+
})
|
|
171
|
+
item.addEventListener('click', () => choose(i))
|
|
172
|
+
list?.append(item)
|
|
173
|
+
return item
|
|
174
|
+
})
|
|
175
|
+
list.addEventListener('keydown', onListKeyDown)
|
|
176
|
+
scope.appendChild(list)
|
|
177
|
+
el.setAttribute('aria-expanded', 'true')
|
|
178
|
+
el.setAttribute('aria-controls', LIST_ID)
|
|
179
|
+
|
|
180
|
+
const current = options.findIndex(o => o.index === el.selectedIndex)
|
|
181
|
+
setActive(start === 'last' ? edge(-1) : current >= 0 && !isOff(current) ? current : edge(1))
|
|
182
|
+
place()
|
|
183
|
+
list.focus({ preventScroll: true })
|
|
184
|
+
window.addEventListener('scroll', onScroll, true)
|
|
185
|
+
window.addEventListener('resize', place)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function close(refocus: boolean) {
|
|
189
|
+
window.removeEventListener('scroll', onScroll, true)
|
|
190
|
+
window.removeEventListener('resize', place)
|
|
191
|
+
list?.remove()
|
|
192
|
+
list = null
|
|
193
|
+
if (!select) return
|
|
194
|
+
select.removeAttribute('aria-expanded')
|
|
195
|
+
select.removeAttribute('aria-controls')
|
|
196
|
+
if (refocus) select.focus({ preventScroll: true })
|
|
197
|
+
select = null
|
|
198
|
+
items = []
|
|
199
|
+
options = []
|
|
200
|
+
active = -1
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function choose(i: number) {
|
|
204
|
+
const el = select
|
|
205
|
+
const o = options[i]
|
|
206
|
+
if (!el || !o || isOff(i)) return
|
|
207
|
+
const changed = o.index !== el.selectedIndex
|
|
208
|
+
close(true)
|
|
209
|
+
if (!changed) return
|
|
210
|
+
el.selectedIndex = o.index
|
|
211
|
+
el.dispatchEvent(new Event('input', { bubbles: true }))
|
|
212
|
+
el.dispatchEvent(new Event('change', { bubbles: true }))
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Scrolling the list itself must not re-place (or close) it.
|
|
216
|
+
const onScroll = (e: Event) => {
|
|
217
|
+
if (list && e.target instanceof Node && list.contains(e.target)) return
|
|
218
|
+
place()
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function onListKeyDown(e: KeyboardEvent) {
|
|
222
|
+
switch (e.key) {
|
|
223
|
+
case 'ArrowDown':
|
|
224
|
+
e.preventDefault()
|
|
225
|
+
setActive(step(active, 1))
|
|
226
|
+
break
|
|
227
|
+
case 'ArrowUp':
|
|
228
|
+
e.preventDefault()
|
|
229
|
+
setActive(step(active, -1))
|
|
230
|
+
break
|
|
231
|
+
case 'Home':
|
|
232
|
+
case 'PageUp':
|
|
233
|
+
e.preventDefault()
|
|
234
|
+
setActive(edge(1))
|
|
235
|
+
break
|
|
236
|
+
case 'End':
|
|
237
|
+
case 'PageDown':
|
|
238
|
+
e.preventDefault()
|
|
239
|
+
setActive(edge(-1))
|
|
240
|
+
break
|
|
241
|
+
case 'Enter':
|
|
242
|
+
case ' ':
|
|
243
|
+
e.preventDefault()
|
|
244
|
+
choose(active)
|
|
245
|
+
break
|
|
246
|
+
case 'Escape':
|
|
247
|
+
// Stop here so an enclosing modal does not close along with the list.
|
|
248
|
+
e.preventDefault()
|
|
249
|
+
e.stopPropagation()
|
|
250
|
+
close(true)
|
|
251
|
+
break
|
|
252
|
+
case 'Tab':
|
|
253
|
+
close(true)
|
|
254
|
+
break
|
|
255
|
+
default:
|
|
256
|
+
if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
257
|
+
const idx = matchTypeahead(e.key)
|
|
258
|
+
if (idx >= 0) setActive(idx)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const onPointerDown = (e: PointerEvent) => {
|
|
264
|
+
lastPointer = e.pointerType
|
|
265
|
+
const target = e.target as Node
|
|
266
|
+
if (list && !list.contains(target) && target !== select) close(false)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const onMouseDown = (e: MouseEvent) => {
|
|
270
|
+
if (e.button !== 0 || lastPointer === 'touch') return
|
|
271
|
+
const el = (e.target as Element | null)?.closest?.('select') ?? null
|
|
272
|
+
if (!eligible(el)) return
|
|
273
|
+
// This is what keeps the OS list from opening.
|
|
274
|
+
e.preventDefault()
|
|
275
|
+
if (select === el) {
|
|
276
|
+
close(true)
|
|
277
|
+
return
|
|
278
|
+
}
|
|
279
|
+
el.focus({ preventScroll: true })
|
|
280
|
+
open(el)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
284
|
+
const el = e.target as Element | null
|
|
285
|
+
if (!eligible(el) || select === el) return
|
|
286
|
+
if (!OPEN_KEYS.has(e.key) || e.ctrlKey || e.metaKey) return
|
|
287
|
+
e.preventDefault()
|
|
288
|
+
open(el, e.key === 'ArrowUp' && !e.altKey ? 'last' : undefined)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
document.addEventListener('pointerdown', onPointerDown, true)
|
|
292
|
+
document.addEventListener('mousedown', onMouseDown, true)
|
|
293
|
+
document.addEventListener('keydown', onKeyDown, true)
|
|
294
|
+
|
|
295
|
+
uninstallCurrent = () => {
|
|
296
|
+
close(false)
|
|
297
|
+
document.removeEventListener('pointerdown', onPointerDown, true)
|
|
298
|
+
document.removeEventListener('mousedown', onMouseDown, true)
|
|
299
|
+
document.removeEventListener('keydown', onKeyDown, true)
|
|
300
|
+
uninstallCurrent = null
|
|
301
|
+
}
|
|
302
|
+
return uninstallCurrent
|
|
303
|
+
}
|