@selvajs/ui 6.6.0-beta.12 → 6.6.0-beta.13
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/components/viewer/SceneManager.svelte +12 -1
- package/dist/components/viewer/SceneManager.svelte.d.ts +3 -1
- package/dist/components/viewer/Viewer.svelte +30 -3
- package/package.json +5 -5
- package/src/lib/components/viewer/SceneManager.svelte +12 -1
- package/src/lib/components/viewer/Viewer.svelte +30 -3
|
@@ -33,9 +33,11 @@
|
|
|
33
33
|
* repaint, so the row updates instantly and the geometry lags behind.
|
|
34
34
|
*/
|
|
35
35
|
onVisibilityChange?: () => void;
|
|
36
|
+
/** The entry under the pointer, or null once it leaves. Drives the viewport preview tint. */
|
|
37
|
+
onHover?: (entry: SceneEntry | null) => void;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
let { outliner, sceneVersion = 0, onVisibilityChange }: Props = $props();
|
|
40
|
+
let { outliner, sceneVersion = 0, onVisibilityChange, onHover }: Props = $props();
|
|
39
41
|
|
|
40
42
|
// A partially hidden layer hides the rest; only a fully hidden one comes back — the same rule
|
|
41
43
|
// `visibility.toggleLayer` applies to objects, restated over entries because a layer's rows are
|
|
@@ -51,6 +53,13 @@
|
|
|
51
53
|
onVisibilityChange?.();
|
|
52
54
|
};
|
|
53
55
|
|
|
56
|
+
// A solve or closing the panel removes the row without a mouseleave, which would leave the
|
|
57
|
+
// preview tint stuck on the viewport.
|
|
58
|
+
$effect(() => {
|
|
59
|
+
void sceneVersion;
|
|
60
|
+
return () => onHover?.(null);
|
|
61
|
+
});
|
|
62
|
+
|
|
54
63
|
// Derived, not destructured: the prop is reassignable, and these must follow it.
|
|
55
64
|
const hidden = $derived(outliner.visibility.hidden);
|
|
56
65
|
const selected = $derived(outliner.selection.selected);
|
|
@@ -331,6 +340,8 @@
|
|
|
331
340
|
class="gap-1.5 pl-5 pr-2 flex h-full cursor-pointer items-center transition-colors
|
|
332
341
|
{isSelected ? 'bg-primary/10 hover:bg-primary/15' : 'hover:bg-muted'}
|
|
333
342
|
{isHidden ? 'opacity-40' : ''}"
|
|
343
|
+
onmouseenter={() => onHover?.(entry)}
|
|
344
|
+
onmouseleave={() => onHover?.(null)}
|
|
334
345
|
onmousedown={(e) => {
|
|
335
346
|
e.stopPropagation();
|
|
336
347
|
if (e.shiftKey) e.preventDefault();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type SceneOutliner } from '@selvajs/visualization/scene';
|
|
1
|
+
import { type SceneEntry, type SceneOutliner } from '@selvajs/visualization/scene';
|
|
2
2
|
interface Props {
|
|
3
3
|
/**
|
|
4
4
|
* Owned by `Viewer.svelte`, never built here: this panel unmounts when it closes, and hidden
|
|
@@ -13,6 +13,8 @@ interface Props {
|
|
|
13
13
|
* repaint, so the row updates instantly and the geometry lags behind.
|
|
14
14
|
*/
|
|
15
15
|
onVisibilityChange?: () => void;
|
|
16
|
+
/** The entry under the pointer, or null once it leaves. Drives the viewport preview tint. */
|
|
17
|
+
onHover?: (entry: SceneEntry | null) => void;
|
|
16
18
|
}
|
|
17
19
|
declare const SceneManager: import("svelte").Component<Props, {}, "">;
|
|
18
20
|
type SceneManager = ReturnType<typeof SceneManager>;
|
|
@@ -129,6 +129,8 @@
|
|
|
129
129
|
let updateShadowBounds: (() => void) | null = null;
|
|
130
130
|
let fitToView: (() => void) | null = null;
|
|
131
131
|
let clearSelection: (() => void) | null = null;
|
|
132
|
+
let selectEntries: ThreeViewer['selectEntries'] | null = null;
|
|
133
|
+
let setHover: ThreeViewer['setHover'] | null = null;
|
|
132
134
|
let viewerInitialized = false;
|
|
133
135
|
let sceneVersion = $state(0);
|
|
134
136
|
let hideButton = $state(false);
|
|
@@ -203,8 +205,8 @@
|
|
|
203
205
|
events: {
|
|
204
206
|
// Fires before onMeshMetadataClicked, so picking an object without metadata closes the
|
|
205
207
|
// previous object's panel instead of leaving it describing the wrong thing.
|
|
206
|
-
onObjectSelected:
|
|
207
|
-
onBackgroundClicked:
|
|
208
|
+
onObjectSelected: dropPanelSelection,
|
|
209
|
+
onBackgroundClicked: dropPanelSelection,
|
|
208
210
|
onMeshMetadataClicked: config.enableMeshClick
|
|
209
211
|
? (metadata: Record<string, unknown>) => {
|
|
210
212
|
if (hasUsefulMetadata(metadata)) {
|
|
@@ -236,6 +238,8 @@
|
|
|
236
238
|
updateShadowBounds = init.updateShadowBounds;
|
|
237
239
|
fitToView = init.fitToView;
|
|
238
240
|
clearSelection = init.clearSelection;
|
|
241
|
+
selectEntries = init.selectEntries;
|
|
242
|
+
setHover = init.setHover;
|
|
239
243
|
projection = init.cameraController.getProjection();
|
|
240
244
|
|
|
241
245
|
// Untracked so the host reading `meshes` or config inside its setup can't re-run onMount's teardown.
|
|
@@ -389,6 +393,24 @@
|
|
|
389
393
|
selectedMeshName = null;
|
|
390
394
|
}
|
|
391
395
|
|
|
396
|
+
// A click on the canvas replaces whatever the panel selected: one selection at a time.
|
|
397
|
+
function dropPanelSelection() {
|
|
398
|
+
closeMetadata();
|
|
399
|
+
selectedObjects.clear();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Mirrors the panel's selection into the viewport. The size check skips walking the entries for
|
|
403
|
+
// the common empty case, which also keeps `has()` from subscribing to every key.
|
|
404
|
+
$effect(() => {
|
|
405
|
+
if (selectedObjects.size === 0) {
|
|
406
|
+
selectEntries?.([]);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const chosen = outliner?.entries().filter((entry) => selectedObjects.has(entry.key)) ?? [];
|
|
410
|
+
closeMetadata();
|
|
411
|
+
selectEntries?.(chosen);
|
|
412
|
+
});
|
|
413
|
+
|
|
392
414
|
function dismissMetadata() {
|
|
393
415
|
closeMetadata();
|
|
394
416
|
clearSelection?.();
|
|
@@ -655,7 +677,12 @@
|
|
|
655
677
|
{#if sceneManagerOpen && scene && outliner}
|
|
656
678
|
<Resizable.Handle withHandle />
|
|
657
679
|
<Resizable.Pane id="scene-manager" order={2} defaultSize={15} minSize={8} maxSize={30}>
|
|
658
|
-
<SceneManager
|
|
680
|
+
<SceneManager
|
|
681
|
+
{outliner}
|
|
682
|
+
{sceneVersion}
|
|
683
|
+
onVisibilityChange={() => invalidate?.()}
|
|
684
|
+
onHover={(entry) => setHover?.(entry?.object ?? null, entry?.memberIndex ?? null)}
|
|
685
|
+
/>
|
|
659
686
|
</Resizable.Pane>
|
|
660
687
|
{/if}
|
|
661
688
|
</Resizable.PaneGroup>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selvajs/ui",
|
|
3
|
-
"version": "6.6.0-beta.
|
|
3
|
+
"version": "6.6.0-beta.13",
|
|
4
4
|
"description": "Shared UI components and utilities for Selva applications",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "VektorNode",
|
|
@@ -64,9 +64,9 @@
|
|
|
64
64
|
"tailwind-variants": "^3.3.1",
|
|
65
65
|
"three": "^0.186.1",
|
|
66
66
|
"@selvajs/compute": "^5.0.0-beta.8",
|
|
67
|
-
"@selvajs/
|
|
68
|
-
"@selvajs/visualization": "^1.6.0-beta.
|
|
69
|
-
"@selvajs/
|
|
67
|
+
"@selvajs/schemas": "^5.3.0-beta.1",
|
|
68
|
+
"@selvajs/visualization": "^1.6.0-beta.7",
|
|
69
|
+
"@selvajs/solve": "^2.0.0-beta.10"
|
|
70
70
|
},
|
|
71
71
|
"peerDependenciesMeta": {
|
|
72
72
|
"three": {
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"@selvajs/compute": "5.0.0-beta.8",
|
|
98
98
|
"@selvajs/config": "0.0.4",
|
|
99
99
|
"@selvajs/schemas": "5.3.0-beta.1",
|
|
100
|
-
"@selvajs/visualization": "1.6.0-beta.
|
|
100
|
+
"@selvajs/visualization": "1.6.0-beta.7",
|
|
101
101
|
"@selvajs/solve": "2.0.0-beta.10"
|
|
102
102
|
},
|
|
103
103
|
"scripts": {
|
|
@@ -33,9 +33,11 @@
|
|
|
33
33
|
* repaint, so the row updates instantly and the geometry lags behind.
|
|
34
34
|
*/
|
|
35
35
|
onVisibilityChange?: () => void;
|
|
36
|
+
/** The entry under the pointer, or null once it leaves. Drives the viewport preview tint. */
|
|
37
|
+
onHover?: (entry: SceneEntry | null) => void;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
let { outliner, sceneVersion = 0, onVisibilityChange }: Props = $props();
|
|
40
|
+
let { outliner, sceneVersion = 0, onVisibilityChange, onHover }: Props = $props();
|
|
39
41
|
|
|
40
42
|
// A partially hidden layer hides the rest; only a fully hidden one comes back — the same rule
|
|
41
43
|
// `visibility.toggleLayer` applies to objects, restated over entries because a layer's rows are
|
|
@@ -51,6 +53,13 @@
|
|
|
51
53
|
onVisibilityChange?.();
|
|
52
54
|
};
|
|
53
55
|
|
|
56
|
+
// A solve or closing the panel removes the row without a mouseleave, which would leave the
|
|
57
|
+
// preview tint stuck on the viewport.
|
|
58
|
+
$effect(() => {
|
|
59
|
+
void sceneVersion;
|
|
60
|
+
return () => onHover?.(null);
|
|
61
|
+
});
|
|
62
|
+
|
|
54
63
|
// Derived, not destructured: the prop is reassignable, and these must follow it.
|
|
55
64
|
const hidden = $derived(outliner.visibility.hidden);
|
|
56
65
|
const selected = $derived(outliner.selection.selected);
|
|
@@ -331,6 +340,8 @@
|
|
|
331
340
|
class="gap-1.5 pl-5 pr-2 flex h-full cursor-pointer items-center transition-colors
|
|
332
341
|
{isSelected ? 'bg-primary/10 hover:bg-primary/15' : 'hover:bg-muted'}
|
|
333
342
|
{isHidden ? 'opacity-40' : ''}"
|
|
343
|
+
onmouseenter={() => onHover?.(entry)}
|
|
344
|
+
onmouseleave={() => onHover?.(null)}
|
|
334
345
|
onmousedown={(e) => {
|
|
335
346
|
e.stopPropagation();
|
|
336
347
|
if (e.shiftKey) e.preventDefault();
|
|
@@ -129,6 +129,8 @@
|
|
|
129
129
|
let updateShadowBounds: (() => void) | null = null;
|
|
130
130
|
let fitToView: (() => void) | null = null;
|
|
131
131
|
let clearSelection: (() => void) | null = null;
|
|
132
|
+
let selectEntries: ThreeViewer['selectEntries'] | null = null;
|
|
133
|
+
let setHover: ThreeViewer['setHover'] | null = null;
|
|
132
134
|
let viewerInitialized = false;
|
|
133
135
|
let sceneVersion = $state(0);
|
|
134
136
|
let hideButton = $state(false);
|
|
@@ -203,8 +205,8 @@
|
|
|
203
205
|
events: {
|
|
204
206
|
// Fires before onMeshMetadataClicked, so picking an object without metadata closes the
|
|
205
207
|
// previous object's panel instead of leaving it describing the wrong thing.
|
|
206
|
-
onObjectSelected:
|
|
207
|
-
onBackgroundClicked:
|
|
208
|
+
onObjectSelected: dropPanelSelection,
|
|
209
|
+
onBackgroundClicked: dropPanelSelection,
|
|
208
210
|
onMeshMetadataClicked: config.enableMeshClick
|
|
209
211
|
? (metadata: Record<string, unknown>) => {
|
|
210
212
|
if (hasUsefulMetadata(metadata)) {
|
|
@@ -236,6 +238,8 @@
|
|
|
236
238
|
updateShadowBounds = init.updateShadowBounds;
|
|
237
239
|
fitToView = init.fitToView;
|
|
238
240
|
clearSelection = init.clearSelection;
|
|
241
|
+
selectEntries = init.selectEntries;
|
|
242
|
+
setHover = init.setHover;
|
|
239
243
|
projection = init.cameraController.getProjection();
|
|
240
244
|
|
|
241
245
|
// Untracked so the host reading `meshes` or config inside its setup can't re-run onMount's teardown.
|
|
@@ -389,6 +393,24 @@
|
|
|
389
393
|
selectedMeshName = null;
|
|
390
394
|
}
|
|
391
395
|
|
|
396
|
+
// A click on the canvas replaces whatever the panel selected: one selection at a time.
|
|
397
|
+
function dropPanelSelection() {
|
|
398
|
+
closeMetadata();
|
|
399
|
+
selectedObjects.clear();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Mirrors the panel's selection into the viewport. The size check skips walking the entries for
|
|
403
|
+
// the common empty case, which also keeps `has()` from subscribing to every key.
|
|
404
|
+
$effect(() => {
|
|
405
|
+
if (selectedObjects.size === 0) {
|
|
406
|
+
selectEntries?.([]);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const chosen = outliner?.entries().filter((entry) => selectedObjects.has(entry.key)) ?? [];
|
|
410
|
+
closeMetadata();
|
|
411
|
+
selectEntries?.(chosen);
|
|
412
|
+
});
|
|
413
|
+
|
|
392
414
|
function dismissMetadata() {
|
|
393
415
|
closeMetadata();
|
|
394
416
|
clearSelection?.();
|
|
@@ -655,7 +677,12 @@
|
|
|
655
677
|
{#if sceneManagerOpen && scene && outliner}
|
|
656
678
|
<Resizable.Handle withHandle />
|
|
657
679
|
<Resizable.Pane id="scene-manager" order={2} defaultSize={15} minSize={8} maxSize={30}>
|
|
658
|
-
<SceneManager
|
|
680
|
+
<SceneManager
|
|
681
|
+
{outliner}
|
|
682
|
+
{sceneVersion}
|
|
683
|
+
onVisibilityChange={() => invalidate?.()}
|
|
684
|
+
onHover={(entry) => setHover?.(entry?.object ?? null, entry?.memberIndex ?? null)}
|
|
685
|
+
/>
|
|
659
686
|
</Resizable.Pane>
|
|
660
687
|
{/if}
|
|
661
688
|
</Resizable.PaneGroup>
|