chrome-devtools-frontend 1.0.1667564 → 1.0.1669021

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 (211) hide show
  1. package/.agents/skills/devtools-ci/SKILL.md +65 -0
  2. package/.agents/skills/devtools-setting-migration/SKILL.md +290 -0
  3. package/.agents/skills/gerrit-cli/SKILL.md +59 -0
  4. package/.agents/skills/gerrit-cli/references/commands.md +93 -0
  5. package/.agents/skills/gerrit-cli/references/workflows.md +56 -0
  6. package/.agents/skills/gerrit-cli/scripts/gerrit_client_wrapper.py +47 -0
  7. package/.agents/skills/gerrit-cli/scripts/import_upstream.py +230 -0
  8. package/.agents/skills/ui-eng-vision-widget-promoter/SKILL.md +11 -4
  9. package/.agents/skills/ui-widgets/SKILL.md +25 -1
  10. package/docs/contributing/infrastructure.md +7 -1
  11. package/docs/ui_engineering.md +44 -1
  12. package/front_end/core/common/Settings.ts +11 -4
  13. package/front_end/core/host/AidaClient.ts +66 -47
  14. package/front_end/core/host/GcaClient.ts +4 -6
  15. package/front_end/core/host/ResourceLoader.ts +4 -2
  16. package/front_end/core/root/Runtime.ts +5 -5
  17. package/front_end/core/sdk/CSSModel.test.api.ts +102 -0
  18. package/front_end/core/sdk/CSSModel.ts +3 -2
  19. package/front_end/core/sdk/ConsoleModel.ts +3 -2
  20. package/front_end/core/sdk/DebuggerModel.ts +16 -7
  21. package/front_end/core/sdk/SDKSettings.ts +38 -0
  22. package/front_end/core/sdk/sdk-meta.ts +0 -120
  23. package/front_end/core/sdk/sdk.ts +2 -0
  24. package/front_end/entrypoints/main/MainImpl.ts +10 -3
  25. package/front_end/foundation/Universe.ts +7 -0
  26. package/front_end/generated/InspectorBackendCommands.ts +2 -2
  27. package/front_end/generated/protocol.ts +11 -3
  28. package/front_end/models/ai_assistance/AiAgent2.ts +1 -1
  29. package/front_end/models/ai_assistance/AiUtils.ts +67 -4
  30. package/front_end/models/ai_assistance/BuiltInAi.ts +11 -6
  31. package/front_end/models/ai_assistance/ChangeManager.ts +17 -1
  32. package/front_end/models/ai_assistance/StorageItem.ts +26 -5
  33. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +2 -19
  34. package/front_end/models/ai_code_generation/AiCodeGeneration.ts +2 -3
  35. package/front_end/models/javascript_metadata/NativeFunctions.js +22 -9
  36. package/front_end/models/source_map_scopes/NamesResolver.ts +23 -17
  37. package/front_end/models/trace/handlers/RendererHandler.ts +20 -4
  38. package/front_end/models/trace/types/File.ts +1 -0
  39. package/front_end/models/workspace/UISourceCode.ts +0 -10
  40. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +61 -182
  41. package/front_end/panels/ai_assistance/components/ChatInput.ts +25 -8
  42. package/front_end/panels/ai_assistance/components/ChatMessage.ts +13 -69
  43. package/front_end/panels/ai_assistance/components/ChatView.ts +14 -8
  44. package/front_end/panels/ai_assistance/components/chatMessage.css +2 -20
  45. package/front_end/panels/application/ApplicationPanelSidebar.ts +2 -2
  46. package/front_end/panels/application/ApplicationPanelTreeElement.ts +41 -3
  47. package/front_end/panels/application/ServiceWorkerUpdateCycleView.ts +131 -134
  48. package/front_end/panels/application/ServiceWorkersView.ts +8 -8
  49. package/front_end/panels/application/application.ts +2 -0
  50. package/front_end/panels/application/components/AdsView.ts +10 -6
  51. package/front_end/panels/application/preloading/PreloadingView.ts +53 -45
  52. package/front_end/panels/application/preloading/components/PreloadingDetailsReportView.ts +111 -119
  53. package/front_end/panels/application/preloading/components/preloadingDetailsReportView.css +22 -24
  54. package/front_end/panels/application/serviceWorkerUpdateCycleView.css +105 -102
  55. package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +107 -96
  56. package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +17 -19
  57. package/front_end/panels/common/AiCodeCompletionTeaser.ts +6 -6
  58. package/front_end/panels/common/AiCodeGenerationTeaser.ts +28 -26
  59. package/front_end/panels/common/common.ts +1 -1
  60. package/front_end/panels/console/ConsoleInsightTeaser.ts +6 -8
  61. package/front_end/panels/console/ConsoleView.ts +6 -4
  62. package/front_end/panels/console/ConsoleViewMessage.ts +1 -1
  63. package/front_end/panels/console/console-meta.ts +28 -0
  64. package/front_end/panels/elements/StylesAiCodeCompletionProvider.ts +1 -1
  65. package/front_end/panels/elements/StylesSidebarPane.ts +3 -2
  66. package/front_end/panels/emulation/DeviceModeToolbar.ts +2 -2
  67. package/front_end/panels/emulation/DeviceModeView.ts +63 -104
  68. package/front_end/panels/emulation/DeviceModeWrapper.ts +18 -4
  69. package/front_end/panels/explain/components/ConsoleInsight.ts +4 -6
  70. package/front_end/panels/explain/explain-meta.ts +7 -49
  71. package/front_end/panels/network/NetworkDataGridNode.ts +1 -1
  72. package/front_end/panels/network/NetworkLogView.ts +1 -0
  73. package/front_end/panels/network/OriginalResourceChunkView.ts +340 -0
  74. package/front_end/panels/network/ResourceChunkView.ts +72 -46
  75. package/front_end/panels/network/ResourceDirectSocketChunkView.ts +3 -2
  76. package/front_end/panels/network/ResourceWebSocketFrameView.ts +3 -3
  77. package/front_end/panels/network/originalResourceChunkView.css +49 -0
  78. package/front_end/panels/screencast/ScreencastView.ts +401 -260
  79. package/front_end/panels/screencast/screencastView.css +113 -100
  80. package/front_end/panels/sensors/LocationsSettingsTab.ts +329 -135
  81. package/front_end/panels/sensors/locationsSettingsTab.css +78 -4
  82. package/front_end/panels/settings/AISettingsTab.ts +17 -100
  83. package/front_end/panels/snippets/ScriptSnippetFileSystem.ts +2 -2
  84. package/front_end/panels/snippets/SnippetsQuickOpen.ts +4 -4
  85. package/front_end/panels/sources/AddSourceMapURLDialog.ts +3 -3
  86. package/front_end/panels/sources/AiCodeCompletionPlugin.ts +4 -3
  87. package/front_end/panels/sources/BreakpointEditDialog.ts +14 -14
  88. package/front_end/panels/sources/BreakpointsView.ts +14 -14
  89. package/front_end/panels/sources/CallStackSidebarPane.ts +6 -6
  90. package/front_end/panels/sources/CategorizedBreakpointL10n.ts +18 -18
  91. package/front_end/panels/sources/CoveragePlugin.ts +7 -7
  92. package/front_end/panels/sources/DebuggerPausedMessage.ts +23 -23
  93. package/front_end/panels/sources/DebuggerPlugin.ts +31 -31
  94. package/front_end/panels/sources/FilteredUISourceCodeListProvider.ts +2 -2
  95. package/front_end/panels/sources/GoToLineQuickOpen.ts +10 -10
  96. package/front_end/panels/sources/NavigatorView.ts +34 -27
  97. package/front_end/panels/sources/OutlineQuickOpen.ts +3 -3
  98. package/front_end/panels/sources/PersistenceActions.ts +9 -9
  99. package/front_end/panels/sources/ProfilePlugin.ts +1 -1
  100. package/front_end/panels/sources/ScopeChainSidebarPane.ts +5 -5
  101. package/front_end/panels/sources/SnippetsPlugin.ts +2 -2
  102. package/front_end/panels/sources/SourcesNavigator.ts +20 -18
  103. package/front_end/panels/sources/SourcesPanel.ts +26 -25
  104. package/front_end/panels/sources/SourcesView.ts +5 -6
  105. package/front_end/panels/sources/TabbedEditorContainer.ts +3 -3
  106. package/front_end/panels/sources/ThreadsSidebarPane.ts +1 -1
  107. package/front_end/panels/sources/UISourceCodeFrame.ts +0 -2
  108. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +7 -7
  109. package/front_end/panels/sources/components/HeadersView.ts +2 -2
  110. package/front_end/panels/sources/sources-meta.ts +164 -71
  111. package/front_end/panels/sources/sources.ts +0 -2
  112. package/front_end/panels/timeline/TimelineHistoryManager.ts +14 -3
  113. package/front_end/panels/timeline/TimelineLoader.ts +72 -42
  114. package/front_end/panels/timeline/TimelinePanel.ts +29 -4
  115. package/front_end/panels/timeline/timelineHistoryManager.css +4 -0
  116. package/front_end/third_party/chromium/README.chromium +1 -1
  117. package/front_end/third_party/puppeteer/README.chromium +2 -2
  118. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +187 -1
  119. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +91 -38
  120. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Browser.d.ts +145 -2
  121. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Browser.d.ts.map +1 -1
  122. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Browser.js +0 -2
  123. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Browser.js.map +1 -1
  124. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/BrowserContext.d.ts +0 -2
  125. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/BrowserContext.d.ts.map +1 -1
  126. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/BrowserContext.js +0 -2
  127. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/BrowserContext.js.map +1 -1
  128. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Dialog.d.ts +3 -2
  129. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Dialog.d.ts.map +1 -1
  130. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Dialog.js +8 -2
  131. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Dialog.js.map +1 -1
  132. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/JSHandle.d.ts +0 -2
  133. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/JSHandle.d.ts.map +1 -1
  134. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/JSHandle.js +0 -2
  135. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/JSHandle.js.map +1 -1
  136. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Page.d.ts +1 -3
  137. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Page.d.ts.map +1 -1
  138. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Page.js +0 -2
  139. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Page.js.map +1 -1
  140. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Realm.d.ts +0 -1
  141. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Realm.d.ts.map +1 -1
  142. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Realm.js +0 -1
  143. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Realm.js.map +1 -1
  144. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/BidiOverCdp.d.ts.map +1 -1
  145. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/BidiOverCdp.js +2 -2
  146. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/BidiOverCdp.js.map +1 -1
  147. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/Browser.d.ts +5 -1
  148. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/Browser.d.ts.map +1 -1
  149. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/Browser.js +12 -0
  150. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/Browser.js.map +1 -1
  151. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/HTTPResponse.d.ts.map +1 -1
  152. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/HTTPResponse.js +4 -1
  153. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/HTTPResponse.js.map +1 -1
  154. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Browser.d.ts +5 -1
  155. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Browser.d.ts.map +1 -1
  156. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Browser.js +49 -0
  157. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Browser.js.map +1 -1
  158. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/EventEmitter.d.ts +0 -6
  159. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/EventEmitter.d.ts.map +1 -1
  160. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/EventEmitter.js +0 -6
  161. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/EventEmitter.js.map +1 -1
  162. package/front_end/third_party/puppeteer/package/lib/puppeteer/generated/injected.d.ts +1 -1
  163. package/front_end/third_party/puppeteer/package/lib/puppeteer/generated/injected.d.ts.map +1 -1
  164. package/front_end/third_party/puppeteer/package/lib/puppeteer/generated/injected.js +1 -1
  165. package/front_end/third_party/puppeteer/package/lib/puppeteer/generated/injected.js.map +1 -1
  166. package/front_end/third_party/puppeteer/package/lib/puppeteer/injected/util.d.ts.map +1 -1
  167. package/front_end/third_party/puppeteer/package/lib/puppeteer/injected/util.js +3 -0
  168. package/front_end/third_party/puppeteer/package/lib/puppeteer/injected/util.js.map +1 -1
  169. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/ScreenRecorder.d.ts +0 -3
  170. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/ScreenRecorder.d.ts.map +1 -1
  171. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/ScreenRecorder.js +0 -3
  172. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/ScreenRecorder.js.map +1 -1
  173. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.d.ts +3 -3
  174. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js +3 -3
  175. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/disposable.d.ts +2 -2
  176. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/disposable.js +2 -2
  177. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.d.ts +1 -1
  178. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.js +1 -1
  179. package/front_end/third_party/puppeteer/package/lib/types.d.ts +187 -1
  180. package/front_end/third_party/puppeteer/package/package.json +5 -5
  181. package/front_end/third_party/puppeteer/package/src/api/Browser.ts +155 -2
  182. package/front_end/third_party/puppeteer/package/src/api/BrowserContext.ts +0 -2
  183. package/front_end/third_party/puppeteer/package/src/api/Dialog.ts +10 -2
  184. package/front_end/third_party/puppeteer/package/src/api/JSHandle.ts +0 -2
  185. package/front_end/third_party/puppeteer/package/src/api/Page.ts +1 -3
  186. package/front_end/third_party/puppeteer/package/src/api/Realm.ts +0 -1
  187. package/front_end/third_party/puppeteer/package/src/bidi/BidiOverCdp.ts +4 -2
  188. package/front_end/third_party/puppeteer/package/src/bidi/Browser.ts +17 -0
  189. package/front_end/third_party/puppeteer/package/src/bidi/HTTPResponse.ts +5 -4
  190. package/front_end/third_party/puppeteer/package/src/cdp/Browser.ts +66 -0
  191. package/front_end/third_party/puppeteer/package/src/common/EventEmitter.ts +0 -6
  192. package/front_end/third_party/puppeteer/package/src/generated/injected.ts +1 -1
  193. package/front_end/third_party/puppeteer/package/src/injected/util.ts +4 -1
  194. package/front_end/third_party/puppeteer/package/src/node/ScreenRecorder.ts +0 -3
  195. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  196. package/front_end/third_party/puppeteer/package/src/util/disposable.ts +2 -2
  197. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  198. package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +9 -7
  199. package/front_end/ui/components/text_editor/AiCodeGenerationProvider.ts +7 -8
  200. package/front_end/ui/legacy/Dialog.ts +103 -4
  201. package/front_end/ui/legacy/InspectorView.ts +1 -5
  202. package/front_end/ui/legacy/PopoverHelper.ts +5 -5
  203. package/front_end/ui/legacy/TabbedPane.ts +7 -2
  204. package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +40 -24
  205. package/front_end/ui/legacy/components/data_grid/DataGrid.ts +1 -0
  206. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +8 -4
  207. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +14 -7
  208. package/front_end/ui/legacy/components/quick_open/CommandMenu.ts +11 -1
  209. package/front_end/ui/visual_logging/KnownContextValues.ts +3 -0
  210. package/package.json +1 -1
  211. package/front_end/panels/sources/AiWarningInfobarPlugin.ts +0 -78
