brep-io-kernel 1.0.21 → 1.0.22

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.
Files changed (34) hide show
  1. package/README.md +4 -1
  2. package/dist-kernel/brep-kernel.js +11065 -10512
  3. package/package.json +3 -2
  4. package/src/BREP/Edge.js +2 -0
  5. package/src/BREP/Face.js +2 -0
  6. package/src/BREP/SolidMethods/visualize.js +372 -365
  7. package/src/BREP/Vertex.js +2 -17
  8. package/src/PartHistory.js +4 -25
  9. package/src/SketchSolver2D.js +3 -0
  10. package/src/UI/AccordionWidget.js +1 -1
  11. package/src/UI/EnvMonacoEditor.js +0 -3
  12. package/src/UI/HistoryWidget.js +3 -0
  13. package/src/UI/SceneListing.js +45 -7
  14. package/src/UI/SelectionFilter.js +469 -442
  15. package/src/UI/SelectionState.js +464 -0
  16. package/src/UI/assembly/AssemblyConstraintCollectionWidget.js +40 -1
  17. package/src/UI/assembly/AssemblyConstraintsWidget.js +17 -3
  18. package/src/UI/assembly/constraintSelectionUtils.js +3 -182
  19. package/src/UI/{assembly/constraintFaceUtils.js → faceUtils.js} +30 -5
  20. package/src/UI/featureDialogs.js +99 -69
  21. package/src/UI/pmi/LabelOverlay.js +32 -0
  22. package/src/UI/pmi/PMIMode.js +23 -0
  23. package/src/UI/pmi/dimensions/HoleCalloutAnnotation.js +7 -1
  24. package/src/UI/toolbarButtons/orientToFaceButton.js +3 -36
  25. package/src/UI/toolbarButtons/registerDefaultButtons.js +2 -0
  26. package/src/UI/toolbarButtons/selectionStateButton.js +206 -0
  27. package/src/UI/viewer.js +16 -16
  28. package/src/assemblyConstraints/AssemblyConstraintHistory.js +18 -42
  29. package/src/assemblyConstraints/constraints/AngleConstraint.js +1 -0
  30. package/src/assemblyConstraints/constraints/DistanceConstraint.js +1 -0
  31. package/src/features/selectionUtils.js +21 -5
  32. package/src/features/sketch/SketchFeature.js +2 -2
  33. package/src/features/sketch/sketchSolver2D/constraintDefinitions.js +3 -2
  34. package/src/utils/selectionResolver.js +258 -0
