chrome-devtools-frontend 1.0.1516909 → 1.0.1518653
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/docs/checklist/README.md +2 -2
- package/docs/checklist/javascript.md +1 -1
- package/docs/contributing/README.md +1 -1
- package/docs/contributing/settings-experiments-features.md +9 -8
- package/docs/cookbook/devtools_on_devtools.md +2 -2
- package/docs/cookbook/localization.md +10 -10
- package/docs/devtools-protocol.md +9 -8
- package/docs/ecosystem/automatic_workspace_folders.md +3 -3
- package/docs/get_the_code.md +0 -2
- package/docs/styleguide/ux/components.md +166 -85
- package/docs/styleguide/ux/numbers.md +3 -4
- package/front_end/core/common/README.md +13 -12
- package/front_end/core/host/GdpClient.ts +16 -1
- package/front_end/core/host/UserMetrics.ts +4 -2
- package/front_end/core/root/Runtime.ts +13 -0
- package/front_end/core/sdk/CSSMatchedStyles.ts +5 -1
- package/front_end/entrypoints/main/MainImpl.ts +6 -3
- package/front_end/generated/InspectorBackendCommands.js +10 -7
- package/front_end/generated/SupportedCSSProperties.js +21 -7
- package/front_end/generated/protocol-mapping.d.ts +16 -1
- package/front_end/generated/protocol-proxy-api.d.ts +13 -1
- package/front_end/generated/protocol.ts +95 -0
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +166 -49
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +14 -181
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +13 -315
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +224 -50
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +310 -11
- package/front_end/models/ai_assistance/performance/AIContext.ts +15 -2
- package/front_end/models/ai_code_completion/AiCodeCompletion.ts +17 -11
- package/front_end/models/badges/Badge.ts +8 -3
- package/front_end/models/badges/CodeWhispererBadge.ts +2 -4
- package/front_end/models/badges/StarterBadge.ts +2 -2
- package/front_end/models/badges/UserBadges.ts +21 -3
- package/front_end/models/javascript_metadata/NativeFunctions.js +1 -1
- package/front_end/models/trace/README.md +28 -1
- package/front_end/models/trace/handlers/UserTimingsHandler.ts +1 -1
- package/front_end/models/trace/helpers/Trace.ts +99 -43
- package/front_end/models/trace/types/TraceEvents.ts +9 -0
- package/front_end/panels/accessibility/ARIAAttributesView.ts +113 -191
- package/front_end/panels/accessibility/AccessibilityNodeView.ts +9 -9
- package/front_end/panels/accessibility/AccessibilitySubPane.ts +6 -4
- package/front_end/panels/accessibility/accessibilityProperties.css +2 -0
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +16 -2
- package/front_end/panels/ai_assistance/components/ChatView.ts +9 -10
- package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +42 -0
- package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +32 -9
- package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +7 -1
- package/front_end/panels/common/BadgeNotification.ts +21 -5
- package/front_end/panels/common/GdpSignUpDialog.ts +18 -9
- package/front_end/panels/console/ConsolePrompt.ts +1 -1
- package/front_end/panels/console/ConsoleView.ts +6 -2
- package/front_end/panels/elements/ElementsPanel.ts +4 -0
- package/front_end/panels/elements/ElementsTreeElement.ts +18 -0
- package/front_end/panels/elements/ElementsTreeOutline.ts +13 -0
- package/front_end/panels/elements/StylePropertyTreeElement.ts +21 -6
- package/front_end/panels/media/TickingFlameChart.ts +1 -1
- package/front_end/panels/profiler/HeapSnapshotView.ts +34 -19
- package/front_end/panels/search/SearchResultsPane.ts +124 -128
- package/front_end/panels/search/SearchView.ts +24 -17
- package/front_end/panels/settings/components/SyncSection.ts +16 -8
- package/front_end/panels/sources/AiCodeCompletionPlugin.ts +6 -1
- package/front_end/panels/sources/SourcesPanel.ts +3 -0
- package/front_end/panels/timeline/AppenderUtils.ts +2 -2
- package/front_end/panels/timeline/ExtensionTrackAppender.ts +13 -4
- package/front_end/panels/timeline/GPUTrackAppender.ts +2 -1
- package/front_end/panels/timeline/InteractionsTrackAppender.ts +5 -1
- package/front_end/panels/timeline/LayoutShiftsTrackAppender.ts +2 -1
- package/front_end/panels/timeline/ThreadAppender.ts +12 -3
- package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +9 -4
- package/front_end/panels/timeline/TimelinePanel.ts +3 -2
- package/front_end/panels/timeline/TimelineUIUtils.ts +5 -4
- package/front_end/panels/timeline/TimingsTrackAppender.ts +6 -1
- package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +95 -82
- package/front_end/panels/timeline/components/LiveMetricsView.ts +2 -2
- package/front_end/panels/timeline/components/cpuThrottlingSelector.css +17 -15
- package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +3 -0
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
- package/front_end/third_party/codemirror.next/chunk/codemirror.js.map +1 -1
- package/front_end/third_party/codemirror.next/codemirror.next.d.ts +6 -9
- package/front_end/third_party/codemirror.next/package.json +2 -1
- package/front_end/third_party/diff/README.chromium +1 -0
- package/front_end/ui/components/text_editor/config.ts +6 -7
- package/front_end/ui/components/tooltips/Tooltip.ts +70 -31
- package/front_end/ui/legacy/README.md +33 -24
- package/front_end/ui/legacy/SearchableView.ts +19 -26
- package/front_end/ui/legacy/TextPrompt.ts +166 -1
- package/front_end/ui/legacy/Treeoutline.ts +16 -2
- package/front_end/ui/legacy/UIUtils.ts +15 -2
- package/front_end/ui/legacy/XElement.ts +0 -43
- package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +20 -4
- package/front_end/ui/visual_logging/KnownContextValues.ts +19 -6
- package/front_end/ui/visual_logging/README.md +43 -27
- package/package.json +1 -1
@@ -7728,13 +7728,6 @@ parser](https://github.com/lezer-parser/html), extended with the
|
|
7728
7728
|
JavaScript and CSS parsers to parse the content of `<script>` and
|
7729
7729
|
`<style>` tags.
|
7730
7730
|
*/
|
7731
|
-
declare const htmlPlain: LRLanguage;
|
7732
|
-
/**
|
7733
|
-
A language provider based on the [Lezer HTML
|
7734
|
-
parser](https://github.com/lezer-parser/html), extended with the
|
7735
|
-
JavaScript and CSS parsers to parse the content of `<script>` and
|
7736
|
-
`<style>` tags.
|
7737
|
-
*/
|
7738
7731
|
declare const htmlLanguage: LRLanguage;
|
7739
7732
|
/**
|
7740
7733
|
Language support for HTML, including
|
@@ -7749,6 +7742,11 @@ declare function html(config?: {
|
|
7749
7742
|
document).
|
7750
7743
|
*/
|
7751
7744
|
matchClosingTags?: boolean;
|
7745
|
+
/**
|
7746
|
+
By default, the parser does not allow arbitrary self-closing tags.
|
7747
|
+
Set this to `true` to turn on support for `/>` self-closing tag
|
7748
|
+
syntax.
|
7749
|
+
*/
|
7752
7750
|
selfClosingTags?: boolean;
|
7753
7751
|
/**
|
7754
7752
|
Determines whether [`autoCloseTags`](https://codemirror.net/6/docs/ref/#lang-html.autoCloseTags)
|
@@ -7786,9 +7784,8 @@ declare const index_d$1_html: typeof html;
|
|
7786
7784
|
declare const index_d$1_htmlCompletionSource: typeof htmlCompletionSource;
|
7787
7785
|
declare const index_d$1_htmlCompletionSourceWith: typeof htmlCompletionSourceWith;
|
7788
7786
|
declare const index_d$1_htmlLanguage: typeof htmlLanguage;
|
7789
|
-
declare const index_d$1_htmlPlain: typeof htmlPlain;
|
7790
7787
|
declare namespace index_d$1 {
|
7791
|
-
export { type index_d$1_TagSpec as TagSpec, autoCloseTags$1 as autoCloseTags, index_d$1_html as html, index_d$1_htmlCompletionSource as htmlCompletionSource, index_d$1_htmlCompletionSourceWith as htmlCompletionSourceWith, index_d$1_htmlLanguage as htmlLanguage
|
7788
|
+
export { type index_d$1_TagSpec as TagSpec, autoCloseTags$1 as autoCloseTags, index_d$1_html as html, index_d$1_htmlCompletionSource as htmlCompletionSource, index_d$1_htmlCompletionSourceWith as htmlCompletionSourceWith, index_d$1_htmlLanguage as htmlLanguage };
|
7792
7789
|
}
|
7793
7790
|
|
7794
7791
|
type Severity = "hint" | "info" | "warning" | "error";
|
@@ -9,7 +9,7 @@
|
|
9
9
|
"@codemirror/lang-angular": "0.1.3",
|
10
10
|
"@codemirror/lang-cpp": "6.0.2",
|
11
11
|
"@codemirror/lang-css": "6.3.0",
|
12
|
-
"@codemirror/lang-html": "6.4.
|
12
|
+
"@codemirror/lang-html": "6.4.10",
|
13
13
|
"@codemirror/lang-java": "6.0.1",
|
14
14
|
"@codemirror/lang-javascript": "6.2.1",
|
15
15
|
"@codemirror/lang-less": "6.0.2",
|
@@ -36,6 +36,7 @@
|
|
36
36
|
"style-mod": "4.1.0"
|
37
37
|
},
|
38
38
|
"devDependencies": {
|
39
|
+
"rollup": "4.50.2",
|
39
40
|
"rollup-plugin-dts": "6.1.1"
|
40
41
|
}
|
41
42
|
}
|
@@ -620,18 +620,16 @@ export const aiAutoCompleteSuggestion: CM.Extension = [
|
|
620
620
|
this.decorations = CM.Decoration.none;
|
621
621
|
return;
|
622
622
|
}
|
623
|
-
// If the user typed the full selected completion, then we don't check for overlap.
|
624
|
-
// (e.g. the user wrote `flex`, traditional suggestion is `flex` and the AI autocompletion is
|
625
|
-
// `;\njustify-content: center`. Then, we want to show the AI completion)
|
626
|
-
const endsWithCompleteSelectedCompletion =
|
627
|
-
update.state.doc.sliceString(head - selectedCompletion.label.length, head) === selectedCompletion.label;
|
628
623
|
// If a traditional autocomplete menu is shown, the AI suggestion is only
|
629
624
|
// shown if it builds upon the currently selected item. If there is no
|
630
625
|
// overlap, we hide the AI suggestion. For example, for the text `console`
|
631
626
|
// if the traditional autocomplete suggests `log` and the AI
|
632
627
|
// suggests `warn`, there is no overlap and the AI suggestion is hidden.
|
633
|
-
|
634
|
-
|
628
|
+
const overlappingText = TextUtils.TextUtils.getOverlap(selectedCompletion.label, ghostText) ?? '';
|
629
|
+
const lineAtAiSuggestion = update.state.doc.lineAt(activeSuggestion.from).text;
|
630
|
+
const overlapsWithSelectedCompletion =
|
631
|
+
(lineAtAiSuggestion + overlappingText).endsWith(selectedCompletion.label);
|
632
|
+
if (!overlapsWithSelectedCompletion) {
|
635
633
|
this.decorations = CM.Decoration.none;
|
636
634
|
return;
|
637
635
|
}
|
@@ -664,6 +662,7 @@ export const aiAutoCompleteSuggestion: CM.Extension = [
|
|
664
662
|
const latency = performance.now() - activeSuggestion.startTime;
|
665
663
|
// only register impression for the first time AI generated suggestion is shown to the user.
|
666
664
|
activeSuggestion.onImpression(activeSuggestion.rpcGlobalId, activeSuggestion.sampleId, latency);
|
665
|
+
this.#lastLoggedSuggestion = activeSuggestion;
|
667
666
|
}
|
668
667
|
},
|
669
668
|
{decorations: p => p.decorations}),
|
@@ -21,6 +21,13 @@ interface PositioningParams {
|
|
21
21
|
currentPopoverRect: DOMRect;
|
22
22
|
}
|
23
23
|
|
24
|
+
enum PositionOption {
|
25
|
+
BOTTOM_SPAN_RIGHT = 'bottom-span-right',
|
26
|
+
BOTTOM_SPAN_LEFT = 'bottom-span-left',
|
27
|
+
TOP_SPAN_RIGHT = 'top-span-right',
|
28
|
+
TOP_SPAN_LEFT = 'top-span-left',
|
29
|
+
}
|
30
|
+
|
24
31
|
const positioningUtils = {
|
25
32
|
bottomSpanRight: ({anchorRect}: PositioningParams): ProposedRect => {
|
26
33
|
return {
|
@@ -93,36 +100,50 @@ const positioningUtils = {
|
|
93
100
|
}
|
94
101
|
};
|
95
102
|
|
96
|
-
const proposedRectForRichTooltip =
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
103
|
+
const proposedRectForRichTooltip = ({inspectorViewRect, anchorRect, currentPopoverRect, preferredPositions}: {
|
104
|
+
inspectorViewRect: DOMRect,
|
105
|
+
anchorRect: DOMRect,
|
106
|
+
currentPopoverRect: DOMRect,
|
107
|
+
preferredPositions: PositionOption[],
|
108
|
+
}): ProposedRect => {
|
109
|
+
// The default positioning order is `BOTTOM_SPAN_RIGHT`, `BOTTOM_SPAN_LEFT`, `TOP_SPAN_RIGHT`
|
110
|
+
// and `TOP_SPAN_LEFT`. If `preferredPositions` are given, those are tried first, before
|
111
|
+
// continuing with the remaining options in default order. Duplicate entries are removed.
|
112
|
+
const uniqueOrder = [
|
113
|
+
...new Set([
|
114
|
+
...preferredPositions,
|
115
|
+
...Object.values(PositionOption),
|
116
|
+
]),
|
117
|
+
];
|
118
|
+
|
119
|
+
// Tries the positioning options in the order given by `uniqueOrder`.
|
120
|
+
// If none of them work out, we default to showing the tooltip in the bottom right and adjust
|
121
|
+
// its insets so that the tooltip is inside the inspector view bounds.
|
122
|
+
for (const positionOption of uniqueOrder) {
|
123
|
+
let proposedRect;
|
124
|
+
switch (positionOption) {
|
125
|
+
case PositionOption.BOTTOM_SPAN_RIGHT:
|
126
|
+
proposedRect = positioningUtils.bottomSpanRight({anchorRect, currentPopoverRect});
|
127
|
+
break;
|
128
|
+
case PositionOption.BOTTOM_SPAN_LEFT:
|
129
|
+
proposedRect = positioningUtils.bottomSpanLeft({anchorRect, currentPopoverRect});
|
130
|
+
break;
|
131
|
+
case PositionOption.TOP_SPAN_RIGHT:
|
132
|
+
proposedRect = positioningUtils.topSpanRight({anchorRect, currentPopoverRect});
|
133
|
+
break;
|
134
|
+
case PositionOption.TOP_SPAN_LEFT:
|
135
|
+
proposedRect = positioningUtils.topSpanLeft({anchorRect, currentPopoverRect});
|
136
|
+
}
|
137
|
+
if (positioningUtils.isInBounds({inspectorViewRect, currentPopoverRect, proposedRect})) {
|
138
|
+
return proposedRect;
|
139
|
+
}
|
140
|
+
}
|
120
141
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
142
|
+
// If none of the options work above, we position to bottom right
|
143
|
+
// and adjust the insets so that it does not go out of bounds.
|
144
|
+
const proposedRect = positioningUtils.bottomSpanRight({anchorRect, currentPopoverRect});
|
145
|
+
return positioningUtils.insetAdjustedRect({anchorRect, currentPopoverRect, inspectorViewRect, proposedRect});
|
146
|
+
};
|
126
147
|
|
127
148
|
const proposedRectForSimpleTooltip =
|
128
149
|
({inspectorViewRect, anchorRect, currentPopoverRect}:
|
@@ -162,7 +183,8 @@ export interface TooltipProperties {
|
|
162
183
|
* @property variant - reflects the `"variant"` attribute.
|
163
184
|
* @property padding - reflects the `"padding"` attribute.
|
164
185
|
* @property useClick - reflects the `"click"` attribute.
|
165
|
-
* @property verticalDistanceIncrease -
|
186
|
+
* @property verticalDistanceIncrease - reflects the `"vertical-distance-increase"` attribute.
|
187
|
+
* @property preferSpanLeft - reflects the `"prefer-span-left"` attribute.
|
166
188
|
* @attribute id - Id of the tooltip. Used for searching an anchor element with aria-describedby.
|
167
189
|
* @attribute hover-delay - Hover length in ms before the tooltip is shown and hidden.
|
168
190
|
* @attribute variant - Variant of the tooltip, `"simple"` for strings only, inverted background,
|
@@ -170,6 +192,9 @@ export interface TooltipProperties {
|
|
170
192
|
* @attribute padding - Which padding to use, defaults to `"small"`. Use `"large"` for richer content.
|
171
193
|
* @attribute use-click - If present, the tooltip will be shown on click instead of on hover.
|
172
194
|
* @attribute vertical-distance-increase - The tooltip is moved vertically this many pixels further away from its anchor.
|
195
|
+
* @attribute prefer-span-left - If present, the tooltip's preferred position is `"span-left"` (The right
|
196
|
+
* side of the tooltip and its anchor are aligned. The tooltip expands to the left from
|
197
|
+
* there.). Applies to rich tooltips only.
|
173
198
|
* @attribute use-hotkey - If present, the tooltip will be shown on hover but not when receiving focus.
|
174
199
|
* Requires a hotkey to open when fosed (Alt-down). When `"use-click"` is present
|
175
200
|
* as well, use-click takes precedence.
|
@@ -254,6 +279,18 @@ export class Tooltip extends HTMLElement {
|
|
254
279
|
this.setAttribute('vertical-distance-increase', increase.toString());
|
255
280
|
}
|
256
281
|
|
282
|
+
get preferSpanLeft(): boolean {
|
283
|
+
return this.hasAttribute('prefer-span-left');
|
284
|
+
}
|
285
|
+
|
286
|
+
set preferSpanLeft(value: boolean) {
|
287
|
+
if (value) {
|
288
|
+
this.setAttribute('prefer-span-left', '');
|
289
|
+
} else {
|
290
|
+
this.removeAttribute('prefer-span-left');
|
291
|
+
}
|
292
|
+
}
|
293
|
+
|
257
294
|
get anchor(): HTMLElement|null {
|
258
295
|
return this.#anchor;
|
259
296
|
}
|
@@ -391,8 +428,10 @@ export class Tooltip extends HTMLElement {
|
|
391
428
|
this.#previousPopoverRect = currentPopoverRect;
|
392
429
|
|
393
430
|
const inspectorViewRect = UI.InspectorView.InspectorView.instance().element.getBoundingClientRect();
|
431
|
+
const preferredPositions =
|
432
|
+
this.preferSpanLeft ? [PositionOption.BOTTOM_SPAN_LEFT, PositionOption.TOP_SPAN_LEFT] : [];
|
394
433
|
const proposedPopoverRect = this.variant === 'rich' ?
|
395
|
-
proposedRectForRichTooltip({inspectorViewRect, anchorRect, currentPopoverRect}) :
|
434
|
+
proposedRectForRichTooltip({inspectorViewRect, anchorRect, currentPopoverRect, preferredPositions}) :
|
396
435
|
proposedRectForSimpleTooltip({inspectorViewRect, anchorRect, currentPopoverRect});
|
397
436
|
this.style.left = `${proposedPopoverRect.left}px`;
|
398
437
|
|
@@ -11,7 +11,7 @@ For a detailed explanation of the behavior of each property, please see the docu
|
|
11
11
|
|
12
12
|
As an example, take the registration of the `Network conditions` pane, which is located in the drawer:
|
13
13
|
|
14
|
-
```
|
14
|
+
```ts
|
15
15
|
UI.ViewManager.registerViewExtension({
|
16
16
|
location: UI.ViewManager.ViewLocationValues.DRAWER_VIEW,
|
17
17
|
id: 'network.config',
|
@@ -24,11 +24,11 @@ UI.ViewManager.registerViewExtension({
|
|
24
24
|
ls`network throttling`,
|
25
25
|
ls`useragent`,
|
26
26
|
ls`user agent`,
|
27
|
-
ls`user-agent
|
27
|
+
ls`user-agent`,
|
28
28
|
],
|
29
|
-
async loadView(): UI.Widget.Widget
|
30
|
-
const
|
31
|
-
return
|
29
|
+
async loadView(): UI.Widget.Widget {
|
30
|
+
const Network = await loadNetworkModule();
|
31
|
+
return Network.NetworkConfigView.NetworkConfigView.instance();
|
32
32
|
},
|
33
33
|
});
|
34
34
|
```
|
@@ -43,38 +43,40 @@ For a detailed explanation of the behavior of each property, please see the docu
|
|
43
43
|
|
44
44
|
As an example, take the registration of the `network.toggle-recording` action, which allows to start or stop recording a network log:
|
45
45
|
|
46
|
-
```
|
46
|
+
```ts
|
47
47
|
UI.ActionRegistration.registerActionExtension({
|
48
|
-
actionId:
|
48
|
+
actionId: 'network.toggle-recording',
|
49
49
|
category: UI.ActionRegistration.ActionCategory.NETWORK,
|
50
50
|
iconClass: UI.ActionRegistration.IconClass.LARGEICON_START_RECORDING,
|
51
|
-
toggleable:
|
51
|
+
toggleable: true,
|
52
52
|
toggledIconClass: UI.ActionRegistration.IconClass.LARGEICON_STOP_RECORDING,
|
53
|
-
toggleWithRedColor:
|
53
|
+
toggleWithRedColor: true,
|
54
54
|
contextTypes(): unknown[] {
|
55
|
-
return maybeRetrieveContextTypes(Network
|
55
|
+
return maybeRetrieveContextTypes(Network => [
|
56
|
+
Network.NetworkPanel.NetworkPanel,
|
57
|
+
]);
|
56
58
|
},
|
57
|
-
async loadActionDelegate(): UI.ActionRegistration.ActionDelegate
|
58
|
-
const
|
59
|
-
return
|
59
|
+
async loadActionDelegate(): UI.ActionRegistration.ActionDelegate {
|
60
|
+
const Network = await loadNetworkModule();
|
61
|
+
return Network.NetworkPanel.ActionDelegate.instance();
|
60
62
|
},
|
61
|
-
options:
|
63
|
+
options: [
|
62
64
|
{
|
63
|
-
value:
|
65
|
+
value: true,
|
64
66
|
title: ls`Record network log`,
|
65
67
|
},
|
66
68
|
{
|
67
|
-
value:
|
69
|
+
value: false,
|
68
70
|
title: ls`Stop recording network log`,
|
69
71
|
},
|
70
72
|
],
|
71
|
-
bindings:
|
73
|
+
bindings: [
|
72
74
|
{
|
73
|
-
shortcut:
|
75
|
+
shortcut: 'Ctrl+E',
|
74
76
|
platform: UI.ActionRegistration.Platforms.WindowsLinux,
|
75
77
|
},
|
76
78
|
{
|
77
|
-
shortcut:
|
79
|
+
shortcut: 'Meta+E',
|
78
80
|
platform: UI.ActionRegistration.Platforms.Mac,
|
79
81
|
},
|
80
82
|
],
|
@@ -105,10 +107,12 @@ A key binding will be used when the selected preset matches any of the keybind s
|
|
105
107
|
If you don't specify additional constraints, the keybinding will be always available.
|
106
108
|
It is possible that this results in duplicate keybindings, for example:
|
107
109
|
|
108
|
-
```
|
110
|
+
```ts
|
109
111
|
UI.ActionRegistration.registerActionExtension({
|
110
112
|
contextTypes() {
|
111
|
-
return maybeRetrieveContextTypes(Elements => [
|
113
|
+
return maybeRetrieveContextTypes(Elements => [
|
114
|
+
Elements.ElementsPanel.ElementsPanel,
|
115
|
+
]);
|
112
116
|
},
|
113
117
|
bindings: [
|
114
118
|
{
|
@@ -133,10 +137,12 @@ These two key bindings both allocate the same key for the `windows, linux` platf
|
|
133
137
|
If key bindings were declared such that **at least one** of the three variables (`platform, contextTypes` and `keybindSets`) doesn't overlap, then no duplication would occur.
|
134
138
|
For example, the following is **not** a form of duplication:
|
135
139
|
|
136
|
-
```
|
140
|
+
```ts
|
137
141
|
UI.ActionRegistration.registerActionExtension({
|
138
142
|
contextTypes() {
|
139
|
-
return maybeRetrieveContextTypes(Elements => [
|
143
|
+
return maybeRetrieveContextTypes(Elements => [
|
144
|
+
Elements.ElementsPanel.ElementsPanel,
|
145
|
+
]);
|
140
146
|
},
|
141
147
|
bindings: [
|
142
148
|
{
|
@@ -146,8 +152,10 @@ UI.ActionRegistration.registerActionExtension({
|
|
146
152
|
],
|
147
153
|
});
|
148
154
|
```
|
155
|
+
|
149
156
|
And
|
150
|
-
|
157
|
+
|
158
|
+
```ts
|
151
159
|
UI.ActionRegistration.registerActionExtension({
|
152
160
|
contextTypes: undefined,
|
153
161
|
bindings: [
|
@@ -159,4 +167,5 @@ UI.ActionRegistration.registerActionExtension({
|
|
159
167
|
],
|
160
168
|
});
|
161
169
|
```
|
170
|
+
|
162
171
|
In this case, they keybindings are declared for different keybind sets and thus do not result in duplcation.
|
@@ -414,21 +414,19 @@ export class SearchableView extends VBox {
|
|
414
414
|
}
|
415
415
|
|
416
416
|
updateSearchMatchesCount(matches: number): void {
|
417
|
-
|
418
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
419
|
-
const untypedSearchProvider = (this.searchProvider as any);
|
420
|
-
if (untypedSearchProvider.currentSearchMatches === matches) {
|
417
|
+
if (this.searchProvider.currentSearchMatches === matches) {
|
421
418
|
return;
|
422
419
|
}
|
423
|
-
|
424
|
-
this.updateSearchMatchesCountAndCurrentMatchIndex(
|
420
|
+
this.searchProvider.currentSearchMatches = matches;
|
421
|
+
this.updateSearchMatchesCountAndCurrentMatchIndex(this.searchProvider.currentQuery ? matches : 0, -1);
|
425
422
|
}
|
426
423
|
|
427
424
|
updateCurrentMatchIndex(currentMatchIndex: number): void {
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
425
|
+
if (!this.searchProvider.currentSearchMatches) {
|
426
|
+
return;
|
427
|
+
}
|
428
|
+
|
429
|
+
this.updateSearchMatchesCountAndCurrentMatchIndex(this.searchProvider.currentSearchMatches, currentMatchIndex);
|
432
430
|
}
|
433
431
|
|
434
432
|
closeSearch(): void {
|
@@ -595,12 +593,9 @@ export class SearchableView extends VBox {
|
|
595
593
|
}
|
596
594
|
|
597
595
|
private clearSearch(): void {
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
delete this.currentQuery;
|
602
|
-
if (Boolean(untypedSearchProvider.currentQuery)) {
|
603
|
-
delete untypedSearchProvider.currentQuery;
|
596
|
+
this.currentQuery = undefined;
|
597
|
+
if (Boolean(this.searchProvider.currentQuery)) {
|
598
|
+
this.searchProvider.currentQuery = undefined;
|
604
599
|
this.searchProvider.onSearchCanceled();
|
605
600
|
}
|
606
601
|
this.updateSearchMatchesCountAndCurrentMatchIndex(0, -1);
|
@@ -614,9 +609,7 @@ export class SearchableView extends VBox {
|
|
614
609
|
}
|
615
610
|
|
616
611
|
this.currentQuery = query;
|
617
|
-
|
618
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
619
|
-
(this.searchProvider as any).currentQuery = query;
|
612
|
+
this.searchProvider.currentQuery = query;
|
620
613
|
|
621
614
|
const searchConfig = this.currentSearchConfig();
|
622
615
|
this.searchProvider.performSearch(searchConfig, shouldJump, jumpBackwards);
|
@@ -644,17 +637,17 @@ export class SearchableView extends VBox {
|
|
644
637
|
|
645
638
|
private replace(): void {
|
646
639
|
if (!this.replaceProvider) {
|
647
|
-
throw new Error('No \'
|
640
|
+
throw new Error('No \'replaceable\' provided to SearchableView!');
|
648
641
|
}
|
649
642
|
const searchConfig = this.currentSearchConfig();
|
650
643
|
this.replaceProvider.replaceSelectionWith(searchConfig, this.replaceInputElement.value);
|
651
|
-
|
644
|
+
this.currentQuery = undefined;
|
652
645
|
this.performSearch(true, true);
|
653
646
|
}
|
654
647
|
|
655
648
|
private replaceAll(): void {
|
656
649
|
if (!this.replaceProvider) {
|
657
|
-
throw new Error('No \'
|
650
|
+
throw new Error('No \'replaceable\' provided to SearchableView!');
|
658
651
|
}
|
659
652
|
const searchConfig = this.currentSearchConfig();
|
660
653
|
this.replaceProvider.replaceAllWith(searchConfig, this.replaceInputElement.value);
|
@@ -666,9 +659,7 @@ export class SearchableView extends VBox {
|
|
666
659
|
return;
|
667
660
|
}
|
668
661
|
|
669
|
-
|
670
|
-
clearTimeout(this.valueChangedTimeoutId);
|
671
|
-
}
|
662
|
+
clearTimeout(this.valueChangedTimeoutId);
|
672
663
|
const timeout = this.searchInputElement.value.length < 3 ? 200 : 0;
|
673
664
|
this.valueChangedTimeoutId = window.setTimeout(this.onValueChanged.bind(this), timeout);
|
674
665
|
}
|
@@ -677,7 +668,7 @@ export class SearchableView extends VBox {
|
|
677
668
|
if (!this.searchIsVisible) {
|
678
669
|
return;
|
679
670
|
}
|
680
|
-
|
671
|
+
this.valueChangedTimeoutId = undefined;
|
681
672
|
this.performSearch(false, true);
|
682
673
|
}
|
683
674
|
}
|
@@ -685,6 +676,8 @@ export class SearchableView extends VBox {
|
|
685
676
|
const searchableViewsByElement = new WeakMap<Element, SearchableView>();
|
686
677
|
|
687
678
|
export interface Searchable {
|
679
|
+
currentQuery?: string;
|
680
|
+
currentSearchMatches?: number;
|
688
681
|
onSearchCanceled(): void;
|
689
682
|
// Called when the search toolbar is closed
|
690
683
|
onSearchClosed?: () => void;
|
@@ -41,7 +41,168 @@ import * as ARIAUtils from './ARIAUtils.js';
|
|
41
41
|
import {SuggestBox, type SuggestBoxDelegate, type Suggestion} from './SuggestBox.js';
|
42
42
|
import textPromptStyles from './textPrompt.css.js';
|
43
43
|
import {Tooltip} from './Tooltip.js';
|
44
|
-
import {ElementFocusRestorer} from './UIUtils.js';
|
44
|
+
import {cloneCustomElement, ElementFocusRestorer} from './UIUtils.js';
|
45
|
+
|
46
|
+
/**
|
47
|
+
* A custom element wrapper around TextPrompt that allows text-editing contents in-place.
|
48
|
+
*
|
49
|
+
* ## Usage ##
|
50
|
+
*
|
51
|
+
* ```
|
52
|
+
* <devtools-prompt>
|
53
|
+
* <b>Structured</b> content
|
54
|
+
* </devtools-prompt>
|
55
|
+
*
|
56
|
+
* ```
|
57
|
+
*
|
58
|
+
* @property completionTimeout Sets the delay for showing the autocomplete suggestion box.
|
59
|
+
* @event commit Editing is done and the result was accepted.
|
60
|
+
* @event expand Editing was canceled.
|
61
|
+
* @event beforeautocomplete This is sent before the autocomplete suggestion box is triggered and before the <datalist>
|
62
|
+
* is read.
|
63
|
+
* @attribute editing Setting/removing this attribute starts/stops editing.
|
64
|
+
* @attribute completions Sets the `id` of the <datalist> containing the autocomplete options.
|
65
|
+
*/
|
66
|
+
export class TextPromptElement extends HTMLElement {
|
67
|
+
static readonly observedAttributes = ['editing', 'completions'];
|
68
|
+
readonly #shadow = this.attachShadow({mode: 'open'});
|
69
|
+
readonly #entrypoint = this.#shadow.createChild('span');
|
70
|
+
readonly #slot = this.#entrypoint.createChild('slot');
|
71
|
+
readonly #textPrompt = new TextPrompt();
|
72
|
+
#completionTimeout: number|null = null;
|
73
|
+
|
74
|
+
constructor() {
|
75
|
+
super();
|
76
|
+
this.#textPrompt.initialize(this.#willAutoComplete.bind(this));
|
77
|
+
}
|
78
|
+
|
79
|
+
attributeChangedCallback(name: string, oldValue: string|null, newValue: string|null): void {
|
80
|
+
if (oldValue === newValue || !this.isConnected) {
|
81
|
+
return;
|
82
|
+
}
|
83
|
+
|
84
|
+
switch (name) {
|
85
|
+
case 'editing':
|
86
|
+
if (newValue !== null && newValue !== 'false' && oldValue === null) {
|
87
|
+
this.#startEditing();
|
88
|
+
} else {
|
89
|
+
this.#stopEditing();
|
90
|
+
}
|
91
|
+
break;
|
92
|
+
case 'completions':
|
93
|
+
if (this.#textPrompt.isSuggestBoxVisible()) {
|
94
|
+
void this.#textPrompt.complete(/* force=*/ true);
|
95
|
+
}
|
96
|
+
break;
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
async #willAutoComplete(expression?: string, filter?: string, force?: boolean): Promise<Suggestion[]> {
|
101
|
+
if (!force) {
|
102
|
+
this.dispatchEvent(new TextPromptElement.BeforeAutoCompleteEvent({expression, filter}));
|
103
|
+
}
|
104
|
+
|
105
|
+
const listId = this.getAttribute('completions');
|
106
|
+
if (!listId) {
|
107
|
+
return [];
|
108
|
+
}
|
109
|
+
|
110
|
+
const datalist = this.getComponentRoot()?.querySelectorAll<HTMLOptionElement>(`datalist#${listId} > option`);
|
111
|
+
if (!datalist?.length) {
|
112
|
+
return [];
|
113
|
+
}
|
114
|
+
|
115
|
+
filter = filter?.toLowerCase();
|
116
|
+
return datalist.values()
|
117
|
+
.filter(option => option.textContent.startsWith(filter ?? ''))
|
118
|
+
.map(option => ({text: option.textContent}))
|
119
|
+
.toArray();
|
120
|
+
}
|
121
|
+
|
122
|
+
#startEditing(): void {
|
123
|
+
const placeholder = this.#entrypoint.createChild('span');
|
124
|
+
placeholder.textContent = this.#slot.deepInnerText();
|
125
|
+
this.#slot.remove();
|
126
|
+
|
127
|
+
const proxy = this.#textPrompt.attachAndStartEditing(placeholder, e => this.#done(e, /* commit=*/ true));
|
128
|
+
proxy.addEventListener('keydown', this.#editingValueKeyDown.bind(this));
|
129
|
+
placeholder.getComponentSelection()?.selectAllChildren(placeholder);
|
130
|
+
}
|
131
|
+
|
132
|
+
#stopEditing(): void {
|
133
|
+
this.#entrypoint.removeChildren();
|
134
|
+
this.#entrypoint.appendChild(this.#slot);
|
135
|
+
this.#textPrompt.detach();
|
136
|
+
}
|
137
|
+
|
138
|
+
connectedCallback(): void {
|
139
|
+
if (this.hasAttribute('editing')) {
|
140
|
+
this.attributeChangedCallback('editing', null, '');
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
#done(e: Event, commit: boolean): void {
|
145
|
+
const target = e.target as HTMLElement;
|
146
|
+
const text = target.textContent || '';
|
147
|
+
if (commit) {
|
148
|
+
this.dispatchEvent(new TextPromptElement.CommitEvent(text));
|
149
|
+
} else {
|
150
|
+
this.dispatchEvent(new TextPromptElement.CancelEvent());
|
151
|
+
}
|
152
|
+
e.consume();
|
153
|
+
}
|
154
|
+
|
155
|
+
#editingValueKeyDown(event: Event): void {
|
156
|
+
if (event.handled || !(event instanceof KeyboardEvent)) {
|
157
|
+
return;
|
158
|
+
}
|
159
|
+
|
160
|
+
if (event.key === 'Enter') {
|
161
|
+
this.#done(event, /* commit=*/ true);
|
162
|
+
} else if (Platform.KeyboardUtilities.isEscKey(event)) {
|
163
|
+
this.#done(event, /* commit=*/ false);
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
set completionTimeout(timeout: number) {
|
168
|
+
this.#completionTimeout = timeout;
|
169
|
+
this.#textPrompt.setAutocompletionTimeout(timeout);
|
170
|
+
}
|
171
|
+
|
172
|
+
override cloneNode(): Node {
|
173
|
+
const clone = cloneCustomElement(this);
|
174
|
+
if (this.#completionTimeout !== null) {
|
175
|
+
clone.completionTimeout = this.#completionTimeout;
|
176
|
+
}
|
177
|
+
return clone;
|
178
|
+
}
|
179
|
+
}
|
180
|
+
|
181
|
+
export namespace TextPromptElement {
|
182
|
+
export class CommitEvent extends CustomEvent<string> {
|
183
|
+
constructor(detail: string) {
|
184
|
+
super('commit', {detail});
|
185
|
+
}
|
186
|
+
}
|
187
|
+
export class CancelEvent extends CustomEvent<string> {
|
188
|
+
constructor() {
|
189
|
+
super('cancel');
|
190
|
+
}
|
191
|
+
}
|
192
|
+
export class BeforeAutoCompleteEvent extends CustomEvent<{expression?: string, filter?: string}> {
|
193
|
+
constructor(detail: {expression?: string, filter?: string}) {
|
194
|
+
super('beforeautocomplete', {detail});
|
195
|
+
}
|
196
|
+
}
|
197
|
+
}
|
198
|
+
|
199
|
+
customElements.define('devtools-prompt', TextPromptElement);
|
200
|
+
|
201
|
+
declare global {
|
202
|
+
interface HTMLElementTagNameMap {
|
203
|
+
'devtools-prompt': TextPromptElement;
|
204
|
+
}
|
205
|
+
}
|
45
206
|
|
46
207
|
export class TextPrompt extends Common.ObjectWrapper.ObjectWrapper<EventTypes> implements SuggestBoxDelegate {
|
47
208
|
private proxyElement!: HTMLElement|undefined;
|
@@ -510,6 +671,10 @@ export class TextPrompt extends Common.ObjectWrapper.ObjectWrapper<EventTypes> i
|
|
510
671
|
|
511
672
|
async complete(force?: boolean): Promise<void> {
|
512
673
|
this.clearAutocompleteTimeout();
|
674
|
+
if (!this.element().isConnected) {
|
675
|
+
return;
|
676
|
+
}
|
677
|
+
|
513
678
|
const selection = this.element().getComponentSelection();
|
514
679
|
if (!selection || selection.rangeCount === 0) {
|
515
680
|
return;
|