diff --git a/app/gui/.dev-env b/app/gui/.dev-env
index ef1288096eb7..82c228604e46 160000
--- a/app/gui/.dev-env
+++ b/app/gui/.dev-env
@@ -1 +1 @@
-Subproject commit ef1288096eb773d3a28e91e90d17b695a5985ac9
+Subproject commit 82c228604e4662ab6adaa3a896455de2d1b3c9db
diff --git a/app/gui/integration-test/project-view/tableVisualisation.spec.ts b/app/gui/integration-test/project-view/tableVisualisation.spec.ts
index 70b127503d6b..811cd533bbf1 100644
--- a/app/gui/integration-test/project-view/tableVisualisation.spec.ts
+++ b/app/gui/integration-test/project-view/tableVisualisation.spec.ts
@@ -91,6 +91,7 @@ test('Copy/paste from Table Visualization', async ({ page, editorPage }) => {
page.evaluate(() => window.navigator.clipboard.readText()),
)
await editorPage
+
await actions.openVisualization(page, 'Table')
const tableVisualization = locate.tableVisualization(page)
await expect(tableVisualization).toExist()
@@ -129,19 +130,25 @@ test('Copy/paste from Table Visualization', async ({ page, editorPage }) => {
await editorPage.press('Mod+V')
await expectTableInputContent(page, node)
- // Copy from table input widget
+ // Select a range in table input widget
await node.getByText('0,0').hover()
await page.mouse.down()
await node.getByText('2,1').hover()
await page.mouse.up()
+
+ // Copy from table input widget
await editorPage.press('Mod+C')
await expectClipboard.toMatch(/^0,0\t0,1\r\n1,0\t1,1\r\n2,0\t2,1$/)
- // Copy from table input widget with headers
+ // Copy from table input widget by menu
+ await node.getByText('0,0').hover()
+ await page.mouse.down({ button: 'right' })
+ await page.mouse.up({ button: 'right' })
+ await page.getByText('Copy', { exact: true }).click()
+ await expectClipboard.toMatch(/^0,0\t0,1\r\n1,0\t1,1\r\n2,0\t2,1$/)
+
+ // Copy from table input widget by menu with headers
await node.getByText('0,0').hover()
- await page.mouse.down()
- await node.getByText('2,1').hover()
- await page.mouse.up()
await page.mouse.down({ button: 'right' })
await page.mouse.up({ button: 'right' })
await page.getByText('Copy with Headers').click()
diff --git a/app/gui/package.json b/app/gui/package.json
index 572609c1a617..483853a9c6ec 100644
--- a/app/gui/package.json
+++ b/app/gui/package.json
@@ -40,12 +40,6 @@
"playwright:install": "playwright install chromium"
},
"dependencies": {
- "@ag-grid-community/client-side-row-model": "^32.3.3",
- "@ag-grid-community/core": "^32.3.3",
- "@ag-grid-community/locale": "^32.3.3",
- "@ag-grid-community/styles": "^32.3.3",
- "@ag-grid-enterprise/core": "^32.3.3",
- "@ag-grid-enterprise/range-selection": "^32.3.3",
"aws-amplify": "^6.16.0",
"@babel/parser": "^7.26.3",
"@codemirror/autocomplete": "catalog:",
@@ -78,8 +72,9 @@
"@vue/reactivity": "^3.5.13",
"@vueuse/core": "^13.0.0",
"@vueuse/gesture": "^2.0.0",
- "ag-grid-community": "^32.3.3",
- "ag-grid-enterprise": "^32.3.3",
+ "ag-grid-community": "^35.2.1",
+ "ag-grid-enterprise": "^35.2.1",
+ "ag-grid-vue3": "^35.2.1",
"ajv": "^8.17.1",
"amazon-cognito-identity-js": "6.3.6",
"codemirror": "^6.0.2",
diff --git a/app/gui/src/project-view/components/AgGridTableView.vue b/app/gui/src/project-view/components/AgGridTableView.vue
index 75eca5804ae6..22888bae2097 100644
--- a/app/gui/src/project-view/components/AgGridTableView.vue
+++ b/app/gui/src/project-view/components/AgGridTableView.vue
@@ -29,6 +29,4 @@ defineOptions({ inheritAttrs: false })
-
-
diff --git a/app/gui/src/project-view/components/GraphEditor/GraphVisualization.vue b/app/gui/src/project-view/components/GraphEditor/GraphVisualization.vue
index 15626086a255..fd6d7afcdc59 100644
--- a/app/gui/src/project-view/components/GraphEditor/GraphVisualization.vue
+++ b/app/gui/src/project-view/components/GraphEditor/GraphVisualization.vue
@@ -223,6 +223,9 @@ customElements.define(ensoVisualizationHost, defineCustomElement(VisualizationHo
:class="{ isFocused }"
@pointerenter="emit('update:hovered', true)"
@pointerleave="emit('update:hovered', false)"
+ @pointerdown.stop.prevent
+ @pointerup.stop.prevent
+ @click.stop.prevent
>
- }
- | undefined
- >,
+ gridApi: ToValue,
colDefs: ToValue<{ colId: string }[]>,
widgetHandlerConstructor: (hooks: WidgetEditHooks) => ShallowRef,
) {
@@ -43,10 +36,7 @@ export function useTableEditHandler(
const editedInGrid = api.getEditingCells()[0]
if (cell == null || cell.rowIndex === 'header') {
api.stopEditing(false)
- } else if (
- editedInGrid?.rowIndex !== cell.rowIndex ||
- editedInGrid?.column.getColId() !== cell.colKey
- ) {
+ } else if (editedInGrid?.rowIndex !== cell.rowIndex || editedInGrid?.colId !== cell.colKey) {
api.startEditingCell({ rowIndex: cell.rowIndex, colKey: cell.colKey })
}
}
diff --git a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetTableEditor/tableInputArgument.ts b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetTableEditor/tableInputArgument.ts
index 0c45b4436931..5354e926d80e 100644
--- a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetTableEditor/tableInputArgument.ts
+++ b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetTableEditor/tableInputArgument.ts
@@ -8,7 +8,7 @@ import { Ast } from '@/util/ast'
import { arrayEquals, findIndexOpt } from '@/util/data/array'
import { ProjectPath } from '@/util/projectPath'
import { qnLastSegment, type QualifiedName } from '@/util/qualifiedName'
-import type { ColDef } from 'ag-grid-enterprise'
+import type { ColDef, DefaultMenuItem } from 'ag-grid-enterprise'
import * as iter from 'enso-common/src/utilities/data/iter'
import {
Err,
@@ -50,8 +50,8 @@ export interface ColumnDef extends ColDef {
colId: string
valueGetter: ({ data }: { data: RowData | undefined }) => any
valueSetter?: ({ data, newValue }: { data: RowData; newValue: string }) => boolean
- mainMenuItems: (string | MenuItem)[]
- contextMenuItems: (string | MenuItem)[]
+ mainMenuItems: (DefaultMenuItem | MenuItem)[]
+ contextMenuItems: (DefaultMenuItem | MenuItem)[]
rowDrag?: ({ data }: { data: RowData | undefined }) => boolean
headerComponentParams: ColumnSpecificParams
}
diff --git a/app/gui/src/project-view/components/shared/AgGridTableView.vue b/app/gui/src/project-view/components/shared/AgGridTableView.vue
index 1a80ade98429..3d3ecf7411f3 100644
--- a/app/gui/src/project-view/components/shared/AgGridTableView.vue
+++ b/app/gui/src/project-view/components/shared/AgGridTableView.vue
@@ -11,13 +11,10 @@ export type AgGridTableViewProps = {
suppressMoveWhenColumnDragging?: boolean
textFormatOption?: TextFormatOptions
processDataFromClipboard?: (params: ProcessDataFromClipboardParams) => string[][] | null
- datasource?: IServerSideDatasource | boolean
+ datasource?: IServerSideDatasource | undefined
rowCount?: number
- isServerSideModel?: boolean
gridIdHash?: string | null
- getContextMenuItems?: (
- params: GetContextMenuItemsParams,
- ) => (MenuItemDef | string)[] | GetContextMenuItems
+ getContextMenuItems?: GetContextMenuItems | undefined
}
/**
@@ -39,7 +36,7 @@ const AGGRID_DEFAULT_PASTE_ICON =
''
/** Whether to include column headers in copied clipboard content or not. See {@link sendToClipboard}. */
-const copyWithHeaders = ref(false)
+const copyWithHeaders = { value: false }
export const commonContextMenuActions = {
cut: {
@@ -109,7 +106,6 @@ import type {
ColumnVisibleEvent,
FirstDataRenderedEvent,
GetContextMenuItems,
- GetContextMenuItemsParams,
GetRowIdFunc,
GridApi,
GridReadyEvent,
@@ -165,7 +161,7 @@ function onGridReady(event: GridReadyEvent) {
}
}
-const rowModelType = computed(() => (props.isServerSideModel ? 'serverSide' : 'clientSide'))
+const rowModelType = computed(() => (props.datasource ? 'serverSide' : 'clientSide'))
const gridKeyIncrement = ref(0)
const gridKey = computed(() =>
@@ -366,7 +362,8 @@ function getRowHeight(params: RowHeightParams): number {
return (maxReturnCharsCount + 1) * DEFAULT_ROW_HEIGHT
}
-const { AgGridVue } = await import('./AgGridTableView/AgGridVue')
+const { AgGridVue } = await import('@/components/shared/AgGridTableView/agGrid')
+const { themeAlpine } = await import('ag-grid-community')
@@ -404,7 +401,8 @@ const { AgGridVue } = await import('./AgGridTableView/AgGridVue')
:allowContextMenuWithControlKey="true"
:cacheBlockSize="rowModelType === 'clientSide' ? undefined : 1000"
:getContextMenuItems="getContextMenuItems"
- :getRowHeight="rowModelType === 'clientSide' ? getRowHeight : null"
+ :getRowHeight="rowModelType === 'clientSide' ? getRowHeight : undefined"
+ :theme="themeAlpine"
@gridReady="onGridReady"
@firstDataRendered="updateColumnWidths"
@rowDataUpdated="(updateColumnWidths($event), emit('rowDataUpdated', $event))"
@@ -423,6 +421,4 @@ const { AgGridVue } = await import('./AgGridTableView/AgGridVue')
-
-
diff --git a/app/gui/src/project-view/components/shared/AgGridTableView/AgGridVue.ts b/app/gui/src/project-view/components/shared/AgGridTableView/AgGridVue.ts
deleted file mode 100644
index 11e1a2228dbc..000000000000
--- a/app/gui/src/project-view/components/shared/AgGridTableView/AgGridVue.ts
+++ /dev/null
@@ -1,301 +0,0 @@
-/**
- * @file Copy of https://github.com/ag-grid/ag-grid/blob/v32.3.3/packages/ag-grid-vue3/src/AgGridVue.ts
- * with our modifications:
- * - special overrides for Vue components removed; we handle them in a better way,
- * - added license key registration.
- *
- * Original file licenced under The MIT License:
- *
- * Copyright (c) 2015-2024 AG GRID LTD
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-// This is not our code, so I just blocked failing lints.
-/* eslint-disable vue/require-default-prop */
-/* eslint-disable vue/require-prop-types */
-/* eslint-disable vue/order-in-components */
-
-import { AG_GRID_LOCALE_EN } from '@ag-grid-community/locale'
-import {
- _combineAttributesAndGridOptions,
- _processOnChange,
- _warnOnce,
- ALWAYS_SYNC_GLOBAL_EVENTS,
- ComponentUtil,
- createGrid,
- LicenseManager,
- type AgEventType,
- type GridApi,
- type GridOptions,
- type IRowNode,
- type Module,
-} from 'ag-grid-enterprise'
-import { defineComponent, getCurrentInstance, h, markRaw, toRaw, type PropType } from 'vue'
-import { convertToRaw, getAgGridProperties, type Properties } from './Utils'
-
-// === Loading AGGrid and its license ===
-
-if (typeof $config.AG_GRID_LICENSE_KEY !== 'string') {
- console.warn('The AG_GRID_LICENSE_KEY is not defined.')
- if (import.meta.env.DEV) {
- // Hide annoying license validation errors in dev mode when the license is not defined. The
- // missing define warning is still displayed to not forget about it, but it isn't as obnoxious.
- const origValidateLicense = LicenseManager.prototype.validateLicense
- LicenseManager.prototype.validateLicense = function (this) {
- if (!('licenseManager' in this))
- Object.defineProperty(this, 'licenseManager', {
- configurable: true,
- set(value: any) {
- Object.getPrototypeOf(value).validateLicense = () => {}
- delete this.licenseManager
- this.licenseManager = value
- },
- })
- origValidateLicense.call(this)
- }
- }
-} else {
- LicenseManager.setLicenseKey($config.AG_GRID_LICENSE_KEY)
-}
-
-const ROW_DATA_EVENTS: Set = new Set([
- 'rowDataUpdated',
- 'cellValueChanged',
- 'rowValueChanged',
-])
-const DATA_MODEL_ATTR_NAME = 'onUpdate:modelValue' // emit name would be update:ModelValue
-const DATA_MODEL_EMIT_NAME = 'update:modelValue'
-
-const [props, computed, watch] = getAgGridProperties()
-
-const customLocale = {
- ...AG_GRID_LOCALE_EN,
- // Add any customizations to the locale here
- loadingError: 'Error fetching data - close and reopen visualization to retry',
-}
-
-export const AgGridVue = defineComponent({
- render() {
- return h('div')
- },
- props: {
- gridOptions: {
- type: Object as PropType,
- default: () => ({}) as GridOptions,
- },
- componentDependencies: {
- type: Array as PropType,
- default: () => [],
- },
- plugins: [],
- modules: {
- type: Array as PropType,
- default: () => [],
- },
- modelValue: {
- type: Array,
- default: undefined,
- required: false,
- },
- ...props,
- },
- data(): {
- api: GridApi | undefined
- gridCreated: boolean
- isDestroyed: boolean
- gridReadyFired: boolean
- emitRowModel?: (() => void | null) | undefined
- batchTimeout: number | null
- batchChanges: { [key: string]: any }
- } {
- return {
- api: undefined,
- gridCreated: false,
- isDestroyed: false,
- gridReadyFired: false,
- emitRowModel: undefined,
- batchTimeout: null,
- batchChanges: markRaw({}),
- }
- },
- computed,
- watch,
- methods: {
- globalEventListenerFactory(restrictToSyncOnly?: boolean) {
- return (eventType: AgEventType) => {
- if (this.isDestroyed) {
- return
- }
-
- if (eventType === 'gridReady') {
- this.gridReadyFired = true
- }
-
- const alwaysSync = ALWAYS_SYNC_GLOBAL_EVENTS.has(eventType)
- if ((alwaysSync && !restrictToSyncOnly) || (!alwaysSync && restrictToSyncOnly)) {
- return
- }
-
- this.updateModelIfUsed(eventType)
- }
- },
- processChanges(propertyName: string, currentValue: any, previousValue: any) {
- if (this.gridCreated) {
- if (this.skipChange(propertyName, currentValue, previousValue)) {
- return
- }
-
- const options: Properties = {
- [propertyName]:
- propertyName === 'rowData' ?
- Object.isFrozen(currentValue) ?
- currentValue
- : markRaw(toRaw(currentValue))
- : currentValue,
- }
- // decouple the row data - if we don't when the grid changes row data directly that'll trigger this component to react to rowData changes,
- // which can reset grid state (ie row selection)
- _processOnChange(options, this.api as any)
- }
- },
- checkForBindingConflicts() {
- const thisAsAny = this as any
- if (
- ((thisAsAny.rowData && thisAsAny.rowData !== 'AG-VUE-OMITTED-PROPERTY') ||
- this.gridOptions.rowData) &&
- thisAsAny.modelValue
- ) {
- _warnOnce('Using both rowData and v-model. rowData will be ignored.')
- }
- },
- getRowData(): any[] {
- const rowData: any[] = []
- this.api?.forEachNode((rowNode: IRowNode) => {
- rowData.push(rowNode.data)
- })
- return rowData
- },
- updateModelIfUsed(eventType: string) {
- if (
- this.gridReadyFired &&
- this.$attrs[DATA_MODEL_ATTR_NAME] &&
- ROW_DATA_EVENTS.has(eventType)
- ) {
- if (this.emitRowModel) {
- this.emitRowModel()
- }
- }
- },
- getRowDataBasedOnBindings() {
- const thisAsAny = this as any
-
- const rowData = thisAsAny.modelValue
- return (
- rowData ? rowData
- : thisAsAny.rowData ? thisAsAny.rowData
- : thisAsAny.gridOptions.rowData
- )
- },
- getProvides() {
- let instance = getCurrentInstance() as any
- let provides = {}
-
- while (instance) {
- if (instance && instance.provides) {
- provides = { ...provides, ...instance.provides }
- }
-
- instance = instance.parent
- }
-
- return provides
- },
- /*
- * Prevents an infinite loop when using v-model for the rowData
- */
- skipChange(propertyName: string, currentValue: any, previousValue: any) {
- if (this.gridReadyFired && propertyName === 'rowData' && this.$attrs[DATA_MODEL_ATTR_NAME]) {
- if (currentValue === previousValue) {
- return true
- }
-
- if (currentValue && previousValue) {
- const currentRowData = currentValue as any[]
- const previousRowData = previousValue as any[]
- if (currentRowData.length === previousRowData.length) {
- for (let i = 0; i < currentRowData.length; i++) {
- if (currentRowData[i] !== previousRowData[i]) {
- return false
- }
- }
- return true
- }
- }
- }
-
- return false
- },
- debounce(func: () => void, delay: number) {
- let timeout: number
- return () => {
- const later = function () {
- func()
- }
- window.clearTimeout(timeout)
- timeout = window.setTimeout(later, delay)
- }
- },
- },
- mounted() {
- // we debounce the model update to prevent a flood of updates in the event there are many individual
- // cell/row updates
- this.emitRowModel = this.debounce(() => {
- this.$emit(DATA_MODEL_EMIT_NAME, Object.freeze(this.getRowData()))
- }, 20)
-
- // the gridOptions we pass to the grid don't need to be reactive (and shouldn't be - it'll cause issues
- // with mergeDeep for example
- const gridOptions = markRaw(_combineAttributesAndGridOptions(toRaw(this.gridOptions), this))
-
- this.checkForBindingConflicts()
-
- const rowData = this.getRowDataBasedOnBindings()
- if (rowData !== ComponentUtil.VUE_OMITTED_PROPERTY) {
- gridOptions.rowData = convertToRaw(rowData)
- }
-
- const gridParams = {
- globalEventListener: this.globalEventListenerFactory().bind(this),
- globalSyncEventListener: this.globalEventListenerFactory(true).bind(this),
- modules: this.modules,
- }
-
- // Set the localeText to improve ERR
- gridOptions.localeText = customLocale
-
- this.api = createGrid(this.$el as HTMLElement, gridOptions, gridParams)
- this.gridCreated = true
- },
- unmounted() {
- if (this.gridCreated) {
- this.api?.destroy()
- this.isDestroyed = true
- }
- },
-})
diff --git a/app/gui/src/project-view/components/shared/AgGridTableView/Utils.ts b/app/gui/src/project-view/components/shared/AgGridTableView/Utils.ts
deleted file mode 100644
index 6f3e9a67bd0e..000000000000
--- a/app/gui/src/project-view/components/shared/AgGridTableView/Utils.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-/**
- * @file Copy of https://github.com/ag-grid/ag-grid/blob/v32.3.3/packages/ag-grid-vue3/src/Utils.ts
- * Used by our version of AgGridVue.ts.
- *
- * Original file licenced under The MIT License:
- *
- * Copyright (c) 2015-2024 AG GRID LTD
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-import { ComponentUtil, _processOnChange } from 'ag-grid-enterprise'
-import { markRaw, toRaw } from 'vue'
-
-export const kebabProperty = (property: string) => {
- return property.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
-}
-
-export const kebabNameToAttrEventName = (kebabName: string) => {
- // grid-ready for example would become onGrid-ready in Vue
- return `on${kebabName.charAt(0).toUpperCase()}${kebabName.substring(1, kebabName.length)}`
-}
-
-export const convertToRaw = (value: any) =>
- value ?
- Object.isFrozen(value) ?
- value
- : markRaw(toRaw(value))
- : value
-
-export interface Properties {
- [propertyName: string]: any
-}
-
-export const getAgGridProperties = (): [Properties, Properties, Properties] => {
- const props: Properties = {}
-
- // for example, 'grid-ready' would become 'onGrid-ready': undefined
- // without this emitting events results in a warning
- // and adding 'grid-ready' (and variations of this to the emits option in AgGridVue doesn't help either)
- const eventNameAsProps = ComponentUtil.PUBLIC_EVENTS.map((eventName: string) =>
- kebabNameToAttrEventName(kebabProperty(eventName)),
- )
- eventNameAsProps.forEach((eventName: string) => (props[eventName] = undefined))
-
- const computed: Properties = {}
-
- const watch: Properties = {
- modelValue: {
- handler(currentValue: any, previousValue: any) {
- if (!this.gridCreated || !this.api) {
- return
- }
-
- /*
- * Prevents an infinite loop when using v-model for the rowData
- */
- if (currentValue === previousValue) {
- return
- }
- if (currentValue && previousValue) {
- if (currentValue.length === previousValue.length) {
- if (currentValue.every((item: any, index: number) => item === previousValue[index])) {
- return
- }
- }
- }
-
- _processOnChange({ rowData: currentValue }, this.api)
- },
- deep: true,
- },
- }
-
- ComponentUtil.ALL_PROPERTIES.filter((propertyName: string) => propertyName != 'gridOptions') // dealt with in AgGridVue itself
- .forEach((propertyName: string) => {
- props[propertyName] = {
- default: ComponentUtil.VUE_OMITTED_PROPERTY,
- }
-
- watch[propertyName] = {
- handler(currentValue: any, _previousValue: any) {
- let currValue = currentValue
-
- if (propertyName === 'rowData' && currentValue != ComponentUtil.VUE_OMITTED_PROPERTY) {
- // Prevent the grids internal edits from being reactive
- currValue = convertToRaw(currentValue)
- }
-
- this.batchChanges[propertyName] =
- currValue === ComponentUtil.VUE_OMITTED_PROPERTY ? undefined : currValue
- if (this.batchTimeout == null) {
- this.batchTimeout = setTimeout(() => {
- // Clear the timeout before processing the changes in case processChanges triggers another change.
- this.batchTimeout = null
- _processOnChange(this.batchChanges, this.api)
- this.batchChanges = markRaw({})
- }, 0)
- }
- },
- deep: true,
- }
- })
-
- return [props, computed, watch]
-}
diff --git a/app/gui/src/project-view/components/shared/AgGridTableView/agGrid.ts b/app/gui/src/project-view/components/shared/AgGridTableView/agGrid.ts
new file mode 100644
index 000000000000..5cab27a506c2
--- /dev/null
+++ b/app/gui/src/project-view/components/shared/AgGridTableView/agGrid.ts
@@ -0,0 +1,60 @@
+import * as ag from 'ag-grid-enterprise'
+import { LicenseManager, ModuleRegistry } from 'ag-grid-enterprise'
+export { AgGridVue } from 'ag-grid-vue3'
+
+if (typeof $config.AG_GRID_LICENSE_KEY !== 'string') {
+ console.warn('The AG_GRID_LICENSE_KEY is not defined.')
+ if (import.meta.env.DEV) {
+ // Hide annoying license validation errors in dev mode when the license is not defined. The
+ // missing define warning is still displayed to not forget about it, but it isn't as obnoxious.
+ const origValidateLicense = LicenseManager.prototype.validateLicense
+ LicenseManager.prototype.validateLicense = function (this) {
+ if (!('licenseManager' in this))
+ Object.defineProperty(this, 'licenseManager', {
+ configurable: true,
+ set(value: any) {
+ Object.getPrototypeOf(value).validateLicense = () => {}
+ delete this.licenseManager
+ this.licenseManager = value
+ },
+ })
+ origValidateLicense.call(this)
+ }
+ }
+} else {
+ LicenseManager.setLicenseKey($config.AG_GRID_LICENSE_KEY)
+}
+
+ModuleRegistry.registerModules([
+ ag.BigIntFilterModule,
+ ag.CellSelectionModule,
+ ag.CellStyleModule,
+ ag.ClientSideRowModelApiModule,
+ ag.ClientSideRowModelModule,
+ ag.ClipboardModule,
+ ag.ColumnApiModule,
+ ag.ColumnAutoSizeModule,
+ ag.ColumnHoverModule,
+ ag.ColumnMenuModule,
+ ag.ContextMenuModule,
+ ag.CsvExportModule,
+ ag.DateFilterModule,
+ ag.EventApiModule,
+ ag.ExcelExportModule,
+ ag.GridStateModule,
+ ag.MultiFilterModule,
+ ag.NumberFilterModule,
+ ag.RenderApiModule,
+ ag.RowAutoHeightModule,
+ ag.RowDragModule,
+ ag.ServerSideRowModelApiModule,
+ ag.ServerSideRowModelModule,
+ ag.SetFilterModule,
+ ag.StatusBarModule,
+ ag.TextEditorModule,
+ ag.TextFilterModule,
+ ag.TooltipModule,
+])
+if (import.meta.env.DEV) {
+ ModuleRegistry.registerModules([ag.ValidationModule])
+}
diff --git a/app/gui/src/project-view/components/shared/AgGridTableView/tableViewStyle.css b/app/gui/src/project-view/components/shared/AgGridTableView/tableViewStyle.css
index e4ad4276a711..2cbeef7d78d4 100644
--- a/app/gui/src/project-view/components/shared/AgGridTableView/tableViewStyle.css
+++ b/app/gui/src/project-view/components/shared/AgGridTableView/tableViewStyle.css
@@ -16,18 +16,17 @@
}
.ag-theme-alpine {
- --ag-grid-size: 3px;
+ --ag-spacing: 3px;
--ag-list-item-height: 20px;
--ag-foreground-color: var(--color-text);
--ag-background-color: var(--color-visualization-bg);
- --ag-header-foreground-color: var(--color-ag-header-text);
+ --ag-header-text-color: var(--color-ag-header-text);
--ag-odd-row-background-color: color-mix(
in srgb,
var(--color-visualization-bg) 98%,
black
);
- --ag-header-background-color: var(--color-visualization-bg);
- font-family: var(--font-mono);
+ --ag-font-family: var(--font-mono);
.ag-header {
background: linear-gradient(
diff --git a/app/gui/src/project-view/components/shared/AgGridTableView/workaroundAg16857.ts b/app/gui/src/project-view/components/shared/AgGridTableView/workaroundAg16857.ts
new file mode 100644
index 000000000000..f39e48be4b11
--- /dev/null
+++ b/app/gui/src/project-view/components/shared/AgGridTableView/workaroundAg16857.ts
@@ -0,0 +1,25 @@
+import * as ag from 'ag-grid-enterprise'
+
+/**
+ * Work around (AG-16857)[https://github.com/ag-grid/ag-grid/issues/12910]. This is needed for cell selection by
+ * click-drag to work within shadow roots in AG Grid versions including 35.2.0. It is expected to be fixed in the next
+ * release. The bug is covered by the 'Copy/paste from Table Visualization' integration test.
+ */
+export function onCellMouseOver(event: ag.CellMouseOverEvent) {
+ const ev = event.event
+ const grid = event.api
+ if (ev instanceof MouseEvent && ev.buttons === 1) {
+ const clickedCell = grid.getFocusedCell()
+ if (clickedCell) {
+ grid.clearCellSelection()
+ grid.addCellRange({
+ rowStartIndex: clickedCell.rowIndex,
+ columnStart: clickedCell.column,
+ rowEndIndex: event.rowIndex,
+ columnEnd: event.column,
+ })
+ }
+ }
+}
+
+ag.ModuleRegistry.registerModules([ag.GridStateModule])
diff --git a/app/gui/src/project-view/components/visualizations/TableVisualization.vue b/app/gui/src/project-view/components/visualizations/TableVisualization.vue
index ca77a7ee6eca..b6c4fd66e9ff 100644
--- a/app/gui/src/project-view/components/visualizations/TableVisualization.vue
+++ b/app/gui/src/project-view/components/visualizations/TableVisualization.vue
@@ -1,5 +1,6 @@