@selvajs/ui 6.6.0-beta.15 → 6.6.0-beta.17
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/README.md +0 -3
- package/dist/components/compute/AppLayout.svelte +5 -3
- package/dist/components/compute/CollapsedPanelStrip.svelte +1 -1
- package/dist/components/compute/ParameterPresetManager.svelte +1 -1
- package/dist/components/compute/SolveMessages.svelte +2 -2
- package/dist/components/layout/FooterItemRenderer.svelte +1 -1
- package/dist/components/layout/PageFooter.svelte +2 -2
- package/dist/components/layout/PageHeader.svelte +1 -1
- package/dist/components/layout/SideNav.svelte +3 -3
- package/dist/components/layout/SubNav.svelte +2 -2
- package/dist/components/preview/ChartOutput.svelte +3 -3
- package/dist/components/preview/Group.svelte +7 -7
- package/dist/components/preview/ImageOutput.svelte +6 -6
- package/dist/components/preview/InputControl.svelte +1 -0
- package/dist/components/preview/OutputDisplay.svelte +8 -6
- package/dist/components/preview/TabBar.svelte +6 -4
- package/dist/components/preview/TabBar.svelte.d.ts +2 -0
- package/dist/components/preview/TabContent.svelte +1 -1
- package/dist/components/preview/TabLayout.svelte +3 -4
- package/dist/components/preview/inputs/ChecklistInput.svelte +3 -3
- package/dist/components/preview/inputs/ColorInput.svelte +356 -14
- package/dist/components/preview/inputs/ColorInput.svelte.d.ts +1 -0
- package/dist/components/preview/inputs/FileInput.svelte +3 -3
- package/dist/components/preview/inputs/SegmentedInput.svelte +2 -1
- package/dist/components/preview/inputs/TileInput.svelte +1 -1
- package/dist/components/primitives/CalculateButton.svelte +2 -2
- package/dist/components/primitives/Callout.svelte +8 -6
- package/dist/components/primitives/Callout.svelte.d.ts +4 -4
- package/dist/components/primitives/DataTable.svelte +1 -1
- package/dist/components/primitives/ImageUploadField.svelte +1 -1
- package/dist/components/primitives/alert-dialog/alert-dialog-content.svelte +1 -1
- package/dist/components/primitives/badge/badge.svelte +2 -2
- package/dist/components/primitives/badge/badge.svelte.d.ts +6 -6
- package/dist/components/primitives/button/button.svelte +6 -7
- package/dist/components/primitives/button/button.svelte.d.ts +18 -18
- package/dist/components/primitives/card/card.svelte +1 -1
- package/dist/components/primitives/card/card.svelte.d.ts +1 -1
- package/dist/components/primitives/checkbox/checkbox.svelte +1 -1
- package/dist/components/primitives/command/command-input.svelte +1 -1
- package/dist/components/primitives/command/command-item.svelte +1 -1
- package/dist/components/primitives/command/command-link-item.svelte +1 -1
- package/dist/components/primitives/command/command.svelte +1 -1
- package/dist/components/primitives/context-menu/context-menu-checkbox-item.svelte +1 -1
- package/dist/components/primitives/context-menu/context-menu-content.svelte +1 -1
- package/dist/components/primitives/context-menu/context-menu-item.svelte +1 -1
- package/dist/components/primitives/context-menu/context-menu-radio-item.svelte +1 -1
- package/dist/components/primitives/context-menu/context-menu-sub-content.svelte +1 -1
- package/dist/components/primitives/context-menu/context-menu-sub-trigger.svelte +1 -1
- package/dist/components/primitives/dialog/dialog-content.svelte +2 -2
- package/dist/components/primitives/drawer/Drawer.svelte +1 -1
- package/dist/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte +31 -11
- package/dist/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte.d.ts +2 -0
- package/dist/components/primitives/dropdown-menu/dropdown-menu-content.svelte +5 -1
- package/dist/components/primitives/dropdown-menu/dropdown-menu-content.svelte.d.ts +2 -0
- package/dist/components/primitives/dropdown-menu/dropdown-menu-item.svelte +4 -2
- package/dist/components/primitives/dropdown-menu/dropdown-menu-item.svelte.d.ts +2 -1
- package/dist/components/primitives/dropdown-menu/dropdown-menu-radio-item.svelte +1 -1
- package/dist/components/primitives/dropdown-menu/dropdown-menu-shortcut.svelte +1 -1
- package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
- package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-trigger.svelte +1 -1
- package/dist/components/primitives/index.d.ts +1 -1
- package/dist/components/primitives/index.js +1 -1
- package/dist/components/primitives/input/input.svelte +2 -2
- package/dist/components/primitives/input-group/input-group.svelte +1 -1
- package/dist/components/primitives/popover/popover-content.svelte +1 -1
- package/dist/components/primitives/resizable/resizable-handle.svelte +2 -2
- package/dist/components/primitives/search/search.svelte +1 -1
- package/dist/components/primitives/select/select-content.svelte +1 -1
- package/dist/components/primitives/select/select-item.svelte +1 -1
- package/dist/components/primitives/select/select-trigger.svelte +1 -1
- package/dist/components/primitives/slider/slider.svelte +1 -1
- package/dist/components/primitives/switch/switch.svelte +1 -1
- package/dist/components/primitives/tabs/tabs-list.svelte +1 -1
- package/dist/components/primitives/tabs/tabs-list.svelte.d.ts +3 -3
- package/dist/components/primitives/tabs/tabs-trigger.svelte +1 -1
- package/dist/components/primitives/tabs/tabs-trigger.svelte.d.ts +3 -3
- package/dist/components/primitives/textarea/textarea.svelte +1 -1
- package/dist/components/primitives/toggle/toggle.svelte +6 -3
- package/dist/components/primitives/toggle/toggle.svelte.d.ts +10 -7
- package/dist/components/primitives/toggle-group/toggle-group-item.svelte +1 -1
- package/dist/components/primitives/toggle-group/toggle-group.svelte +1 -1
- package/dist/components/primitives/tooltip/index.d.ts +6 -0
- package/dist/components/primitives/tooltip/index.js +8 -0
- package/dist/components/primitives/tooltip/tooltip-content.svelte +34 -0
- package/dist/components/primitives/tooltip/tooltip-content.svelte.d.ts +10 -0
- package/dist/components/primitives/tooltip/tooltip-portal.svelte +7 -0
- package/dist/components/primitives/tooltip/tooltip-portal.svelte.d.ts +4 -0
- package/dist/components/primitives/tooltip/tooltip-provider.svelte +7 -0
- package/dist/components/primitives/tooltip/tooltip-provider.svelte.d.ts +4 -0
- package/dist/components/primitives/tooltip/tooltip-trigger.svelte +7 -0
- package/dist/components/primitives/tooltip/tooltip-trigger.svelte.d.ts +4 -0
- package/dist/components/primitives/tooltip/tooltip.svelte +7 -0
- package/dist/components/primitives/tooltip/tooltip.svelte.d.ts +4 -0
- package/dist/components/viewer/MeshMetadataPanel.svelte +1 -1
- package/dist/components/viewer/SceneManager.svelte +19 -19
- package/dist/components/viewer/Viewer.svelte +68 -157
- package/dist/components/viewer/ViewerDock.svelte +404 -0
- package/dist/components/viewer/ViewerDock.svelte.d.ts +42 -0
- package/dist/i18n/messages.d.ts +11 -0
- package/dist/i18n/messages.js +18 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/public.d.ts +1 -0
- package/dist/public.js +1 -0
- package/dist/schema/visibility-rules.d.ts +7 -1
- package/dist/schema/visibility-rules.js +17 -0
- package/dist/styles/base.css +81 -80
- package/dist/utils/color-space.d.ts +21 -0
- package/dist/utils/color-space.js +90 -0
- package/package.json +8 -9
- package/src/lib/components/compute/AppLayout.svelte +5 -3
- package/src/lib/components/compute/CollapsedPanelStrip.svelte +1 -1
- package/src/lib/components/compute/ParameterPresetManager.svelte +1 -1
- package/src/lib/components/compute/SolveMessages.svelte +2 -2
- package/src/lib/components/layout/FooterItemRenderer.svelte +1 -1
- package/src/lib/components/layout/PageFooter.svelte +2 -2
- package/src/lib/components/layout/PageHeader.svelte +1 -1
- package/src/lib/components/layout/SideNav.svelte +3 -3
- package/src/lib/components/layout/SubNav.svelte +2 -2
- package/src/lib/components/preview/ChartOutput.svelte +3 -3
- package/src/lib/components/preview/Group.svelte +7 -7
- package/src/lib/components/preview/ImageOutput.svelte +6 -6
- package/src/lib/components/preview/InputControl.svelte +1 -0
- package/src/lib/components/preview/OutputDisplay.svelte +8 -6
- package/src/lib/components/preview/TabBar.svelte +6 -4
- package/src/lib/components/preview/TabContent.svelte +1 -1
- package/src/lib/components/preview/TabLayout.svelte +3 -4
- package/src/lib/components/preview/inputs/ChecklistInput.svelte +3 -3
- package/src/lib/components/preview/inputs/ColorInput.svelte +356 -14
- package/src/lib/components/preview/inputs/FileInput.svelte +3 -3
- package/src/lib/components/preview/inputs/SegmentedInput.svelte +2 -1
- package/src/lib/components/preview/inputs/TileInput.svelte +1 -1
- package/src/lib/components/primitives/CalculateButton.svelte +2 -2
- package/src/lib/components/primitives/Callout.svelte +8 -6
- package/src/lib/components/primitives/DataTable.svelte +1 -1
- package/src/lib/components/primitives/ImageUploadField.svelte +1 -1
- package/src/lib/components/primitives/alert-dialog/alert-dialog-content.svelte +1 -1
- package/src/lib/components/primitives/badge/badge.svelte +2 -2
- package/src/lib/components/primitives/button/button.svelte +6 -7
- package/src/lib/components/primitives/card/card.svelte +1 -1
- package/src/lib/components/primitives/checkbox/checkbox.svelte +1 -1
- package/src/lib/components/primitives/command/command-input.svelte +1 -1
- package/src/lib/components/primitives/command/command-item.svelte +1 -1
- package/src/lib/components/primitives/command/command-link-item.svelte +1 -1
- package/src/lib/components/primitives/command/command.svelte +1 -1
- package/src/lib/components/primitives/context-menu/context-menu-checkbox-item.svelte +1 -1
- package/src/lib/components/primitives/context-menu/context-menu-content.svelte +1 -1
- package/src/lib/components/primitives/context-menu/context-menu-item.svelte +1 -1
- package/src/lib/components/primitives/context-menu/context-menu-radio-item.svelte +1 -1
- package/src/lib/components/primitives/context-menu/context-menu-sub-content.svelte +1 -1
- package/src/lib/components/primitives/context-menu/context-menu-sub-trigger.svelte +1 -1
- package/src/lib/components/primitives/dialog/dialog-content.svelte +2 -2
- package/src/lib/components/primitives/drawer/Drawer.svelte +1 -1
- package/src/lib/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte +31 -11
- package/src/lib/components/primitives/dropdown-menu/dropdown-menu-content.svelte +5 -1
- package/src/lib/components/primitives/dropdown-menu/dropdown-menu-item.svelte +4 -2
- package/src/lib/components/primitives/dropdown-menu/dropdown-menu-radio-item.svelte +1 -1
- package/src/lib/components/primitives/dropdown-menu/dropdown-menu-shortcut.svelte +1 -1
- package/src/lib/components/primitives/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
- package/src/lib/components/primitives/dropdown-menu/dropdown-menu-sub-trigger.svelte +1 -1
- package/src/lib/components/primitives/index.ts +1 -1
- package/src/lib/components/primitives/input/input.svelte +2 -2
- package/src/lib/components/primitives/input-group/input-group.svelte +1 -1
- package/src/lib/components/primitives/popover/popover-content.svelte +1 -1
- package/src/lib/components/primitives/resizable/resizable-handle.svelte +2 -2
- package/src/lib/components/primitives/search/search.svelte +1 -1
- package/src/lib/components/primitives/select/select-content.svelte +1 -1
- package/src/lib/components/primitives/select/select-item.svelte +1 -1
- package/src/lib/components/primitives/select/select-trigger.svelte +1 -1
- package/src/lib/components/primitives/slider/slider.svelte +1 -1
- package/src/lib/components/primitives/switch/switch.svelte +1 -1
- package/src/lib/components/primitives/tabs/tabs-list.svelte +1 -1
- package/src/lib/components/primitives/tabs/tabs-trigger.svelte +1 -1
- package/src/lib/components/primitives/textarea/textarea.svelte +1 -1
- package/src/lib/components/primitives/toggle/toggle.svelte +6 -3
- package/src/lib/components/primitives/toggle-group/toggle-group-item.svelte +1 -1
- package/src/lib/components/primitives/toggle-group/toggle-group.svelte +1 -1
- package/src/lib/components/primitives/tooltip/index.ts +19 -0
- package/src/lib/components/primitives/tooltip/tooltip-content.svelte +34 -0
- package/src/lib/components/primitives/tooltip/tooltip-portal.svelte +7 -0
- package/src/lib/components/primitives/tooltip/tooltip-provider.svelte +7 -0
- package/src/lib/components/primitives/tooltip/tooltip-trigger.svelte +7 -0
- package/src/lib/components/primitives/tooltip/tooltip.svelte +7 -0
- package/src/lib/components/viewer/MeshMetadataPanel.svelte +1 -1
- package/src/lib/components/viewer/SceneManager.svelte +19 -19
- package/src/lib/components/viewer/Viewer.svelte +68 -157
- package/src/lib/components/viewer/ViewerDock.svelte +404 -0
- package/src/lib/i18n/messages.ts +29 -0
- package/src/lib/index.ts +1 -0
- package/src/lib/public.ts +1 -0
- package/src/lib/schema/visibility-rules.ts +27 -1
- package/src/lib/styles/base.css +81 -80
- package/src/lib/utils/color-space.ts +107 -0
- package/dist/components/primitives/theme-switcher/ThemeSwitcher.svelte +0 -39
- package/dist/components/primitives/theme-switcher/ThemeSwitcher.svelte.d.ts +0 -3
- package/dist/components/primitives/theme-switcher/index.d.ts +0 -1
- package/dist/components/primitives/theme-switcher/index.js +0 -1
- package/dist/styles/themes/cyberpunk.css +0 -98
- package/dist/styles/themes/neutral.css +0 -72
- package/dist/styles/themes/ocean.css +0 -75
- package/dist/styles/themes/selva.css +0 -105
- package/dist/theme/themeStore.svelte.d.ts +0 -12
- package/dist/theme/themeStore.svelte.js +0 -40
- package/dist/theme/themes.d.ts +0 -5
- package/dist/theme/themes.js +0 -13
- package/src/lib/components/primitives/theme-switcher/ThemeSwitcher.svelte +0 -39
- package/src/lib/components/primitives/theme-switcher/index.ts +0 -1
- package/src/lib/styles/themes/cyberpunk.css +0 -98
- package/src/lib/styles/themes/neutral.css +0 -72
- package/src/lib/styles/themes/ocean.css +0 -75
- package/src/lib/styles/themes/selva.css +0 -105
- package/src/lib/theme/themeStore.svelte.ts +0 -50
- package/src/lib/theme/themes.ts +0 -18
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { MediaQuery } from 'svelte/reactivity';
|
|
3
|
+
import {
|
|
4
|
+
Camera,
|
|
5
|
+
Grid3x3,
|
|
6
|
+
Layers,
|
|
7
|
+
LocateFixed,
|
|
8
|
+
Maximize,
|
|
9
|
+
Minimize,
|
|
10
|
+
Moon,
|
|
11
|
+
Ellipsis,
|
|
12
|
+
Palette,
|
|
13
|
+
Ruler,
|
|
14
|
+
Settings2,
|
|
15
|
+
Spline,
|
|
16
|
+
Cuboid
|
|
17
|
+
} from '@lucide/svelte';
|
|
18
|
+
import type { CameraProjection, Look, ViewPreset } from '@selvajs/visualization/render';
|
|
19
|
+
import * as DropdownMenu from '$lib/components/primitives/dropdown-menu/index.js';
|
|
20
|
+
import type { ViewerMessages } from '$lib/i18n/messages';
|
|
21
|
+
|
|
22
|
+
interface Props {
|
|
23
|
+
t: ViewerMessages;
|
|
24
|
+
/** Fades the dock out while a host drawer covers the viewer. */
|
|
25
|
+
hidden?: boolean;
|
|
26
|
+
showDisplay: boolean;
|
|
27
|
+
showGrid: boolean;
|
|
28
|
+
showSceneManager: boolean;
|
|
29
|
+
showScreenshot: boolean;
|
|
30
|
+
showFullscreen: boolean;
|
|
31
|
+
styleOptions: { look: Look; label: string }[];
|
|
32
|
+
viewPresets: { preset: ViewPreset; label: string }[];
|
|
33
|
+
renderStyle: Look;
|
|
34
|
+
nightOn: boolean;
|
|
35
|
+
edgesVisible: boolean;
|
|
36
|
+
measureActive: boolean;
|
|
37
|
+
gridVisible: boolean;
|
|
38
|
+
sceneManagerOpen: boolean;
|
|
39
|
+
projection: CameraProjection;
|
|
40
|
+
isFullscreen: boolean;
|
|
41
|
+
onSetLook: (look: Look) => void;
|
|
42
|
+
onToggleNight: () => void;
|
|
43
|
+
onToggleEdges: () => void;
|
|
44
|
+
onToggleMeasure: () => void;
|
|
45
|
+
onToggleGrid: () => void;
|
|
46
|
+
onToggleSceneManager: () => void;
|
|
47
|
+
onToggleProjection: () => void;
|
|
48
|
+
onSetView: (preset: ViewPreset) => void;
|
|
49
|
+
onFit: () => void;
|
|
50
|
+
onScreenshot: () => void;
|
|
51
|
+
onToggleFullscreen: () => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let {
|
|
55
|
+
t,
|
|
56
|
+
hidden = false,
|
|
57
|
+
showDisplay,
|
|
58
|
+
showGrid,
|
|
59
|
+
showSceneManager,
|
|
60
|
+
showScreenshot,
|
|
61
|
+
showFullscreen,
|
|
62
|
+
styleOptions,
|
|
63
|
+
viewPresets,
|
|
64
|
+
renderStyle,
|
|
65
|
+
nightOn,
|
|
66
|
+
edgesVisible,
|
|
67
|
+
measureActive,
|
|
68
|
+
gridVisible,
|
|
69
|
+
sceneManagerOpen,
|
|
70
|
+
projection,
|
|
71
|
+
isFullscreen,
|
|
72
|
+
onSetLook,
|
|
73
|
+
onToggleNight,
|
|
74
|
+
onToggleEdges,
|
|
75
|
+
onToggleMeasure,
|
|
76
|
+
onToggleGrid,
|
|
77
|
+
onToggleSceneManager,
|
|
78
|
+
onToggleProjection,
|
|
79
|
+
onSetView,
|
|
80
|
+
onFit,
|
|
81
|
+
onScreenshot,
|
|
82
|
+
onToggleFullscreen
|
|
83
|
+
}: Props = $props();
|
|
84
|
+
|
|
85
|
+
// A hairline pill at the bottom edge is the iOS home indicator on a phone, and swiping it leaves
|
|
86
|
+
// the app. Touch gets a visible round button instead.
|
|
87
|
+
const touch = new MediaQuery('(pointer: coarse)');
|
|
88
|
+
const reducedMotion = new MediaQuery('(prefers-reduced-motion: reduce)');
|
|
89
|
+
|
|
90
|
+
const COLLAPSED = $derived(touch.current ? { w: 44, h: 44 } : { w: 72, h: 8 });
|
|
91
|
+
const EXPANDED_H = $derived(touch.current ? 54 : 46);
|
|
92
|
+
const PAD = 4;
|
|
93
|
+
|
|
94
|
+
let dockEl: HTMLDivElement | undefined = $state();
|
|
95
|
+
let rowWidth = $state(0);
|
|
96
|
+
let engaged = $state(false);
|
|
97
|
+
// Set by an outside press: the dock was dismissed, so it shrinks faster.
|
|
98
|
+
let dismissed = $state(false);
|
|
99
|
+
let menusOpen = $state(0);
|
|
100
|
+
let focusWithin = $state(false);
|
|
101
|
+
let idleTimer: ReturnType<typeof setTimeout> | undefined;
|
|
102
|
+
|
|
103
|
+
const wantOpen = $derived(engaged || menusOpen > 0 || focusWithin);
|
|
104
|
+
|
|
105
|
+
// Opening leads: the pill grows first and the buttons pop in behind it. Closing mirrors it: the
|
|
106
|
+
// buttons fade first, then the pill shrinks. Without the delay on close the row is clipped mid-fade.
|
|
107
|
+
const EASE = 'cubic-bezier(0.32, 0.72, 0, 1)';
|
|
108
|
+
const containerStyle = $derived.by(() => {
|
|
109
|
+
const bottom = touch.current ? 'max(1rem,env(safe-area-inset-bottom))' : '0.75rem';
|
|
110
|
+
const geometry = wantOpen
|
|
111
|
+
? `width:${rowWidth + PAD * 2 + 2}px;height:${EXPANDED_H}px;border-radius:14px;padding:${PAD}px`
|
|
112
|
+
: `width:${COLLAPSED.w}px;height:${COLLAPSED.h}px;border-radius:${touch.current ? 22 : 99}px;padding:0px`;
|
|
113
|
+
const size = `bottom:${bottom};${geometry}`;
|
|
114
|
+
if (reducedMotion.current) return size;
|
|
115
|
+
const ms = wantOpen ? 420 : dismissed ? 130 : 170;
|
|
116
|
+
const delay = wantOpen || dismissed ? 0 : 20;
|
|
117
|
+
const props = ['width', 'height', 'border-radius', 'padding'];
|
|
118
|
+
return `${size};transition:${props.map((p) => `${p} ${ms}ms ${EASE} ${delay}ms`).join(',')},opacity 200ms ease`;
|
|
119
|
+
});
|
|
120
|
+
const itemStyle = (index: number) => {
|
|
121
|
+
if (reducedMotion.current) return `opacity:${wantOpen ? 1 : 0}`;
|
|
122
|
+
const delay = 110 + index * 35;
|
|
123
|
+
return wantOpen
|
|
124
|
+
? `opacity:1;transform:none;transition:opacity 240ms ease ${delay}ms,transform 420ms cubic-bezier(0.34,1.45,0.5,1) ${delay}ms`
|
|
125
|
+
: 'opacity:0;transform:translateY(6px) scale(0.85);transition:opacity 100ms ease,transform 100ms ease';
|
|
126
|
+
};
|
|
127
|
+
// The touch button's settings glyph hands over to the row.
|
|
128
|
+
const glyphStyle = $derived(
|
|
129
|
+
`opacity:${touch.current && !wantOpen ? 1 : 0};${reducedMotion.current ? '' : `transition:opacity ${wantOpen ? 100 : 100}ms ease ${wantOpen || dismissed ? 0 : 60}ms`}`
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
function scheduleCollapse(delay = touch.current ? 3500 : 150) {
|
|
133
|
+
clearTimeout(idleTimer);
|
|
134
|
+
idleTimer = setTimeout(() => (engaged = false), delay);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function engage() {
|
|
138
|
+
engaged = true;
|
|
139
|
+
dismissed = false;
|
|
140
|
+
clearTimeout(idleTimer);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function handlePointerMove(event: PointerEvent) {
|
|
144
|
+
if (!dockEl || touch.current || event.pointerType === 'touch') return;
|
|
145
|
+
// Orbiting or box-dragging with the button down should never pop the dock open.
|
|
146
|
+
if (event.buttons) return;
|
|
147
|
+
const host = dockEl.offsetParent;
|
|
148
|
+
if (!host || !(event.target instanceof Node) || !host.contains(event.target)) {
|
|
149
|
+
if (engaged) scheduleCollapse();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const r = dockEl.getBoundingClientRect();
|
|
153
|
+
const near =
|
|
154
|
+
event.clientY > r.top - 36 && Math.abs(event.clientX - (r.left + r.width / 2)) < 110;
|
|
155
|
+
if (near) engage();
|
|
156
|
+
else if (engaged) scheduleCollapse();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function handleWindowPointerDown(event: PointerEvent) {
|
|
160
|
+
if (!engaged || !dockEl || !(event.target instanceof Node)) return;
|
|
161
|
+
if (!dockEl.contains(event.target)) {
|
|
162
|
+
clearTimeout(idleTimer);
|
|
163
|
+
dismissed = true;
|
|
164
|
+
engaged = false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function openFromCollapsed() {
|
|
169
|
+
if (wantOpen) return;
|
|
170
|
+
engage();
|
|
171
|
+
if (touch.current) scheduleCollapse();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Any use of the open dock on touch pushes the idle collapse back.
|
|
175
|
+
function keepAlive() {
|
|
176
|
+
if (touch.current && engaged) scheduleCollapse();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function trackMenu(open: boolean) {
|
|
180
|
+
menusOpen += open ? 1 : -1;
|
|
181
|
+
if (!open && !touch.current) scheduleCollapse(250);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Buttons in the row, in order, so the stagger index stays right when some are hidden.
|
|
185
|
+
const buttons = $derived(
|
|
186
|
+
[
|
|
187
|
+
showDisplay && 'look',
|
|
188
|
+
showSceneManager && 'scene',
|
|
189
|
+
'measure',
|
|
190
|
+
'fit',
|
|
191
|
+
showFullscreen && 'full',
|
|
192
|
+
'more'
|
|
193
|
+
].filter(Boolean) as string[]
|
|
194
|
+
);
|
|
195
|
+
const idx = (id: string) => buttons.indexOf(id);
|
|
196
|
+
|
|
197
|
+
const BTN =
|
|
198
|
+
'relative grid size-9.5 shrink-0 place-items-center rounded-lg text-muted-foreground outline-none transition-colors hover:bg-hover hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring aria-pressed:text-foreground data-[state=open]:bg-selected data-[state=open]:text-foreground pointer-coarse:size-11';
|
|
199
|
+
const DOT =
|
|
200
|
+
'aria-pressed:after:absolute aria-pressed:after:bottom-0.5 aria-pressed:after:size-1 aria-pressed:after:rounded-full aria-pressed:after:bg-primary';
|
|
201
|
+
</script>
|
|
202
|
+
|
|
203
|
+
<!-- eslint-disable shadcn/no-inline-styles -- the pill animates between measured pixel sizes and staggered per-item delays; no class can express those -->
|
|
204
|
+
|
|
205
|
+
<svelte:window onpointermove={handlePointerMove} onpointerdown={handleWindowPointerDown} />
|
|
206
|
+
|
|
207
|
+
<!-- Resting state is a pill; it grows into the row. Only the container is clickable while collapsed. -->
|
|
208
|
+
<div
|
|
209
|
+
bind:this={dockEl}
|
|
210
|
+
role="toolbar"
|
|
211
|
+
aria-label={t.toolsMenu}
|
|
212
|
+
tabindex="-1"
|
|
213
|
+
class="shadow-lg backdrop-blur-md absolute left-1/2 z-50 -translate-x-1/2 overflow-hidden border {hidden
|
|
214
|
+
? 'pointer-events-none opacity-0'
|
|
215
|
+
: ''} {wantOpen
|
|
216
|
+
? 'bg-background/70'
|
|
217
|
+
: touch.current
|
|
218
|
+
? 'cursor-pointer border-border bg-background/90'
|
|
219
|
+
: nightOn
|
|
220
|
+
? 'border-white/50 bg-white/40 hover:bg-white/65 cursor-pointer'
|
|
221
|
+
: 'cursor-pointer border-border-strong bg-faint-foreground hover:bg-muted-foreground'}"
|
|
222
|
+
style={containerStyle}
|
|
223
|
+
onclick={openFromCollapsed}
|
|
224
|
+
onkeydown={(e) => (e.key === 'Escape' ? (engaged = false) : undefined)}
|
|
225
|
+
onfocusin={() => (focusWithin = true)}
|
|
226
|
+
onfocusout={(e) => {
|
|
227
|
+
if (!dockEl?.contains(e.relatedTarget as Node | null)) focusWithin = false;
|
|
228
|
+
}}
|
|
229
|
+
onpointerdown={keepAlive}
|
|
230
|
+
>
|
|
231
|
+
{#if touch.current}
|
|
232
|
+
<span
|
|
233
|
+
class="inset-0 pointer-events-none absolute grid place-items-center text-foreground"
|
|
234
|
+
style={glyphStyle}
|
|
235
|
+
aria-hidden="true"
|
|
236
|
+
>
|
|
237
|
+
<Settings2 class="size-5" />
|
|
238
|
+
</span>
|
|
239
|
+
{/if}
|
|
240
|
+
|
|
241
|
+
<div
|
|
242
|
+
bind:offsetWidth={rowWidth}
|
|
243
|
+
class="gap-0.5 mx-auto flex w-max items-center"
|
|
244
|
+
inert={!wantOpen}
|
|
245
|
+
>
|
|
246
|
+
{#if showDisplay}
|
|
247
|
+
<div style={itemStyle(idx('look'))}>
|
|
248
|
+
<DropdownMenu.Root onOpenChange={trackMenu}>
|
|
249
|
+
<DropdownMenu.Trigger>
|
|
250
|
+
{#snippet child({ props })}
|
|
251
|
+
<button {...props} class={BTN} title={t.display} aria-label={t.display}>
|
|
252
|
+
<Palette class="size-4.5" />
|
|
253
|
+
</button>
|
|
254
|
+
{/snippet}
|
|
255
|
+
</DropdownMenu.Trigger>
|
|
256
|
+
<DropdownMenu.Content
|
|
257
|
+
side="top"
|
|
258
|
+
align="center"
|
|
259
|
+
sideOffset={12}
|
|
260
|
+
variant="glass"
|
|
261
|
+
class="min-w-52 z-10001 w-auto whitespace-nowrap"
|
|
262
|
+
>
|
|
263
|
+
<DropdownMenu.RadioGroup
|
|
264
|
+
value={renderStyle}
|
|
265
|
+
onValueChange={(look) => onSetLook(look as Look)}
|
|
266
|
+
>
|
|
267
|
+
{#each styleOptions as { look, label } (look)}
|
|
268
|
+
<DropdownMenu.RadioItem value={look} closeOnSelect={false}>
|
|
269
|
+
{label}
|
|
270
|
+
</DropdownMenu.RadioItem>
|
|
271
|
+
{/each}
|
|
272
|
+
</DropdownMenu.RadioGroup>
|
|
273
|
+
<DropdownMenu.Separator />
|
|
274
|
+
<DropdownMenu.CheckboxItem
|
|
275
|
+
switch
|
|
276
|
+
checked={edgesVisible}
|
|
277
|
+
onCheckedChange={onToggleEdges}
|
|
278
|
+
>
|
|
279
|
+
<Spline />
|
|
280
|
+
{t.edges}
|
|
281
|
+
<DropdownMenu.Shortcut class="pointer-coarse:hidden">E</DropdownMenu.Shortcut>
|
|
282
|
+
</DropdownMenu.CheckboxItem>
|
|
283
|
+
</DropdownMenu.Content>
|
|
284
|
+
</DropdownMenu.Root>
|
|
285
|
+
</div>
|
|
286
|
+
{/if}
|
|
287
|
+
|
|
288
|
+
{#if showSceneManager}
|
|
289
|
+
<div style={itemStyle(idx('scene'))}>
|
|
290
|
+
<button
|
|
291
|
+
class="{BTN} {DOT}"
|
|
292
|
+
title="{t.sceneManager} (S)"
|
|
293
|
+
aria-label={t.sceneManager}
|
|
294
|
+
aria-pressed={sceneManagerOpen}
|
|
295
|
+
onclick={onToggleSceneManager}
|
|
296
|
+
>
|
|
297
|
+
<Layers class="size-4.5" />
|
|
298
|
+
</button>
|
|
299
|
+
</div>
|
|
300
|
+
{/if}
|
|
301
|
+
|
|
302
|
+
<div style={itemStyle(idx('measure'))}>
|
|
303
|
+
<button
|
|
304
|
+
class="{BTN} {DOT}"
|
|
305
|
+
title="{t.measure} (M)"
|
|
306
|
+
aria-label={t.measure}
|
|
307
|
+
aria-pressed={measureActive}
|
|
308
|
+
onclick={onToggleMeasure}
|
|
309
|
+
>
|
|
310
|
+
<Ruler class="size-4.5" />
|
|
311
|
+
</button>
|
|
312
|
+
</div>
|
|
313
|
+
|
|
314
|
+
<div style={itemStyle(idx('fit'))}>
|
|
315
|
+
<button class={BTN} title="{t.fitToView} (F)" aria-label="{t.fitToView} (F)" onclick={onFit}>
|
|
316
|
+
<LocateFixed class="size-4.5" />
|
|
317
|
+
</button>
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
{#if showFullscreen}
|
|
321
|
+
<div style={itemStyle(idx('full'))}>
|
|
322
|
+
<button
|
|
323
|
+
class={BTN}
|
|
324
|
+
title={isFullscreen ? t.exitFullscreen : t.fullscreen}
|
|
325
|
+
aria-label={isFullscreen ? t.exitFullscreen : t.fullscreen}
|
|
326
|
+
onclick={onToggleFullscreen}
|
|
327
|
+
>
|
|
328
|
+
{#if isFullscreen}<Minimize class="size-4.5" />{:else}<Maximize class="size-4.5" />{/if}
|
|
329
|
+
</button>
|
|
330
|
+
</div>
|
|
331
|
+
{/if}
|
|
332
|
+
|
|
333
|
+
<div style={itemStyle(idx('more'))}>
|
|
334
|
+
<DropdownMenu.Root onOpenChange={trackMenu}>
|
|
335
|
+
<DropdownMenu.Trigger>
|
|
336
|
+
{#snippet child({ props })}
|
|
337
|
+
<button {...props} class={BTN} title={t.toolsMenu} aria-label={t.toolsMenu}>
|
|
338
|
+
<Ellipsis class="size-4.5" />
|
|
339
|
+
</button>
|
|
340
|
+
{/snippet}
|
|
341
|
+
</DropdownMenu.Trigger>
|
|
342
|
+
<DropdownMenu.Content
|
|
343
|
+
side="top"
|
|
344
|
+
align="end"
|
|
345
|
+
sideOffset={12}
|
|
346
|
+
variant="glass"
|
|
347
|
+
class="min-w-60 z-10001 w-auto whitespace-nowrap"
|
|
348
|
+
>
|
|
349
|
+
<DropdownMenu.Label>{t.views}</DropdownMenu.Label>
|
|
350
|
+
<div class="gap-1 px-1 pb-1 grid grid-cols-4">
|
|
351
|
+
{#each viewPresets as { preset, label } (preset)}
|
|
352
|
+
<DropdownMenu.Item
|
|
353
|
+
closeOnSelect={false}
|
|
354
|
+
onSelect={() => onSetView(preset)}
|
|
355
|
+
variant="chip"
|
|
356
|
+
>
|
|
357
|
+
{label}
|
|
358
|
+
</DropdownMenu.Item>
|
|
359
|
+
{/each}
|
|
360
|
+
</div>
|
|
361
|
+
|
|
362
|
+
<DropdownMenu.Separator />
|
|
363
|
+
|
|
364
|
+
<DropdownMenu.CheckboxItem
|
|
365
|
+
switch
|
|
366
|
+
checked={projection === 'orthographic'}
|
|
367
|
+
onCheckedChange={onToggleProjection}
|
|
368
|
+
>
|
|
369
|
+
<Cuboid />
|
|
370
|
+
{t.switchToOrthographic}
|
|
371
|
+
<DropdownMenu.Shortcut class="pointer-coarse:hidden">O</DropdownMenu.Shortcut>
|
|
372
|
+
</DropdownMenu.CheckboxItem>
|
|
373
|
+
|
|
374
|
+
{#if showGrid}
|
|
375
|
+
<DropdownMenu.CheckboxItem switch checked={gridVisible} onCheckedChange={onToggleGrid}>
|
|
376
|
+
<Grid3x3 />
|
|
377
|
+
{t.grid}
|
|
378
|
+
<DropdownMenu.Shortcut class="pointer-coarse:hidden">G</DropdownMenu.Shortcut>
|
|
379
|
+
</DropdownMenu.CheckboxItem>
|
|
380
|
+
{/if}
|
|
381
|
+
|
|
382
|
+
{#if showDisplay}
|
|
383
|
+
<DropdownMenu.CheckboxItem switch checked={nightOn} onCheckedChange={onToggleNight}>
|
|
384
|
+
<Moon />
|
|
385
|
+
{t.nightMode}
|
|
386
|
+
<DropdownMenu.Shortcut class="pointer-coarse:hidden">N</DropdownMenu.Shortcut>
|
|
387
|
+
</DropdownMenu.CheckboxItem>
|
|
388
|
+
{/if}
|
|
389
|
+
|
|
390
|
+
{#if showScreenshot}
|
|
391
|
+
<DropdownMenu.Separator />
|
|
392
|
+
{/if}
|
|
393
|
+
|
|
394
|
+
{#if showScreenshot}
|
|
395
|
+
<DropdownMenu.Item onSelect={onScreenshot}>
|
|
396
|
+
<Camera />
|
|
397
|
+
{t.screenshot}
|
|
398
|
+
</DropdownMenu.Item>
|
|
399
|
+
{/if}
|
|
400
|
+
</DropdownMenu.Content>
|
|
401
|
+
</DropdownMenu.Root>
|
|
402
|
+
</div>
|
|
403
|
+
</div>
|
|
404
|
+
</div>
|
package/src/lib/i18n/messages.ts
CHANGED
|
@@ -41,6 +41,7 @@ export interface ViewerMessages {
|
|
|
41
41
|
lookStudio: string;
|
|
42
42
|
lookShowcase: string;
|
|
43
43
|
lookNight: string;
|
|
44
|
+
nightMode: string;
|
|
44
45
|
lookArctic: string;
|
|
45
46
|
lookXray: string;
|
|
46
47
|
lookLineart: string;
|
|
@@ -132,6 +133,16 @@ export interface ViewerMessages {
|
|
|
132
133
|
/** `{max}` is replaced with the field's character limit. */
|
|
133
134
|
maxLength: string;
|
|
134
135
|
invalidFormat: string;
|
|
136
|
+
/** Trigger label; `{color}` is the current hex value. */
|
|
137
|
+
colorPick: string;
|
|
138
|
+
colorArea: string;
|
|
139
|
+
/** `{s}` and `{v}` are percentages. */
|
|
140
|
+
colorAreaValue: string;
|
|
141
|
+
colorHue: string;
|
|
142
|
+
colorHex: string;
|
|
143
|
+
colorHexInvalid: string;
|
|
144
|
+
colorPresets: string;
|
|
145
|
+
colorEyedropper: string;
|
|
135
146
|
invalidPattern: string;
|
|
136
147
|
|
|
137
148
|
// Geometry input (Rhino only)
|
|
@@ -270,6 +281,7 @@ const en: ViewerMessages = {
|
|
|
270
281
|
lookStudio: 'Studio',
|
|
271
282
|
lookShowcase: 'Showcase',
|
|
272
283
|
lookNight: 'Night',
|
|
284
|
+
nightMode: 'Night mode',
|
|
273
285
|
lookArctic: 'Arctic',
|
|
274
286
|
lookXray: 'X-Ray',
|
|
275
287
|
lookLineart: 'Line Art',
|
|
@@ -342,6 +354,14 @@ const en: ViewerMessages = {
|
|
|
342
354
|
fineDragHint: 'Hold Shift to drag finely',
|
|
343
355
|
maxLength: 'Maximum {max} characters allowed',
|
|
344
356
|
invalidFormat: 'Invalid format',
|
|
357
|
+
colorPick: 'Choose color, currently {color}',
|
|
358
|
+
colorArea: 'Saturation and brightness',
|
|
359
|
+
colorAreaValue: 'Saturation {s}%, brightness {v}%',
|
|
360
|
+
colorHue: 'Hue',
|
|
361
|
+
colorHex: 'Hex color',
|
|
362
|
+
colorHexInvalid: 'Enter a hex color such as #2f6fd6',
|
|
363
|
+
colorPresets: 'Presets',
|
|
364
|
+
colorEyedropper: 'Pick a color from the screen',
|
|
345
365
|
invalidPattern: 'Invalid validation pattern configured',
|
|
346
366
|
|
|
347
367
|
geometryPickAnyOne: 'Pick an object',
|
|
@@ -464,6 +484,7 @@ const de: ViewerMessages = {
|
|
|
464
484
|
lookStudio: 'Studio',
|
|
465
485
|
lookShowcase: 'Präsentation',
|
|
466
486
|
lookNight: 'Nacht',
|
|
487
|
+
nightMode: 'Nachtmodus',
|
|
467
488
|
lookArctic: 'Arktis',
|
|
468
489
|
lookXray: 'Röntgen',
|
|
469
490
|
lookLineart: 'Strichzeichnung',
|
|
@@ -537,6 +558,14 @@ const de: ViewerMessages = {
|
|
|
537
558
|
fineDragHint: 'Umschalttaste halten für feine Schritte',
|
|
538
559
|
maxLength: 'Höchstens {max} Zeichen erlaubt',
|
|
539
560
|
invalidFormat: 'Ungültiges Format',
|
|
561
|
+
colorPick: 'Farbe wählen, aktuell {color}',
|
|
562
|
+
colorArea: 'Sättigung und Helligkeit',
|
|
563
|
+
colorAreaValue: 'Sättigung {s} %, Helligkeit {v} %',
|
|
564
|
+
colorHue: 'Farbton',
|
|
565
|
+
colorHex: 'Hex-Farbe',
|
|
566
|
+
colorHexInvalid: 'Hex-Farbe eingeben, z. B. #2f6fd6',
|
|
567
|
+
colorPresets: 'Vorgaben',
|
|
568
|
+
colorEyedropper: 'Farbe vom Bildschirm aufnehmen',
|
|
540
569
|
invalidPattern: 'Ungültiges Prüfmuster hinterlegt',
|
|
541
570
|
|
|
542
571
|
geometryPickAnyOne: 'Objekt wählen',
|
package/src/lib/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ export { default as Viewer } from './components/viewer/Viewer.svelte';
|
|
|
19
19
|
export * from './schema/defaults';
|
|
20
20
|
export * from './schema/dynamic-value-list';
|
|
21
21
|
export * from './schema/traversal';
|
|
22
|
+
export { visibleInputItems } from './schema/visibility-rules';
|
|
22
23
|
export {
|
|
23
24
|
type UIHost,
|
|
24
25
|
type ElementKind,
|
package/src/lib/public.ts
CHANGED
|
@@ -80,6 +80,7 @@ export {
|
|
|
80
80
|
export * from './schema/defaults';
|
|
81
81
|
export * from './schema/traversal';
|
|
82
82
|
export * from './schema/dynamic-value-list';
|
|
83
|
+
export { visibleInputItems } from './schema/visibility-rules';
|
|
83
84
|
|
|
84
85
|
// UI-facing runtime types (not from schema)
|
|
85
86
|
export type { ActionButton } from './types/actionButton';
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
InputLayoutItem,
|
|
2
3
|
LayoutItem,
|
|
3
4
|
OutputLayoutItem,
|
|
4
5
|
VisibilityRule,
|
|
5
6
|
VisibilityCondition,
|
|
6
7
|
GroupVisibilityCondition,
|
|
7
|
-
TabConfig
|
|
8
|
+
TabConfig,
|
|
9
|
+
UISchema
|
|
8
10
|
} from '@selvajs/schemas';
|
|
9
11
|
import { isFileData } from '../utils/file-download';
|
|
10
12
|
|
|
@@ -148,6 +150,30 @@ export function isTabVisible(tab: TabConfig, values: Record<string, unknown>): b
|
|
|
148
150
|
);
|
|
149
151
|
}
|
|
150
152
|
|
|
153
|
+
/**
|
|
154
|
+
* The input items the form shows for these values: each passes its own condition, inside a
|
|
155
|
+
* shown group and, when tabbed, a shown tab. For a host that repeats the form back, such as a
|
|
156
|
+
* summary before a commit, without re-deriving the renderer's rules.
|
|
157
|
+
*/
|
|
158
|
+
export function visibleInputItems(
|
|
159
|
+
schema: UISchema,
|
|
160
|
+
values: Record<string, unknown>
|
|
161
|
+
): InputLayoutItem[] {
|
|
162
|
+
const layout = schema?.layout;
|
|
163
|
+
const groups =
|
|
164
|
+
layout?.type === 'tabbed'
|
|
165
|
+
? (layout.tabs ?? []).filter((t) => isTabVisible(t, values)).flatMap((t) => t.groups ?? [])
|
|
166
|
+
: layout?.type === 'flat'
|
|
167
|
+
? (layout.groups ?? [])
|
|
168
|
+
: [];
|
|
169
|
+
return groups
|
|
170
|
+
.filter((g) => evaluateGroupVisibility(g, values))
|
|
171
|
+
.flatMap((g) => g.items ?? [])
|
|
172
|
+
.filter(
|
|
173
|
+
(i): i is InputLayoutItem => i.type === 'input' && evaluateVisibility(i, values).visible
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
151
177
|
/**
|
|
152
178
|
* A linebreak is always "visible" but renders as a divider only, so a group left with
|
|
153
179
|
* nothing but separators counts as empty.
|