@@ -3,16 +3,20 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  import '../../ui/kit/kit.js';
6
+ import '../../ui/components/lists/lists.js';
6
7
 
7
8
  import * as Common from '../../core/common/common.js';
8
9
  import * as i18n from '../../core/i18n/i18n.js';
9
10
  import * as SDK from '../../core/sdk/sdk.js';
10
11
  import * as Buttons from '../../ui/components/buttons/buttons.js';
12
+ import type * as Lists from '../../ui/components/lists/lists.js';
11
13
  import * as UI from '../../ui/legacy/legacy.js';
12
- import {html, type LitTemplate, render} from '../../ui/lit/lit.js';
14
+ import {Directives, html, type LitTemplate, nothing, render} from '../../ui/lit/lit.js';
13
15
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
14
16
 
15
17
  import locationsSettingsTabStyles from './locationsSettingsTab.css.js';
18
+ const {createRef, ref} = Directives;
19
+ export {locationsSettingsTabStyles};
16
20
 
17
21
  const UIStrings = {
18
22
  /**
@@ -110,6 +114,22 @@ const UIStrings = {
110
114
  * @description Text of add locations button in the Locations settings tab of the Device toolbar.
111
115
  */
112
116
  addLocation: 'Add location',
117
+ /**
118
+ * @description Title for the dialog when editing an existing location in the Locations settings tab.
119
+ */
120
+ editLocation: 'Edit location',
121
+ /**
122
+ * @description Label for button to save location changes.
123
+ */
124
+ save: 'Save',
125
+ /**
126
+ * @description Label for button to cancel location edits.
127
+ */
128
+ cancel: 'Cancel',
129
+ /**
130
+ * @description Label for button to close the location dialog.
131
+ */
132
+ close: 'Close',
113
133
  } as const;
