Skip to content

Commit 445f68b

Browse files
committed
fixes #1055: context menu does not always appear on right-click after moving strand
1 parent 016b568 commit 445f68b

11 files changed

Lines changed: 115 additions & 28 deletions

lib/src/constants.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ const LEFT_CLICK_BUTTON = 0;
8585
const MIDDLE_CLICK_BUTTON = 1;
8686
const RIGHT_CLICK_BUTTON = 2;
8787

88+
const context_menu_event_name = 'contextmenu';
89+
8890
const KEY_CODE_SHOW_POTENTIAL_HELIX = KeyCode.H;
8991
const KEY_CODE_MOUSEOVER_HELIX_VIEW_INFO = KeyCode.W;
9092
final KEY_CODE_COMMAND_MAC =

lib/src/view/design_main_helix.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,15 @@ class DesignMainHelixComponent extends UiComponent2<DesignMainHelixProps> with P
139139
componentDidMount() {
140140
if (props.show_helix_circles) {
141141
var elt = querySelector('#${group_id()}')!;
142-
elt.addEventListener('contextmenu', on_context_menu);
142+
elt.addEventListener(constants.context_menu_event_name, on_context_menu);
143143
}
144144
}
145145

146146
@override
147147
componentWillUnmount() {
148148
if (props.show_helix_circles) {
149149
var elt = querySelector('#${group_id()}')!;
150-
elt.removeEventListener('contextmenu', on_context_menu);
150+
elt.removeEventListener(constants.context_menu_event_name, on_context_menu);
151151
}
152152
super.componentWillUnmount();
153153
}

lib/src/view/design_main_strand_crossover.dart

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,17 +153,38 @@ class DesignMainStrandCrossoverComponent
153153
// (Dom.svgTitle()(tooltip));
154154
}
155155

156+
// React may replace the underlying DOM element on re-render (e.g., after moving a strand),
157+
// so we track the element we attached to and re-attach in componentDidUpdate if it changed.
158+
Element? _attachedElement;
159+
160+
void _attachContextMenuListener() {
161+
var element = querySelector('#${props.crossover.id}');
162+
if (element != null && !identical(element, _attachedElement)) {
163+
_detachContextMenuListener();
164+
element.addEventListener(constants.context_menu_event_name, on_context_menu);
165+
_attachedElement = element;
166+
}
167+
}
168+
169+
void _detachContextMenuListener() {
170+
_attachedElement?.removeEventListener(constants.context_menu_event_name, on_context_menu);
171+
_attachedElement = null;
172+
}
173+
156174
@override
157175
componentDidMount() {
158-
var element = querySelector('#${props.crossover.id}')!;
159-
element.addEventListener('contextmenu', on_context_menu);
176+
_attachContextMenuListener();
160177
super.componentDidMount();
161178
}
162179

180+
@override
181+
componentDidUpdate(Map prevProps, Map prevState, [dynamic snapshot]) {
182+
_attachContextMenuListener();
183+
}
184+
163185
@override
164186
componentWillUnmount() {
165-
var element = querySelector('#${props.crossover.id}')!;
166-
element.removeEventListener('contextmenu', on_context_menu);
187+
_detachContextMenuListener();
167188
super.componentWillUnmount();
168189
}
169190

lib/src/view/design_main_strand_dna_end.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ class DesignMainDNAEndComponent extends UiComponent2<DesignMainDNAEndProps> with
216216
id = props.domain != null ? props.domain!.dnaend_3p.id : props.ext!.dnaend_free.id;
217217
}
218218
var element = querySelector('#${id}')!;
219-
element.addEventListener('contextmenu', on_context_menu);
219+
element.addEventListener(constants.context_menu_event_name, on_context_menu);
220220
}
221221

