@lvce-editor/extension-host-worker 8.35.0 → 8.37.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.
@@ -2536,6 +2536,26 @@ var normalizeMenuEntries = (entries) => {
2536
2536
  }
2537
2537
  return entries.map(normalizeMenuEntry);
2538
2538
  };
2539
+ var normalizeViewAction = (action, index) => {
2540
+ if (!action || typeof action !== "object" || Array.isArray(action)) {
2541
+ throw new ExtensionApiError(`view action ${index} must be an object`);
2542
+ }
2543
+ const viewAction = action;
2544
+ assertString(viewAction.title, `view action ${index} is missing title`);
2545
+ assertString(viewAction.icon, `view action ${index} is missing icon`);
2546
+ assertString(viewAction.command, `view action ${index} is missing command`);
2547
+ return {
2548
+ command: viewAction.command,
2549
+ icon: viewAction.icon,
2550
+ title: viewAction.title
2551
+ };
2552
+ };
2553
+ var normalizeViewActions = (actions) => {
2554
+ if (!Array.isArray(actions)) {
2555
+ throw new ExtensionApiError("view actions must be an array");
2556
+ }
2557
+ return actions.map(normalizeViewAction);
2558
+ };
2539
2559
  var renderPatches = async (uid, instance) => {
2540
2560
  const oldDom = renderedDoms[uid] || [];
2541
2561
  const newDom = await renderDom(instance);
@@ -2571,14 +2591,23 @@ var isSameContext = (oldContext, newContext) => {
2571
2591
  }
2572
2592
  return true;
2573
2593
  };
