Repository: deephaven/deephaven-plugins (language: python)

Issue:
ui.list_view - action support
JS Plugin side of action support for ui.list_view


Discussion / hints:

Fix patch:
diff --git a/package-lock.json b/package-lock.json
index 2b135de22..347962b4b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2169,9 +2169,9 @@
       }
     },
     "node_modules/@csstools/css-parser-algorithms": {
-      "version": "2.6.1",
-      "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.1.tgz",
-      "integrity": "sha512-ubEkAaTfVZa+WwGhs5jbo5Xfqpeaybr/RvWzvFxRs4jfq16wH8l8Ty/QEEpINxll4xhuGfdMbipRyz5QZh9+FA==",
+      "version": "2.6.3",
+      "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.3.tgz",
+      "integrity": "sha512-xI/tL2zxzEbESvnSxwFgwvy5HS00oCXxL4MLs6HUiDcYfwowsoQaABKxUElp1ARITrINzBnsECOc1q0eg2GOrA==",
       "dev": true,
       "funding": [
         {
@@ -2188,13 +2188,13 @@
         "node": "^14 || ^16 || >=18"
       },
       "peerDependencies": {
-        "@csstools/css-tokenizer": "^2.2.4"
+        "@csstools/css-tokenizer": "^2.3.1"
       }
     },
     "node_modules/@csstools/css-tokenizer": {
-      "version": "2.2.4",
-      "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.4.tgz",
-      "integrity": "sha512-PuWRAewQLbDhGeTvFuq2oClaSCKPIBmHyIobCV39JHRYN0byDcUWJl5baPeNUcqrjtdMNqFooE0FGl31I3JOqw==",
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.3.1.tgz",
+      "integrity": "sha512-iMNHTyxLbBlWIfGtabT157LH9DUx9X8+Y3oymFEuMj8HNc+rpE3dPFGFgHjpKfjeFDjLjYIAIhXPGvS2lKxL9g==",
       "dev": true,
       "funding": [
         {
@@ -2212,9 +2212,9 @@
       }
     },
     "node_modules/@csstools/media-query-list-parser": {
-      "version": "2.1.9",
-      "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.9.tgz",
-      "integrity": "sha512-qqGuFfbn4rUmyOB0u8CVISIp5FfJ5GAR3mBrZ9/TKndHakdnm6pY0L/fbLcpPnrzwCyyTEZl1nUcXAYHEWneTA==",
+      "version": "2.1.11",
+      "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.11.tgz",
+      "integrity": "sha512-uox5MVhvNHqitPP+SynrB1o8oPxPMt2JLgp5ghJOWf54WGQ5OKu47efne49r1SWqs3wRP8xSWjnO9MBKxhB1dA==",
       "dev": true,
       "funding": [
         {
@@ -2231,14 +2231,14 @@
         "node": "^14 || ^16 || >=18"
       },
       "peerDependencies": {
-        "@csstools/css-parser-algorithms": "^2.6.1",
-        "@csstools/css-tokenizer": "^2.2.4"
+        "@csstools/css-parser-algorithms": "^2.6.3",
+        "@csstools/css-tokenizer": "^2.3.1"
       }
     },
     "node_modules/@csstools/selector-specificity": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.3.tgz",
-      "integrity": "sha512-KEPNw4+WW5AVEIyzC80rTbWEUatTW2lXpN8+8ILC8PiPeWPjwUzrPZDIOZ2wwqDmeqOYTdSGyL3+vE5GC3FB3Q==",
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz",
+      "integrity": "sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA==",
       "dev": true,
       "funding": [
         {
@@ -4258,9 +4258,9 @@
       }
     },
     "node_modules/@dual-bundle/import-meta-resolve": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz",
-      "integrity": "sha512-ZKXyJeFAzcpKM2kk8ipoGIPUqx9BX52omTGnfwjJvxOCaZTM2wtDK7zN0aIgPRbT9XYAlha0HtmZ+XKteuh0Gw==",
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
+      "integrity": "sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==",
       "dev": true,
       "peer": true,
       "funding": {
@@ -16002,9 +16002,9 @@
       }
     },
     "node_modules/enhanced-resolve": {
-      "version": "5.16.0",
-      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz",
-      "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==",
+      "version": "5.16.1",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz",
+      "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==",
       "dev": true,
       "peer": true,
       "dependencies": {
@@ -17921,9 +17921,9 @@
       }
     },
     "node_modules/get-tsconfig": {
-      "version": "4.7.3",
-      "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz",
-      "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==",
+      "version": "4.7.5",
+      "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz",
+      "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==",
       "dev": true,
       "peer": true,
       "dependencies": {
@@ -34879,21 +34879,21 @@
       "license": "Apache-2.0",
       "dependencies": {
         "@adobe/react-spectrum": "^3.34.1",
-        "@deephaven/chart": "^0.76.0",
-        "@deephaven/components": "^0.76.0",
-        "@deephaven/dashboard": "^0.76.0",
-        "@deephaven/dashboard-core-plugins": "^0.76.0",
-        "@deephaven/grid": "^0.76.0",
-        "@deephaven/icons": "^0.76.0",
-        "@deephaven/iris-grid": "^0.76.0",
-        "@deephaven/jsapi-bootstrap": "^0.76.0",
-        "@deephaven/jsapi-components": "^0.76.0",
-        "@deephaven/jsapi-types": "^1.0.0-dev0.34.1",
-        "@deephaven/log": "^0.76.0",
-        "@deephaven/plugin": "^0.76.0",
-        "@deephaven/react-hooks": "^0.76.0",
-        "@deephaven/redux": "^0.76.0",
-        "@deephaven/utils": "^0.76.0",
+        "@deephaven/chart": "^0.78.0",
+        "@deephaven/components": "^0.78.0",
+        "@deephaven/dashboard": "^0.78.0",
+        "@deephaven/dashboard-core-plugins": "^0.78.0",
+        "@deephaven/grid": "^0.78.0",
+        "@deephaven/icons": "^0.78.0",
+        "@deephaven/iris-grid": "^0.78.0",
+        "@deephaven/jsapi-bootstrap": "^0.78.0",
+        "@deephaven/jsapi-components": "^0.78.0",
+        "@deephaven/jsapi-types": "^1.0.0-dev0.34.2",
+        "@deephaven/log": "^0.78.0",
+        "@deephaven/plugin": "^0.78.0",
+        "@deephaven/react-hooks": "^0.78.0",
+        "@deephaven/redux": "^0.78.0",
+        "@deephaven/utils": "^0.78.0",
         "@fortawesome/react-fontawesome": "^0.2.0",
         "@react-types/shared": "^3.22.0",
         "classnames": "^2.5.1",
@@ -34982,17 +34982,17 @@
       }
     },
     "plugins/ui/src/js/node_modules/@deephaven/chart": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/chart/-/chart-0.76.0.tgz",
-      "integrity": "sha512-n/ZjmJiHjgheu2C6qpsEQJJbh5yfAx8KDdYkt/PBVmFmS9zUNi30WzWyQdn/wJdddm8V878wPpBls1XCvEGl1g==",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/chart/-/chart-0.78.0.tgz",
+      "integrity": "sha512-G28KsnH+p3sot94UZP+LkwpyHPBUmtwbIyaIUQQxvEA8Hr5vJlN92IkMKCWEtPFRTiHhCrHYkgfQ54SN2XyNZQ==",
       "dependencies": {
-        "@deephaven/components": "^0.76.0",
-        "@deephaven/icons": "^0.76.0",
+        "@deephaven/components": "^0.78.0",
+        "@deephaven/icons": "^0.78.0",
         "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-        "@deephaven/jsapi-utils": "^0.76.0",
-        "@deephaven/log": "^0.76.0",
-        "@deephaven/react-hooks": "^0.76.0",
-        "@deephaven/utils": "^0.76.0",
+        "@deephaven/jsapi-utils": "^0.78.0",
+        "@deephaven/log": "^0.78.0",
+        "@deephaven/react-hooks": "^0.78.0",
+        "@deephaven/utils": "^0.78.0",
         "buffer": "^6.0.3",
         "fast-deep-equal": "^3.1.3",
         "lodash.debounce": "^4.0.8",
@@ -35016,15 +35016,15 @@
       "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng=="
     },
     "plugins/ui/src/js/node_modules/@deephaven/components": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/components/-/components-0.76.0.tgz",
-      "integrity": "sha512-s755ZS70bgxy4Rdj1d9uM8haNKIgBPgUuiHYQ0G8Ady6DCziTZPTKMVqDKtNuWR6mdwrsD7+fGkCIo0qdfXJ7Q==",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/components/-/components-0.78.0.tgz",
+      "integrity": "sha512-gx+yc1ZNRb4Pa3ptilGKBypPRNVoPrIqlDb7wJeZCKX+OnMZczFZLdEn12bh0s4Dnp460VNhegG2iM5W14GBHA==",
       "dependencies": {
         "@adobe/react-spectrum": "3.33.1",
-        "@deephaven/icons": "^0.76.0",
-        "@deephaven/log": "^0.76.0",
-        "@deephaven/react-hooks": "^0.76.0",
-        "@deephaven/utils": "^0.76.0",
+        "@deephaven/icons": "^0.78.0",
+        "@deephaven/log": "^0.78.0",
+        "@deephaven/react-hooks": "^0.78.0",
+        "@deephaven/utils": "^0.78.0",
         "@fortawesome/fontawesome-svg-core": "^6.2.1",
         "@fortawesome/react-fontawesome": "^0.2.0",
         "@react-spectrum/theme-default": "^3.5.1",
@@ -35055,19 +35055,19 @@
       }
     },
     "plugins/ui/src/js/node_modules/@deephaven/console": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/console/-/console-0.76.0.tgz",
-      "integrity": "sha512-Hd4sr9fHRq1Ob4pt9VYuInJ8tQDCpulU1HVKCjXv/oSukv/lfHmyqkgPVUF+VQ6o6x9kDdvP6ym05UYoI26xHA==",
-      "dependencies": {
-        "@deephaven/chart": "^0.76.0",
-        "@deephaven/components": "^0.76.0",
-        "@deephaven/icons": "^0.76.0",
-        "@deephaven/jsapi-bootstrap": "^0.76.0",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/console/-/console-0.78.0.tgz",
+      "integrity": "sha512-mWFRwGLUAEKpyedmDFhLnvm75GDYchPmM8e4X/iG1Zna72euhar3++EDwLcjO+7IBqqDN1mU27hVoGKp0FmnNg==",
+      "dependencies": {
+        "@deephaven/chart": "^0.78.0",
+        "@deephaven/components": "^0.78.0",
+        "@deephaven/icons": "^0.78.0",
+        "@deephaven/jsapi-bootstrap": "^0.78.0",
         "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-        "@deephaven/log": "^0.76.0",
-        "@deephaven/react-hooks": "^0.76.0",
-        "@deephaven/storage": "^0.76.0",
-        "@deephaven/utils": "^0.76.0",
+        "@deephaven/log": "^0.78.0",
+        "@deephaven/react-hooks": "^0.78.0",
+        "@deephaven/storage": "^0.78.0",
+        "@deephaven/utils": "^0.78.0",
         "@fortawesome/react-fontawesome": "^0.2.0",
         "classnames": "^2.3.1",
         "linkifyjs": "^4.1.0",
@@ -35096,16 +35096,16 @@
       "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng=="
     },
     "plugins/ui/src/js/node_modules/@deephaven/dashboard": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/dashboard/-/dashboard-0.76.0.tgz",
-      "integrity": "sha512-ThxUFELHTtdlXSpjLiQRxBxCf14z7Ipc+a2ELrprD9hoa7OvEl/nzQzuX1QTU378AOIbQhMKwDgmbVjDVXPx3g==",
-      "dependencies": {
-        "@deephaven/components": "^0.76.0",
-        "@deephaven/golden-layout": "^0.76.0",
-        "@deephaven/log": "^0.76.0",
-        "@deephaven/react-hooks": "^0.76.0",
-        "@deephaven/redux": "^0.76.0",
-        "@deephaven/utils": "^0.76.0",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/dashboard/-/dashboard-0.78.0.tgz",
+      "integrity": "sha512-PzlWlJrA8HiIYkpzQUjyDhPJr8vh0dvKQDX9eBM4QuWGoIk75Oo6oDUUJhmNyp8X7TilnR5rdOnOVosbs110vg==",
+      "dependencies": {
+        "@deephaven/components": "^0.78.0",
+        "@deephaven/golden-layout": "^0.78.0",
+        "@deephaven/log": "^0.78.0",
+        "@deephaven/react-hooks": "^0.78.0",
+        "@deephaven/redux": "^0.78.0",
+        "@deephaven/utils": "^0.78.0",
         "fast-deep-equal": "^3.1.3",
         "lodash.ismatch": "^4.1.1",
         "lodash.throttle": "^4.1.1",
@@ -35123,30 +35123,30 @@
       }
     },
     "plugins/ui/src/js/node_modules/@deephaven/dashboard-core-plugins": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/dashboard-core-plugins/-/dashboard-core-plugins-0.76.0.tgz",
-      "integrity": "sha512-nnXsYm55vKThBxKeILLP7fR66is805mJ1JOXxZcuG4rDzqMstFZyhQF4GtS9BuxsCJKZE/frlLedYVfCrUvp/g==",
-      "dependencies": {
-        "@deephaven/chart": "^0.76.0",
-        "@deephaven/components": "^0.76.0",
-        "@deephaven/console": "^0.76.0",
-        "@deephaven/dashboard": "^0.76.0",
-        "@deephaven/file-explorer": "^0.76.0",
-        "@deephaven/filters": "^0.76.0",
-        "@deephaven/golden-layout": "^0.76.0",
-        "@deephaven/grid": "^0.76.0",
-        "@deephaven/icons": "^0.76.0",
-        "@deephaven/iris-grid": "^0.76.0",
-        "@deephaven/jsapi-bootstrap": "^0.76.0",
-        "@deephaven/jsapi-components": "^0.76.0",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/dashboard-core-plugins/-/dashboard-core-plugins-0.78.0.tgz",
+      "integrity": "sha512-hssDMaXWRzj3KsGP4J+9Q+Ww6cW1NQBrmCvCVO3/R7DZZXCWohQFU1zPwKBxXEDk3BbGx4zc7imM/CsU5eB5cw==",
+      "dependencies": {
+        "@deephaven/chart": "^0.78.0",
+        "@deephaven/components": "^0.78.0",
+        "@deephaven/console": "^0.78.0",
+        "@deephaven/dashboard": "^0.78.0",
+        "@deephaven/file-explorer": "^0.78.0",
+        "@deephaven/filters": "^0.78.0",
+        "@deephaven/golden-layout": "^0.78.0",
+        "@deephaven/grid": "^0.78.0",
+        "@deephaven/icons": "^0.78.0",
+        "@deephaven/iris-grid": "^0.78.0",
+        "@deephaven/jsapi-bootstrap": "^0.78.0",
+        "@deephaven/jsapi-components": "^0.78.0",
         "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-        "@deephaven/jsapi-utils": "^0.76.0",
-        "@deephaven/log": "^0.76.0",
-        "@deephaven/plugin": "^0.76.0",
-        "@deephaven/react-hooks": "^0.76.0",
-        "@deephaven/redux": "^0.76.0",
-        "@deephaven/storage": "^0.76.0",
-        "@deephaven/utils": "^0.76.0",
+        "@deephaven/jsapi-utils": "^0.78.0",
+        "@deephaven/log": "^0.78.0",
+        "@deephaven/plugin": "^0.78.0",
+        "@deephaven/react-hooks": "^0.78.0",
+        "@deephaven/redux": "^0.78.0",
+        "@deephaven/storage": "^0.78.0",
+        "@deephaven/utils": "^0.78.0",
         "@fortawesome/react-fontawesome": "^0.2.0",
         "classnames": "^2.3.1",
         "fast-deep-equal": "^3.1.3",
@@ -35179,15 +35179,15 @@
       "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng=="
     },
     "plugins/ui/src/js/node_modules/@deephaven/file-explorer": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/file-explorer/-/file-explorer-0.76.0.tgz",
-      "integrity": "sha512-E6h1SA73NlhdQ6S6X4NyAe5acTgM6Lmrxu03lKgF9bUJVxExvuOzpSK1gZJqSksCzUnl5b/R06X/UJTxx65xpg==",
-      "dependencies": {
-        "@deephaven/components": "^0.76.0",
-        "@deephaven/icons": "^0.76.0",
-        "@deephaven/log": "^0.76.0",
-        "@deephaven/storage": "^0.76.0",
-        "@deephaven/utils": "^0.76.0",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/file-explorer/-/file-explorer-0.78.0.tgz",
+      "integrity": "sha512-qXeDu1FB08k6g+Y+3WElWO6Iq4TryqhWlh+sw4iLx6Ey7rkpZZkFvPpdtVldVoDixgCO+3zuUwWkAYE3rO0LGg==",
+      "dependencies": {
+        "@deephaven/components": "^0.78.0",
+        "@deephaven/icons": "^0.78.0",
+        "@deephaven/log": "^0.78.0",
+        "@deephaven/storage": "^0.78.0",
+        "@deephaven/utils": "^0.78.0",
         "@fortawesome/fontawesome-svg-core": "^6.2.1",
         "@fortawesome/react-fontawesome": "^0.2.0",
         "classnames": "^2.3.1",
@@ -35202,19 +35202,19 @@
       }
     },
     "plugins/ui/src/js/node_modules/@deephaven/filters": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/filters/-/filters-0.76.0.tgz",
-      "integrity": "sha512-rL2X88+0o2hw17L5i1lwG4y3zbDvpB8QUnF/2bd/B2dGVpixPeOw+p5mRWwmOfxU+U9wwtRypcbsy8Dv0QIH8w==",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/filters/-/filters-0.78.0.tgz",
+      "integrity": "sha512-0C9udzidoX99nZXxQ5UOn+zRq2dAA16ic0FvnUBBJtKKwxfuAHPIq95H4b5VHgUh6lVOG6Q432xrRSinB7wrlw==",
       "engines": {
         "node": ">=16"
       }
     },
     "plugins/ui/src/js/node_modules/@deephaven/golden-layout": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/golden-layout/-/golden-layout-0.76.0.tgz",
-      "integrity": "sha512-rJ6Za+vclfMjXNakdH5F7tAlJZz1Ti+Enidzs8MakVSTTS7Q8cwGJX8Fpbdv5g9RBDjCbxvaarmX2CyecAR6Ng==",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/golden-layout/-/golden-layout-0.78.0.tgz",
+      "integrity": "sha512-TFnlrb/TIqJlUlRN3I4v5i/UMpLCbcVnD5+FVEkN1Rwnn9n/lfY4kgu3o0Cr56TLXllfdmfuEHWJY4pJpoFrlg==",
       "dependencies": {
-        "@deephaven/components": "^0.76.0",
+        "@deephaven/components": "^0.78.0",
         "jquery": "^3.6.0"
       },
       "peerDependencies": {
@@ -35223,11 +35223,11 @@
       }
     },
     "plugins/ui/src/js/node_modules/@deephaven/grid": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/grid/-/grid-0.76.0.tgz",
-      "integrity": "sha512-o0Or6LGga0Kaz8QtXjnkemdMvLftU7piranylTZdtbViDvnUohMmunnrE8oNzozdZhM99z/3rl3XznCrrYAEpg==",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/grid/-/grid-0.78.0.tgz",
+      "integrity": "sha512-w5qC8MhS/eUJzro8lkTHe2XOw+KAnhFgqkA1RFAr4A0/T0pUha7IJhdjH0JMBYOLaik6bMCPrQDiU8GvFyJPtA==",
       "dependencies": {
-        "@deephaven/utils": "^0.76.0",
+        "@deephaven/utils": "^0.78.0",
         "classnames": "^2.3.1",
         "color-convert": "^2.0.1",
         "event-target-shim": "^6.0.2",
@@ -35245,9 +35245,9 @@
       }
     },
     "plugins/ui/src/js/node_modules/@deephaven/icons": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/icons/-/icons-0.76.0.tgz",
-      "integrity": "sha512-5CXvy3QzXWPIUVAz/6pDLcXdhI1ylxQITcvmisjfcvLjw/0+X763yKELrFE1bnfI2RfNBDgmTCCRdC8argZy2A==",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/icons/-/icons-0.78.0.tgz",
+      "integrity": "sha512-gFR7hWghDJGgNphF9FpI0VnjCQrZ7FpQqFmxTzWTBSLWAxwRL1jR3glAR1/GbpfugE4BwuQARI+zRBU0xB4z1w==",
       "dependencies": {
         "@fortawesome/fontawesome-common-types": "^6.1.1"
       },
@@ -35257,22 +35257,22 @@
       }
     },
     "plugins/ui/src/js/node_modules/@deephaven/iris-grid": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/iris-grid/-/iris-grid-0.76.0.tgz",
