@codingame/monaco-vscode-welcome-service-override 36.2.7 → 37.1.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.
Files changed (32) hide show
  1. package/index.js +1 -0
  2. package/package.json +3 -2
  3. package/vscode/src/vs/workbench/contrib/onboarding/browser/media/spotlight.css +5 -0
  4. package/vscode/src/vs/workbench/contrib/onboarding/browser/onboarding.contribution.js +37 -7
  5. package/vscode/src/vs/workbench/contrib/onboarding/browser/onboardingService.d.ts +2 -0
  6. package/vscode/src/vs/workbench/contrib/onboarding/browser/onboardingService.js +36 -5
  7. package/vscode/src/vs/workbench/contrib/onboarding/browser/sequence/runOnboardingStep.d.ts +17 -0
  8. package/vscode/src/vs/workbench/contrib/onboarding/browser/sequence/runOnboardingStep.js +32 -0
  9. package/vscode/src/vs/workbench/contrib/onboarding/browser/sequence/sequencePresentation.d.ts +10 -0
  10. package/vscode/src/vs/workbench/contrib/onboarding/browser/sequence/sequencePresentation.js +154 -0
  11. package/vscode/src/vs/workbench/contrib/onboarding/browser/spotlight/onboardingTarget.d.ts +7 -1
  12. package/vscode/src/vs/workbench/contrib/onboarding/browser/spotlight/onboardingTarget.js +5 -1
  13. package/vscode/src/vs/workbench/contrib/onboarding/browser/spotlight/spotlightOverlay.d.ts +13 -9
  14. package/vscode/src/vs/workbench/contrib/onboarding/browser/spotlight/spotlightOverlay.js +60 -15
  15. package/vscode/src/vs/workbench/contrib/onboarding/browser/spotlight/spotlightPresentation.d.ts +5 -1
  16. package/vscode/src/vs/workbench/contrib/onboarding/browser/spotlight/spotlightPresentation.js +222 -49
  17. package/vscode/src/vs/workbench/contrib/onboarding/browser/spotlight/spotlightTypes.d.ts +23 -5
  18. package/vscode/src/vs/workbench/contrib/onboarding/common/onboardingPresentation.d.ts +4 -0
  19. package/vscode/src/vs/workbench/contrib/onboarding/common/onboardingScenario.d.ts +9 -8
  20. package/vscode/src/vs/workbench/contrib/onboarding/common/onboardingScenarioService.d.ts +7 -0
  21. package/vscode/src/vs/workbench/contrib/onboarding/common/onboardingScenarioService.js +2 -1
  22. package/vscode/src/vs/workbench/contrib/onboarding/common/onboardingSequence.d.ts +45 -0
  23. package/vscode/src/vs/workbench/contrib/onboarding/common/onboardingSequence.js +29 -0
  24. package/vscode/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.contribution.d.ts +1 -0
  25. package/vscode/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.contribution.js +158 -0
  26. package/vscode/src/vs/workbench/contrib/welcomeOnboarding/browser/media/variationA.css +2 -149
  27. package/vscode/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.d.ts +5 -5
  28. package/vscode/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.js +61 -131
  29. package/vscode/src/vs/workbench/contrib/welcomeOnboarding/browser/welcomeOnboarding.contribution.js +1 -1
  30. package/vscode/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.js +10 -10
  31. package/vscode/src/vs/workbench/contrib/welcomeViews/common/viewsWelcomeContribution.js +2 -2
  32. package/vscode/src/vs/workbench/contrib/welcomeViews/common/viewsWelcomeExtensionPoint.js +8 -8
@@ -33,6 +33,7 @@ class SpotlightOverlay extends Disposable {
33
33
  this._onDidSkip = this._register(( new Emitter()));
34
34
  this.onDidSkip = this._onDidSkip.event;
35
35
  this._options = {};
36
+ this._hasShown = false;
36
37
  this._root = append(this._container, $(".spotlight-overlay"));
37
38
  this._root.style.display = "none";
38
39
  this._blockers = [
@@ -63,8 +64,8 @@ class SpotlightOverlay extends Disposable {
63
64
  ...defaultButtonStyles,
64
65
  secondary: true
65
66
  })));
66
- this._skipButton.label = ( localize(12888, "End Tour"));
67
- this._skipButton.setTitle(( localize(12889, "End Tour (Esc)")));
67
+ this._skipButton.label = ( localize(14872, "End Tour"));
68
+ this._skipButton.setTitle(( localize(14873, "End Tour (Esc)")));
68
69
  this._register(
69
70
  this._skipButton.onDidClick(() => this._onDidSkip.fire(OnboardingDismissReason.SkipButton))
70
71
  );
