chrome-devtools-frontend 1.0.1652307 → 1.0.1656291

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 (267) hide show
  1. package/.agents/skills/fixing-skipped-tests/SKILL.md +32 -0
  2. package/.agents/skills/migrate-chromium-test/SKILL.md +144 -0
  3. package/.agents/skills/ui-eng-vision-local-lit-renderer/SKILL.md +116 -0
  4. package/.agents/skills/ui-eng-vision-logic-consolidator/SKILL.md +82 -0
  5. package/.agents/skills/ui-eng-vision-orchestrator/SKILL.md +123 -0
  6. package/.agents/skills/ui-eng-vision-test-scaffolder/SKILL.md +125 -0
  7. package/.agents/skills/ui-eng-vision-widget-promoter/SKILL.md +75 -0
  8. package/AUTHORS +1 -0
  9. package/config/owner/COMMON_OWNERS +0 -4
  10. package/docs/get_the_code.md +1 -2
  11. package/docs/playbook.md +1 -1
  12. package/docs/ui_engineering.md +3 -1
  13. package/eslint.config.mjs +0 -5
  14. package/front_end/Tests.js +1 -0
  15. package/front_end/core/common/Settings.ts +47 -30
  16. package/front_end/core/sdk/CPUThrottlingManager.ts +23 -167
  17. package/front_end/core/sdk/CSSMetadata.ts +23 -0
  18. package/front_end/core/sdk/CompilerSourceMappingContentProvider.ts +8 -4
  19. package/front_end/core/sdk/ConsoleModel.ts +13 -11
  20. package/front_end/core/sdk/DOMDebuggerModel.ts +11 -5
  21. package/front_end/core/sdk/DOMModel.ts +4 -0
  22. package/front_end/core/sdk/EmulationModel.ts +4 -0
  23. package/front_end/core/sdk/EventBreakpointsModel.ts +9 -5
  24. package/front_end/core/sdk/IsolateManager.ts +6 -6
  25. package/front_end/core/sdk/ResourceTreeModel.ts +8 -5
  26. package/front_end/core/sdk/TargetManager.ts +17 -0
  27. package/front_end/entrypoints/devtools_app/devtools_app.ts +0 -1
  28. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +459 -74
  29. package/front_end/entrypoints/main/MainImpl.ts +12 -42
  30. package/front_end/foundation/Universe.ts +116 -3
  31. package/front_end/generated/InspectorBackendCommands.ts +11 -6
  32. package/front_end/generated/SupportedCSSProperties.js +124 -34
  33. package/front_end/generated/protocol-mapping.d.ts +8 -0
  34. package/front_end/generated/protocol-proxy-api.d.ts +15 -0
  35. package/front_end/generated/protocol.ts +94 -1
  36. package/front_end/models/ai_assistance/AiConversation.ts +2 -7
  37. package/front_end/models/ai_assistance/README.md +4 -3
  38. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +1 -1
  39. package/front_end/models/ai_assistance/agents/NetworkAgent.snapshot.txt +1 -1
  40. package/front_end/models/ai_assistance/agents/PerformanceAgent.snapshot.txt +2 -2
  41. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +13 -320
  42. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +0 -24
  43. package/front_end/models/ai_assistance/agents/StylingAgent.ts +2 -358
  44. package/front_end/models/ai_assistance/ai_assistance.ts +6 -8
  45. package/front_end/models/ai_assistance/contexts/PerformanceTraceContext.ts +295 -0
  46. package/front_end/models/ai_assistance/contexts/RequestContext.snapshot.txt +0 -1
  47. package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +0 -2
  48. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +10 -10
  49. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +12 -11
  50. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +6 -6
  51. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +1 -14
  52. package/front_end/models/ai_assistance/skills/accessibility.md +4 -2
  53. package/front_end/models/ai_assistance/tools/GetElementAccessibilityDetails.ts +142 -0
  54. package/front_end/models/ai_assistance/tools/GetStyles.ts +1 -1
  55. package/front_end/models/ai_assistance/tools/README.md +21 -1
  56. package/front_end/models/ai_assistance/tools/{ResolveLighthousePath.ts → ResolveDevtoolsNodePath.ts} +18 -17
  57. package/front_end/models/ai_assistance/tools/Tool.ts +6 -1
  58. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +4 -2
  59. package/front_end/models/autofill_manager/AutofillManager.ts +6 -15
  60. package/front_end/models/bindings/NetworkProject.ts +4 -8
  61. package/front_end/models/breakpoints/BreakpointManager.ts +9 -9
  62. package/front_end/models/emulation/DeviceModeModel.ts +24 -0
  63. package/front_end/models/emulation/EmulatedDevices.ts +108 -0
  64. package/front_end/models/heap_snapshot/HeapSnapshotModel.ts +36 -0
  65. package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +8 -0
  66. package/front_end/models/issues_manager/IssuesManager.ts +10 -6
  67. package/front_end/models/javascript_metadata/JavaScriptMetadata.ts +4 -5
  68. package/front_end/models/javascript_metadata/NativeFunctions.js +1 -1
  69. package/front_end/models/live-metrics/LiveMetrics.ts +6 -6
  70. package/front_end/models/live-metrics/web-vitals-injected/spec/spec.ts +5 -5
  71. package/front_end/models/live-metrics/web-vitals-injected/web-vitals-injected.ts +3 -3
  72. package/front_end/models/logs/LogManager.ts +25 -10
  73. package/front_end/models/logs/NetworkLog.ts +24 -19
  74. package/front_end/models/persistence/AutomaticFileSystemManager.ts +14 -13
  75. package/front_end/models/persistence/AutomaticFileSystemWorkspaceBinding.ts +26 -14
  76. package/front_end/models/persistence/IsolatedFileSystem.ts +9 -11
  77. package/front_end/models/persistence/IsolatedFileSystemManager.ts +22 -11
  78. package/front_end/models/persistence/NetworkPersistenceManager.ts +73 -44
  79. package/front_end/models/persistence/PersistenceImpl.ts +4 -5
  80. package/front_end/models/project_settings/ProjectSettingsModel.ts +12 -11
  81. package/front_end/models/trace/EntityMapper.ts +1 -1
  82. package/front_end/models/trace/handlers/ScreenshotsHandler.ts +14 -59
  83. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +12 -38
  84. package/front_end/panels/ai_assistance/ExportConversation.ts +4 -1
  85. package/front_end/panels/ai_assistance/components/ChatInput.ts +2 -2
  86. package/front_end/panels/application/DeviceBoundSessionsView.ts +15 -3
  87. package/front_end/panels/application/FrameDetailsView.ts +2 -1
  88. package/front_end/panels/application/IndexedDBViews.ts +38 -36
  89. package/front_end/panels/application/ServiceWorkersView.ts +23 -16
  90. package/front_end/panels/application/components/AdsView.ts +4 -0
  91. package/front_end/panels/autofill/AutofillView.ts +1 -1
  92. package/front_end/panels/autofill/autofill-meta.ts +3 -2
  93. package/front_end/panels/common/CPUThrottlingOption.ts +142 -0
  94. package/front_end/panels/common/ThrottlingUtils.ts +9 -4
  95. package/front_end/panels/common/common.ts +1 -1
  96. package/front_end/panels/console/ConsoleViewMessage.ts +45 -0
  97. package/front_end/panels/elements/AccessibilityTreeUtils.ts +26 -15
  98. package/front_end/panels/elements/AccessibilityTreeView.ts +12 -9
  99. package/front_end/panels/elements/ElementsPanel.ts +0 -74
  100. package/front_end/panels/elements/ElementsTreeOutline.ts +5 -4
  101. package/front_end/panels/emulation/DeviceModeToolbar.ts +46 -45
  102. package/front_end/panels/layer_viewer/LayerTreeOutline.ts +118 -68
  103. package/front_end/panels/layer_viewer/Layers3DView.ts +1 -1
  104. package/front_end/panels/layer_viewer/layerTreeOutline.css +20 -13
  105. package/front_end/panels/layers/LayersPanel.ts +1 -1
  106. package/front_end/panels/mobile_throttling/CalibrationController.ts +6 -5
  107. package/front_end/panels/mobile_throttling/MobileThrottlingSelector.ts +1 -1
  108. package/front_end/panels/mobile_throttling/ThrottlingManager.ts +40 -13
  109. package/front_end/panels/mobile_throttling/ThrottlingPresets.ts +13 -17
  110. package/front_end/panels/mobile_throttling/ThrottlingSettingsTab.ts +79 -30
  111. package/front_end/panels/mobile_throttling/throttlingSettingsTab.css +5 -0
  112. package/front_end/panels/network/LinkPreloadGenerator.ts +101 -0
  113. package/front_end/panels/network/NetworkLogView.ts +28 -9
  114. package/front_end/panels/network/NetworkPanel.ts +1 -63
  115. package/front_end/panels/network/RequestInitiatorView.ts +29 -4
  116. package/front_end/panels/network/components/HeaderSectionRow.css +9 -7
  117. package/front_end/panels/network/components/HeaderSectionRow.ts +20 -1
  118. package/front_end/panels/network/components/RequestHeaderSection.css +5 -3
  119. package/front_end/panels/network/components/RequestHeaderSection.ts +1 -2
  120. package/front_end/panels/network/components/RequestHeadersView.css +1 -1
  121. package/front_end/panels/network/network.ts +2 -0
  122. package/front_end/panels/profiler/HeapSnapshotView.ts +121 -33
  123. package/front_end/panels/profiler/ModuleUIStrings.ts +4 -0
  124. package/front_end/panels/security/SecurityPanel.ts +36 -23
  125. package/front_end/panels/settings/SettingsScreen.ts +18 -103
  126. package/front_end/panels/settings/settings-meta.ts +0 -24
  127. package/front_end/panels/timeline/TimelineController.ts +3 -2
  128. package/front_end/panels/timeline/TimelineHistoryManager.ts +38 -37
  129. package/front_end/panels/timeline/TimelinePanel.ts +52 -11
  130. package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +20 -18
  131. package/front_end/panels/timeline/components/LiveMetricsView.ts +38 -38
  132. package/front_end/panels/timeline/components/MetricCard.ts +18 -18
  133. package/front_end/panels/timeline/components/liveMetricsView.css +6 -6
  134. package/front_end/panels/timeline/components/metricCard.css +4 -4
  135. package/front_end/panels/timeline/timelineFlamechartPopover.css +2 -0
  136. package/front_end/panels/utils/utils.ts +25 -24
  137. package/front_end/panels/whats_new/ReleaseNoteText.ts +3 -3
  138. package/front_end/panels/whats_new/resources/WNDT.md +1 -1
  139. package/front_end/third_party/chromium/README.chromium +1 -1
  140. package/front_end/third_party/puppeteer/README.chromium +2 -2
  141. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +34 -55
  142. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +96 -83
  143. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Browser.d.ts +10 -1
  144. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Browser.d.ts.map +1 -1
  145. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Browser.js.map +1 -1
  146. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/BrowserContext.d.ts.map +1 -1
  147. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/BrowserContext.js +1 -1
  148. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/BrowserContext.js.map +1 -1
  149. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/CDPSession.d.ts.map +1 -1
  150. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/CDPSession.js.map +1 -1
  151. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/HTTPResponse.d.ts +3 -2
  152. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/HTTPResponse.d.ts.map +1 -1
  153. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/HTTPResponse.js.map +1 -1
  154. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Realm.d.ts.map +1 -1
  155. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Realm.js.map +1 -1
  156. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Target.d.ts +1 -1
  157. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Target.js +1 -1
  158. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/locators/locators.d.ts.map +1 -1
  159. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/locators/locators.js.map +1 -1
  160. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/HTTPResponse.d.ts.map +1 -1
  161. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/HTTPResponse.js +2 -1
  162. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/HTTPResponse.js.map +1 -1
  163. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/core/Realm.d.ts.map +1 -1
  164. package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/core/Realm.js.map +1 -1
  165. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Browser.d.ts +2 -2
  166. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Browser.d.ts.map +1 -1
  167. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Browser.js +5 -2
  168. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Browser.js.map +1 -1
  169. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Frame.js +1 -1
  170. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Frame.js.map +1 -1
  171. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/FrameManager.d.ts.map +1 -1
  172. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/FrameManager.js +23 -13
  173. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/FrameManager.js.map +1 -1
  174. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/HTTPResponse.d.ts.map +1 -1
  175. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/HTTPResponse.js +4 -1
  176. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/HTTPResponse.js.map +1 -1
  177. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Input.js +1 -1
  178. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/LifecycleWatcher.d.ts.map +1 -1
  179. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/LifecycleWatcher.js.map +1 -1
  180. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Target.d.ts.map +1 -1
  181. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Target.js.map +1 -1
  182. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/WebMCP.d.ts +3 -41
  183. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/WebMCP.d.ts.map +1 -1
  184. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/WebMCP.js.map +1 -1
  185. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/ConnectOptions.d.ts.map +1 -1
  186. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.d.ts.map +1 -1
  187. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js +4 -2
  188. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js.map +1 -1
  189. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/LaunchOptions.d.ts +5 -0
  190. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/LaunchOptions.d.ts.map +1 -1
  191. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.d.ts +1 -1
  192. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js +1 -1
  193. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/httpUtils.d.ts +1 -1
  194. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/httpUtils.d.ts.map +1 -1
  195. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/httpUtils.js +5 -5
  196. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/httpUtils.js.map +1 -1
  197. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.d.ts +1 -1
  198. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.js +1 -1
  199. package/front_end/third_party/puppeteer/package/lib/types.d.ts +34 -55
  200. package/front_end/third_party/puppeteer/package/package.json +4 -4
  201. package/front_end/third_party/puppeteer/package/src/api/Browser.ts +13 -1
  202. package/front_end/third_party/puppeteer/package/src/api/BrowserContext.ts +2 -3
  203. package/front_end/third_party/puppeteer/package/src/api/CDPSession.ts +3 -1
  204. package/front_end/third_party/puppeteer/package/src/api/HTTPResponse.ts +3 -2
  205. package/front_end/third_party/puppeteer/package/src/api/Realm.ts +1 -2
  206. package/front_end/third_party/puppeteer/package/src/api/Target.ts +1 -1
  207. package/front_end/third_party/puppeteer/package/src/api/locators/locators.ts +1 -2
  208. package/front_end/third_party/puppeteer/package/src/bidi/HTTPResponse.ts +3 -1
  209. package/front_end/third_party/puppeteer/package/src/bidi/core/Realm.ts +1 -3
  210. package/front_end/third_party/puppeteer/package/src/cdp/Browser.ts +13 -3
  211. package/front_end/third_party/puppeteer/package/src/cdp/Frame.ts +1 -1
  212. package/front_end/third_party/puppeteer/package/src/cdp/FrameManager.ts +24 -13
  213. package/front_end/third_party/puppeteer/package/src/cdp/HTTPResponse.ts +4 -1
  214. package/front_end/third_party/puppeteer/package/src/cdp/Input.ts +1 -1
  215. package/front_end/third_party/puppeteer/package/src/cdp/LifecycleWatcher.ts +1 -4
  216. package/front_end/third_party/puppeteer/package/src/cdp/Target.ts +2 -4
  217. package/front_end/third_party/puppeteer/package/src/cdp/WebMCP.ts +7 -76
  218. package/front_end/third_party/puppeteer/package/src/common/ConnectOptions.ts +1 -4
  219. package/front_end/third_party/puppeteer/package/src/node/BrowserLauncher.ts +4 -1
  220. package/front_end/third_party/puppeteer/package/src/node/LaunchOptions.ts +5 -0
  221. package/front_end/third_party/puppeteer/package/src/revisions.ts +1 -1
  222. package/front_end/third_party/puppeteer/package/src/util/httpUtils.ts +5 -5
  223. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  224. package/front_end/third_party/puppeteer/third_party/parsel/README.chromium +1 -1
  225. package/front_end/ui/components/dialogs/Dialog.ts +3 -0
  226. package/front_end/ui/components/markdown_view/MarkdownLinksMap.ts +0 -7
  227. package/front_end/ui/helpers/OpenInNewTab.ts +15 -7
  228. package/front_end/ui/legacy/InspectorDrawerView.ts +7 -5
  229. package/front_end/ui/legacy/ListWidget.ts +12 -0
  230. package/front_end/ui/legacy/SplitWidget.ts +12 -11
  231. package/front_end/ui/legacy/TabbedPane.ts +1 -108
  232. package/front_end/ui/legacy/components/data_grid/DataGrid.ts +9 -0
  233. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +192 -139
  234. package/front_end/ui/legacy/components/data_grid/DataGridExporter.ts +89 -0
  235. package/front_end/ui/legacy/components/data_grid/dataGrid.css +10 -0
  236. package/front_end/ui/legacy/components/data_grid/data_grid.ts +3 -1
  237. package/front_end/ui/legacy/components/object_ui/CustomPreviewComponent.ts +8 -1
  238. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +110 -17
  239. package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +89 -40
  240. package/front_end/ui/legacy/treeoutline.css +1 -1
  241. package/front_end/ui/visual_logging/KnownContextValues.ts +3 -0
  242. package/package.json +4 -3
  243. package/front_end/Images/navigationControls.png +0 -0
  244. package/front_end/Images/navigationControls_2x.png +0 -0
  245. package/front_end/Images/popoverArrows.png +0 -0
  246. package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +0 -746
  247. package/front_end/entrypoints/greendev_floaty/floaty.css +0 -301
  248. package/front_end/entrypoints/greendev_floaty/floaty.html +0 -38
  249. package/front_end/entrypoints/greendev_floaty/greendev_floaty.ts +0 -415
  250. package/front_end/models/ai_assistance/agents/GreenDevAgent.ts +0 -834
  251. package/front_end/models/ai_assistance/agents/GreenDevAgentAntigravityCliSocketClient.ts +0 -53
  252. package/front_end/models/ai_assistance/agents/GreenDevAgentGeminiCliSocketClient.ts +0 -117
  253. package/front_end/models/annotations/AnnotationRepository.ts +0 -238
  254. package/front_end/models/annotations/AnnotationType.ts +0 -10
  255. package/front_end/models/annotations/README.md +0 -7
  256. package/front_end/models/annotations/annotations.ts +0 -6
  257. package/front_end/models/greendev/Prototypes.ts +0 -68
  258. package/front_end/models/greendev/README.md +0 -5
  259. package/front_end/models/greendev/greendev.ts +0 -5
  260. package/front_end/panels/common/Annotation.ts +0 -184
  261. package/front_end/panels/common/AnnotationManager.ts +0 -208
  262. package/front_end/panels/common/annotation.css +0 -40
  263. package/front_end/panels/greendev/GreenDevPanel.css +0 -282
  264. package/front_end/panels/greendev/GreenDevPanel.ts +0 -364
  265. package/front_end/panels/greendev/GreenDevShared.ts +0 -13
  266. package/front_end/panels/greendev/greendev-meta.ts +0 -52
  267. package/front_end/panels/greendev/greendev.ts +0 -9
