@signal9/era-ui 1.26.0 → 1.26.1
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/os/launcher.svelte +20 -13
- package/dist/os/launcher.svelte.d.ts +4 -2
- package/dist/os/wm.svelte.d.ts +3 -1
- package/dist/os/wm.svelte.js +10 -2
- package/dist/styles/themes.css +9 -9
- package/dist/ui/button/index.d.ts +1 -1
- package/dist/ui/command-bar/command-bar.svelte +10 -12
- package/dist/ui/command-bar/command-bar.svelte.d.ts +2 -1
- package/dist/utils/hotkeys.d.ts +6 -1
- package/dist/utils/hotkeys.js +14 -1
- package/package.json +1 -1
package/dist/os/launcher.svelte
CHANGED
|
@@ -3,34 +3,39 @@
|
|
|
3
3
|
import * as Command from '../ui/command';
|
|
4
4
|
import Search from '@lucide/svelte/icons/search';
|
|
5
5
|
import { cn } from '../utils/index.js';
|
|
6
|
+
import { keys, isTextEntryFocused, type KeyBindingMap } from '../utils/hotkeys.js';
|
|
6
7
|
import { getWindowManager } from './wm.svelte.js';
|
|
7
8
|
import { LAYER } from './layers.js';
|
|
8
9
|
|
|
9
10
|
let {
|
|
10
11
|
open = $bindable(false),
|
|
11
|
-
hotkey
|
|
12
|
+
hotkey
|
|
12
13
|
}: {
|
|
13
14
|
open?: boolean;
|
|
14
|
-
/**
|
|
15
|
-
|
|
15
|
+
/** Global binding(s) that toggle the launcher (tinykeys grammar, e.g.
|
|
16
|
+
* `'$mod+k'`). No default — the taskbar button is the base affordance.
|
|
17
|
+
* Escape always closes while open. */
|
|
18
|
+
hotkey?: string | string[];
|
|
16
19
|
} = $props();
|
|
17
20
|
|
|
18
21
|
const wm = getWindowManager();
|
|
19
22
|
const apps = $derived(Object.values(wm.apps));
|
|
20
23
|
let query = $state('');
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
// One central binding map through the shared hotkeys engine ($mod
|
|
26
|
+
// normalization, exact-modifier matching, text-entry guard) instead of a
|
|
27
|
+
// hand-rolled listener. An empty map (no hotkey, closed) attaches nothing.
|
|
28
|
+
const bindings = $derived.by((): KeyBindingMap => {
|
|
29
|
+
const map: KeyBindingMap = {};
|
|
30
|
+
for (const b of hotkey ? (Array.isArray(hotkey) ? hotkey : [hotkey]) : []) {
|
|
31
|
+
map[b] = (e) => {
|
|
32
|
+
if (isTextEntryFocused() && !open) return;
|
|
26
33
|
e.preventDefault();
|
|
27
34
|
open = !open;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
window.addEventListener('keydown', onKey);
|
|
33
|
-
return () => window.removeEventListener('keydown', onKey);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (open) map['Escape'] = () => (open = false);
|
|
38
|
+
return map;
|
|
34
39
|
});
|
|
35
40
|
|
|
36
41
|
// Reset the query each time it opens so it's fresh.
|
|
@@ -44,6 +49,8 @@
|
|
|
44
49
|
}
|
|
45
50
|
</script>
|
|
46
51
|
|
|
52
|
+
<svelte:window use:keys={bindings} />
|
|
53
|
+
|
|
47
54
|
{#if open}
|
|
48
55
|
<div
|
|
49
56
|
class={cn(
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
type $$ComponentProps = {
|
|
2
2
|
open?: boolean;
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/** Global binding(s) that toggle the launcher (tinykeys grammar, e.g.
|
|
4
|
+
* `'$mod+k'`). No default — the taskbar button is the base affordance.
|
|
5
|
+
* Escape always closes while open. */
|
|
6
|
+
hotkey?: string | string[];
|
|
5
7
|
};
|
|
6
8
|
declare const Launcher: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
7
9
|
type Launcher = ReturnType<typeof Launcher>;
|
package/dist/os/wm.svelte.d.ts
CHANGED
|
@@ -126,7 +126,9 @@ export declare class WindowManager {
|
|
|
126
126
|
* singleton — from any workspace; it is brought to the active one). */
|
|
127
127
|
open(appId: string, overrides?: Partial<Pick<AppWindow, 'title' | 'pos' | 'size' | 'resizable' | 'workspaceId'>>): string | null;
|
|
128
128
|
close(id: string): void;
|
|
129
|
-
/**
|
|
129
|
+
/** Make a window the frontmost VISIBLE one: switch to its workspace,
|
|
130
|
+
* un-minimize it, and bring it to the front. Focus means reveal at every
|
|
131
|
+
* level — never a silent off-screen z bump. */
|
|
130
132
|
focus(id: string): void;
|
|
131
133
|
minimize(id: string): void;
|
|
132
134
|
/** Taskbar behaviour: focus if not focused, else minimize; restore if minimized. */
|
package/dist/os/wm.svelte.js
CHANGED
|
@@ -77,7 +77,11 @@ export class WindowManager {
|
|
|
77
77
|
if (app.singleton) {
|
|
78
78
|
const existing = this.windows.find((w) => w.appId === appId);
|
|
79
79
|
if (existing) {
|
|
80
|
-
|
|
80
|
+
// Reveal, don't relocate: restore() → focus() switches to the
|
|
81
|
+
// workspace the window already lives on, the way every virtual
|
|
82
|
+
// desktop behaves. An explicit override still moves it.
|
|
83
|
+
if (overrides?.workspaceId)
|
|
84
|
+
existing.workspaceId = overrides.workspaceId;
|
|
81
85
|
this.restore(existing.id);
|
|
82
86
|
return existing.id;
|
|
83
87
|
}
|
|
@@ -108,11 +112,15 @@ export class WindowManager {
|
|
|
108
112
|
close(id) {
|
|
109
113
|
this.windows = this.windows.filter((w) => w.id !== id);
|
|
110
114
|
}
|
|
111
|
-
/**
|
|
115
|
+
/** Make a window the frontmost VISIBLE one: switch to its workspace,
|
|
116
|
+
* un-minimize it, and bring it to the front. Focus means reveal at every
|
|
117
|
+
* level — never a silent off-screen z bump. */
|
|
112
118
|
focus(id) {
|
|
113
119
|
const w = this.#find(id);
|
|
114
120
|
if (!w)
|
|
115
121
|
return;
|
|
122
|
+
if (w.workspaceId !== this.activeWorkspaceId)
|
|
123
|
+
this.switchWorkspace(w.workspaceId);
|
|
116
124
|
if (w.state === 'minimized')
|
|
117
125
|
w.state = 'normal';
|
|
118
126
|
if (w.z <= this.#z)
|
package/dist/styles/themes.css
CHANGED
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
--color-destructive-fg: oklch(0.96 0 0);
|
|
57
57
|
--color-success: oklch(0.65 0.17 145);
|
|
58
58
|
--color-success-fg: oklch(0.96 0 0);
|
|
59
|
-
--color-warning: oklch(0.8 0.16 85);
|
|
59
|
+
--color-warning: oklch(0.8 0.16 85);
|
|
60
60
|
--color-warning-fg: oklch(0.162 0 0);
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
--color-destructive-fg: oklch(0.98 0 0);
|
|
96
96
|
--color-success: oklch(0.5 0.17 145);
|
|
97
97
|
--color-success-fg: oklch(0.98 0 0);
|
|
98
|
-
--color-warning: oklch(0.72 0.15 85);
|
|
98
|
+
--color-warning: oklch(0.72 0.15 85);
|
|
99
99
|
--color-warning-fg: oklch(0.162 0 0);
|
|
100
100
|
}
|
|
101
101
|
}
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
--color-destructive-fg: oklch(0.98 0 0);
|
|
132
132
|
--color-success: oklch(0.5 0.17 145);
|
|
133
133
|
--color-success-fg: oklch(0.98 0 0);
|
|
134
|
-
--color-warning: oklch(0.72 0.15 85);
|
|
134
|
+
--color-warning: oklch(0.72 0.15 85);
|
|
135
135
|
--color-warning-fg: oklch(0.162 0 0);
|
|
136
136
|
}
|
|
137
137
|
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
--color-destructive-fg: oklch(0.96 0.016 90);
|
|
176
176
|
--color-success: oklch(0.7 0.125 184);
|
|
177
177
|
--color-success-fg: oklch(0.19 0.014 50);
|
|
178
|
-
--color-warning: oklch(0.78 0.14 82);
|
|
178
|
+
--color-warning: oklch(0.78 0.14 82);
|
|
179
179
|
--color-warning-fg: oklch(0.19 0.014 50);
|
|
180
180
|
}
|
|
181
181
|
|
|
@@ -218,7 +218,7 @@
|
|
|
218
218
|
--color-destructive-fg: oklch(0.98 0.012 78);
|
|
219
219
|
--color-success: oklch(0.5 0.13 158);
|
|
220
220
|
--color-success-fg: oklch(0.98 0.012 78);
|
|
221
|
-
--color-warning: oklch(0.7 0.13 80);
|
|
221
|
+
--color-warning: oklch(0.7 0.13 80);
|
|
222
222
|
--color-warning-fg: oklch(0.19 0.014 50);
|
|
223
223
|
}
|
|
224
224
|
}
|
|
@@ -253,7 +253,7 @@
|
|
|
253
253
|
--color-destructive-fg: oklch(0.98 0.012 78);
|
|
254
254
|
--color-success: oklch(0.5 0.13 158);
|
|
255
255
|
--color-success-fg: oklch(0.98 0.012 78);
|
|
256
|
-
--color-warning: oklch(0.7 0.13 80);
|
|
256
|
+
--color-warning: oklch(0.7 0.13 80);
|
|
257
257
|
--color-warning-fg: oklch(0.19 0.014 50);
|
|
258
258
|
}
|
|
259
259
|
|
|
@@ -291,7 +291,7 @@
|
|
|
291
291
|
--color-destructive-fg: var(--color-12);
|
|
292
292
|
--color-success: oklch(0.71 0.09 164);
|
|
293
293
|
--color-success-fg: var(--color-1);
|
|
294
|
-
--color-warning: oklch(0.78 0.1 85);
|
|
294
|
+
--color-warning: oklch(0.78 0.1 85);
|
|
295
295
|
--color-warning-fg: var(--color-1);
|
|
296
296
|
}
|
|
297
297
|
|
|
@@ -329,7 +329,7 @@
|
|
|
329
329
|
--color-destructive-fg: oklch(0.98 0.008 80);
|
|
330
330
|
--color-success: oklch(0.54 0.11 164);
|
|
331
331
|
--color-success-fg: oklch(0.98 0.008 80);
|
|
332
|
-
--color-warning: oklch(0.68 0.1 82);
|
|
332
|
+
--color-warning: oklch(0.68 0.1 82);
|
|
333
333
|
--color-warning-fg: oklch(0.2 0.012 60);
|
|
334
334
|
}
|
|
335
335
|
}
|
|
@@ -364,6 +364,6 @@
|
|
|
364
364
|
--color-destructive-fg: oklch(0.98 0.008 80);
|
|
365
365
|
--color-success: oklch(0.54 0.11 164);
|
|
366
366
|
--color-success-fg: oklch(0.98 0.008 80);
|
|
367
|
-
--color-warning: oklch(0.68 0.1 82);
|
|
367
|
+
--color-warning: oklch(0.68 0.1 82);
|
|
368
368
|
--color-warning-fg: oklch(0.2 0.012 60);
|
|
369
369
|
}
|
|
@@ -2,7 +2,7 @@ import Button from './button.svelte';
|
|
|
2
2
|
export { Button };
|
|
3
3
|
export { buttonVariants } from './variants.js';
|
|
4
4
|
export interface ButtonVariants {
|
|
5
|
-
tone?: 'default' | 'accent' | 'destructive' | 'success';
|
|
5
|
+
tone?: 'default' | 'accent' | 'destructive' | 'success' | 'warning';
|
|
6
6
|
size?: 'default' | 'sm' | 'xs' | 'icon';
|
|
7
7
|
variant?: 'filled' | 'link';
|
|
8
8
|
icon?: boolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
|
3
3
|
import { cn } from '../../utils/index.js';
|
|
4
|
-
import { keys, type KeyBindingMap } from '../../utils/hotkeys.js';
|
|
4
|
+
import { keys, isTextEntryFocused, type KeyBindingMap } from '../../utils/hotkeys.js';
|
|
5
5
|
import type { CommandBarItem } from './types.js';
|
|
6
6
|
|
|
7
7
|
let {
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
/** Empty-state message when the filter matches nothing. */
|
|
18
18
|
empty?: string;
|
|
19
19
|
/** Global binding(s) that focus the bar (tinykeys grammar, e.g.
|
|
20
|
-
* `['
|
|
20
|
+
* `['`', 'Escape']`). While another field has focus only Escape
|
|
21
|
+
* acts — it blurs that field instead of stealing the caret. */
|
|
21
22
|
hotkey?: string | string[];
|
|
22
23
|
class?: string;
|
|
23
24
|
} = $props();
|
|
@@ -131,15 +132,9 @@
|
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
// Focus hotkeys: a central map on window (tinykeys grammar). Guarded the
|
|
134
|
-
// way the terminal guards them —
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
const el = document.activeElement;
|
|
138
|
-
return (
|
|
139
|
-
el instanceof HTMLElement &&
|
|
140
|
-
(el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable)
|
|
141
|
-
);
|
|
142
|
-
}
|
|
135
|
+
// way the terminal guards them — while any field is focused, only Escape
|
|
136
|
+
// acts (it blurs the field); every other binding is ignored so typing a
|
|
137
|
+
// backtick in a text input never steals the caret.
|
|
143
138
|
const bindings = $derived.by((): KeyBindingMap => {
|
|
144
139
|
if (!hotkey) return {};
|
|
145
140
|
const list = Array.isArray(hotkey) ? hotkey : [hotkey];
|
|
@@ -147,7 +142,10 @@
|
|
|
147
142
|
list.map((b) => [
|
|
148
143
|
b,
|
|
149
144
|
(e: KeyboardEvent) => {
|
|
150
|
-
if (
|
|
145
|
+
if (isTextEntryFocused()) {
|
|
146
|
+
if (b === 'Escape') (document.activeElement as HTMLElement).blur();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
151
149
|
e.preventDefault();
|
|
152
150
|
input?.focus();
|
|
153
151
|
}
|
|
@@ -6,7 +6,8 @@ type $$ComponentProps = {
|
|
|
6
6
|
/** Empty-state message when the filter matches nothing. */
|
|
7
7
|
empty?: string;
|
|
8
8
|
/** Global binding(s) that focus the bar (tinykeys grammar, e.g.
|
|
9
|
-
* `['
|
|
9
|
+
* `['`', 'Escape']`). While another field has focus only Escape
|
|
10
|
+
* acts — it blurs that field instead of stealing the caret. */
|
|
10
11
|
hotkey?: string | string[];
|
|
11
12
|
class?: string;
|
|
12
13
|
};
|
package/dist/utils/hotkeys.d.ts
CHANGED
|
@@ -31,13 +31,18 @@ export declare function parseKeybinding(binding: string): KeyBindingPress[];
|
|
|
31
31
|
export declare function matchKeyBindingPress(event: KeyboardEvent, [mods, key]: KeyBindingPress): boolean;
|
|
32
32
|
/** One listener that walks the whole map, tracking partial sequence matches. */
|
|
33
33
|
export declare function createKeybindingsHandler(bindings: KeyBindingMap, options?: KeyBindingOptions): (event: Event) => void;
|
|
34
|
-
/** Attach a binding map to a target; returns the unsubscriber.
|
|
34
|
+
/** Attach a binding map to a target; returns the unsubscriber. An empty map
|
|
35
|
+
* attaches nothing — a consumer with no bindings costs zero per keystroke. */
|
|
35
36
|
export declare function hotkeys(target: Window | HTMLElement, bindings: KeyBindingMap, options?: KeyBindingOptions): () => void;
|
|
36
37
|
/** Svelte action form — `<svelte:window use:keys={{ '$mod+k': open }} />`. */
|
|
37
38
|
export declare function keys(node: Window | HTMLElement, bindings: KeyBindingMap): {
|
|
38
39
|
update(next: KeyBindingMap): void;
|
|
39
40
|
destroy(): void;
|
|
40
41
|
};
|
|
42
|
+
/** Is the user currently typing somewhere? The standard guard for global
|
|
43
|
+
* focus-stealing hotkeys — shared so every consumer agrees on what counts
|
|
44
|
+
* as a text-entry context. */
|
|
45
|
+
export declare function isTextEntryFocused(): boolean;
|
|
41
46
|
/** Format one binding for display: `'$mod+k'` → `⌘K` on Apple, `Ctrl+K`
|
|
42
47
|
* elsewhere. Sequences join with spaces (`'g d'` → `G D`). */
|
|
43
48
|
export declare function formatKeybinding(binding: string): string;
|
package/dist/utils/hotkeys.js
CHANGED
|
@@ -87,8 +87,11 @@ export function createKeybindingsHandler(bindings, options = {}) {
|
|
|
87
87
|
timer = setTimeout(() => possible.clear(), timeout);
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
|
-
/** Attach a binding map to a target; returns the unsubscriber.
|
|
90
|
+
/** Attach a binding map to a target; returns the unsubscriber. An empty map
|
|
91
|
+
* attaches nothing — a consumer with no bindings costs zero per keystroke. */
|
|
91
92
|
export function hotkeys(target, bindings, options = {}) {
|
|
93
|
+
if (Object.keys(bindings).length === 0)
|
|
94
|
+
return () => { };
|
|
92
95
|
const handler = createKeybindingsHandler(bindings, options);
|
|
93
96
|
const event = options.event ?? 'keydown';
|
|
94
97
|
target.addEventListener(event, handler, { capture: options.capture });
|
|
@@ -107,6 +110,16 @@ export function keys(node, bindings) {
|
|
|
107
110
|
}
|
|
108
111
|
};
|
|
109
112
|
}
|
|
113
|
+
/** Is the user currently typing somewhere? The standard guard for global
|
|
114
|
+
* focus-stealing hotkeys — shared so every consumer agrees on what counts
|
|
115
|
+
* as a text-entry context. */
|
|
116
|
+
export function isTextEntryFocused() {
|
|
117
|
+
if (typeof document === 'undefined')
|
|
118
|
+
return false;
|
|
119
|
+
const el = document.activeElement;
|
|
120
|
+
return (el instanceof HTMLElement &&
|
|
121
|
+
(el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable));
|
|
122
|
+
}
|
|
110
123
|
const APPLE_GLYPHS = {
|
|
111
124
|
Meta: '⌘',
|
|
112
125
|
Alt: '⌥',
|