Skip to content

Commit b3c2071

Browse files
committed
feat(core): add selection api (NgtSelection and NgtSelect)
1 parent 19510f5 commit b3c2071

File tree

2 files changed

+96
-0
lines changed

2 files changed

+96
-0
lines changed

Diff for: libs/core/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
export * from './lib/canvas';
22
export * from './lib/directives/args';
3+
export * from './lib/directives/selection';
34
export * from './lib/html';
45
export * from './lib/instance';
56
export * from './lib/loader';
67
export * from './lib/loop';
8+
export * from './lib/pipes/hexify';
79
export * from './lib/portal';
810
export * from './lib/renderer';
911
export * from './lib/roots';

Diff for: libs/core/src/lib/directives/selection.ts

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import {
2+
afterNextRender,
3+
booleanAttribute,
4+
ChangeDetectionStrategy,
5+
Component,
6+
CUSTOM_ELEMENTS_SCHEMA,
7+
Directive,
8+
ElementRef,
9+
inject,
10+
input,
11+
signal,
12+
untracked,
13+
viewChild,
14+
} from '@angular/core';
15+
import { getLocalState, NgtObjectEvents } from 'angular-three';
16+
import { injectAutoEffect } from 'ngxtension/auto-effect';
17+
import { Group, Object3D } from 'three';
18+
import { extend } from '../renderer';
19+
import { NgtGroup } from '../three-types';
20+
import { NgtObjectEventsHostDirective } from '../utils/object-events';
21+
22+
@Directive({ standalone: true, selector: '[ngtSelection]' })
23+
export class NgtSelection {
24+
enabled = input(true, { alias: 'ngtSelection', transform: booleanAttribute });
25+
26+
private selection = signal<Array<ElementRef<Object3D> | Object3D>>([]);
27+
collection = this.selection.asReadonly();
28+
29+
select(...objects: Array<ElementRef<Object3D> | Object3D>) {
30+
this.selection.update((prev) => [...prev, ...objects]);
31+
}
32+
33+
unselect(...objects: Array<ElementRef<Object3D> | Object3D>) {
34+
this.selection.update((prev) => prev.filter((selected) => !objects.includes(selected)));
35+
}
36+
}
37+
38+
@Component({
39+
selector: 'ngt-select',
40+
standalone: true,
41+
template: `
42+
<ngt-group #group [parameters]="options()">
43+
<ng-content />
44+
</ngt-group>
45+
`,
46+
hostDirectives: [NgtObjectEventsHostDirective],
47+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
48+
changeDetection: ChangeDetectionStrategy.OnPush,
49+
})
50+
export class NgtSelect {
51+
enabled = input(false, { transform: booleanAttribute });
52+
options = input({} as Partial<NgtGroup>);
53+
54+
groupRef = viewChild.required<ElementRef<Group>>('group');
55+
56+
constructor() {
57+
extend({ Group });
58+
59+
const objectEvents = inject(NgtObjectEvents, { host: true });
60+
const selection = inject(NgtSelection);
61+
const autoEffect = injectAutoEffect();
62+
63+
afterNextRender(() => {
64+
objectEvents.ngtObjectEvents.set(this.groupRef());
65+
66+
autoEffect(
67+
() => {
68+
const group = this.groupRef().nativeElement;
69+
const localState = getLocalState(group);
70+
if (!localState) return;
71+
72+
const enabled = this.enabled();
73+
if (!enabled) return;
74+
75+
const [collection] = [untracked(selection.collection), localState.objects()];
76+
let changed = false;
77+
const current: Object3D[] = [];
78+
group.traverse((child) => {
79+
child.type === 'Mesh' && current.push(child);
80+
if (collection.indexOf(child) === -1) changed = true;
81+
});
82+
83+
if (!changed) return;
84+
85+
selection.select(...current);
86+
return () => {
87+
selection.unselect(...current);
88+
};
89+
},
90+
{ allowSignalWrites: true },
91+
);
92+
});
93+
}
94+
}

0 commit comments

Comments
 (0)