@salmexio/ui 0.4.0 → 1.1.0
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 +52 -3
- package/dist/dialogs/ContextMenu/ContextMenu.svelte +97 -94
- package/dist/dialogs/ContextMenu/ContextMenu.svelte.d.ts +3 -2
- package/dist/dialogs/ContextMenu/ContextMenu.svelte.d.ts.map +1 -1
- package/dist/dialogs/Modal/Modal.svelte +112 -116
- package/dist/dialogs/Modal/Modal.svelte.d.ts +1 -1
- package/dist/feedback/Alert/Alert.svelte +119 -220
- package/dist/feedback/Alert/Alert.svelte.d.ts +1 -1
- package/dist/feedback/ProgressBar/ProgressBar.svelte +265 -0
- package/dist/feedback/ProgressBar/ProgressBar.svelte.d.ts +40 -0
- package/dist/feedback/ProgressBar/ProgressBar.svelte.d.ts.map +1 -0
- package/dist/feedback/ProgressBar/index.d.ts +2 -0
- package/dist/feedback/ProgressBar/index.d.ts.map +1 -0
- package/dist/feedback/ProgressBar/index.js +1 -0
- package/dist/feedback/Skeleton/Skeleton.svelte +157 -0
- package/dist/feedback/Skeleton/Skeleton.svelte.d.ts +37 -0
- package/dist/feedback/Skeleton/Skeleton.svelte.d.ts.map +1 -0
- package/dist/feedback/Skeleton/index.d.ts +2 -0
- package/dist/feedback/Skeleton/index.d.ts.map +1 -0
- package/dist/feedback/Skeleton/index.js +1 -0
- package/dist/feedback/Spinner/Spinner.svelte +86 -151
- package/dist/feedback/Spinner/Spinner.svelte.d.ts +5 -3
- package/dist/feedback/Spinner/Spinner.svelte.d.ts.map +1 -1
- package/dist/feedback/Toast/Toaster.svelte +431 -0
- package/dist/feedback/Toast/Toaster.svelte.d.ts +22 -0
- package/dist/feedback/Toast/Toaster.svelte.d.ts.map +1 -0
- package/dist/feedback/Toast/index.d.ts +4 -0
- package/dist/feedback/Toast/index.d.ts.map +1 -0
- package/dist/feedback/Toast/index.js +2 -0
- package/dist/feedback/Toast/toastStore.d.ts +34 -0
- package/dist/feedback/Toast/toastStore.d.ts.map +1 -0
- package/dist/feedback/Toast/toastStore.js +43 -0
- package/dist/feedback/index.d.ts +4 -0
- package/dist/feedback/index.d.ts.map +1 -1
- package/dist/feedback/index.js +3 -0
- package/dist/forms/Checkbox/Checkbox.svelte +87 -104
- package/dist/forms/Checkbox/Checkbox.svelte.d.ts +1 -1
- package/dist/forms/Select/Select.svelte +137 -179
- package/dist/forms/Select/Select.svelte.d.ts +1 -1
- package/dist/forms/Slider/Slider.svelte +356 -0
- package/dist/forms/Slider/Slider.svelte.d.ts +50 -0
- package/dist/forms/Slider/Slider.svelte.d.ts.map +1 -0
- package/dist/forms/Slider/index.d.ts +2 -0
- package/dist/forms/Slider/index.d.ts.map +1 -0
- package/dist/forms/Slider/index.js +1 -0
- package/dist/forms/TextInput/TextInput.svelte +161 -167
- package/dist/forms/TextInput/TextInput.svelte.d.ts +1 -1
- package/dist/forms/Textarea/Textarea.svelte +615 -0
- package/dist/forms/Textarea/Textarea.svelte.d.ts +47 -0
- package/dist/forms/Textarea/Textarea.svelte.d.ts.map +1 -0
- package/dist/forms/Textarea/index.d.ts +2 -0
- package/dist/forms/Textarea/index.d.ts.map +1 -0
- package/dist/forms/Textarea/index.js +1 -0
- package/dist/forms/Toggle/Toggle.svelte +239 -0
- package/dist/forms/Toggle/Toggle.svelte.d.ts +39 -0
- package/dist/forms/Toggle/Toggle.svelte.d.ts.map +1 -0
- package/dist/forms/Toggle/index.d.ts +2 -0
- package/dist/forms/Toggle/index.d.ts.map +1 -0
- package/dist/forms/Toggle/index.js +1 -0
- package/dist/forms/index.d.ts +3 -0
- package/dist/forms/index.d.ts.map +1 -1
- package/dist/forms/index.js +3 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/layout/Card/Card.svelte +66 -39
- package/dist/layout/Card/Card.svelte.d.ts +1 -1
- package/dist/layout/Card/Card.svelte.d.ts.map +1 -1
- package/dist/layout/Container/Container.svelte +71 -71
- package/dist/layout/Container/Container.svelte.d.ts +2 -2
- package/dist/layout/ThermalBackground/ThermalBackground.svelte +313 -0
- package/dist/layout/ThermalBackground/ThermalBackground.svelte.d.ts +16 -0
- package/dist/layout/ThermalBackground/ThermalBackground.svelte.d.ts.map +1 -0
- package/dist/layout/ThermalBackground/index.d.ts +2 -0
- package/dist/layout/ThermalBackground/index.d.ts.map +1 -0
- package/dist/layout/ThermalBackground/index.js +1 -0
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/index.d.ts.map +1 -1
- package/dist/layout/index.js +1 -0
- package/dist/navigation/CommandPalette/CommandPalette.svelte +407 -189
- package/dist/navigation/CommandPalette/CommandPalette.svelte.d.ts +8 -3
- package/dist/navigation/CommandPalette/CommandPalette.svelte.d.ts.map +1 -1
- package/dist/navigation/Tabs/Tabs.svelte +139 -192
- package/dist/navigation/Tabs/Tabs.svelte.d.ts +2 -2
- package/dist/primitives/Badge/Badge.svelte +85 -220
- package/dist/primitives/Badge/Badge.svelte.d.ts +2 -2
- package/dist/primitives/Badge/Badge.svelte.d.ts.map +1 -1
- package/dist/primitives/Button/Button.svelte +214 -194
- package/dist/primitives/Button/Button.svelte.d.ts +3 -3
- package/dist/primitives/Button/Button.svelte.d.ts.map +1 -1
- package/dist/primitives/Tooltip/Tooltip.svelte +260 -0
- package/dist/primitives/Tooltip/Tooltip.svelte.d.ts +36 -0
- package/dist/primitives/Tooltip/Tooltip.svelte.d.ts.map +1 -0
- package/dist/primitives/Tooltip/index.d.ts +2 -0
- package/dist/primitives/Tooltip/index.d.ts.map +1 -0
- package/dist/primitives/Tooltip/index.js +1 -0
- package/dist/primitives/index.d.ts +1 -0
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/index.js +1 -0
- package/dist/styles/tokens.css +329 -260
- package/package.json +5 -5
- package/dist/windowing/Window/Window.svelte +0 -637
- package/dist/windowing/Window/Window.svelte.d.ts +0 -65
- package/dist/windowing/Window/Window.svelte.d.ts.map +0 -1
- package/dist/windowing/Window/index.d.ts +0 -2
- package/dist/windowing/Window/index.d.ts.map +0 -1
- package/dist/windowing/Window/index.js +0 -1
- package/dist/windowing/WindowManager/WindowManager.svelte +0 -425
- package/dist/windowing/WindowManager/WindowManager.svelte.d.ts +0 -38
- package/dist/windowing/WindowManager/WindowManager.svelte.d.ts.map +0 -1
- package/dist/windowing/WindowManager/index.d.ts +0 -2
- package/dist/windowing/WindowManager/index.d.ts.map +0 -1
- package/dist/windowing/WindowManager/index.js +0 -1
- package/dist/windowing/index.d.ts +0 -5
- package/dist/windowing/index.d.ts.map +0 -1
- package/dist/windowing/index.js +0 -3
- package/dist/windowing/windowStore.svelte.d.ts +0 -49
- package/dist/windowing/windowStore.svelte.d.ts.map +0 -1
- package/dist/windowing/windowStore.svelte.js +0 -170
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salmexio/ui",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Salmex I/O Design System —
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Salmex I/O Design System — INFRARED component library for Salmex products",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"publishConfig": {
|
|
@@ -74,10 +74,10 @@
|
|
|
74
74
|
"sveltekit",
|
|
75
75
|
"ui",
|
|
76
76
|
"components",
|
|
77
|
-
"
|
|
77
|
+
"infrared",
|
|
78
|
+
"dark-mode",
|
|
78
79
|
"accessibility",
|
|
79
|
-
"typescript"
|
|
80
|
-
"ai-assistant"
|
|
80
|
+
"typescript"
|
|
81
81
|
],
|
|
82
82
|
"svelte": "./dist/index.js",
|
|
83
83
|
"types": "./dist/index.d.ts"
|
|
@@ -1,637 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
@component Window
|
|
3
|
-
|
|
4
|
-
Win2K x Basquiat -- Draggable window with title bar chrome, minimize/maximize/close
|
|
5
|
-
controls, 3D beveled borders, and flat shadow. Lives inside a WindowManager container.
|
|
6
|
-
Non-modal dialog (role="dialog") -- windows do not trap focus.
|
|
7
|
-
Title bar supports pointer-based dragging with container boundary clamping.
|
|
8
|
-
|
|
9
|
-
@example
|
|
10
|
-
<WindowManager>
|
|
11
|
-
<Window id="notes" title="Notes">
|
|
12
|
-
<p>Window body content.</p>
|
|
13
|
-
</Window>
|
|
14
|
-
</WindowManager>
|
|
15
|
-
-->
|
|
16
|
-
<script lang="ts">
|
|
17
|
-
import type { Snippet } from 'svelte';
|
|
18
|
-
import { getContext, onMount, onDestroy } from 'svelte';
|
|
19
|
-
import { cn } from '../../utils/cn.js';
|
|
20
|
-
import { generateId, Keys } from '../../utils/keyboard.js';
|
|
21
|
-
import type { WindowManagerState } from '../windowStore.svelte.js';
|
|
22
|
-
|
|
23
|
-
type WindowSize = 'sm' | 'md' | 'lg' | 'xl';
|
|
24
|
-
|
|
25
|
-
interface Props {
|
|
26
|
-
/** Unique window identifier. Auto-generated if omitted. */
|
|
27
|
-
id?: string;
|
|
28
|
-
/** Title displayed in the title bar. */
|
|
29
|
-
title?: string;
|
|
30
|
-
/** Initial width in pixels. */
|
|
31
|
-
width?: number;
|
|
32
|
-
/** Initial height in pixels. */
|
|
33
|
-
height?: number;
|
|
34
|
-
/** Initial X position (px from container left). */
|
|
35
|
-
x?: number;
|
|
36
|
-
/** Initial Y position (px from container top). */
|
|
37
|
-
y?: number;
|
|
38
|
-
/** Preset size (overrides width/height). */
|
|
39
|
-
size?: WindowSize;
|
|
40
|
-
/** Show minimize button. */
|
|
41
|
-
minimizable?: boolean;
|
|
42
|
-
/** Show maximize button. */
|
|
43
|
-
maximizable?: boolean;
|
|
44
|
-
/** Show close button. */
|
|
45
|
-
closable?: boolean;
|
|
46
|
-
/** Prevent the window from being dragged. */
|
|
47
|
-
disableDrag?: boolean;
|
|
48
|
-
/** Icon snippet rendered before the title. */
|
|
49
|
-
icon?: Snippet;
|
|
50
|
-
/** Custom title bar content (overrides title text). */
|
|
51
|
-
titleBar?: Snippet;
|
|
52
|
-
/** Status bar at bottom of window. */
|
|
53
|
-
statusBar?: Snippet;
|
|
54
|
-
/** Main body content. */
|
|
55
|
-
children?: Snippet;
|
|
56
|
-
/** Called when close button is clicked. */
|
|
57
|
-
onclose?: () => void;
|
|
58
|
-
/** Called when minimize button is clicked. */
|
|
59
|
-
onminimize?: () => void;
|
|
60
|
-
/** Called when maximize/restore button is clicked. */
|
|
61
|
-
onmaximize?: () => void;
|
|
62
|
-
/** Called when window gains focus. */
|
|
63
|
-
onfocus?: () => void;
|
|
64
|
-
/** Additional CSS class. */
|
|
65
|
-
class?: string;
|
|
66
|
-
/** Test ID for automated testing. */
|
|
67
|
-
testId?: string;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
let {
|
|
71
|
-
id,
|
|
72
|
-
title = 'Window',
|
|
73
|
-
width = 420,
|
|
74
|
-
height = 320,
|
|
75
|
-
x = 20,
|
|
76
|
-
y = 20,
|
|
77
|
-
size,
|
|
78
|
-
minimizable = true,
|
|
79
|
-
maximizable = true,
|
|
80
|
-
closable = true,
|
|
81
|
-
disableDrag = false,
|
|
82
|
-
icon,
|
|
83
|
-
titleBar,
|
|
84
|
-
statusBar,
|
|
85
|
-
children,
|
|
86
|
-
onclose,
|
|
87
|
-
onminimize,
|
|
88
|
-
onmaximize,
|
|
89
|
-
onfocus,
|
|
90
|
-
class: className = '',
|
|
91
|
-
testId
|
|
92
|
-
}: Props = $props();
|
|
93
|
-
|
|
94
|
-
const sizeMap: Record<WindowSize, { w: number; h: number }> = {
|
|
95
|
-
sm: { w: 320, h: 240 },
|
|
96
|
-
md: { w: 420, h: 320 },
|
|
97
|
-
lg: { w: 560, h: 420 },
|
|
98
|
-
xl: { w: 720, h: 520 }
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const resolvedWidth = $derived(size ? sizeMap[size].w : width);
|
|
102
|
-
const resolvedHeight = $derived(size ? sizeMap[size].h : height);
|
|
103
|
-
|
|
104
|
-
const windowId = $derived(id ?? generateId('win'));
|
|
105
|
-
const titleId = $derived(`${windowId}-title`);
|
|
106
|
-
|
|
107
|
-
const manager = getContext<WindowManagerState>('salmex-window-manager');
|
|
108
|
-
|
|
109
|
-
let windowEl = $state<HTMLDivElement | null>(null);
|
|
110
|
-
let isDragging = $state(false);
|
|
111
|
-
let dragOffset = $state({ x: 0, y: 0 });
|
|
112
|
-
|
|
113
|
-
// Register with manager on mount
|
|
114
|
-
onMount(() => {
|
|
115
|
-
manager.register(windowId, title, {
|
|
116
|
-
x,
|
|
117
|
-
y,
|
|
118
|
-
width: resolvedWidth,
|
|
119
|
-
height: resolvedHeight
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
onDestroy(() => {
|
|
124
|
-
manager.unregister(windowId);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
const win = $derived(manager.getWindow(windowId));
|
|
128
|
-
const isMinimized = $derived(win?.minimized ?? false);
|
|
129
|
-
const isMaximized = $derived(win?.maximized ?? false);
|
|
130
|
-
const isFocused = $derived(win?.focused ?? false);
|
|
131
|
-
|
|
132
|
-
// Sync title changes
|
|
133
|
-
$effect(() => {
|
|
134
|
-
const w = manager.getWindow(windowId);
|
|
135
|
-
if (w && w.title !== title) {
|
|
136
|
-
w.title = title;
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
function handleMouseDown() {
|
|
141
|
-
if (!isFocused) {
|
|
142
|
-
manager.focus(windowId);
|
|
143
|
-
onfocus?.();
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function handleTitleBarPointerDown(e: PointerEvent) {
|
|
148
|
-
if (disableDrag || isMaximized) return;
|
|
149
|
-
// Only drag on primary button, not on control buttons
|
|
150
|
-
if (e.button !== 0) return;
|
|
151
|
-
const target = e.target as HTMLElement;
|
|
152
|
-
if (target.closest('.salmex-win-controls')) return;
|
|
153
|
-
|
|
154
|
-
isDragging = true;
|
|
155
|
-
const currentWin = manager.getWindow(windowId);
|
|
156
|
-
if (!currentWin) return;
|
|
157
|
-
dragOffset = {
|
|
158
|
-
x: e.clientX - currentWin.x,
|
|
159
|
-
y: e.clientY - currentWin.y
|
|
160
|
-
};
|
|
161
|
-
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
|
162
|
-
e.preventDefault();
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function handlePointerMove(e: PointerEvent) {
|
|
166
|
-
if (!isDragging) return;
|
|
167
|
-
const container = windowEl?.parentElement;
|
|
168
|
-
if (!container) return;
|
|
169
|
-
const rect = container.getBoundingClientRect();
|
|
170
|
-
const currentWin = manager.getWindow(windowId);
|
|
171
|
-
if (!currentWin) return;
|
|
172
|
-
|
|
173
|
-
// Clamp: keep at least 60px of title bar visible
|
|
174
|
-
const minVisible = 60;
|
|
175
|
-
const newX = Math.max(
|
|
176
|
-
-(currentWin.width - minVisible),
|
|
177
|
-
Math.min(e.clientX - dragOffset.x, rect.width - minVisible)
|
|
178
|
-
);
|
|
179
|
-
const newY = Math.max(0, Math.min(e.clientY - dragOffset.y, rect.height - 32));
|
|
180
|
-
manager.move(windowId, newX, newY);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function handlePointerUp() {
|
|
184
|
-
isDragging = false;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function handleMinimize() {
|
|
188
|
-
manager.minimize(windowId);
|
|
189
|
-
onminimize?.();
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function handleMaximize() {
|
|
193
|
-
const container = windowEl?.parentElement;
|
|
194
|
-
if (!container) return;
|
|
195
|
-
if (isMaximized) {
|
|
196
|
-
manager.restore(windowId);
|
|
197
|
-
} else {
|
|
198
|
-
const rect = container.getBoundingClientRect();
|
|
199
|
-
manager.maximize(windowId, rect.width, rect.height);
|
|
200
|
-
}
|
|
201
|
-
onmaximize?.();
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function handleClose() {
|
|
205
|
-
onclose?.();
|
|
206
|
-
manager.close(windowId);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function handleTitleBarDblClick() {
|
|
210
|
-
if (maximizable) handleMaximize();
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function handleKeyDown(e: KeyboardEvent) {
|
|
214
|
-
if (e.key === Keys.Escape && closable) {
|
|
215
|
-
e.stopPropagation();
|
|
216
|
-
handleClose();
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
</script>
|
|
220
|
-
|
|
221
|
-
{#if win && !isMinimized}
|
|
222
|
-
<div
|
|
223
|
-
bind:this={windowEl}
|
|
224
|
-
id={windowId}
|
|
225
|
-
class={cn(
|
|
226
|
-
'salmex-win',
|
|
227
|
-
isFocused && 'salmex-win-focused',
|
|
228
|
-
isMaximized && 'salmex-win-maximized',
|
|
229
|
-
isDragging && 'salmex-win-dragging',
|
|
230
|
-
className
|
|
231
|
-
)}
|
|
232
|
-
style="left: {win.x}px; top: {win.y}px; width: {win.width}px; height: {win.height}px; z-index: {win.zIndex};"
|
|
233
|
-
role="dialog"
|
|
234
|
-
aria-labelledby={titleId}
|
|
235
|
-
tabindex="-1"
|
|
236
|
-
data-testid={testId}
|
|
237
|
-
data-window-id={windowId}
|
|
238
|
-
onmousedown={handleMouseDown}
|
|
239
|
-
onkeydown={handleKeyDown}
|
|
240
|
-
>
|
|
241
|
-
<!-- Title bar -->
|
|
242
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
243
|
-
<div
|
|
244
|
-
class="salmex-win-titlebar"
|
|
245
|
-
onpointerdown={handleTitleBarPointerDown}
|
|
246
|
-
onpointermove={handlePointerMove}
|
|
247
|
-
onpointerup={handlePointerUp}
|
|
248
|
-
ondblclick={handleTitleBarDblClick}
|
|
249
|
-
style="touch-action: none;"
|
|
250
|
-
>
|
|
251
|
-
<div id={titleId} class="salmex-win-title-content">
|
|
252
|
-
{#if titleBar}
|
|
253
|
-
{@render titleBar()}
|
|
254
|
-
{:else}
|
|
255
|
-
{#if icon}
|
|
256
|
-
<span class="salmex-win-icon" aria-hidden="true">{@render icon()}</span>
|
|
257
|
-
{/if}
|
|
258
|
-
<span class="salmex-win-title-text">{title}</span>
|
|
259
|
-
{/if}
|
|
260
|
-
</div>
|
|
261
|
-
|
|
262
|
-
<div class="salmex-win-controls">
|
|
263
|
-
{#if minimizable}
|
|
264
|
-
<button
|
|
265
|
-
type="button"
|
|
266
|
-
class="salmex-win-btn salmex-win-btn-minimize"
|
|
267
|
-
onclick={handleMinimize}
|
|
268
|
-
aria-label="Minimize {title}"
|
|
269
|
-
>
|
|
270
|
-
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
|
271
|
-
<line x1="2" y1="9" x2="10" y2="9" stroke="currentColor" stroke-width="2" />
|
|
272
|
-
</svg>
|
|
273
|
-
</button>
|
|
274
|
-
{/if}
|
|
275
|
-
{#if maximizable}
|
|
276
|
-
<button
|
|
277
|
-
type="button"
|
|
278
|
-
class="salmex-win-btn salmex-win-btn-maximize"
|
|
279
|
-
onclick={handleMaximize}
|
|
280
|
-
aria-label={isMaximized ? `Restore ${title}` : `Maximize ${title}`}
|
|
281
|
-
>
|
|
282
|
-
{#if isMaximized}
|
|
283
|
-
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
|
284
|
-
<rect x="3" y="1" width="8" height="8" stroke="currentColor" stroke-width="1.5" fill="none" />
|
|
285
|
-
<rect x="1" y="3" width="8" height="8" stroke="currentColor" stroke-width="1.5" fill="rgb(var(--salmex-button-face))" />
|
|
286
|
-
</svg>
|
|
287
|
-
{:else}
|
|
288
|
-
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
|
289
|
-
<rect x="1" y="1" width="10" height="10" stroke="currentColor" stroke-width="1.5" fill="none" />
|
|
290
|
-
<line x1="1" y1="2.5" x2="11" y2="2.5" stroke="currentColor" stroke-width="2" />
|
|
291
|
-
</svg>
|
|
292
|
-
{/if}
|
|
293
|
-
</button>
|
|
294
|
-
{/if}
|
|
295
|
-
{#if closable}
|
|
296
|
-
<button
|
|
297
|
-
type="button"
|
|
298
|
-
class="salmex-win-btn salmex-win-btn-close"
|
|
299
|
-
onclick={handleClose}
|
|
300
|
-
aria-label="Close {title}"
|
|
301
|
-
>
|
|
302
|
-
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
|
303
|
-
<line x1="2" y1="2" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
|
|
304
|
-
<line x1="10" y1="2" x2="2" y2="10" stroke="currentColor" stroke-width="2" />
|
|
305
|
-
</svg>
|
|
306
|
-
</button>
|
|
307
|
-
{/if}
|
|
308
|
-
</div>
|
|
309
|
-
</div>
|
|
310
|
-
|
|
311
|
-
<!-- Body -->
|
|
312
|
-
{#if children}
|
|
313
|
-
<div class="salmex-win-body">
|
|
314
|
-
{@render children()}
|
|
315
|
-
</div>
|
|
316
|
-
{/if}
|
|
317
|
-
|
|
318
|
-
<!-- Status bar -->
|
|
319
|
-
{#if statusBar}
|
|
320
|
-
<div class="salmex-win-statusbar">
|
|
321
|
-
{@render statusBar()}
|
|
322
|
-
</div>
|
|
323
|
-
{/if}
|
|
324
|
-
</div>
|
|
325
|
-
{/if}
|
|
326
|
-
|
|
327
|
-
<style>
|
|
328
|
-
/* ========================================
|
|
329
|
-
WINDOW CONTAINER
|
|
330
|
-
======================================== */
|
|
331
|
-
|
|
332
|
-
.salmex-win {
|
|
333
|
-
position: absolute;
|
|
334
|
-
display: flex;
|
|
335
|
-
flex-direction: column;
|
|
336
|
-
background: rgb(var(--salmex-window-surface));
|
|
337
|
-
border: 3px solid rgb(var(--salmex-button-dark-edge));
|
|
338
|
-
border-radius: var(--salmex-radius-md);
|
|
339
|
-
overflow: hidden;
|
|
340
|
-
box-shadow:
|
|
341
|
-
/* 3D raised outer edge */
|
|
342
|
-
inset 1px 1px 0 rgb(var(--salmex-button-highlight)),
|
|
343
|
-
inset -1px -1px 0 rgb(var(--salmex-button-shadow)),
|
|
344
|
-
/* Flat bold drop shadow */
|
|
345
|
-
4px 4px 3px rgb(0 0 0 / 0.25);
|
|
346
|
-
outline: none;
|
|
347
|
-
transition:
|
|
348
|
-
box-shadow var(--salmex-transition-fast),
|
|
349
|
-
opacity var(--salmex-transition-fast);
|
|
350
|
-
min-width: 200px;
|
|
351
|
-
min-height: 120px;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
.salmex-win:not(.salmex-win-focused) {
|
|
355
|
-
box-shadow:
|
|
356
|
-
inset 1px 1px 0 rgb(var(--salmex-button-highlight)),
|
|
357
|
-
inset -1px -1px 0 rgb(var(--salmex-button-shadow)),
|
|
358
|
-
3px 3px 2px rgb(0 0 0 / 0.15);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
:global([data-theme='dark']) .salmex-win {
|
|
362
|
-
box-shadow:
|
|
363
|
-
inset 1px 1px 0 rgb(var(--salmex-button-highlight)),
|
|
364
|
-
inset -1px -1px 0 rgb(var(--salmex-button-shadow)),
|
|
365
|
-
4px 4px 3px rgb(0 0 0 / 0.5);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
:global([data-theme='dark']) .salmex-win:not(.salmex-win-focused) {
|
|
369
|
-
box-shadow:
|
|
370
|
-
inset 1px 1px 0 rgb(var(--salmex-button-highlight)),
|
|
371
|
-
inset -1px -1px 0 rgb(var(--salmex-button-shadow)),
|
|
372
|
-
3px 3px 2px rgb(0 0 0 / 0.35);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
.salmex-win-maximized {
|
|
376
|
-
border-width: 2px;
|
|
377
|
-
border-radius: 0;
|
|
378
|
-
box-shadow: none !important;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
.salmex-win-dragging {
|
|
382
|
-
opacity: 0.92;
|
|
383
|
-
cursor: grabbing;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/* ========================================
|
|
387
|
-
TITLE BAR
|
|
388
|
-
======================================== */
|
|
389
|
-
|
|
390
|
-
.salmex-win-titlebar {
|
|
391
|
-
display: flex;
|
|
392
|
-
align-items: center;
|
|
393
|
-
justify-content: space-between;
|
|
394
|
-
gap: var(--salmex-space-2);
|
|
395
|
-
padding: var(--salmex-space-1) var(--salmex-space-2);
|
|
396
|
-
/* Win2K active title gradient */
|
|
397
|
-
background: linear-gradient(
|
|
398
|
-
90deg,
|
|
399
|
-
rgb(var(--salmex-electric-blue)),
|
|
400
|
-
rgb(var(--salmex-titlebar-bold))
|
|
401
|
-
);
|
|
402
|
-
color: rgb(var(--salmex-chalk-white));
|
|
403
|
-
border-bottom: 1px solid rgb(var(--salmex-button-dark-edge));
|
|
404
|
-
border-radius: 3px 3px 0 0;
|
|
405
|
-
flex-shrink: 0;
|
|
406
|
-
font-family: var(--salmex-font-display);
|
|
407
|
-
user-select: none;
|
|
408
|
-
cursor: grab;
|
|
409
|
-
min-height: 28px;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
.salmex-win-dragging .salmex-win-titlebar {
|
|
413
|
-
cursor: grabbing;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
.salmex-win-maximized .salmex-win-titlebar {
|
|
417
|
-
cursor: default;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/* Inactive title bar - gray gradient (Win2K) */
|
|
421
|
-
.salmex-win:not(.salmex-win-focused) .salmex-win-titlebar {
|
|
422
|
-
background: linear-gradient(
|
|
423
|
-
90deg,
|
|
424
|
-
rgb(var(--salmex-button-shadow)),
|
|
425
|
-
rgb(var(--salmex-border-light))
|
|
426
|
-
);
|
|
427
|
-
color: rgb(var(--salmex-text-disabled));
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/* Dark mode — darker gradient for better text contrast (~5-6:1) */
|
|
431
|
-
:global([data-theme='dark']) .salmex-win-titlebar {
|
|
432
|
-
background: linear-gradient(
|
|
433
|
-
90deg,
|
|
434
|
-
rgb(0 75 180),
|
|
435
|
-
rgb(0 105 210)
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
:global([data-theme='dark']) .salmex-win:not(.salmex-win-focused) .salmex-win-titlebar {
|
|
440
|
-
background: linear-gradient(
|
|
441
|
-
90deg,
|
|
442
|
-
rgb(50 50 50),
|
|
443
|
-
rgb(70 70 70)
|
|
444
|
-
);
|
|
445
|
-
color: rgb(var(--salmex-text-disabled));
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/* Graffiti yellow underline on focused title bar */
|
|
449
|
-
.salmex-win-focused .salmex-win-titlebar::after {
|
|
450
|
-
content: '';
|
|
451
|
-
position: absolute;
|
|
452
|
-
bottom: 0;
|
|
453
|
-
left: 0;
|
|
454
|
-
width: 50%;
|
|
455
|
-
height: 3px;
|
|
456
|
-
background: rgb(var(--salmex-crown-yellow));
|
|
457
|
-
border-radius: 0 2px 0 0;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
:root:not([data-theme='dark']) .salmex-win-focused .salmex-win-titlebar::after {
|
|
461
|
-
background: rgb(var(--salmex-street-yellow));
|
|
462
|
-
box-shadow: 0 1px 2px rgb(0 0 0 / 0.3);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
.salmex-win-titlebar {
|
|
466
|
-
position: relative;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
/* ========================================
|
|
470
|
-
TITLE CONTENT
|
|
471
|
-
======================================== */
|
|
472
|
-
|
|
473
|
-
.salmex-win-title-content {
|
|
474
|
-
display: flex;
|
|
475
|
-
align-items: center;
|
|
476
|
-
gap: var(--salmex-space-2);
|
|
477
|
-
flex: 1;
|
|
478
|
-
min-width: 0;
|
|
479
|
-
overflow: hidden;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
.salmex-win-icon {
|
|
483
|
-
display: flex;
|
|
484
|
-
align-items: center;
|
|
485
|
-
flex-shrink: 0;
|
|
486
|
-
width: 16px;
|
|
487
|
-
height: 16px;
|
|
488
|
-
font-size: 14px;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
.salmex-win-title-text {
|
|
492
|
-
font-size: var(--salmex-font-size-sm);
|
|
493
|
-
font-weight: 900;
|
|
494
|
-
text-transform: uppercase;
|
|
495
|
-
letter-spacing: 0.5px;
|
|
496
|
-
line-height: 1.3;
|
|
497
|
-
white-space: nowrap;
|
|
498
|
-
overflow: hidden;
|
|
499
|
-
text-overflow: ellipsis;
|
|
500
|
-
text-shadow: 1px 1px 2px rgb(0 0 0 / 0.3);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
.salmex-win:not(.salmex-win-focused) .salmex-win-title-text {
|
|
504
|
-
text-shadow: none;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/* ========================================
|
|
508
|
-
WINDOW CONTROL BUTTONS (Minimize / Maximize / Close)
|
|
509
|
-
======================================== */
|
|
510
|
-
|
|
511
|
-
.salmex-win-controls {
|
|
512
|
-
display: flex;
|
|
513
|
-
gap: 2px;
|
|
514
|
-
flex-shrink: 0;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
.salmex-win-btn {
|
|
518
|
-
display: flex;
|
|
519
|
-
align-items: center;
|
|
520
|
-
justify-content: center;
|
|
521
|
-
width: 22px;
|
|
522
|
-
height: 22px;
|
|
523
|
-
padding: 0;
|
|
524
|
-
background: rgb(var(--salmex-button-face));
|
|
525
|
-
border: 1px solid rgb(var(--salmex-button-dark-edge));
|
|
526
|
-
border-radius: var(--salmex-radius-sm);
|
|
527
|
-
color: rgb(var(--salmex-text-primary));
|
|
528
|
-
cursor: pointer;
|
|
529
|
-
box-shadow:
|
|
530
|
-
inset 1px 1px 0 rgb(var(--salmex-button-highlight)),
|
|
531
|
-
inset -1px -1px 0 rgb(var(--salmex-button-shadow));
|
|
532
|
-
transition: all var(--salmex-transition-fast);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
.salmex-win-btn:hover {
|
|
536
|
-
background: rgb(var(--salmex-button-light));
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
.salmex-win-btn:active {
|
|
540
|
-
box-shadow:
|
|
541
|
-
inset -1px -1px 0 rgb(var(--salmex-button-highlight)),
|
|
542
|
-
inset 1px 1px 0 rgb(var(--salmex-button-shadow));
|
|
543
|
-
transform: translate(1px, 1px);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
.salmex-win-btn:focus-visible {
|
|
547
|
-
outline: none;
|
|
548
|
-
box-shadow:
|
|
549
|
-
inset 1px 1px 0 rgb(var(--salmex-button-highlight)),
|
|
550
|
-
inset -1px -1px 0 rgb(var(--salmex-button-shadow)),
|
|
551
|
-
0 0 0 2px rgb(var(--salmex-midnight-black)),
|
|
552
|
-
0 0 2px 4px rgb(var(--salmex-crown-yellow));
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
:global([data-theme='dark']) .salmex-win-btn:focus-visible {
|
|
556
|
-
box-shadow:
|
|
557
|
-
inset 1px 1px 0 rgb(var(--salmex-button-highlight)),
|
|
558
|
-
inset -1px -1px 0 rgb(var(--salmex-button-shadow)),
|
|
559
|
-
0 0 2px 3px rgb(var(--salmex-crown-yellow));
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
/* Close button — red accent on hover */
|
|
563
|
-
.salmex-win-btn-close:hover {
|
|
564
|
-
background: rgb(var(--salmex-street-red));
|
|
565
|
-
color: rgb(var(--salmex-chalk-white));
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
/* ========================================
|
|
569
|
-
BODY
|
|
570
|
-
======================================== */
|
|
571
|
-
|
|
572
|
-
.salmex-win-body {
|
|
573
|
-
flex: 1;
|
|
574
|
-
overflow: auto;
|
|
575
|
-
padding: var(--salmex-space-4);
|
|
576
|
-
font-family: var(--salmex-font-system);
|
|
577
|
-
color: rgb(var(--salmex-text-primary));
|
|
578
|
-
min-height: 0;
|
|
579
|
-
background: rgb(var(--salmex-bg-primary));
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
/* Sunken inset for the body area — Win2K content area.
|
|
583
|
-
L-shaped top+left inset reads "sunken" without the full
|
|
584
|
-
4-sided frame that would double-frame child inputs. */
|
|
585
|
-
.salmex-win-body {
|
|
586
|
-
box-shadow:
|
|
587
|
-
inset 0 1px 0 rgb(var(--salmex-button-shadow)),
|
|
588
|
-
inset 1px 0 0 rgb(var(--salmex-button-shadow) / 0.5);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
:global([data-theme='dark']) .salmex-win-body {
|
|
592
|
-
box-shadow:
|
|
593
|
-
inset 0 1px 0 rgb(0 0 0 / 0.4),
|
|
594
|
-
inset 1px 0 0 rgb(0 0 0 / 0.2);
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
/* ========================================
|
|
598
|
-
STATUS BAR
|
|
599
|
-
======================================== */
|
|
600
|
-
|
|
601
|
-
.salmex-win-statusbar {
|
|
602
|
-
display: flex;
|
|
603
|
-
align-items: center;
|
|
604
|
-
gap: var(--salmex-space-2);
|
|
605
|
-
padding: var(--salmex-space-1) var(--salmex-space-3);
|
|
606
|
-
background: rgb(var(--salmex-window-surface));
|
|
607
|
-
border-top: 1px solid rgb(var(--salmex-button-highlight));
|
|
608
|
-
border-radius: 0 0 3px 3px;
|
|
609
|
-
font-size: var(--salmex-font-size-xs);
|
|
610
|
-
font-family: var(--salmex-font-system);
|
|
611
|
-
color: rgb(var(--salmex-text-secondary));
|
|
612
|
-
flex-shrink: 0;
|
|
613
|
-
/* Sunken inset segments */
|
|
614
|
-
box-shadow: inset 1px 1px 0 rgb(var(--salmex-button-shadow)),
|
|
615
|
-
inset -1px -1px 0 rgb(var(--salmex-button-highlight));
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
:global([data-theme='dark']) .salmex-win-statusbar {
|
|
619
|
-
border-top-color: rgb(var(--salmex-button-shadow));
|
|
620
|
-
box-shadow:
|
|
621
|
-
inset 1px 1px 0 rgb(0 0 0 / 0.3),
|
|
622
|
-
inset -1px -1px 0 rgb(var(--salmex-button-highlight));
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
/* ========================================
|
|
626
|
-
REDUCED MOTION
|
|
627
|
-
======================================== */
|
|
628
|
-
|
|
629
|
-
@media (prefers-reduced-motion: reduce) {
|
|
630
|
-
.salmex-win {
|
|
631
|
-
transition: none;
|
|
632
|
-
}
|
|
633
|
-
.salmex-win-btn {
|
|
634
|
-
transition: none;
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
</style>
|