222222
@override
@@ -228,7 +228,7 @@ class DesignMainDNAEndComponent extends UiComponent2<DesignMainDNAEndProps> with
228228
id = props.domain != null ? props.domain!.dnaend_3p.id : props.ext!.dnaend_free.id;
229229
}
230230
var element = querySelector('#${id}')!;
231-
element.removeEventListener('contextmenu', on_context_menu);
231+
element.removeEventListener(constants.context_menu_event_name, on_context_menu);
232232
super.componentWillUnmount();
233233
}
234234

lib/src/view/design_main_strand_domain.dart

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,16 +189,38 @@ class DesignMainDomainComponent extends UiComponent2<DesignMainDomainProps> with
189189

190190
// needed for capturing right-click events with React:
191191
// https://medium.com/@ericclemmons/react-event-preventdefault-78c28c950e46
192+
//
193+
// React may replace the underlying DOM element on re-render (e.g., after moving a strand),
194+
// so we track the element we attached to and re-attach in componentDidUpdate if it changed.
195+
Element? _attachedElement;
196+
197+
void _attachContextMenuListener() {
198+
var element = querySelector('#${props.domain.id}');
199+
if (element != null && !identical(element, _attachedElement)) {
200+
_detachContextMenuListener();
201+
element.addEventListener(constants.context_menu_event_name, on_context_menu);
202+
_attachedElement = element;
203+
}
204+
}
205+
206+
void _detachContextMenuListener() {
207+
_attachedElement?.removeEventListener(constants.context_menu_event_name, on_context_menu);
208+
_attachedElement = null;
209+
}
210+
192211
@override
193212
componentDidMount() {
194-
var element = querySelector('#${props.domain.id}')!;
195-
element.addEventListener('contextmenu', on_context_menu);
213+
_attachContextMenuListener();
214+
}
215+
216+
@override
217+
componentDidUpdate(Map prevProps, Map prevState, [dynamic snapshot]) {
218+
_attachContextMenuListener();
196219
}
197220

198221
@override
199222
componentWillUnmount() {
200-
var element = querySelector('#${props.domain.id}')!;
201-
element.removeEventListener('contextmenu', on_context_menu);
223+
_detachContextMenuListener();
202224
super.componentWillUnmount();
203225
}
204226

lib/src/view/design_main_strand_domain_text.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,13 @@ class DesignMainStrandDomainTextComponent extends UiComponent2<DesignMainStrandD
8888
@override
8989
componentDidMount() {
9090
var element = querySelector('#${id()}')!;
91-
element.addEventListener('contextmenu', on_context_menu);
91+
element.addEventListener(constants.context_menu_event_name, on_context_menu);
9292
}
9393

9494
@override
9595
componentWillUnmount() {
9696
var element = querySelector('#${id()}')!;
97-
element.removeEventListener('contextmenu', on_context_menu);
97+
element.removeEventListener(constants.context_menu_event_name, on_context_menu);
9898
super.componentWillUnmount();
9999
}
100100

lib/src/view/design_main_strand_extension.dart

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,17 +157,38 @@ class DesignMainExtensionComponent extends UiComponent2<DesignMainExtensionProps
157157
}
158158
}
159159

160+
// React may replace the underlying DOM element on re-render (e.g., after moving a strand),
161+
// so we track the element we attached to and re-attach in componentDidUpdate if it changed.
162+
Element? _attachedElement;
163+
164+
void _attachContextMenuListener() {
165+
var element = querySelector('#${props.ext.id}');
166+
if (element != null && !identical(element, _attachedElement)) {
167+
_detachContextMenuListener();
168+
element.addEventListener(constants.context_menu_event_name, on_context_menu);
169+
_attachedElement = element;
170+
}
171+
}
172+
173+
void _detachContextMenuListener() {
174+
_attachedElement?.removeEventListener(constants.context_menu_event_name, on_context_menu);
175+
_attachedElement = null;
176+
}
177+
160178
@override
161179
componentDidMount() {
162-
var element = querySelector('#${props.ext.id}')!;
163-
element.addEventListener('contextmenu', on_context_menu);
180+
_attachContextMenuListener();
181+
}
182+
183+
@override
184+
componentDidUpdate(Map prevProps, Map prevState, [dynamic snapshot]) {
185+
_attachContextMenuListener();
164186
}
165187