-      "integrity": "sha512-kx/ql5cpMViV4zxgvGWGDTcQx2Sq1OJB+vgdnAHYk+UldBnb4fQqQLTJqp7PZOKJURa5CcVXG4NKC3R+MNq8wA==",
-      "dependencies": {
-        "@deephaven/components": "^0.76.0",
-        "@deephaven/console": "^0.76.0",
-        "@deephaven/filters": "^0.76.0",
-        "@deephaven/grid": "^0.76.0",
-        "@deephaven/icons": "^0.76.0",
-        "@deephaven/jsapi-components": "^0.76.0",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/iris-grid/-/iris-grid-0.78.0.tgz",
+      "integrity": "sha512-kRjZC+aRjFlmQUSV3qcYL4BxdruxQKr4CY96FRC9pPgRztaLbU/pQHawuOp8aqydxkrpIR5eF1EVQIzPlR9piA==",
+      "dependencies": {
+        "@deephaven/components": "^0.78.0",
+        "@deephaven/console": "^0.78.0",
+        "@deephaven/filters": "^0.78.0",
+        "@deephaven/grid": "^0.78.0",
+        "@deephaven/icons": "^0.78.0",
+        "@deephaven/jsapi-components": "^0.78.0",
         "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-        "@deephaven/jsapi-utils": "^0.76.0",
-        "@deephaven/log": "^0.76.0",
-        "@deephaven/react-hooks": "^0.76.0",
-        "@deephaven/storage": "^0.76.0",
-        "@deephaven/utils": "^0.76.0",
+        "@deephaven/jsapi-utils": "^0.78.0",
+        "@deephaven/log": "^0.78.0",
+        "@deephaven/react-hooks": "^0.78.0",
+        "@deephaven/storage": "^0.78.0",
+        "@deephaven/utils": "^0.78.0",
         "@dnd-kit/core": "^6.1.0",
         "@dnd-kit/sortable": "^7.0.2",
         "@dnd-kit/utilities": "^3.2.2",
@@ -35304,14 +35304,14 @@
       "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng=="
     },
     "plugins/ui/src/js/node_modules/@deephaven/jsapi-bootstrap": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/jsapi-bootstrap/-/jsapi-bootstrap-0.76.0.tgz",
-      "integrity": "sha512-FdZyKcVaxSRaKtUL85VxX5apkm3+Sfu1yl/58iHHQVWXrU5RWjyav3U6vDZkg7yAvlP4/hC4MMfijK9LIZJSVg==",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/jsapi-bootstrap/-/jsapi-bootstrap-0.78.0.tgz",
+      "integrity": "sha512-XeQnxcCTLH6CdycDvo4dw/gpoqOnGYQQUSAlWqHjwiR0Ep+11JBkeIZRufsT1/GBfaQDho7GtZAJ0vlgADFSow==",
       "dependencies": {
-        "@deephaven/components": "^0.76.0",
+        "@deephaven/components": "^0.78.0",
         "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-        "@deephaven/log": "^0.76.0",
-        "@deephaven/react-hooks": "^0.76.0"
+        "@deephaven/log": "^0.78.0",
+        "@deephaven/react-hooks": "^0.78.0"
       },
       "engines": {
         "node": ">=16"
@@ -35326,17 +35326,17 @@
       "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng=="
     },
     "plugins/ui/src/js/node_modules/@deephaven/jsapi-components": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/jsapi-components/-/jsapi-components-0.76.0.tgz",
-      "integrity": "sha512-Smk6RKYGEO5fmcXYRuj09ipebKeV9JnTpHwmgr2PiFKlZHaWNspTpQb1Ed0O1U+YYdoVF6ZaJSx8HJdi5GnSEg==",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/jsapi-components/-/jsapi-components-0.78.0.tgz",
+      "integrity": "sha512-VSQD7gvollbyQTbzW6jhIjxQplm6i5tvL/nyqY6P19W0bxCcraYGeVzoaL5XvTQo91GvQkqB6d1nltOytI3dmw==",
       "dependencies": {
-        "@deephaven/components": "^0.76.0",
-        "@deephaven/jsapi-bootstrap": "^0.76.0",
+        "@deephaven/components": "^0.78.0",
+        "@deephaven/jsapi-bootstrap": "^0.78.0",
         "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-        "@deephaven/jsapi-utils": "^0.76.0",
-        "@deephaven/log": "^0.76.0",
-        "@deephaven/react-hooks": "^0.76.0",
-        "@deephaven/utils": "^0.76.0",
+        "@deephaven/jsapi-utils": "^0.78.0",
+        "@deephaven/log": "^0.78.0",
+        "@deephaven/react-hooks": "^0.78.0",
+        "@deephaven/utils": "^0.78.0",
         "@types/js-cookie": "^3.0.3",
         "classnames": "^2.3.2",
         "js-cookie": "^3.0.5",
@@ -35356,19 +35356,19 @@
       "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng=="
     },
     "plugins/ui/src/js/node_modules/@deephaven/jsapi-types": {
-      "version": "1.0.0-dev0.34.1",
-      "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.1.tgz",
-      "integrity": "sha512-zOS96nGk9l/FDw84hoXQ1ESMDXBJSBck1hHFyMyxJzhJbbGVz2Lumr/ZRDy6IVVrC6TKuI6Ox+jBlXwRCl/U+w=="
+      "version": "1.0.0-dev0.34.2",
+      "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.2.tgz",
+      "integrity": "sha512-vHEP+93r2zsPPZs5hLT4I6eYw2pWqLqL7e4GSedX7K37P57+MzLtRR+eTp29o1BVwbuRpQfz6D/hSROzvw3XxQ=="
     },
     "plugins/ui/src/js/node_modules/@deephaven/jsapi-utils": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/jsapi-utils/-/jsapi-utils-0.76.0.tgz",
-      "integrity": "sha512-sYGLCcfS3NoLxF0btK9Q1Om7PIBumnwe0Jt+lvevLEWclikz9RQliHN1GcX5OHQYAtky8OB4TpofWL7mugsenA==",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/jsapi-utils/-/jsapi-utils-0.78.0.tgz",
+      "integrity": "sha512-VeCCns+nkKucbQYRGOak1MisEFQzI9bJQ+pAmqXBhVxJHj5NxqRiqnyoyp1VQig6zg1dzVqUj4h5ZTHXJ1QHdw==",
       "dependencies": {
-        "@deephaven/filters": "^0.76.0",
+        "@deephaven/filters": "^0.78.0",
         "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-        "@deephaven/log": "^0.76.0",
-        "@deephaven/utils": "^0.76.0",
+        "@deephaven/log": "^0.78.0",
+        "@deephaven/utils": "^0.78.0",
         "lodash.clamp": "^4.0.3",
         "shortid": "^2.2.16"
       },
@@ -35382,9 +35382,9 @@
       "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng=="
     },
     "plugins/ui/src/js/node_modules/@deephaven/log": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/log/-/log-0.76.0.tgz",
-      "integrity": "sha512-NCxpWHEcEmh2WXAj4IG4MFMMTmke+DD4JQg4yxH7wUn4YeZIMpXdbrWh20Jrf9q3NdKl2+BPb5IyaUIvKeJeSg==",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/log/-/log-0.78.0.tgz",
+      "integrity": "sha512-6GXUm1yjHTUWpi/p22sJ/F8yZ+l7I3jnfs9HlRjSErB7tb5jmpBXGAMTkL8oCuvvycLdx0QKcs17VJxV0saejw==",
       "dependencies": {
         "event-target-shim": "^6.0.2"
       },
@@ -35393,17 +35393,17 @@
       }
     },
     "plugins/ui/src/js/node_modules/@deephaven/plugin": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/plugin/-/plugin-0.76.0.tgz",
-      "integrity": "sha512-bL64Ck7kHZIOk3b3N9gxy2UU4SZ2m/YV49737zl2V5nh6cm1VW9RVL6n4Tk/d8uVxMKom5EzKuMFeEZnBWWrDQ==",
-      "dependencies": {
-        "@deephaven/components": "^0.76.0",
-        "@deephaven/golden-layout": "^0.76.0",
-        "@deephaven/icons": "^0.76.0",
-        "@deephaven/iris-grid": "^0.76.0",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/plugin/-/plugin-0.78.0.tgz",
+      "integrity": "sha512-HLE4lEA3/8ICkhgrD4N0cH2gAkQd44yftHKr6a1UeZWp/A3fheSrawzB1hjQCXsmpNf82UGuhrFyILTyWcHxwQ==",
+      "dependencies": {
+        "@deephaven/components": "^0.78.0",
+        "@deephaven/golden-layout": "^0.78.0",
+        "@deephaven/icons": "^0.78.0",
+        "@deephaven/iris-grid": "^0.78.0",
         "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-        "@deephaven/log": "^0.76.0",
-        "@deephaven/react-hooks": "^0.76.0",
+        "@deephaven/log": "^0.78.0",
+        "@deephaven/react-hooks": "^0.78.0",
         "@fortawesome/fontawesome-common-types": "^6.1.1",
         "@fortawesome/react-fontawesome": "^0.2.0"
       },
@@ -35420,13 +35420,13 @@
       "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng=="
     },
     "plugins/ui/src/js/node_modules/@deephaven/react-hooks": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/react-hooks/-/react-hooks-0.76.0.tgz",
