sh3-core 0.16.1 → 0.17.2
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/Sh3.svelte +50 -108
- package/dist/__screenshots__/handheld.browser.test.ts/handheld-viewport-flip-e2e-viewport-override-flips-chrome-and-body-branches-1.png +0 -0
- package/dist/actions/ctx-actions.svelte.test.js +4 -4
- package/dist/actions/listActionsFromEntries.test.js +29 -0
- package/dist/actions/listActive.js +2 -0
- package/dist/actions/listeners.js +4 -0
- package/dist/actions/programmatic-dispatch.svelte.test.js +9 -2
- package/dist/actions/types.d.ts +8 -0
- package/dist/api.d.ts +6 -1
- package/dist/api.js +1 -0
- package/dist/chrome/CompactChrome.svelte +96 -0
- package/dist/chrome/CompactChrome.svelte.d.ts +3 -0
- package/dist/chrome/CompactChrome.svelte.test.d.ts +1 -0
- package/dist/chrome/CompactChrome.svelte.test.js +67 -0
- package/dist/chrome/MenuSheet.svelte +224 -0
- package/dist/chrome/MenuSheet.svelte.d.ts +7 -0
- package/dist/chrome/MenuSheet.svelte.test.d.ts +1 -0
- package/dist/chrome/MenuSheet.svelte.test.js +46 -0
- package/dist/contributions/index.d.ts +1 -1
- package/dist/contributions/index.js +1 -1
- package/dist/contributions/registry.d.ts +17 -1
- package/dist/contributions/registry.js +50 -2
- package/dist/contributions/scope.test.d.ts +1 -0
- package/dist/contributions/scope.test.js +52 -0
- package/dist/contributions/types.d.ts +11 -3
- package/dist/createShell.js +7 -1
- package/dist/fields/address.d.ts +3 -0
- package/dist/fields/address.js +36 -0
- package/dist/fields/address.test.d.ts +1 -0
- package/dist/fields/address.test.js +34 -0
- package/dist/fields/decoration.d.ts +7 -0
- package/dist/fields/decoration.js +199 -0
- package/dist/fields/decoration.svelte.test.d.ts +1 -0
- package/dist/fields/decoration.svelte.test.js +177 -0
- package/dist/fields/dispatch.d.ts +22 -0
- package/dist/fields/dispatch.js +254 -0
- package/dist/fields/dispatch.test.d.ts +1 -0
- package/dist/fields/dispatch.test.js +175 -0
- package/dist/fields/types.d.ts +101 -0
- package/dist/fields/types.js +16 -0
- package/dist/fields/walker.svelte.test.d.ts +1 -0
- package/dist/fields/walker.svelte.test.js +138 -0
- package/dist/handheld.browser.test.d.ts +1 -0
- package/dist/handheld.browser.test.js +90 -0
- package/dist/host.js +27 -2
- package/dist/host.svelte.test.d.ts +1 -0
- package/dist/host.svelte.test.js +92 -0
- package/dist/layout/LayoutRenderer.svelte +12 -1
- package/dist/layout/LayoutRenderer.svelte.d.ts +2 -1
- package/dist/layout/compact/CompactRenderer.svelte +53 -0
- package/dist/layout/compact/CompactRenderer.svelte.d.ts +3 -0
- package/dist/layout/compact/CompactRenderer.svelte.test.d.ts +1 -0
- package/dist/layout/compact/CompactRenderer.svelte.test.js +76 -0
- package/dist/layout/compact/derive.d.ts +3 -0
- package/dist/layout/compact/derive.js +155 -0
- package/dist/layout/compact/derive.test.d.ts +1 -0
- package/dist/layout/compact/derive.test.js +160 -0
- package/dist/layout/compact/drawerStore.svelte.d.ts +21 -0
- package/dist/layout/compact/drawerStore.svelte.js +75 -0
- package/dist/layout/compact/drawerStore.svelte.test.d.ts +1 -0
- package/dist/layout/compact/drawerStore.svelte.test.js +43 -0
- package/dist/layout/compact/resolveRole.d.ts +6 -0
- package/dist/layout/compact/resolveRole.js +13 -0
- package/dist/layout/compact/resolveRole.test.d.ts +1 -0
- package/dist/layout/compact/resolveRole.test.js +18 -0
- package/dist/layout/compact/types.d.ts +27 -0
- package/dist/layout/compact/types.js +15 -0
- package/dist/layout/presets.compactVariant.test.d.ts +1 -0
- package/dist/layout/presets.compactVariant.test.js +27 -0
- package/dist/layout/presets.d.ts +12 -0
- package/dist/layout/presets.js +16 -0
- package/dist/layout/slotHostPool.svelte.d.ts +8 -0
- package/dist/layout/slotHostPool.svelte.js +14 -1
- package/dist/layout/store.drawers.svelte.test.d.ts +1 -0
- package/dist/layout/store.drawers.svelte.test.js +49 -0
- package/dist/layout/store.schemaVersion.test.d.ts +1 -0
- package/dist/layout/store.schemaVersion.test.js +35 -0
- package/dist/layout/store.svelte.js +52 -2
- package/dist/layout/types.d.ts +43 -1
- package/dist/layout/types.js +1 -1
- package/dist/overlays/DrawerSurface.svelte +141 -0
- package/dist/overlays/DrawerSurface.svelte.d.ts +12 -0
- package/dist/overlays/DrawerSurface.svelte.test.d.ts +1 -0
- package/dist/overlays/DrawerSurface.svelte.test.js +67 -0
- package/dist/overlays/OverlayRoots.svelte +89 -0
- package/dist/overlays/OverlayRoots.svelte.d.ts +3 -0
- package/dist/overlays/types.d.ts +1 -1
- package/dist/platform/tauri-backend.d.ts +3 -3
- package/dist/platform/tauri-backend.js +24 -3
- package/dist/projects/session-state.svelte.d.ts +3 -3
- package/dist/projects/session-state.svelte.js +5 -4
- package/dist/runtime/runVerb.js +2 -2
- package/dist/satellite/SatelliteShell.svelte +58 -11
- package/dist/satellite/SatelliteShell.svelte.test.d.ts +1 -0
- package/dist/satellite/SatelliteShell.svelte.test.js +61 -0
- package/dist/sh3Api/fields-walker.svelte.test.d.ts +1 -0
- package/dist/sh3Api/fields-walker.svelte.test.js +75 -0
- package/dist/sh3Api/headless.d.ts +9 -0
- package/dist/sh3Api/headless.js +171 -16
- package/dist/sh3Api/headless.svelte.test.js +54 -10
- package/dist/sh3Runtime.svelte.d.ts +36 -0
- package/dist/sh3Runtime.svelte.js +33 -0
- package/dist/sh3core-shard/sh3coreShard.svelte.js +2 -2
- package/dist/shards/activate-fields.svelte.test.d.ts +1 -0
- package/dist/shards/activate-fields.svelte.test.js +121 -0
- package/dist/shards/activate-runtime.test.js +8 -8
- package/dist/shards/activate.svelte.js +29 -35
- package/dist/shards/types.d.ts +23 -76
- package/dist/shell-shard/ScrollbackView.svelte +55 -9
- package/dist/shell-shard/Terminal.svelte +1 -1
- package/dist/shell-shard/scrollback-stick.d.ts +9 -0
- package/dist/shell-shard/scrollback-stick.js +21 -0
- package/dist/shell-shard/scrollback-stick.test.d.ts +1 -0
- package/dist/shell-shard/scrollback-stick.test.js +25 -0
- package/dist/tokens.css +3 -2
- package/dist/verbs/types.d.ts +59 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/viewport/classify.d.ts +8 -0
- package/dist/viewport/classify.js +20 -0
- package/dist/viewport/classify.test.d.ts +1 -0
- package/dist/viewport/classify.test.js +32 -0
- package/dist/viewport/store.browser.test.d.ts +1 -0
- package/dist/viewport/store.browser.test.js +33 -0
- package/dist/viewport/store.svelte.d.ts +9 -0
- package/dist/viewport/store.svelte.js +71 -0
- package/dist/viewport/store.svelte.test.d.ts +1 -0
- package/dist/viewport/store.svelte.test.js +54 -0
- package/dist/viewport/types.d.ts +9 -0
- package/dist/viewport/types.js +6 -0
- package/package.json +1 -1
package/dist/Sh3.svelte
CHANGED
|
@@ -15,11 +15,7 @@
|
|
|
15
15
|
import './tokens.css';
|
|
16
16
|
import './primitives/base.css';
|
|
17
17
|
import LayoutRenderer from './layout/LayoutRenderer.svelte';
|
|
18
|
-
import
|
|
19
|
-
import FloatLayer from './overlays/FloatLayer.svelte';
|
|
20
|
-
import type { OverlayLayer } from './overlays/types';
|
|
21
|
-
import { registerLayerRoot, unregisterLayerRoot } from './overlays/roots';
|
|
22
|
-
import { bindFloatStore, unbindFloatStore } from './overlays/float';
|
|
18
|
+
import OverlayRoots from './overlays/OverlayRoots.svelte';
|
|
23
19
|
import { returnToHome, isAdmin } from './api';
|
|
24
20
|
import { getActiveRoot, layoutStore } from './layout/store.svelte';
|
|
25
21
|
import { syncMountedViewIdsFromLayout } from './actions/state.svelte';
|
|
@@ -30,6 +26,9 @@
|
|
|
30
26
|
import { startServerSideStream } from './keys/revocation-bus.svelte';
|
|
31
27
|
import BrandSlot from './BrandSlot.svelte';
|
|
32
28
|
import MenuBar from './actions/MenuBar.svelte';
|
|
29
|
+
import CompactChrome from './chrome/CompactChrome.svelte';
|
|
30
|
+
import CompactRenderer from './layout/compact/CompactRenderer.svelte';
|
|
31
|
+
import { sh3 } from './sh3Runtime.svelte';
|
|
33
32
|
|
|
34
33
|
const authenticated = $derived(isAuthenticated());
|
|
35
34
|
const user = $derived(getUser());
|
|
@@ -38,41 +37,8 @@
|
|
|
38
37
|
// getActiveApp() here: returnToHome() keeps the app warm (activeApp.id
|
|
39
38
|
// stays set) and only flips the layout store's activeRoot back to 'home'.
|
|
40
39
|
const onHome = $derived(getActiveRoot() === 'home');
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// Index 0 here is layer 1 (floating panels); layer 0 is the content area.
|
|
44
|
-
const overlayLayers: { layer: number; name: OverlayLayer }[] = [
|
|
45
|
-
{ layer: 1, name: 'floating' },
|
|
46
|
-
{ layer: 2, name: 'drag-preview' },
|
|
47
|
-
{ layer: 3, name: 'popup' },
|
|
48
|
-
{ layer: 4, name: 'modal' },
|
|
49
|
-
{ layer: 5, name: 'toast' },
|
|
50
|
-
{ layer: 6, name: 'command' },
|
|
51
|
-
];
|
|
52
|
-
|
|
53
|
-
// Populated by bind:this during render; registered with the overlay
|
|
54
|
-
// module via $effect after mount so layer managers (sh3.modal,
|
|
55
|
-
// sh3.popup, sh3.toast) can find their target DOM roots.
|
|
56
|
-
const overlayRoots: Partial<Record<OverlayLayer, HTMLDivElement>> = $state({});
|
|
57
|
-
|
|
58
|
-
$effect(() => {
|
|
59
|
-
for (const { name } of overlayLayers) {
|
|
60
|
-
const el = overlayRoots[name];
|
|
61
|
-
if (el) registerLayerRoot(name, el);
|
|
62
|
-
}
|
|
63
|
-
return () => {
|
|
64
|
-
for (const { name } of overlayLayers) unregisterLayerRoot(name);
|
|
65
|
-
};
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
$effect(() => {
|
|
69
|
-
const tree = layoutStore.tree;
|
|
70
|
-
bindFloatStore(tree.floats, () => ({
|
|
71
|
-
w: window.innerWidth,
|
|
72
|
-
h: window.innerHeight,
|
|
73
|
-
}));
|
|
74
|
-
return () => unbindFloatStore();
|
|
75
|
-
});
|
|
40
|
+
// Reactive viewport class — drives the compact-vs-desktop chrome/body fork.
|
|
41
|
+
const viewportClass = $derived(sh3.viewport.current.class);
|
|
76
42
|
|
|
77
43
|
// Keep the actions dispatcher's `mountedViewIds` set in sync with the
|
|
78
44
|
// live layout tree, so `view:<viewId>` scope checks (context menu,
|
|
@@ -99,73 +65,60 @@
|
|
|
99
65
|
});
|
|
100
66
|
</script>
|
|
101
67
|
|
|
102
|
-
<div class="sh3">
|
|
103
|
-
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
68
|
+
<div class="sh3" data-sh3-viewport={viewportClass}>
|
|
69
|
+
{#if viewportClass === 'desktop'}
|
|
70
|
+
<header class="sh3-tabbar" data-sh3-region="tabbar">
|
|
71
|
+
<button
|
|
72
|
+
type="button"
|
|
73
|
+
class="sh3-tabbar-home-button"
|
|
74
|
+
onclick={() => returnToHome()}
|
|
75
|
+
disabled={onHome}
|
|
76
|
+
title="Home"
|
|
77
|
+
>
|
|
78
|
+
<svg class="sh3-tabbar-home-icon" aria-hidden="true">
|
|
79
|
+
<use href="{iconsUrl}#house" />
|
|
80
|
+
</svg>
|
|
81
|
+
</button>
|
|
82
|
+
<BrandSlot />
|
|
83
|
+
<MenuBar />
|
|
84
|
+
{#if authenticated && user}
|
|
85
|
+
<div class="sh3-tabbar-user">
|
|
86
|
+
<span class="sh3-tabbar-user-name">{user.displayName}</span>
|
|
87
|
+
<span class="sh3-tabbar-tag">{elevated ? 'admin' : 'user'}</span>
|
|
88
|
+
{#if !isLocalOwner()}
|
|
89
|
+
<button
|
|
90
|
+
type="button"
|
|
91
|
+
class="sh3-tabbar-signout"
|
|
92
|
+
onclick={() => logout()}
|
|
93
|
+
title="Sign out"
|
|
94
|
+
>
|
|
95
|
+
<svg class="sh3-tabbar-signout-icon" aria-hidden="true">
|
|
96
|
+
<use href="{iconsUrl}#log-out" />
|
|
97
|
+
</svg>
|
|
98
|
+
</button>
|
|
99
|
+
{/if}
|
|
100
|
+
</div>
|
|
101
|
+
{/if}
|
|
102
|
+
</header>
|
|
103
|
+
{:else}
|
|
104
|
+
<CompactChrome />
|
|
105
|
+
{/if}
|
|
136
106
|
|
|
137
107
|
<GuestBanner />
|
|
138
108
|
|
|
139
109
|
<main class="sh3-content" data-sh3-region="content" data-sh3-layer="0">
|
|
140
|
-
|
|
110
|
+
{#if viewportClass === 'desktop'}
|
|
111
|
+
<LayoutRenderer />
|
|
112
|
+
{:else}
|
|
113
|
+
<CompactRenderer />
|
|
114
|
+
{/if}
|
|
141
115
|
</main>
|
|
142
116
|
|
|
143
117
|
<footer class="sh3-statusbar" data-sh3-region="statusbar">
|
|
144
118
|
<!-- alpha tag moved to Sh3Home title row -->
|
|
145
119
|
</footer>
|
|
146
120
|
|
|
147
|
-
|
|
148
|
-
Overlay roots. Each is absolutely positioned over the entire sh3 with
|
|
149
|
-
pointer-events: none by default; layer managers enable pointer events on
|
|
150
|
-
the specific surfaces they portal in.
|
|
151
|
-
-->
|
|
152
|
-
<div class="sh3-overlays" aria-hidden="true">
|
|
153
|
-
{#each overlayLayers as { layer, name } (layer)}
|
|
154
|
-
<div
|
|
155
|
-
class="sh3-overlay-root"
|
|
156
|
-
data-sh3-overlay={name}
|
|
157
|
-
data-sh3-layer={layer}
|
|
158
|
-
style="z-index: var(--sh3-z-layer-{layer});"
|
|
159
|
-
bind:this={overlayRoots[name]}
|
|
160
|
-
>
|
|
161
|
-
{#if name === 'floating'}
|
|
162
|
-
<FloatLayer />
|
|
163
|
-
{:else if name === 'drag-preview'}
|
|
164
|
-
<DragPreview />
|
|
165
|
-
{/if}
|
|
166
|
-
</div>
|
|
167
|
-
{/each}
|
|
168
|
-
</div>
|
|
121
|
+
<OverlayRoots />
|
|
169
122
|
|
|
170
123
|
<!--
|
|
171
124
|
Sh3-owned consent dialog for ctx.keys.mint().
|
|
@@ -217,17 +170,6 @@
|
|
|
217
170
|
user-select: none;
|
|
218
171
|
}
|
|
219
172
|
|
|
220
|
-
.sh3-overlays {
|
|
221
|
-
position: absolute;
|
|
222
|
-
inset: 0;
|
|
223
|
-
pointer-events: none;
|
|
224
|
-
}
|
|
225
|
-
.sh3-overlay-root {
|
|
226
|
-
position: absolute;
|
|
227
|
-
inset: 0;
|
|
228
|
-
pointer-events: none;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
173
|
.sh3-tabbar-home-button {
|
|
232
174
|
display: flex;
|
|
233
175
|
align-items: center;
|
|
Binary file
|
|
@@ -35,7 +35,7 @@ describe('ShardContext.listActions / runAction (integration)', () => {
|
|
|
35
35
|
});
|
|
36
36
|
await activateShard('producer');
|
|
37
37
|
await activateShard('consumer');
|
|
38
|
-
const list = consumerCtx.listActions();
|
|
38
|
+
const list = consumerCtx.sh3.listActions();
|
|
39
39
|
const ids = list.map((d) => d.id);
|
|
40
40
|
expect(ids).toContain('producer.do');
|
|
41
41
|
const desc = list.find((d) => d.id === 'producer.do');
|
|
@@ -62,7 +62,7 @@ describe('ShardContext.listActions / runAction (integration)', () => {
|
|
|
62
62
|
});
|
|
63
63
|
await activateShard('producer');
|
|
64
64
|
await activateShard('consumer');
|
|
65
|
-
const snapshot = consumerCtx.listActions({ activeOnly: true });
|
|
65
|
+
const snapshot = consumerCtx.sh3.listActions({ activeOnly: true });
|
|
66
66
|
const ids = snapshot.map((d) => d.id);
|
|
67
67
|
expect(ids).toContain('home.go');
|
|
68
68
|
expect(ids).not.toContain('app.go');
|
|
@@ -87,7 +87,7 @@ describe('ShardContext.listActions / runAction (integration)', () => {
|
|
|
87
87
|
});
|
|
88
88
|
await activateShard('producer');
|
|
89
89
|
await activateShard('consumer');
|
|
90
|
-
await consumerCtx.runAction('producer.go');
|
|
90
|
+
await consumerCtx.sh3.runAction('producer.go');
|
|
91
91
|
expect(invokedVia).toBe('programmatic');
|
|
92
92
|
});
|
|
93
93
|
it('runAction rejects when the target action is inactive', async () => {
|
|
@@ -106,6 +106,6 @@ describe('ShardContext.listActions / runAction (integration)', () => {
|
|
|
106
106
|
});
|
|
107
107
|
await activateShard('producer');
|
|
108
108
|
await activateShard('consumer');
|
|
109
|
-
await expect(consumerCtx.runAction('gated.go')).rejects.toThrow(/not active/);
|
|
109
|
+
await expect(consumerCtx.sh3.runAction('gated.go')).rejects.toThrow(/not active/);
|
|
110
110
|
});
|
|
111
111
|
});
|
|
@@ -75,4 +75,33 @@ describe('listActionsFromEntries', () => {
|
|
|
75
75
|
expect(out).toHaveLength(1);
|
|
76
76
|
expect(out[0].ownerShardId).toBe('shard.a');
|
|
77
77
|
});
|
|
78
|
+
it('passes through submenu=true on a parent descriptor', () => {
|
|
79
|
+
const entries = [{
|
|
80
|
+
ownerShardId: 'shard.x',
|
|
81
|
+
action: { id: 'p', label: 'P', scope: 'home', submenu: true },
|
|
82
|
+
}];
|
|
83
|
+
const out = listActionsFromEntries(entries, mkState());
|
|
84
|
+
expect(out[0].submenu).toBe(true);
|
|
85
|
+
expect(out[0].submenuOf).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
it('passes through submenuOf on a child descriptor', () => {
|
|
88
|
+
const entries = [
|
|
89
|
+
{
|
|
90
|
+
ownerShardId: 'shard.x',
|
|
91
|
+
action: {
|
|
92
|
+
id: 'p:dark', label: 'Dark', scope: 'home',
|
|
93
|
+
submenuOf: 'p', run: () => { },
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
const out = listActionsFromEntries(entries, mkState());
|
|
98
|
+
expect(out[0].submenuOf).toBe('p');
|
|
99
|
+
expect(out[0].submenu).toBeUndefined();
|
|
100
|
+
});
|
|
101
|
+
it('omits submenu and submenuOf on plain actions', () => {
|
|
102
|
+
const entries = [mkEntry({ id: 'plain', scope: 'home' })];
|
|
103
|
+
const out = listActionsFromEntries(entries, mkState());
|
|
104
|
+
expect(out[0].submenu).toBeUndefined();
|
|
105
|
+
expect(out[0].submenuOf).toBeUndefined();
|
|
106
|
+
});
|
|
78
107
|
});
|
|
@@ -50,6 +50,8 @@ export function listActionsFromEntries(entries, state) {
|
|
|
50
50
|
ownerShardId: entry.ownerShardId,
|
|
51
51
|
paletteItem: entry.action.paletteItem !== false,
|
|
52
52
|
contextItem: entry.action.contextItem !== false,
|
|
53
|
+
submenu: entry.action.submenu,
|
|
54
|
+
submenuOf: entry.action.submenuOf,
|
|
53
55
|
active,
|
|
54
56
|
};
|
|
55
57
|
if (active) {
|
|
@@ -94,6 +94,10 @@ export function dispatchActionProgrammatic(actionId, _opts) {
|
|
|
94
94
|
const state = getLiveDispatcherState();
|
|
95
95
|
const desc = listActionsFromEntries(entries, state).find((d) => d.id === actionId);
|
|
96
96
|
if (!desc || !desc.active) {
|
|
97
|
+
if (entry.action.submenu === true &&
|
|
98
|
+
typeof entry.action.run !== 'function') {
|
|
99
|
+
return Promise.reject(new Error(`action "${actionId}" is a submenu parent — list children with listActions({ submenuOf: "${actionId}" })`));
|
|
100
|
+
}
|
|
97
101
|
return Promise.reject(new Error(`action "${actionId}" is not active in current scope`));
|
|
98
102
|
}
|
|
99
103
|
// run is guaranteed non-null by `desc.active === true`.
|
|
@@ -34,11 +34,18 @@ describe('dispatchActionProgrammatic', () => {
|
|
|
34
34
|
await expect(dispatchActionProgrammatic('d')).rejects.toThrow(/action "d" is not active/);
|
|
35
35
|
expect(run).not.toHaveBeenCalled();
|
|
36
36
|
});
|
|
37
|
-
it('rejects on a submenu parent
|
|
37
|
+
it('rejects on a submenu parent with the parent-specific message', async () => {
|
|
38
38
|
registerAction({
|
|
39
39
|
id: 's', label: 'S', scope: 'home', submenu: true,
|
|
40
40
|
}, 'shard.x');
|
|
41
|
-
await expect(dispatchActionProgrammatic('s')).rejects.toThrow(/action "s" is
|
|
41
|
+
await expect(dispatchActionProgrammatic('s')).rejects.toThrow(/action "s" is a submenu parent — list children with listActions\(\{ submenuOf: "s" \}\)/);
|
|
42
|
+
});
|
|
43
|
+
it('still uses the generic inactive message for non-submenu inactive actions', async () => {
|
|
44
|
+
registerAction({
|
|
45
|
+
id: 'gated2', label: 'G', scope: 'app', run: () => { },
|
|
46
|
+
}, 'shard.x');
|
|
47
|
+
// No active app -> 'app' scope is inactive.
|
|
48
|
+
await expect(dispatchActionProgrammatic('gated2')).rejects.toThrow(/action "gated2" is not active/);
|
|
42
49
|
});
|
|
43
50
|
it('invokes run with invokedVia="programmatic" on happy path', async () => {
|
|
44
51
|
let captured = null;
|
package/dist/actions/types.d.ts
CHANGED
|
@@ -148,6 +148,10 @@ export interface ActiveActionDescriptor {
|
|
|
148
148
|
ownerShardId: string;
|
|
149
149
|
paletteItem: boolean;
|
|
150
150
|
contextItem: boolean;
|
|
151
|
+
/** True when this action is a submenu parent (children opened by drill). */
|
|
152
|
+
submenu?: true;
|
|
153
|
+
/** Parent action id when this action is a submenu child. */
|
|
154
|
+
submenuOf?: string;
|
|
151
155
|
}
|
|
152
156
|
/**
|
|
153
157
|
* Read-only snapshot describing one action in the registry. Produced by
|
|
@@ -183,6 +187,10 @@ export interface ActionDescriptor {
|
|
|
183
187
|
ownerShardId: string;
|
|
184
188
|
paletteItem: boolean;
|
|
185
189
|
contextItem: boolean;
|
|
190
|
+
/** True when this action is a submenu parent (children opened by drill). */
|
|
191
|
+
submenu?: true;
|
|
192
|
+
/** Parent action id when this action is a submenu child. */
|
|
193
|
+
submenuOf?: string;
|
|
186
194
|
/** True when `runAction(id)` would dispatch right now. */
|
|
187
195
|
active: boolean;
|
|
188
196
|
}
|
package/dist/api.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export { sh3 } from './sh3Runtime.svelte';
|
|
2
|
-
export type { Sh3 } from './sh3Runtime.svelte';
|
|
2
|
+
export type { Sh3, Sh3Drawers, Sh3Viewport } from './sh3Runtime.svelte';
|
|
3
|
+
export type { ViewportClass, ViewportInfo } from './viewport/types';
|
|
4
|
+
export type { SlotRole } from './layout/types';
|
|
5
|
+
export type { DrawerAnchor, DrawerSpec, DrawerState, DrawerStateMap, CompactRendering, } from './layout/compact/types';
|
|
3
6
|
export type { Shard, ShardManifest, SourceShard, SourceShardManifest, ShardContext, ViewDeclaration, ViewFactory, ViewHandle, MountContext, } from './shards/types';
|
|
4
7
|
export type { LayoutNode, SplitNode, TabsNode, SlotNode, TabEntry, SplitDirection, SizeMode, LayoutTree, FloatEntry, LayoutPreset, CanonicalPreset, TreeRootRef as SlotLocation, } from './layout/types';
|
|
5
8
|
export type { ActionDescriptor, ActiveActionDescriptor, BindingSource, AtomicScope, ActionScope, } from './actions/types';
|
|
@@ -80,3 +83,5 @@ export { default as Select } from './primitives/widgets/Select.svelte';
|
|
|
80
83
|
export type { SelectOption } from './primitives/widgets/Select';
|
|
81
84
|
export { default as AppPicker } from './primitives/widgets/AppPicker.svelte';
|
|
82
85
|
export { default as UserPicker } from './primitives/widgets/UserPicker.svelte';
|
|
86
|
+
export type { FieldKind, FieldAddress, FieldView, ControllableFieldDescriptor, ImperativeFieldDescriptor, ElementRefFieldDescriptor, ReadonlyFieldDescriptor, FieldsApi, DecorationHandle, } from './fields/types';
|
|
87
|
+
export { fieldAddressToString, fieldAddressFromString } from './fields/address';
|
package/dist/api.js
CHANGED
|
@@ -97,3 +97,4 @@ export { default as FilePicker } from './primitives/widgets/FilePicker.svelte';
|
|
|
97
97
|
export { default as Select } from './primitives/widgets/Select.svelte';
|
|
98
98
|
export { default as AppPicker } from './primitives/widgets/AppPicker.svelte';
|
|
99
99
|
export { default as UserPicker } from './primitives/widgets/UserPicker.svelte';
|
|
100
|
+
export { fieldAddressToString, fieldAddressFromString } from './fields/address';
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* Top app bar for compact mode. Three-column grid:
|
|
4
|
+
* leading — one button per non-null drawer anchor (read from
|
|
5
|
+
* the active CompactRendering)
|
|
6
|
+
* title — active app name
|
|
7
|
+
* trailing — palette button + overflow (menu sheet) button
|
|
8
|
+
*
|
|
9
|
+
* MenuSheet handles the overflow menu; this component owns the open
|
|
10
|
+
* state and renders MenuSheet conditionally.
|
|
11
|
+
*/
|
|
12
|
+
import { sh3 } from '../sh3Runtime.svelte';
|
|
13
|
+
import { layoutStore } from '../layout/store.svelte';
|
|
14
|
+
import { derive } from '../layout/compact/derive';
|
|
15
|
+
import { getLiveDispatcherState } from '../actions/state.svelte';
|
|
16
|
+
import { getRegisteredApp } from '../apps/registry.svelte';
|
|
17
|
+
import MenuSheet from './MenuSheet.svelte';
|
|
18
|
+
import type { DrawerAnchor } from '../layout/compact/types';
|
|
19
|
+
|
|
20
|
+
const rendering = $derived(derive(layoutStore.root));
|
|
21
|
+
const dispatcher = $derived(getLiveDispatcherState());
|
|
22
|
+
const title = $derived.by(() => {
|
|
23
|
+
const id = dispatcher.activeAppId;
|
|
24
|
+
if (!id) return 'SH3';
|
|
25
|
+
return getRegisteredApp(id)?.manifest.label ?? id;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
let menuOpen = $state(false);
|
|
29
|
+
|
|
30
|
+
function toggleDrawer(anchor: DrawerAnchor) {
|
|
31
|
+
sh3.drawers.toggle(anchor);
|
|
32
|
+
}
|
|
33
|
+
function openPalette() {
|
|
34
|
+
sh3.actions.openPalette();
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<header class="sh3-compact-chrome" data-sh3-region="compact-chrome">
|
|
39
|
+
<div class="leading">
|
|
40
|
+
{#if rendering.drawers.left}
|
|
41
|
+
<button onclick={() => toggleDrawer('left')} aria-label="Toggle left drawer" data-sh3-anchor="left">≡</button>
|
|
42
|
+
{/if}
|
|
43
|
+
{#if rendering.drawers.right}
|
|
44
|
+
<button onclick={() => toggleDrawer('right')} aria-label="Toggle right drawer" data-sh3-anchor="right">▣</button>
|
|
45
|
+
{/if}
|
|
46
|
+
{#if rendering.drawers.top}
|
|
47
|
+
<button onclick={() => toggleDrawer('top')} aria-label="Toggle top drawer" data-sh3-anchor="top">⫶</button>
|
|
48
|
+
{/if}
|
|
49
|
+
</div>
|
|
50
|
+
<div class="title">{title}</div>
|
|
51
|
+
<div class="trailing">
|
|
52
|
+
<button onclick={openPalette} aria-label="Open command palette">⌘</button>
|
|
53
|
+
<button onclick={() => (menuOpen = true)} aria-label="Open menu">⋯</button>
|
|
54
|
+
</div>
|
|
55
|
+
</header>
|
|
56
|
+
|
|
57
|
+
<MenuSheet open={menuOpen} onClose={() => (menuOpen = false)} />
|
|
58
|
+
|
|
59
|
+
<style>
|
|
60
|
+
.sh3-compact-chrome {
|
|
61
|
+
display: grid;
|
|
62
|
+
grid-template-columns: auto 1fr auto;
|
|
63
|
+
align-items: center;
|
|
64
|
+
height: var(--sh3-tabbar-height);
|
|
65
|
+
padding: 0 var(--sh3-pad-sm);
|
|
66
|
+
gap: var(--sh3-pad-sm);
|
|
67
|
+
background: var(--sh3-grad-bg-elevated, var(--sh3-bg-elevated));
|
|
68
|
+
border-bottom: 1px solid var(--sh3-border);
|
|
69
|
+
color: var(--sh3-fg);
|
|
70
|
+
}
|
|
71
|
+
.leading,
|
|
72
|
+
.trailing {
|
|
73
|
+
display: inline-flex;
|
|
74
|
+
gap: var(--sh3-pad-xs);
|
|
75
|
+
}
|
|
76
|
+
button {
|
|
77
|
+
width: 40px;
|
|
78
|
+
height: 40px;
|
|
79
|
+
font-size: var(--sh3-font-lg);
|
|
80
|
+
border: none;
|
|
81
|
+
background: none;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
border-radius: var(--sh3-radius-sm);
|
|
84
|
+
color: var(--sh3-fg);
|
|
85
|
+
}
|
|
86
|
+
button:active {
|
|
87
|
+
background: var(--sh3-bg-sunken);
|
|
88
|
+
}
|
|
89
|
+
.title {
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
text-align: center;
|
|
92
|
+
overflow: hidden;
|
|
93
|
+
text-overflow: ellipsis;
|
|
94
|
+
white-space: nowrap;
|
|
95
|
+
}
|
|
96
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* DOM smoke for CompactChrome — verifies the toolbar renders the
|
|
3
|
+
* expected leading drawer toggles based on the active layout's
|
|
4
|
+
* derived rendering, plus the trailing palette + overflow buttons.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
7
|
+
import { mount, unmount, flushSync } from 'svelte';
|
|
8
|
+
import CompactChrome from './CompactChrome.svelte';
|
|
9
|
+
import { __resetLayoutStoreForTest, attachApp, detachApp, switchToApp, } from '../layout/store.svelte';
|
|
10
|
+
import { drawerStore } from '../layout/compact/drawerStore.svelte';
|
|
11
|
+
const CompactChromeAny = CompactChrome;
|
|
12
|
+
function fakeApp() {
|
|
13
|
+
return {
|
|
14
|
+
manifest: { id: 'cc-app', label: 'CC App', layoutVersion: 5 },
|
|
15
|
+
initialLayout: {
|
|
16
|
+
type: 'split', direction: 'horizontal', sizes: [0.2, 0.6, 0.2],
|
|
17
|
+
children: [
|
|
18
|
+
{ type: 'slot', slotId: 'sb', viewId: 'v:sb', role: 'sidebar' },
|
|
19
|
+
{ type: 'slot', slotId: 'body', viewId: 'v:body', role: 'body' },
|
|
20
|
+
{ type: 'slot', slotId: 'ins', viewId: 'v:ins', role: 'inspector' },
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
let mounted = null;
|
|
26
|
+
let host = null;
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
if (mounted) {
|
|
29
|
+
unmount(mounted);
|
|
30
|
+
mounted = null;
|
|
31
|
+
}
|
|
32
|
+
if (host) {
|
|
33
|
+
host.remove();
|
|
34
|
+
host = null;
|
|
35
|
+
}
|
|
36
|
+
detachApp();
|
|
37
|
+
});
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
__resetLayoutStoreForTest();
|
|
40
|
+
drawerStore.__reset();
|
|
41
|
+
});
|
|
42
|
+
describe('CompactChrome (dom)', () => {
|
|
43
|
+
it('renders a leading toggle for each present drawer anchor', () => {
|
|
44
|
+
attachApp(fakeApp());
|
|
45
|
+
switchToApp();
|
|
46
|
+
flushSync();
|
|
47
|
+
host = document.createElement('div');
|
|
48
|
+
document.body.appendChild(host);
|
|
49
|
+
mounted = mount(CompactChromeAny, { target: host });
|
|
50
|
+
flushSync();
|
|
51
|
+
const leading = host.querySelectorAll('.leading button');
|
|
52
|
+
expect(leading.length).toBe(2);
|
|
53
|
+
expect(host.querySelector('.leading button[data-sh3-anchor="left"]')).not.toBeNull();
|
|
54
|
+
expect(host.querySelector('.leading button[data-sh3-anchor="right"]')).not.toBeNull();
|
|
55
|
+
});
|
|
56
|
+
it('renders palette + overflow buttons in the trailing section', () => {
|
|
57
|
+
attachApp(fakeApp());
|
|
58
|
+
switchToApp();
|
|
59
|
+
flushSync();
|
|
60
|
+
host = document.createElement('div');
|
|
61
|
+
document.body.appendChild(host);
|
|
62
|
+
mounted = mount(CompactChromeAny, { target: host });
|
|
63
|
+
flushSync();
|
|
64
|
+
const trailing = host.querySelectorAll('.trailing button');
|
|
65
|
+
expect(trailing.length).toBe(2);
|
|
66
|
+
});
|
|
67
|
+
});
|