sh3-core 0.15.1 → 0.15.3
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/actions/ctx-actions.svelte.test.js +111 -0
- package/dist/actions/dispatcher.svelte.js +23 -2
- package/dist/actions/dispatcher.test.js +33 -0
- package/dist/actions/listActionsFromEntries.test.js +78 -0
- package/dist/actions/listActive.d.ts +2 -1
- package/dist/actions/listActive.js +43 -17
- package/dist/actions/listeners.d.ts +16 -0
- package/dist/actions/listeners.js +68 -14
- package/dist/actions/programmatic-dispatch.svelte.test.d.ts +1 -0
- package/dist/actions/programmatic-dispatch.svelte.test.js +98 -0
- package/dist/actions/types.d.ts +37 -0
- package/dist/api.d.ts +5 -2
- package/dist/api.js +2 -0
- package/dist/app-appearance/appearanceShard.svelte.js +19 -6
- package/dist/app-appearance/appearanceState.svelte.js +3 -3
- package/dist/apps/lifecycle.d.ts +21 -2
- package/dist/apps/lifecycle.js +13 -7
- package/dist/apps/lifecycle.test.js +18 -0
- package/dist/boot/satelliteMode.d.ts +7 -0
- package/dist/boot/satelliteMode.js +22 -0
- package/dist/boot/satelliteMode.test.d.ts +1 -0
- package/dist/boot/satelliteMode.test.js +55 -0
- package/dist/boot/satellitePayload.d.ts +17 -0
- package/dist/boot/satellitePayload.js +60 -0
- package/dist/boot/satellitePayload.test.d.ts +1 -0
- package/dist/boot/satellitePayload.test.js +53 -0
- package/dist/createShell.js +72 -25
- package/dist/host.d.ts +13 -0
- package/dist/host.js +38 -1
- package/dist/layout/store.svelte.d.ts +11 -0
- package/dist/layout/store.svelte.js +15 -0
- package/dist/layouts-shard/LayoutSaveModal.svelte +145 -0
- package/dist/layouts-shard/LayoutSaveModal.svelte.d.ts +12 -0
- package/dist/layouts-shard/LayoutsSection.svelte +142 -0
- package/dist/layouts-shard/LayoutsSection.svelte.d.ts +3 -0
- package/dist/layouts-shard/filter.d.ts +3 -0
- package/dist/layouts-shard/filter.js +66 -0
- package/dist/layouts-shard/filter.test.d.ts +1 -0
- package/dist/layouts-shard/filter.test.js +123 -0
- package/dist/layouts-shard/index.d.ts +1 -0
- package/dist/layouts-shard/index.js +1 -0
- package/dist/layouts-shard/layoutsApi.d.ts +12 -0
- package/dist/layouts-shard/layoutsApi.js +41 -0
- package/dist/layouts-shard/layoutsApi.test.d.ts +1 -0
- package/dist/layouts-shard/layoutsApi.test.js +74 -0
- package/dist/layouts-shard/layoutsShard.svelte.d.ts +11 -0
- package/dist/layouts-shard/layoutsShard.svelte.js +231 -0
- package/dist/layouts-shard/layoutsShard.svelte.test.d.ts +1 -0
- package/dist/layouts-shard/layoutsShard.svelte.test.js +215 -0
- package/dist/layouts-shard/layoutsState.svelte.d.ts +9 -0
- package/dist/layouts-shard/layoutsState.svelte.js +50 -0
- package/dist/layouts-shard/layoutsState.test.d.ts +1 -0
- package/dist/layouts-shard/layoutsState.test.js +43 -0
- package/dist/layouts-shard/types.d.ts +21 -0
- package/dist/layouts-shard/types.js +6 -0
- package/dist/{app-appearance/AppAppearanceModal.svelte → overlays/EntityAppearanceModal.svelte} +36 -31
- package/dist/overlays/EntityAppearanceModal.svelte.d.ts +19 -0
- package/dist/overlays/EntityAppearanceModal.test.d.ts +1 -0
- package/dist/overlays/EntityAppearanceModal.test.js +57 -0
- package/dist/overlays/FloatFrame.svelte +53 -0
- package/dist/overlays/float.d.ts +17 -1
- package/dist/overlays/float.js +16 -0
- package/dist/overlays/float.test.js +35 -0
- package/dist/runtime/runVerb-shell.test.js +0 -39
- package/dist/runtime/runVerb.test.js +17 -0
- package/dist/satellite/SatelliteShell.svelte +60 -0
- package/dist/satellite/SatelliteShell.svelte.d.ts +9 -0
- package/dist/satellite/seed.d.ts +3 -0
- package/dist/satellite/seed.js +20 -0
- package/dist/satellite/seed.test.d.ts +1 -0
- package/dist/satellite/seed.test.js +38 -0
- package/dist/satellite/walkShards.d.ts +2 -0
- package/dist/satellite/walkShards.js +44 -0
- package/dist/satellite/walkShards.test.d.ts +1 -0
- package/dist/satellite/walkShards.test.js +65 -0
- package/dist/sh3core-shard/ShellHome.svelte +3 -0
- package/dist/sh3core-shard/appActions.js +51 -0
- package/dist/shards/activate.svelte.d.ts +2 -2
- package/dist/shards/activate.svelte.js +12 -3
- package/dist/shards/registry.d.ts +2 -1
- package/dist/shards/registry.js +13 -4
- package/dist/shards/registry.test.js +22 -1
- package/dist/shards/types.d.ts +34 -1
- package/dist/shell-shard/CommandLine.svelte +146 -0
- package/dist/shell-shard/CommandLine.svelte.d.ts +27 -0
- package/dist/shell-shard/CommandLine.svelte.test.d.ts +1 -0
- package/dist/shell-shard/CommandLine.svelte.test.js +43 -0
- package/dist/shell-shard/InputLine.svelte +20 -40
- package/dist/shell-shard/InputLine.svelte.d.ts +4 -0
- package/dist/shell-shard/ScrollbackView.svelte +10 -3
- package/dist/shell-shard/ScrollbackView.svelte.d.ts +1 -0
- package/dist/shell-shard/Terminal.svelte +117 -22
- package/dist/shell-shard/buffer-store.d.ts +15 -0
- package/dist/shell-shard/buffer-store.js +124 -0
- package/dist/shell-shard/buffer-store.svelte.test.d.ts +1 -0
- package/dist/shell-shard/buffer-store.svelte.test.js +107 -0
- package/dist/shell-shard/buffer-zone-state.svelte.d.ts +38 -0
- package/dist/shell-shard/buffer-zone-state.svelte.js +31 -0
- package/dist/shell-shard/contract.d.ts +7 -0
- package/dist/shell-shard/dispatch-custom.test.js +3 -1
- package/dist/shell-shard/dispatch-gating.test.js +6 -2
- package/dist/shell-shard/dispatch-invoke.test.js +10 -8
- package/dist/shell-shard/dispatch-to-terminal.d.ts +13 -0
- package/dist/shell-shard/dispatch-to-terminal.js +37 -0
- package/dist/shell-shard/dispatch-to-terminal.test.d.ts +1 -0
- package/dist/shell-shard/dispatch-to-terminal.test.js +79 -0
- package/dist/shell-shard/dispatch.d.ts +7 -2
- package/dist/shell-shard/dispatch.js +23 -27
- package/dist/shell-shard/display-cwd.d.ts +1 -0
- package/dist/shell-shard/display-cwd.js +27 -0
- package/dist/shell-shard/display-cwd.test.d.ts +1 -0
- package/dist/shell-shard/display-cwd.test.js +29 -0
- package/dist/shell-shard/entries/StatusEntry.svelte +2 -0
- package/dist/shell-shard/manifest.js +2 -1
- package/dist/shell-shard/manifest.test.d.ts +1 -0
- package/dist/shell-shard/manifest.test.js +8 -0
- package/dist/shell-shard/mode-buffer.svelte.d.ts +8 -0
- package/dist/shell-shard/mode-buffer.svelte.js +19 -0
- package/dist/shell-shard/mode-buffer.svelte.test.d.ts +1 -0
- package/dist/shell-shard/mode-buffer.svelte.test.js +25 -0
- package/dist/shell-shard/modes/builtin.js +2 -0
- package/dist/shell-shard/modes/types.d.ts +8 -0
- package/dist/shell-shard/protocol.d.ts +12 -6
- package/dist/shell-shard/replay.d.ts +3 -0
- package/dist/shell-shard/replay.js +44 -0
- package/dist/shell-shard/replay.svelte.test.d.ts +1 -0
- package/dist/shell-shard/replay.svelte.test.js +47 -0
- package/dist/shell-shard/rich-registry.d.ts +5 -0
- package/dist/shell-shard/rich-registry.js +25 -0
- package/dist/shell-shard/rich-registry.test.d.ts +1 -0
- package/dist/shell-shard/rich-registry.test.js +31 -0
- package/dist/shell-shard/scrollback.svelte.d.ts +2 -0
- package/dist/shell-shard/scrollback.svelte.js +23 -0
- package/dist/shell-shard/scrollback.svelte.test.d.ts +1 -0
- package/dist/shell-shard/scrollback.svelte.test.js +51 -0
- package/dist/shell-shard/session-client.svelte.d.ts +18 -2
- package/dist/shell-shard/session-client.svelte.js +21 -4
- package/dist/shell-shard/shellApi.d.ts +2 -1
- package/dist/shell-shard/shellApi.js +33 -3
- package/dist/shell-shard/shellApi.svelte.test.d.ts +1 -0
- package/dist/shell-shard/shellApi.svelte.test.js +59 -0
- package/dist/shell-shard/shellShard.svelte.js +11 -1
- package/dist/shell-shard/terminal-dispatch.test.js +3 -1
- package/dist/shell-shard/terminal-registry.d.ts +25 -0
- package/dist/shell-shard/terminal-registry.js +62 -0
- package/dist/shell-shard/terminal-registry.test.d.ts +1 -0
- package/dist/shell-shard/terminal-registry.test.js +88 -0
- package/dist/shell-shard/verbs/apps.js +7 -0
- package/dist/shell-shard/verbs/env.js +4 -0
- package/dist/shell-shard/verbs/help.js +4 -0
- package/dist/shell-shard/verbs/history.js +8 -1
- package/dist/shell-shard/verbs/index.js +0 -8
- package/dist/shell-shard/verbs/shards.js +4 -0
- package/dist/shell-shard/verbs/views.js +4 -0
- package/dist/shell-shard/verbs/zones.js +7 -0
- package/dist/shellApi/window.d.ts +15 -0
- package/dist/shellApi/window.js +43 -0
- package/dist/shellApi/window.test.d.ts +1 -0
- package/dist/shellApi/window.test.js +19 -0
- package/dist/verbs/types.d.ts +15 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/app-appearance/AppAppearanceModal.svelte.d.ts +0 -8
- package/dist/shell-shard/verbs/cat.d.ts +0 -2
- package/dist/shell-shard/verbs/cat.js +0 -35
- package/dist/shell-shard/verbs/cd.test.js +0 -56
- package/dist/shell-shard/verbs/ls.d.ts +0 -2
- package/dist/shell-shard/verbs/ls.js +0 -30
- package/dist/shell-shard/verbs/ls.test.js +0 -49
- package/dist/shell-shard/verbs/session.d.ts +0 -4
- package/dist/shell-shard/verbs/session.js +0 -99
- /package/dist/{shell-shard/verbs/cd.test.d.ts → actions/ctx-actions.svelte.test.d.ts} +0 -0
- /package/dist/{shell-shard/verbs/ls.test.d.ts → actions/listActionsFromEntries.test.d.ts} +0 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { MemoryDocumentBackend } from '../documents/backends';
|
|
3
|
+
import { __setDocumentBackend, __setTenantId } from '../documents/config';
|
|
4
|
+
import { registerShard, activateShard, __resetShardRegistryForTest, } from '../shards/activate.svelte';
|
|
5
|
+
import { __resetViewRegistryForTest } from '../shards/registry';
|
|
6
|
+
import { __resetActionsRegistryForTest } from './registry';
|
|
7
|
+
import { __resetContributionsForTest } from '../contributions/registry';
|
|
8
|
+
import { __resetDispatcherStateForTest } from './state.svelte';
|
|
9
|
+
describe('ShardContext.listActions / runAction (integration)', () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
__resetShardRegistryForTest();
|
|
12
|
+
__resetViewRegistryForTest();
|
|
13
|
+
__resetContributionsForTest();
|
|
14
|
+
__resetActionsRegistryForTest();
|
|
15
|
+
__resetDispatcherStateForTest();
|
|
16
|
+
__setDocumentBackend(new MemoryDocumentBackend());
|
|
17
|
+
__setTenantId('tenant-test');
|
|
18
|
+
});
|
|
19
|
+
it('listActions enumerates actions registered by other shards', async () => {
|
|
20
|
+
registerShard({
|
|
21
|
+
manifest: { id: 'producer', label: 'P', version: '0.0.0', views: [] },
|
|
22
|
+
activate(ctx) {
|
|
23
|
+
ctx.actions.register({
|
|
24
|
+
id: 'producer.do',
|
|
25
|
+
label: 'Do',
|
|
26
|
+
scope: 'home',
|
|
27
|
+
run: () => { },
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
let consumerCtx = null;
|
|
32
|
+
registerShard({
|
|
33
|
+
manifest: { id: 'consumer', label: 'C', version: '0.0.0', views: [] },
|
|
34
|
+
activate(ctx) { consumerCtx = ctx; },
|
|
35
|
+
});
|
|
36
|
+
await activateShard('producer');
|
|
37
|
+
await activateShard('consumer');
|
|
38
|
+
const list = consumerCtx.listActions();
|
|
39
|
+
const ids = list.map((d) => d.id);
|
|
40
|
+
expect(ids).toContain('producer.do');
|
|
41
|
+
const desc = list.find((d) => d.id === 'producer.do');
|
|
42
|
+
expect(desc.active).toBe(true);
|
|
43
|
+
expect(desc.ownerShardId).toBe('producer');
|
|
44
|
+
});
|
|
45
|
+
it('listActions({ activeOnly: true }) filters out inactive actions', async () => {
|
|
46
|
+
registerShard({
|
|
47
|
+
manifest: { id: 'producer', label: 'P', version: '0.0.0', views: [] },
|
|
48
|
+
activate(ctx) {
|
|
49
|
+
ctx.actions.register({
|
|
50
|
+
id: 'home.go', label: 'H', scope: 'home', run: () => { },
|
|
51
|
+
});
|
|
52
|
+
ctx.actions.register({
|
|
53
|
+
// 'app' tier inactive without an active app
|
|
54
|
+
id: 'app.go', label: 'A', scope: 'app', run: () => { },
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
let consumerCtx = null;
|
|
59
|
+
registerShard({
|
|
60
|
+
manifest: { id: 'consumer', label: 'C', version: '0.0.0', views: [] },
|
|
61
|
+
activate(ctx) { consumerCtx = ctx; },
|
|
62
|
+
});
|
|
63
|
+
await activateShard('producer');
|
|
64
|
+
await activateShard('consumer');
|
|
65
|
+
const snapshot = consumerCtx.listActions({ activeOnly: true });
|
|
66
|
+
const ids = snapshot.map((d) => d.id);
|
|
67
|
+
expect(ids).toContain('home.go');
|
|
68
|
+
expect(ids).not.toContain('app.go');
|
|
69
|
+
});
|
|
70
|
+
it('runAction dispatches through the programmatic path', async () => {
|
|
71
|
+
let invokedVia = null;
|
|
72
|
+
registerShard({
|
|
73
|
+
manifest: { id: 'producer', label: 'P', version: '0.0.0', views: [] },
|
|
74
|
+
activate(ctx) {
|
|
75
|
+
ctx.actions.register({
|
|
76
|
+
id: 'producer.go',
|
|
77
|
+
label: 'Go',
|
|
78
|
+
scope: 'home',
|
|
79
|
+
run: (rctx) => { invokedVia = rctx.invokedVia; },
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
let consumerCtx = null;
|
|
84
|
+
registerShard({
|
|
85
|
+
manifest: { id: 'consumer', label: 'C', version: '0.0.0', views: [] },
|
|
86
|
+
activate(ctx) { consumerCtx = ctx; },
|
|
87
|
+
});
|
|
88
|
+
await activateShard('producer');
|
|
89
|
+
await activateShard('consumer');
|
|
90
|
+
await consumerCtx.runAction('producer.go');
|
|
91
|
+
expect(invokedVia).toBe('programmatic');
|
|
92
|
+
});
|
|
93
|
+
it('runAction rejects when the target action is inactive', async () => {
|
|
94
|
+
registerShard({
|
|
95
|
+
manifest: { id: 'producer', label: 'P', version: '0.0.0', views: [] },
|
|
96
|
+
activate(ctx) {
|
|
97
|
+
ctx.actions.register({
|
|
98
|
+
id: 'gated.go', label: 'G', scope: 'app', run: () => { },
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
let consumerCtx = null;
|
|
103
|
+
registerShard({
|
|
104
|
+
manifest: { id: 'consumer', label: 'C', version: '0.0.0', views: [] },
|
|
105
|
+
activate(ctx) { consumerCtx = ctx; },
|
|
106
|
+
});
|
|
107
|
+
await activateShard('producer');
|
|
108
|
+
await activateShard('consumer');
|
|
109
|
+
await expect(consumerCtx.runAction('gated.go')).rejects.toThrow(/not active/);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -76,6 +76,22 @@ function isTextInput(target) {
|
|
|
76
76
|
return true;
|
|
77
77
|
return false;
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* An input opted into letting Ctrl/Alt/Meta-bearing shortcuts pass through
|
|
81
|
+
* to the global dispatcher. Plain typing (letters, Shift+letters, Tab,
|
|
82
|
+
* Escape, Enter, etc.) still goes to the input. Marked via
|
|
83
|
+
* `data-sh3-passthrough-modifiers` on the input or any ancestor; used by
|
|
84
|
+
* command-style inputs (e.g. the shell terminal) so the user can hit
|
|
85
|
+
* Ctrl+K mid-line and still get the palette.
|
|
86
|
+
*/
|
|
87
|
+
function isModifierPassthroughTarget(target) {
|
|
88
|
+
if (!(target instanceof Element))
|
|
89
|
+
return false;
|
|
90
|
+
return target.closest('[data-sh3-passthrough-modifiers]') !== null;
|
|
91
|
+
}
|
|
92
|
+
function shortcutHasRealModifier(shortcut) {
|
|
93
|
+
return /(?:^|\+)(?:Ctrl|Alt|Meta)\+/.test(shortcut);
|
|
94
|
+
}
|
|
79
95
|
export function dispatchKeydown(env) {
|
|
80
96
|
var _a, _b, _c;
|
|
81
97
|
const idx = buildTierIndex(env.entries, env.state);
|
|
@@ -87,9 +103,14 @@ export function dispatchKeydown(env) {
|
|
|
87
103
|
const entry = actionById.get(id);
|
|
88
104
|
if (!entry)
|
|
89
105
|
continue;
|
|
90
|
-
// Input-target blocking
|
|
106
|
+
// Input-target blocking. Inputs opted into modifier passthrough still
|
|
107
|
+
// let Ctrl/Alt/Meta-bearing shortcuts through so global bindings like
|
|
108
|
+
// Ctrl+K reach the dispatcher while the user is typing.
|
|
91
109
|
if (isTextInput(env.target) && !entry.action.allowInInputs) {
|
|
92
|
-
|
|
110
|
+
const passthrough = isModifierPassthroughTarget(env.target)
|
|
111
|
+
&& shortcutHasRealModifier(env.shortcut);
|
|
112
|
+
if (!passthrough)
|
|
113
|
+
return null;
|
|
93
114
|
}
|
|
94
115
|
env.runAction(id, {
|
|
95
116
|
action: { id: entry.action.id, label: resolveLabel(entry.action) },
|
|
@@ -152,4 +152,37 @@ describe('dispatchKeydown', () => {
|
|
|
152
152
|
const result = dispatchKeydown(mkEnv({ entries, target: div }));
|
|
153
153
|
expect(result).toBeNull();
|
|
154
154
|
});
|
|
155
|
+
it('lets Ctrl-bearing shortcuts through when input has data-sh3-passthrough-modifiers', () => {
|
|
156
|
+
const wrap = document.createElement('div');
|
|
157
|
+
wrap.setAttribute('data-sh3-passthrough-modifiers', '');
|
|
158
|
+
const inp = document.createElement('input');
|
|
159
|
+
wrap.appendChild(inp);
|
|
160
|
+
const entries = [mkEntry({ id: 'x', scope: 'home', defaultShortcut: 'Ctrl+K' })];
|
|
161
|
+
const result = dispatchKeydown(mkEnv({ entries, target: inp, shortcut: 'Ctrl+K' }));
|
|
162
|
+
expect(result).toBe('x');
|
|
163
|
+
});
|
|
164
|
+
it('still blocks plain (no-modifier) shortcuts on passthrough inputs', () => {
|
|
165
|
+
const wrap = document.createElement('div');
|
|
166
|
+
wrap.setAttribute('data-sh3-passthrough-modifiers', '');
|
|
167
|
+
const inp = document.createElement('input');
|
|
168
|
+
wrap.appendChild(inp);
|
|
169
|
+
const entries = [mkEntry({ id: 'x', scope: 'home', defaultShortcut: 'Escape' })];
|
|
170
|
+
const result = dispatchKeydown(mkEnv({ entries, target: inp, shortcut: 'Escape' }));
|
|
171
|
+
expect(result).toBeNull();
|
|
172
|
+
});
|
|
173
|
+
it('still blocks Shift-only shortcuts on passthrough inputs', () => {
|
|
174
|
+
const wrap = document.createElement('div');
|
|
175
|
+
wrap.setAttribute('data-sh3-passthrough-modifiers', '');
|
|
176
|
+
const inp = document.createElement('input');
|
|
177
|
+
wrap.appendChild(inp);
|
|
178
|
+
const entries = [mkEntry({ id: 'x', scope: 'home', defaultShortcut: 'Shift+K' })];
|
|
179
|
+
const result = dispatchKeydown(mkEnv({ entries, target: inp, shortcut: 'Shift+K' }));
|
|
180
|
+
expect(result).toBeNull();
|
|
181
|
+
});
|
|
182
|
+
it('Ctrl-bearing shortcut on a plain input (no passthrough) is still blocked', () => {
|
|
183
|
+
const inp = document.createElement('input');
|
|
184
|
+
const entries = [mkEntry({ id: 'x', scope: 'home', defaultShortcut: 'Ctrl+K' })];
|
|
185
|
+
const result = dispatchKeydown(mkEnv({ entries, target: inp, shortcut: 'Ctrl+K' }));
|
|
186
|
+
expect(result).toBeNull();
|
|
187
|
+
});
|
|
155
188
|
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { listActionsFromEntries } from './listActive';
|
|
3
|
+
const mkEntry = (a, owner = 'shard.x') => ({
|
|
4
|
+
ownerShardId: owner,
|
|
5
|
+
action: Object.assign({ id: 'a', label: 'A', scope: 'home', run: () => { } }, a),
|
|
6
|
+
});
|
|
7
|
+
const mkState = (o = {}) => (Object.assign({ activeAppId: null, activeAppRequiredShards: new Set(), autostartShards: new Set(), mountedViewIds: new Set(), focusedViewId: null, selection: null, bindings: {}, platform: 'other' }, o));
|
|
8
|
+
describe('listActionsFromEntries', () => {
|
|
9
|
+
it('returns [] for an empty registry', () => {
|
|
10
|
+
expect(listActionsFromEntries([], mkState())).toEqual([]);
|
|
11
|
+
});
|
|
12
|
+
it('marks an in-scope action as active', () => {
|
|
13
|
+
const entries = [mkEntry({ id: 'open', scope: 'home' })];
|
|
14
|
+
const out = listActionsFromEntries(entries, mkState());
|
|
15
|
+
expect(out).toHaveLength(1);
|
|
16
|
+
expect(out[0]).toMatchObject({ id: 'open', active: true, scope: 'home' });
|
|
17
|
+
});
|
|
18
|
+
it('marks an out-of-scope action as inactive', () => {
|
|
19
|
+
const entries = [mkEntry({ id: 'p', scope: 'app' })];
|
|
20
|
+
const out = listActionsFromEntries(entries, mkState()); // no active app
|
|
21
|
+
expect(out).toHaveLength(1);
|
|
22
|
+
expect(out[0].id).toBe('p');
|
|
23
|
+
expect(out[0].active).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
it('marks a disabled action as inactive even when its scope is active', () => {
|
|
26
|
+
const entries = [mkEntry({ id: 'd', scope: 'home', disabled: true })];
|
|
27
|
+
const out = listActionsFromEntries(entries, mkState());
|
|
28
|
+
expect(out[0].active).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
it('re-evaluates function-form disabled per call', () => {
|
|
31
|
+
let flag = false;
|
|
32
|
+
const entries = [mkEntry({
|
|
33
|
+
id: 'd', scope: 'home', disabled: () => flag,
|
|
34
|
+
})];
|
|
35
|
+
expect(listActionsFromEntries(entries, mkState())[0].active).toBe(true);
|
|
36
|
+
flag = true;
|
|
37
|
+
expect(listActionsFromEntries(entries, mkState())[0].active).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
it('marks a submenu parent without run as inactive', () => {
|
|
40
|
+
const entries = [{
|
|
41
|
+
ownerShardId: 'shard.x',
|
|
42
|
+
action: {
|
|
43
|
+
id: 's', label: 'S', scope: 'home', submenu: true,
|
|
44
|
+
},
|
|
45
|
+
}];
|
|
46
|
+
const out = listActionsFromEntries(entries, mkState());
|
|
47
|
+
expect(out[0].active).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
it('orders active descriptors by tier (innermost first), inactive trailing in insertion order', () => {
|
|
50
|
+
const entries = [
|
|
51
|
+
mkEntry({ id: 'inactive-a', scope: 'app' }, 'shard.a'), // app not active -> inactive
|
|
52
|
+
mkEntry({ id: 'home-1', scope: 'home' }, 'shard.a'), // active home tier
|
|
53
|
+
mkEntry({ id: 'inactive-b', scope: 'app' }, 'shard.b'), // inactive
|
|
54
|
+
mkEntry({ id: 'view-1', scope: 'view:editor' }, '__sh3core__'), // active view tier (innermost)
|
|
55
|
+
];
|
|
56
|
+
const state = mkState({
|
|
57
|
+
activeAppId: null, // app tier inactive
|
|
58
|
+
autostartShards: new Set(['__sh3core__']),
|
|
59
|
+
mountedViewIds: new Set(['editor']),
|
|
60
|
+
});
|
|
61
|
+
const out = listActionsFromEntries(entries, state);
|
|
62
|
+
expect(out.map((d) => d.id)).toEqual([
|
|
63
|
+
'view-1', // innermost active tier first
|
|
64
|
+
'home-1', // then home tier
|
|
65
|
+
'inactive-a', // inactive in insertion order
|
|
66
|
+
'inactive-b',
|
|
67
|
+
]);
|
|
68
|
+
});
|
|
69
|
+
it('dedupes by action id (first registration wins) for both active and inactive', () => {
|
|
70
|
+
const entries = [
|
|
71
|
+
mkEntry({ id: 'dup', scope: 'home' }, 'shard.a'),
|
|
72
|
+
mkEntry({ id: 'dup', scope: 'home' }, 'shard.b'),
|
|
73
|
+
];
|
|
74
|
+
const out = listActionsFromEntries(entries, mkState());
|
|
75
|
+
expect(out).toHaveLength(1);
|
|
76
|
+
expect(out[0].ownerShardId).toBe('shard.a');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ActionEntry } from './registry';
|
|
2
2
|
import { type DispatcherState } from './dispatcher.svelte';
|
|
3
|
-
import { type ActiveActionDescriptor } from './types';
|
|
3
|
+
import { type ActionDescriptor, type ActiveActionDescriptor } from './types';
|
|
4
|
+
export declare function listActionsFromEntries(entries: ActionEntry[], state: DispatcherState): ActionDescriptor[];
|
|
4
5
|
export declare function listActiveFromEntries(entries: ActionEntry[], state: DispatcherState): ActiveActionDescriptor[];
|
|
@@ -1,43 +1,69 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Pure read-side
|
|
2
|
+
* Pure read-side producers for action enumeration.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* The shell's live wrapper calls this with `listActions()` + `getLiveDispatcherState()`.
|
|
4
|
+
* `listActionsFromEntries` returns one descriptor per registered action,
|
|
5
|
+
* with an `active` flag indicating whether dispatching it right now would
|
|
6
|
+
* actually run. `listActiveFromEntries` is a filter on top of it: the active
|
|
7
|
+
* subset, in tier-innermost-first order, structurally compatible with the
|
|
8
|
+
* `ActiveActionDescriptor[]` shape consumed by `shell.actions.listActive()`.
|
|
10
9
|
*/
|
|
11
10
|
import { TIER_ORDER, } from './dispatcher.svelte';
|
|
12
11
|
import { effectiveShortcutWithSource } from './bindings';
|
|
13
12
|
import { innermostActiveScope, scopeBadge, scopeToTier } from './scope-helpers';
|
|
14
|
-
import { resolveLabel } from './types';
|
|
15
|
-
|
|
13
|
+
import { resolveLabel, } from './types';
|
|
14
|
+
function isDisabled(entry) {
|
|
15
|
+
const d = entry.action.disabled;
|
|
16
|
+
if (typeof d === 'function')
|
|
17
|
+
return Boolean(d());
|
|
18
|
+
return Boolean(d);
|
|
19
|
+
}
|
|
20
|
+
function firstScope(entry) {
|
|
21
|
+
const s = entry.action.scope;
|
|
22
|
+
return Array.isArray(s) ? s[0] : s;
|
|
23
|
+
}
|
|
24
|
+
export function listActionsFromEntries(entries, state) {
|
|
16
25
|
const byTier = {
|
|
17
26
|
element: [], focus: [], view: [], app: [], home: [],
|
|
18
27
|
};
|
|
28
|
+
const inactive = [];
|
|
19
29
|
const seen = new Set();
|
|
20
30
|
for (const entry of entries) {
|
|
21
31
|
if (seen.has(entry.action.id))
|
|
22
32
|
continue;
|
|
23
|
-
const winning = innermostActiveScope(entry.action.scope, state, entry.ownerShardId);
|
|
24
|
-
if (!winning)
|
|
25
|
-
continue;
|
|
26
33
|
seen.add(entry.action.id);
|
|
27
34
|
const { shortcut, source } = effectiveShortcutWithSource(entry.action, state.bindings, state.platform);
|
|
28
|
-
|
|
35
|
+
const winning = innermostActiveScope(entry.action.scope, state, entry.ownerShardId);
|
|
36
|
+
const hasRun = typeof entry.action.run === 'function';
|
|
37
|
+
const active = winning !== null &&
|
|
38
|
+
hasRun &&
|
|
39
|
+
!isDisabled(entry);
|
|
40
|
+
const scope = active ? winning : firstScope(entry);
|
|
41
|
+
const desc = {
|
|
29
42
|
id: entry.action.id,
|
|
30
43
|
label: resolveLabel(entry.action),
|
|
31
44
|
effectiveShortcut: shortcut,
|
|
32
45
|
bindingSource: source,
|
|
33
|
-
scope
|
|
34
|
-
scopeBadge: scopeBadge(
|
|
46
|
+
scope,
|
|
47
|
+
scopeBadge: scopeBadge(scope),
|
|
35
48
|
group: entry.action.group,
|
|
36
49
|
icon: entry.action.icon,
|
|
37
50
|
ownerShardId: entry.ownerShardId,
|
|
38
51
|
paletteItem: entry.action.paletteItem !== false,
|
|
39
52
|
contextItem: entry.action.contextItem !== false,
|
|
40
|
-
|
|
53
|
+
active,
|
|
54
|
+
};
|
|
55
|
+
if (active) {
|
|
56
|
+
byTier[scopeToTier(winning)].push(desc);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
inactive.push(desc);
|
|
60
|
+
}
|
|
41
61
|
}
|
|
42
|
-
return
|
|
62
|
+
return [
|
|
63
|
+
...TIER_ORDER.flatMap((tier) => byTier[tier]),
|
|
64
|
+
...inactive,
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
export function listActiveFromEntries(entries, state) {
|
|
68
|
+
return listActionsFromEntries(entries, state).filter((d) => d.active);
|
|
43
69
|
}
|
|
@@ -11,6 +11,22 @@ export interface OpenPaletteOpts {
|
|
|
11
11
|
submenuOf?: string;
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Programmatic action dispatch entry point. Invokes a registered action by
|
|
16
|
+
* id with `invokedVia: 'programmatic'`, using current live state for
|
|
17
|
+
* appId / viewId / selection. Returns a Promise that resolves after the
|
|
18
|
+
* registered `run` settles (or its return value resolves), rejects on:
|
|
19
|
+
* - unknown id,
|
|
20
|
+
* - registered but inactive (out of scope, disabled, submenu without run),
|
|
21
|
+
* - any error thrown synchronously or asynchronously by `run`.
|
|
22
|
+
*
|
|
23
|
+
* Used by `ctx.runAction(id, opts?)` (returns the promise) and by
|
|
24
|
+
* `chainedDispatch` (the in-action `ctx.dispatch(id)` callback, which
|
|
25
|
+
* discards the promise to keep its existing fire-and-forget contract).
|
|
26
|
+
*/
|
|
27
|
+
export declare function dispatchActionProgrammatic(actionId: string, _opts?: {
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
}): Promise<void>;
|
|
14
30
|
export declare function attachGlobalListeners(): void;
|
|
15
31
|
export declare function detachGlobalListeners(): void;
|
|
16
32
|
export declare function openContextMenu(opts: OpenContextMenuOpts): void;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { mount } from 'svelte';
|
|
7
7
|
import { listActions } from './registry';
|
|
8
|
+
import { listActionsFromEntries } from './listActive';
|
|
8
9
|
import { dispatchKeydown } from './dispatcher.svelte';
|
|
9
10
|
import { getLiveDispatcherState, setFocusedViewId, } from './state.svelte';
|
|
10
11
|
import { eventToShortcut } from './shortcuts';
|
|
@@ -71,25 +72,56 @@ function runAction(actionId, ctx) {
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
75
|
+
* Programmatic action dispatch entry point. Invokes a registered action by
|
|
76
|
+
* id with `invokedVia: 'programmatic'`, using current live state for
|
|
77
|
+
* appId / viewId / selection. Returns a Promise that resolves after the
|
|
78
|
+
* registered `run` settles (or its return value resolves), rejects on:
|
|
79
|
+
* - unknown id,
|
|
80
|
+
* - registered but inactive (out of scope, disabled, submenu without run),
|
|
81
|
+
* - any error thrown synchronously or asynchronously by `run`.
|
|
82
|
+
*
|
|
83
|
+
* Used by `ctx.runAction(id, opts?)` (returns the promise) and by
|
|
84
|
+
* `chainedDispatch` (the in-action `ctx.dispatch(id)` callback, which
|
|
85
|
+
* discards the promise to keep its existing fire-and-forget contract).
|
|
77
86
|
*/
|
|
78
|
-
function
|
|
87
|
+
export function dispatchActionProgrammatic(actionId, _opts) {
|
|
79
88
|
var _a, _b;
|
|
80
|
-
const
|
|
89
|
+
const entries = listActions();
|
|
90
|
+
const entry = entries.find((e) => e.action.id === actionId);
|
|
81
91
|
if (!entry) {
|
|
82
|
-
|
|
83
|
-
return;
|
|
92
|
+
return Promise.reject(new Error(`action "${actionId}" not registered`));
|
|
84
93
|
}
|
|
85
94
|
const state = getLiveDispatcherState();
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
const desc = listActionsFromEntries(entries, state).find((d) => d.id === actionId);
|
|
96
|
+
if (!desc || !desc.active) {
|
|
97
|
+
return Promise.reject(new Error(`action "${actionId}" is not active in current scope`));
|
|
98
|
+
}
|
|
99
|
+
// run is guaranteed non-null by `desc.active === true`.
|
|
100
|
+
const run = entry.action.run;
|
|
101
|
+
try {
|
|
102
|
+
const result = run({
|
|
103
|
+
action: { id: entry.action.id, label: resolveLabel(entry.action) },
|
|
104
|
+
appId: state.activeAppId,
|
|
105
|
+
viewId: (_a = state.focusedViewId) !== null && _a !== void 0 ? _a : undefined,
|
|
106
|
+
selection: (_b = state.selection) !== null && _b !== void 0 ? _b : undefined,
|
|
107
|
+
invokedVia: 'programmatic',
|
|
108
|
+
dispatch: chainedDispatch,
|
|
109
|
+
});
|
|
110
|
+
return Promise.resolve(result).then(() => undefined);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
return Promise.reject(err);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* In-action `ctx.dispatch(id)` callback. Fire-and-forget: discards the
|
|
118
|
+
* promise from {@link dispatchActionProgrammatic} so action authors can
|
|
119
|
+
* keep calling it as `dispatch(id)` without `await`. Errors are logged
|
|
120
|
+
* via console.error to preserve the original `chainedDispatch` behavior.
|
|
121
|
+
*/
|
|
122
|
+
function chainedDispatch(actionId) {
|
|
123
|
+
dispatchActionProgrammatic(actionId).catch((err) => {
|
|
124
|
+
console.error(`[sh3] ctx.dispatch("${actionId}"):`, err);
|
|
93
125
|
});
|
|
94
126
|
}
|
|
95
127
|
function onFocusIn(ev) {
|
|
@@ -272,6 +304,12 @@ export function openPalette(opts) {
|
|
|
272
304
|
const entries = listActions();
|
|
273
305
|
const state = getLiveDispatcherState();
|
|
274
306
|
const candidates = buildPaletteCandidates(entries, state, { filter: opts === null || opts === void 0 ? void 0 : opts.filter });
|
|
307
|
+
// Snapshot the focus origin so a stateless invocation (or Escape)
|
|
308
|
+
// returns the user to whatever they were typing. If the action moved
|
|
309
|
+
// focus elsewhere, activeElement is no longer body and we leave it.
|
|
310
|
+
const previousFocus = typeof document !== 'undefined' && document.activeElement instanceof HTMLElement
|
|
311
|
+
? document.activeElement
|
|
312
|
+
: null;
|
|
275
313
|
const handle = shell.modal.open(CommandPalette, {
|
|
276
314
|
candidates,
|
|
277
315
|
recency,
|
|
@@ -308,4 +346,20 @@ export function openPalette(opts) {
|
|
|
308
346
|
},
|
|
309
347
|
onClose: () => handle.close(),
|
|
310
348
|
}, { dismissOnBackdrop: true });
|
|
349
|
+
if (previousFocus) {
|
|
350
|
+
const innerClose = handle.close;
|
|
351
|
+
handle.close = () => {
|
|
352
|
+
innerClose();
|
|
353
|
+
// After unmount, if no other element claimed focus (stateless action
|
|
354
|
+
// ran, Escape, backdrop click), put the user back where they came
|
|
355
|
+
// from. A stateful action that focused a new element leaves
|
|
356
|
+
// activeElement non-body and we leave it alone.
|
|
357
|
+
queueMicrotask(() => {
|
|
358
|
+
if (document.activeElement === document.body
|
|
359
|
+
|| document.activeElement === null) {
|
|
360
|
+
previousFocus.focus();
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
};
|
|
364
|
+
}
|
|
311
365
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { dispatchActionProgrammatic } from './listeners';
|
|
3
|
+
import { registerAction, __resetActionsRegistryForTest, } from './registry';
|
|
4
|
+
import { __resetContributionsForTest } from '../contributions/registry';
|
|
5
|
+
import { __resetDispatcherStateForTest, setActiveApp } from './state.svelte';
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
__resetContributionsForTest();
|
|
8
|
+
__resetActionsRegistryForTest();
|
|
9
|
+
__resetDispatcherStateForTest();
|
|
10
|
+
setActiveApp(null, new Set());
|
|
11
|
+
});
|
|
12
|
+
describe('dispatchActionProgrammatic', () => {
|
|
13
|
+
it('rejects on unknown action id', async () => {
|
|
14
|
+
await expect(dispatchActionProgrammatic('nope')).rejects.toThrow(/action "nope" not registered/);
|
|
15
|
+
});
|
|
16
|
+
it('rejects when the action is registered but inactive (out of scope)', async () => {
|
|
17
|
+
const run = vi.fn();
|
|
18
|
+
const action = {
|
|
19
|
+
id: 'gated',
|
|
20
|
+
label: 'Gated',
|
|
21
|
+
scope: 'app',
|
|
22
|
+
run,
|
|
23
|
+
};
|
|
24
|
+
registerAction(action, 'shard.x');
|
|
25
|
+
// No active app -> 'app' scope is inactive.
|
|
26
|
+
await expect(dispatchActionProgrammatic('gated')).rejects.toThrow(/action "gated" is not active/);
|
|
27
|
+
expect(run).not.toHaveBeenCalled();
|
|
28
|
+
});
|
|
29
|
+
it('rejects when the action is disabled', async () => {
|
|
30
|
+
const run = vi.fn();
|
|
31
|
+
registerAction({
|
|
32
|
+
id: 'd', label: 'D', scope: 'home', run, disabled: true,
|
|
33
|
+
}, 'shard.x');
|
|
34
|
+
await expect(dispatchActionProgrammatic('d')).rejects.toThrow(/action "d" is not active/);
|
|
35
|
+
expect(run).not.toHaveBeenCalled();
|
|
36
|
+
});
|
|
37
|
+
it('rejects on a submenu parent without run', async () => {
|
|
38
|
+
registerAction({
|
|
39
|
+
id: 's', label: 'S', scope: 'home', submenu: true,
|
|
40
|
+
}, 'shard.x');
|
|
41
|
+
await expect(dispatchActionProgrammatic('s')).rejects.toThrow(/action "s" is not active/);
|
|
42
|
+
});
|
|
43
|
+
it('invokes run with invokedVia="programmatic" on happy path', async () => {
|
|
44
|
+
let captured = null;
|
|
45
|
+
registerAction({
|
|
46
|
+
id: 'go', label: 'Go', scope: 'home', run: (ctx) => { captured = ctx; },
|
|
47
|
+
}, 'shard.x');
|
|
48
|
+
await dispatchActionProgrammatic('go');
|
|
49
|
+
expect(captured).not.toBeNull();
|
|
50
|
+
expect(captured.invokedVia).toBe('programmatic');
|
|
51
|
+
expect(captured.action).toEqual({ id: 'go', label: 'Go' });
|
|
52
|
+
});
|
|
53
|
+
it('awaits async run', async () => {
|
|
54
|
+
let resolveInner = null;
|
|
55
|
+
const inner = new Promise((res) => { resolveInner = res; });
|
|
56
|
+
let runReturned = false;
|
|
57
|
+
registerAction({
|
|
58
|
+
id: 'a',
|
|
59
|
+
label: 'A',
|
|
60
|
+
scope: 'home',
|
|
61
|
+
run: async () => {
|
|
62
|
+
await inner;
|
|
63
|
+
runReturned = true;
|
|
64
|
+
},
|
|
65
|
+
}, 'shard.x');
|
|
66
|
+
const dispatch = dispatchActionProgrammatic('a');
|
|
67
|
+
// Microtask break: the run promise has not settled yet.
|
|
68
|
+
await Promise.resolve();
|
|
69
|
+
expect(runReturned).toBe(false);
|
|
70
|
+
resolveInner();
|
|
71
|
+
await dispatch;
|
|
72
|
+
expect(runReturned).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
it('rejects with the original error when run throws synchronously', async () => {
|
|
75
|
+
registerAction({
|
|
76
|
+
id: 't', label: 'T', scope: 'home', run: () => { throw new Error('boom'); },
|
|
77
|
+
}, 'shard.x');
|
|
78
|
+
await expect(dispatchActionProgrammatic('t')).rejects.toThrow('boom');
|
|
79
|
+
});
|
|
80
|
+
it('rejects with the original error when an async run rejects', async () => {
|
|
81
|
+
registerAction({
|
|
82
|
+
id: 't', label: 'T', scope: 'home', run: async () => { throw new Error('async boom'); },
|
|
83
|
+
}, 'shard.x');
|
|
84
|
+
await expect(dispatchActionProgrammatic('t')).rejects.toThrow('async boom');
|
|
85
|
+
});
|
|
86
|
+
it('exposes opts.signal on the dispatch context', async () => {
|
|
87
|
+
let captured = null;
|
|
88
|
+
registerAction({
|
|
89
|
+
id: 'go', label: 'Go', scope: 'home', run: (ctx) => { captured = ctx; },
|
|
90
|
+
}, 'shard.x');
|
|
91
|
+
const ctrl = new AbortController();
|
|
92
|
+
await dispatchActionProgrammatic('go', { signal: ctrl.signal });
|
|
93
|
+
// We don't assert on a typed `signal` field — opts is parity-only and not
|
|
94
|
+
// part of ActionDispatchContext today. The test just ensures the call
|
|
95
|
+
// path accepts the option without error.
|
|
96
|
+
expect(captured).not.toBeNull();
|
|
97
|
+
});
|
|
98
|
+
});
|
package/dist/actions/types.d.ts
CHANGED
|
@@ -149,6 +149,43 @@ export interface ActiveActionDescriptor {
|
|
|
149
149
|
paletteItem: boolean;
|
|
150
150
|
contextItem: boolean;
|
|
151
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Read-only snapshot describing one action in the registry. Produced by
|
|
154
|
+
* `listActionsFromEntries()` and surfaced to shards via `ctx.listActions()`.
|
|
155
|
+
*
|
|
156
|
+
* Compared with {@link ActiveActionDescriptor}: this descriptor carries the
|
|
157
|
+
* full registered set, including actions that would not dispatch right now
|
|
158
|
+
* (out-of-scope, disabled, submenu parent without `run`). The `active` flag
|
|
159
|
+
* tells the caller whether `ctx.runAction(id)` would dispatch.
|
|
160
|
+
*/
|
|
161
|
+
export interface ActionDescriptor {
|
|
162
|
+
/** Stable action id as registered. */
|
|
163
|
+
id: string;
|
|
164
|
+
/** Human-readable label as registered. */
|
|
165
|
+
label: string;
|
|
166
|
+
/**
|
|
167
|
+
* Shortcut string as it would dispatch right now (platform-resolved,
|
|
168
|
+
* user-rebind applied). `null` when `bindingSource` is `'disabled'` or
|
|
169
|
+
* `'none'`.
|
|
170
|
+
*/
|
|
171
|
+
effectiveShortcut: string | null;
|
|
172
|
+
/** Where the effective shortcut came from. */
|
|
173
|
+
bindingSource: BindingSource;
|
|
174
|
+
/**
|
|
175
|
+
* The innermost active tier of the action's scope when `active === true`.
|
|
176
|
+
* When `active === false`, the first scope of the registered action.
|
|
177
|
+
*/
|
|
178
|
+
scope: AtomicScope;
|
|
179
|
+
/** Display hint: `null` for home/app, else full string or element type. */
|
|
180
|
+
scopeBadge: string | null;
|
|
181
|
+
group?: string;
|
|
182
|
+
icon?: string;
|
|
183
|
+
ownerShardId: string;
|
|
184
|
+
paletteItem: boolean;
|
|
185
|
+
contextItem: boolean;
|
|
186
|
+
/** True when `runAction(id)` would dispatch right now. */
|
|
187
|
+
active: boolean;
|
|
188
|
+
}
|
|
152
189
|
/**
|
|
153
190
|
* Resolve an Action's label to a string. Function labels are called on each
|
|
154
191
|
* read; string labels are returned unchanged.
|