-      "integrity": "sha512-4ItEuVYDZSkz5thiqPwC1/j4UgfOn/QY1IxyEg7c2yh1CT4y7u2X8FfvaHa9Ed5uUlKSk3PTJFzTtcvXprzk5A==",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/react-hooks/-/react-hooks-0.78.0.tgz",
+      "integrity": "sha512-ic4KTEsykk58hF/RiRaTgNWKYphH6E8jZ/XiJ5sUHFLq+p/z8RCULyQhGcsAt3DwcwRtzHr97g7FvUPxyVE2WA==",
       "dependencies": {
         "@adobe/react-spectrum": "3.33.1",
-        "@deephaven/log": "^0.76.0",
-        "@deephaven/utils": "^0.76.0",
+        "@deephaven/log": "^0.78.0",
+        "@deephaven/utils": "^0.78.0",
         "lodash.debounce": "^4.0.8",
         "lodash.throttle": "^4.1.1",
         "shortid": "^2.2.16"
@@ -35439,14 +35439,14 @@
       }
     },
     "plugins/ui/src/js/node_modules/@deephaven/redux": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/redux/-/redux-0.76.0.tgz",
-      "integrity": "sha512-9UxbMb33wVJBf7i1nw2TT/ABU9WFM8sfXyfUqP57RfQ4FF7Tq/6n8Icv6SMiysE1T+aOs88NG8QeJXTsbB8rSQ==",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/redux/-/redux-0.78.0.tgz",
+      "integrity": "sha512-6G2hDFzkT5hc0P3ARtTfrReXnEJytjnpLFPI2BLR7elQl8A55boQFBOG2KnS0oNiCZaJ50oE4t/CyW0bUTOHcw==",
       "dependencies": {
         "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-        "@deephaven/jsapi-utils": "^0.76.0",
-        "@deephaven/log": "^0.76.0",
-        "@deephaven/plugin": "^0.76.0",
+        "@deephaven/jsapi-utils": "^0.78.0",
+        "@deephaven/log": "^0.78.0",
+        "@deephaven/plugin": "^0.78.0",
         "fast-deep-equal": "^3.1.3",
         "proxy-memoize": "^3.0.0",
         "redux-thunk": "2.4.1"
@@ -35464,12 +35464,12 @@
       "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng=="
     },
     "plugins/ui/src/js/node_modules/@deephaven/storage": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/storage/-/storage-0.76.0.tgz",
-      "integrity": "sha512-OJWPy6bs6U9gZvHzNVRkx3TozXRUdXAeZywOwJ1AjOAU1k2OPS9Rwcy8E4xzbSmvSYnlpB3esCzPsfEAj547lQ==",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/storage/-/storage-0.78.0.tgz",
+      "integrity": "sha512-+2bSIa4iaNqjv6d4v8GBtzOQduJZEMSR2W4kT48yZrItsyABnYnGdPagszgnI6CjCSXNNjJUa6SQLy/jZfSQzQ==",
       "dependencies": {
-        "@deephaven/filters": "^0.76.0",
-        "@deephaven/log": "^0.76.0",
+        "@deephaven/filters": "^0.78.0",
+        "@deephaven/log": "^0.78.0",
         "lodash.throttle": "^4.1.1"
       },
       "engines": {
@@ -35480,9 +35480,9 @@
       }
     },
     "plugins/ui/src/js/node_modules/@deephaven/utils": {
-      "version": "0.76.0",
-      "resolved": "https://registry.npmjs.org/@deephaven/utils/-/utils-0.76.0.tgz",
-      "integrity": "sha512-QXZG3Rr31QOw09TO8fINwWAQOh6baFDuwbkog7WdR3lPMxrL8Y1bLSUYyNbsEHPWmBT6WxWOkWF9x7P+YCdzhA==",
+      "version": "0.78.0",
+      "resolved": "https://registry.npmjs.org/@deephaven/utils/-/utils-0.78.0.tgz",
+      "integrity": "sha512-uQdEiQYoPHDbF1jRlliJITmxDNAYpv1CT8H2vHnTRuGevP17WpudTdUOXjq1Jas4oggpj0Q60/bu0XbxgO3QTA==",
       "engines": {
         "node": ">=16"
       }
@@ -37031,32 +37031,32 @@
       }
     },
     "@csstools/css-parser-algorithms": {
-      "version": "2.6.1",
-      "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.1.tgz",
-      "integrity": "sha512-ubEkAaTfVZa+WwGhs5jbo5Xfqpeaybr/RvWzvFxRs4jfq16wH8l8Ty/QEEpINxll4xhuGfdMbipRyz5QZh9+FA==",
+      "version": "2.6.3",
+      "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.3.tgz",
+      "integrity": "sha512-xI/tL2zxzEbESvnSxwFgwvy5HS00oCXxL4MLs6HUiDcYfwowsoQaABKxUElp1ARITrINzBnsECOc1q0eg2GOrA==",
       "dev": true,
       "peer": true,
       "requires": {}
     },
     "@csstools/css-tokenizer": {
-      "version": "2.2.4",
-      "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.4.tgz",
-      "integrity": "sha512-PuWRAewQLbDhGeTvFuq2oClaSCKPIBmHyIobCV39JHRYN0byDcUWJl5baPeNUcqrjtdMNqFooE0FGl31I3JOqw==",
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.3.1.tgz",
+      "integrity": "sha512-iMNHTyxLbBlWIfGtabT157LH9DUx9X8+Y3oymFEuMj8HNc+rpE3dPFGFgHjpKfjeFDjLjYIAIhXPGvS2lKxL9g==",
       "dev": true,
       "peer": true
     },
     "@csstools/media-query-list-parser": {
-      "version": "2.1.9",
-      "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.9.tgz",
-      "integrity": "sha512-qqGuFfbn4rUmyOB0u8CVISIp5FfJ5GAR3mBrZ9/TKndHakdnm6pY0L/fbLcpPnrzwCyyTEZl1nUcXAYHEWneTA==",
+      "version": "2.1.11",
+      "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.11.tgz",
+      "integrity": "sha512-uox5MVhvNHqitPP+SynrB1o8oPxPMt2JLgp5ghJOWf54WGQ5OKu47efne49r1SWqs3wRP8xSWjnO9MBKxhB1dA==",
       "dev": true,
       "peer": true,
       "requires": {}
     },
     "@csstools/selector-specificity": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.3.tgz",
-      "integrity": "sha512-KEPNw4+WW5AVEIyzC80rTbWEUatTW2lXpN8+8ILC8PiPeWPjwUzrPZDIOZ2wwqDmeqOYTdSGyL3+vE5GC3FB3Q==",
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz",
+      "integrity": "sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA==",
       "dev": true,
       "peer": true,
       "requires": {}
@@ -39485,21 +39485,21 @@
       "version": "file:plugins/ui/src/js",
       "requires": {
         "@adobe/react-spectrum": "^3.34.1",
-        "@deephaven/chart": "^0.76.0",
-        "@deephaven/components": "^0.76.0",
-        "@deephaven/dashboard": "^0.76.0",
-        "@deephaven/dashboard-core-plugins": "^0.76.0",
-        "@deephaven/grid": "^0.76.0",
-        "@deephaven/icons": "^0.76.0",
-        "@deephaven/iris-grid": "^0.76.0",
-        "@deephaven/jsapi-bootstrap": "^0.76.0",
-        "@deephaven/jsapi-components": "^0.76.0",
-        "@deephaven/jsapi-types": "^1.0.0-dev0.34.1",
-        "@deephaven/log": "^0.76.0",
-        "@deephaven/plugin": "^0.76.0",
-        "@deephaven/react-hooks": "^0.76.0",
-        "@deephaven/redux": "^0.76.0",
-        "@deephaven/utils": "^0.76.0",
+        "@deephaven/chart": "^0.78.0",
+        "@deephaven/components": "^0.78.0",
+        "@deephaven/dashboard": "^0.78.0",
+        "@deephaven/dashboard-core-plugins": "^0.78.0",
+        "@deephaven/grid": "^0.78.0",
+        "@deephaven/icons": "^0.78.0",
+        "@deephaven/iris-grid": "^0.78.0",
+        "@deephaven/jsapi-bootstrap": "^0.78.0",
+        "@deephaven/jsapi-components": "^0.78.0",
+        "@deephaven/jsapi-types": "^1.0.0-dev0.34.2",
+        "@deephaven/log": "^0.78.0",
+        "@deephaven/plugin": "^0.78.0",
+        "@deephaven/react-hooks": "^0.78.0",
+        "@deephaven/redux": "^0.78.0",
+        "@deephaven/utils": "^0.78.0",
         "@fortawesome/react-fontawesome": "^0.2.0",
         "@react-types/shared": "^3.22.0",
         "@types/react": "^17.0.2",
@@ -39578,17 +39578,17 @@
           }
         },
         "@deephaven/chart": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/chart/-/chart-0.76.0.tgz",
-          "integrity": "sha512-n/ZjmJiHjgheu2C6qpsEQJJbh5yfAx8KDdYkt/PBVmFmS9zUNi30WzWyQdn/wJdddm8V878wPpBls1XCvEGl1g==",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/chart/-/chart-0.78.0.tgz",
+          "integrity": "sha512-G28KsnH+p3sot94UZP+LkwpyHPBUmtwbIyaIUQQxvEA8Hr5vJlN92IkMKCWEtPFRTiHhCrHYkgfQ54SN2XyNZQ==",
           "requires": {
-            "@deephaven/components": "^0.76.0",
-            "@deephaven/icons": "^0.76.0",
+            "@deephaven/components": "^0.78.0",
+            "@deephaven/icons": "^0.78.0",
             "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-            "@deephaven/jsapi-utils": "^0.76.0",
-            "@deephaven/log": "^0.76.0",
-            "@deephaven/react-hooks": "^0.76.0",
-            "@deephaven/utils": "^0.76.0",
+            "@deephaven/jsapi-utils": "^0.78.0",
+            "@deephaven/log": "^0.78.0",
+            "@deephaven/react-hooks": "^0.78.0",
+            "@deephaven/utils": "^0.78.0",
             "buffer": "^6.0.3",
             "fast-deep-equal": "^3.1.3",
             "lodash.debounce": "^4.0.8",
@@ -39608,15 +39608,15 @@
           }
         },
         "@deephaven/components": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/components/-/components-0.76.0.tgz",
-          "integrity": "sha512-s755ZS70bgxy4Rdj1d9uM8haNKIgBPgUuiHYQ0G8Ady6DCziTZPTKMVqDKtNuWR6mdwrsD7+fGkCIo0qdfXJ7Q==",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/components/-/components-0.78.0.tgz",
+          "integrity": "sha512-gx+yc1ZNRb4Pa3ptilGKBypPRNVoPrIqlDb7wJeZCKX+OnMZczFZLdEn12bh0s4Dnp460VNhegG2iM5W14GBHA==",
           "requires": {
             "@adobe/react-spectrum": "3.33.1",
-            "@deephaven/icons": "^0.76.0",
-            "@deephaven/log": "^0.76.0",
-            "@deephaven/react-hooks": "^0.76.0",
-            "@deephaven/utils": "^0.76.0",
+            "@deephaven/icons": "^0.78.0",
+            "@deephaven/log": "^0.78.0",
+            "@deephaven/react-hooks": "^0.78.0",
+            "@deephaven/utils": "^0.78.0",
             "@fortawesome/fontawesome-svg-core": "^6.2.1",
             "@fortawesome/react-fontawesome": "^0.2.0",
             "@react-spectrum/theme-default": "^3.5.1",
@@ -39640,19 +39640,19 @@
           }
         },
         "@deephaven/console": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/console/-/console-0.76.0.tgz",
-          "integrity": "sha512-Hd4sr9fHRq1Ob4pt9VYuInJ8tQDCpulU1HVKCjXv/oSukv/lfHmyqkgPVUF+VQ6o6x9kDdvP6ym05UYoI26xHA==",
-          "requires": {
-            "@deephaven/chart": "^0.76.0",
-            "@deephaven/components": "^0.76.0",
-            "@deephaven/icons": "^0.76.0",
-            "@deephaven/jsapi-bootstrap": "^0.76.0",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/console/-/console-0.78.0.tgz",
+          "integrity": "sha512-mWFRwGLUAEKpyedmDFhLnvm75GDYchPmM8e4X/iG1Zna72euhar3++EDwLcjO+7IBqqDN1mU27hVoGKp0FmnNg==",
+          "requires": {
+            "@deephaven/chart": "^0.78.0",
+            "@deephaven/components": "^0.78.0",
+            "@deephaven/icons": "^0.78.0",
+            "@deephaven/jsapi-bootstrap": "^0.78.0",
             "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-            "@deephaven/log": "^0.76.0",
-            "@deephaven/react-hooks": "^0.76.0",
-            "@deephaven/storage": "^0.76.0",
-            "@deephaven/utils": "^0.76.0",
+            "@deephaven/log": "^0.78.0",
+            "@deephaven/react-hooks": "^0.78.0",
+            "@deephaven/storage": "^0.78.0",
+            "@deephaven/utils": "^0.78.0",
             "@fortawesome/react-fontawesome": "^0.2.0",
             "classnames": "^2.3.1",
             "linkifyjs": "^4.1.0",
@@ -39676,16 +39676,16 @@
           }
         },
         "@deephaven/dashboard": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/dashboard/-/dashboard-0.76.0.tgz",