@@ -72,12 +73,12 @@ class SpotlightOverlay extends Disposable {
72
73
  ...defaultButtonStyles,
73
74
  secondary: true
74
75
  })));
75
- this._backButton.label = ( localize(12890, "Back"));
76
+ this._backButton.label = ( localize(14874, "Back"));
76
77
  this._register(this._backButton.onDidClick(() => this._onDidClickPrevious.fire()));
77
78
  this._nextButton = this._register(( new Button(actions, {
78
79
  ...defaultButtonStyles
79
80
  })));
80
- this._nextButton.label = ( localize(12891, "Next"));
81
+ this._nextButton.label = ( localize(14875, "Next"));
81
82
  this._register(this._nextButton.onDidClick(() => this._onDidClickNext.fire("button")));
82
83
  for (const button of [this._skipButton, this._backButton, this._nextButton]) {
83
84
  this._register(
@@ -92,13 +93,16 @@ class SpotlightOverlay extends Disposable {
92
93
  });
93
94
  }
94
95
  show(target, content, options = {}) {
95
- const isFirstShow = this._root.style.display === "none";
96
- if (isFirstShow) {
96
+ if (!this._hasShown) {
97
+ this._hasShown = true;
97
98
  this._previousFocus = isHTMLElement(getActiveElement()) ? getActiveElement() : undefined;
98
99
  }
99
100
  this._target = target;
100
101
  this._options = options;
101
102
  this._renderContent(content);
103
+ const externalUiParticipates = !!options.targetOverlayVisible || !!options.allowTargetInteraction || !!options.advanceOnTargetClick || !!options.hideNext;
104
+ this._root.classList.toggle("target-overlay-visible", externalUiParticipates);
105
+ this._callout.setAttribute("aria-modal", externalUiParticipates ? "false" : "true");
102
106
  this._root.style.display = "";
103
107
  this._stepListeners.clear();
104
108
  const targetWindow = getWindow(this._container);
@@ -119,15 +123,45 @@ class SpotlightOverlay extends Disposable {
119
123
  this._scheduledLayout = undefined;
120
124
  }));
121
125
  const advanceOnTargetClick = !!options.advanceOnTargetClick;
122
- this._nextButton.element.style.display = advanceOnTargetClick ? "none" : "";
126
+ const advanceOnly = options.advanceOnTargetClick === "advanceOnly";
127
+ const hideNext = options.hideNext ?? advanceOnTargetClick;
128
+ this._nextButton.element.style.display = hideNext ? "none" : "";
123
129
  if (advanceOnTargetClick) {
124
- this._stepListeners.add(
125
- addDisposableListener(target, EventType.CLICK, () => this._onDidClickNext.fire("target"))
126
- );
130
+ this._stepListeners.add(addDisposableListener(target, EventType.CLICK, event => {
131
+ if (advanceOnly) {
132
+ event.preventDefault();
133
+ event.stopImmediatePropagation();
134
+ }
135
+ this._onDidClickNext.fire("target");
136
+ }, advanceOnly));
137
+ }
138
+ if (advanceOnly) {
139
+ const onTargetKey = event => {
140
+ const key = ( new StandardKeyboardEvent(event));
141
+ if (key.equals(KeyCode.Enter) || key.equals(KeyCode.Space)) {
142
+ event.preventDefault();
143
+ event.stopImmediatePropagation();
144
+ if (event.type === EventType.KEY_UP) {
145
+ this._onDidClickNext.fire("target");
146
+ }
147
+ } else if (event.type === EventType.KEY_DOWN) {
148
+ this._onKeyDown(event);
149
+ }
150
+ };
151
+ this._stepListeners.add(addDisposableListener(target, EventType.KEY_DOWN, onTargetKey, true));
152
+ this._stepListeners.add(addDisposableListener(target, EventType.KEY_UP, onTargetKey, true));
153
+ } else if (options.allowTargetInteraction || advanceOnTargetClick || options.hideNext) {
127
154
  this._stepListeners.add(addDisposableListener(target, EventType.KEY_DOWN, e => this._onKeyDown(e)));
128
155
  }
129
156
  this.layout();
130
- (advanceOnTargetClick ? target : this._nextButton.element).focus();
157
+ (hideNext ? target : this._nextButton.element).focus();
158
+ }
159
+ hide() {
160
+ this._stepListeners.clear();
161
+ this._root.style.display = "none";
162
+ this._root.classList.remove("target-overlay-visible");
163
+ this._target = undefined;
164
+ this._options = {};
131
165
  }