@@ -12,6 +12,8 @@ import dataGridStyles from './dataGrid.css.js';
12
12
  import {
13
13
  Align,
14
14
  type ColumnDescriptor,
15
+ DataGridImpl,
16
+ DataGridNode,
15
17
  DataType,
16
18
  Events as DataGridEvents,
17
19
  Order,
@@ -50,10 +52,15 @@ const DUMMY_COLUMN_ID = 'dummy'; // SortableDataGrid.create requires at least o
50
52
  * @attribute striped If true, the data grid will have striped rows.
51
53
  * @attribute displayName
52
54
  */
55
+ type DataGridElementNode = SortableNode|DynamicHeightNode;
56
+
57
+ const elementToNode = new WeakMap<Element, DataGridElementNode>();
58
+
53
59
  export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate {
54
60
  static readonly observedAttributes = ['striped', 'name', 'inline', 'resize'];
55
61
 
56
- #dataGrid = SortableDataGrid.create([DUMMY_COLUMN_ID], [], '') as SortableDataGrid<DataGridElementNode>;
62
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
+ #dataGrid!: DataGridImpl<any>;
57
64
  #resizeObserver = new ResizeObserver(() => {
58
65
  if (!this.inline) {
59
66
  this.#dataGrid.onResize();
@@ -69,6 +76,18 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
69
76
  super();
70
77
  // TODO(dsv): Move this to the data_grid.css once all the data grid usage is migrated to this web component.
71
78
  this.style.display = 'flex';
79
+
80
+ const autoRowHeight = this.getAttribute('row-height') === 'auto';
81
+ if (autoRowHeight) {
82
+ this.#dataGrid = new DataGridImpl<DynamicHeightNode>({
83
+ displayName: this.getAttribute('name') ?? '',
84
+ columns: [],
85
+ });
86
+ this.#dataGrid.element.classList.add('auto-row-height');
87
+ } else {
88
+ this.#dataGrid = SortableDataGrid.create([DUMMY_COLUMN_ID], [], '') as SortableDataGrid<SortableNode>;
89
+ }
90
+
72
91
  this.#dataGrid.element.style.flex = 'auto';
73
92
 
74
93
  this.#shadowRoot = UI.UIUtils.createShadowRootWithCoreStyles(this, {delegatesFocus: true, cssFile: dataGridStyles});
@@ -77,8 +96,8 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
77
96
  this.#dataGrid.addEventListener(
78
97
  DataGridEvents.SELECTED_NODE,
79
98
  e => (e.data as DataGridElementNode).configElement.dispatchEvent(new CustomEvent('select')));
