Table Input Widget: Add column with plus (#11388)

Fixes #10863

(The `Column #3` in a screenshot below is just created and actually existing).

![image](https://github.com/user-attachments/assets/995d1a85-eb04-4b8d-bf23-b47ea61185e4)
This commit is contained in:
Adam Obuchowicz 2024-10-25 14:26:21 +02:00 committed by GitHub
parent 1b6a1f990b
commit 08fd9213e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 203 additions and 136 deletions

View File

@ -9,12 +9,15 @@
- [Copying and pasting in Table Editor Widget now works properly][11332]
- [Fix invisible selection in Table Input Widget][11358]
- [Enable cloud file browser in local projects][11383]
- [Changed the way of adding new column in Table Input Widget][11388]. The
"virtual column" is replaced with an explicit (+) button.
[11151]: https://github.com/enso-org/enso/pull/11151
[11271]: https://github.com/enso-org/enso/pull/11271
[11332]: https://github.com/enso-org/enso/pull/11332
[11358]: https://github.com/enso-org/enso/pull/11358
[11383]: https://github.com/enso-org/enso/pull/11383
[11388]: https://github.com/enso-org/enso/pull/11388
#### Enso Standard Library

View File

@ -26,15 +26,16 @@ test('Doc panel focus (regression #10471)', async ({ page }) => {
await page.keyboard.press(`${CONTROL_KEY}+D`)
await page.keyboard.press(`${CONTROL_KEY}+\``)
await expect(locate.rightDock(page)).toBeVisible()
await expect(locate.bottomDock(page)).toBeVisible()
const codeEditor = page.locator('.CodeEditor')
await expect(codeEditor).toBeVisible()
// Focus code editor.
await locate.bottomDock(page).click()
await codeEditor.click()
await page.evaluate(() => {
const codeEditor = (window as any).__codeEditorApi
const docStart = codeEditor.indexOf('The main method')
codeEditor.placeCursor(docStart + 8)
const codeEditorApi = (window as any).__codeEditorApi
const docStart = codeEditorApi.indexOf('The main method')
codeEditorApi.placeCursor(docStart + 8)
})
await page.keyboard.press('Space')
await page.keyboard.press('T')

View File

@ -58,6 +58,7 @@ test('Copy from Table Visualization', async ({ page, context }) => {
const node = await actions.createTableNode(page)
const widget = node.locator('.WidgetTableEditor')
await expect(widget).toBeVisible()
await widget.getByRole('button', { name: 'Add new column' }).click()
await widget.locator('.ag-cell', { hasNotText: /0/ }).first().click()
await page.keyboard.press('Control+V')
await expect(widget.locator('.ag-cell')).toHaveText([

View File

@ -544,27 +544,32 @@ test('Table widget', async ({ page }) => {
const node = await actions.createTableNode(page)
const widget = node.locator('.WidgetTableEditor')
await expect(widget).toBeVisible()
await expect(widget.locator('.ag-header-cell-text')).toHaveText(['#', 'New Column'])
await expect(widget.locator('.ag-header-cell-text', { hasText: 'New Column' })).toHaveClass(
/(?<=^| )virtualColumn(?=$| )/,
)
// There are two cells, one with row number, second allowing creating first row and column
await expect(widget.locator('.ag-cell')).toHaveCount(2)
await expect(widget.locator('.ag-header-cell-text')).toHaveText(['#'])
await expect(widget.getByRole('button', { name: 'Add new column' })).toExist()
await expect(widget.locator('.ag-cell')).toHaveText(['0', ''])
// Create first column
await widget.getByRole('button', { name: 'Add new column' }).click()
await expect(widget.locator('.ag-header-cell-text')).toHaveText(['#', 'Column #1'])
await expect(widget.locator('.ag-cell')).toHaveText(['0', '', ''])
// Putting first value
await widget.locator('.ag-cell', { hasNotText: '0' }).click()
await widget.locator('.ag-cell', { hasNotText: '0' }).first().click()
await page.keyboard.type('Value')
await page.keyboard.press('Enter')
// There will be new blank column and new blank row allowing adding new columns and rows
// (so 4 cells in total)
await expect(widget.locator('.ag-header-cell-text')).toHaveText(['#', 'Column #0', 'New Column'])
// There will be new blank row allowing adding new rows.
await expect(widget.locator('.ag-cell')).toHaveText(['0', 'Value', '', '1', '', ''])
// Renaming column
await widget.locator('.ag-header-cell-text', { hasText: 'Column #0' }).first().click()
await widget.locator('.ag-header-cell-text', { hasText: 'Column #1' }).first().click()
await page.keyboard.type('Header')
await page.keyboard.press('Enter')
await expect(widget.locator('.ag-header-cell-text')).toHaveText(['#', 'Header', 'New Column'])
await expect(widget.locator('.ag-header-cell-text')).toHaveText(['#', 'Header'])
// Adding next column
await widget.getByRole('button', { name: 'Add new column' }).click()
await expect(widget.locator('.ag-header-cell-text')).toHaveText(['#', 'Header', 'Column #2'])
await expect(widget.locator('.ag-cell')).toHaveText(['0', 'Value', '', '', '1', '', '', ''])
// Switching edit between cells and headers - check we will never edit two things at once.
await expect(widget.locator('.ag-text-field-input')).toHaveCount(0)

View File

@ -9,6 +9,7 @@ import {
import ResizeHandles from '@/components/ResizeHandles.vue'
import AgGridTableView from '@/components/shared/AgGridTableView.vue'
import { injectGraphNavigator } from '@/providers/graphNavigator'
import { useTooltipRegistry } from '@/providers/tooltipState'
import { Score, defineWidget, widgetProps } from '@/providers/widgetRegistry'
import { WidgetEditHandler } from '@/providers/widgetRegistry/editHandler'
import { useGraphStore } from '@/stores/graph'
@ -20,12 +21,13 @@ import '@ag-grid-community/styles/ag-theme-alpine.css'
import type {
CellEditingStartedEvent,
CellEditingStoppedEvent,
ColDef,
Column,
ColumnMovedEvent,
ProcessDataFromClipboardParams,
RowDragEndEvent,
} from 'ag-grid-enterprise'
import { computed, ref } from 'vue'
import { computed, markRaw, ref } from 'vue'
import type { ComponentExposed } from 'vue-component-type-helpers'
const props = defineProps(widgetProps(widgetDefinition))
@ -178,14 +180,22 @@ function processDataFromClipboard({ data, api }: ProcessDataFromClipboardParams<
// === Column Default Definition ===
const defaultColDef = {
const tooltipRegistry = useTooltipRegistry()
const defaultColDef: ColDef<RowData> = {
editable: true,
resizable: true,
sortable: false,
lockPinned: true,
menuTabs: ['generalMenuTab'],
headerComponentParams: {
onHeaderEditingStarted: headerEditHandler.headerEditedInGrid.bind(headerEditHandler),
onHeaderEditingStopped: headerEditHandler.headerEditingStoppedInGrid.bind(headerEditHandler),
// TODO[ao]: we mark raw, because otherwise any change _inside_ tooltipRegistry causes the grid
// to be refreshed. Technically, shallowReactive should work here, but it does not,
// I don't know why
tooltipRegistry: markRaw(tooltipRegistry),
editHandlers: {
onHeaderEditingStarted: headerEditHandler.headerEditedInGrid.bind(headerEditHandler),
onHeaderEditingStopped: headerEditHandler.headerEditingStoppedInGrid.bind(headerEditHandler),
},
},
}
</script>

View File

@ -1,22 +1,39 @@
<script lang="ts">
import SvgButton from '@/components/SvgButton.vue'
import { provideTooltipRegistry, TooltipRegistry } from '@/providers/tooltipState'
import type { IHeaderParams } from 'ag-grid-community'
import { ref, watch } from 'vue'
import { computed, ref, watch } from 'vue'
export interface HeaderEditHandlers {
/** Setter called when column name is changed by the user. */
nameSetter: (newName: string) => void
onHeaderEditingStarted?: (stop: (cancel: boolean) => void) => void
onHeaderEditingStopped?: () => void
}
/**
* A subset of {@link HeaderParams} which is meant to be specified for columns separately
* (not in defaultColumnDef).
*/
export type ColumnSpecificHeaderParams =
| {
type: 'astColumn'
editHandlers: HeaderEditHandlers
}
| { type: 'newColumn'; newColumnRequested: () => void }
| { type: 'rowIndexColumn' }
/**
* Parameters recognized by this header component.
*
* They are set through `headerComponentParams` option in AGGrid column definition.
*/
export interface HeaderParams {
/** Setter called when column name is changed by the user. */
nameSetter?: (newName: string) => void
export type HeaderParams = ColumnSpecificHeaderParams & {
/**
* Column is virtual if it is not represented in the AST. Such column might be used
* to create new one.
* AgGrid mounts header components as separate "App", so we don't have access to any context.
* Threfore the tooltip registry must be provided by props.
*/
virtualColumn?: boolean
onHeaderEditingStarted?: (stop: (cancel: boolean) => void) => void
onHeaderEditingStopped?: () => void
tooltipRegistry: TooltipRegistry
}
</script>
@ -25,17 +42,23 @@ const props = defineProps<{
params: IHeaderParams & HeaderParams
}>()
/** Re-provide tooltipRegistry. See `tooltipRegistry` docs in {@link HeaderParams} */
provideTooltipRegistry.provideConstructed(props.params.tooltipRegistry)
const editing = ref(false)
const inputElement = ref<HTMLInputElement>()
const editHandlers = computed(() =>
props.params.type === 'astColumn' ? props.params.editHandlers : undefined,
)
watch(editing, (newVal) => {
if (newVal) {
props.params.onHeaderEditingStarted?.((cancel: boolean) => {
editHandlers.value?.onHeaderEditingStarted?.((cancel: boolean) => {
if (cancel) editing.value = false
else acceptNewName()
})
} else {
props.params.onHeaderEditingStopped?.()
editHandlers.value?.onHeaderEditingStopped?.()
}
})
@ -48,33 +71,47 @@ watch(inputElement, (newVal, oldVal) => {
})
function acceptNewName() {
if (editHandlers.value == null) {
console.error("Tried to accept header new name where it's not editable!")
return
}
if (inputElement.value == null) {
console.error('Tried to accept header new name without input element!')
return
}
props.params.nameSetter?.(inputElement.value.value)
editHandlers.value.nameSetter(inputElement.value.value)
editing.value = false
}
function onMouseClick() {
if (!editing.value && props.params.nameSetter != null) {
function onMouseClick(event: MouseEvent) {
if (!editing.value && props.params.type === 'astColumn') {
editing.value = true
event.stopPropagation()
}
}
function onMouseRightClick(event: MouseEvent) {
if (!editing.value) {
props.params.showColumnMenuAfterMouseClick(event)
event.preventDefault()
event.stopPropagation()
}
}
</script>
<template>
<SvgButton
v-if="params.type === 'newColumn'"
class="addColumnButton"
name="add"
title="Add new column"
@click.stop="params.newColumnRequested()"
/>
<div
v-else
class="ag-cell-label-container"
role="presentation"
@pointerdown.stop
@click.stop
@click="onMouseClick"
@click.right="onMouseRightClick"
>
@ -93,14 +130,18 @@ function onMouseRightClick(event: MouseEvent) {
<span
v-else
class="ag-header-cell-text"
:class="{ virtualColumn: params.virtualColumn === true }"
:class="{ virtualColumn: params.type !== 'astColumn' }"
>{{ params.displayName }}</span
>
</div>
</div>
</template>
<style>
<style scoped>
.addColumnButton {
margin-left: 10px;
}
.virtualColumn {
color: rgba(0, 0, 0, 0.5);
}

View File

@ -1,4 +1,7 @@
import {
DEFAULT_COLUMN_PREFIX,
NEW_COLUMN_ID,
ROW_INDEX_HEADER,
RowData,
tableNewCallMayBeHandled,
useTableNewArgument,
@ -18,58 +21,61 @@ function suggestionDbWithNothing() {
return db
}
const expectedRowIndexColumnDef = { headerName: ROW_INDEX_HEADER }
const expectedNewColumnDef = { cellStyle: { display: 'none' } }
test.each([
{
code: 'Table.new [["a", [1, 2, 3]], ["b", [4, 5, "six"]], ["empty", [Nothing, Standard.Base.Nothing, Nothing]]]',
expectedColumnDefs: [
{ headerName: '#' },
expectedRowIndexColumnDef,
{ headerName: 'a' },
{ headerName: 'b' },
{ headerName: 'empty' },
{ headerName: 'New Column' },
expectedNewColumnDef,
],
expectedRows: [
{ '#': 0, a: 1, b: 4, empty: null, 'New Column': null },
{ '#': 1, a: 2, b: 5, empty: null, 'New Column': null },
{ '#': 2, a: 3, b: 'six', empty: null, 'New Column': null },
{ '#': 3, a: null, b: null, empty: null, 'New Column': null },
{ [ROW_INDEX_HEADER]: 0, a: 1, b: 4, empty: null, '': null },
{ [ROW_INDEX_HEADER]: 1, a: 2, b: 5, empty: null, '': null },
{ [ROW_INDEX_HEADER]: 2, a: 3, b: 'six', empty: null, '': null },
{ [ROW_INDEX_HEADER]: 3, a: null, b: null, empty: null, '': null },
],
},
{
code: 'Table.new []',
expectedColumnDefs: [{ headerName: '#' }, { headerName: 'New Column' }],
expectedRows: [{ '#': 0, 'New Column': null }],
expectedColumnDefs: [expectedRowIndexColumnDef, expectedNewColumnDef],
expectedRows: [{ [ROW_INDEX_HEADER]: 0, '': null }],
},
{
code: 'Table.new',
expectedColumnDefs: [{ headerName: '#' }, { headerName: 'New Column' }],
expectedRows: [{ '#': 0, 'New Column': null }],
expectedColumnDefs: [expectedRowIndexColumnDef, expectedNewColumnDef],
expectedRows: [{ [ROW_INDEX_HEADER]: 0, '': null }],
},
{
code: 'Table.new _',
expectedColumnDefs: [{ headerName: '#' }, { headerName: 'New Column' }],
expectedRows: [{ '#': 0, 'New Column': null }],
expectedColumnDefs: [expectedRowIndexColumnDef, expectedNewColumnDef],
expectedRows: [{ [ROW_INDEX_HEADER]: 0, '': null }],
},
{
code: 'Table.new [["a", []]]',
expectedColumnDefs: [{ headerName: '#' }, { headerName: 'a' }, { headerName: 'New Column' }],
expectedRows: [{ '#': 0, a: null, 'New Column': null }],
expectedColumnDefs: [expectedRowIndexColumnDef, { headerName: 'a' }, expectedNewColumnDef],
expectedRows: [{ [ROW_INDEX_HEADER]: 0, a: null, '': null }],
},
{
code: 'Table.new [["a", [1,,2]], ["b", [3, 4,]], ["c", [, 5, 6]], ["d", [,,]]]',
expectedColumnDefs: [
{ headerName: '#' },
expectedRowIndexColumnDef,
{ headerName: 'a' },
{ headerName: 'b' },
{ headerName: 'c' },
{ headerName: 'd' },
{ headerName: 'New Column' },
expectedNewColumnDef,
],
expectedRows: [
{ '#': 0, a: 1, b: 3, c: null, d: null, 'New Column': null },
{ '#': 1, a: null, b: 4, c: 5, d: null, 'New Column': null },
{ '#': 2, a: 2, b: null, c: 6, d: null, 'New Column': null },
{ '#': 3, a: null, b: null, c: null, d: null, 'New Column': null },
{ [ROW_INDEX_HEADER]: 0, a: 1, b: 3, c: null, d: null, '': null },
{ [ROW_INDEX_HEADER]: 1, a: null, b: 4, c: 5, d: null, '': null },
{ [ROW_INDEX_HEADER]: 2, a: 2, b: null, c: 6, d: null, '': null },
{ [ROW_INDEX_HEADER]: 3, a: null, b: null, c: null, d: null, '': null },
],
},
])('Read table from $code', ({ code, expectedColumnDefs, expectedRows }) => {
@ -172,32 +178,7 @@ test.each([
expected: "Table.new [['a', [1, 2, 3]], ['b', [4, Nothing, 6]]]",
importExpected: true,
},
{
code: "Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]]]",
description: 'Add new column',
edit: { column: 3, row: 1, value: 8 },
expected:
"Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]], ['Column #2', [Nothing, 8, Nothing]]]",
importExpected: true,
},
{
code: 'Table.new []',
description: 'Add first column',
edit: { column: 1, row: 0, value: 8 },
expected: "Table.new [['Column #0', [8]]]",
},
{
code: 'Table.new',
description: 'Add parameter',
edit: { column: 1, row: 0, value: 8 },
expected: "Table.new [['Column #0', [8]]]",
},
{
code: 'Table.new _',
description: 'Update parameter',
edit: { column: 1, row: 0, value: 8 },
expected: "Table.new [['Column #0', [8]]]",
},
{
code: "Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]]]",
description: 'Add new row',
@ -212,14 +193,6 @@ test.each([
expected: "Table.new [['a', [Nothing]], ['b', ['val']]]",
importExpected: true,
},
{
code: "Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]]]",
description: 'Add new row and column (the cell in the corner)',
edit: { column: 3, row: 3, value: 7 },
expected:
"Table.new [['a', [1, 2, 3, Nothing]], ['b', [4, 5, 6, Nothing]], ['Column #2', [Nothing, Nothing, Nothing, 7]]]",
importExpected: true,
},
{
code: "Table.new [['a', [1, ,3]]]",
description: 'Set missing value',
@ -257,6 +230,38 @@ test.each([
else expect(addMissingImports).not.toHaveBeenCalled()
})
test.each([
{
code: "Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]]]",
expected: `Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]], ['${DEFAULT_COLUMN_PREFIX}3', [Nothing, Nothing, Nothing]]]`,
importExpected: true,
},
{
code: 'Table.new []',
expected: `Table.new [['${DEFAULT_COLUMN_PREFIX}1', []]]`,
},
{
code: 'Table.new',
expected: `Table.new [['${DEFAULT_COLUMN_PREFIX}1', []]]`,
},
{
code: 'Table.new _',
expected: `Table.new [['${DEFAULT_COLUMN_PREFIX}1', []]]`,
},
])('Add column to table $code', ({ code, expected, importExpected }) => {
const { tableNewArgs, onUpdate, addMissingImports } = tableEditFixture(code, expected)
const newColumnDef = tableNewArgs.columnDefs.value.find(
(colDef) => colDef.colId === NEW_COLUMN_ID,
)
assert(newColumnDef != null)
assert(newColumnDef.headerComponentParams?.type === 'newColumn')
assert(newColumnDef.headerComponentParams.newColumnRequested != null)
newColumnDef.headerComponentParams.newColumnRequested()
expect(onUpdate).toHaveBeenCalledOnce()
if (importExpected) expect(addMissingImports).toHaveBeenCalled()
else expect(addMissingImports).not.toHaveBeenCalled()
})
function getCustomMenuItemByName(
name: string,
items:
@ -405,7 +410,7 @@ test.each([
['1', '3'],
['2', '4'],
],
expected: "Table.new [['Column #0', [1, 2]], ['Column #1', [3, 4]]]",
expected: `Table.new [['${DEFAULT_COLUMN_PREFIX}1', [1, 2]], ['${DEFAULT_COLUMN_PREFIX}2', [3, 4]]]`,
},
{
code: 'Table.new []',
@ -414,13 +419,13 @@ test.each([
['1', '3'],
['2', '4'],
],
expected: "Table.new [['Column #0', [1, 2]], ['Column #1', [3, 4]]]",
expected: `Table.new [['${DEFAULT_COLUMN_PREFIX}1', [1, 2]], ['${DEFAULT_COLUMN_PREFIX}2', [3, 4]]]`,
},
{
code: 'Table.new []',
focused: { rowIndex: 0, colIndex: 1 },
data: [['a single cell']],
expected: "Table.new [['Column #0', ['a single cell']]]",
expected: `Table.new [['${DEFAULT_COLUMN_PREFIX}1', ['a single cell']]]`,
},
{
code: "Table.new [['a', [1, 2]], ['b', [3, 4]]]",
@ -445,7 +450,7 @@ test.each([
code: "Table.new [['a', [1, 2]], ['b', [3, 4]]]",
focused: { rowIndex: 1, colIndex: 3 },
data: [['a single cell']],
expected: "Table.new [['a', [1, 2]], ['b', [3, 4]], ['Column #2', [Nothing, 'a single cell']]]",
expected: `Table.new [['a', [1, 2]], ['b', [3, 4]], ['${DEFAULT_COLUMN_PREFIX}3', [Nothing, 'a single cell']]]`,
importExpected: true,
},
{
@ -473,7 +478,7 @@ test.each([
['5', '7'],
['6', '8'],
],
expected: "Table.new [['a', [1, 2]], ['b', [5, 6]], ['Column #2', [7, 8]]]",
expected: `Table.new [['a', [1, 2]], ['b', [5, 6]], ['${DEFAULT_COLUMN_PREFIX}3', [7, 8]]]`,
},
{
code: "Table.new [['a', [1, 2]], ['b', [3, 4]]]",
@ -482,19 +487,17 @@ test.each([
['5', '7'],
['6', '8'],
],
expected:
"Table.new [['a', [1, 2, Nothing]], ['b', [3, 5, 6]], ['Column #2', [Nothing, 7, 8]]]",
expected: `Table.new [['a', [1, 2, Nothing]], ['b', [3, 5, 6]], ['${DEFAULT_COLUMN_PREFIX}3', [Nothing, 7, 8]]]`,
importExpected: true,
},
{
code: "Table.new [['a', [1, 2]], ['b', [3, 4]]]",
focused: { rowIndex: 2, colIndex: 3 },
focused: { rowIndex: 2, colIndex: 2 },
data: [
['5', '7'],
['6', '8'],
],
expected:
"Table.new [['a', [1, 2, Nothing, Nothing]], ['b', [3, 4, Nothing, Nothing]], ['Column #2', [Nothing, Nothing, 5, 6]], ['Column #3', [Nothing, Nothing, 7, 8]]]",
expected: `Table.new [['a', [1, 2, Nothing, Nothing]], ['b', [3, 4, 5, 6]], ['${DEFAULT_COLUMN_PREFIX}3', [Nothing, Nothing, 7, 8]]]`,
importExpected: true,
},
])(
@ -502,7 +505,6 @@ test.each([
({ code, focused, data, expected, importExpected }) => {
const { tableNewArgs, onUpdate, addMissingImports } = tableEditFixture(code, expected)
const focusedCol = tableNewArgs.columnDefs.value[focused.colIndex]
console.log(focusedCol?.colId, focusedCol?.headerName)
assert(focusedCol?.colId != null)
tableNewArgs.pasteFromClipboard(data, {
rowIndex: focused.rowIndex,

View File

@ -12,12 +12,15 @@ import { qnLastSegment, type QualifiedName } from '@/util/qualifiedName'
import type { ToValue } from '@/util/reactivity'
import type { ColDef } from 'ag-grid-enterprise'
import { computed, toValue } from 'vue'
import { ColumnSpecificHeaderParams } from './TableHeader.vue'
const NEW_COLUMN_ID = 'NewColumn'
/** Id of a fake column with "Add new column" option. */
export const NEW_COLUMN_ID = 'NewColumn'
const ROW_INDEX_COLUMN_ID = 'RowIndex'
const NEW_COLUMN_HEADER = 'New Column'
const ROW_INDEX_HEADER = '#'
const DEFAULT_COLUMN_PREFIX = 'Column #'
/** A header of Row Index Column. */
export const ROW_INDEX_HEADER = '#'
/** A default prefix added to the column's index in newly created columns. */
export const DEFAULT_COLUMN_PREFIX = 'Column #'
const NOTHING_PATH = 'Standard.Base.Nothing.Nothing' as QualifiedName
const NOTHING_NAME = qnLastSegment(NOTHING_PATH)
@ -37,6 +40,7 @@ export interface ColumnDef extends ColDef<RowData> {
mainMenuItems: (string | MenuItem<RowData>)[]
contextMenuItems: (string | MenuItem<RowData>)[]
rowDrag?: ({ data }: { data: RowData | undefined }) => boolean
headerComponentParams?: ColumnSpecificHeaderParams
}
namespace cellValueConversion {
@ -260,34 +264,26 @@ export function useTableNewArgument(
const newColumnDef = computed<ColumnDef>(() => ({
colId: NEW_COLUMN_ID,
headerName: NEW_COLUMN_HEADER,
headerName: '',
valueGetter: () => null,
valueSetter: ({ data, newValue }: { data: RowData; newValue: any }) => {
const edit = graph.startEdit()
if (data.index === rowCount.value) {
addRow(edit)
}
addColumn(
edit,
`${DEFAULT_COLUMN_PREFIX}${columns.value.length}`,
(index) => (index === data.index ? newValue : null),
Math.max(rowCount.value, data.index + 1),
)
onUpdate({ edit })
return true
},
editable: false,
resizable: false,
suppressNavigable: true,
width: 40,
maxWidth: 40,
headerComponentParams: {
nameSetter: (newName: string) => {
type: 'newColumn',
newColumnRequested: () => {
const edit = graph.startEdit()
fixColumns(edit)
addColumn(edit, newName)
addColumn(edit, `${DEFAULT_COLUMN_PREFIX}${columns.value.length + 1}`)
onUpdate({ edit })
},
virtualColumn: true,
},
mainMenuItems: ['autoSizeThis', 'autoSizeAll'],
contextMenuItems: [commonContextMenuActions.paste, 'separator', removeRowMenuItem],
contextMenuItems: [removeRowMenuItem],
lockPosition: 'right',
cellStyle: { display: 'none' },
}))
const rowIndexColumnDef = computed<ColumnDef>(() => ({
@ -295,6 +291,11 @@ export function useTableNewArgument(
headerName: ROW_INDEX_HEADER,
valueGetter: ({ data }: { data: RowData | undefined }) => data?.index,
editable: false,
resizable: false,
suppressNavigable: true,
headerComponentParams: {
type: 'rowIndexColumn',
},
mainMenuItems: ['autoSizeThis', 'autoSizeAll'],
contextMenuItems: [removeRowMenuItem],
cellStyle: { color: 'rgba(0, 0, 0, 0.4)' },
@ -338,11 +339,14 @@ export function useTableNewArgument(
return true
},
headerComponentParams: {
nameSetter: (newName: string) => {
const edit = graph.startEdit()
fixColumns(edit)
edit.getVersion(col.name).setRawTextContent(newName)
onUpdate({ edit })
type: 'astColumn',
editHandlers: {
nameSetter: (newName: string) => {
const edit = graph.startEdit()
fixColumns(edit)
edit.getVersion(col.name).setRawTextContent(newName)
onUpdate({ edit })
},
},
},
mainMenuItems: ['autoSizeThis', 'autoSizeAll', removeColumnMenuItem(col.id)],
@ -357,7 +361,7 @@ export function useTableNewArgument(
'separator',
'export',
],
}) satisfies ColDef<RowData>,
}) satisfies ColumnDef,
)
cols.unshift(rowIndexColumnDef.value)
cols.push(newColumnDef.value)
@ -470,7 +474,7 @@ export function useTableNewArgument(
for (let i = columns.value.length; i < newColCount; ++i) {
modifiedColumnsAst = addColumn(
edit,
`${DEFAULT_COLUMN_PREFIX}${i}`,
`${DEFAULT_COLUMN_PREFIX}${i + 1}`,
(index) => newValueGetter(index, i),
newRowCount,
modifiedColumnsAst,

View File

@ -6,7 +6,6 @@ import { toRef, useSlots } from 'vue'
usePropagateScopesToAllRoots()
const registry = useTooltipRegistry()
const slots = useSlots()
const tooltipSlot = toRef(slots, 'tooltip')
const registered = registry.registerTooltip(tooltipSlot)

View File

@ -46,9 +46,10 @@ export function createContextStore<F extends (...args: any[]) => any>(name: stri
/**
* An method allowing to directly provide a store value to any app or component context, possibly
* skipping invoking the factory function. **Do not use in application code.**
* skipping invoking the factory function. **Use in tests only, or in exceptional situations
* (like in TableHeader.vue)**.
*/
provideFn._mock = function (
provideFn.provideConstructed = function (
valueOrArgs: Parameters<F> | ReturnType<F>,
app?: App,
): ReturnType<F> {