Skip to content

Commit 416720a

Browse files
committed
move toolbar ui
1 parent c9f713d commit 416720a

File tree

4 files changed

+219
-73
lines changed

4 files changed

+219
-73
lines changed

packages/svelte/src/toolbar/ToolBar.svelte

Lines changed: 192 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,136 @@
11
<script>
2-
import { mount, onMount, tick, unmount } from 'svelte';
2+
import { onMount, tick } from 'svelte';
33
import Icon from './Icon.svelte';
4-
import { SvelteMap } from 'svelte/reactivity';
54
6-
let {
7-
/** @type import('./public.d.ts').ResolvedConfig */
8-
config
9-
} = $props();
10-
let open = $state(false); // Default to closed
5+
/** @type {{ config: import('./public.d.ts').ResolvedConfig }} */
6+
let { config } = $props();
7+
let open = $state(false);
118
12-
/** @type {import('svelte').Component} */
13-
let ActiveComponent = $state(null);
9+
/** @type {import('svelte').Component | undefined} */
10+
let ActiveComponent = $state();
1411
/** @type {HTMLElement} */
15-
let toolbar;
12+
let toolbarSelector;
1613
/** @type {HTMLElement} */
1714
let toolbarPanels;
15+
/** @type {HTMLElement} */
16+
let toolbarTools;
1817
1918
let dragOffsetX = 0;
2019
let dragOffsetY = 0;
20+
let toolbarScreenOffset = 20;
21+
22+
/** @type {import('./public').Config['position']} */
23+
let computedPosition = config.position;
2124
2225
onMount(() => {
23-
toolbar.style.right = '20px';
24-
toolbar.style.bottom = '20px';
25-
recalculate_toolbar_panel_position();
26+
computedPosition = config.position;
27+
layout_selector();
28+
});
29+
30+
$effect(() => {
31+
computedPosition = config.position;
32+
layout_selector();
33+
layout_toolbar();
2634
});
2735
36+
function layout_selector() {
37+
const rect = toolbarSelector.getBoundingClientRect();
38+
39+
let x = 0;
40+
let y = 0;
41+
42+
switch (computedPosition) {
43+
case 'top-left':
44+
x = toolbarScreenOffset;
45+
y = toolbarScreenOffset;
46+
break;
47+
48+
case 'top-right':
49+
x = window.innerWidth - rect.width - toolbarScreenOffset;
50+
y = toolbarScreenOffset;
51+
break;
52+
53+
case 'bottom-right':
54+
x = window.innerWidth - rect.width - toolbarScreenOffset;
55+
y = window.innerHeight - rect.height - toolbarScreenOffset;
56+
break;
57+
58+
case 'bottom-left':
59+
x = toolbarScreenOffset;
60+
y = window.innerHeight - rect.height - toolbarScreenOffset;
61+
break;
62+
63+
default:
64+
break;
65+
}
66+
67+
toolbarSelector.style.left = x + 'px';
68+
toolbarSelector.style.top = y + 'px';
69+
}
70+
71+
function layout_toolbar() {
72+
const toolbarSelectorRect = toolbarSelector.getBoundingClientRect();
73+
const toolbarToolsRect = toolbarTools.getBoundingClientRect();
74+
const toolbarPanelsRect = toolbarPanels.getBoundingClientRect();
75+
76+
switch (computedPosition) {
77+
case 'top-left':
78+
toolbarTools.style.top = toolbarSelector.style.top;
79+
toolbarTools.style.left =
80+
parseFloat(toolbarSelector.style.left) + toolbarSelectorRect.width + 'px';
81+
82+
toolbarPanels.style.top =
83+
parseFloat(toolbarSelector.style.top) + toolbarSelectorRect.height + 'px';
84+
toolbarPanels.style.left = toolbarSelectorRect.x + 'px';
85+
break;
86+
87+
case 'top-right':
88+
toolbarTools.style.top = toolbarSelector.style.top;
89+
toolbarTools.style.left =
90+
parseFloat(toolbarSelector.style.left) - toolbarToolsRect.width + 'px';
91+
92+
toolbarPanels.style.top =
93+
parseFloat(toolbarSelector.style.top) + toolbarSelectorRect.height + 'px';
94+
toolbarPanels.style.left =
95+
parseFloat(toolbarSelector.style.left) -
96+
toolbarPanelsRect.width +
97+
toolbarSelectorRect.width +
98+
'px';
99+
break;
100+
101+
case 'bottom-left':
102+
toolbarTools.style.top = toolbarSelector.style.top;
103+
toolbarTools.style.left =
104+
parseFloat(toolbarSelector.style.left) + toolbarSelectorRect.width + 'px';
105+
106+
toolbarPanels.style.top =
107+
parseFloat(toolbarSelector.style.top) - toolbarPanelsRect.height + 'px';
108+
toolbarPanels.style.left = toolbarSelectorRect.x + 'px';
109+
break;
110+
111+
case 'bottom-right':
112+
toolbarTools.style.top = toolbarSelector.style.top;
113+
toolbarTools.style.left =
114+
parseFloat(toolbarSelector.style.left) - toolbarToolsRect.width + 'px';
115+
116+
toolbarPanels.style.top =
117+
parseFloat(toolbarSelector.style.top) - toolbarPanelsRect.height + 'px';
118+
toolbarPanels.style.left =
119+
parseFloat(toolbarSelector.style.left) -
120+
toolbarPanelsRect.width +
121+
toolbarSelectorRect.width +
122+
'px';
123+
break;
124+
125+
default:
126+
break;
127+
}
128+
}
129+
28130
/**
29131
* @param {import('./public').Tool} tool
30132
*/
31-
function toggle_tool(tool) {
133+
async function toggle_tool(tool) {
32134
if (tool.component === ActiveComponent) {
33135
ActiveComponent = undefined;
34136
} else {
@@ -37,13 +139,16 @@
37139
38140
if (!ActiveComponent) toolbarPanels.style.display = 'none';
39141
else toolbarPanels.style.display = 'block';
142+
143+
await tick();
144+
layout_toolbar();
40145
}
41146
42147
/**
43148
* @param {DragEvent} event
44149
*/
45150
function drag_start(event) {
46-
const rect = toolbar.getBoundingClientRect();
151+
const rect = toolbarSelector.getBoundingClientRect();
47152
dragOffsetX = event.clientX - rect.x;
48153
dragOffsetY = event.clientY - rect.y;
49154
}
@@ -54,79 +159,93 @@
54159
function drag(event) {
55160
if (event.clientX === 0 || event.clientY === 0) return;
56161
57-
const rect = toolbar.getBoundingClientRect();
162+
const x = event.clientX - dragOffsetX;
163+
const y = event.clientY - dragOffsetY;
164+
toolbarSelector.style.left = x + 'px';
165+
toolbarSelector.style.top = y + 'px';
166+
167+
let top = false;
168+
let left = false;
169+
170+
if (y < window.innerHeight / 2) top = true;
171+
if (x < window.innerWidth / 2) left = true;
58172
59-
const x = window.innerWidth - event.clientX + dragOffsetX - rect.width;
60-
const y = window.innerHeight - event.clientY + dragOffsetY - rect.height;
61-
toolbar.style.right = x + 'px';
62-
toolbar.style.bottom = y + 'px';
173+
if (top && left) computedPosition = 'top-left';
174+
if (top && !left) computedPosition = 'top-right';
175+
if (!top && left) computedPosition = 'bottom-left';
176+
if (!top && !left) computedPosition = 'bottom-right';
63177
64-
recalculate_toolbar_panel_position();
178+
layout_toolbar();
65179
}
66180
67181
async function toggle_toolbar() {
68182
open = !open;
69183
await tick();
70-
recalculate_toolbar_panel_position();
71-
}
72-
73-
function recalculate_toolbar_panel_position() {
74-
const rect = toolbar.getBoundingClientRect();
75-
toolbarPanels.style.right = toolbar.style.right;
76-
toolbarPanels.style.bottom = parseFloat(toolbar.style.bottom ?? 0) + rect.height + 10 + 'px'; // Add a small gap
77184
}
78185
</script>
79186

80-
<svelte:window onresize={recalculate_toolbar_panel_position} />
81-
82-
<div
83-
class="svelte-toolbar"
84-
bind:this={toolbar}
85-
draggable="true"
86-
ondrag={drag}
87-
ondragstart={drag_start}
88-
role="toolbar"
89-
tabindex="-1"
90-
>
91-
{#if open}
92-
<ul class="svelte-toolbar-tools">
93-
{#each config.tools as tool}
94-
<li class:active={tool.component === ActiveComponent}>
95-
<button onclick={() => toggle_tool(tool)} aria-label={tool.name}>{@html tool.icon}</button
96-
>
97-
</li>
98-
{/each}
99-
</ul>
100-
{/if}
101-
<button type="button" class="svelte-toolbar-selector" onclick={toggle_toolbar}>
102-
<Icon />
103-
</button>
104-
</div>
105-
<div class="svelte-toolbar-panels" bind:this={toolbarPanels}>
106-
{#if ActiveComponent}
107-
<ActiveComponent />
108-
{/if}
187+
<svelte:window onresize={layout_toolbar} />
188+
189+
<div class="svelte-toolbar">
190+
<div
191+
bind:this={toolbarSelector}
192+
draggable="true"
193+
ondrag={drag}
194+
ondragstart={drag_start}
195+
role="toolbar"
196+
tabindex="-1"
197+
class="svelte-toolbar-selector svelte-toolbar-base"
198+
>
199+
<button type="button" onclick={toggle_toolbar}>
200+
<Icon />
201+
</button>
202+
</div>
203+
204+
<div class="svelte-toolbar-tools svelte-toolbar-base" bind:this={toolbarTools}>
205+
{#if open}
206+
<ul>
207+
{#each config.tools as tool}
208+
<li class:active={tool.component === ActiveComponent}>
209+
<button onclick={() => toggle_tool(tool)} aria-label={tool.name}>
210+
{@html tool.icon}
211+
</button>
212+
</li>
213+
{/each}
214+
</ul>
215+
{/if}
216+
</div>
217+
218+
<div class="svelte-toolbar-panels" bind:this={toolbarPanels}>
219+
{#if ActiveComponent}
220+
<ActiveComponent {config} />
221+
{/if}
222+
</div>
109223
</div>
110224

111225
<style>
112226
.svelte-toolbar {
113227
display: inline-flex;
114228
background-color: var(--toolbar-background);
115229
color: var(--toolbar-color);
116-
position: fixed;
117230
z-index: 1000;
118231
border-radius: 8px;
119232
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
120-
padding: 8px;
121233
}
122234
123235
.svelte-toolbar-selector {
236+
position: fixed;
237+
}
238+
239+
.svelte-toolbar-selector button {
124240
cursor: pointer;
125-
background: none;
241+
}
242+
243+
.svelte-toolbar-base {
126244
border: none;
127-
padding: 8px;
128245
border-radius: 6px;
129246
transition: background-color 0.2s ease-in-out;
247+
background-color: var(--toolbar-background);
248+
margin: 0;
130249
}
131250
132251
.svelte-toolbar-selector:hover {
@@ -140,9 +259,14 @@
140259
}
141260
142261
.svelte-toolbar-tools {
262+
position: fixed;
263+
background-color: var(--toolbar-background);
264+
}
265+
266+
.svelte-toolbar-tools ul {
143267
list-style: none;
144268
margin: 0;
145-
padding: 0;
269+
padding: 8px;
146270
display: flex;
147271
align-items: center;
148272
}
@@ -204,7 +328,9 @@
204328
color: var(--toolbar-color);
205329
}
206330
207-
:root {
331+
.svelte-toolbar-selector,
332+
.svelte-toolbar,
333+
.svelte-toolbar-panels {
208334
--toolbar-background: #f0f0f0;
209335
--toolbar-color: #222;
210336
--toolbar-selector-hover-background: #e0e0e0;
@@ -218,7 +344,9 @@
218344
}
219345
220346
@media (prefers-color-scheme: dark) {
221-
:root {
347+
.svelte-toolbar-selector,
348+
.svelte-toolbar,
349+
.svelte-toolbar-panels {
222350
--toolbar-background: #1e1e27;
223351
--toolbar-color: white;
224352
--toolbar-selector-hover-background: #333344;

packages/svelte/src/toolbar/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,8 @@ import { svelte_inspector } from './tools/inspector/index.js';
44
import { svelte_config } from './tools/config/index.js';
55
export * from './configure.svelte.js';
66

7-
configureSvelte({ tools: [svelte_inspector, svelte_config] });
7+
configureSvelte({
8+
position: 'top-left',
9+
tools: [svelte_inspector, svelte_config]
10+
});
811
mountUI();

packages/svelte/src/toolbar/public.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export * from './index.js';
44

55
export interface Tool {
66
name: string;
7-
icon: string; // url or svg
7+
icon: string; // TODO: url or svg
88
activate: () => void;
99
deactivate: () => void;
1010
keyCombo?: string;
@@ -14,7 +14,7 @@ export interface Tool {
1414
type ToolFn = () => Tool;
1515

1616
export interface Config {
17-
position?: 'top' | 'bottom';
17+
position?: 'top-left' | 'top-right' | 'bottom-right' | 'bottom-left';
1818
tools?: (Tool | ToolFn)[];
1919
}
2020

0 commit comments

Comments
 (0)