114
134
  const str_ = i18n.i18n.registerUIStrings('panels/sensors/LocationsSettingsTab.ts', UIStrings);
115
135
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -136,49 +156,239 @@ function renderItemView(location: LocationDescription): LitTemplate {
136
156
  // clang-format on
137
157
  }
138
158
 
139
- interface EditorInputControls {
140
- titleInput: Element;
141
- latInput: Element;
142
- longInput: Element;
143
- timezoneIdInput: Element;
144
- localeInput: Element;
145
- accuracyInput: Element;
159
+ export interface EditorInputControls {
160
+ titleInput: LitTemplate|Element;
161
+ latInput: LitTemplate|Element;
162
+ longInput: LitTemplate|Element;
163
+ timezoneIdInput: LitTemplate|Element;
164
+ localeInput: LitTemplate|Element;
165
+ accuracyInput: LitTemplate|Element;
146
166
  }
147
167
 
148
- function renderEditorView(controls: EditorInputControls): LitTemplate {
168
+ export interface LocationValidationErrors {
169
+ title?: string|null;
170
+ lat?: string|null;
171
+ long?: string|null;
172
+ timezoneId?: string|null;
173
+ locale?: string|null;
174
+ accuracy?: string|null;
175
+ }
176
+ export interface LocationDialogInput {
177
+ location: LocationDescription;
178
+ isNew: boolean;
179
+ errors?: LocationValidationErrors;
180
+ onSave: (location: LocationDescription) => void;
181
+ onCancel: () => void;
182
+ onValidateErrors: (errors: LocationValidationErrors) => void;
183
+ }
184
+
185
+ export function renderEditorView(controls: EditorInputControls, errors?: LocationDialogInput['errors'],
186
+ isDialog = false): LitTemplate {
187
+ if (!isDialog) {
188
+ // clang-format off
189
+ return html`
190
+ <div class="locations-edit-row">
191
+ <div class="locations-list-text locations-list-title">${i18nString(UIStrings.locationName)}</div>
192
+ <div class="locations-list-separator locations-list-separator-invisible"></div>
193
+ <div class="locations-list-text">${i18nString(UIStrings.lat)}</div>
194
+ <div class="locations-list-separator locations-list-separator-invisible"></div>
195
+ <div class="locations-list-text">${i18nString(UIStrings.long)}</div>
196
+ <div class="locations-list-separator locations-list-separator-invisible"></div>
197
+ <div class="locations-list-text">${i18nString(UIStrings.timezoneId)}</div>
198
+ <div class="locations-list-separator locations-list-separator-invisible"></div>
199
+ <div class="locations-list-text">${i18nString(UIStrings.locale)}</div>
200
+ <div class="locations-list-separator locations-list-separator-invisible"></div>
201
+ <div class="locations-list-text">${i18nString(UIStrings.accuracy)}</div>
202
+ </div>
203
+ <div class="locations-edit-row">
204
+ <div class="locations-list-text locations-list-title locations-input-container">${controls.titleInput}</div>
205
+ <div class="locations-list-separator locations-list-separator-invisible"></div>
206
+ <div class="locations-list-text locations-input-container">${controls.latInput}</div>
207
+ <div class="locations-list-separator locations-list-separator-invisible"></div>
208
+ <div class="locations-list-text locations-list-text-longitude locations-input-container">${controls.longInput}</div>
209
+ <div class="locations-list-separator locations-list-separator-invisible"></div>
210
+ <div class="locations-list-text locations-input-container">${controls.timezoneIdInput}</div>
211
+ <div class="locations-list-separator locations-list-separator-invisible"></div>
212
+ <div class="locations-list-text locations-input-container">${controls.localeInput}</div>
213
+ <div class="locations-list-separator locations-list-separator-invisible"></div>
214
+ <div class="locations-list-text locations-input-container">${controls.accuracyInput}</div>
215
+ </div>
216
+ ${errors ? html`
217
+ <div class="locations-edit-row locations-error-row" role="alert">
218
+ <div class="locations-list-text locations-list-title locations-error-cell">${errors.title ?? nothing}</div>
219
+ <div class="locations-list-separator locations-list-separator-invisible"></div>
220
+ <div class="locations-list-text locations-error-cell">${errors.lat ?? nothing}</div>
221
+ <div class="locations-list-separator locations-list-separator-invisible"></div>
222
+ <div class="locations-list-text locations-list-text-longitude locations-error-cell">${errors.long ?? nothing}</div>
223
+ <div class="locations-list-separator locations-list-separator-invisible"></div>
224
+ <div class="locations-list-text locations-error-cell">${errors.timezoneId ?? nothing}</div>
225
+ <div class="locations-list-separator locations-list-separator-invisible"></div>
226
+ <div class="locations-list-text locations-error-cell">${errors.locale ?? nothing}</div>
227
+ <div class="locations-list-separator locations-list-separator-invisible"></div>
228
+ <div class="locations-list-text locations-error-cell">${errors.accuracy ?? nothing}</div>
229
+ </div>
230
+ ` : nothing}`;
231
+ // clang-format on
232
+ }
233
+
149
234
  // clang-format off
150
235
  return html`
151
- <div class="locations-edit-row">
152
- <div class="locations-list-text locations-list-title">${i18nString(UIStrings.locationName)}</div>
153
- <div class="locations-list-separator locations-list-separator-invisible"></div>
154
- <div class="locations-list-text">${i18nString(UIStrings.lat)}</div>
155
- <div class="locations-list-separator locations-list-separator-invisible"></div>
156
- <div class="locations-list-text">${i18nString(UIStrings.long)}</div>
157
- <div class="locations-list-separator locations-list-separator-invisible"></div>
158
- <div class="locations-list-text">${i18nString(UIStrings.timezoneId)}</div>
159
- <div class="locations-list-separator locations-list-separator-invisible"></div>
160
- <div class="locations-list-text">${i18nString(UIStrings.locale)}</div>
161
- <div class="locations-list-separator locations-list-separator-invisible"></div>
162
- <div class="locations-list-text">${i18nString(UIStrings.accuracy)}</div>
236
+ <div class="editor-grid">
237
+ <div class="editor-field editor-field-full-width ${errors?.title ? 'has-error' : ''}">
238
+ <label class="editor-field-label" for="location-title">${i18nString(UIStrings.locationName)}</label>
239
+ ${controls.titleInput}
240
+ ${errors?.title ? html`<div class="editor-field-error" role="alert">${errors.title}</div>` : nothing}
241
+ </div>
242
+ <div class="editor-field ${errors?.lat ? 'has-error' : ''}">
243
+ <label class="editor-field-label" for="location-lat">${i18nString(UIStrings.lat)}</label>
244
+ ${controls.latInput}
245
+ ${errors?.lat ? html`<div class="editor-field-error" role="alert">${errors.lat}</div>` : nothing}
246
+ </div>
247
+ <div class="editor-field ${errors?.long ? 'has-error' : ''}">
248
+ <label class="editor-field-label" for="location-long">${i18nString(UIStrings.long)}</label>
249
+ ${controls.longInput}
250
+ ${errors?.long ? html`<div class="editor-field-error" role="alert">${errors.long}</div>` : nothing}
251
+ </div>
252
+ <div class="editor-field ${errors?.timezoneId ? 'has-error' : ''}">
253
+ <label class="editor-field-label" for="location-timezone">${i18nString(UIStrings.timezoneId)}</label>
254
+ ${controls.timezoneIdInput}
255
+ ${errors?.timezoneId ? html`<div class="editor-field-error" role="alert">${errors.timezoneId}</div>` : nothing}
256
+ </div>
257
+ <div class="editor-field ${errors?.locale ? 'has-error' : ''}">
258
+ <label class="editor-field-label" for="location-locale">${i18nString(UIStrings.locale)}</label>
259
+ ${controls.localeInput}
260
+ ${errors?.locale ? html`<div class="editor-field-error" role="alert">${errors.locale}</div>` : nothing}
261
+ </div>
262
+ <div class="editor-field editor-field-full-width ${errors?.accuracy ? 'has-error' : ''}">
263
+ <label class="editor-field-label" for="location-accuracy">${i18nString(UIStrings.accuracy)}</label>
264
+ ${controls.accuracyInput}
265
+ ${errors?.accuracy ? html`<div class="editor-field-error" role="alert">${errors.accuracy}</div>` : nothing}
266
+ </div>
163
267
  </div>
164
- <div class="locations-edit-row">
165
- <div class="locations-list-text locations-list-title locations-input-container">${controls.titleInput}</div>
166
- <div class="locations-list-separator locations-list-separator-invisible"></div>
167
- <div class="locations-list-text locations-input-container">${controls.latInput}</div>
168
- <div class="locations-list-separator locations-list-separator-invisible"></div>
169
- <div class="locations-list-text locations-list-text-longitude locations-input-container">${controls.longInput}</div>
170
- <div class="locations-list-separator locations-list-separator-invisible"></div>
171
- <div class="locations-list-text locations-input-container">${controls.timezoneIdInput}</div>
172
- <div class="locations-list-separator locations-list-separator-invisible"></div>
173
- <div class="locations-list-text locations-input-container">${controls.localeInput}</div>
174
- <div class="locations-list-separator locations-list-separator-invisible"></div>
175
- <div class="locations-list-text locations-input-container">${controls.accuracyInput}</div>
176
- </div>`;
268
+ `;
269
+ // clang-format on
270
+ }
271
+
272
+ export function renderLocationDialog(input: LocationDialogInput): LitTemplate {
273
+ const titleInputRef = createRef<HTMLInputElement>();
274
+ const latInputRef = createRef<HTMLInputElement>();
275
+ const longInputRef = createRef<HTMLInputElement>();
276
+ const timezoneIdInputRef = createRef<HTMLInputElement>();
277
+ const localeInputRef = createRef<HTMLInputElement>();
278
+ const accuracyInputRef = createRef<HTMLInputElement>();
279
+
280
+ const handleSave = (): void => {
281
+ const title = titleInputRef.value?.value.trim() ?? '';
282
+ const latStr = latInputRef.value?.value.trim() ?? '';
283
+ const longStr = longInputRef.value?.value.trim() ?? '';
284
+ const timezoneId = timezoneIdInputRef.value?.value.trim() ?? '';
285
+ const locale = localeInputRef.value?.value.trim() ?? '';
286
+ const accuracyStr = accuracyInputRef.value?.value.trim() ?? '';
287
+
288
+ const titleError = validateTitle(title);
289
+ const latError = validateLatitude(latStr);
290
+ const longError = validateLongitude(longStr);
291
+ const tzError = validateTimezoneId(timezoneId);
292
+ const localeError = validateLocale(locale);
293
+ const accuracyError = validateAccuracy(accuracyStr);
294
+
295
+ if (titleError || latError || longError || tzError || localeError || accuracyError) {
296
+ input.onValidateErrors({
297
+ title: titleError,
298
+ lat: latError,
299
+ long: longError,
300
+ timezoneId: tzError,
301
+ locale: localeError,
302
+ accuracy: accuracyError,
303
+ });
304
+ return;
305
+ }
306
+
307
+ input.onSave({
308
+ title,
309
+ lat: latStr ? parseFloat(latStr) : 0,
310
+ long: longStr ? parseFloat(longStr) : 0,
311
+ timezoneId,
312
+ locale,
313
+ accuracy: accuracyStr ? parseFloat(accuracyStr) : SDK.EmulationModel.Location.DEFAULT_ACCURACY,
314
+ });
315
+ };
316
+
317
+ const editorControls: EditorInputControls = {
318
+ titleInput:
319
+ html`<input id="location-title" aria-label=${i18nString(UIStrings.locationName)} type="text" placeholder=${
320
+ i18nString(UIStrings.locationName)} .value=${input.location.title} ${ref(titleInputRef)}>`,
321
+ latInput: html`<input id="location-lat" aria-label=${i18nString(UIStrings.lat)} type="text" placeholder=${
322
+ i18nString(UIStrings.latitude)} .value=${String(input.location.lat)} ${ref(latInputRef)}>`,
323
+ longInput: html`<input id="location-long" aria-label=${i18nString(UIStrings.long)} type="text" placeholder=${
324
+ i18nString(UIStrings.longitude)} .value=${String(input.location.long)} ${ref(longInputRef)}>`,
325
+ timezoneIdInput:
326
+ html`<input id="location-timezone" aria-label=${i18nString(UIStrings.timezoneId)} type="text" placeholder=${
327
+ i18nString(UIStrings.timezoneId)} .value=${input.location.timezoneId} ${ref(timezoneIdInputRef)}>`,
328
+ localeInput: html`<input id="location-locale" aria-label=${i18nString(UIStrings.locale)} type="text" placeholder=${
329
+ i18nString(UIStrings.locale)} .value=${input.location.locale} ${ref(localeInputRef)}>`,
330
+ accuracyInput: html`<input id="location-accuracy" aria-label=${
331
+ i18nString(UIStrings.accuracy)} type="text" placeholder=${i18nString(UIStrings.accuracy)} .value=${
332
+ String(input.location.accuracy ?? SDK.EmulationModel.Location.DEFAULT_ACCURACY)} ${ref(accuracyInputRef)}>`,
333
+ };
334
+
335
+ // clang-format off
336
+
337
+ const dialogContent = html`
338
+ <style>${locationsSettingsTabStyles}</style>
339
+ <div class="location-dialog-content">
340
+ <div class="dialog-header">
341
+ <span class="dialog-title">${input.isNew ? i18nString(UIStrings.addLocation) : i18nString(UIStrings.editLocation)}</span>
342
+ <devtools-button
343
+ class="dialog-close-button"
344
+ .iconName=${'cross'}
345
+ .variant=${Buttons.Button.Variant.ICON}
346
+ .title=${i18nString(UIStrings.close)}
347
+ .jslogContext=${'dialog-close'}
348
+ @click=${input.onCancel}
349
+ aria-label=${i18nString(UIStrings.close)}>
350
+ </devtools-button>
351
+ </div>
352
+ ${renderEditorView(editorControls, input.errors, true)}
353
+ <div class="dialog-buttons">
354
+ <devtools-button
355
+ class="save-button"
356
+ .variant=${Buttons.Button.Variant.PRIMARY}
357
+ @click=${handleSave}>
358
+ ${i18nString(UIStrings.save)}
359
+ </devtools-button>
360
+ <devtools-button
361
+ class="cancel-button"
362
+ .variant=${Buttons.Button.Variant.OUTLINED}
363
+ @click=${input.onCancel}>
364
+ ${i18nString(UIStrings.cancel)}
365
+ </devtools-button>
366
+ </div>
367
+ </div>
368
+ `;
369
+
370
+ return html`
371
+ <style>${locationsSettingsTabStyles}</style>
372
+ <devtools-widget
373
+ class="location-dialog-widget"
374
+ ${UI.Widget.widget(UI.Dialog.DialogWidget, {
375
+ open: true,
376
+ jslogContext: 'location-dialog',
377
+ dialogStack: true,
378
+ content: dialogContent,
379
+ })}
380
+ @hidden=${input.onCancel}>
381
+ </devtools-widget>
382
+ `;
177
383
  // clang-format on
178
384
  }
179
385
 
180
386
  interface LocationsViewInput {
387
+ locations: LocationDescription[];
181
388
  onAddLocation: () => void;
389
+ onEditLocation: (index: number) => void;
390
+ onRemoveLocation: (index: number) => void;
391
+ activeDialog?: LocationDialogInput;
182
392
  }
183
393
 
184
394
  export type ViewOutput = undefined;
@@ -190,7 +400,18 @@ export const DEFAULT_VIEW = (input: LocationsViewInput, _output: ViewOutput, tar
190
400
  <div class="settings-card-container-wrapper">
191
401
  <div class="settings-card-container">
192
402
  <devtools-card .heading=${i18nString(UIStrings.locations)}>
193
- <div class="list-container"></div>
403
+ <div>
404
+ ${input.locations.length > 0 ? html`
405
+ <devtools-list
406
+ class="locations-list square-corners"
407
+ .editable=${true}
408
+ .deletable=${true}
409
+ @edit=${(e: Lists.List.ItemEditEvent) => input.onEditLocation(e.detail.index)}
410
+ @delete=${(e: Lists.List.ItemRemoveEvent) => input.onRemoveLocation(e.detail.index)}>
411
+ ${input.locations.map(location => renderItemView(location))}
412
+ </devtools-list>
413
+ ` : nothing}
414
+ </div>
194
415
  <devtools-button
195
416
  class="add-locations-button"
196
417
  .variant=${Buttons.Button.Variant.OUTLINED}
@@ -201,16 +422,22 @@ export const DEFAULT_VIEW = (input: LocationsViewInput, _output: ViewOutput, tar
201
422
  </devtools-button>
202
423
  </devtools-card>
203
424
  </div>
204
- </div>`, target);
425
+ </div>
426
+ ${input.activeDialog ? renderLocationDialog(input.activeDialog) : nothing}
427
+ `, target);
205
428
  // clang-format on
206
429
  };
207
430
 
208
431
  export type View = typeof DEFAULT_VIEW;
209
- export class LocationsSettingsTab extends UI.Widget.VBox implements UI.ListWidget.Delegate<LocationDescription> {
210
- private readonly list: UI.ListWidget.ListWidget<LocationDescription>;
432
+ export class LocationsSettingsTab extends UI.Widget.VBox {
211
433
  private readonly customSetting: Common.Settings.Setting<LocationDescription[]>;
212
- private editor?: UI.ListWidget.Editor<LocationDescription>;
213
434
  #view: View;
435
+ #activeDialogState?: {
436
+ location: LocationDescription,
437
+ isNew: boolean,
438
+ index?: number,
439
+ errors?: LocationValidationErrors,
440
+ };
214
441
 
215
442
  constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
216
443
  super(element, {
@@ -219,9 +446,6 @@ export class LocationsSettingsTab extends UI.Widget.VBox implements UI.ListWidge
219
446
  });
220
447
  this.#view = view;
221
448
 
222
- this.list = new UI.ListWidget.ListWidget(this, undefined, true);
223
- this.list.element.classList.add('locations-list');
224
- this.list.registerRequiredCSS(locationsSettingsTabStyles);
225
449
  this.customSetting =
226
450
  Common.Settings.Settings.instance().moduleSetting<LocationDescription[]>('emulation.locations');
227
451
  const list =
@@ -258,127 +482,93 @@ export class LocationsSettingsTab extends UI.Widget.VBox implements UI.ListWidge
258
482
  }
259
483
 
260
484
  override performUpdate(): void {
261
- const viewInput = {
485
+ let activeDialogInput: LocationDialogInput|undefined;
486
+ if (this.#activeDialogState) {
487
+ const state = this.#activeDialogState;
488
+ activeDialogInput = {
489
+ location: state.location,
490
+ isNew: state.isNew,
491
+ errors: state.errors,
492
+ onSave: (updatedLocation: LocationDescription) => this.saveDialog(updatedLocation),
493
+ onCancel: () => this.closeDialog(),
494
+ onValidateErrors: (errors: LocationValidationErrors) => this.updateDialogErrors(errors),
495
+ };
496
+ }
497
+
498
+ const viewInput: LocationsViewInput = {
499
+ locations: this.customSetting.get(),
262
500
  onAddLocation: () => this.addButtonClicked(),
501
+ onEditLocation: (index: number) => this.editLocationClicked(index),
502
+ onRemoveLocation: (index: number) => this.removeLocationClicked(index),
503
+ activeDialog: activeDialogInput,
263
504
  };
264
505
  this.#view(viewInput, undefined, this.contentElement as HTMLElement);
265
506
 
266
- const listContainer = this.contentElement.querySelector('.list-container');
267
- if (listContainer) {
268
- this.list.show(listContainer);
269
507
  }
270
- }
271
508
  private locationsUpdated(): void {
272
- this.list.clear();
273
-
274
- const conditions = this.customSetting.get();
275
- for (const condition of conditions) {
276
- this.list.appendItem(condition, true);
277
- }
278
-
279
- this.list.appendSeparator();
280
509
  this.requestUpdate();
281
510
  }
282
511
 
283
512
  private addButtonClicked(): void {
284
- this.list.addNewItem(this.customSetting.get().length, {
285
- title: '',
286
- lat: 0,
287
- long: 0,
288
- timezoneId: '',
289
- locale: '',
290
- accuracy: SDK.EmulationModel.Location.DEFAULT_ACCURACY,
291
- });
513
+ this.#activeDialogState = {
514
+ location: {
515
+ title: '',
516
+ lat: 0,
517
+ long: 0,
518
+ timezoneId: '',
519
+ locale: '',
520
+ accuracy: SDK.EmulationModel.Location.DEFAULT_ACCURACY,
521
+ },
522
+ isNew: true,
523
+ };
524
+ this.requestUpdate();
292
525
  }
293
526
 
294
- renderItem(location: LocationDescription, _editable: boolean): Element {
295
- const fragment = document.createDocumentFragment();
296
- // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
297
- render(renderItemView(location), fragment);
298
- return fragment.firstElementChild as Element;
527
+ private editLocationClicked(index: number): void {
528
+ const list = this.customSetting.get();
529
+ const location = list[index];
530
+ if (!location) {
531
+ return;
532
+ }
533
+ this.#activeDialogState = {
534
+ location: {...location},
535
+ isNew: false,
536
+ index,
537
+ };
538
+ this.requestUpdate();
299
539
  }
300
540
 
301
- removeItemRequested(_item: LocationDescription, index: number): void {
541
+ private removeLocationClicked(index: number): void {
302
542
  const list = this.customSetting.get();
303
543
  list.splice(index, 1);
304
544
  this.customSetting.set(list);
305
545
  }
306
546
 
307
- commitEdit(location: LocationDescription, editor: UI.ListWidget.Editor<LocationDescription>, isNew: boolean): void {
308
- location.title = editor.control('title').value.trim();
309
- const lat = editor.control('lat').value.trim();
310
- location.lat = lat ? parseFloat(lat) : 0;
311
- const long = editor.control('long').value.trim();
312
- location.long = long ? parseFloat(long) : 0;
313
- const timezoneId = editor.control('timezone-id').value.trim();
314
- location.timezoneId = timezoneId;
315
- const locale = editor.control('locale').value.trim();
316
- location.locale = locale;
317
- const accuracy = editor.control('accuracy').value.trim();
318
- location.accuracy = accuracy ? parseFloat(accuracy) : SDK.EmulationModel.Location.DEFAULT_ACCURACY;
547
+ private saveDialog(updatedLocation: LocationDescription): void {
548
+ if (!this.#activeDialogState) {
549
+ return;
550
+ }
319
551
 
320
552
  const list = this.customSetting.get();
321
- if (isNew) {
322
- list.push(location);
553
+ if (this.#activeDialogState.isNew) {
554
+ list.push(updatedLocation);
555
+ } else if (this.#activeDialogState.index !== undefined) {
556
+ list[this.#activeDialogState.index] = updatedLocation;
323
557
  }
558
+ this.#activeDialogState = undefined;
324
559
  this.customSetting.set(list);
325
560
  }
326
561
 
327
- beginEdit(location: LocationDescription): UI.ListWidget.Editor<LocationDescription> {
328
- const editor = this.createEditor();
329
- editor.control('title').value = location.title;
330
- editor.control('lat').value = String(location.lat);
331
- editor.control('long').value = String(location.long);
332
- editor.control('timezone-id').value = location.timezoneId;
333
- editor.control('locale').value = location.locale;
334
- editor.control('accuracy').value = String(location.accuracy ?? SDK.EmulationModel.Location.DEFAULT_ACCURACY);
335
- return editor;
562
+ private closeDialog(): void {
563
+ this.#activeDialogState = undefined;
564
+ this.requestUpdate();
336
565
  }
337
566
 
338
- private createEditor(): UI.ListWidget.Editor<LocationDescription> {
339
- if (this.editor) {
340
- return this.editor;
567
+ private updateDialogErrors(errors: LocationValidationErrors): void {
568
+ if (this.#activeDialogState) {
569
+ this.#activeDialogState.errors = errors;
570
+ this.requestUpdate();
341
571
  }
342
-
343
- const editor = new UI.ListWidget.Editor<LocationDescription>();
344
- this.editor = editor;
345
- const content = editor.contentElement();
346
-
347
- const createValidator = (validator: (value: string) => string | null) =>
348
- (_item: LocationDescription, _index: number,
349
- input: UI.ListWidget.EditorControl): UI.ListWidget.ValidatorResult => {
350
- const errorMessage = validator(input.value);
351
- if (errorMessage) {
352
- return {valid: false, errorMessage};
353
- }
354
- return {valid: true};
355
- };
356
-
357
- const titleInput =
358
- editor.createInput('title', 'text', i18nString(UIStrings.locationName), createValidator(validateTitle));
359
- const latInput =
360
- editor.createInput('lat', 'text', i18nString(UIStrings.latitude), createValidator(validateLatitude));
361
- const longInput =
362
- editor.createInput('long', 'text', i18nString(UIStrings.longitude), createValidator(validateLongitude));
363
- const timezoneIdInput = editor.createInput('timezone-id', 'text', i18nString(UIStrings.timezoneId),
364
- createValidator(validateTimezoneId));
365
- const localeInput =
366
- editor.createInput('locale', 'text', i18nString(UIStrings.locale), createValidator(validateLocale));
367
- const accuracyInput =
368
- editor.createInput('accuracy', 'text', i18nString(UIStrings.accuracy), createValidator(validateAccuracy));
369
-
370
- // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
371
- render(renderEditorView({
372
- titleInput,
373
- latInput,
374
- longInput,
375
- timezoneIdInput,
376
- localeInput,
377
- accuracyInput,
378
- }),
379
- content as HTMLElement);
380
-
381
- return editor;
382
572
  }
383
573
  }
384
574
  export interface LocationDescription {
@@ -397,6 +587,7 @@ export function validateTitle(value: string): string|null {
397
587
  if (!trimmedValue.length) {
398
588
  return i18nString(UIStrings.locationNameCannotBeEmpty);
399
589
  }
590
+
400
591
  if (trimmedValue.length > maxLength) {
401
592
  return i18nString(UIStrings.locationNameMustBeLessThanS, {PH1: maxLength});
402
593
  }
@@ -412,6 +603,7 @@ export function validateLatitude(value: string): string|null {
412
603
  if (!trimmedValue) {
413
604
  return null;
414
605
  }
606
+
415
607
  if (Number.isNaN(parsedValue)) {
416
608
  return i18nString(UIStrings.latitudeMustBeANumber);
417
609
  }
@@ -433,6 +625,7 @@ export function validateLongitude(value: string): string|null {
433
625
  if (!trimmedValue) {
434
626
  return null;
435
627
  }
628
+
436
629
  if (Number.isNaN(parsedValue)) {
437
630
  return i18nString(UIStrings.longitudeMustBeANumber);
438
631
  }
@@ -482,6 +675,7 @@ export function validateAccuracy(value: string): string|null {
482
675
  if (!trimmedValue) {
483
676
  return null;
484
677
  }
678
+
485
679
  if (Number.isNaN(parsedValue)) {
486
680
  return i18nString(UIStrings.accuracyMustBeANumber);
487
681
  }