From b8b9cb508ad18117059fe5113f76c08fcf68c8cd Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Mon, 17 Mar 2025 14:14:15 +0200 Subject: [PATCH 1/2] [#61959] Segmented control jumps when changing the date field https://community.openproject.org/work_packages/61959 --- .../work_packages/date_picker/form_component.html.erb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/components/work_packages/date_picker/form_component.html.erb b/app/components/work_packages/date_picker/form_component.html.erb index c9b1ae698dc..ac7bc08a619 100644 --- a/app/components/work_packages/date_picker/form_component.html.erb +++ b/app/components/work_packages/date_picker/form_component.html.erb @@ -23,7 +23,8 @@ icon: :pin, href: work_package_datepicker_dialog_content_path(params.merge(schedule_manually: true).permit!), data: { - turbo_stream: true, + turbo_frame: "wp-datepicker-dialog--content", + morph: true, qa_selected: schedule_manually }, test_selector: "op-datepicker-modal--scheduling_manual", @@ -35,7 +36,8 @@ icon: "op-auto-date", href: work_package_datepicker_dialog_content_path(params.merge(schedule_manually: false).permit!), data: { - turbo_stream: true, + turbo_frame: "wp-datepicker-dialog--content", + morph: true, qa_selected: !schedule_manually }, test_selector: "op-datepicker-modal--scheduling_automatic", From 02eb62d6c9533b87ab511fad6d16b6a50b293c37 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Mon, 17 Mar 2025 21:41:13 +0200 Subject: [PATCH 2/2] Re-initialise the catalyst SegmentedController to avoid control button flickering. --- .../date_picker/form_component.html.erb | 6 ++-- .../modal-with-turbo-content.directive.ts | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/app/components/work_packages/date_picker/form_component.html.erb b/app/components/work_packages/date_picker/form_component.html.erb index ac7bc08a619..c9b1ae698dc 100644 --- a/app/components/work_packages/date_picker/form_component.html.erb +++ b/app/components/work_packages/date_picker/form_component.html.erb @@ -23,8 +23,7 @@ icon: :pin, href: work_package_datepicker_dialog_content_path(params.merge(schedule_manually: true).permit!), data: { - turbo_frame: "wp-datepicker-dialog--content", - morph: true, + turbo_stream: true, qa_selected: schedule_manually }, test_selector: "op-datepicker-modal--scheduling_manual", @@ -36,8 +35,7 @@ icon: "op-auto-date", href: work_package_datepicker_dialog_content_path(params.merge(schedule_manually: false).permit!), data: { - turbo_frame: "wp-datepicker-dialog--content", - morph: true, + turbo_stream: true, qa_selected: !schedule_manually }, test_selector: "op-datepicker-modal--scheduling_automatic", diff --git a/frontend/src/app/shared/components/fields/edit/modal-with-turbo-content/modal-with-turbo-content.directive.ts b/frontend/src/app/shared/components/fields/edit/modal-with-turbo-content/modal-with-turbo-content.directive.ts index 24a9e8acb68..2d5463893ec 100644 --- a/frontend/src/app/shared/components/fields/edit/modal-with-turbo-content/modal-with-turbo-content.directive.ts +++ b/frontend/src/app/shared/components/fields/edit/modal-with-turbo-content/modal-with-turbo-content.directive.ts @@ -56,6 +56,7 @@ export class ModalWithTurboContentDirective implements AfterViewInit, OnDestroy @Output() cancel= new EventEmitter(); private contextBasedListenerBound = this.contextBasedListener.bind(this); + private preserveSegmentAttributesBound = this.preserveSegmentAttributes.bind(this); private cancelListenerBound = this.cancelListener.bind(this); constructor( @@ -72,6 +73,8 @@ export class ModalWithTurboContentDirective implements AfterViewInit, OnDestroy ngAfterViewInit() { (this.elementRef.nativeElement as HTMLElement) .addEventListener('turbo:submit-end', this.contextBasedListenerBound); + (this.elementRef.nativeElement as HTMLElement) + .addEventListener('turbo:before-frame-render', this.preserveSegmentAttributesBound); document .addEventListener('cancelModalWithTurboContent', this.cancelListenerBound); @@ -80,6 +83,8 @@ export class ModalWithTurboContentDirective implements AfterViewInit, OnDestroy ngOnDestroy() { (this.elementRef.nativeElement as HTMLElement) .removeEventListener('turbo:submit-end', this.contextBasedListenerBound); + (this.elementRef.nativeElement as HTMLElement) + .removeEventListener('turbo:before-frame-render', this.preserveSegmentAttributesBound); document .removeEventListener('cancelModalWithTurboContent', this.cancelListenerBound); @@ -94,6 +99,30 @@ export class ModalWithTurboContentDirective implements AfterViewInit, OnDestroy } } + private preserveSegmentAttributes(event:CustomEvent) { + const turboEvent = event as CustomEvent<{ newFrame?:HTMLElement }>; + + const element = turboEvent.detail?.newFrame?.querySelector('segmented-control'); + if (!element) return; + + const connectedCallback = Object.getOwnPropertyDescriptor( + Object.getPrototypeOf(element), + 'connectedCallback', + )?.value as (() => void) | undefined; + + if (connectedCallback) { + // Re-initialize the SegmentedControl components as they are being + // re-rendered from turbo. This is necessary, because segmented-controls have + // a custom catalyst controller attached that prevents flickering of the control + // elements. See more here: + // https://github.com/primer/view_components/blob/main/app/components/primer/alpha/segmented_control.ts#L27 + // Ideally canceling the `turbo:before-morph-attribute` event on the "data-content" should + // suffice, but since the datepicker does not work well with morphing at the moment, + // this is the best possible solution. + connectedCallback.call(element); + } + } + private cancelListener():void { this.cancel.emit(); }