-          "integrity": "sha512-ThxUFELHTtdlXSpjLiQRxBxCf14z7Ipc+a2ELrprD9hoa7OvEl/nzQzuX1QTU378AOIbQhMKwDgmbVjDVXPx3g==",
-          "requires": {
-            "@deephaven/components": "^0.76.0",
-            "@deephaven/golden-layout": "^0.76.0",
-            "@deephaven/log": "^0.76.0",
-            "@deephaven/react-hooks": "^0.76.0",
-            "@deephaven/redux": "^0.76.0",
-            "@deephaven/utils": "^0.76.0",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/dashboard/-/dashboard-0.78.0.tgz",
+          "integrity": "sha512-PzlWlJrA8HiIYkpzQUjyDhPJr8vh0dvKQDX9eBM4QuWGoIk75Oo6oDUUJhmNyp8X7TilnR5rdOnOVosbs110vg==",
+          "requires": {
+            "@deephaven/components": "^0.78.0",
+            "@deephaven/golden-layout": "^0.78.0",
+            "@deephaven/log": "^0.78.0",
+            "@deephaven/react-hooks": "^0.78.0",
+            "@deephaven/redux": "^0.78.0",
+            "@deephaven/utils": "^0.78.0",
             "fast-deep-equal": "^3.1.3",
             "lodash.ismatch": "^4.1.1",
             "lodash.throttle": "^4.1.1",
@@ -39694,30 +39694,30 @@
           }
         },
         "@deephaven/dashboard-core-plugins": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/dashboard-core-plugins/-/dashboard-core-plugins-0.76.0.tgz",
-          "integrity": "sha512-nnXsYm55vKThBxKeILLP7fR66is805mJ1JOXxZcuG4rDzqMstFZyhQF4GtS9BuxsCJKZE/frlLedYVfCrUvp/g==",
-          "requires": {
-            "@deephaven/chart": "^0.76.0",
-            "@deephaven/components": "^0.76.0",
-            "@deephaven/console": "^0.76.0",
-            "@deephaven/dashboard": "^0.76.0",
-            "@deephaven/file-explorer": "^0.76.0",
-            "@deephaven/filters": "^0.76.0",
-            "@deephaven/golden-layout": "^0.76.0",
-            "@deephaven/grid": "^0.76.0",
-            "@deephaven/icons": "^0.76.0",
-            "@deephaven/iris-grid": "^0.76.0",
-            "@deephaven/jsapi-bootstrap": "^0.76.0",
-            "@deephaven/jsapi-components": "^0.76.0",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/dashboard-core-plugins/-/dashboard-core-plugins-0.78.0.tgz",
+          "integrity": "sha512-hssDMaXWRzj3KsGP4J+9Q+Ww6cW1NQBrmCvCVO3/R7DZZXCWohQFU1zPwKBxXEDk3BbGx4zc7imM/CsU5eB5cw==",
+          "requires": {
+            "@deephaven/chart": "^0.78.0",
+            "@deephaven/components": "^0.78.0",
+            "@deephaven/console": "^0.78.0",
+            "@deephaven/dashboard": "^0.78.0",
+            "@deephaven/file-explorer": "^0.78.0",
+            "@deephaven/filters": "^0.78.0",
+            "@deephaven/golden-layout": "^0.78.0",
+            "@deephaven/grid": "^0.78.0",
+            "@deephaven/icons": "^0.78.0",
+            "@deephaven/iris-grid": "^0.78.0",
+            "@deephaven/jsapi-bootstrap": "^0.78.0",
+            "@deephaven/jsapi-components": "^0.78.0",
             "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-            "@deephaven/jsapi-utils": "^0.76.0",
-            "@deephaven/log": "^0.76.0",
-            "@deephaven/plugin": "^0.76.0",
-            "@deephaven/react-hooks": "^0.76.0",
-            "@deephaven/redux": "^0.76.0",
-            "@deephaven/storage": "^0.76.0",
-            "@deephaven/utils": "^0.76.0",
+            "@deephaven/jsapi-utils": "^0.78.0",
+            "@deephaven/log": "^0.78.0",
+            "@deephaven/plugin": "^0.78.0",
+            "@deephaven/react-hooks": "^0.78.0",
+            "@deephaven/redux": "^0.78.0",
+            "@deephaven/storage": "^0.78.0",
+            "@deephaven/utils": "^0.78.0",
             "@fortawesome/react-fontawesome": "^0.2.0",
             "classnames": "^2.3.1",
             "fast-deep-equal": "^3.1.3",
@@ -39744,15 +39744,15 @@
           }
         },
         "@deephaven/file-explorer": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/file-explorer/-/file-explorer-0.76.0.tgz",
-          "integrity": "sha512-E6h1SA73NlhdQ6S6X4NyAe5acTgM6Lmrxu03lKgF9bUJVxExvuOzpSK1gZJqSksCzUnl5b/R06X/UJTxx65xpg==",
-          "requires": {
-            "@deephaven/components": "^0.76.0",
-            "@deephaven/icons": "^0.76.0",
-            "@deephaven/log": "^0.76.0",
-            "@deephaven/storage": "^0.76.0",
-            "@deephaven/utils": "^0.76.0",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/file-explorer/-/file-explorer-0.78.0.tgz",
+          "integrity": "sha512-qXeDu1FB08k6g+Y+3WElWO6Iq4TryqhWlh+sw4iLx6Ey7rkpZZkFvPpdtVldVoDixgCO+3zuUwWkAYE3rO0LGg==",
+          "requires": {
+            "@deephaven/components": "^0.78.0",
+            "@deephaven/icons": "^0.78.0",
+            "@deephaven/log": "^0.78.0",
+            "@deephaven/storage": "^0.78.0",
+            "@deephaven/utils": "^0.78.0",
             "@fortawesome/fontawesome-svg-core": "^6.2.1",
             "@fortawesome/react-fontawesome": "^0.2.0",
             "classnames": "^2.3.1",
@@ -39761,25 +39761,25 @@
           }
         },
         "@deephaven/filters": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/filters/-/filters-0.76.0.tgz",
-          "integrity": "sha512-rL2X88+0o2hw17L5i1lwG4y3zbDvpB8QUnF/2bd/B2dGVpixPeOw+p5mRWwmOfxU+U9wwtRypcbsy8Dv0QIH8w=="
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/filters/-/filters-0.78.0.tgz",
+          "integrity": "sha512-0C9udzidoX99nZXxQ5UOn+zRq2dAA16ic0FvnUBBJtKKwxfuAHPIq95H4b5VHgUh6lVOG6Q432xrRSinB7wrlw=="
         },
         "@deephaven/golden-layout": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/golden-layout/-/golden-layout-0.76.0.tgz",
-          "integrity": "sha512-rJ6Za+vclfMjXNakdH5F7tAlJZz1Ti+Enidzs8MakVSTTS7Q8cwGJX8Fpbdv5g9RBDjCbxvaarmX2CyecAR6Ng==",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/golden-layout/-/golden-layout-0.78.0.tgz",
+          "integrity": "sha512-TFnlrb/TIqJlUlRN3I4v5i/UMpLCbcVnD5+FVEkN1Rwnn9n/lfY4kgu3o0Cr56TLXllfdmfuEHWJY4pJpoFrlg==",
           "requires": {
-            "@deephaven/components": "^0.76.0",
+            "@deephaven/components": "^0.78.0",
             "jquery": "^3.6.0"
           }
         },
         "@deephaven/grid": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/grid/-/grid-0.76.0.tgz",
-          "integrity": "sha512-o0Or6LGga0Kaz8QtXjnkemdMvLftU7piranylTZdtbViDvnUohMmunnrE8oNzozdZhM99z/3rl3XznCrrYAEpg==",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/grid/-/grid-0.78.0.tgz",
+          "integrity": "sha512-w5qC8MhS/eUJzro8lkTHe2XOw+KAnhFgqkA1RFAr4A0/T0pUha7IJhdjH0JMBYOLaik6bMCPrQDiU8GvFyJPtA==",
           "requires": {
-            "@deephaven/utils": "^0.76.0",
+            "@deephaven/utils": "^0.78.0",
             "classnames": "^2.3.1",
             "color-convert": "^2.0.1",
             "event-target-shim": "^6.0.2",
@@ -39791,30 +39791,30 @@
           }
         },
         "@deephaven/icons": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/icons/-/icons-0.76.0.tgz",
-          "integrity": "sha512-5CXvy3QzXWPIUVAz/6pDLcXdhI1ylxQITcvmisjfcvLjw/0+X763yKELrFE1bnfI2RfNBDgmTCCRdC8argZy2A==",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/icons/-/icons-0.78.0.tgz",
+          "integrity": "sha512-gFR7hWghDJGgNphF9FpI0VnjCQrZ7FpQqFmxTzWTBSLWAxwRL1jR3glAR1/GbpfugE4BwuQARI+zRBU0xB4z1w==",
           "requires": {
             "@fortawesome/fontawesome-common-types": "^6.1.1"
           }
         },
         "@deephaven/iris-grid": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/iris-grid/-/iris-grid-0.76.0.tgz",
-          "integrity": "sha512-kx/ql5cpMViV4zxgvGWGDTcQx2Sq1OJB+vgdnAHYk+UldBnb4fQqQLTJqp7PZOKJURa5CcVXG4NKC3R+MNq8wA==",
-          "requires": {
-            "@deephaven/components": "^0.76.0",
-            "@deephaven/console": "^0.76.0",
-            "@deephaven/filters": "^0.76.0",
-            "@deephaven/grid": "^0.76.0",
-            "@deephaven/icons": "^0.76.0",
-            "@deephaven/jsapi-components": "^0.76.0",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/iris-grid/-/iris-grid-0.78.0.tgz",
+          "integrity": "sha512-kRjZC+aRjFlmQUSV3qcYL4BxdruxQKr4CY96FRC9pPgRztaLbU/pQHawuOp8aqydxkrpIR5eF1EVQIzPlR9piA==",
+          "requires": {
+            "@deephaven/components": "^0.78.0",
+            "@deephaven/console": "^0.78.0",
+            "@deephaven/filters": "^0.78.0",
+            "@deephaven/grid": "^0.78.0",
+            "@deephaven/icons": "^0.78.0",
+            "@deephaven/jsapi-components": "^0.78.0",
             "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-            "@deephaven/jsapi-utils": "^0.76.0",
-            "@deephaven/log": "^0.76.0",
-            "@deephaven/react-hooks": "^0.76.0",
-            "@deephaven/storage": "^0.76.0",
-            "@deephaven/utils": "^0.76.0",
+            "@deephaven/jsapi-utils": "^0.78.0",
+            "@deephaven/log": "^0.78.0",
+            "@deephaven/react-hooks": "^0.78.0",
+            "@deephaven/storage": "^0.78.0",
+            "@deephaven/utils": "^0.78.0",
             "@dnd-kit/core": "^6.1.0",
             "@dnd-kit/sortable": "^7.0.2",
             "@dnd-kit/utilities": "^3.2.2",
@@ -39841,14 +39841,14 @@
           }
         },
         "@deephaven/jsapi-bootstrap": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/jsapi-bootstrap/-/jsapi-bootstrap-0.76.0.tgz",
-          "integrity": "sha512-FdZyKcVaxSRaKtUL85VxX5apkm3+Sfu1yl/58iHHQVWXrU5RWjyav3U6vDZkg7yAvlP4/hC4MMfijK9LIZJSVg==",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/jsapi-bootstrap/-/jsapi-bootstrap-0.78.0.tgz",
+          "integrity": "sha512-XeQnxcCTLH6CdycDvo4dw/gpoqOnGYQQUSAlWqHjwiR0Ep+11JBkeIZRufsT1/GBfaQDho7GtZAJ0vlgADFSow==",
           "requires": {
-            "@deephaven/components": "^0.76.0",
+            "@deephaven/components": "^0.78.0",
             "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-            "@deephaven/log": "^0.76.0",
-            "@deephaven/react-hooks": "^0.76.0"
+            "@deephaven/log": "^0.78.0",
+            "@deephaven/react-hooks": "^0.78.0"
           },
           "dependencies": {
             "@deephaven/jsapi-types": {
@@ -39859,17 +39859,17 @@
           }
         },
         "@deephaven/jsapi-components": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/jsapi-components/-/jsapi-components-0.76.0.tgz",
-          "integrity": "sha512-Smk6RKYGEO5fmcXYRuj09ipebKeV9JnTpHwmgr2PiFKlZHaWNspTpQb1Ed0O1U+YYdoVF6ZaJSx8HJdi5GnSEg==",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/jsapi-components/-/jsapi-components-0.78.0.tgz",
+          "integrity": "sha512-VSQD7gvollbyQTbzW6jhIjxQplm6i5tvL/nyqY6P19W0bxCcraYGeVzoaL5XvTQo91GvQkqB6d1nltOytI3dmw==",
           "requires": {
-            "@deephaven/components": "^0.76.0",
-            "@deephaven/jsapi-bootstrap": "^0.76.0",
+            "@deephaven/components": "^0.78.0",
+            "@deephaven/jsapi-bootstrap": "^0.78.0",
             "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-            "@deephaven/jsapi-utils": "^0.76.0",
-            "@deephaven/log": "^0.76.0",
-            "@deephaven/react-hooks": "^0.76.0",
-            "@deephaven/utils": "^0.76.0",
+            "@deephaven/jsapi-utils": "^0.78.0",
+            "@deephaven/log": "^0.78.0",
+            "@deephaven/react-hooks": "^0.78.0",
+            "@deephaven/utils": "^0.78.0",
             "@types/js-cookie": "^3.0.3",
             "classnames": "^2.3.2",
             "js-cookie": "^3.0.5",
@@ -39885,19 +39885,19 @@
           }
         },
         "@deephaven/jsapi-types": {
-          "version": "1.0.0-dev0.34.1",
-          "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.1.tgz",
-          "integrity": "sha512-zOS96nGk9l/FDw84hoXQ1ESMDXBJSBck1hHFyMyxJzhJbbGVz2Lumr/ZRDy6IVVrC6TKuI6Ox+jBlXwRCl/U+w=="
+          "version": "1.0.0-dev0.34.2",
+          "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.2.tgz",
+          "integrity": "sha512-vHEP+93r2zsPPZs5hLT4I6eYw2pWqLqL7e4GSedX7K37P57+MzLtRR+eTp29o1BVwbuRpQfz6D/hSROzvw3XxQ=="
         },
         "@deephaven/jsapi-utils": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/jsapi-utils/-/jsapi-utils-0.76.0.tgz",
-          "integrity": "sha512-sYGLCcfS3NoLxF0btK9Q1Om7PIBumnwe0Jt+lvevLEWclikz9RQliHN1GcX5OHQYAtky8OB4TpofWL7mugsenA==",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/jsapi-utils/-/jsapi-utils-0.78.0.tgz",
+          "integrity": "sha512-VeCCns+nkKucbQYRGOak1MisEFQzI9bJQ+pAmqXBhVxJHj5NxqRiqnyoyp1VQig6zg1dzVqUj4h5ZTHXJ1QHdw==",
           "requires": {
-            "@deephaven/filters": "^0.76.0",
+            "@deephaven/filters": "^0.78.0",
             "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-            "@deephaven/log": "^0.76.0",
-            "@deephaven/utils": "^0.76.0",
+            "@deephaven/log": "^0.78.0",
+            "@deephaven/utils": "^0.78.0",
             "lodash.clamp": "^4.0.3",
             "shortid": "^2.2.16"
           },