166188
@override
167189
componentWillUnmount() {
190+
_detachContextMenuListener();
168191
super.componentWillUnmount();
169-
var element = querySelector('#${props.ext.id}')!;
170-
element.removeEventListener('contextmenu', on_context_menu);
171192
}
172193

173194
on_context_menu(Event ev) {

lib/src/view/design_main_strand_insertion.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,14 +217,14 @@ class DesignMainStrandInsertionComponent extends UiComponent2<DesignMainStrandIn
217217
@override
218218
componentDidMount() {
219219
var element = querySelector('#${props.selectable_insertion.id_group}')!;
220-
element.addEventListener('contextmenu', on_context_menu);
220+
element.addEventListener(constants.context_menu_event_name, on_context_menu);
221221
super.componentDidMount();
222222
}
223223

224224
@override
225225
componentWillUnmount() {
226226
var element = querySelector('#${props.selectable_insertion.id_group}')!;
227-
element.removeEventListener('contextmenu', on_context_menu);
227+
element.removeEventListener(constants.context_menu_event_name, on_context_menu);
228228
super.componentWillUnmount();
229229
}
230230

lib/src/view/design_main_strand_loopout.dart

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,38 @@ class DesignMainLoopoutComponent extends UiStatefulComponent2<DesignMainLoopoutP
139139
return path_props(Dom.svgTitle()(tooltip));
140140
}
141141

142+
// React may replace the underlying DOM element on re-render (e.g., after moving a strand),
143+
// so we track the element we attached to and re-attach in componentDidUpdate if it changed.
144+
Element? _attachedElement;
145+
146+
void _attachContextMenuListener() {
147+
var element = querySelector('#${props.loopout.id}');
148+
if (element != null && !identical(element, _attachedElement)) {
149+
_detachContextMenuListener();
150+
element.addEventListener(constants.context_menu_event_name, on_context_menu);
151+
_attachedElement = element;
152+
}
153+
}
154+
155+
void _detachContextMenuListener() {
156+
_attachedElement?.removeEventListener(constants.context_menu_event_name, on_context_menu);
157+
_attachedElement = null;
158+
}
159+
142160
@override
143161
componentDidMount() {
144-
var element = querySelector('#${props.loopout.id}')!;
145-
element.addEventListener('contextmenu', on_context_menu);
162+
_attachContextMenuListener();
163+
}
164+
165+
@override
166+
componentDidUpdate(Map prevProps, Map prevState, [dynamic snapshot]) {
167+
_attachContextMenuListener();
146168
}
147169

148170
@override
149171
componentWillUnmount() {
172+
_detachContextMenuListener();
150173
super.componentWillUnmount();
151-
var element = querySelector('#${props.loopout.id}')!;
152-
element.removeEventListener('contextmenu', on_context_menu);
153174
}
154175

155176
on_context_menu(Event ev) {

lib/src/view/design_main_strand_modification.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,14 @@ class DesignMainStrandModificationComponent extends UiComponent2<DesignMainStran
130130
@override
131131
componentDidMount() {
132132
var element = querySelector('#${props.selectable_modification.id}')!;
133-
element.addEventListener('contextmenu', on_context_menu);
133+
element.addEventListener(constants.context_menu_event_name, on_context_menu);
134134
}
135135

136136
@override
137137
componentWillUnmount() {
138138
super.componentWillUnmount();
139139
var element = querySelector('#${props.selectable_modification.id}')!;
140-
element.removeEventListener('contextmenu', on_context_menu);
140+
element.removeEventListener(constants.context_menu_event_name, on_context_menu);
141141
}
142142

143143
on_context_menu(Event ev) {

0 commit comments

Comments
 (0)