@stnd/launcher 1.2.0 → 1.3.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/CHANGELOG.md +12 -0
- package/Launcher.svelte +134 -1
- package/README.md +9 -6
- package/launcher.ts +6 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
package/Launcher.svelte
CHANGED
|
@@ -33,7 +33,14 @@
|
|
|
33
33
|
// --- PROPS (all optional — the Launcher is fully self-booting) ---
|
|
34
34
|
// Apps wire services via provide/inject (the Standard OS runtime store).
|
|
35
35
|
// Props exist only for backward compat or one-off overrides.
|
|
36
|
-
let {
|
|
36
|
+
let {
|
|
37
|
+
WelcomeView = null,
|
|
38
|
+
FooterView = null,
|
|
39
|
+
footer = null,
|
|
40
|
+
showFooter = false,
|
|
41
|
+
load = null,
|
|
42
|
+
hideLogin = false,
|
|
43
|
+
} = $props();
|
|
37
44
|
|
|
38
45
|
// --- STATE (Synced with Engine) ---
|
|
39
46
|
let pState = $state({
|
|
@@ -380,6 +387,21 @@
|
|
|
380
387
|
return null;
|
|
381
388
|
});
|
|
382
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
|
+
|
|
383
405
|
// Determine view size from registry meta
|
|
384
406
|
let activeViewSize = $derived.by(() => {
|
|
385
407
|
// 1. Dynamic Override
|
|
@@ -639,6 +661,57 @@
|
|
|
639
661
|
<div class="std-launcher-empty">Nothing came up.</div>
|
|
640
662
|
{/if}
|
|
641
663
|
</div>
|
|
664
|
+
|
|
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={() => {
|
|
681
|
+
if (registryState.views["::account-settings"]) {
|
|
682
|
+
launcherView("::account-settings");
|
|
683
|
+
} else if (registryState.views["::settings"]) {
|
|
684
|
+
launcherView("::settings");
|
|
685
|
+
} else {
|
|
686
|
+
launcherView("::my-space");
|
|
687
|
+
}
|
|
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}
|
|
706
|
+
</div>
|
|
707
|
+
|
|
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}
|
|
713
|
+
</footer>
|
|
714
|
+
{/if}
|
|
642
715
|
</div>
|
|
643
716
|
</div>
|
|
644
717
|
{/if}
|
|
@@ -984,6 +1057,66 @@
|
|
|
984
1057
|
font-size: var(--scale-d2);
|
|
985
1058
|
}
|
|
986
1059
|
|
|
1060
|
+
/* Optional Footer */
|
|
1061
|
+
.std-launcher-footer {
|
|
1062
|
+
display: flex;
|
|
1063
|
+
justify-content: space-between;
|
|
1064
|
+
align-items: center;
|
|
1065
|
+
padding: var(--space-d4) var(--space-d2);
|
|
1066
|
+
min-height: 28px;
|
|
1067
|
+
font-size: var(--scale-d2, 0.75rem);
|
|
1068
|
+
color: var(--color-muted);
|
|
1069
|
+
border-top: var(--border);
|
|
1070
|
+
background-color: var(--color-surface-high);
|
|
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);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
.std-launcher-user {
|
|
1082
|
+
display: flex;
|
|
1083
|
+
align-items: center;
|
|
1084
|
+
gap: var(--space-d4);
|
|
1085
|
+
color: var(--color-foreground-muted);
|
|
1086
|
+
padding: var(--space-d5) var(--space-d3);
|
|
1087
|
+
border-radius: var(--radius);
|
|
1088
|
+
transition: all var(--transition-fast);
|
|
1089
|
+
cursor: pointer;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
.std-launcher-user:hover {
|
|
1093
|
+
background: var(--color-surface-highest);
|
|
1094
|
+
color: var(--color-foreground);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
.std-launcher-user span {
|
|
1098
|
+
max-width: 150px;
|
|
1099
|
+
white-space: nowrap;
|
|
1100
|
+
overflow: hidden;
|
|
1101
|
+
text-overflow: ellipsis;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
.std-launcher-hints {
|
|
1105
|
+
display: flex;
|
|
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);
|
|
1119
|
+
}
|
|
987
1120
|
|
|
988
1121
|
/* Mobile: Bottom Sheet */
|
|
989
1122
|
@media (max-width: 640px) {
|
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.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"svelte": "5.57.0",
|
|
22
22
|
"wrangler": "^4.129.0",
|
|
23
|
-
"@stnd/account": "0.2.
|
|
23
|
+
"@stnd/account": "0.2.12",
|
|
24
24
|
"@stnd/client": "0.7.0",
|
|
25
25
|
"@stnd/icon": "0.2.2",
|
|
26
26
|
"@stnd/log": "0.5.1",
|