@stnd/launcher 1.1.0 → 1.3.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/CHANGELOG.md +12 -0
- package/Launcher.svelte +89 -75
- package/README.md +9 -6
- package/launcher.ts +6 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
package/Launcher.svelte
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* Integrated with the Standard Garden OS aesthetic.
|
|
6
6
|
*/
|
|
7
7
|
import Icon from "@stnd/icon/Icon.svelte";
|
|
8
|
-
import Avatar from "@stnd/account/ui/Avatar.svelte";
|
|
9
8
|
import {
|
|
10
9
|
registry,
|
|
11
10
|
palette,
|
|
@@ -34,22 +33,14 @@
|
|
|
34
33
|
// --- PROPS (all optional — the Launcher is fully self-booting) ---
|
|
35
34
|
// Apps wire services via provide/inject (the Standard OS runtime store).
|
|
36
35
|
// Props exist only for backward compat or one-off overrides.
|
|
37
|
-
let {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
(typeof window !== "undefined" && !!window.__INITIAL_USER__),
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
// Check global window variable as fallback (set via window.__HIDE_LAUNCHER_LOGIN__)
|
|
49
|
-
let shouldHideLogin = $derived(
|
|
50
|
-
hideLogin ||
|
|
51
|
-
(typeof window !== "undefined" && window.__HIDE_LAUNCHER_LOGIN__),
|
|
52
|
-
);
|
|
36
|
+
let {
|
|
37
|
+
WelcomeView = null,
|
|
38
|
+
FooterView = null,
|
|
39
|
+
footer = null,
|
|
40
|
+
showFooter = false,
|
|
41
|
+
load = null,
|
|
42
|
+
hideLogin = false,
|
|
43
|
+
} = $props();
|
|
53
44
|
|
|
54
45
|
// --- STATE (Synced with Engine) ---
|
|
55
46
|
let pState = $state({
|
|
@@ -396,6 +387,21 @@
|
|
|
396
387
|
return null;
|
|
397
388
|
});
|
|
398
389
|
|
|
390
|
+
// --- FOOTER RESOLUTION (Optional) ---
|
|
391
|
+
let ActiveFooterComponent = $derived.by(() => {
|
|
392
|
+
if (FooterView) return FooterView;
|
|
393
|
+
if (registryState.views["::footer"]) return registryState.views["::footer"];
|
|
394
|
+
return inject("launcher:footer", null);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
let shouldRenderFooter = $derived.by(() => {
|
|
398
|
+
if (footer || ActiveFooterComponent) return true;
|
|
399
|
+
const show = showFooter || inject("launcher:showFooter", false);
|
|
400
|
+
if (!show) return false;
|
|
401
|
+
if (ActiveViewComponent && show !== "always") return false;
|
|
402
|
+
return true;
|
|
403
|
+
});
|
|
404
|
+
|
|
399
405
|
// Determine view size from registry meta
|
|
400
406
|
let activeViewSize = $derived.by(() => {
|
|
401
407
|
// 1. Dynamic Override
|
|
@@ -656,26 +662,22 @@
|
|
|
656
662
|
{/if}
|
|
657
663
|
</div>
|
|
658
664
|
|
|
659
|
-
<!--
|
|
660
|
-
{#if
|
|
661
|
-
<footer class="std-launcher-footer
|
|
662
|
-
|
|
663
|
-
{
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
676
|
-
}}
|
|
677
|
-
onkeydown={(e) => {
|
|
678
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
665
|
+
<!-- Optional Launcher Footer -->
|
|
666
|
+
{#if shouldRenderFooter}
|
|
667
|
+
<footer class="std-launcher-footer">
|
|
668
|
+
{#if footer}
|
|
669
|
+
{@render footer({ currentUser, pState, close: launcherClose })}
|
|
670
|
+
{:else if ActiveFooterComponent}
|
|
671
|
+
<ActiveFooterComponent {currentUser} {pState} close={launcherClose} />
|
|
672
|
+
{:else}
|
|
673
|
+
<!-- Default Navigation Hints & Info -->
|
|
674
|
+
<div class="std-launcher-footer-left">
|
|
675
|
+
{#if currentUser && currentUser.isAuthenticated}
|
|
676
|
+
<div
|
|
677
|
+
class="std-launcher-user"
|
|
678
|
+
role="button"
|
|
679
|
+
tabindex="0"
|
|
680
|
+
onclick={() => {
|
|
679
681
|
if (registryState.views["::account-settings"]) {
|
|
680
682
|
launcherView("::account-settings");
|
|
681
683
|
} else if (registryState.views["::settings"]) {
|
|
@@ -683,38 +685,31 @@
|
|
|
683
685
|
} else {
|
|
684
686
|
launcherView("::my-space");
|
|
685
687
|
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
onclick={() => launcherView("::login")}
|
|
705
|
-
>
|
|
706
|
-
Login
|
|
707
|
-
</button>
|
|
708
|
-
{/if}
|
|
709
|
-
<div class="std-launcher-mode smaller inline hidden">
|
|
710
|
-
COMMAND MODE
|
|
688
|
+
}}
|
|
689
|
+
onkeydown={(e) => {
|
|
690
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
691
|
+
if (registryState.views["::account-settings"]) {
|
|
692
|
+
launcherView("::account-settings");
|
|
693
|
+
} else if (registryState.views["::settings"]) {
|
|
694
|
+
launcherView("::settings");
|
|
695
|
+
} else {
|
|
696
|
+
launcherView("::my-space");
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}}
|
|
700
|
+
>
|
|
701
|
+
<span class="smaller muted">
|
|
702
|
+
{currentUser.name || currentUser.username || currentUser.email}
|
|
703
|
+
</span>
|
|
704
|
+
</div>
|
|
705
|
+
{/if}
|
|
711
706
|
</div>
|
|
712
|
-
</div>
|
|
713
707
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
708
|
+
<div class="std-launcher-hints">
|
|
709
|
+
<span><kbd>↑</kbd> <kbd>↓</kbd> navigate</span>
|
|
710
|
+
<span><kbd>esc</kbd> close</span>
|
|
711
|
+
</div>
|
|
712
|
+
{/if}
|
|
718
713
|
</footer>
|
|
719
714
|
{/if}
|
|
720
715
|
</div>
|
|
@@ -920,7 +915,7 @@
|
|
|
920
915
|
Naming it is the point: views used to hardcode `var(--space)` to undo a
|
|
921
916
|
padding they could only guess, so the two drifted apart and content got
|
|
922
917
|
clipped at the frame. One name, one source of truth. */
|
|
923
|
-
--launcher-pad:
|
|
918
|
+
--launcher-pad: var(--space);
|
|
924
919
|
}
|
|
925
920
|
|
|
926
921
|
/* Full-bleed views (settings, welcome horizon) manage their own internal layout */
|
|
@@ -1062,29 +1057,36 @@
|
|
|
1062
1057
|
font-size: var(--scale-d2);
|
|
1063
1058
|
}
|
|
1064
1059
|
|
|
1065
|
-
/* Footer
|
|
1060
|
+
/* Optional Footer */
|
|
1066
1061
|
.std-launcher-footer {
|
|
1067
1062
|
display: flex;
|
|
1068
1063
|
justify-content: space-between;
|
|
1069
1064
|
align-items: center;
|
|
1070
|
-
padding:
|
|
1065
|
+
padding: var(--space-d4) var(--space-d2);
|
|
1071
1066
|
min-height: 28px;
|
|
1072
|
-
font-size: var(--
|
|
1067
|
+
font-size: var(--scale-d2, 0.75rem);
|
|
1073
1068
|
color: var(--color-muted);
|
|
1074
1069
|
border-top: var(--border);
|
|
1075
1070
|
background-color: var(--color-surface-high);
|
|
1076
|
-
|
|
1077
1071
|
box-shadow: var(--shadow-raised);
|
|
1072
|
+
flex-shrink: 0;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
.std-launcher-footer-left {
|
|
1076
|
+
display: flex;
|
|
1077
|
+
align-items: center;
|
|
1078
|
+
gap: var(--space-d3);
|
|
1078
1079
|
}
|
|
1079
1080
|
|
|
1080
1081
|
.std-launcher-user {
|
|
1081
1082
|
display: flex;
|
|
1082
1083
|
align-items: center;
|
|
1083
|
-
gap: var(--space-d4
|
|
1084
|
+
gap: var(--space-d4);
|
|
1084
1085
|
color: var(--color-foreground-muted);
|
|
1085
|
-
padding:
|
|
1086
|
+
padding: var(--space-d5) var(--space-d3);
|
|
1086
1087
|
border-radius: var(--radius);
|
|
1087
1088
|
transition: all var(--transition-fast);
|
|
1089
|
+
cursor: pointer;
|
|
1088
1090
|
}
|
|
1089
1091
|
|
|
1090
1092
|
.std-launcher-user:hover {
|
|
@@ -1101,7 +1103,19 @@
|
|
|
1101
1103
|
|
|
1102
1104
|
.std-launcher-hints {
|
|
1103
1105
|
display: flex;
|
|
1104
|
-
|
|
1106
|
+
align-items: center;
|
|
1107
|
+
gap: var(--space-d1);
|
|
1108
|
+
font-size: var(--scale-d2, 0.75rem);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
.std-launcher-hints kbd {
|
|
1112
|
+
font-family: var(--font-mono, monospace);
|
|
1113
|
+
font-size: var(--scale-d3, 0.7rem);
|
|
1114
|
+
padding: 1px var(--space-d4);
|
|
1115
|
+
border-radius: var(--radius-sm, 3px);
|
|
1116
|
+
background: var(--color-surface-highest);
|
|
1117
|
+
border: var(--border);
|
|
1118
|
+
color: var(--color-foreground);
|
|
1105
1119
|
}
|
|
1106
1120
|
|
|
1107
1121
|
/* Mobile: Bottom Sheet */
|
package/README.md
CHANGED
|
@@ -65,12 +65,15 @@ Import and mount the `<Launcher />` component in your layout:
|
|
|
65
65
|
|
|
66
66
|
### Props
|
|
67
67
|
|
|
68
|
-
| Prop | Type | Default | Description
|
|
69
|
-
| :------------ | :------------------------------- | :------ |
|
|
70
|
-
| `load` | `string \| string[] \| function` | `null` | Filter specific commands/views to load.
|
|
71
|
-
| `userStore` | `readable` | `null` | Svelte store containing the current user.
|
|
72
|
-
| `searchNotes` | `function` | `null` | Async function to search app content.
|
|
73
|
-
| `WelcomeView` | `Component` | `null` | Custom component for the empty state.
|
|
68
|
+
| Prop | Type | Default | Description |
|
|
69
|
+
| :------------ | :------------------------------- | :------ | :----------------------------------------------------- |
|
|
70
|
+
| `load` | `string \| string[] \| function` | `null` | Filter specific commands/views to load. |
|
|
71
|
+
| `userStore` | `readable` | `null` | Svelte store containing the current user. |
|
|
72
|
+
| `searchNotes` | `function` | `null` | Async function to search app content. |
|
|
73
|
+
| `WelcomeView` | `Component` | `null` | Custom component for the empty state. |
|
|
74
|
+
| `FooterView` | `Component` | `null` | Custom component for the optional launcher footer. |
|
|
75
|
+
| `footer` | `Snippet` | `null` | Svelte 5 snippet for custom footer content. |
|
|
76
|
+
| `showFooter` | `boolean \| "always"` | `false` | Enable built-in footer (navigation hints & info). |
|
|
74
77
|
|
|
75
78
|
## Core Concepts
|
|
76
79
|
|
package/launcher.ts
CHANGED
|
@@ -117,11 +117,15 @@ export function configureLauncher(
|
|
|
117
117
|
searchNotes?: (query: string) => Promise<LauncherResult[]>;
|
|
118
118
|
userStore?: any;
|
|
119
119
|
filter?: (trigger: string, meta: CommandMeta) => boolean;
|
|
120
|
+
footer?: any;
|
|
121
|
+
showFooter?: boolean | "always";
|
|
120
122
|
} = {},
|
|
121
123
|
) {
|
|
122
124
|
// Legacy compat: if callers still pass these, push them into the runtime store
|
|
123
125
|
if (options.searchNotes) provide("search", options.searchNotes);
|
|
124
126
|
if (options.userStore) provide("user", options.userStore);
|
|
127
|
+
if (options.footer) provide("launcher:footer", options.footer);
|
|
128
|
+
if (options.showFooter !== undefined) provide("launcher:showFooter", options.showFooter);
|
|
125
129
|
if (options.filter) appFilter = options.filter;
|
|
126
130
|
}
|
|
127
131
|
|
|
@@ -135,6 +139,8 @@ export function initLauncher(
|
|
|
135
139
|
searchNotes?: (query: string) => Promise<LauncherResult[]>;
|
|
136
140
|
userStore?: any;
|
|
137
141
|
filter?: (trigger: string, meta: CommandMeta) => boolean;
|
|
142
|
+
footer?: any;
|
|
143
|
+
showFooter?: boolean | "always";
|
|
138
144
|
} = {},
|
|
139
145
|
) {
|
|
140
146
|
configureLauncher(options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stnd/launcher",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"svelte": "5.57.0",
|
|
22
22
|
"wrangler": "^4.129.0",
|
|
23
|
+
"@stnd/account": "0.2.11",
|
|
23
24
|
"@stnd/client": "0.7.0",
|
|
24
|
-
"@stnd/log": "0.5.1",
|
|
25
25
|
"@stnd/icon": "0.2.2",
|
|
26
|
-
"@stnd/
|
|
26
|
+
"@stnd/log": "0.5.1",
|
|
27
27
|
"@stnd/store": "0.5.1",
|
|
28
28
|
"@stnd/ui": "0.6.1"
|
|
29
29
|
},
|