80
- this.#dataGrid.addEventListener(
81
- DataGridEvents.DESELECTED_NODE, () => this.dispatchEvent(new CustomEvent('deselect')));
99
+ this.#dataGrid.addEventListener(DataGridEvents.DESELECTED_NODE,
100
+ () => this.dispatchEvent(new CustomEvent('deselect')));
82
101
  this.#dataGrid.addEventListener(
83
102
  DataGridEvents.OPENED_NODE,
84
103
  e => (e.data as DataGridElementNode).configElement.dispatchEvent(new CustomEvent('open')));
@@ -169,8 +188,12 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
169
188
  }
170
189
 
171
190
  set filters(filters: TextUtils.TextUtils.ParsedFilter[]) {
172
- this.#dataGrid.setFilters(filters);
173
- this.#dataGrid.element.setAttribute('aria-rowcount', String(this.#dataGrid.getNumberOfRows()));
191
+ if (this.#dataGrid instanceof SortableDataGrid) {
192
+ this.#dataGrid.setFilters(filters);
193
+ this.#dataGrid.element.setAttribute('aria-rowcount', String(this.#dataGrid.getNumberOfRows()));
194
+ } else {
195
+ this.#dataGrid.element.setAttribute('aria-rowcount', String(this.#dataGrid.rootNode().children.length));
196
+ }
174
197
  }
