|
28 | 28 | :class="{ dp__flex_row: items.length >= 3 }" |
29 | 29 | > |
30 | 30 | <div |
31 | | - v-for="(col, ind) in row" |
| 31 | + v-for="col in row" |
32 | 32 | :key="col.value" |
33 | | - :ref="(el) => assignRef(el, col, i, ind)" |
34 | 33 | role="gridcell" |
35 | 34 | :class="cellClassName" |
36 | 35 | :aria-selected="col.active || undefined" |
37 | 36 | :aria-disabled="col.disabled || undefined" |
| 37 | + :data-dp-action-element="level ?? 1" |
| 38 | + :data-dp-element-active="col.active ? (level ?? 1) : undefined" |
38 | 39 | tabindex="0" |
39 | 40 | :data-test-id="col.text" |
40 | 41 | @click.prevent="onClick(col)" |
|
57 | 58 | :aria-label="ariaLabels?.toggleOverlay" |
58 | 59 | :class="actionButtonClass" |
59 | 60 | tabindex="0" |
| 61 | + :data-dp-action-element="level ?? 1" |
60 | 62 | @click="toggle" |
61 | 63 | @keydown="onBtnKeyDown" |
62 | 64 | > |
|
69 | 71 | import { computed, nextTick, onBeforeUpdate, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue'; |
70 | 72 | import { unrefElement } from '@vueuse/core'; |
71 | 73 |
|
72 | | - import { useArrowNavigation, useContext, useHelperFns } from '@/composables'; |
| 74 | + import { useContext, useHelperFns } from '@/composables'; |
73 | 75 | import { useNavigationDisplay } from '@/components/shared/useNavigationDisplay.ts'; |
74 | 76 |
|
75 | 77 | import { EventKey } from '@/constants'; |
76 | 78 | import type { DynamicClass, OverlayGridItem, PickerSection } from '@/types'; |
77 | 79 |
|
78 | | - const { setSelectionGrid, buildMultiLevelMatrix, setMonthPicker } = useArrowNavigation(); |
79 | | -
|
80 | 80 | const emit = defineEmits<{ |
81 | 81 | selected: [value: number]; |
82 | 82 | toggle: []; |
|
87 | 87 | const props = defineProps<{ |
88 | 88 | items: OverlayGridItem[][]; |
89 | 89 | type: PickerSection; |
90 | | - isLast: boolean; |
91 | | - skipButtonRef?: boolean; |
92 | | - headerRefs?: (HTMLElement | null)[]; |
93 | 90 | useRelative?: boolean; |
94 | 91 | height?: number | string; |
95 | | - noOverlayFocus?: boolean; |
96 | | - focusValue?: number; |
97 | | - menuWrapRef?: HTMLElement | null; |
98 | 92 | overlayLabel?: string; |
| 93 | + isLast: boolean; |
| 94 | + level?: 0 | 1 | 2; |
99 | 95 | }>(); |
100 | 96 |
|
101 | 97 | const { |
102 | | - rootProps, |
103 | | - defaults: { ariaLabels, textInput, config }, |
| 98 | + setState, |
| 99 | + defaults: { ariaLabels, config }, |
104 | 100 | } = useContext(); |
105 | 101 | const { hideNavigationButtons } = useNavigationDisplay(); |
106 | | - const { handleEventPropagation, convertType, checkKeyDown, checkStopPropagation, getElWithin, findFocusableEl } = |
107 | | - useHelperFns(); |
| 102 | + const { handleEventPropagation, checkKeyDown } = useHelperFns(); |
108 | 103 |
|
109 | 104 | const toggleButton = useTemplateRef('toggle-button'); |
110 | 105 | const containerRef = useTemplateRef('overlay-container'); |
111 | 106 | const gridWrapRef = useTemplateRef('grid-wrap'); |
112 | 107 |
|
113 | 108 | const scrollable = ref(false); |
114 | 109 | const selectionActiveRef = ref<HTMLElement | null>(null); |
115 | | - const elementRefs = ref<Array<HTMLElement | null>[]>([]); |
116 | 110 | const hoverValue = ref(); |
117 | 111 | const containerHeight = ref(0); |
118 | 112 |
|
119 | 113 | onBeforeUpdate(() => { |
120 | 114 | selectionActiveRef.value = null; |
121 | 115 | }); |
122 | 116 |
|
123 | | - /** |
124 | | - * On mounted hook, set the scroll position, if any to a selected value when opening overlay |
125 | | - */ |
126 | | - onMounted(() => { |
127 | | - nextTick().then(() => setContainerHeightAndScroll()); |
128 | | - if (!props.noOverlayFocus) { |
129 | | - focusGrid(); |
130 | | - } |
131 | | - handleArrowNav(true); |
| 117 | + onMounted(async () => { |
| 118 | + await nextTick(); |
| 119 | + setContainerHeightAndScroll(); |
| 120 | + setState('arrowNavigationLevel', props.level ?? 1); |
132 | 121 | }); |
133 | 122 |
|
134 | | - onUnmounted(() => handleArrowNav(false)); |
135 | | -
|
136 | | - const handleArrowNav = (value: boolean): void => { |
137 | | - if (rootProps.arrowNavigation) { |
138 | | - if (props.headerRefs?.length) { |
139 | | - setMonthPicker(value); |
140 | | - } else { |
141 | | - setSelectionGrid(value); |
142 | | - } |
143 | | - } |
144 | | - }; |
145 | | -
|
146 | | - const focusGrid = (): void => { |
147 | | - const elm = unrefElement(gridWrapRef); |
148 | | - if (elm) { |
149 | | - if (!textInput.value.enabled) { |
150 | | - if (selectionActiveRef.value) { |
151 | | - selectionActiveRef.value?.focus({ preventScroll: true }); |
152 | | - } else { |
153 | | - elm.focus({ preventScroll: true }); |
154 | | - } |
155 | | - } |
156 | | -
|
157 | | - scrollable.value = elm.clientHeight < elm.scrollHeight; |
158 | | - } |
159 | | - }; |
| 123 | + onUnmounted(() => { |
| 124 | + setState('arrowNavigationLevel', (props.level ?? 1) - 1); |
| 125 | + }); |
160 | 126 |
|
161 | 127 | // Dynamic class for the overlay |
162 | 128 | const dpOverlayClass = computed( |
|
202 | 168 |
|
203 | 169 | const setContainerHeightAndScroll = (setScroll = true) => { |
204 | 170 | nextTick().then(() => { |
205 | | - const el = unrefElement(selectionActiveRef); |
| 171 | + const el = document.querySelector<HTMLElement>(`[data-dp-element-active="${props.level ?? 1}"]`); |
206 | 172 | const parent = unrefElement(gridWrapRef); |
207 | 173 | const btn = unrefElement(toggleButton); |
208 | 174 | const container = unrefElement(containerRef); |
|
245 | 211 | } |
246 | 212 | }; |
247 | 213 |
|
248 | | - const assignRef = (el: any, col: OverlayGridItem, rowInd: number, colInd: number): void => { |
249 | | - if (el) { |
250 | | - if (col.active || col.value === props.focusValue) { |
251 | | - selectionActiveRef.value = el; |
252 | | - } |
253 | | - if (rootProps.arrowNavigation) { |
254 | | - if (Array.isArray(elementRefs.value[rowInd])) { |
255 | | - elementRefs.value[rowInd][colInd] = el; |
256 | | - } else { |
257 | | - elementRefs.value[rowInd] = [el]; |
258 | | - } |
259 | | - buildMatrix(); |
260 | | - } |
261 | | - } |
262 | | - }; |
263 | | -
|
264 | | - const buildMatrix = () => { |
265 | | - const refs = props.headerRefs?.length |
266 | | - ? [props.headerRefs].concat(elementRefs.value) |
267 | | - : elementRefs.value.concat([props.skipButtonRef ? [] : [toggleButton.value]]); |
268 | | -
|
269 | | - buildMultiLevelMatrix(convertType(refs), props.headerRefs?.length ? 'monthPicker' : 'selectionGrid'); |
270 | | - }; |
271 | | -
|
272 | | - const handleArrowKey = (ev: KeyboardEvent) => { |
273 | | - if (rootProps.arrowNavigation) return; |
274 | | - checkStopPropagation(ev, config.value, true); |
275 | | - }; |
276 | | -
|
277 | 214 | const setHoverValue = (val: number) => { |
278 | 215 | hoverValue.value = val; |
279 | 216 | emit('hover-value', val); |
280 | 217 | }; |
281 | 218 |
|
282 | | - const onTab = () => { |
283 | | - toggle(); |
284 | | - if (!props.isLast) { |
285 | | - const actionRow = getElWithin(props.menuWrapRef ?? null, 'action-row'); |
286 | | - if (actionRow) { |
287 | | - const focusable = findFocusableEl(actionRow); |
288 | | - focusable?.focus(); |
289 | | - } |
290 | | - } |
291 | | - }; |
292 | | -
|
293 | 219 | const onKeyDown = (ev: KeyboardEvent) => { |
294 | 220 | switch (ev.key) { |
295 | 221 | case EventKey.esc: |
296 | 222 | return handleEsc(ev); |
297 | | - case EventKey.arrowLeft: |
298 | | - return handleArrowKey(ev); |
299 | | - case EventKey.arrowRight: |
300 | | - return handleArrowKey(ev); |
301 | | - case EventKey.arrowUp: |
302 | | - return handleArrowKey(ev); |
303 | | - case EventKey.arrowDown: |
304 | | - return handleArrowKey(ev); |
305 | 223 | default: |
306 | 224 | return; |
307 | 225 | } |
308 | 226 | }; |
309 | 227 |
|
310 | 228 | const onBtnKeyDown = (ev: KeyboardEvent) => { |
311 | 229 | if (ev.key === EventKey.enter) return toggle(); |
312 | | - if (ev.key === EventKey.tab) return onTab(); |
313 | 230 | }; |
314 | | -
|
315 | | - defineExpose({ focusGrid }); |
316 | 231 | </script> |
0 commit comments