@@ -39910,25 +39910,25 @@
           }
         },
         "@deephaven/log": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/log/-/log-0.76.0.tgz",
-          "integrity": "sha512-NCxpWHEcEmh2WXAj4IG4MFMMTmke+DD4JQg4yxH7wUn4YeZIMpXdbrWh20Jrf9q3NdKl2+BPb5IyaUIvKeJeSg==",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/log/-/log-0.78.0.tgz",
+          "integrity": "sha512-6GXUm1yjHTUWpi/p22sJ/F8yZ+l7I3jnfs9HlRjSErB7tb5jmpBXGAMTkL8oCuvvycLdx0QKcs17VJxV0saejw==",
           "requires": {
             "event-target-shim": "^6.0.2"
           }
         },
         "@deephaven/plugin": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/plugin/-/plugin-0.76.0.tgz",
-          "integrity": "sha512-bL64Ck7kHZIOk3b3N9gxy2UU4SZ2m/YV49737zl2V5nh6cm1VW9RVL6n4Tk/d8uVxMKom5EzKuMFeEZnBWWrDQ==",
-          "requires": {
-            "@deephaven/components": "^0.76.0",
-            "@deephaven/golden-layout": "^0.76.0",
-            "@deephaven/icons": "^0.76.0",
-            "@deephaven/iris-grid": "^0.76.0",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/plugin/-/plugin-0.78.0.tgz",
+          "integrity": "sha512-HLE4lEA3/8ICkhgrD4N0cH2gAkQd44yftHKr6a1UeZWp/A3fheSrawzB1hjQCXsmpNf82UGuhrFyILTyWcHxwQ==",
+          "requires": {
+            "@deephaven/components": "^0.78.0",
+            "@deephaven/golden-layout": "^0.78.0",
+            "@deephaven/icons": "^0.78.0",
+            "@deephaven/iris-grid": "^0.78.0",
             "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-            "@deephaven/log": "^0.76.0",
-            "@deephaven/react-hooks": "^0.76.0",
+            "@deephaven/log": "^0.78.0",
+            "@deephaven/react-hooks": "^0.78.0",
             "@fortawesome/fontawesome-common-types": "^6.1.1",
             "@fortawesome/react-fontawesome": "^0.2.0"
           },
@@ -39941,27 +39941,27 @@
           }
         },
         "@deephaven/react-hooks": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/react-hooks/-/react-hooks-0.76.0.tgz",
-          "integrity": "sha512-4ItEuVYDZSkz5thiqPwC1/j4UgfOn/QY1IxyEg7c2yh1CT4y7u2X8FfvaHa9Ed5uUlKSk3PTJFzTtcvXprzk5A==",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/react-hooks/-/react-hooks-0.78.0.tgz",
+          "integrity": "sha512-ic4KTEsykk58hF/RiRaTgNWKYphH6E8jZ/XiJ5sUHFLq+p/z8RCULyQhGcsAt3DwcwRtzHr97g7FvUPxyVE2WA==",
           "requires": {
             "@adobe/react-spectrum": "3.33.1",
-            "@deephaven/log": "^0.76.0",
-            "@deephaven/utils": "^0.76.0",
+            "@deephaven/log": "^0.78.0",
+            "@deephaven/utils": "^0.78.0",
             "lodash.debounce": "^4.0.8",
             "lodash.throttle": "^4.1.1",
             "shortid": "^2.2.16"
           }
         },
         "@deephaven/redux": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/redux/-/redux-0.76.0.tgz",
-          "integrity": "sha512-9UxbMb33wVJBf7i1nw2TT/ABU9WFM8sfXyfUqP57RfQ4FF7Tq/6n8Icv6SMiysE1T+aOs88NG8QeJXTsbB8rSQ==",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/redux/-/redux-0.78.0.tgz",
+          "integrity": "sha512-6G2hDFzkT5hc0P3ARtTfrReXnEJytjnpLFPI2BLR7elQl8A55boQFBOG2KnS0oNiCZaJ50oE4t/CyW0bUTOHcw==",
           "requires": {
             "@deephaven/jsapi-types": "1.0.0-dev0.34.0",
-            "@deephaven/jsapi-utils": "^0.76.0",
-            "@deephaven/log": "^0.76.0",
-            "@deephaven/plugin": "^0.76.0",
+            "@deephaven/jsapi-utils": "^0.78.0",
+            "@deephaven/log": "^0.78.0",
+            "@deephaven/plugin": "^0.78.0",
             "fast-deep-equal": "^3.1.3",
             "proxy-memoize": "^3.0.0",
             "redux-thunk": "2.4.1"
@@ -39975,19 +39975,19 @@
           }
         },
         "@deephaven/storage": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/storage/-/storage-0.76.0.tgz",
-          "integrity": "sha512-OJWPy6bs6U9gZvHzNVRkx3TozXRUdXAeZywOwJ1AjOAU1k2OPS9Rwcy8E4xzbSmvSYnlpB3esCzPsfEAj547lQ==",
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/storage/-/storage-0.78.0.tgz",
+          "integrity": "sha512-+2bSIa4iaNqjv6d4v8GBtzOQduJZEMSR2W4kT48yZrItsyABnYnGdPagszgnI6CjCSXNNjJUa6SQLy/jZfSQzQ==",
           "requires": {
-            "@deephaven/filters": "^0.76.0",
-            "@deephaven/log": "^0.76.0",
+            "@deephaven/filters": "^0.78.0",
+            "@deephaven/log": "^0.78.0",
             "lodash.throttle": "^4.1.1"
           }
         },
         "@deephaven/utils": {
-          "version": "0.76.0",
-          "resolved": "https://registry.npmjs.org/@deephaven/utils/-/utils-0.76.0.tgz",
-          "integrity": "sha512-QXZG3Rr31QOw09TO8fINwWAQOh6baFDuwbkog7WdR3lPMxrL8Y1bLSUYyNbsEHPWmBT6WxWOkWF9x7P+YCdzhA=="
+          "version": "0.78.0",
+          "resolved": "https://registry.npmjs.org/@deephaven/utils/-/utils-0.78.0.tgz",
+          "integrity": "sha512-uQdEiQYoPHDbF1jRlliJITmxDNAYpv1CT8H2vHnTRuGevP17WpudTdUOXjq1Jas4oggpj0Q60/bu0XbxgO3QTA=="
         },
         "buffer": {
           "version": "6.0.3",
@@ -40465,9 +40465,9 @@
       }
     },
     "@dual-bundle/import-meta-resolve": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz",
-      "integrity": "sha512-ZKXyJeFAzcpKM2kk8ipoGIPUqx9BX52omTGnfwjJvxOCaZTM2wtDK7zN0aIgPRbT9XYAlha0HtmZ+XKteuh0Gw==",
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
+      "integrity": "sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==",
       "dev": true,
       "peer": true
     },