175
198
 
176
199
  get columns(): ColumnDescriptor[] {
@@ -239,9 +262,7 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
239
262
  }
240
263
  }
241
264
  const visibleColumns = new Set(this.#columns.map(({id}) => id).filter(id => !this.#hiddenColumns.has(id)));
242
- if (visibleColumns.size) {
243
- this.#dataGrid.setColumnsVisibility(visibleColumns);
244
- }
265
+ this.#dataGrid.setColumnsVisibility(visibleColumns);
245
266
  this.#dataGrid.setEditCallback(hasEditableColumn ? this.#editCallback.bind(this) : undefined, INTERNAL_TOKEN);
246
267
  this.#dataGrid.deleteCallback = hasEditableColumn ? this.#deleteCallback.bind(this) : undefined;
247
268
  }
@@ -291,7 +312,7 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
291
312
 
292
313
  #findNextExistingNode(element: Element): DataGridElementNode|null {
293
314
  for (let e = element.nextElementSibling; e; e = e.nextElementSibling) {
294
- const nextNode = DataGridElementNode.get(e);
315
+ const nextNode = getNode(e);
295
316
  if (nextNode) {
296
317
  return nextNode;
297
318
  }
@@ -302,11 +323,13 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
302
323
  override addNodes(nodes: NodeList): void {
303
324
  for (const element of this.#getDataRows(nodes)) {
304
325
  const parentRow = element.parentElement?.closest('td')?.closest('tr');
305
- const parentDataGridNode = parentRow ? DataGridElementNode.get(parentRow) : undefined;
326
+ const parentDataGridNode = parentRow ? getNode(parentRow) : undefined;
306
327
  const parentNode = parentDataGridNode || this.#dataGrid.rootNode();
307
328
  const nextNode = this.#findNextExistingNode(element);
308
329
  const index = nextNode ? parentNode.children.indexOf(nextNode) : parentNode.children.length;
309
- const node = new DataGridElementNode(element, this);
330
+
331
+ const node = this.#dataGrid instanceof SortableDataGrid ? new SortableNode(element, this) :
332
+ new DynamicHeightNode(element, this);
310
333
  this.#updateHasChildren(node, element);
311
334
  if ((parentRow || node.hasChildren()) && !this.#dataGrid.disclosureColumnId) {
312
335
  this.#dataGrid.disclosureColumnId = this.#columns[0].id;
@@ -333,9 +356,9 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
333
356
 
334
357
  override removeNodes(nodes: NodeList): void {
335
358
  for (const element of this.#getDataRows(nodes)) {
336
- const node = DataGridElementNode.get(element);
359
+ const node = getNode(element);
337
360
  if (node) {
338
- DataGridElementNode.remove(node);
361
+ removeNode(node);
339
362
  }
340
363
  }
341
364
  }
@@ -345,7 +368,7 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
345
368
  node = node.parentNode;
346
369
  }
347
370
  const dataRow = node instanceof HTMLElement ? node.closest('tr') : null;
348
- const dataGridNode = dataRow ? DataGridElementNode.get(dataRow) : null;
371
+ const dataGridNode = dataRow ? getNode(dataRow) : null;
349
372
  if (dataGridNode && dataRow) {
350
373
  if (attributeName === 'selected') {
351
374
  if (hasBooleanAttribute(dataRow, 'selected')) {
@@ -372,7 +395,7 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
372
395
 
373
396
  #updateCreationNode(): void {
374
397
  if (this.#usedCreationNode) {
375
- DataGridElementNode.remove(this.#usedCreationNode);
398
+ removeNode(this.#usedCreationNode);
376
399
  this.#usedCreationNode = null;
377
400
  this.#dataGrid.creationNode = undefined;
378
401
  }
@@ -380,9 +403,11 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
380
403
  if (!placeholder) {
381
404
  this.#dataGrid.creationNode?.remove();
382
405
  this.#dataGrid.creationNode = undefined;
383
- } else if (!DataGridElementNode.get(placeholder)) {
406
+ } else if (!getNode(placeholder)) {
384
407
  this.#dataGrid.creationNode?.remove();
385
- const node = new DataGridElementNode(placeholder, this);
408
+
409
+ const node = this.#dataGrid instanceof SortableDataGrid ? new SortableNode(placeholder, this) :
410
+ new DynamicHeightNode(placeholder, this);
386
411
  this.#dataGrid.creationNode = node;
387
412
  this.#dataGrid.rootNode().appendChild(node);
388
413
  }
@@ -404,9 +429,8 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
404
429
  }
405
430
  }
406
431
 
407
- #editCallback(
408
- node: DataGridElementNode, columnId: string, valueBeforeEditing: string, newText: string,
409
- moveDirection?: string): void {
432
+ #editCallback(node: DataGridElementNode, columnId: string, valueBeforeEditing: string, newText: string,
433
+ moveDirection?: string): void {
410
434
  if (node.isCreationNode) {
411
435
  this.#usedCreationNode = node;
412
436
  let hasNextEditableColumn = false;
@@ -432,9 +456,8 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
432
456
  override addEventListener<K extends keyof HTMLElementEventMap>(
433
457
  type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => void,
434
458
  options?: boolean|AddEventListenerOptions|undefined): void;
435
- override addEventListener(
436
- type: string, listener: EventListenerOrEventListenerObject,
437
- options?: boolean|AddEventListenerOptions|undefined): void;
459
+ override addEventListener(type: string, listener: EventListenerOrEventListenerObject,
460
+ options?: boolean|AddEventListenerOptions|undefined): void;
438
461
  override addEventListener(...args: Parameters<HTMLElement['addEventListener']>): void {
439
462
  super.addEventListener(...args);
440
463
  if (args[0] === 'refresh') {
@@ -447,145 +470,163 @@ export class DataGridElement extends UI.UIUtils.HTMLElementWithLightDOMTemplate
447
470
  }
448
471
  }
449
472
 
450
- class DataGridElementNode extends SortableDataGridNode<DataGridElementNode> {
451
- static #elementToNode = new WeakMap<Element, DataGridElementNode>();
452
- #configElement: Element;
453
- #dataGridElement: DataGridElement;
454
- #addedClasses = new Set<string>();
455
- constructor(configElement: Element, dataGridElement: DataGridElement) {
456
- super();
457
- this.#configElement = configElement;
458
- DataGridElementNode.#elementToNode.set(configElement, this);
459
- this.#dataGridElement = dataGridElement;
460
- this.#updateData();
461
- this.isCreationNode = hasBooleanAttribute(this.#configElement, 'placeholder');
462
- }
463
-
464
- static get(configElement: Element|undefined): DataGridElementNode|undefined {
465
- return configElement && DataGridElementNode.#elementToNode.get(configElement);
466
- }
473
+ type Constructor<T = object> = new (...args: any[]) => T;
467
474
 
468
- get configElement(): Element {
469
- return this.#configElement;
470
- }
475
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-function-return-type
476
+ function nodeMixin<TBase extends Constructor<DataGridNode<any>>>(base: TBase) {
477
+ return class extends base {
478
+ #configElement!: Element;
479
+ #dataGridElement!: DataGridElement;
480
+ #addedClasses = new Set<string>();
471
481
 
472
- #updateData(): void {
473
- const cells = [...this.#configElement.children].filter(c => c.tagName === 'TD');
474
- for (let i = 0; i < this.#dataGridElement.columns.length; ++i) {
475
- const cell = cells[i] as HTMLElement;
476
- if (!cell) {
477
- continue;
478
- }
479
- const column = this.#dataGridElement.columns[i];
480
- if (column.dataType === DataType.BOOLEAN) {
481
- this.data[column.id] = hasBooleanAttribute(cell, 'data-value') || cell.textContent === 'true';
482
- } else {
483
- this.data[column.id] = cell.dataset.value ?? cell.textContent ?? '';
484
- }
482
+ initElementNode(configElement: Element, dataGridElement: DataGridElement): void {
483
+ this.#configElement = configElement;
484
+ this.#dataGridElement = dataGridElement;
485
+ this.#updateData();
486
+ this.isCreationNode = hasBooleanAttribute(this.#configElement, 'placeholder');
485
487
  }
486
- }
487
488
 
488
- override createElement(): HTMLElement {
489
- const element = super.createElement();
490
- element.addEventListener('click', this.#onRowMouseEvent.bind(this));
491
- element.addEventListener('mouseenter', this.#onRowMouseEvent.bind(this));
492
- element.addEventListener('mouseleave', this.#onRowMouseEvent.bind(this));
493
- if (this.#configElement.hasAttribute('style')) {
494
- element.setAttribute('style', this.#configElement.getAttribute('style') || '');
489
+ get configElement(): Element {
490
+ return this.#configElement;
495
491
  }
496
- for (const classToAdd of this.#configElement.classList) {
497
- element.classList.add(classToAdd);
498
- }
499
- return element;
500
- }
501
492
 
502
- override refresh(): void {
503
- this.#updateData();
504
- super.refresh();
505
- const existingElement = this.existingElement();
506
- if (!existingElement) {
507
- return;
508
- }
509
- if (this.#configElement.hasAttribute('style')) {
510
- existingElement.setAttribute('style', this.#configElement.getAttribute('style') || '');
493
+ get dataGridElement(): DataGridElement {
494
+ return this.#dataGridElement;
511
495
  }
512
- for (const addedClass of this.#addedClasses) {
513
- existingElement.classList.remove(addedClass);
496
+
497
+ #updateData(): void {
498
+ const cells = [...this.#configElement.children].filter(c => c.tagName === 'TD');
499
+ for (let i = 0; i < this.#dataGridElement.columns.length; ++i) {
500
+ const cell = cells[i] as HTMLElement;
501
+ if (!cell) {
502
+ continue;
503
+ }
504
+ const column = this.#dataGridElement.columns[i];
505
+ if (column.dataType === DataType.BOOLEAN) {
506
+ this.data[column.id] = hasBooleanAttribute(cell, 'data-value') || cell.textContent === 'true';
507
+ } else {
508
+ this.data[column.id] = cell.dataset.value ?? cell.textContent ?? '';
509
+ }
510
+ }
514
511
  }
515
- for (const classToAdd of this.#configElement.classList) {
516
- existingElement.classList.add(classToAdd);
512
+
513
+ override createElement(): HTMLElement {
514
+ const element = super.createElement();
515
+ element.addEventListener('click', this.#onRowMouseEvent.bind(this));
516
+ element.addEventListener('mouseenter', this.#onRowMouseEvent.bind(this));
517
+ element.addEventListener('mouseleave', this.#onRowMouseEvent.bind(this));
518
+ if (this.#configElement.hasAttribute('style')) {
519
+ element.setAttribute('style', this.#configElement.getAttribute('style') || '');
520
+ }
521
+ for (const classToAdd of this.#configElement.classList) {
522
+ element.classList.add(classToAdd);
523
+ }
524
+ return element;
517
525
  }
518
- }
519
526
 
520
- #onRowMouseEvent(event: MouseEvent): void {
521
- const targetInConfigRow = UI.UIUtils.HTMLElementWithLightDOMTemplate.findCorrespondingElement(
522
- event.target as HTMLElement, event.currentTarget as HTMLElement, this.#configElement);
523
- if (!targetInConfigRow) {
524
- throw new Error('Cell click event target not found in the data grid');
527
+ override refresh(): void {
528
+ this.#updateData();
529
+ super.refresh();
530
+ const existingElement = this.existingElement();
531
+ if (!existingElement) {
532
+ return;
533
+ }
534
+ if (this.#configElement.hasAttribute('style')) {
535
+ existingElement.setAttribute('style', this.#configElement.getAttribute('style') || '');
536
+ }
537
+ for (const addedClass of this.#addedClasses) {
538
+ existingElement.classList.remove(addedClass);
539
+ }
540
+ for (const classToAdd of this.#configElement.classList) {
541
+ existingElement.classList.add(classToAdd);
542
+ }
525
543
  }
526
544
 
527
- if (targetInConfigRow instanceof HTMLElement) {
528
- targetInConfigRow?.dispatchEvent(new MouseEvent(event.type, {bubbles: true, composed: true}));
545
+ #onRowMouseEvent(event: MouseEvent): void {
546
+ const targetInConfigRow = UI.UIUtils.HTMLElementWithLightDOMTemplate.findCorrespondingElement(
547
+ event.target as HTMLElement, event.currentTarget as HTMLElement, this.#configElement);
548
+ if (!targetInConfigRow) {
549
+ throw new Error('Cell click event target not found in the data grid');
550
+ }
551
+
552
+ if (targetInConfigRow instanceof HTMLElement) {
553
+ targetInConfigRow?.dispatchEvent(new MouseEvent(event.type, {bubbles: true, composed: true}));
554
+ }
529
555
  }
530
- }
531
556
 
532
- override createCells(element: Element): void {
533
- const configCells = [...this.#configElement.querySelectorAll('td')];
534
- const hasCollspan = configCells.some(cell => cell.hasAttribute('colspan'));
535
- if (!hasCollspan) {
536
- super.createCells(element);
537
- } else {
538
- for (const cell of configCells) {
539
- element.appendChild(cell.cloneNode(true));
557
+ override createCells(element: Element): void {
558
+ const configCells = [...this.#configElement.querySelectorAll('td')];
559
+ const hasCollspan = configCells.some(cell => cell.hasAttribute('colspan'));
560
+ if (!hasCollspan) {
561
+ super.createCells(element);
562
+ } else {
563
+ for (const cell of configCells) {
564
+ element.appendChild(cell.cloneNode(true));
565
+ }
540
566
  }
541
567
  }
542
- }
543
568
 
544
- override createCell(columnId: string): HTMLElement {
545
- const index = this.#dataGridElement.columns.findIndex(({id}) => id === columnId);
546
- if (this.#dataGridElement.columns[index].dataType === DataType.BOOLEAN) {
547
- const cell = super.createCell(columnId);
569
+ override createCell(columnId: string): HTMLElement {
570
+ const index = this.#dataGridElement.columns.findIndex(({id}) => id === columnId);
571
+ if (this.#dataGridElement.columns[index].dataType === DataType.BOOLEAN) {
572
+ const cell = super.createCell(columnId);
573
+ cell.setAttribute('part', `${columnId}-column`);
574
+ return cell;
575
+ }
576
+ const cell = this.createTD(columnId);
548
577
  cell.setAttribute('part', `${columnId}-column`);
578
+ const configCells = [...this.#configElement.children].filter(c => c.tagName === 'TD') as HTMLTableCellElement[];
579
+ const configCell = configCells[index];
580
+ if (this.isCreationNode && !configCell) {
581
+ return cell;
582
+ }
583
+ if (!configCell) {
584
+ throw new Error(`Column ${columnId} not found in the data grid`);
585
+ }
586
+ for (const child of configCell.childNodes) {
587
+ cell.appendChild(child.cloneNode(true));
588
+ }
589
+ for (const cssClass of configCell.classList) {
590
+ cell.classList.add(cssClass);
591
+ }
592
+ cell.title = configCell.title;
593
+ if (configCell.hasAttribute('aria-label')) {
594
+ this.setCellAccessibleName(configCell.getAttribute('aria-label') || '', cell, columnId);
595
+ }
596
+ const style = configCell.getAttribute('style');
597
+ if (style !== null) {
598
+ cell.setAttribute('style', style);
599
+ }
600
+
549
601
  return cell;
550
602
  }
551
- const cell = this.createTD(columnId);
552
- cell.setAttribute('part', `${columnId}-column`);
553
- const configCells = [...this.#configElement.children].filter(c => c.tagName === 'TD') as HTMLTableCellElement[];
554
- const configCell = configCells[index];
555
- if (this.isCreationNode && !configCell) {
556
- return cell;
557
- }
558
- if (!configCell) {
559
- throw new Error(`Column ${columnId} not found in the data grid`);
560
- }
561
- for (const child of configCell.childNodes) {
562
- cell.appendChild(child.cloneNode(true));
563
- }
564
- for (const cssClass of configCell.classList) {
565
- cell.classList.add(cssClass);
566
- }
567
- cell.title = configCell.title;
568
- if (configCell.hasAttribute('aria-label')) {
569
- this.setCellAccessibleName(configCell.getAttribute('aria-label') || '', cell, columnId);
570
- }
571
- const style = configCell.getAttribute('style');
572
- if (style !== null) {
573
- cell.setAttribute('style', style);
574
- }
575
603
 
576
- return cell;
577
- }
604
+ override deselect(): void {
605
+ super.deselect();
606
+ if (this.isCreationNode) {
607
+ this.#dataGridElement.dispatchEvent(new CustomEvent('create', {detail: this.data}));
608
+ }
609
+ }
610
+ };
611
+ }
578
612
 
579
- static remove(node: DataGridElementNode): void {
580
- DataGridElementNode.#elementToNode.delete(node.#configElement);
581
- node.remove();
613
+ // clang-format off
614
+ class SortableNode extends nodeMixin(SortableDataGridNode<SortableNode>) {
615
+ // clang-format on
616
+ constructor(configElement: Element, dataGridElement: DataGridElement) {
617
+ super();
618
+ this.initElementNode(configElement, dataGridElement);
619
+ elementToNode.set(configElement, this);
582
620
  }
621
+ }
583
622
 
584
- override deselect(): void {
585
- super.deselect();
586
- if (this.isCreationNode) {
587
- this.#dataGridElement.dispatchEvent(new CustomEvent('create', {detail: this.data}));
588
- }
623
+ // clang-format off
624
+ class DynamicHeightNode extends nodeMixin(DataGridNode<DynamicHeightNode>) {
625
+ // clang-format on
626
+ constructor(configElement: Element, dataGridElement: DataGridElement) {
627
+ super();
628
+ this.initElementNode(configElement, dataGridElement);
629
+ elementToNode.set(configElement, this);
589
630
  }
590
631
  }
591
632
 
@@ -633,7 +674,7 @@ class IfExpandedDirective extends Lit.Directive.Directive {
633
674
  if (!(element instanceof HTMLElement)) {
634
675
  return false;
635
676
  }
636
- const node = DataGridElementNode.get(element.closest('tr') ?? undefined);
677
+ const node = getNode(element.closest('tr') ?? undefined);
637
678
  if (!node) {
638
679
  return false;
639
680
  }
@@ -641,3 +682,15 @@ class IfExpandedDirective extends Lit.Directive.Directive {
641
682
  }
642
683
  }
643
684
  export const ifExpanded = Lit.Directive.directive(IfExpandedDirective);
685
+
686
+ function getNode(element: Element|undefined): DataGridElementNode|undefined {
687
+ return element ? elementToNode.get(element) : undefined;
688
+ }
689
+
690
+ function removeNode(node: DataGridElementNode): void {
691
+ const configElement = node.configElement;
692
+ if (configElement) {
693
+ elementToNode.delete(configElement);
694
+ }
695
+ node.remove();
696
+ }
@@ -0,0 +1,89 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import type {DataGridImpl} from './DataGrid.js';
6
+
7
+ /**
8
+ * Exports the visual contents of a DataGrid as a GitHub Flavored Markdown table.
9
+ */
10
+ export function exportToMarkdown(dataGrid: DataGridImpl<unknown>): string {
11
+ const grid = serializeGrid(dataGrid, escapeMarkdown);
12
+ if (grid.length === 0) {
13
+ return '';
14
+ }
15
+ const header = '| ' + grid[0].join(' | ') + ' |';
16
+ const separator = '| ' + grid[0].map(() => '---').join(' | ') + ' |';
17
+ const body = grid.slice(1).map(row => '| ' + row.join(' | ') + ' |');
18
+ return [header, separator, ...body].join('\n');
19
+ }
20
+
21
+ export function exportToCSV(dataGrid: DataGridImpl<unknown>): string {
22
+ const grid = serializeGrid(dataGrid, escapeCSV);
23
+ return grid.map(row => row.join(',')).join('\n');
24
+ }
25
+
26
+ function serializeGrid(dataGrid: DataGridImpl<unknown>, cellEscaper: (val: string) => string): string[][] {
27
+ const columns = dataGrid.visibleColumnsArray;
28
+ if (columns.length === 0) {
29
+ return [];
30
+ }
31
+ const rows = dataGrid.rootNode().children;
32
+
33
+ const result: string[][] = [];
34
+ // Header
35
+ result.push(columns.map(col => cellEscaper(String(col.title || ''))));
36
+
37
+ // Body
38
+ for (const row of rows) {
39
+ result.push(columns.map(col => {
40
+ const cellValue = row.data[col.id];
41
+ const cellText = extractText(cellValue);
42
+ return cellEscaper(cellText);
43
+ }));
44
+ }
45
+
46
+ return result;
47
+ }
48
+
49
+ function extractText(value: unknown): string {
50
+ if (value instanceof Node) {
51
+ return value.textContent || '';
52
+ }
53
+ if (value === undefined || value === null) {
54
+ return '';
55
+ }
56
+ return String(value);
57
+ }
58
+
59
+ /**
60
+ * Escapes a string so it can be safely rendered within a Markdown table cell.
61
+ *
62
+ * It performs three operations:
63
+ * 1. HTML-escapes `<`, `>`, and `&` to prevent text from being parsed as raw HTML.
64
+ * 2. Backslash-escapes Markdown control characters (e.g., `*`, `_`, `[`) to avoid unexpected formatting.
65
+ * 3. Replaces newlines with `<br>` tags since Markdown tables do not support multiline cells.
66
+ */
67
+ function escapeMarkdown(val: string): string {
68
+ return val.replace(/&/g, '&amp;')
69
+ .replace(/</g, '&lt;')
70
+ .replace(/>/g, '&gt;')
71
+ .replace(/([\\`*_{}[\]()#+\-.!|])/g, '\\$1')
72
+ .replace(/\r?\n/g, '<br>');
73
+ }
74
+
75
+ function escapeCSV(val: string): string {
76
+ let needQuotes = false;
77
+ let escaped = val;
78
+ if (escaped.includes('"')) {
79
+ escaped = escaped.replace(/"/g, '""');
80
+ needQuotes = true;
81
+ }
82
+ if (escaped.includes(',') || escaped.includes('\n') || escaped.includes('\r')) {
83
+ needQuotes = true;
84
+ }
85
+ if (needQuotes) {
86
+ return `"${escaped}"`;
87
+ }
88
+ return escaped;
89
+ }
@@ -110,6 +110,16 @@
110
110
  padding: 1px 4px;
111
111
  }
112
112
 
113
+ .data-grid.auto-row-height tbody tr {
114
+ height: auto;
115
+ }
116
+
117
+ .data-grid.auto-row-height td {
118
+ white-space: normal;
119
+ height: auto;
120
+ }
121
+
122
+
113
123
  .data-grid td {
114
124
  vertical-align: top;
115
125
  user-select: text;
@@ -7,11 +7,13 @@ import './ViewportDataGrid.js';
7
7
  import './SortableDataGrid.js';
8
8
  import './ShowMoreDataGridNode.js';
9
9
  import './DataGridElement.js';
10
+ import './DataGridExporter.js';
10
11
 
11
12
  import * as DataGrid from './DataGrid.js';
13
+ import * as DataGridExporter from './DataGridExporter.js';
12
14
  import * as ShowMoreDataGridNode from './ShowMoreDataGridNode.js';
13
15
  import * as SortableDataGrid from './SortableDataGrid.js';
14
16
  import * as ViewportDataGrid from './ViewportDataGrid.js';
15
17
 
16
18
  export {ifExpanded} from './DataGridElement.js';
17
- export {DataGrid, ShowMoreDataGridNode, SortableDataGrid, ViewportDataGrid};
19
+ export {DataGrid, DataGridExporter, ShowMoreDataGridNode, SortableDataGrid, ViewportDataGrid};
@@ -81,7 +81,14 @@ export class CustomPreviewSection {
81
81
  return document.createTextNode(String(jsonML));
82
82
  }
83
83
 
84
- return jsonML[0] === 'object' ? this.layoutObjectTag(jsonML) : this.renderElement(jsonML);
84
+ if (jsonML[0] !== 'object') {
85
+ return this.renderElement(jsonML);
86
+ }
87
+ if (jsonML.length !== 2) {
88
+ Common.Console.Console.instance().error('Broken formatter: object reference must contain exactly two elements');
89
+ return document.createElement('span');
90
+ }
91
+ return this.layoutObjectTag(jsonML);
85
92
  }
86
93
 
87
94
  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)