132
166
  layout() {
133
167
  const target = this._target;
@@ -311,9 +345,10 @@ class SpotlightOverlay extends Disposable {
311
345
  } else {
312
346
  this._description.textContent = content.description;
313
347
  }
314
- this._counter.textContent = ( localize(12892, "{0} of {1}", content.stepIndex + 1, content.stepCount));
348
+ this._counter.textContent = content.stepCount > 1 ? ( localize(14876, "{0} of {1}", content.stepIndex + 1, content.stepCount)) : "";
349
+ this._skipButton.element.style.display = content.isLastStep ? "none" : "";
315
350
  this._backButton.element.style.display = content.canGoBack ? "" : "none";
316
- this._nextButton.label = content.isLastStep ? ( localize(12893, "Done")) : ( localize(12891, "Next"));
351
+ this._nextButton.label = content.nextButtonLabel ?? (content.isLastStep ? ( localize(14877, "Done")) : ( localize(14875, "Next")));
317
352
  }
318
353
  _onKeyDown(e) {
319
354
  const event = ( new StandardKeyboardEvent(e));
@@ -346,14 +381,24 @@ class SpotlightOverlay extends Disposable {
346
381
  focusable[nextIndex].focus();
347
382
  }
348
383
  _collectFocusable() {
349
- const target = (this._options.advanceOnTargetClick && this._target) ? [this._target] : [];
384
+ const targetFocusables =
385
+ (this._options.allowTargetInteraction || this._options.advanceOnTargetClick || this._options.hideNext) && this._target ? [this._target, ...this._target.querySelectorAll(
386
+ "a[href], button, input, select, textarea, [tabindex]:not([tabindex=\"-1\"])"
387
+ )] : [];
350
388
  const descriptionFocusables = Array.from(
351
389
  this._description.querySelectorAll(
352
390
  "a[href], button, input, select, textarea, [tabindex]:not([tabindex=\"-1\"])"
353
391
  )
354
392
  );
355
393
  const buttons = ( [this._skipButton, this._backButton, this._nextButton].filter(button => button.element.style.display !== "none").map(button => button.element));
356
- return [...target, ...descriptionFocusables, ...buttons];
394
+ return [...targetFocusables, ...descriptionFocusables, ...buttons].filter(element => this._isTabbable(element));
395
+ }
396
+ _isTabbable(element) {
397
+ if (!element.isConnected || element.getAttribute("aria-hidden") === "true" || element.tabIndex === -1 || element.hasAttribute("disabled")) {
398
+ return false;
399
+ }
400
+ const style = getWindow(this._container).getComputedStyle(element);
401
+ return style.display !== "none" && style.visibility !== "hidden";
357
402
  }
358
403
  scheduleLayout() {
359
404
  if (this._scheduledLayout) {
@@ -4,19 +4,23 @@ import { IWorkbenchLayoutService } from "@codingame/monaco-vscode-api/vscode/vs/
4
4
  import { IHostService } from "@codingame/monaco-vscode-api/vscode/vs/workbench/services/host/browser/host.service";
5
5
  import { IOnboardingPresentation, IOnboardingRunContext } from "../../common/onboardingPresentation.js";
6
6
  import { IOnboardingRunResult, IOnboardingScenario } from "../../common/onboardingScenario.js";
7
+ import { IOnboardingSequenceStep, IOnboardingSequenceStepContext, IOnboardingSequenceStepPresentation, IOnboardingSequenceStepResult } from "../../common/onboardingSequence.js";
7
8
  /**
8
9
  * Renders {@link ISpotlightPayload} scenarios: it dims the window (including the
9
10
  * native window controls), walks the steps, and shows an anchored callout for
10
11
  * each. Implements the engine's {@link IOnboardingPresentation} contract so the
11
12
  * scenario engine can drive it without knowing anything about spotlights.
12
13
  */
13
- export declare class SpotlightPresentation extends Disposable implements IOnboardingPresentation {
14
+ export declare class SpotlightPresentation extends Disposable implements IOnboardingPresentation, IOnboardingSequenceStepPresentation {
14
15
  private readonly layoutService;
15
16
  private readonly hostService;
16
17
  private readonly contextKeyService;
17
18
  readonly kind = "spotlight";
19
+ readonly countsAsVisualStep = true;
18
20
  constructor(layoutService: IWorkbenchLayoutService, hostService: IHostService, contextKeyService: IContextKeyService);
19
21
  run(scenario: IOnboardingScenario, context: IOnboardingRunContext): Promise<IOnboardingRunResult>;
22
+ runStep(sequenceStep: IOnboardingSequenceStep, context: IOnboardingSequenceStepContext): Promise<IOnboardingSequenceStepResult>;
23
+ private _runPayload;
20
24
  private _resolveTarget;
21
25
  private _waitForTargetReady;
22
26
  private _getActiveFiniteAnimations;
@@ -1,13 +1,14 @@
1
1
 
2
2
  import { __decorate, __param } from '@codingame/monaco-vscode-api/external/tslib/tslib.es6';
3
3
  import { timeout } from '@codingame/monaco-vscode-api/vscode/vs/base/common/async';
4
+ import { CancellationTokenSource } from '@codingame/monaco-vscode-api/vscode/vs/base/common/cancellation';
4
5
  import { onUnexpectedError } from '@codingame/monaco-vscode-api/vscode/vs/base/common/errors';
5
6
  import { Disposable, DisposableStore, toDisposable } from '@codingame/monaco-vscode-api/vscode/vs/base/common/lifecycle';
6
7
  import { IContextKeyService } from '@codingame/monaco-vscode-api/vscode/vs/platform/contextkey/common/contextkey.service';
7
8
  import { IWorkbenchLayoutService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/layout/browser/layoutService.service';
8
9
  import { IHostService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/host/browser/host.service';
9
10
  import { OnboardingDismissReason, OnboardingOutcome } from '../../common/onboardingScenario.js';
10
- import { findOnboardingTarget } from './onboardingTarget.js';
11
+ import { findOnboardingTarget, openOnboardingTarget } from './onboardingTarget.js';
11
12
  import { SpotlightOverlay } from './spotlightOverlay.js';
12
13
  import { SPOTLIGHT_PRESENTATION_KIND } from './spotlightTypes.js';
13
14
 
@@ -21,9 +22,102 @@ let SpotlightPresentation = class SpotlightPresentation extends Disposable {
21
22
  this.hostService = hostService;
22
23
  this.contextKeyService = contextKeyService;
23
24
  this.kind = SPOTLIGHT_PRESENTATION_KIND;
25
+ this.countsAsVisualStep = true;
24
26
  }
25
27
  async run(scenario, context) {
26
28
  const payload = scenario.presentation.payload;
29
+ return this._runPayload(payload, context);
30
+ }
31
+ async runStep(sequenceStep, context) {
32
+ const step = sequenceStep.payload;
33
+ if (step.when && !this.contextKeyService.contextMatchesRules(step.when)) {
34
+ return {
35
+ action: "skipStep",
36
+ shown: false
37
+ };
38
+ }
39
+ try {
40
+ await step.onBeforeShow?.();
41
+ } catch (error) {
42
+ onUnexpectedError(error);
43
+ }
44
+ if (context.cancellationToken.isCancellationRequested) {
45
+ return {
46
+ action: "abort",
47
+ shown: false
48
+ };
49
+ }
50
+ const target = await this._resolveTarget(
51
+ context.targetWindow,
52
+ step.targetId,
53
+ context.cancellationToken,
54
+ step.missingTarget
55
+ );
56
+ if (!target) {
57
+ return context.cancellationToken.isCancellationRequested || step.missingTarget?.kind === "abort" ? {
58
+ action: "abort",
59
+ shown: false
60
+ } : {
61
+ action: "skipStep",
62
+ shown: false
63
+ };
64
+ }
65
+ await this._waitForTargetReady(context.targetWindow, target);
66
+ if (context.cancellationToken.isCancellationRequested) {
67
+ return {
68
+ action: "abort",
69
+ shown: false
70
+ };
71
+ }
72
+ const store = ( new DisposableStore());
73
+ try {
74
+ const container = this.layoutService.getContainer(context.targetWindow);
75
+ const overlay = store.add(( new SpotlightOverlay(container)));
76
+ this.hostService.setWindowDimmed(context.targetWindow, true);
77
+ store.add(
78
+ toDisposable(() => this.hostService.setWindowDimmed(context.targetWindow, false))
79
+ );
80
+ store.add(this.layoutService.onDidLayoutContainer(() => overlay.scheduleLayout()));
81
+ const end = await this._runStep(
82
+ overlay,
83
+ context,
84
+ step,
85
+ target,
86
+ context.visualStepIndex,
87
+ context.visualStepCount,
88
+ context.canGoBack,
89
+ context.isLastVisualStep
90
+ );
91
+ overlay.hide();
92
+ switch (end.action) {
93
+ case "next":
94
+ return {
95
+ action: "next",
96
+ shown: true,
97
+ dismissReason: end.via === "target" ? OnboardingDismissReason.TargetClick : OnboardingDismissReason.Completed
98
+ };
99
+ case "back":
100
+ return {
101
+ action: "back",
102
+ shown: true
103
+ };
104
+ case "skip":
105
+ return {
106
+ action: "skipSequence",
107
+ shown: true,
108
+ dismissReason: end.reason
109
+ };
110
+ case "abort":
111
+ return {
112
+ action: "abort",
113
+ shown: true
114
+ };
115
+ }
116
+ } finally {
117
+ store.dispose();
118
+ }
119
+ }
120
+ async _runPayload(payload, context) {
27
121
  const steps = payload?.steps ?? [];
28
122
  const stepCount = steps.length;
29
123
  if (stepCount === 0) {
@@ -37,6 +131,7 @@ let SpotlightPresentation = class SpotlightPresentation extends Disposable {
37
131
  }
38
132
  let lastStepIndex = 0;
39
133
  let shown = false;
134
+ const skippedStepIndexes = ( new Set());
40
135
  const store = ( new DisposableStore());
41
136
  try {
42
137
  const container = this.layoutService.getContainer(context.targetWindow);
@@ -46,8 +141,10 @@ let SpotlightPresentation = class SpotlightPresentation extends Disposable {
46
141
  toDisposable(() => this.hostService.setWindowDimmed(context.targetWindow, false))
47
142
  );
48
143
  let aborted = false;
144
+ const targetResolutionCancellation = store.add(( new CancellationTokenSource()));
49
145
  store.add(context.onAbort(() => {
50
146
  aborted = true;
147
+ targetResolutionCancellation.cancel();
51
148
  }));
52
149
  store.add(this.layoutService.onDidLayoutContainer(() => overlay.scheduleLayout()));
53
150
  let index = 0;
@@ -55,6 +152,7 @@ let SpotlightPresentation = class SpotlightPresentation extends Disposable {
55
152
  while (index >= 0 && index < stepCount && !aborted) {
56
153
  const step = steps[index];
57
154
  if (step.when && !this.contextKeyService.contextMatchesRules(step.when)) {
155
+ skippedStepIndexes.add(index);
58
156
  index += direction;
59
157
  continue;
60
158
  }
@@ -66,21 +164,36 @@ let SpotlightPresentation = class SpotlightPresentation extends Disposable {
66
164
  if (aborted) {
67
165
  break;
68
166
  }
69
- const target = await this._resolveTarget(context.targetWindow, step.targetId);
167
+ const target = await this._resolveTarget(
168
+ context.targetWindow,
169
+ step.targetId,
170
+ targetResolutionCancellation.token,
171
+ step.missingTarget
172
+ );
70
173
  if (aborted) {
71
174
  break;
72
175
  }
73
176
  if (!target) {
177
+ if (step.missingTarget?.kind === "abort") {
178
+ aborted = true;
179
+ break;
180
+ }
181
+ skippedStepIndexes.add(index);
74
182
  index += direction;
75
183
  continue;
76
184
  }
185
+ skippedStepIndexes.delete(index);
77
186
  await this._waitForTargetReady(context.targetWindow, target);
78
187
  if (aborted) {
79
188
  break;
80
189
  }
81
190
  lastStepIndex = Math.max(lastStepIndex, index);
82
191
  shown = true;
83
- const end = await this._runStep(overlay, context, step, target, index, stepCount);
192
+ const skippedBefore = Array.from(skippedStepIndexes).filter(skippedIndex => skippedIndex < index).length;
193
+ const displayStepIndex = index - skippedBefore;
194
+ const displayStepCount = stepCount - skippedStepIndexes.size;
195
+ const end = await this._runStep(overlay, context, step, target, displayStepIndex, displayStepCount);
196
+ overlay.hide();
84
197
  switch (end.action) {
85
198
  case "next":
86
199
  if (index === stepCount - 1) {
@@ -118,13 +231,16 @@ let SpotlightPresentation = class SpotlightPresentation extends Disposable {
118
231
  };
119
232
  }
120
233
  }
121
- return aborted ? {
122
- outcome: OnboardingOutcome.Aborted,
123
- shown,
124
- dismissReason: OnboardingDismissReason.Aborted,
125
- lastStepIndex,
126
- stepCount
127
- } : {
234
+ if (aborted) {
235
+ return {
236
+ outcome: OnboardingOutcome.Aborted,
237
+ shown,
238
+ dismissReason: OnboardingDismissReason.Aborted,
239
+ lastStepIndex,
240
+ stepCount
241
+ };
242
+ }
243
+ return {
128
244
  outcome: OnboardingOutcome.Completed,
129
245
  shown,
130
246
  dismissReason: OnboardingDismissReason.Completed,
@@ -135,11 +251,25 @@ let SpotlightPresentation = class SpotlightPresentation extends Disposable {
135
251
  store.dispose();
136
252
  }
137
253
  }
138
- async _resolveTarget(targetWindow, targetId) {
139
- const deadline = Date.now() + TARGET_RESOLVE_TIMEOUT;
254
+ async _resolveTarget(targetWindow, targetId, cancellationToken, behavior) {
255
+ if (cancellationToken.isCancellationRequested) {
256
+ return undefined;
257
+ }
140
258
  let element = findOnboardingTarget(targetWindow, targetId);
141
- while (!element && Date.now() < deadline) {
142
- await timeout(TARGET_POLL_INTERVAL);
259
+ if (element || behavior?.kind === "skip" || behavior?.kind === "abort") {
260
+ return element;
261
+ }
262
+ const timeoutMs = behavior?.kind === "wait" ? Math.max(0, behavior.timeoutMs) : TARGET_RESOLVE_TIMEOUT;
263
+ const deadline = Date.now() + timeoutMs;
264
+ while (!element && Date.now() < deadline && !cancellationToken.isCancellationRequested) {
265
+ try {
266
+ await timeout(TARGET_POLL_INTERVAL, cancellationToken);
267
+ } catch (error) {
268
+ if (cancellationToken.isCancellationRequested) {
269
+ return undefined;
270
+ }
271
+ throw error;
272
+ }
143
273
  element = findOnboardingTarget(targetWindow, targetId);
144
274
  }
145
275
  return element;
@@ -162,42 +292,85 @@ let SpotlightPresentation = class SpotlightPresentation extends Disposable {
162
292
  }
163
293
  return animations;
164
294
  }
165
- _runStep(overlay, context, step, target, index, stepCount) {
166
- return ( new Promise(resolve => {
167
- const stepStore = ( new DisposableStore());
168
- const done = end => {
169
- stepStore.dispose();
170
- resolve(end);
171
- };
172
- stepStore.add(overlay.onDidClickNext(via => done({
173
- action: "next",
174
- via
175
- })));
176
- stepStore.add(overlay.onDidClickPrevious(() => done({
177
- action: "back"
178
- })));
179
- stepStore.add(overlay.onDidSkip(reason => done({
180
- action: "skip",
181
- reason
182
- })));
183
- stepStore.add(context.onAbort(() => done({
184
- action: "abort"
185
- })));
186
- const content = {
187
- title: step.title,
188
- description: step.description,
189
- stepIndex: index,
190
- stepCount,
191
- canGoBack: index > 0,
192
- isLastStep: index === stepCount - 1
295
+ async _runStep(
296
+ overlay,
297
+ context,
298
+ step,
299
+ target,
300
+ index,
301
+ stepCount,
302
+ canGoBack = index > 0,
303
+ isLastStep = index === stepCount - 1
304
+ ) {
305
+ const stepStore = ( new DisposableStore());
306
+ let ended = false;
307
+ let resolveStep;
308
+ const result = ( new Promise(resolve => resolveStep = resolve));
309
+ const done = end => {
310
+ if (ended) {
311
+ return;
312
+ }
313
+ ended = true;
314
+ stepStore.dispose();
315
+ resolveStep(end);
316
+ };
317
+ stepStore.add(overlay.onDidClickNext(via => done({
318
+ action: "next",
319
+ via
320
+ })));
321
+ stepStore.add(overlay.onDidClickPrevious(() => done({
322
+ action: "back"
323
+ })));
324
+ stepStore.add(overlay.onDidSkip(reason => done({
325
+ action: "skip",
326
+ reason
327
+ })));
328
+ stepStore.add(context.onAbort(() => done({
329
+ action: "abort"
330
+ })));
331
+ const content = {
332
+ title: step.title,
333
+ description: step.description,
334
+ nextButtonLabel: step.nextButtonLabel,
335
+ stepIndex: index,
336
+ stepCount,
337
+ canGoBack,
338
+ isLastStep
339
+ };
340
+ overlay.show(target, content, {
341
+ placement: step.placement,
342
+ allowTargetInteraction: step.allowTargetInteraction,
343
+ advanceOnTargetClick: step.advanceOnTargetClick,
344
+ hideNext: step.advanceWhen ? true : step.hideNext,
345
+ targetOverlayVisible: step.openTarget,
346
+ padding: step.padding
347
+ });
348
+ context.onDidShow?.();
349
+ if (step.advanceWhen) {
350
+ const keys = ( new Set(( step.advanceWhen.keys())));
351
+ const advanceIfSatisfied = () => {
352
+ if (this.contextKeyService.contextMatchesRules(step.advanceWhen)) {
353
+ done({
354
+ action: "next",
355
+ via: "condition"
356
+ });
357
+ }
193
358
  };
194
- overlay.show(target, content, {
195
- placement: step.placement,
196
- allowTargetInteraction: step.allowTargetInteraction,
197
- advanceOnTargetClick: step.advanceOnTargetClick,
198
- padding: step.padding
199
- });
200
- }));
359
+ stepStore.add(this.contextKeyService.onDidChangeContext(event => {
360
+ if (event.affectsSome(keys)) {
361
+ advanceIfSatisfied();
362
+ }
363
+ }));
364
+ advanceIfSatisfied();
365
+ }
366
+ if (step.openTarget && !ended) {
367
+ try {
368
+ await openOnboardingTarget(target);
369
+ } catch (error) {
370
+ onUnexpectedError(error);
371
+ }
372
+ }
373
+ return result;
201
374
  }
202
375
  };
203
376
  SpotlightPresentation = ( __decorate([( __param(0, IWorkbenchLayoutService)), ( __param(1, IHostService)), ( __param(2, IContextKeyService))], SpotlightPresentation));
@@ -4,6 +4,17 @@ import { ContextKeyExpression } from "@codingame/monaco-vscode-api/vscode/vs/pla
4
4
  export declare const SPOTLIGHT_PRESENTATION_KIND = "spotlight";
5
5
  /** Preferred placement of the callout relative to the spotlighted target. */
6
6
  export type SpotlightPlacement = "above" | "below" | "left" | "right" | "auto";
7
+ /** `advanceOnly` consumes target activation instead of running the target's action. */
8
+ export type SpotlightTargetClickBehavior = boolean | "advanceOnly";
9
+ /** Behavior when a spotlight target is not rendered when its step is reached. */
10
+ export type SpotlightMissingTargetBehavior = {
11
+ readonly kind: "skip";
12
+ } | {
13
+ readonly kind: "abort";
14
+ } | {
15
+ readonly kind: "wait";
16
+ readonly timeoutMs: number;
17
+ };
7
18
  /**
8
19
  * A single step in a spotlight tour. Steps are pure data; the spotlight
9
20
  * presentation turns them into the dim overlay, the cut-out highlight and the
@@ -22,17 +33,24 @@ export interface ISpotlightStep {
22
33
  readonly title: string;
23
34
  /** Callout body (localized string or markdown). */
24
35
  readonly description: string | IMarkdownString;
36
+ /** Localized primary button label, replacing the default Next or Done. */
37
+ readonly nextButtonLabel?: string;
25
38
  /** Preferred placement of the callout. Defaults to `'auto'`. */
26
39
  readonly placement?: SpotlightPlacement;
27
40
  /** When present and unsatisfied, the step is skipped. */
28
41
  readonly when?: ContextKeyExpression;
42
+ /** Defaults to waiting two seconds before skipping; `abort` ends the run immediately if the target is missing. */
43
+ readonly missingTarget?: SpotlightMissingTargetBehavior;
44
+ /** Opens or expands the target through its owner before the step begins. */
45
+ readonly openTarget?: boolean;
29
46
  /** Allow the spotlighted element to remain interactive. Defaults to `false`. */
30
47
  readonly allowTargetInteraction?: boolean;
31
- /**
32
- * When set, the step advances when the user clicks the spotlighted target
33
- * itself (rather than a "Next" button). The target is kept interactive.
34
- */
35
- readonly advanceOnTargetClick?: boolean;
48
+ /** Advances on target activation; `advanceOnly` consumes the activation without running its action. */
49
+ readonly advanceOnTargetClick?: SpotlightTargetClickBehavior;
50
+ /** Overrides hiding Next when advancing on target clicks. `advanceWhen` always hides Next. */
51
+ readonly hideNext?: boolean;
52
+ /** Hides Next and advances once this context expression becomes satisfied. */
53
+ readonly advanceWhen?: ContextKeyExpression;
36
54
  /** Extra padding (px) around the target when cutting the highlight hole. */
37
55
  readonly padding?: number;
38
56
  /**
@@ -8,6 +8,10 @@ import { IOnboardingRunResult, IOnboardingScenario } from "./onboardingScenario.
8
8
  export interface IOnboardingRunContext {
9
9
  /** The window hosting the UI the scenario targets (usually the main window). */
10
10
  readonly targetWindow: Window;
11
+ /**
12
+ * Reports that the presentation rendered its first visible element. Called at most once per run.
13
+ */
14
+ readonly onDidShow?: () => void;
11
15
  /**
12
16
  * Fires when the engine wants the presentation to abort the current run
13
17
  * (e.g. the application is shutting down). The presentation should resolve
@@ -39,17 +39,17 @@ export interface IOnboardingPresentationRef<TPayload = unknown> {
39
39
  * onboarding would be shown — otherwise the scorecard counts activity from before the
40
40
  * experiment could have had any effect.
41
41
  *
42
- * The treatment flags that tell the client *which* identifier belongs to a given tour resolve
42
+ * The treatment flags that tell the client *which* variant belongs to a given tour resolve
43
43
  * asynchronously, after the assignment context has already been committed to telemetry, so
44
44
  * they are too late to block the very first events. This prefix solves that race: because it
45
45
  * is a *static, well-known* string, the client can pre-emptively exclude **any**
46
46
  * assignment-context entry that starts with it from the very first event, and then
47
- * selectively unblock the one specific identifier once the user reaches the onboarding moment.
47
+ * selectively unblock the one specific variant once the user reaches the onboarding moment.
48
48
  *
49
49
  * ### Contract for experiment authors
50
- * - Every onboarding experiment configured in ExP MUST set its assignment-context identifier
51
- * to a value beginning with this prefix.
52
- * - That same value MUST be returned by the experiment's
50
+ * - Every onboarding experiment configured in ExP MUST use variant names beginning with this
51
+ * prefix.
52
+ * - Each arm's variant name MUST be returned by the experiment's
53
53
  * {@link IOnboardingExperiment.assignmentContextIdFlag} treatment flag.
54
54
  *
55
55
  * The engine enforces this defensively: an id that lacks the prefix is rejected (the experiment
@@ -77,9 +77,8 @@ export interface IOnboardingExperiment {
77
77
  */
78
78
  readonly behaviorFlag: string;
79
79
  /**
80
- * Name of the string treatment flag whose value is this experiment's assignment-context
81
- * identifier — the exact entry that appears inside the telemetry `abexp.assignmentcontext`
82
- * property and that the scorecard keys on. The value MUST start with
80
+ * Name of the string treatment flag whose value is the current arm's ExP variant name. The
81
+ * telemetry entry appends `:<allocationId>`; the value itself MUST start with
83
82
  * {@link ONBOARDING_ASSIGNMENT_CONTEXT_PREFIX}; an id without the prefix is rejected (the
84
83
  * experiment is treated as inactive) so it can never leak into telemetry ungated.
85
84
  */
@@ -115,6 +114,8 @@ export interface IOnboardingScenario<TPayload = unknown> {
115
114
  * {@link IOnboardingExperiment}.
116
115
  */
117
116
  readonly experiment?: IOnboardingExperiment;
117
+ /** Variations exposed through the onboarding developer-mode variation override. */
118
+ readonly developerModeVariations?: readonly string[];
118
119
  /** When `true`, the scenario runs on every eligible session instead of once per user. */
119
120
  readonly repeatable?: boolean;
120
121
  }
@@ -26,6 +26,8 @@ export declare const ONBOARDING_ENABLED_CONFIG = "onboarding.enabled";
26
26
  * available ids are discoverable (and toggleable) in the settings editor.
27
27
  */
28
28
  export declare const ONBOARDING_DEVELOPER_MODE_CONFIG = "onboarding.developerMode";
29
+ /** Developer override for a scenario's experiment-selected variation. */
30
+ export declare const ONBOARDING_DEVELOPER_MODE_VARIATIONS_CONFIG = "onboarding.developerModeVariations";
29
31
  /**
30
32
  * The shape of the {@link ONBOARDING_DEVELOPER_MODE_CONFIG} setting: a map of
31
33
  * scenario/tour id to whether developer mode is enabled for that scenario.
@@ -33,9 +35,14 @@ export declare const ONBOARDING_DEVELOPER_MODE_CONFIG = "onboarding.developerMod
33
35
  export type OnboardingDeveloperMode = {
34
36
  readonly [scenarioId: string]: boolean;
35
37
  };
38
+ /** The shape of the {@link ONBOARDING_DEVELOPER_MODE_VARIATIONS_CONFIG} setting. */
39
+ export type OnboardingDeveloperModeVariations = {
40
+ readonly [scenarioId: string]: string;
41
+ };
36
42
  /**
37
43
  * Whether onboarding developer mode is enabled for the given scenario/tour id.
38
44
  * Reads {@link ONBOARDING_DEVELOPER_MODE_CONFIG} and returns `true` only when the
39
45
  * setting is an object whose entry for `scenarioId` is explicitly `true`.
40
46
  */
41
47
  export declare function isOnboardingDeveloperModeEnabled(configurationService: IConfigurationService, scenarioId: string): boolean;
48
+ export declare function getOnboardingDeveloperModeVariation(configurationService: IConfigurationService, scenarioId: string): string | undefined;