@@ -49438,9 +49438,9 @@
       }
     },
     "enhanced-resolve": {
-      "version": "5.16.0",
-      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz",
-      "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==",
+      "version": "5.16.1",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz",
+      "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==",
       "dev": true,
       "peer": true,
       "requires": {
@@ -50902,9 +50902,9 @@
       }
     },
     "get-tsconfig": {
-      "version": "4.7.3",
-      "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz",
-      "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==",
+      "version": "4.7.5",
+      "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz",
+      "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==",
       "dev": true,
       "peer": true,
       "requires": {
diff --git a/plugins/ui/docs/README.md b/plugins/ui/docs/README.md
index 5d3abe99d..8c8e5c2af 100644
--- a/plugins/ui/docs/README.md
+++ b/plugins/ui/docs/README.md
@@ -203,6 +203,52 @@ my_checkbox = ui_checkbox()
 
 ![Checkbox](_assets/checkbox.png)
 
+## ActionGroup (string values)
+An ActionGroup is a grouping of ActionButtons that are related to one another.
+
+```python
+@ui.component
+def ui_action_group():
+    [action, on_action] = ui.use_state()
+
+    return ui.flex(
+        ui.action_group(
+            "Aaa",
+            "Bbb",
+            "Ccc",
+            on_action=on_action,
+        ),
+        ui.text(action),
+        direction="column",
+    )
+
+
+my_action_group = ui_action_group()
+```
+
+## ActionMenu (string values)
+ActionMenu combines an ActionButton with a Menu for simple "more actions" use cases.
+
+```python
+@ui.component
+def ui_action_menu():
+    [action, on_action] = ui.use_state()
+
+    return ui.flex(
+        ui.action_menu(
+            "Aaa",
+            "Bbb",
+            "Ccc",
+            on_action=on_action,
+        ),
+        ui.text(action),
+        direction="column",
+    )
+
+
+my_action_menu = ui_action_menu()
+```
+
 ## Picker (string values)
 
 The `ui.picker` component can be used to select from a list of items. Here's a basic example for selecting from a list of string values and displaying the selected key in a text field.
@@ -463,6 +509,113 @@ lv_table_source = ui_list_view_table_source()
 
 ![Use a list view to select from a table source](_assets/lv_table_source.png)
 
+## ListView (list action group)
+
+A list view can take a `list_action_group` as its `actions` prop.
+
+```python
+from deephaven import time_table, ui
+import datetime
+
+# Ticking table with initial row count of 200 that adds a row every second
+initial_row_count = 200
+_column_types = time_table(
+    "PT1S",
+    start_time=datetime.datetime.now() - datetime.timedelta(seconds=initial_row_count),
+).update(
+    [
+        "Id=new String(`key-`+i)",
+        "Display=new String(`Display `+i)",
+    ]
+)
+
+# `ui.list_view`` with `ui.list_action_group` actions
+@ui.component
+def ui_list_view_action_group():
+    value, set_value = ui.use_state(["key-2", "key-4", "key-5"])
+
+    action_item_keys, set_action_item_idx = ui.use_state(["", ""])
+    on_action = ui.use_callback(
+        lambda action_key, item_key: set_action_item_idx([action_key, str(item_key)]),
+        [],
+    )
+
+    lv = ui.list_view(
+        _column_types,
+        key_column="Id",
+        label_column="Display",
+        aria_label="List View",
+        on_change=set_value,
+        selected_keys=value,
+        actions=ui.list_action_group(
+            "Edit",
+            "Delete",
+            on_action=on_action,
+        ),
+    )
+
+    text_selection = ui.text("Selection: " + ", ".join(map(str, value)))
+    text_action = ui.text("Action: " + " ".join(map(str, action_item_keys)))
+
+    return lv, text_selection, text_action
+
+
+my_list_view_action_group = ui_list_view_action_group()
+```
+
+## ListView (list action menu)
+A list view can take a `list_action_menu` as its `actions` prop.
+
+```python
+from deephaven import time_table, ui
+import datetime
+
+# Ticking table with initial row count of 200 that adds a row every second
+initial_row_count = 200
+_column_types = time_table(
+    "PT1S",
+    start_time=datetime.datetime.now() - datetime.timedelta(seconds=initial_row_count),
+).update(
+    [
+        "Id=new String(`key-`+i)",
+        "Display=new String(`Display `+i)",
+    ]
+)
+
+# `ui.list_view`` with `ui.list_action_menu` actions
+@ui.component
+def ui_list_view_action_menu():
+    value, set_value = ui.use_state(["key-2", "key-4", "key-5"])
+
+    action_item_keys, set_action_item_idx = ui.use_state(["", ""])
+    on_action = ui.use_callback(
+        lambda action_key, item_key: set_action_item_idx([action_key, str(item_key)]),
+        [],
+    )
+
+    lv = ui.list_view(
+        _column_types,
+        key_column="Id",
+        label_column="Display",
+        aria_label="List View",
+        on_change=set_value,
+        selected_keys=value,
+        actions=ui.list_action_menu(
+            "Edit",
+            "Delete",
+            on_action=on_action,
+        ),
+    )
+
+    text_selection = ui.text("Selection: " + ", ".join(map(str, value)))
+    text_action = ui.text("Action: " + " ".join(map(str, action_item_keys)))
+
+    return lv, text_selection, text_action
+
+
+my_list_view_action_menu = ui_list_view_action_menu()
+```
+
 ## Form (two variables)
 
 You can have state with multiple different variables in one component. This example creates a [text field](https://react-spectrum.adobe.com/react-spectrum/TextField.html) and a [slider](https://react-spectrum.adobe.com/react-spectrum/Slider.html), and we display the values of both of them.
diff --git a/plugins/ui/src/deephaven/ui/components/__init__.py b/plugins/ui/src/deephaven/ui/components/__init__.py
index 09efe6596..39b43c0a1 100644
--- a/plugins/ui/src/deephaven/ui/components/__init__.py
+++ b/plugins/ui/src/deephaven/ui/components/__init__.py
@@ -1,3 +1,5 @@
+from .action_group import action_group
+from .action_menu import action_menu
 from .icon import icon
 from .make_component import make_component as component
 from .fragment import fragment
@@ -21,6 +23,8 @@
 
 __all__ = [
     "action_button",
+    "action_group",
+    "action_menu",
     "button",
     "button_group",
     "checkbox",
diff --git a/plugins/ui/src/deephaven/ui/components/action_group.py b/plugins/ui/src/deephaven/ui/components/action_group.py
new file mode 100644
index 000000000..c2093d587
--- /dev/null
+++ b/plugins/ui/src/deephaven/ui/components/action_group.py
@@ -0,0 +1,13 @@
+from ..elements import BaseElement
+
+
+# TODO: pydocs for action_group #481
+def action_group(*children, **props):
+    """
+    An ActionGroup is a grouping of ActionButtons that are related to one another.
+
+    Args:
+        children: A list of Item or primitive elements.
+        **props: Any other ActionGroup prop.
+    """
+    return BaseElement(f"deephaven.ui.components.ActionGroup", *children, **props)
diff --git a/plugins/ui/src/deephaven/ui/components/action_menu.py b/plugins/ui/src/deephaven/ui/components/action_menu.py
new file mode 100644
index 000000000..6a451996e
--- /dev/null
+++ b/plugins/ui/src/deephaven/ui/components/action_menu.py
@@ -0,0 +1,13 @@
+from ..elements import BaseElement
+
+
+# TODO: pydocs for action_menu #482
+def action_menu(*children, **props):
+    """
+    ActionMenu combines an ActionButton with a Menu for simple "more actions" use cases.
+
+    Args:
+        children: A list of Item or primitive elements.
+        **props: Any other ActionMenu prop.
+    """
+    return BaseElement(f"deephaven.ui.components.ActionMenu", *children, **props)
diff --git a/plugins/ui/src/deephaven/ui/components/list_action_group.py b/plugins/ui/src/deephaven/ui/components/list_action_group.py
index a2a815eba..b5d2358f1 100644
--- a/plugins/ui/src/deephaven/ui/components/list_action_group.py
+++ b/plugins/ui/src/deephaven/ui/components/list_action_group.py
@@ -11,6 +11,7 @@
 ListActionGroupElement = Element
 
 
+# TODO: pydocs for list_action_group #483
 def list_action_group(
     *children: ActionGroupItem,
     on_action: Callable[[ActionKey, Key], None] | None = None,
diff --git a/plugins/ui/src/deephaven/ui/components/list_action_menu.py b/plugins/ui/src/deephaven/ui/components/list_action_menu.py
index 7a9eb0af5..4c103299d 100644
--- a/plugins/ui/src/deephaven/ui/components/list_action_menu.py
+++ b/plugins/ui/src/deephaven/ui/components/list_action_menu.py
@@ -12,6 +12,7 @@
 ListActionMenuElement = Element
 
 
+# TODO: pydocs for list_action_menu #484
 def list_action_menu(
     *children: ActionMenuItem,
     on_action: Callable[[ActionKey, Key], None] | None = None,
diff --git a/plugins/ui/src/js/package.json b/plugins/ui/src/js/package.json
index bfa65f588..3e2699d21 100644
--- a/plugins/ui/src/js/package.json
+++ b/plugins/ui/src/js/package.json
@@ -42,21 +42,21 @@
   },
   "dependencies": {
     "@adobe/react-spectrum": "^3.34.1",
-    "@deephaven/chart": "^0.76.0",
-    "@deephaven/components": "^0.76.0",
-    "@deephaven/dashboard": "^0.76.0",
-    "@deephaven/dashboard-core-plugins": "^0.76.0",
-    "@deephaven/grid": "^0.76.0",
-    "@deephaven/icons": "^0.76.0",
-    "@deephaven/iris-grid": "^0.76.0",
-    "@deephaven/jsapi-bootstrap": "^0.76.0",
-    "@deephaven/jsapi-components": "^0.76.0",
-    "@deephaven/jsapi-types": "^1.0.0-dev0.34.1",
-    "@deephaven/log": "^0.76.0",
-    "@deephaven/plugin": "^0.76.0",
-    "@deephaven/react-hooks": "^0.76.0",
-    "@deephaven/redux": "^0.76.0",
-    "@deephaven/utils": "^0.76.0",
+    "@deephaven/chart": "^0.78.0",
+    "@deephaven/components": "^0.78.0",
+    "@deephaven/dashboard": "^0.78.0",
+    "@deephaven/dashboard-core-plugins": "^0.78.0",
+    "@deephaven/grid": "^0.78.0",
+    "@deephaven/icons": "^0.78.0",
+    "@deephaven/iris-grid": "^0.78.0",
+    "@deephaven/jsapi-bootstrap": "^0.78.0",
+    "@deephaven/jsapi-components": "^0.78.0",
+    "@deephaven/jsapi-types": "^1.0.0-dev0.34.2",
+    "@deephaven/log": "^0.78.0",
+    "@deephaven/plugin": "^0.78.0",
+    "@deephaven/react-hooks": "^0.78.0",
+    "@deephaven/redux": "^0.78.0",
+    "@deephaven/utils": "^0.78.0",
     "@fortawesome/react-fontawesome": "^0.2.0",
     "@react-types/shared": "^3.22.0",
     "classnames": "^2.5.1",
diff --git a/plugins/ui/src/js/src/elements/ActionGroup.tsx b/plugins/ui/src/js/src/elements/ActionGroup.tsx
new file mode 100644
index 000000000..4b435b20c
--- /dev/null
+++ b/plugins/ui/src/js/src/elements/ActionGroup.tsx
@@ -0,0 +1,39 @@
+import {
+  ActionGroup as DHActionGroup,
+  ActionGroupProps as DHActionGroupProps,
+} from '@deephaven/components';
+import {
+  SerializedSelectionProps,
+  useSelectionProps,
+} from './spectrum/useSelectionProps';
+
+export type SerializedActionGroupProps<T> = Omit<
+  DHActionGroupProps<T>,
+  'selectionMode' | 'onChange' | 'onSelectionChange'
+> &
+  SerializedSelectionProps;
+
+function ActionGroup<T>({
+  selectionMode: selectionModeMaybeUppercase,
+  onChange: serializedOnChange,
+  onSelectionChange: serializedOnSelectionChange,
+  ...props
+}: SerializedActionGroupProps<T>): JSX.Element {
+  const { selectionMode, onChange, onSelectionChange } = useSelectionProps({
+    selectionMode: selectionModeMaybeUppercase,
+    onChange: serializedOnChange,
+    onSelectionChange: serializedOnSelectionChange,
+  });
+
+  return (
+    <DHActionGroup
+      // eslint-disable-next-line react/jsx-props-no-spreading
+      {...props}
+      selectionMode={selectionMode}
+      onChange={onChange}
+      onSelectionChange={onSelectionChange}
+    />
+  );
+}
+
+export default ActionGroup;
diff --git a/plugins/ui/src/js/src/elements/ElementConstants.ts b/plugins/ui/src/js/src/elements/ElementConstants.ts
index 3cacf5b6a..24383121a 100644
--- a/plugins/ui/src/js/src/elements/ElementConstants.ts
+++ b/plugins/ui/src/js/src/elements/ElementConstants.ts
@@ -5,39 +5,43 @@ import type * as icons from '@deephaven/icons';
 export const UI_COMPONENTS_NAMESPACE = 'deephaven.ui.components';
 export const UI_ELEMENTS_NAMESPACE = 'deephaven.ui.elements';
 
-/** Table */
-export const UITABLE_ELEMENT_TYPE = `${UI_ELEMENTS_NAMESPACE}.UITable` as const;
-export type UITableElementName = typeof UITABLE_ELEMENT_TYPE;
-
-/** Layout */
-export const PANEL_ELEMENT_NAME = `${UI_COMPONENTS_NAMESPACE}.Panel` as const;
-export const ROW_ELEMENT_NAME = `${UI_COMPONENTS_NAMESPACE}.Row` as const;
-export const COLUMN_ELEMENT_NAME = `${UI_COMPONENTS_NAMESPACE}.Column` as const;
-export const STACK_ELEMENT_NAME = `${UI_COMPONENTS_NAMESPACE}.Stack` as const;
-export const DASHBOARD_ELEMENT_NAME =
-  `${UI_COMPONENTS_NAMESPACE}.Dashboard` as const;
-
-export type PanelElementType = typeof PANEL_ELEMENT_NAME;
-export type RowElementType = typeof ROW_ELEMENT_NAME;
-export type ColumnElementType = typeof COLUMN_ELEMENT_NAME;
-export type StackElementType = typeof STACK_ELEMENT_NAME;
-export type DashboardElementType = typeof DASHBOARD_ELEMENT_NAME;
-
-/** Icons */
-export const ICON_ELEMENT_TYPE_PREFIX = 'deephaven.ui.icons.';
-export type IconElementName =
-  `${typeof ICON_ELEMENT_TYPE_PREFIX}${keyof typeof icons}`;
-
-/** HTML */
-export const HTML_ELEMENT_NAME_PREFIX = 'deephaven.ui.html.';
-export type HTMLElementType =
-  `${typeof HTML_ELEMENT_NAME_PREFIX}${keyof ReactHTML}`;
-
-/** Specific Components */
-export const FRAGMENT_ELEMENT_NAME =
-  `${UI_COMPONENTS_NAMESPACE}.Fragment` as const;
-export const ITEM_ELEMENT_NAME = `${UI_COMPONENTS_NAMESPACE}.Item` as const;
-export const LIST_VIEW_NAME = `${UI_COMPONENTS_NAMESPACE}.ListView` as const;
-export const PICKER_ELEMENT_NAME = `${UI_COMPONENTS_NAMESPACE}.Picker` as const;
-export const SECTION_ELEMENT_NAME =
-  `${UI_COMPONENTS_NAMESPACE}.Section` as const;
+const uiComponentName = <T extends string>(name: T) =>
+  `${UI_COMPONENTS_NAMESPACE}.${name}` as const;
+
+const uiElementName = <T extends string>(name: T) =>
+  `${UI_ELEMENTS_NAMESPACE}.${name}` as const;
+
+export const ELEMENT_NAME = {
+  /** Elements */
+  uiTable: uiElementName('UITable'),
+
+  /** Layout Components */
+  column: uiComponentName('Column'),
+  dashboard: uiComponentName('Dashboard'),
+  panel: uiComponentName('Panel'),
+  row: uiComponentName('Row'),
+  stack: uiComponentName('Stack'),
+
+  /** Other Components */
+  actionGroup: uiComponentName('ActionGroup'),
+  actionMenu: uiComponentName('ActionMenu'),
+  fragment: uiComponentName('Fragment'),
+  item: uiComponentName('Item'),
+  listActionGroup: uiComponentName('ListActionGroup'),
+  listActionMenu: uiComponentName('ListActionMenu'),
+  listView: uiComponentName('ListView'),
+  picker: uiComponentName('Picker'),
+  section: uiComponentName('Section'),
+} as const;
+
+export type ElementName = typeof ELEMENT_NAME;
+
+export const ELEMENT_PREFIX = {
+  icon: 'deephaven.ui.icons.' as const,
+  html: 'deephaven.ui.html.' as const,
+};
+
+export type ElementPrefix = {
+  icon: `${typeof ELEMENT_PREFIX.icon}${keyof typeof icons}`;
+  html: `${typeof ELEMENT_PREFIX.html}${keyof ReactHTML}`;
+};
diff --git a/plugins/ui/src/js/src/elements/ElementUtils.tsx b/plugins/ui/src/js/src/elements/ElementUtils.tsx
index 95b91ff1e..b5fa2759e 100644
--- a/plugins/ui/src/js/src/elements/ElementUtils.tsx
+++ b/plugins/ui/src/js/src/elements/ElementUtils.tsx
@@ -1,7 +1,7 @@
 import React from 'react';
 import { Text } from '@deephaven/components';
 import type { dh } from '@deephaven/jsapi-types';
-import { ITEM_ELEMENT_NAME } from './ElementConstants';
+import { ELEMENT_NAME } from './ElementConstants';
 import ObjectView from './ObjectView';
 
 export const CALLABLE_KEY = '__dhCbid';
@@ -159,7 +159,7 @@ export function wrapElementChildren(element: ElementNode): ElementNode {
 
   const newProps = { ...element.props };
 
-  const isItemElement = isElementNode(element, ITEM_ELEMENT_NAME);
+  const isItemElement = isElementNode(element, ELEMENT_NAME.item);
 
   // We will be wrapping all primitive `Item` children in a `Text` element to
   // ensure proper layout. Since `Item` components require a `textValue` prop
diff --git a/plugins/ui/src/js/src/elements/HTMLElementUtils.ts b/plugins/ui/src/js/src/elements/HTMLElementUtils.ts
index d7926c48c..9530a0e69 100644
--- a/plugins/ui/src/js/src/elements/HTMLElementUtils.ts
+++ b/plugins/ui/src/js/src/elements/HTMLElementUtils.ts
@@ -1,5 +1,5 @@
 import { ReactHTML } from 'react';
-import { HTMLElementType, HTML_ELEMENT_NAME_PREFIX } from './ElementConstants';
+import { ELEMENT_PREFIX, ElementPrefix } from './ElementConstants';
 import { ELEMENT_KEY, ElementNode, isElementNode } from './ElementUtils';
 
 /**
@@ -8,12 +8,12 @@ import { ELEMENT_KEY, ElementNode, isElementNode } from './ElementUtils';
  * For example, `deephaven.ui.html.div` would be rendered as `<div>`.
  * The props are passed directly to the HTML element as attributes.
  */
-export type HTMLElementNode = ElementNode<HTMLElementType>;
+export type HTMLElementNode = ElementNode<ElementPrefix['html']>;
 
 export function isHTMLElementNode(obj: unknown): obj is HTMLElementNode {
   return (
     isElementNode(obj) &&
-    (obj as HTMLElementNode)[ELEMENT_KEY].startsWith(HTML_ELEMENT_NAME_PREFIX)
+    (obj as HTMLElementNode)[ELEMENT_KEY].startsWith(ELEMENT_PREFIX.html)
   );
 }
 
@@ -22,6 +22,6 @@ export function isHTMLElementNode(obj: unknown): obj is HTMLElementNode {
  * @param name Name of the element
  * @returns The HTML tag name for the element
  */
-export function getHTMLTag(name: HTMLElementType): keyof ReactHTML {
-  return name.substring(HTML_ELEMENT_NAME_PREFIX.length) as keyof ReactHTML;
+export function getHTMLTag(name: ElementPrefix['html']): keyof ReactHTML {
+  return name.substring(ELEMENT_PREFIX.html.length) as keyof ReactHTML;
 }
diff --git a/plugins/ui/src/js/src/elements/IconElementUtils.ts b/plugins/ui/src/js/src/elements/IconElementUtils.ts
index 871d23a25..1bdfe0974 100644
--- a/plugins/ui/src/js/src/elements/IconElementUtils.ts
+++ b/plugins/ui/src/js/src/elements/IconElementUtils.ts
@@ -1,5 +1,5 @@
 import * as icons from '@deephaven/icons';
-import { IconElementName, ICON_ELEMENT_TYPE_PREFIX } from './ElementConstants';
+import { ELEMENT_PREFIX, ElementPrefix } from './ElementConstants';
 import { ELEMENT_KEY, ElementNode, isElementNode } from './ElementUtils';
 
 /**
@@ -8,17 +8,17 @@ import { ELEMENT_KEY, ElementNode, isElementNode } from './ElementUtils';
  * For example, `deephaven.ui.icons.vsBell` will render the icon named `vsBell`.
  * The props are passed directly to the icon component.
  */
-export type IconElementNode = ElementNode<IconElementName>;
+export type IconElementNode = ElementNode<ElementPrefix['icon']>;
 
 export function isIconElementNode(obj: unknown): obj is IconElementNode {
   return (
     isElementNode(obj) &&
-    (obj as IconElementNode)[ELEMENT_KEY].startsWith(ICON_ELEMENT_TYPE_PREFIX)
+    (obj as IconElementNode)[ELEMENT_KEY].startsWith(ELEMENT_PREFIX.icon)
   );
 }
 
-export function getIcon(name: IconElementName): icons.IconDefinition {
+export function getIcon(name: ElementPrefix['icon']): icons.IconDefinition {
   return icons[
-    name.substring(ICON_ELEMENT_TYPE_PREFIX.length) as keyof typeof icons
+    name.substring(ELEMENT_PREFIX.icon.length) as keyof typeof icons
   ] as icons.IconDefinition;
 }
diff --git a/plugins/ui/src/js/src/elements/SpectrumElementUtils.ts b/plugins/ui/src/js/src/elements/SpectrumElementUtils.ts
index 6a07ce442..d070f9d04 100644
--- a/plugins/ui/src/js/src/elements/SpectrumElementUtils.ts
+++ b/plugins/ui/src/js/src/elements/SpectrumElementUtils.ts
@@ -1,6 +1,8 @@
 import { ButtonGroup, Checkbox } from '@adobe/react-spectrum';
 import { ValueOf } from '@deephaven/utils';
 import {
+  ActionGroup,
+  ActionMenu,
   Content,
   ContextualHelp,
   Grid,
@@ -8,6 +10,8 @@ import {
   Icon,
   Item,
   IllustratedMessage,
+  ListActionGroup,
+  ListActionMenu,
   NumberField,
   Switch,
   Tabs,
@@ -32,6 +36,8 @@ export const SPECTRUM_ELEMENT_TYPE_PREFIX = 'deephaven.ui.spectrum.';
 
 export const SpectrumSupportedTypes = {
   ActionButton,
+  ActionGroup,
+  ActionMenu,
   Button,
   ButtonGroup,
   Checkbox,
@@ -43,6 +49,8 @@ export const SpectrumSupportedTypes = {
   Heading,
   Icon,
   IllustratedMessage,
+  ListActionGroup,
+  ListActionMenu,
   NumberField,
   Item,
   RangeSlider,
diff --git a/plugins/ui/src/js/src/elements/UITableUtils.tsx b/plugins/ui/src/js/src/elements/UITableUtils.tsx
index b86dc7bb4..e9a6d48fb 100644
--- a/plugins/ui/src/js/src/elements/UITableUtils.tsx
+++ b/plugins/ui/src/js/src/elements/UITableUtils.tsx
@@ -1,7 +1,7 @@
 import type { dh } from '@deephaven/jsapi-types';
 import { ColumnName, DehydratedSort, RowIndex } from '@deephaven/iris-grid';
 import { ELEMENT_KEY, ElementNode, isElementNode } from './ElementUtils';
-import { UITableElementName, UITABLE_ELEMENT_TYPE } from './ElementConstants';
+import { ELEMENT_NAME, ElementName } from './ElementConstants';
 
 export type CellData = {
   type: string;
@@ -37,12 +37,12 @@ export interface UITableProps {
 }
 
 export type UITableNode = Required<
-  ElementNode<UITableElementName, UITableProps>
+  ElementNode<ElementName['uiTable'], UITableProps>
 >;
 
 export function isUITable(obj: unknown): obj is UITableNode {
   return (
     isElementNode(obj) &&
-    (obj as UITableNode)[ELEMENT_KEY] === UITABLE_ELEMENT_TYPE
+    (obj as UITableNode)[ELEMENT_KEY] === ELEMENT_NAME.uiTable
   );
 }
diff --git a/plugins/ui/src/js/src/elements/spectrum/useSelectionEventCallback.ts b/plugins/ui/src/js/src/elements/spectrum/useSelectionEventCallback.ts
deleted file mode 100644
index c64c95226..000000000
--- a/plugins/ui/src/js/src/elements/spectrum/useSelectionEventCallback.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import type { ItemKey, ItemSelection } from '@deephaven/components';
-import { useCallback } from 'react';
-
-export type SerializedSelection = 'all' | ItemKey[];
-
-export type SerializedSelectionEventCallback = (
-  event: SerializedSelection
-) => void;
-
-/**
- * Selection can be 'all' or a Set of keys. If it is a Set, serialize it as an
- * array.
- * @param selection Selection to serialize
- * @returns Serialized selection
- */
-export function serializeSelectionEvent(
-  selection: ItemSelection
-): SerializedSelection {
-  if (selection instanceof Set) {
-    return [...selection];
-  }
-
-  return selection;
-}
-
-/**
- * Get a callback function that can be passed to selection change event handler
- * props of Spectrum components.
- * @param callback Callback to be called with the serialized selection
- * @returns A callback to be passed into the Spectrum component that transforms
- * the selection and calls the provided callback
- */
-export function useSelectionEventCallback(
-  callback?: SerializedSelectionEventCallback
-): (selection: ItemSelection) => void {
-  return useCallback(
-    (selection: ItemSelection) => {
-      callback?.(serializeSelectionEvent(selection));
-    },
-    [callback]
-  );
-}
diff --git a/plugins/ui/src/js/src/elements/spectrum/useSelectionProps.ts b/plugins/ui/src/js/src/elements/spectrum/useSelectionProps.ts
new file mode 100644
index 000000000..b802626b2
--- /dev/null
+++ b/plugins/ui/src/js/src/elements/spectrum/useSelectionProps.ts
@@ -0,0 +1,97 @@
+import { useCallback } from 'react';
+import type { SelectionMode } from '@react-types/shared';
+import type { ItemKey, ItemSelection } from '@deephaven/components';
+
+export type SerializedSelection = 'all' | ItemKey[];
+
+export type SerializedSelectionEventCallback = (
+  event: SerializedSelection
+) => void;
+
+/**
+ * Serialized selection props we receive from dh ui.
+ */
+export interface SerializedSelectionProps {
+  selectionMode?: SelectionMode | Uppercase<SelectionMode>;
+
+  /** Handler that is called when selection changes */
+  onChange?: SerializedSelectionEventCallback;
+
+  /**
+   * Handler that is called when the selection changes.
+   * @deprecated Use `onChange` instead
+   */
+  onSelectionChange?: SerializedSelectionEventCallback;
+}
+
+/**
+ * Selection props that can be passed to DHC components.
+ */
+export interface SelectionProps {
+  selectionMode?: SelectionMode;
+  onChange?: (selection: ItemSelection) => void;
+  onSelectionChange?: (selection: ItemSelection) => void;
+}
+
+/**
+ * Selection can be 'all' or a Set of keys. If it is a Set, serialize it as an
+ * array.
+ * @param selection Selection to serialize
+ * @returns Serialized selection
+ */
+export function serializeSelectionEvent(
+  selection: ItemSelection
+): SerializedSelection {
+  if (selection instanceof Set) {
+    return [...selection];
+  }
+
+  return selection;
+}
+
+/**
+ * Get a callback function that can be passed to selection change event handler
+ * props of Spectrum components.
+ * @param callback Callback to be called with the serialized selection
+ * @returns A callback to be passed into the Spectrum component that transforms
+ * the selection and calls the provided callback
+ */
+export function useSelectionEventCallback(
+  callback?: SerializedSelectionEventCallback
+): (selection: ItemSelection) => void {
+  return useCallback(
+    (selection: ItemSelection) => {
+      callback?.(serializeSelectionEvent(selection));
+    },
+    [callback]
+  );
+}
+
+/**
+ * Converts serialized selection props to props that can be passed to DHC
+ * components.
+ * @param selectionMode selection mode (may be uppercase)
+ * @param onChange serialized selection change event handler
+ * @param onSelectionChange serialized selection change event handler
+ * @returns selection props
+ */
+export function useSelectionProps({
+  selectionMode: selectionModeMaybeUppercase,
+  onChange: serializedOnChange,
+  onSelectionChange: serializedOnSelectionChange,
+}: SerializedSelectionProps): SelectionProps {
+  const selectionModeLc =
+    selectionModeMaybeUppercase?.toLowerCase() as SelectionMode;
+
+  const onChange = useSelectionEventCallback(serializedOnChange);
+  const onSelectionChange = useSelectionEventCallback(
+    serializedOnSelectionChange
+  );
+
+  return {
+    selectionMode: selectionModeLc,
+    onChange: serializedOnChange == null ? undefined : onChange,
+    onSelectionChange:
+      serializedOnSelectionChange == null ? undefined : onSelectionChange,
+  };
+}
diff --git a/plugins/ui/src/js/src/elements/useListViewProps.ts b/plugins/ui/src/js/src/elements/useListViewProps.ts
index 8b976f7b0..467030437 100644
--- a/plugins/ui/src/js/src/elements/useListViewProps.ts
+++ b/plugins/ui/src/js/src/elements/useListViewProps.ts
@@ -4,9 +4,9 @@ import { ListViewProps as DHListViewProps } from '@deephaven/components';
 import { ListViewProps as DHListViewJSApiProps } from '@deephaven/jsapi-components';
 import { ObjectViewProps } from './ObjectView';
 import {
-  SerializedSelectionEventCallback,
-  useSelectionEventCallback,
-} from './spectrum/useSelectionEventCallback';
+  SerializedSelectionProps,
+  useSelectionProps,
+} from './spectrum/useSelectionProps';
 
 type Density = Required<DHListViewProps>['density'];
 
@@ -25,22 +25,11 @@ type WrappedDHListViewProps = Omit<
   selectionMode?: SelectionMode | Uppercase<SelectionMode>;
 };
 
-export interface SerializedListViewEventProps {
-  /** Handler that is called when selection changes */
-  onChange?: SerializedSelectionEventCallback;
-
-  /**
-   * Handler that is called when the selection changes.
-   * @deprecated Use `onChange` instead
-   */
-  onSelectionChange?: SerializedSelectionEventCallback;
-}
-
 export type SerializedListViewProps = (
   | WrappedDHListViewProps
   | WrappedDHListViewJSApiProps
 ) &
-  SerializedListViewEventProps;
+  SerializedSelectionProps;
 
 /**
  * Wrap ListView props with the appropriate serialized event callbacks.
@@ -49,24 +38,24 @@ export type SerializedListViewProps = (
  */
 export function useListViewProps({
   density,
-  selectionMode,
-  onChange,
-  onSelectionChange,
+  selectionMode: selectionModeMaybeUppercase,
+  onChange: serializedOnChange,
+  onSelectionChange: serializedOnSelectionChange,
   ...otherProps
 }: SerializedListViewProps): DHListViewProps | WrappedDHListViewJSApiProps {
   const densityLc = density?.toLowerCase() as Density;
-  const selectionModeLc = selectionMode?.toLowerCase() as SelectionMode;
 
-  const serializedOnChange = useSelectionEventCallback(onChange);
-  const serializedOnSelectionChange =
-    useSelectionEventCallback(onSelectionChange);
+  const { selectionMode, onChange, onSelectionChange } = useSelectionProps({
+    selectionMode: selectionModeMaybeUppercase,
+    onChange: serializedOnChange,
+    onSelectionChange: serializedOnSelectionChange,
+  });
 
   return {
     density: densityLc,
-    selectionMode: selectionModeLc,
-    onChange: onChange == null ? undefined : serializedOnChange,
-    onSelectionChange:
-      onSelectionChange == null ? undefined : serializedOnSelectionChange,
+    selectionMode,
+    onChange,
+    onSelectionChange,
     // The @deephaven/components `ListView` has its own normalization logic that
     // handles primitive children types (string, number, boolean). It also
     // handles nested children inside of `Item` and `Section` components, so
diff --git a/plugins/ui/src/js/src/layout/LayoutUtils.tsx b/plugins/ui/src/js/src/layout/LayoutUtils.tsx
index 459565c05..6cca370dd 100644
--- a/plugins/ui/src/js/src/layout/LayoutUtils.tsx
+++ b/plugins/ui/src/js/src/layout/LayoutUtils.tsx
@@ -13,18 +13,7 @@ import Column from './Column';
 import Row from './Row';
 import Stack from './Stack';
 import ReactPanel from './ReactPanel';
-import {
-  ColumnElementType,
-  COLUMN_ELEMENT_NAME,
-  DashboardElementType,
-  DASHBOARD_ELEMENT_NAME,
-  PanelElementType,
-  PANEL_ELEMENT_NAME,
-  RowElementType,
-  ROW_ELEMENT_NAME,
-  StackElementType,
-  STACK_ELEMENT_NAME,
-} from '../elements/ElementConstants';
+import { ElementName, ELEMENT_NAME } from '../elements/ElementConstants';
 
 export type GoldenLayoutParent = RowOrColumn | GLStack | Root;
 
@@ -37,7 +26,10 @@ export type ReactPanelProps = React.PropsWithChildren<{
  * Describes a panel element that can be rendered in the UI.
  * Will be placed in the current dashboard, or within a user created dashboard if specified.
  */
-export type PanelElementNode = ElementNode<PanelElementType, ReactPanelProps>;
+export type PanelElementNode = ElementNode<
+  ElementName['panel'],
+  ReactPanelProps
+>;
 
 /**
  * Check if an object is a PanelElementNode
@@ -47,7 +39,7 @@ export type PanelElementNode = ElementNode<PanelElementType, ReactPanelProps>;
 export function isPanelElementNode(obj: unknown): obj is PanelElementNode {
   return (
     isElementNode(obj) &&
-    (obj as ElementNode)[ELEMENT_KEY] === PANEL_ELEMENT_NAME
+    (obj as ElementNode)[ELEMENT_KEY] === ELEMENT_NAME.panel
   );
 }
 
@@ -58,7 +50,7 @@ export type RowElementProps = React.PropsWithChildren<{
 /**
  * Describes a row element that can be rendered in the UI.
  */
-export type RowElementNode = ElementNode<RowElementType, RowElementProps>;
+export type RowElementNode = ElementNode<ElementName['row'], RowElementProps>;
 
 /**
  * Check if an object is a RowElementNode
@@ -67,7 +59,7 @@ export type RowElementNode = ElementNode<RowElementType, RowElementProps>;
  */
 export function isRowElementNode(obj: unknown): obj is RowElementNode {
   return (
-    isElementNode(obj) && (obj as ElementNode)[ELEMENT_KEY] === ROW_ELEMENT_NAME
+    isElementNode(obj) && (obj as ElementNode)[ELEMENT_KEY] === ELEMENT_NAME.row
   );
 }
 
@@ -79,7 +71,7 @@ export type ColumnElementProps = React.PropsWithChildren<{
  * Describes a column element that can be rendered in the UI.
  */
 export type ColumnElementNode = ElementNode<
-  ColumnElementType,
+  ElementName['column'],
   ColumnElementProps
 >;
 
@@ -91,7 +83,7 @@ export type ColumnElementNode = ElementNode<
 export function isColumnElementNode(obj: unknown): obj is ColumnElementNode {
   return (
     isElementNode(obj) &&
-    (obj as ElementNode)[ELEMENT_KEY] === COLUMN_ELEMENT_NAME
+    (obj as ElementNode)[ELEMENT_KEY] === ELEMENT_NAME.column
   );
 }
 
@@ -104,7 +96,10 @@ export type StackElementProps = React.PropsWithChildren<{
 /**
  * Describes a stack element that can be rendered in the UI.
  */
-export type StackElementNode = ElementNode<StackElementType, StackElementProps>;
+export type StackElementNode = ElementNode<
+  ElementName['stack'],
+  StackElementProps
+>;
 
 /**
  * Check if an object is a StackElementNode
@@ -114,7 +109,7 @@ export type StackElementNode = ElementNode<StackElementType, StackElementProps>;
 export function isStackElementNode(obj: unknown): obj is StackElementNode {
   return (
     isElementNode(obj) &&
-    (obj as ElementNode)[ELEMENT_KEY] === STACK_ELEMENT_NAME
+    (obj as ElementNode)[ELEMENT_KEY] === ELEMENT_NAME.stack
   );
 }
 
@@ -126,7 +121,7 @@ export type DashboardElementProps = React.PropsWithChildren<
  * Describes a dashboard element that can be rendered in the UI.
  */
 export type DashboardElementNode = ElementNode<
-  DashboardElementType,
+  ElementName['dashboard'],
   DashboardElementProps
 >;
 
@@ -140,7 +135,7 @@ export function isDashboardElementNode(
 ): obj is DashboardElementNode {
   return (
     isElementNode(obj) &&
-    (obj as ElementNode)[ELEMENT_KEY] === DASHBOARD_ELEMENT_NAME
+    (obj as ElementNode)[ELEMENT_KEY] === ELEMENT_NAME.dashboard
   );
 }
 
diff --git a/plugins/ui/src/js/src/widget/WidgetUtils.tsx b/plugins/ui/src/js/src/widget/WidgetUtils.tsx
index 73172ae8c..76c1ed142 100644
--- a/plugins/ui/src/js/src/widget/WidgetUtils.tsx
+++ b/plugins/ui/src/js/src/widget/WidgetUtils.tsx
@@ -3,7 +3,14 @@
 import React, { ComponentType } from 'react';
 // Importing `Item` and `Section` compnents directly since they should not be
 // wrapped due to how Spectrum collection components consume them.
-import { Item, Section } from '@deephaven/components';
+import {
+  ActionMenu,
+  Item,
+  ListActionGroup,
+  ListActionMenu,
+  Section,
+} from '@deephaven/components';
+import { ValueOf } from '@deephaven/utils';
 import { ReadonlyWidgetData } from './WidgetTypes';
 import {
   ElementNode,
@@ -18,19 +25,7 @@ import SpectrumElementView from '../elements/SpectrumElementView';
 import { isIconElementNode } from '../elements/IconElementUtils';
 import IconElementView from '../elements/IconElementView';
 import UITable from '../elements/UITable';
-import {
-  COLUMN_ELEMENT_NAME,
-  DASHBOARD_ELEMENT_NAME,
-  FRAGMENT_ELEMENT_NAME,
-  ITEM_ELEMENT_NAME,
-  LIST_VIEW_NAME,
-  PANEL_ELEMENT_NAME,
-  PICKER_ELEMENT_NAME,
-  ROW_ELEMENT_NAME,
-  SECTION_ELEMENT_NAME,
-  STACK_ELEMENT_NAME,
-  UITABLE_ELEMENT_TYPE,
-} from '../elements/ElementConstants';
+import { ELEMENT_NAME, ElementName } from '../elements/ElementConstants';
 import ReactPanel from '../layout/ReactPanel';
 import Row from '../layout/Row';
 import Stack from '../layout/Stack';
@@ -38,23 +33,33 @@ import Column from '../layout/Column';
 import Dashboard from '../layout/Dashboard';
 import ListView from '../elements/ListView';
 import Picker from '../elements/Picker';
+import ActionGroup from '../elements/ActionGroup';
 
 /*
  * Map element node names to their corresponding React components
  */
 export const elementComponentMap = {
-  [PANEL_ELEMENT_NAME]: ReactPanel,
-  [ROW_ELEMENT_NAME]: Row,
-  [COLUMN_ELEMENT_NAME]: Column,
-  [FRAGMENT_ELEMENT_NAME]: React.Fragment,
-  [STACK_ELEMENT_NAME]: Stack,
-  [DASHBOARD_ELEMENT_NAME]: Dashboard,
-  [ITEM_ELEMENT_NAME]: Item,
-  [LIST_VIEW_NAME]: ListView,
-  [PICKER_ELEMENT_NAME]: Picker,
-  [SECTION_ELEMENT_NAME]: Section,
-  [UITABLE_ELEMENT_TYPE]: UITable,
-} as const;
+  // Elements
+  [ELEMENT_NAME.uiTable]: UITable,
+
+  // Layout
+  [ELEMENT_NAME.column]: Column,
+  [ELEMENT_NAME.dashboard]: Dashboard,
+  [ELEMENT_NAME.panel]: ReactPanel,
+  [ELEMENT_NAME.row]: Row,
+  [ELEMENT_NAME.stack]: Stack,
+
+  // Other components
+  [ELEMENT_NAME.actionGroup]: ActionGroup,
+  [ELEMENT_NAME.actionMenu]: ActionMenu,
+  [ELEMENT_NAME.fragment]: React.Fragment,
+  [ELEMENT_NAME.item]: Item,
+  [ELEMENT_NAME.listActionGroup]: ListActionGroup,
+  [ELEMENT_NAME.listActionMenu]: ListActionMenu,
+  [ELEMENT_NAME.listView]: ListView,
+  [ELEMENT_NAME.picker]: Picker,
+  [ELEMENT_NAME.section]: Section,
+} as const satisfies Record<ValueOf<ElementName>, unknown>;
 
 export function getComponentTypeForElement<P extends Record<string, unknown>>(
   element: ElementNode<string, P>


Test patch:
diff --git a/plugins/ui/src/js/src/elements/ElementUtils.test.tsx b/plugins/ui/src/js/src/elements/ElementUtils.test.tsx
index 8499a8448..88bfbef6a 100644
--- a/plugins/ui/src/js/src/elements/ElementUtils.test.tsx
+++ b/plugins/ui/src/js/src/elements/ElementUtils.test.tsx
@@ -9,7 +9,7 @@ import {
   wrapElementChildren,
 } from './ElementUtils';
 import ObjectView from './ObjectView';
-import { ITEM_ELEMENT_NAME } from './ElementConstants';
+import { ELEMENT_NAME } from './ElementConstants';
 
 const { asMock, createMockProxy } = TestUtils;
 
@@ -136,7 +136,7 @@ describe('wrapElementChildren', () => {
             : textValue;
 
         const expected = {
-          [ELEMENT_KEY]: ITEM_ELEMENT_NAME,
+          [ELEMENT_KEY]: ELEMENT_NAME.item,
           props: {
             key: itemKey ?? textValue,
             textValue: expectedTextValue,
@@ -145,7 +145,7 @@ describe('wrapElementChildren', () => {
         };
 
         const actual = wrapElementChildren({
-          [ELEMENT_KEY]: ITEM_ELEMENT_NAME,
+          [ELEMENT_KEY]: ELEMENT_NAME.item,
           props: givenProps,
         });
 
diff --git a/plugins/ui/src/js/src/layout/LayoutUtils.test.tsx b/plugins/ui/src/js/src/layout/LayoutUtils.test.tsx
index da8535034..8063bc8e2 100644
--- a/plugins/ui/src/js/src/layout/LayoutUtils.test.tsx
+++ b/plugins/ui/src/js/src/layout/LayoutUtils.test.tsx
@@ -15,12 +15,7 @@ import {
 import Row from './Row';
 import Stack from './Stack';
 import ReactPanel from './ReactPanel';
-import {
-  PANEL_ELEMENT_NAME,
-  ROW_ELEMENT_NAME,
-  COLUMN_ELEMENT_NAME,
-  STACK_ELEMENT_NAME,
-} from '../elements/ElementConstants';
+import { ELEMENT_NAME } from '../elements/ElementConstants';
 
 beforeEach(() => {
   TestUtils.disableConsoleOutput();
@@ -30,7 +25,7 @@ describe('isPanelElementNode', () => {
   test.each([
     [{ props: { title: 'test' } }, false],
     [{ props: { title: 'test' }, __dhElemName: 'a different name' }, false],
-    [{ props: { title: 'test' }, __dhElemName: PANEL_ELEMENT_NAME }, true],
+    [{ props: { title: 'test' }, __dhElemName: ELEMENT_NAME.panel }, true],
   ])(`isPanelElementNode(%s)`, (element, result) => {
     expect(isPanelElementNode(element)).toBe(result);
   });
@@ -40,7 +35,7 @@ describe('isRowElementNode', () => {
   test.each([
     [{ props: { height: 100 } }, false],
     [{ props: { height: 100 }, __dhElemName: 'a different name' }, false],
-    [{ props: { height: 100 }, __dhElemName: ROW_ELEMENT_NAME }, true],
+    [{ props: { height: 100 }, __dhElemName: ELEMENT_NAME.row }, true],
   ])(`isRowElementNode(%s)`, (element, result) => {
     expect(isRowElementNode(element)).toBe(result);
   });
@@ -50,7 +45,7 @@ describe('isColumnElementNode', () => {
   test.each([
     [{ props: { width: 100 } }, false],
     [{ props: { width: 100 }, __dhElemName: 'a different name' }, false],
-    [{ props: { width: 100 }, __dhElemName: COLUMN_ELEMENT_NAME }, true],
+    [{ props: { width: 100 }, __dhElemName: ELEMENT_NAME.column }, true],
   ])(`isColumnElementNode(%s)`, (element, result) => {
     expect(isColumnElementNode(element)).toBe(result);
   });
@@ -64,7 +59,7 @@ describe('isStackElementNode', () => {
       false,
     ],
     [
-      { props: { height: 100, width: 100 }, __dhElemName: STACK_ELEMENT_NAME },
+      { props: { height: 100, width: 100 }, __dhElemName: ELEMENT_NAME.stack },
       true,
     ],
   ])(`isStackElementNode(%s)`, (element, result) => {
diff --git a/plugins/ui/src/js/src/widget/DocumentHandler.test.tsx b/plugins/ui/src/js/src/widget/DocumentHandler.test.tsx
index 5b7023f23..43aae2c39 100644
--- a/plugins/ui/src/js/src/widget/DocumentHandler.test.tsx
+++ b/plugins/ui/src/js/src/widget/DocumentHandler.test.tsx
@@ -6,10 +6,7 @@ import DocumentHandler, { DocumentHandlerProps } from './DocumentHandler';
 import { ReactPanelProps } from '../layout/LayoutUtils';
 import { MixedPanelsError, NoChildrenError } from '../errors';
 import { getComponentForElement } from './WidgetUtils';
-import {
-  DASHBOARD_ELEMENT_NAME,
-  PANEL_ELEMENT_NAME,
-} from '../elements/ElementConstants';
+import { ELEMENT_NAME } from '../elements/ElementConstants';
 
 const mockReactPanel = jest.fn((props: ReactPanelProps) => (
   <div>ReactPanel</div>
@@ -67,7 +64,7 @@ it('should throw an error if the document mixes panel and non-panel elements', (
   TestUtils.disableConsoleOutput();
 
   const children = makeDocument([
-    makeElement(PANEL_ELEMENT_NAME),
+    makeElement(ELEMENT_NAME.panel),
     makeElement('not panel element'),
   ]);
   expect(() => render(makeDocumentHandler({ children }))).toThrow(
@@ -83,8 +80,8 @@ it('should combine multiple single elements into one panel', () => {
 
 it('should render multiple panels', () => {
   const children = makeDocument([
-    makeElement(PANEL_ELEMENT_NAME),
-    makeElement(PANEL_ELEMENT_NAME),
+    makeElement(ELEMENT_NAME.panel),
+    makeElement(ELEMENT_NAME.panel),
   ]);
   render(makeDocumentHandler({ children }));
   expect(mockReactPanel).toHaveBeenCalledTimes(2);
@@ -92,8 +89,8 @@ it('should render multiple panels', () => {
 
 it('should throw an error if the document mixes dashboard and non-dashboard elements', () => {
   const children = makeDocument([
-    makeElement(PANEL_ELEMENT_NAME),
-    makeElement(DASHBOARD_ELEMENT_NAME),
+    makeElement(ELEMENT_NAME.panel),
+    makeElement(ELEMENT_NAME.dashboard),
   ]);
   expect(() => render(makeDocumentHandler({ children }))).toThrow(
     MixedPanelsError
@@ -101,7 +98,7 @@ it('should throw an error if the document mixes dashboard and non-dashboard elem
 });
 
 it('should render a dashboard', () => {
-  const children = makeDocument([makeElement(DASHBOARD_ELEMENT_NAME)]);
+  const children = makeDocument([makeElement(ELEMENT_NAME.dashboard)]);
   render(makeDocumentHandler({ children }));
   expect(mockReactPanel).toHaveBeenCalledTimes(0);
 });
diff --git a/plugins/ui/src/js/src/widget/WidgetUtils.test.tsx b/plugins/ui/src/js/src/widget/WidgetUtils.test.tsx
index b8109e775..ee6665160 100644
--- a/plugins/ui/src/js/src/widget/WidgetUtils.test.tsx
+++ b/plugins/ui/src/js/src/widget/WidgetUtils.test.tsx
@@ -1,11 +1,6 @@
 import React from 'react';
 import { Text } from '@deephaven/components';
-import {
-  FRAGMENT_ELEMENT_NAME,
-  ITEM_ELEMENT_NAME,
-  HTML_ELEMENT_NAME_PREFIX,
-  ICON_ELEMENT_TYPE_PREFIX,
-} from '../elements/ElementConstants';
+import { ELEMENT_NAME, ELEMENT_PREFIX } from '../elements/ElementConstants';
 import { ElementNode, ELEMENT_KEY } from '../elements/ElementUtils';
 import HTMLElementView from '../elements/HTMLElementView';
 import IconElementView from '../elements/IconElementView';
@@ -33,9 +28,9 @@ describe('getComponentTypeForElement', () => {
 describe('getComponentForElement', () => {
   it.each([
     /* eslint-disable react/jsx-key */
-    [`${HTML_ELEMENT_NAME_PREFIX}div`, HTMLElementView],
+    [`${ELEMENT_PREFIX.html}div`, HTMLElementView],
     [`${SPECTRUM_ELEMENT_TYPE_PREFIX}ActionButton`, SpectrumElementView],
-    [`${ICON_ELEMENT_TYPE_PREFIX}vsAdd`, IconElementView],
+    [`${ELEMENT_PREFIX.icon}vsAdd`, IconElementView],
     /* eslint-enable react/jsx-key */
   ] as [string, ({ element }: { element: unknown }) => JSX.Element][])(
     'should use expected element factory function: %s',
@@ -52,12 +47,12 @@ describe('getComponentForElement', () => {
   )('should spread props for element nodes: %s', elementKey => {
     let element: ElementNode = { [ELEMENT_KEY]: elementKey };
 
-    if (elementKey === FRAGMENT_ELEMENT_NAME) {
+    if (elementKey === ELEMENT_NAME.fragment) {
       element = {
         ...element,
         props: { key: 'mock.key', children: ['Some child'] },
       };
-    } else if (elementKey === ITEM_ELEMENT_NAME) {
+    } else if (elementKey === ELEMENT_NAME.item) {
       element = {
         ...element,
         props: { children: <Text>Some child</Text> },