brep-io-kernel 1.0.16 → 1.0.18
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.
- package/dist-kernel/brep-kernel.js +12652 -11749
- package/package.json +1 -1
- package/src/PartHistory.js +106 -0
- package/src/UI/CADmaterials.js +2 -2
- package/src/UI/MainToolbar.js +39 -2
- package/src/UI/SelectionFilter.js +194 -0
- package/src/UI/featureDialogWidgets/booleanOperationField.js +33 -52
- package/src/UI/featureDialogWidgets/referenceSelectionField.js +10 -0
- package/src/UI/featureDialogs.js +593 -7
- package/src/UI/toolbarButtons/orientToFaceButton.js +3 -0
- package/src/UI/toolbarButtons/registerDefaultButtons.js +0 -6
- package/src/UI/toolbarButtons/registerSelectionButtons.js +68 -0
- package/src/UI/viewer.js +22 -4
package/package.json
CHANGED
package/src/PartHistory.js
CHANGED
|
@@ -341,6 +341,7 @@ export class PartHistory {
|
|
|
341
341
|
if (JSON.stringify(feature.inputParams) !== feature.lastRunInputParams) feature.dirty = true;
|
|
342
342
|
|
|
343
343
|
instance.inputParams = await this.sanitizeInputParams(FeatureClass.inputParamsSchema, feature.inputParams);
|
|
344
|
+
try { this._captureReferencePreviewSnapshots(feature, FeatureClass.inputParamsSchema, instance.inputParams, instance.persistentData); } catch { }
|
|
344
345
|
// check the timestamps of any objects referenced by reference_selection inputs; if any are newer than the feature timestamp, mark dirty
|
|
345
346
|
for (const key in FeatureClass.inputParamsSchema) {
|
|
346
347
|
if (Object.prototype.hasOwnProperty.call(FeatureClass.inputParamsSchema, key)) {
|
|
@@ -460,6 +461,111 @@ export class PartHistory {
|
|
|
460
461
|
return this;
|
|
461
462
|
}
|
|
462
463
|
|
|
464
|
+
_captureReferencePreviewSnapshots(feature, schema, resolvedParams, persistentTarget = null) {
|
|
465
|
+
if (!schema || !resolvedParams) return;
|
|
466
|
+
const stores = [];
|
|
467
|
+
if (feature) {
|
|
468
|
+
feature.persistentData = feature.persistentData || {};
|
|
469
|
+
stores.push(feature.persistentData);
|
|
470
|
+
}
|
|
471
|
+
if (persistentTarget && typeof persistentTarget === 'object') {
|
|
472
|
+
stores.push(persistentTarget);
|
|
473
|
+
}
|
|
474
|
+
if (!stores.length) return;
|
|
475
|
+
const ensureBucket = (store, key) => {
|
|
476
|
+
if (!store.__refPreviewSnapshots || typeof store.__refPreviewSnapshots !== 'object') {
|
|
477
|
+
store.__refPreviewSnapshots = {};
|
|
478
|
+
}
|
|
479
|
+
if (!store.__refPreviewSnapshots[key] || typeof store.__refPreviewSnapshots[key] !== 'object') {
|
|
480
|
+
store.__refPreviewSnapshots[key] = {};
|
|
481
|
+
}
|
|
482
|
+
return store.__refPreviewSnapshots[key];
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const normalizeRefName = (obj) => {
|
|
486
|
+
if (!obj) return null;
|
|
487
|
+
const raw = obj.name != null ? String(obj.name).trim() : '';
|
|
488
|
+
if (raw) return raw;
|
|
489
|
+
const type = obj.type || 'OBJECT';
|
|
490
|
+
const pos = obj.position || {};
|
|
491
|
+
const x = Number.isFinite(pos.x) ? pos.x : 0;
|
|
492
|
+
const y = Number.isFinite(pos.y) ? pos.y : 0;
|
|
493
|
+
const z = Number.isFinite(pos.z) ? pos.z : 0;
|
|
494
|
+
return `${type}(${x},${y},${z})`;
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
const extractEdgeWorldPositions = (obj) => {
|
|
498
|
+
if (!obj) return [];
|
|
499
|
+
try { obj.updateMatrixWorld?.(true); } catch { }
|
|
500
|
+
try {
|
|
501
|
+
if (typeof obj.points === 'function') {
|
|
502
|
+
const pts = obj.points(true);
|
|
503
|
+
if (Array.isArray(pts) && pts.length) {
|
|
504
|
+
const flat = [];
|
|
505
|
+
for (const p of pts) {
|
|
506
|
+
if (!p) continue;
|
|
507
|
+
const x = Number(p.x);
|
|
508
|
+
const y = Number(p.y);
|
|
509
|
+
const z = Number(p.z);
|
|
510
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) continue;
|
|
511
|
+
flat.push(x, y, z);
|
|
512
|
+
}
|
|
513
|
+
if (flat.length >= 6) return flat;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
} catch { /* ignore */ }
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
const geom = obj.geometry;
|
|
520
|
+
const pos = geom && typeof geom.getAttribute === 'function' ? geom.getAttribute('position') : null;
|
|
521
|
+
if (!pos || pos.itemSize !== 3 || pos.count < 2) return [];
|
|
522
|
+
const tmp = new THREE.Vector3();
|
|
523
|
+
const flat = [];
|
|
524
|
+
for (let i = 0; i < pos.count; i++) {
|
|
525
|
+
tmp.set(pos.getX(i), pos.getY(i), pos.getZ(i));
|
|
526
|
+
tmp.applyMatrix4(obj.matrixWorld);
|
|
527
|
+
flat.push(tmp.x, tmp.y, tmp.z);
|
|
528
|
+
}
|
|
529
|
+
return flat.length >= 6 ? flat : [];
|
|
530
|
+
} catch { /* ignore */ }
|
|
531
|
+
return [];
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
for (const key in schema) {
|
|
535
|
+
if (!Object.prototype.hasOwnProperty.call(schema, key)) continue;
|
|
536
|
+
const def = schema[key];
|
|
537
|
+
if (!def || def.type !== 'reference_selection') continue;
|
|
538
|
+
const selected = Array.isArray(resolvedParams[key]) ? resolvedParams[key] : [];
|
|
539
|
+
if (!selected.length) continue;
|
|
540
|
+
const buckets = stores.map((store) => ensureBucket(store, key));
|
|
541
|
+
for (const obj of selected) {
|
|
542
|
+
if (!obj || typeof obj !== 'object') continue;
|
|
543
|
+
const refName = normalizeRefName(obj);
|
|
544
|
+
if (!refName) continue;
|
|
545
|
+
const objType = String(obj.type || '').toUpperCase();
|
|
546
|
+
const sourceUuid = obj.uuid || null;
|
|
547
|
+
const sourceFeatureId = obj.owningFeatureID ?? null;
|
|
548
|
+
if (objType === SelectionFilter.EDGE || objType === 'EDGE') {
|
|
549
|
+
const positions = extractEdgeWorldPositions(obj);
|
|
550
|
+
if (positions && positions.length >= 6) {
|
|
551
|
+
for (const bucket of buckets) {
|
|
552
|
+
bucket[refName] = { type: 'EDGE', positions, sourceUuid, sourceFeatureId };
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
} else if (objType === SelectionFilter.VERTEX || objType === 'VERTEX') {
|
|
556
|
+
const pos = new THREE.Vector3();
|
|
557
|
+
try {
|
|
558
|
+
if (typeof obj.getWorldPosition === 'function') obj.getWorldPosition(pos);
|
|
559
|
+
else pos.set(obj.position?.x || 0, obj.position?.y || 0, obj.position?.z || 0);
|
|
560
|
+
} catch { }
|
|
561
|
+
for (const bucket of buckets) {
|
|
562
|
+
bucket[refName] = { type: 'VERTEX', position: [pos.x, pos.y, pos.z], sourceUuid, sourceFeatureId };
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
463
569
|
|
|
464
570
|
async _coerceRunEffects(result, featureType, featureID) {
|
|
465
571
|
if (result == null) return { added: [], removed: [] };
|
package/src/UI/CADmaterials.js
CHANGED
|
@@ -127,8 +127,8 @@ export const CADmaterials = {
|
|
|
127
127
|
depthWrite: true,
|
|
128
128
|
// Keep selected faces slightly behind edges as well.
|
|
129
129
|
polygonOffset: true,
|
|
130
|
-
polygonOffsetFactor:
|
|
131
|
-
polygonOffsetUnits:
|
|
130
|
+
polygonOffsetFactor: -2,
|
|
131
|
+
polygonOffsetUnits: 1,
|
|
132
132
|
emissiveIntensity: 0,
|
|
133
133
|
})
|
|
134
134
|
},
|
package/src/UI/MainToolbar.js
CHANGED
|
@@ -79,6 +79,14 @@ export class MainToolbar {
|
|
|
79
79
|
color: #e9f0ff;
|
|
80
80
|
box-shadow: 0 0 0 1px rgba(110,168,254,.2) inset;
|
|
81
81
|
}
|
|
82
|
+
.mtb-selection {
|
|
83
|
+
display: none;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: 6px;
|
|
86
|
+
padding-left: 6px;
|
|
87
|
+
margin-left: 2px;
|
|
88
|
+
border-left: 1px solid #364053;
|
|
89
|
+
}
|
|
82
90
|
`;
|
|
83
91
|
document.head.appendChild(style);
|
|
84
92
|
}
|
|
@@ -103,7 +111,8 @@ export class MainToolbar {
|
|
|
103
111
|
b.className = 'mtb-btn';
|
|
104
112
|
b.textContent = label;
|
|
105
113
|
b.title = title || label;
|
|
106
|
-
b.
|
|
114
|
+
b.__mtbOnClick = onClick;
|
|
115
|
+
b.addEventListener('click', (e) => { e.stopPropagation(); try { b.__mtbOnClick && b.__mtbOnClick(); } catch {} });
|
|
107
116
|
return b;
|
|
108
117
|
}
|
|
109
118
|
|
|
@@ -111,11 +120,39 @@ export class MainToolbar {
|
|
|
111
120
|
addCustomButton({ label, title, onClick }) {
|
|
112
121
|
try {
|
|
113
122
|
const btn = this._btn(String(label ?? '🔧'), String(title || ''), onClick);
|
|
114
|
-
this._left
|
|
123
|
+
const anchor = this._selectionContainer && this._selectionContainer.parentNode === this._left
|
|
124
|
+
? this._selectionContainer
|
|
125
|
+
: null;
|
|
126
|
+
if (anchor) this._left?.insertBefore(btn, anchor);
|
|
127
|
+
else this._left?.appendChild(btn);
|
|
115
128
|
return btn;
|
|
116
129
|
} catch { return null; }
|
|
117
130
|
}
|
|
118
131
|
|
|
132
|
+
_ensureSelectionContainer() {
|
|
133
|
+
if (this._selectionContainer) return this._selectionContainer;
|
|
134
|
+
const wrap = document.createElement('div');
|
|
135
|
+
wrap.className = 'mtb-selection';
|
|
136
|
+
this._selectionContainer = wrap;
|
|
137
|
+
this._left?.appendChild(wrap);
|
|
138
|
+
return wrap;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Public: allow selection-based buttons in their own cluster
|
|
142
|
+
addSelectionButton({ label, title, onClick }) {
|
|
143
|
+
try {
|
|
144
|
+
const btn = this._btn(String(label ?? '🔧'), String(title || ''), onClick);
|
|
145
|
+
if (label && String(label).length <= 2) btn.classList.add('mtb-icon');
|
|
146
|
+
const wrap = this._ensureSelectionContainer();
|
|
147
|
+
wrap.appendChild(btn);
|
|
148
|
+
return btn;
|
|
149
|
+
} catch { return null; }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
getSelectionContainer() {
|
|
153
|
+
return this._ensureSelectionContainer();
|
|
154
|
+
}
|
|
155
|
+
|
|
119
156
|
_positionWithSidebar() {
|
|
120
157
|
try {
|
|
121
158
|
const sb = this.viewer?.sidebar;
|
|
@@ -17,6 +17,12 @@ export class SelectionFilter {
|
|
|
17
17
|
static previouseAllowedSelectionTypes = null;
|
|
18
18
|
static _hovered = new Set(); // objects currently hover-highlighted
|
|
19
19
|
static hoverColor = '#fbff00'; // default hover tint
|
|
20
|
+
static _selectionActions = new Map();
|
|
21
|
+
static _selectionActionOrder = [];
|
|
22
|
+
static _selectionActionSeq = 1;
|
|
23
|
+
static _selectionActionListenerBound = false;
|
|
24
|
+
static _selectionActionsPending = false;
|
|
25
|
+
static _selectionActionBar = null;
|
|
20
26
|
|
|
21
27
|
constructor() {
|
|
22
28
|
throw new Error("SelectionFilter is static and cannot be instantiated.");
|
|
@@ -429,6 +435,11 @@ export class SelectionFilter {
|
|
|
429
435
|
if (!targetObj) return false;
|
|
430
436
|
|
|
431
437
|
// Update the reference input with the chosen object
|
|
438
|
+
try {
|
|
439
|
+
if (activeRefInput && typeof activeRefInput.__captureReferencePreview === 'function') {
|
|
440
|
+
activeRefInput.__captureReferencePreview(targetObj);
|
|
441
|
+
}
|
|
442
|
+
} catch (_) { /* ignore preview capture errors */ }
|
|
432
443
|
const objType = targetObj.type;
|
|
433
444
|
const objectName = targetObj.name || `${objType}(${targetObj.position?.x || 0},${targetObj.position?.y || 0},${targetObj.position?.z || 0})`;
|
|
434
445
|
|
|
@@ -617,6 +628,189 @@ export class SelectionFilter {
|
|
|
617
628
|
} catch (_) { /* noop */ }
|
|
618
629
|
}
|
|
619
630
|
|
|
631
|
+
static getSelectedObjects(options = {}) {
|
|
632
|
+
const scene = options.scene
|
|
633
|
+
|| SelectionFilter.viewer?.partHistory?.scene
|
|
634
|
+
|| SelectionFilter.viewer?.scene
|
|
635
|
+
|| null;
|
|
636
|
+
const selected = [];
|
|
637
|
+
if (!scene || typeof scene.traverse !== 'function') return selected;
|
|
638
|
+
scene.traverse((obj) => {
|
|
639
|
+
if (obj && obj.selected) selected.push(obj);
|
|
640
|
+
});
|
|
641
|
+
return selected;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
static registerSelectionAction(spec = {}) {
|
|
645
|
+
if (!spec) return null;
|
|
646
|
+
const id = String(spec.id || `selection-action-${SelectionFilter._selectionActionSeq++}`);
|
|
647
|
+
const entry = SelectionFilter._selectionActions.get(id) || { id };
|
|
648
|
+
entry.label = spec.label ?? entry.label ?? '';
|
|
649
|
+
entry.title = spec.title ?? entry.title ?? entry.label ?? '';
|
|
650
|
+
entry.onClick = spec.onClick ?? entry.onClick ?? null;
|
|
651
|
+
entry.shouldShow = typeof spec.shouldShow === 'function' ? spec.shouldShow : (entry.shouldShow || null);
|
|
652
|
+
SelectionFilter._selectionActions.set(id, entry);
|
|
653
|
+
if (!SelectionFilter._selectionActionOrder.includes(id)) {
|
|
654
|
+
SelectionFilter._selectionActionOrder.push(id);
|
|
655
|
+
}
|
|
656
|
+
SelectionFilter._ensureSelectionActionListener();
|
|
657
|
+
SelectionFilter._syncSelectionActions();
|
|
658
|
+
return id;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
static unregisterSelectionAction(id) {
|
|
662
|
+
if (!id) return;
|
|
663
|
+
const entry = SelectionFilter._selectionActions.get(id);
|
|
664
|
+
if (entry?.btn && entry.btn.parentNode) {
|
|
665
|
+
try { entry.btn.parentNode.removeChild(entry.btn); } catch { }
|
|
666
|
+
}
|
|
667
|
+
SelectionFilter._selectionActions.delete(id);
|
|
668
|
+
SelectionFilter._selectionActionOrder = SelectionFilter._selectionActionOrder.filter((k) => k !== id);
|
|
669
|
+
SelectionFilter._syncSelectionActions();
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
static refreshSelectionActions() {
|
|
673
|
+
SelectionFilter._syncSelectionActions();
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
static _ensureSelectionActionListener() {
|
|
677
|
+
if (SelectionFilter._selectionActionListenerBound) return;
|
|
678
|
+
if (typeof window === 'undefined') return;
|
|
679
|
+
SelectionFilter._selectionActionListenerBound = true;
|
|
680
|
+
window.addEventListener('selection-changed', () => SelectionFilter._syncSelectionActions());
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
static _syncSelectionActions() {
|
|
684
|
+
const actions = SelectionFilter._selectionActions;
|
|
685
|
+
if (!actions || actions.size === 0) {
|
|
686
|
+
try {
|
|
687
|
+
const bar = SelectionFilter._selectionActionBar;
|
|
688
|
+
if (bar) bar.style.display = 'none';
|
|
689
|
+
} catch { }
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
const viewer = SelectionFilter.viewer;
|
|
693
|
+
const bar = SelectionFilter._ensureSelectionActionBar(viewer);
|
|
694
|
+
if (!bar) {
|
|
695
|
+
SelectionFilter._selectionActionsPending = true;
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
SelectionFilter._selectionActionsPending = false;
|
|
699
|
+
const selection = SelectionFilter.getSelectedObjects();
|
|
700
|
+
const hideAll = !!viewer?._sketchMode;
|
|
701
|
+
let visibleCount = 0;
|
|
702
|
+
const updateButton = (entry, show) => {
|
|
703
|
+
if (!entry || !entry.btn) return;
|
|
704
|
+
try { entry.btn.style.display = show ? '' : 'none'; } catch { }
|
|
705
|
+
if (show) visibleCount += 1;
|
|
706
|
+
};
|
|
707
|
+
for (const id of SelectionFilter._selectionActionOrder) {
|
|
708
|
+
const entry = actions.get(id);
|
|
709
|
+
if (!entry) continue;
|
|
710
|
+
if (!entry.btn) {
|
|
711
|
+
entry.btn = SelectionFilter._createSelectionActionButton(entry);
|
|
712
|
+
}
|
|
713
|
+
if (!entry.btn) continue;
|
|
714
|
+
try {
|
|
715
|
+
entry.btn.textContent = String(entry.label ?? '');
|
|
716
|
+
entry.btn.title = String(entry.title ?? entry.label ?? '');
|
|
717
|
+
entry.btn.__sabOnClick = entry.onClick;
|
|
718
|
+
const isIcon = String(entry.label || '').length <= 2;
|
|
719
|
+
entry.btn.classList.toggle('sab-icon', isIcon);
|
|
720
|
+
} catch { }
|
|
721
|
+
let show = !hideAll;
|
|
722
|
+
if (show) {
|
|
723
|
+
if (typeof entry.shouldShow === 'function') {
|
|
724
|
+
try { show = !!entry.shouldShow(selection, viewer); } catch { show = false; }
|
|
725
|
+
} else {
|
|
726
|
+
show = selection.length > 0;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
updateButton(entry, show);
|
|
730
|
+
if (entry.btn.parentNode !== bar) {
|
|
731
|
+
try { bar.appendChild(entry.btn); } catch { }
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
try {
|
|
735
|
+
if (bar) bar.style.display = visibleCount > 0 ? 'flex' : 'none';
|
|
736
|
+
} catch { }
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
static _ensureSelectionActionBar(viewer) {
|
|
740
|
+
if (SelectionFilter._selectionActionBar && SelectionFilter._selectionActionBar.isConnected) {
|
|
741
|
+
return SelectionFilter._selectionActionBar;
|
|
742
|
+
}
|
|
743
|
+
if (typeof document === 'undefined') return null;
|
|
744
|
+
const host = viewer?.container || document.body || null;
|
|
745
|
+
if (!host) return null;
|
|
746
|
+
try {
|
|
747
|
+
if (!document.getElementById('selection-action-bar-styles')) {
|
|
748
|
+
const style = document.createElement('style');
|
|
749
|
+
style.id = 'selection-action-bar-styles';
|
|
750
|
+
style.textContent = `
|
|
751
|
+
.selection-action-bar {
|
|
752
|
+
position: absolute;
|
|
753
|
+
top: 100px;
|
|
754
|
+
right: 8px;
|
|
755
|
+
display: flex;
|
|
756
|
+
flex-direction: column;
|
|
757
|
+
gap: 6px;
|
|
758
|
+
align-items: stretch;
|
|
759
|
+
background: rgba(20,24,30,.85);
|
|
760
|
+
border: 1px solid #262b36;
|
|
761
|
+
border-radius: 8px;
|
|
762
|
+
padding: 6px;
|
|
763
|
+
color: #ddd;
|
|
764
|
+
min-width: 40px;
|
|
765
|
+
max-width: 150px;
|
|
766
|
+
z-index: 12;
|
|
767
|
+
user-select: none;
|
|
768
|
+
}
|
|
769
|
+
.selection-action-bar .sab-btn {
|
|
770
|
+
background: transparent;
|
|
771
|
+
border-radius: 6px;
|
|
772
|
+
padding: 4px 8px;
|
|
773
|
+
width: 100%;
|
|
774
|
+
min-height: 34px;
|
|
775
|
+
box-sizing: border-box;
|
|
776
|
+
color: #ddd;
|
|
777
|
+
border: 1px solid #364053;
|
|
778
|
+
cursor: pointer;
|
|
779
|
+
}
|
|
780
|
+
.selection-action-bar .sab-btn:hover { filter: brightness(1.08); }
|
|
781
|
+
.selection-action-bar .sab-btn:active { filter: brightness(1.15); }
|
|
782
|
+
.selection-action-bar .sab-btn.sab-icon {
|
|
783
|
+
font-size: 16px;
|
|
784
|
+
min-width: 36px;
|
|
785
|
+
}
|
|
786
|
+
`;
|
|
787
|
+
document.head.appendChild(style);
|
|
788
|
+
}
|
|
789
|
+
} catch { }
|
|
790
|
+
const bar = document.createElement('div');
|
|
791
|
+
bar.className = 'selection-action-bar';
|
|
792
|
+
host.appendChild(bar);
|
|
793
|
+
SelectionFilter._selectionActionBar = bar;
|
|
794
|
+
return bar;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
static _createSelectionActionButton(entry) {
|
|
798
|
+
try {
|
|
799
|
+
const btn = document.createElement('button');
|
|
800
|
+
btn.className = 'sab-btn';
|
|
801
|
+
btn.textContent = String(entry?.label ?? '');
|
|
802
|
+
btn.title = String(entry?.title ?? entry?.label ?? '');
|
|
803
|
+
btn.__sabOnClick = entry?.onClick ?? null;
|
|
804
|
+
btn.addEventListener('click', (e) => {
|
|
805
|
+
e.stopPropagation();
|
|
806
|
+
try { btn.__sabOnClick && btn.__sabOnClick(); } catch { }
|
|
807
|
+
});
|
|
808
|
+
const isIcon = String(entry?.label || '').length <= 2;
|
|
809
|
+
if (isIcon) btn.classList.add('sab-icon');
|
|
810
|
+
return btn;
|
|
811
|
+
} catch { return null; }
|
|
812
|
+
}
|
|
813
|
+
|
|
620
814
|
static #logAllowedTypesChange(next, reason = '') {
|
|
621
815
|
try {
|
|
622
816
|
const desc = next === SelectionFilter.ALL
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { renderReferenceSelectionField } from './referenceSelectionField.js';
|
|
2
|
+
|
|
1
3
|
export function renderBooleanOperationField({ ui, key, def, controlWrap }) {
|
|
2
4
|
if (!ui.params[key] || typeof ui.params[key] !== 'object') {
|
|
3
|
-
ui.params[key] = { targets: [], operation: 'NONE'
|
|
5
|
+
ui.params[key] = { targets: [], operation: 'NONE' };
|
|
4
6
|
} else {
|
|
5
7
|
if (!Array.isArray(ui.params[key].targets)) ui.params[key].targets = [];
|
|
6
|
-
if (!ui.params[key].operation
|
|
8
|
+
if (!ui.params[key].operation) ui.params[key].operation = 'NONE';
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
const wrap = document.createElement('div');
|
|
@@ -19,70 +21,49 @@ export function renderBooleanOperationField({ ui, key, def, controlWrap }) {
|
|
|
19
21
|
opt.textContent = String(op);
|
|
20
22
|
sel.appendChild(opt);
|
|
21
23
|
}
|
|
22
|
-
sel.value = String(
|
|
24
|
+
sel.value = String(ui.params[key].operation || 'NONE');
|
|
23
25
|
sel.addEventListener('change', () => {
|
|
24
26
|
if (!ui.params[key] || typeof ui.params[key] !== 'object') ui.params[key] = { targets: [], operation: 'NONE' };
|
|
25
27
|
ui.params[key].operation = sel.value;
|
|
26
|
-
ui.params[key].operation = sel.value;
|
|
27
28
|
ui._emitParamsChange(key, ui.params[key]);
|
|
28
29
|
});
|
|
29
30
|
wrap.appendChild(sel);
|
|
30
31
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const inputElTargets = document.createElement('input');
|
|
38
|
-
inputElTargets.type = 'text';
|
|
39
|
-
inputElTargets.className = 'input';
|
|
40
|
-
inputElTargets.dataset.multiple = 'true';
|
|
41
|
-
inputElTargets.placeholder = 'Click then select solids…';
|
|
42
|
-
ui._renderChips(chipsWrap, key, Array.isArray(ui.params[key].targets) ? ui.params[key].targets : []);
|
|
43
|
-
|
|
44
|
-
const activate = () => {
|
|
45
|
-
ui._activateReferenceSelection(inputElTargets, { selectionFilter: ['SOLID'] });
|
|
32
|
+
const refMount = document.createElement('div');
|
|
33
|
+
const targetsDef = {
|
|
34
|
+
type: 'reference_selection',
|
|
35
|
+
multiple: true,
|
|
36
|
+
selectionFilter: ['SOLID'],
|
|
46
37
|
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
ui.
|
|
55
|
-
|
|
56
|
-
|
|
38
|
+
const valueAdapter = {
|
|
39
|
+
read: () => {
|
|
40
|
+
const current = ui.params[key];
|
|
41
|
+
if (!current || typeof current !== 'object') return [];
|
|
42
|
+
return Array.isArray(current.targets) ? current.targets : [];
|
|
43
|
+
},
|
|
44
|
+
write: (next) => {
|
|
45
|
+
if (!ui.params[key] || typeof ui.params[key] !== 'object') ui.params[key] = { targets: [], operation: sel.value || 'NONE' };
|
|
46
|
+
ui.params[key].targets = Array.isArray(next) ? next : [];
|
|
47
|
+
},
|
|
48
|
+
emit: () => {
|
|
57
49
|
ui._emitParamsChange(key, ui.params[key]);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
const cur = Array.isArray(ui.params[key].targets) ? ui.params[key].targets : [];
|
|
69
|
-
for (const name of incoming) {
|
|
70
|
-
if (!cur.includes(name)) cur.push(name);
|
|
71
|
-
}
|
|
72
|
-
ui.params[key].targets = cur;
|
|
73
|
-
ui._renderChips(chipsWrap, key, cur);
|
|
74
|
-
inputElTargets.value = '';
|
|
75
|
-
ui._emitParamsChange(key, ui.params[key]);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
const refField = renderReferenceSelectionField({
|
|
53
|
+
ui,
|
|
54
|
+
key,
|
|
55
|
+
def: targetsDef,
|
|
56
|
+
id: `${key}-targets`,
|
|
57
|
+
controlWrap: refMount,
|
|
58
|
+
valueAdapter,
|
|
76
59
|
});
|
|
77
|
-
|
|
78
|
-
refWrap.appendChild(inputElTargets);
|
|
79
|
-
wrap.appendChild(refWrap);
|
|
60
|
+
wrap.appendChild(refMount);
|
|
80
61
|
|
|
81
62
|
controlWrap.appendChild(wrap);
|
|
82
63
|
|
|
83
64
|
return {
|
|
84
|
-
inputEl:
|
|
85
|
-
activate,
|
|
65
|
+
inputEl: refField.inputEl,
|
|
66
|
+
activate: refField.activate,
|
|
86
67
|
readValue() {
|
|
87
68
|
const current = ui.params[key];
|
|
88
69
|
if (!current || typeof current !== 'object') {
|
|
@@ -5,6 +5,7 @@ export function renderReferenceSelectionField({ ui, key, def, id, controlWrap, v
|
|
|
5
5
|
inputEl.type = 'hidden';
|
|
6
6
|
inputEl.id = id;
|
|
7
7
|
try { inputEl.dataset.key = String(key); } catch (_) { }
|
|
8
|
+
try { inputEl.__refSelectionDef = def; } catch (_) { }
|
|
8
9
|
|
|
9
10
|
const isMulti = !!def.multiple;
|
|
10
11
|
if (isMulti) inputEl.dataset.multiple = 'true';
|
|
@@ -132,6 +133,15 @@ export function renderReferenceSelectionField({ ui, key, def, id, controlWrap, v
|
|
|
132
133
|
inputEl.value = initial ?? '';
|
|
133
134
|
|
|
134
135
|
valueWrap.addEventListener('click', () => ui._activateReferenceSelection(inputEl, def));
|
|
136
|
+
valueWrap.addEventListener('mouseenter', () => {
|
|
137
|
+
const normalized = normalizeReferenceName(inputEl.value || readRawValue());
|
|
138
|
+
if (normalized) {
|
|
139
|
+
try { ui._hoverReferenceSelectionItem?.(inputEl, def, normalized); } catch (_) { }
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
valueWrap.addEventListener('mouseleave', () => {
|
|
143
|
+
try { ui._clearReferenceSelectionHover?.(inputEl); } catch (_) { }
|
|
144
|
+
});
|
|
135
145
|
refWrap.appendChild(valueWrap);
|
|
136
146
|
|
|
137
147
|
inputEl.addEventListener('change', () => {
|