diff --git a/app/src/plugins/tm@_components/z-datepicker.vue b/app/src/plugins/tm@_components/z-datepicker.vue index c2e1573..039efd1 100644 --- a/app/src/plugins/tm@_components/z-datepicker.vue +++ b/app/src/plugins/tm@_components/z-datepicker.vue @@ -61,9 +61,8 @@ export default { mounted() { // Attach focus listeners on mount to catch any focus events - this.$nextTick(() => { - this.attachFocusListeners(); - }); + // Use immediate attachment (no $nextTick) to catch early focus events + this.attachFocusListeners(); }, methods: { @@ -72,47 +71,36 @@ export default { }, handleOpen(event) { - console.log( - '🔵 [z-datepicker] handleOpen called at:', - new Date().toISOString(), - ); this.isFocused = true; - // IMPORTANT: Set flag and save position BEFORE any other action - this.preventInputFocus = true; - console.log( - '🔵 [z-datepicker] preventInputFocus set to:', - this.preventInputFocus, - ); - - // Save scroll position - const modalContent = document.querySelector('.v-dialog .modal-content'); - if (modalContent) { - this.savedScrollPosition = modalContent.scrollTop; - console.log( - '🔵 [z-datepicker] Saved scroll position:', - this.savedScrollPosition, - ); - } else { - console.log('🔵 [z-datepicker] Modal content not found'); + // IMPORTANT: If scroll position wasn't saved yet (handleClick wasn't called), + // save it now but it might be too late and already scrolled to 0 + if (this.savedScrollPosition === null) { + this.preventInputFocus = true; + const modalContent = document.querySelector('.v-dialog .modal-content'); + if (modalContent) { + this.savedScrollPosition = modalContent.scrollTop; + } } + // CRITICAL: Blur any currently focused input (v-dialog autofocus issue) + this.blurFocusedInputs(); + // Reattach focus event listeners (for dynamically added inputs) this.attachFocusListeners(); + // Position the calendar menu relative to the modal + this.positionCalendar(); + // Reset after datepicker is opened setTimeout(() => { this.preventInputFocus = false; this.savedScrollPosition = null; - console.log( - '🔵 [z-datepicker] Reset preventInputFocus and savedScrollPosition after 500ms', - ); }, 500); // Emit to parent (for backward compatibility) const wrapperElement = event?.target?.closest?.('.z-datepicker-wrapper') || true; - console.log('🔵 [z-datepicker] Emitting open event to parent'); this.$emit('open', wrapperElement); }, @@ -122,7 +110,6 @@ export default { }, handleClick(event) { - console.log('🔵 [z-datepicker] Wrapper clicked'); // Prevent any default behavior event.preventDefault(); event.stopPropagation(); @@ -130,16 +117,16 @@ export default { // CRITICAL: Set prevention flag BEFORE opening menu to catch early focus events this.preventInputFocus = true; - // Save scroll position immediately + // Save scroll position FIRST (before blur or opening menu) const modalContent = document.querySelector('.v-dialog .modal-content'); if (modalContent) { this.savedScrollPosition = modalContent.scrollTop; - console.log( - '🔵 [z-datepicker] Pre-saved scroll position in handleClick:', - this.savedScrollPosition, - ); } + // CRITICAL: Blur any currently focused input + this.blurFocusedInputs(false); // Pass false to not restore scroll yet + + // Open the menu if (this.$refs.datepickerRef?.openMenu) { this.$refs.datepickerRef.openMenu(); } @@ -151,106 +138,114 @@ export default { } }, + positionCalendar() { + setTimeout(() => { + const calendar = document.querySelector( + '.dp__menu.dp__menu_index.dp__theme_light', + ); + + if (!calendar) return; + + // Get the datepicker wrapper element + const datepickerWrapper = this.$el; + + // Get the modal container + const modalContainer = document.querySelector('.v-overlay__content'); + + if (!datepickerWrapper || !modalContainer) return; + + // Get bounding rectangles + const wrapperRect = datepickerWrapper.getBoundingClientRect(); + const modalRect = modalContainer.getBoundingClientRect(); + + // Calculate position relative to modal + const topPosition = + wrapperRect.top - modalRect.top + wrapperRect.height; + const leftPosition = wrapperRect.left - modalRect.left; + + // Apply positioning + calendar.style.top = `${topPosition + 210}px`; + }, 100); + }, + + blurFocusedInputs(restoreScroll = true) { + const activeElement = document.activeElement; + + if (activeElement && activeElement.tagName === 'INPUT') { + // Don't blur the datepicker's own input + if ( + activeElement.closest('.dp__input_wrap') || + activeElement.closest('.z-datepicker-wrapper') + ) { + return; + } + + activeElement.blur(); + + // Restore scroll position after blur (only if requested) + if (restoreScroll && this.savedScrollPosition !== null) { + const modalContent = document.querySelector( + '.v-dialog .modal-content', + ); + if (modalContent) { + modalContent.scrollTop = this.savedScrollPosition; + } + } + } + }, + attachFocusListeners() { - console.log('🔵 [z-datepicker] attachFocusListeners called'); // Remove old listeners first this.removeFocusListeners(); - // Attach new listeners to all autocomplete inputs AND text fields - const selectors = [ - '.v-autocomplete input', - '.v-text-field input', - 'input[type="text"]', - ]; + // Attach new listeners to autocomplete inputs only (like the mixin) + // Trying to catch all inputs causes issues with event timing + const inputs = document.querySelectorAll('.v-autocomplete input'); let totalListeners = 0; - selectors.forEach((selector) => { - const inputs = document.querySelectorAll(selector); - let selectorCount = 0; + inputs.forEach((input) => { + // Skip the datepicker's own input + if (input.closest('.dp__input_wrap')) { + return; + } - inputs.forEach((input) => { - // Skip the datepicker's own input - if (input.closest('.dp__input_wrap')) { - return; - } - - const listener = this.onInputFocus.bind(this); - this.focusListeners.set(input, listener); - input.addEventListener('focus', listener, true); // Use capture phase - selectorCount++; - totalListeners++; - }); - - console.log( - `🔵 [z-datepicker] Found ${selectorCount} inputs for selector: ${selector}`, - ); + const listener = this.onInputFocus.bind(this); + this.focusListeners.set(input, listener); + input.addEventListener('focus', listener); + totalListeners++; }); - - console.log( - `🔵 [z-datepicker] Total focus listeners attached: ${totalListeners}`, - ); }, onInputFocus(event) { const target = event.target; - console.log('🔵 [z-datepicker] onInputFocus triggered on:', { - tagName: target.tagName, - type: target.type, - id: target.id, - className: target.className, - preventInputFocus: this.preventInputFocus, - savedScrollPosition: this.savedScrollPosition, - }); // Skip if it's the datepicker's own input if ( target.closest('.dp__input_wrap') || target.closest('.z-datepicker-wrapper') ) { - console.log( - "🔵 [z-datepicker] Skipping - this is datepicker's own input", - ); return; } - // If prevention flag is set, block the focus immediately - if (this.preventInputFocus) { - console.log('🔵 [z-datepicker] PREVENTING FOCUS on:', target); + // If prevention flag is set, block the focus + if (this.preventInputFocus && this.savedScrollPosition !== null) { const scrollBefore = document.querySelector( '.v-dialog .modal-content', )?.scrollTop; - console.log('🔵 [z-datepicker] Scroll position before:', scrollBefore); - // Prevent the focus - stop all propagation - event.preventDefault(); - event.stopPropagation(); - event.stopImmediatePropagation(); + // Blur the focused element with a slight delay (like the mixin does) + setTimeout(() => { + target.blur(); - // Blur the focused element immediately (no timeout) - target.blur(); - - // Restore scroll position if we have one saved - if (this.savedScrollPosition !== null) { + // Restore scroll position after blur const modalContent = document.querySelector( '.v-dialog .modal-content', ); - if (modalContent) { + if (modalContent && this.savedScrollPosition !== null) { modalContent.scrollTop = this.savedScrollPosition; const scrollAfter = modalContent.scrollTop; - console.log( - '🔵 [z-datepicker] Scroll position restored to:', - scrollAfter, - ); } - } - } else { - console.log( - '🔵 [z-datepicker] Focus allowed (preventInputFocus:', - this.preventInputFocus, - ', savedScrollPosition:', - this.savedScrollPosition, - ')', - ); + }, 0); } }, @@ -319,6 +314,11 @@ export default { :deep(.dp__input_wrap) pointer-events: none + cursor: pointer + + :deep(.dp__input) + cursor: pointer + pointer-events: none !important :deep(.dp__clear_icon) pointer-events: auto !important diff --git a/app/src/plugins/tm@modal/_mixins/vuetify-fix-focus.ts b/app/src/plugins/tm@modal/_mixins/vuetify-fix-focus.ts index c48f2d3..8d46da1 100644 --- a/app/src/plugins/tm@modal/_mixins/vuetify-fix-focus.ts +++ b/app/src/plugins/tm@modal/_mixins/vuetify-fix-focus.ts @@ -10,19 +10,9 @@ export default { methods: { setFocusEvent() { const inputs = document.querySelectorAll('.v-autocomplete input'); - console.log( - '🟢 [VuetifyFixFocusMixin] setFocusEvent - Found', - inputs.length, - 'autocomplete inputs', - ); inputs.forEach((input) => { input.addEventListener('focus', this.onInputFocus); }); - console.log( - '🟢 [VuetifyFixFocusMixin] Attached focus listeners to', - inputs.length, - 'inputs', - ); }, onDateFocus($event) { @@ -32,53 +22,26 @@ export default { }, onDateOpen($event) { - console.log( - '🟢 [VuetifyFixFocusMixin] onDateOpen called at:', - new Date().toISOString(), - 'with event:', - $event, - ); // $event should be the element reference (or any truthy value to trigger saving) if ($event) { // Find the scrollable modal container const modalContent = document.querySelector('.v-dialog .modal-content'); if (modalContent) { this.savedScrollPosition = modalContent.scrollTop; - console.log( - '🟢 [VuetifyFixFocusMixin] Saved scroll position:', - this.savedScrollPosition, - ); - } else { - console.log('🟢 [VuetifyFixFocusMixin] Modal content not found'); } this.preventInputFocus = true; - console.log( - '🟢 [VuetifyFixFocusMixin] preventInputFocus set to:', - this.preventInputFocus, - ); // Reset after datepicker is opened setTimeout(() => { this.preventInputFocus = false; this.savedScrollPosition = null; - console.log( - '🟢 [VuetifyFixFocusMixin] Reset preventInputFocus and savedScrollPosition after 500ms', - ); }, 500); } }, onInputFocus($event) { const target = $event.target; - console.log('🟢 [VuetifyFixFocusMixin] onInputFocus triggered on:', { - tagName: target.tagName, - type: target.type, - id: target.id, - className: target.className, - preventInputFocus: this.preventInputFocus, - savedScrollPosition: this.savedScrollPosition, - }); // If we have a saved scroll position and an unwanted focus happened if (this.preventInputFocus && this.savedScrollPosition !== null) { @@ -86,14 +49,9 @@ export default { // Only intercept text input focus (autocomplete fields) if (target.tagName === 'INPUT' && target.type === 'text') { - console.log('🟢 [VuetifyFixFocusMixin] PREVENTING FOCUS on:', target); const scrollBefore = document.querySelector( '.v-dialog .modal-content', )?.scrollTop; - console.log( - '🟢 [VuetifyFixFocusMixin] Scroll position before:', - scrollBefore, - ); // Blur the focused element setTimeout(() => { @@ -105,25 +63,9 @@ export default { if (modalContent && this.savedScrollPosition !== null) { modalContent.scrollTop = this.savedScrollPosition; const scrollAfter = modalContent.scrollTop; - console.log( - '🟢 [VuetifyFixFocusMixin] Scroll position restored to:', - scrollAfter, - ); } }, 0); - } else { - console.log( - '🟢 [VuetifyFixFocusMixin] Not a text input, skipping prevention', - ); } - } else { - console.log( - '🟢 [VuetifyFixFocusMixin] Focus allowed (preventInputFocus:', - this.preventInputFocus, - ', savedScrollPosition:', - this.savedScrollPosition, - ')', - ); } }, }, diff --git a/app/src/plugins/tm@modal/task/z-task-modal.vue b/app/src/plugins/tm@modal/task/z-task-modal.vue index e1a9385..44836f6 100644 --- a/app/src/plugins/tm@modal/task/z-task-modal.vue +++ b/app/src/plugins/tm@modal/task/z-task-modal.vue @@ -505,12 +505,6 @@ export default { methods: { // Override onDateOpen from VuetifyFixFocusMixin to add logging onDateOpen($event) { - console.log( - '🟡 [z-task-modal] onDateOpen called at:', - new Date().toISOString(), - 'with event:', - $event, - ); // Call the parent mixin's onDateOpen method if ( VuetifyFixFocusMixin.methods && @@ -523,17 +517,6 @@ export default { // Override onInputFocus from VuetifyFixFocusMixin to add logging onInputFocus($event) { const target = $event?.target; - console.log( - '🟡 [z-task-modal] onInputFocus called at:', - new Date().toISOString(), - 'on:', - { - tagName: target?.tagName, - type: target?.type, - id: target?.id, - className: target?.className, - }, - ); // Call the parent mixin's onInputFocus method if ( VuetifyFixFocusMixin.methods && @@ -601,9 +584,6 @@ export default { // CRITICAL: Prevent v-dialog autofocus BEFORE modal opens this.preventInputFocus = true; - console.log( - '🟡 [z-task-modal] showModal - preventInputFocus set to true BEFORE modal opens', - ); this.visible = true; this.timeblocks = [ @@ -620,9 +600,6 @@ export default { // Reset after modal is fully rendered and v-dialog autofocus has been blocked setTimeout(() => { this.preventInputFocus = false; - console.log( - '🟡 [z-task-modal] showModal - preventInputFocus reset to false after 300ms', - ); }, 300); });