@@ -0,0 +1,464 @@
1
+ import { CADmaterials } from "./CADmaterials.js";
2
+
3
+ const debugMode = false;
4
+
5
+ export class SelectionState {
6
+ static hoverColor = '#fbff00';
7
+
8
+ static attach(obj, { deep = false } = {}) {
9
+ if (!obj || typeof obj !== 'object') return false;
10
+ let changed = false;
11
+
12
+ const attachOne = (target) => {
13
+ if (!target || typeof target !== 'object') return;
14
+ const state = SelectionState._ensureState(target);
15
+ if (!state) return;
16
+ const wasAttached = !!state._attached;
17
+
18
+ const selectedDesc = Object.getOwnPropertyDescriptor(target, 'selected');
19
+ if (!selectedDesc || !selectedDesc.get || !selectedDesc.get.__selectionState) {
20
+ const getter = function () { return state.selected; };
21
+ getter.__selectionState = true;
22
+ const setter = function (v) {
23
+ const nv = !!v;
24
+ if (state.selected === nv) return;
25
+ if (debugMode) {
26
+ try {
27
+ console.log('[SelectionState] selected changed', {
28
+ name: target?.name,
29
+ type: target?.type,
30
+ prev: state.selected,
31
+ next: nv,
32
+ target,
33
+ });
34
+ console.trace('[SelectionState] selected stack');
35
+ } catch { }
36
+ }
37
+ state.selected = nv;
38
+ SelectionState.apply(target);
39
+ };
40
+ setter.__selectionState = true;
41
+ Object.defineProperty(target, 'selected', {
42
+ get: getter,
43
+ set: setter,
44
+ configurable: true,
45
+ enumerable: true,
46
+ });
47
+ changed = true;
48
+ }
49
+
50
+ const hoveredDesc = Object.getOwnPropertyDescriptor(target, 'hovered');
51
+ if (!hoveredDesc || !hoveredDesc.get || !hoveredDesc.get.__selectionState) {
52
+ const getter = function () { return state.hovered; };
53
+ getter.__selectionState = true;
54
+ const setter = function (v) {
55
+ const nv = !!v;
56
+ if (state.hovered === nv) return;
57
+ if (debugMode) {
58
+ try {
59
+ console.log('[SelectionState] hovered changed', {
60
+ name: target?.name,
61
+ type: target?.type,
62
+ prev: state.hovered,
63
+ next: nv,
64
+ target,
65
+ });
66
+ console.trace('[SelectionState] hovered stack');
67
+ } catch { }
68
+ }
69
+ state.hovered = nv;
70
+ SelectionState.apply(target);
71
+ };
72
+ setter.__selectionState = true;
73
+ Object.defineProperty(target, 'hovered', {
74
+ get: getter,
75
+ set: setter,
76
+ configurable: true,
77
+ enumerable: true,
78
+ });
79
+ changed = true;
80
+ }
81
+
82
+ SelectionState._seedBaseMaterials(target);
83
+ if (!wasAttached) state._attached = true;
84
+
85
+ if (!wasAttached && (state.selected || state.hovered)) {
86
+ SelectionState.apply(target);
87
+ }
88
+ };
89
+
90
+ if (!deep) {
91
+ attachOne(obj);
92
+ return changed;
93
+ }
94
+
95
+ const stack = [obj];
96
+ while (stack.length) {
97
+ const current = stack.pop();
98
+ attachOne(current);
99
+ const kids = Array.isArray(current?.children) ? current.children : [];
100
+ for (const child of kids) {
101
+ if (child) stack.push(child);
102
+ }
103
+ }
104
+ return changed;
105
+ }
106
+
107
+ static setHoverColor(color) {
108
+ if (!color) return;
109
+ try { SelectionState.hoverColor = String(color); } catch { }
110
+ }
111
+
112
+ static apply(obj, { force = false } = {}) {
113
+ if (!obj || typeof obj !== 'object') return;
114
+ SelectionState.attach(obj);
115
+ const state = SelectionState._getState(obj);
116
+ if (!state) return;
117
+
118
+ const type = obj.type || '';
119
+ if (type === 'SOLID' || type === 'COMPONENT') {
120
+ SelectionState._applyToSolid(obj, state, { force });
121
+ return;
122
+ }
123
+
124
+ SelectionState._applyForObject(obj, {
125
+ selected: state.selected,
126
+ hovered: state.hovered,
127
+ force,
128
+ });
129
+ }
130
+
131
+ static setBaseMaterial(obj, material, { force = true } = {}) {
132
+ if (!obj || !material) return;
133
+ SelectionState.attach(obj);
134
+ const targets = SelectionState._getDrawableTargets(obj);
135
+ for (const target of targets) {
136
+ if (!target) continue;
137
+ target.userData = target.userData || {};
138
+ target.userData.__baseMaterial = material;
139
+ }
140
+ SelectionState.apply(obj, { force: !!force });
141
+ }
142
+
143
+ static getBaseMaterial(obj, rootType = obj?.type) {
144
+ if (!obj) return null;
145
+ const ud = obj.userData || {};
146
+ if (ud.__baseMaterial) return ud.__baseMaterial;
147
+ if (ud.__defaultMaterial) return ud.__defaultMaterial;
148
+
149
+ const type = rootType || obj.type || '';
150
+ if (type === 'FACE') return CADmaterials.FACE?.BASE ?? obj.material;
151
+ if (type === 'PLANE') return CADmaterials.PLANE?.BASE ?? CADmaterials.FACE?.BASE ?? obj.material;
152
+ if (type === 'EDGE') return CADmaterials.EDGE?.BASE ?? obj.material;
153
+ if (type === 'VERTEX') return CADmaterials.VERTEX?.BASE ?? obj.material;
154
+ return obj.material ?? null;
155
+ }
156
+
157
+ static getHoverTargets(target) {
158
+ if (!target) return [];
159
+ const type = target.type || '';
160
+ if (type === 'SOLID' || type === 'COMPONENT') {
161
+ const out = [];
162
+ const kids = Array.isArray(target.children) ? target.children : [];
163
+ for (const child of kids) {
164
+ if (!child) continue;
165
+ if (child.type === 'SOLID' || child.type === 'COMPONENT') {
166
+ const nested = Array.isArray(child.children) ? child.children : [];
167
+ for (const nestedChild of nested) {
168
+ if (nestedChild && (nestedChild.type === 'FACE' || nestedChild.type === 'EDGE')) out.push(nestedChild);
169
+ }
170
+ } else if (child.type === 'FACE' || child.type === 'EDGE') {
171
+ out.push(child);
172
+ }
173
+ }
174
+ return out;
175
+ }
176
+ return [target];
177
+ }
178
+
179
+ static _ensureState(obj) {
180
+ if (!obj || typeof obj !== 'object') return null;
181
+ let state = obj.__selectionState;
182
+ if (!state) {
183
+ const existingSelected = !!obj.selected;
184
+ const existingHovered = !!obj.hovered;
185
+ state = { selected: existingSelected, hovered: existingHovered };
186
+ try {
187
+ Object.defineProperty(obj, '__selectionState', {
188
+ value: state,
189
+ writable: true,
190
+ configurable: true,
191
+ enumerable: false,
192
+ });
193
+ } catch {
194
+ obj.__selectionState = state;
195
+ }
196
+ }
197
+ return state;
198
+ }
199
+
200
+ static _getState(obj) {
201
+ return obj?.__selectionState || null;
202
+ }
203
+
204
+ static _seedBaseMaterials(obj) {
205
+ const targets = SelectionState._getDrawableTargets(obj);
206
+ for (const target of targets) {
207
+ if (!target) continue;
208
+ target.userData = target.userData || {};
209
+ if (!target.userData.__baseMaterial) {
210
+ if (target.userData.__defaultMaterial) {
211
+ target.userData.__baseMaterial = target.userData.__defaultMaterial;
212
+ } else if (target.material) {
213
+ target.userData.__baseMaterial = target.material;
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ static _getDrawableTargets(obj) {
220
+ if (!obj) return [];
221
+ if (obj.type === 'VERTEX') {
222
+ const targets = [];
223
+ if (obj._point && obj._point.material) targets.push(obj._point);
224
+ const kids = Array.isArray(obj.children) ? obj.children : [];
225
+ for (const child of kids) {
226
+ if (child && child.material && !targets.includes(child)) targets.push(child);
227
+ }
228
+ return targets.length ? targets : [obj];
229
+ }
230
+ if (obj.material) return [obj];
231
+ return [obj];
232
+ }
233
+
234
+ static _applyToSolid(obj, state, { force = false } = {}) {
235
+ const children = Array.isArray(obj.children) ? obj.children : [];
236
+ if (state.hovered) {
237
+ for (const child of children) {
238
+ if (!child) continue;
239
+ if (child.type === 'SOLID' || child.type === 'COMPONENT') {
240
+ const nested = Array.isArray(child.children) ? child.children : [];
241
+ for (const nestedChild of nested) {
242
+ if (nestedChild && (nestedChild.type === 'FACE' || nestedChild.type === 'EDGE')) {
243
+ SelectionState._applyForObject(nestedChild, { hovered: true, force });
244
+ }
245
+ }
246
+ } else if (child.type === 'FACE' || child.type === 'EDGE') {
247
+ SelectionState._applyForObject(child, { hovered: true, force });
248
+ }
249
+ }
250
+ return;
251
+ }
252
+
253
+ if (state.selected) {
254
+ for (const child of children) {
255
+ if (!child) continue;
256
+ if (child.type === 'FACE' || child.type === 'EDGE' || child.type === 'PLANE') {
257
+ SelectionState._applyForObject(child, { selected: true, force });
258
+ }
259
+ }
260
+ return;
261
+ }
262
+
263
+ for (const child of children) {
264
+ if (!child) continue;
265
+ if (child.type === 'FACE' || child.type === 'EDGE' || child.type === 'PLANE') {
266
+ SelectionState.attach(child);
267
+ const childState = SelectionState._getState(child);
268
+ SelectionState._applyForObject(child, {
269
+ selected: !!childState?.selected,
270
+ hovered: !!childState?.hovered,
271
+ force,
272
+ });
273
+ }
274
+ }
275
+ }
276
+
277
+ static _applyForObject(obj, { selected = false, hovered = false, force = false } = {}) {
278
+ if (!obj) return;
279
+ SelectionState._seedBaseMaterials(obj);
280
+ const rootType = obj.type || '';
281
+ const targets = SelectionState._getDrawableTargets(obj);
282
+ for (const target of targets) {
283
+ if (!target) continue;
284
+ if (hovered) {
285
+ SelectionState._applyHover(target, rootType, { force });
286
+ continue;
287
+ }
288
+ SelectionState._clearHover(target);
289
+ if (selected) {
290
+ SelectionState._applySelected(target, rootType);
291
+ } else {
292
+ SelectionState._applyBase(target, rootType);
293
+ }
294
+ }
295
+ }
296
+
297
+ static _applyBase(target, rootType) {
298
+ const base = SelectionState.getBaseMaterial(target, rootType);
299
+ try {
300
+ const ud = target.userData || {};
301
+ if (ud.__selectedMat && ud.__selectedMat !== base) {
302
+ if (Array.isArray(ud.__selectedMat)) {
303
+ for (const m of ud.__selectedMat) {
304
+ try { if (m && typeof m.dispose === 'function') m.dispose(); } catch { }
305
+ }
306
+ } else if (typeof ud.__selectedMat.dispose === 'function') {
307
+ ud.__selectedMat.dispose();
308
+ }
309
+ }
310
+ try { delete ud.__selectedMat; } catch { }
311
+ try { delete ud.__selectedMatBase; } catch { }
312
+ try { delete ud.__selectedColor; } catch { }
313
+ } catch { }
314
+ if (base) SelectionState._assignMaterial(target, base);
315
+ }
316
+
317
+ static _applySelected(target, rootType) {
318
+ const base = SelectionState.getBaseMaterial(target, rootType);
319
+ let mat = base;
320
+ if (rootType === 'FACE') {
321
+ mat = CADmaterials.FACE?.SELECTED ?? base;
322
+ } else if (rootType === 'PLANE') {
323
+ mat = CADmaterials.PLANE?.SELECTED ?? CADmaterials.FACE?.SELECTED ?? base;
324
+ } else if (rootType === 'EDGE') {
325
+ const selMat = CADmaterials.EDGE?.SELECTED ?? CADmaterials.EDGE?.BASE ?? base;
326
+ const selColor = SelectionState._resolveColor(selMat?.color ?? selMat?.color?.getHexString?.());
327
+ if (selColor && base) {
328
+ const ud = target.userData || (target.userData = {});
329
+ const prevMat = ud.__selectedMat;
330
+ const sameBase = ud.__selectedMatBase === base;
331
+ const sameColor = ud.__selectedColor === selColor;
332
+ if (prevMat && sameBase && sameColor) {
333
+ mat = prevMat;
334
+ } else {
335
+ if (prevMat && prevMat !== base) {
336
+ try {
337
+ if (Array.isArray(prevMat)) {
338
+ for (const m of prevMat) {
339
+ try { if (m && typeof m.dispose === 'function') m.dispose(); } catch { }
340
+ }
341
+ } else if (typeof prevMat.dispose === 'function') {
342
+ prevMat.dispose();
343
+ }
344
+ } catch { }
345
+ }
346
+ mat = SelectionState._cloneMaterialWithColor(base, selColor);
347
+ ud.__selectedMat = mat;
348
+ ud.__selectedMatBase = base;
349
+ ud.__selectedColor = selColor;
350
+ }
351
+ } else {
352
+ mat = selMat ?? base;
353
+ }
354
+ } else if (rootType === 'VERTEX') {
355
+ mat = CADmaterials.VERTEX?.SELECTED ?? base;
356
+ }
357
+ if (mat) SelectionState._assignMaterial(target, mat);
358
+ }
359
+
360
+ static _applyHover(target, rootType, { force = false } = {}) {
361
+ const ud = target.userData || (target.userData = {});
362
+ if (ud.__hoverMatApplied) {
363
+ if (!force) return;
364
+ SelectionState._clearHover(target);
365
+ }
366
+ const base = SelectionState.getBaseMaterial(target, rootType);
367
+ if (!base) return;
368
+ const hoverColor = SelectionState.hoverColor;
369
+ const hoverMat = SelectionState._cloneMaterialWithColor(base, hoverColor);
370
+ ud.__hoverOrigMat = base;
371
+ ud.__hoverMatApplied = true;
372
+ ud.__hoverMat = hoverMat;
373
+ if (hoverMat) SelectionState._assignMaterial(target, hoverMat);
374
+ }
375
+
376
+ static _clearHover(target) {
377
+ const ud = target.userData || {};
378
+ if (!ud.__hoverMatApplied) return;
379
+ try {
380
+ if (ud.__hoverMat && ud.__hoverMat !== ud.__hoverOrigMat) {
381
+ if (Array.isArray(ud.__hoverMat)) {
382
+ for (const m of ud.__hoverMat) {
383
+ try { if (m && typeof m.dispose === 'function') m.dispose(); } catch { }
384
+ }
385
+ } else if (typeof ud.__hoverMat.dispose === 'function') {
386
+ ud.__hoverMat.dispose();
387
+ }
388
+ }
389
+ } catch { }
390
+ try { delete ud.__hoverMatApplied; } catch { }
391
+ try { delete ud.__hoverOrigMat; } catch { }
392
+ try { delete ud.__hoverMat; } catch { }
393
+ }
394
+
395
+ static _assignMaterial(target, material) {
396
+ try {
397
+ const prev = target?.material;
398
+ if (prev !== material && debugMode) {
399
+ console.log('[SelectionState] material changed', {
400
+ name: target?.name,
401
+ type: target?.type,
402
+ prev,
403
+ next: material,
404
+ target,
405
+ });
406
+ console.trace('[SelectionState] material stack');
407
+ }
408
+ } catch { }
409
+ try { target.material = material; } catch { }
410
+ }
411
+
412
+ static _cloneMaterialWithColor(material, color) {
413
+ if (!material) return material;
414
+ if (Array.isArray(material)) {
415
+ return material.map((m) => SelectionState._cloneMaterialWithColor(m, color));
416
+ }
417
+ let clone = material;
418
+ try {
419
+ if (material && typeof material.clone === 'function') clone = material.clone();
420
+ } catch {
421
+ clone = material;
422
+ }
423
+ try {
424
+ if (clone && clone.color && typeof clone.color.set === 'function' && color) {
425
+ clone.color.set(color);
426
+ }
427
+ } catch { }
428
+ try {
429
+ if (material && clone && material.resolution && clone.resolution && typeof clone.resolution.copy === 'function') {
430
+ clone.resolution.copy(material.resolution);
431
+ }
432
+ } catch { }
433
+ try {
434
+ if (material && clone && typeof material.dashed !== 'undefined' && typeof clone.dashed !== 'undefined') {
435
+ clone.dashed = material.dashed;
436
+ }
437
+ if (material && clone && typeof material.dashSize !== 'undefined' && typeof clone.dashSize !== 'undefined') {
438
+ clone.dashSize = material.dashSize;
439
+ }
440
+ if (material && clone && typeof material.gapSize !== 'undefined' && typeof clone.gapSize !== 'undefined') {
441
+ clone.gapSize = material.gapSize;
442
+ }
443
+ if (material && clone && typeof material.dashScale !== 'undefined' && typeof clone.dashScale !== 'undefined') {
444
+ clone.dashScale = material.dashScale;
445
+ }
446
+ } catch { }
447
+ return clone;
448
+ }
449
+
450
+ static _resolveColor(value) {
451
+ if (!value) return null;
452
+ if (value?.isColor) return value;
453
+ if (typeof value === 'string') {
454
+ const v = value.trim();
455
+ if (/^[0-9a-fA-F]{6}$/.test(v)) return `#${v}`;
456
+ return v;
457
+ }
458
+ if (typeof value === 'number') return value;
459
+ if (typeof value?.getHexString === 'function') {
460
+ try { return `#${value.getHexString()}`; } catch { }
461
+ }
462
+ return null;
463
+ }
464
+ }
@@ -164,6 +164,7 @@ export class AssemblyConstraintCollectionWidget extends HistoryCollectionWidget
164
164
 
165
165
  this.partHistory = partHistory || null;
166
166
  this.viewer = viewer || null;
167
+ this._pendingFocusField = null;
167
168
  this._autoFocusOnExpand = true;
168
169
  this._highlightCallback = typeof onHighlightRequest === 'function' ? onHighlightRequest : null;
169
170
  this._clearHighlightCallback = typeof onClearHighlight === 'function' ? onClearHighlight : null;
@@ -175,11 +176,15 @@ export class AssemblyConstraintCollectionWidget extends HistoryCollectionWidget
175
176
  this.partHistory = partHistory || null;
176
177
  }
177
178
 
178
- focusEntryById(targetId, { behavior = 'smooth' } = {}) {
179
+ focusEntryById(targetId, { behavior = 'smooth', focusField = null } = {}) {
179
180
  if (targetId == null) return;
180
181
  const id = String(targetId);
181
182
  if (!id) return;
182
183
  this._expandedId = id;
184
+ if (this._autoFocusOnExpand) {
185
+ this._pendingFocusEntryId = id;
186
+ }
187
+ this._pendingFocusField = focusField != null ? String(focusField) : null;
183
188
  this.render();
184
189
  const root = this._shadow || null;
185
190
  if (!root) return;
@@ -190,6 +195,40 @@ export class AssemblyConstraintCollectionWidget extends HistoryCollectionWidget
190
195
  }
191
196
  }
192
197
 
198
+ _applyPendingFocus() {
199
+ if (!this._autoFocusOnExpand) return;
200
+ const targetId = this._pendingFocusEntryId;
201
+ if (!targetId) return;
202
+ if (!this._expandedId || String(this._expandedId) !== String(targetId)) {
203
+ this._pendingFocusEntryId = null;
204
+ this._pendingFocusField = null;
205
+ return;
206
+ }
207
+ const form = this.getFormForEntry(targetId);
208
+ if (!form) {
209
+ this._pendingFocusEntryId = null;
210
+ this._pendingFocusField = null;
211
+ return;
212
+ }
213
+ const focusField = this._pendingFocusField;
214
+ const focus = () => {
215
+ let handled = false;
216
+ if (focusField && typeof form.activateField === 'function') {
217
+ try { handled = form.activateField(focusField) === true; } catch { /* ignore */ }
218
+ }
219
+ if (!handled) {
220
+ try {
221
+ if (typeof form.focusFirstField === 'function') form.focusFirstField();
222
+ else if (typeof form.activateFirstReferenceSelection === 'function') form.activateFirstReferenceSelection();
223
+ } catch { /* ignore */ }
224
+ }
225
+ };
226
+ if (typeof requestAnimationFrame === 'function') requestAnimationFrame(() => focus());
227
+ else setTimeout(focus, 0);
228
+ this._pendingFocusEntryId = null;
229
+ this._pendingFocusField = null;
230
+ }
231
+
193
232
  #decorateEntry(context = {}) {
194
233
  const { entry = null, elements = {}, index = 0 } = context;
195
234
  if (!elements || !elements.item) return;
@@ -7,7 +7,7 @@ import {
7
7
  computeFaceOrigin,
8
8
  computeFaceNormal,
9
9
  estimateArrowLength,
10
- } from './constraintFaceUtils.js';
10
+ } from '../faceUtils.js';
11
11
  import { applyHighlightMaterial, restoreHighlightRecords } from './constraintHighlightUtils.js';
12
12
  import { extractWorldPoint } from './constraintPointUtils.js';
13
13
  import { constraintStatusInfo } from './constraintStatusUtils.js';
@@ -1251,6 +1251,17 @@ export class AssemblyConstraintsWidget {
1251
1251
  return null;
1252
1252
  }
1253
1253
 
1254
+ #resolveFocusFieldForConstraint(entry) {
1255
+ const constraintClass = this._resolveConstraintClass(entry);
1256
+ const field = constraintClass?.focusField;
1257
+ if (field) return field;
1258
+ const type = constraintClass?.constraintType || entry?.type || entry?.inputParams?.type;
1259
+ const normalized = typeof type === 'string' ? type.toLowerCase() : '';
1260
+ if (normalized === 'distance') return 'distance';
1261
+ if (normalized === 'angle') return 'angle';
1262
+ return null;
1263
+ }
1264
+
1254
1265
  #handleLabelClick(idx, _ann, ev) {
1255
1266
  if (idx == null) return;
1256
1267
  const id = String(idx);
@@ -1259,12 +1270,14 @@ export class AssemblyConstraintsWidget {
1259
1270
  try { ev.preventDefault(); } catch { }
1260
1271
  try { ev.stopPropagation(); } catch { }
1261
1272
  }
1273
+ const entries = this.history?.list?.() || [];
1274
+ const targetEntry = entries.find((entry) => resolveConstraintId(entry) === id) || null;
1275
+
1262
1276
  let changed = false;
1263
1277
  if (typeof this.history?.setExclusiveOpen === 'function') {
1264
1278
  changed = this.history.setExclusiveOpen(id);
1265
1279
  }
1266
1280
  if (!changed) {
1267
- const entries = this.history?.list?.() || [];
1268
1281
  for (const entry of entries) {
1269
1282
  const entryId = resolveConstraintId(entry);
1270
1283
  const shouldOpen = entryId === id;
@@ -1276,7 +1289,8 @@ export class AssemblyConstraintsWidget {
1276
1289
  this.history?.setOpenState?.(id, true);
1277
1290
  }
1278
1291
 
1279
- this._constraintList?.focusEntryById?.(id);
1292
+ const focusField = this.#resolveFocusFieldForConstraint(targetEntry);
1293
+ this._constraintList?.focusEntryById?.(id, { focusField });
1280
1294
  }
1281
1295
 
1282
1296
  _createNormalArrow(object, color, label) {