2594
+ var unchangedContext = {
2595
+ changed: false,
2596
+ newContext: {},
2597
+ oldContext: {}
2598
+ };
2574
2599
  var maybeNotifyContextChanged = async (uid, viewId, instance) => {
2575
2600
  if (typeof instance.getContext !== "function") {
2576
- return;
2601
+ return unchangedContext;
2577
2602
  }
2578
2603
  const oldContext = contexts[uid] || {};
2579
2604
  const newContext = normalizeContext(instance.getContext());
2580
2605
  if (isSameContext(oldContext, newContext)) {
2581
- return;
2606
+ return {
2607
+ changed: false,
2608
+ newContext,
2609
+ oldContext
2610
+ };
2582
2611
  }
2583
2612
  if (Object.keys(newContext).length === 0) {
2584
2613
  delete contexts[uid];
@@ -2586,6 +2615,31 @@ var maybeNotifyContextChanged = async (uid, viewId, instance) => {
2586
2615
  contexts[uid] = newContext;
2587
2616
  }
2588
2617
  await ExtensionManagementWorker_exports.invoke("Extensions.handleViewContextChange", uid, viewId, newContext);
2618
+ return {
2619
+ changed: true,
2620
+ newContext,
2621
+ oldContext
2622
+ };
2623
+ };
2624
+ var renderFocus = async (instance, contextChange) => {
2625
+ if (!contextChange.changed || typeof instance.renderFocus !== "function") {
2626
+ return "";
2627
+ }
2628
+ const focusSelector = await instance.renderFocus(contextChange.oldContext, contextChange.newContext);
2629
+ if (typeof focusSelector !== "string") {
2630
+ throw new ExtensionApiError("view renderFocus result must be a string");
2631
+ }
2632
+ return focusSelector;
2633
+ };
2634
+ var withFocusSelector = async (result, instance, contextChange) => {
2635
+ const focusSelector = await renderFocus(instance, contextChange);
2636
+ if (!focusSelector) {
2637
+ return result;
2638
+ }
2639
+ return {
2640
+ ...result,
2641
+ focusSelector
2642
+ };
2589
2643
  };
2590
2644
  var maybeClearContext = async (uid, viewId) => {
2591
2645
  if (!contexts[uid]) {
@@ -2621,11 +2675,12 @@ var createViewInstance = async (viewId, uid, context) => {
2621
2675
  contextViewIds[uid] = viewId;
2622
2676
  const dom = await renderDom(instance);
2623
2677
  renderedDoms[uid] = dom;
2624
- await maybeNotifyContextChanged(uid, viewId, instance);
2625
- return {
2678
+ const result = {
2626
2679
  dom,
2627
2680
  type: "setDom"
2628
2681
  };
2682
+ const contextChange = await maybeNotifyContextChanged(uid, viewId, instance);
2683
+ return withFocusSelector(result, instance, contextChange);
2629
2684
  };
2630
2685
  var dispatchViewEvent = async (uid, event) => {
2631
2686
  const instance = getVirtualDomInstance(uid);
@@ -2633,14 +2688,14 @@ var dispatchViewEvent = async (uid, event) => {
2633
2688
  await instance.handleEvent(event);
2634
2689
  }
2635
2690
  const result = await renderPatches(uid, instance);
2636
- await maybeNotifyContextChanged(uid, contextViewIds[uid], instance);
2637
- return result;
2691
+ const contextChange = await maybeNotifyContextChanged(uid, contextViewIds[uid], instance);
2692
+ return withFocusSelector(result, instance, contextChange);
2638
2693
  };
2639
2694
  var renderViewInstance = async (uid) => {
2640
2695
  const instance = getVirtualDomInstance(uid);
2641
2696
  const result = await renderPatches(uid, instance);
2642
- await maybeNotifyContextChanged(uid, contextViewIds[uid], instance);
2643
- return result;
2697
+ const contextChange = await maybeNotifyContextChanged(uid, contextViewIds[uid], instance);
2698
+ return withFocusSelector(result, instance, contextChange);
2644
2699
  };
2645
2700
  var disposeViewInstance = async (uid) => {
2646
2701
  const instance = instances[uid];
@@ -2667,6 +2722,13 @@ var getViewMenuEntries = async (uid, menuId) => {
2667
2722
  }
2668
2723
  return normalizeMenuEntries(await instance.getMenuEntries(menuId));
2669
2724
  };
2725
+ var getViewActions = async (uid) => {
2726
+ const instance = getVirtualDomInstance(uid);
2727
+ if (typeof instance.renderActions !== "function") {
2728
+ return [];
2729
+ }
2730
+ return normalizeViewActions(await instance.renderActions());
2731
+ };
2670
2732
  var getViewRegistrySnapshot = () => {
2671
2733
  return {
2672
2734
  views: Object.values(views).map(toRegisteredView)
@@ -2720,6 +2782,7 @@ var commandMap = {
2720
2782
  "ExtensionApi.getOutputChannelRegistrySnapshot": getOutputChannelRegistrySnapshot,
2721
2783
  "ExtensionApi.getSourceControlProviderRegistrySnapshot": getSourceControlProviderRegistrySnapshot,
2722
2784
  "ExtensionApi.getStatusBarItems": getStatusBarItems,
2785
+ "ExtensionApi.getViewActions": getViewActions,
2723
2786
  "ExtensionApi.getViewMenuEntries": getViewMenuEntries,
2724
2787
  "ExtensionApi.getViewRegistrySnapshot": getViewRegistrySnapshot,
2725
2788
  "ExtensionApi.renderViewInstance": renderViewInstance,
@@ -23,6 +23,7 @@ import {
23
23
  dispatchViewEvent,
24
24
  disposeViewInstance,
25
25
  executeViewProvider,
26
+ getViewActions,
26
27
  getViewMenuEntries,
27
28
  getViewRegistrySnapshot,
28
29
  renderViewInstance,
@@ -57,6 +58,7 @@ const commandMap = {
57
58
  "ExtensionApi.getOutputChannelRegistrySnapshot": getOutputChannelRegistrySnapshot,
58
59
  "ExtensionApi.getSourceControlProviderRegistrySnapshot": getSourceControlProviderRegistrySnapshot,
59
60
  "ExtensionApi.getStatusBarItems": getStatusBarItems,
61
+ "ExtensionApi.getViewActions": getViewActions,
60
62
  "ExtensionApi.getViewMenuEntries": getViewMenuEntries,
61
63
  "ExtensionApi.getViewRegistrySnapshot": getViewRegistrySnapshot,
62
64
  "ExtensionApi.renderViewInstance": renderViewInstance,
@@ -147,6 +147,26 @@ const normalizeMenuEntries = (entries) => {
147
147
  }
148
148
  return entries.map(normalizeMenuEntry);
149
149
  };
150
+ const normalizeViewAction = (action, index) => {
151
+ if (!action || typeof action !== "object" || Array.isArray(action)) {
152
+ throw new ExtensionApiError(`view action ${index} must be an object`);
153
+ }
154
+ const viewAction = action;
155
+ assertString(viewAction.title, `view action ${index} is missing title`);
156
+ assertString(viewAction.icon, `view action ${index} is missing icon`);
157
+ assertString(viewAction.command, `view action ${index} is missing command`);
158
+ return {
159
+ command: viewAction.command,
160
+ icon: viewAction.icon,
161
+ title: viewAction.title
162
+ };
163
+ };
164
+ const normalizeViewActions = (actions) => {
165
+ if (!Array.isArray(actions)) {
166
+ throw new ExtensionApiError("view actions must be an array");
167
+ }
168
+ return actions.map(normalizeViewAction);
169
+ };
150
170
  const renderPatches = async (uid, instance) => {
151
171
  const oldDom = renderedDoms[uid] || [];
152
172
  const newDom = await renderDom(instance);
@@ -182,14 +202,23 @@ const isSameContext = (oldContext, newContext) => {
182
202
  }
183
203
  return true;
184
204
  };
205
+ const unchangedContext = {
206
+ changed: false,
207
+ newContext: {},
208
+ oldContext: {}
209
+ };
185
210
  const maybeNotifyContextChanged = async (uid, viewId, instance) => {
186
211
  if (typeof instance.getContext !== "function") {
187
- return;
212
+ return unchangedContext;
188
213
  }
189
214
  const oldContext = contexts[uid] || {};
190
215
  const newContext = normalizeContext(instance.getContext());
191
216
  if (isSameContext(oldContext, newContext)) {
192
- return;
217
+ return {
218
+ changed: false,
219
+ newContext,
220
+ oldContext
221
+ };
193
222
  }
194
223
  if (Object.keys(newContext).length === 0) {
195
224
  delete contexts[uid];
@@ -197,6 +226,31 @@ const maybeNotifyContextChanged = async (uid, viewId, instance) => {
197
226
  contexts[uid] = newContext;
198
227
  }
199
228
  await ExtensionManagementWorker.invoke("Extensions.handleViewContextChange", uid, viewId, newContext);
229
+ return {
230
+ changed: true,
231
+ newContext,
232
+ oldContext
233
+ };
234
+ };
235
+ const renderFocus = async (instance, contextChange) => {
236
+ if (!contextChange.changed || typeof instance.renderFocus !== "function") {
237
+ return "";
238
+ }
239
+ const focusSelector = await instance.renderFocus(contextChange.oldContext, contextChange.newContext);
240
+ if (typeof focusSelector !== "string") {
241
+ throw new ExtensionApiError("view renderFocus result must be a string");
242
+ }
243
+ return focusSelector;
244
+ };
245
+ const withFocusSelector = async (result, instance, contextChange) => {
246
+ const focusSelector = await renderFocus(instance, contextChange);
247
+ if (!focusSelector) {
248
+ return result;
249
+ }
250
+ return {
251
+ ...result,
252
+ focusSelector
253
+ };
200
254
  };
201
255
  const maybeClearContext = async (uid, viewId) => {
202
256
  if (!contexts[uid]) {
@@ -232,11 +286,12 @@ const createViewInstance = async (viewId, uid, context) => {
232
286
  contextViewIds[uid] = viewId;
233
287
  const dom = await renderDom(instance);
234
288
  renderedDoms[uid] = dom;
235
- await maybeNotifyContextChanged(uid, viewId, instance);
236
- return {
289
+ const result = {
237
290
  dom,
238
291
  type: "setDom"
239
292
  };
293
+ const contextChange = await maybeNotifyContextChanged(uid, viewId, instance);
294
+ return withFocusSelector(result, instance, contextChange);
240
295
  };
241
296
  const dispatchViewEvent = async (uid, event) => {
242
297
  const instance = getVirtualDomInstance(uid);
@@ -244,14 +299,14 @@ const dispatchViewEvent = async (uid, event) => {
244
299
  await instance.handleEvent(event);
245
300
  }
246
301
  const result = await renderPatches(uid, instance);
247
- await maybeNotifyContextChanged(uid, contextViewIds[uid], instance);
248
- return result;
302
+ const contextChange = await maybeNotifyContextChanged(uid, contextViewIds[uid], instance);
303
+ return withFocusSelector(result, instance, contextChange);
249
304
  };
250
305
  const renderViewInstance = async (uid) => {
251
306
  const instance = getVirtualDomInstance(uid);
252
307
  const result = await renderPatches(uid, instance);
253
- await maybeNotifyContextChanged(uid, contextViewIds[uid], instance);
254
- return result;
308
+ const contextChange = await maybeNotifyContextChanged(uid, contextViewIds[uid], instance);
309
+ return withFocusSelector(result, instance, contextChange);
255
310
  };
256
311
  const disposeViewInstance = async (uid) => {
257
312
  const instance = instances[uid];
@@ -278,6 +333,13 @@ const getViewMenuEntries = async (uid, menuId) => {
278
333
  }
279
334
  return normalizeMenuEntries(await instance.getMenuEntries(menuId));
280
335
  };
336
+ const getViewActions = async (uid) => {
337
+ const instance = getVirtualDomInstance(uid);
338
+ if (typeof instance.renderActions !== "function") {
339
+ return [];
340
+ }
341
+ return normalizeViewActions(await instance.renderActions());
342
+ };
281
343
  const getViewRegistrySnapshot = () => {
282
344
  return {
283
345
  views: Object.values(views).map(toRegisteredView)
@@ -305,6 +367,7 @@ export {
305
367
  dispatchViewEvent,
306
368
  disposeViewInstance,
307
369
  executeViewProvider,
370
+ getViewActions,
308
371
  getViewMenuEntries,
309
372
  getViewRegistrySnapshot,
310
373
  registerView,
@@ -12,7 +12,7 @@ export { executeSourceControlAcceptInput, executeSourceControlAdd, executeSource
12
12
  export { createViewInstance, dispatchViewEvent, disposeViewInstance, executeViewProvider, getViewRegistrySnapshot, renderViewInstance, registerView, resetViewRegistry, saveViewInstanceState, } from './parts/ViewRegistry/ViewRegistry.ts';
13
13
  export { createOutputChannel, getOutputChannelRegistrySnapshot, resetOutputChannelRegistry } from './parts/OutputChannel/OutputChannel.ts';
14
14
  export { getStatusBarItemProviderRegistrySnapshot, registerStatusBarItemProvider, resetStatusBarItemProviderRegistry, } from './parts/StatusBar/StatusBar.ts';
15
- export type { RegisteredView, View, ViewContext, ViewEvent, ViewKind, MenuEntry, ViewRegistrySnapshot, ViewRenderResult, VirtualDomViewInstance, } from './parts/View/View.ts';
15
+ export type { RegisteredView, View, ViewAction, ViewContext, ViewEvent, ViewKind, MenuEntry, ViewRegistrySnapshot, ViewRenderResult, VirtualDomViewInstance, } from './parts/View/View.ts';
16
16
  export { handleExtensionManagementMessagePort } from './parts/HandleExtensionManagementMessagePort/HandleExtensionManagementMessagePort.ts';
17
17
  export type { Command } from './parts/Command/Command.ts';
18
18
  export type { CommandCallback } from './parts/CommandCallback/CommandCallback.ts';
@@ -28,6 +28,7 @@ export declare const commandMap: {
28
28
  'ExtensionApi.getOutputChannelRegistrySnapshot': () => import("../OutputChannelRegistrySnapshot/OutputChannelRegistrySnapshot.ts").OutputChannelRegistrySnapshot;
29
29
  'ExtensionApi.getSourceControlProviderRegistrySnapshot': () => import("../SourceControlProviderRegistrySnapshot/SourceControlProviderRegistrySnapshot.ts").SourceControlProviderRegistrySnapshot;
30
30
  'ExtensionApi.getStatusBarItems': () => readonly import("../StatusBarItem/StatusBarItem.ts").StatusBarItem[];
31
+ 'ExtensionApi.getViewActions': (uid: number) => Promise<readonly import("../View/View.ts").ViewAction[]>;
31
32
  'ExtensionApi.getViewMenuEntries': (uid: number, menuId: string) => Promise<readonly import("../View/View.ts").MenuEntry[]>;
32
33
  'ExtensionApi.getViewRegistrySnapshot': () => import("../View/View.ts").ViewRegistrySnapshot;
33
34
  'ExtensionApi.renderViewInstance': (uid: number) => Promise<import("../View/View.ts").ViewRenderResult>;
@@ -27,6 +27,7 @@ export declare const commandMap: {
27
27
  'ExtensionApi.getOutputChannelRegistrySnapshot': () => import("../OutputChannelRegistrySnapshot/OutputChannelRegistrySnapshot.ts").OutputChannelRegistrySnapshot;
28
28
  'ExtensionApi.getSourceControlProviderRegistrySnapshot': () => import("../SourceControlProviderRegistrySnapshot/SourceControlProviderRegistrySnapshot.ts").SourceControlProviderRegistrySnapshot;
29
29
  'ExtensionApi.getStatusBarItems': () => readonly import("../StatusBarItem/StatusBarItem.ts").StatusBarItem[];
30
+ 'ExtensionApi.getViewActions': (uid: number) => Promise<readonly import("../View/View.ts").ViewAction[]>;
30
31
  'ExtensionApi.getViewMenuEntries': (uid: number, menuId: string) => Promise<readonly import("../View/View.ts").MenuEntry[]>;
31
32
  'ExtensionApi.getViewRegistrySnapshot': () => import("../View/View.ts").ViewRegistrySnapshot;
32
33
  'ExtensionApi.renderViewInstance': (uid: number) => Promise<import("../View/View.ts").ViewRenderResult>;
@@ -6,7 +6,7 @@ import { getStatusBarItems } from "../GetStatusBarItems/GetStatusBarItems.js";
6
6
  import { executeHoverProvider, getHoverProviderRegistrySnapshot } from "../Hover/Hover.js";
7
7
  import { getOutputChannelRegistrySnapshot } from "../OutputChannel/OutputChannel.js";
8
8
  import { executeSourceControlAcceptInput, executeSourceControlAdd, executeSourceControlDiscard, executeSourceControlGenerateCommitMessage, executeSourceControlGetChangedFiles, executeSourceControlGetFeatures, executeSourceControlGetFileBefore, executeSourceControlGetFileDecorations, executeSourceControlGetGroups, executeSourceControlIsActive, getSourceControlProviderRegistrySnapshot, } from "../SourceControl/SourceControl.js";
9
- import { createViewInstance, dispatchViewEvent, disposeViewInstance, executeViewProvider, getViewMenuEntries, getViewRegistrySnapshot, renderViewInstance, saveViewInstanceState, } from "../ViewRegistry/ViewRegistry.js";
9
+ import { createViewInstance, dispatchViewEvent, disposeViewInstance, executeViewProvider, getViewActions, getViewMenuEntries, getViewRegistrySnapshot, renderViewInstance, saveViewInstanceState, } from "../ViewRegistry/ViewRegistry.js";
10
10
  export const commandMap = {
11
11
  'ExtensionApi.createViewInstance': createViewInstance,
12
12
  'ExtensionApi.dispatchViewEvent': dispatchViewEvent,
@@ -36,6 +36,7 @@ export const commandMap = {
36
36
  'ExtensionApi.getOutputChannelRegistrySnapshot': getOutputChannelRegistrySnapshot,
37
37
  'ExtensionApi.getSourceControlProviderRegistrySnapshot': getSourceControlProviderRegistrySnapshot,
38
38
  'ExtensionApi.getStatusBarItems': getStatusBarItems,
39
+ 'ExtensionApi.getViewActions': getViewActions,
39
40
  'ExtensionApi.getViewMenuEntries': getViewMenuEntries,
40
41
  'ExtensionApi.getViewRegistrySnapshot': getViewRegistrySnapshot,
41
42
  'ExtensionApi.renderViewInstance': renderViewInstance,
@@ -29,6 +29,7 @@ export declare const commandMap: {
29
29
  'ExtensionApi.getOutputChannelRegistrySnapshot': () => import("../OutputChannelRegistrySnapshot/OutputChannelRegistrySnapshot.ts").OutputChannelRegistrySnapshot;
30
30
  'ExtensionApi.getSourceControlProviderRegistrySnapshot': () => import("../SourceControlProviderRegistrySnapshot/SourceControlProviderRegistrySnapshot.ts").SourceControlProviderRegistrySnapshot;
31
31
  'ExtensionApi.getStatusBarItems': () => readonly import("../StatusBarItem/StatusBarItem.ts").StatusBarItem[];
32
+ 'ExtensionApi.getViewActions': (uid: number) => Promise<readonly import("../View/View.ts").ViewAction[]>;
32
33
  'ExtensionApi.getViewMenuEntries': (uid: number, menuId: string) => Promise<readonly import("../View/View.ts").MenuEntry[]>;
33
34
  'ExtensionApi.getViewRegistrySnapshot': () => import("../View/View.ts").ViewRegistrySnapshot;
34
35
  'ExtensionApi.renderViewInstance': (uid: number) => Promise<import("../View/View.ts").ViewRenderResult>;
@@ -21,6 +21,11 @@ export interface MenuEntry {
21
21
  readonly id: string;
22
22
  readonly label: string;
23
23
  }
24
+ export interface ViewAction {
25
+ readonly command: string;
26
+ readonly icon: string;
27
+ readonly title: string;
28
+ }
24
29
  export interface DomEventListener {
25
30
  readonly capture?: boolean;
26
31
  readonly name: string | number;
@@ -35,6 +40,8 @@ export interface VirtualDomViewInstance {
35
40
  readonly getMenuEntries?: (menuId: string) => readonly MenuEntry[] | Promise<readonly MenuEntry[]>;
36
41
  readonly handleEvent?: (event: ViewEvent) => unknown;
37
42
  readonly render: () => readonly VirtualDomNode[] | Promise<readonly VirtualDomNode[]>;
43
+ readonly renderActions?: () => readonly ViewAction[] | Promise<readonly ViewAction[]>;
44
+ readonly renderFocus?: (oldContext: Readonly<Record<string, boolean>>, newContext: Readonly<Record<string, boolean>>) => string | Promise<string>;
38
45
  readonly saveState?: () => unknown;
39
46
  }
40
47
  export interface View {
@@ -61,9 +68,11 @@ export interface ViewRegistrySnapshot {
61
68
  }
62
69
  export interface ViewRenderResultDom {
63
70
  readonly dom: readonly VirtualDomNode[];
71
+ readonly focusSelector?: string;
64
72
  readonly type: 'setDom';
65
73
  }
66
74
  export interface ViewRenderResultPatches {
75
+ readonly focusSelector?: string;
67
76
  readonly patches: readonly unknown[];
68
77
  readonly type: 'setPatches';
69
78
  }
@@ -1,5 +1,5 @@
1
1
  import type { Disposable } from '../Disposable/Disposable.ts';
2
- import type { MenuEntry, View, ViewContext, ViewEvent, ViewRegistrySnapshot, ViewRenderResult } from '../View/View.ts';
2
+ import type { MenuEntry, View, ViewAction, ViewContext, ViewEvent, ViewRegistrySnapshot, ViewRenderResult } from '../View/View.ts';
3
3
  export declare const registerView: (view: View) => Disposable;
4
4
  export declare const executeViewProvider: (id: string) => unknown;
5
5
  export declare const createViewInstance: (viewId: string, uid: number, context?: ViewContext) => Promise<ViewRenderResult>;
@@ -8,5 +8,6 @@ export declare const renderViewInstance: (uid: number) => Promise<ViewRenderResu
8
8
  export declare const disposeViewInstance: (uid: number) => Promise<void>;
9
9
  export declare const saveViewInstanceState: (uid: number) => Promise<unknown>;
10
10
  export declare const getViewMenuEntries: (uid: number, menuId: string) => Promise<readonly MenuEntry[]>;
11
+ export declare const getViewActions: (uid: number) => Promise<readonly ViewAction[]>;
11
12
  export declare const getViewRegistrySnapshot: () => ViewRegistrySnapshot;
12
13
  export declare const resetViewRegistry: () => void;
@@ -147,6 +147,26 @@ const normalizeMenuEntries = (entries) => {
147
147
  }
148
148
  return entries.map(normalizeMenuEntry);
149
149
  };
150
+ const normalizeViewAction = (action, index) => {
151
+ if (!action || typeof action !== 'object' || Array.isArray(action)) {
152
+ throw new ExtensionApiError(`view action ${index} must be an object`);
153
+ }
154
+ const viewAction = action;
155
+ assertString(viewAction.title, `view action ${index} is missing title`);
156
+ assertString(viewAction.icon, `view action ${index} is missing icon`);
157
+ assertString(viewAction.command, `view action ${index} is missing command`);
158
+ return {
159
+ command: viewAction.command,
160
+ icon: viewAction.icon,
161
+ title: viewAction.title,
162
+ };
163
+ };
164
+ const normalizeViewActions = (actions) => {
165
+ if (!Array.isArray(actions)) {
166
+ throw new ExtensionApiError('view actions must be an array');
167
+ }
168
+ return actions.map(normalizeViewAction);
169
+ };
150
170
  const renderPatches = async (uid, instance) => {
151
171
  const oldDom = renderedDoms[uid] || [];
152
172
  const newDom = await renderDom(instance);
@@ -182,14 +202,23 @@ const isSameContext = (oldContext, newContext) => {
182
202
  }
183
203
  return true;
184
204
  };
205
+ const unchangedContext = {
206
+ changed: false,
207
+ newContext: {},
208
+ oldContext: {},
209
+ };
185
210
  const maybeNotifyContextChanged = async (uid, viewId, instance) => {
186
211
  if (typeof instance.getContext !== 'function') {
187
- return;
212
+ return unchangedContext;
188
213
  }
189
214
  const oldContext = contexts[uid] || {};
190
215
  const newContext = normalizeContext(instance.getContext());
191
216
  if (isSameContext(oldContext, newContext)) {
192
- return;
217
+ return {
218
+ changed: false,
219
+ newContext,
220
+ oldContext,
221
+ };
193
222
  }
194
223
  if (Object.keys(newContext).length === 0) {
195
224
  delete contexts[uid];
@@ -198,6 +227,31 @@ const maybeNotifyContextChanged = async (uid, viewId, instance) => {
198
227
  contexts[uid] = newContext;
199
228
  }
200
229
  await ExtensionManagementWorker.invoke('Extensions.handleViewContextChange', uid, viewId, newContext);
230
+ return {
231
+ changed: true,
232
+ newContext,
233
+ oldContext,
234
+ };
235
+ };
236
+ const renderFocus = async (instance, contextChange) => {
237
+ if (!contextChange.changed || typeof instance.renderFocus !== 'function') {
238
+ return '';
239
+ }
240
+ const focusSelector = await instance.renderFocus(contextChange.oldContext, contextChange.newContext);
241
+ if (typeof focusSelector !== 'string') {
242
+ throw new ExtensionApiError('view renderFocus result must be a string');
243
+ }
244
+ return focusSelector;
245
+ };
246
+ const withFocusSelector = async (result, instance, contextChange) => {
247
+ const focusSelector = await renderFocus(instance, contextChange);
248
+ if (!focusSelector) {
249
+ return result;
250
+ }
251
+ return {
252
+ ...result,
253
+ focusSelector,
254
+ };
201
255
  };
202
256
  const maybeClearContext = async (uid, viewId) => {
203
257
  if (!contexts[uid]) {
@@ -233,11 +287,12 @@ export const createViewInstance = async (viewId, uid, context) => {
233
287
  contextViewIds[uid] = viewId;
234
288
  const dom = await renderDom(instance);
235
289
  renderedDoms[uid] = dom;
236
- await maybeNotifyContextChanged(uid, viewId, instance);
237
- return {
290
+ const result = {
238
291
  dom,
239
292
  type: 'setDom',
240
293
  };
294
+ const contextChange = await maybeNotifyContextChanged(uid, viewId, instance);
295
+ return withFocusSelector(result, instance, contextChange);
241
296
  };
242
297
  export const dispatchViewEvent = async (uid, event) => {
243
298
  const instance = getVirtualDomInstance(uid);
@@ -245,14 +300,14 @@ export const dispatchViewEvent = async (uid, event) => {
245
300
  await instance.handleEvent(event);
246
301
  }
247
302
  const result = await renderPatches(uid, instance);
248
- await maybeNotifyContextChanged(uid, contextViewIds[uid], instance);
249
- return result;
303
+ const contextChange = await maybeNotifyContextChanged(uid, contextViewIds[uid], instance);
304
+ return withFocusSelector(result, instance, contextChange);
250
305
  };
251
306
  export const renderViewInstance = async (uid) => {
252
307
  const instance = getVirtualDomInstance(uid);
253
308
  const result = await renderPatches(uid, instance);
254
- await maybeNotifyContextChanged(uid, contextViewIds[uid], instance);
255
- return result;
309
+ const contextChange = await maybeNotifyContextChanged(uid, contextViewIds[uid], instance);
310
+ return withFocusSelector(result, instance, contextChange);
256
311
  };
257
312
  export const disposeViewInstance = async (uid) => {
258
313
  const instance = instances[uid];
@@ -279,6 +334,13 @@ export const getViewMenuEntries = async (uid, menuId) => {
279
334
  }
280
335
  return normalizeMenuEntries(await instance.getMenuEntries(menuId));
281
336
  };
337
+ export const getViewActions = async (uid) => {
338
+ const instance = getVirtualDomInstance(uid);
339
+ if (typeof instance.renderActions !== 'function') {
340
+ return [];
341
+ }
342
+ return normalizeViewActions(await instance.renderActions());
343
+ };
282
344
  export const getViewRegistrySnapshot = () => {
283
345
  return {
284
346
  views: Object.values(views).map(toRegisteredView),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/api",
3
- "version": "8.35.0",
3
+ "version": "8.37.0",
4
4
  "description": "Tree-shakeable extension API for Lvce Editor extensions.",
5
5
  "keywords": [
6
6
  "lvce-editor",