fix(editor): show added or deleted rows immediately in grouped table and Kanban views (#12731)

https://github.com/user-attachments/assets/214fbe4f-b667-44b7-85a3-77ef4cfa8cca

This PR fixes a bug where adding or deleting rows in a grouped table
view did not visually update the UI until the user manually refreshed
the page or navigated away and back. The issue gave the impression that
the action had not completed.

Same issue for Kanban cards.

The result now is:
Users now see new rows or deleted rows reflected in real-time without
needing to reload or navigate away. This applies to both grouped table
views and Kanban cards.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Bug Fixes**
- Ensured the UI updates immediately after adding, deleting, or moving
cards and rows in Kanban and Table views on both mobile and desktop.
- Fixed issues where UI changes were not reflected after certain
actions, such as ungrouping, deleting, or inserting items.
- Improved row locking behavior during add and delete operations to
prevent UI inconsistencies.
- **Tests**
- Added comprehensive tests for row operations and menu interactions to
verify UI updates and correct method calls in data views.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: zzj3720 <zuozijian1994@gmail.com>
This commit is contained in:
Richard Lora
2025-06-12 04:02:37 -04:00
committed by GitHub
parent d2664480f7
commit 8ca17864f1
22 changed files with 174 additions and 2 deletions

View File

@@ -112,7 +112,10 @@ export class GroupTrait {
return;
}
const { staticMap, groupInfo } = staticInfo;
const groupMap: Record<string, Group> = { ...staticMap };
const groupMap: Record<string, Group> = {};
Object.entries(staticMap).forEach(([key, group]) => {
groupMap[key] = new Group(key, group.value, groupInfo, this);
});
this.view.rows$.value.forEach(row => {
const value = this.view.cellGetOrCreate(row.rowId, groupInfo.property.id)
.jsonValue$.value;
@@ -182,6 +185,7 @@ export class GroupTrait {
) {}
addToGroup(rowId: string, key: string) {
this.view.lockRows(false);
const groupMap = this.groupDataMap$.value;
const groupInfo = this.groupInfo$.value;
if (!groupMap || !groupInfo) {
@@ -254,6 +258,7 @@ export class GroupTrait {
toGroupKey: string,
position: InsertToPosition
) {
this.view.lockRows(false);
const groupMap = this.groupDataMap$.value;
if (!groupMap) {
return;
@@ -290,6 +295,7 @@ export class GroupTrait {
}
moveGroupTo(groupKey: string, position: InsertToPosition) {
this.view.lockRows(false);
const groups = this.groupsDataList$.value;
if (!groups) {
return;
@@ -305,6 +311,7 @@ export class GroupTrait {
}
removeFromGroup(rowId: string, key: string) {
this.view.lockRows(false);
const groupMap = this.groupDataMap$.value;
if (!groupMap) {
return;
@@ -323,6 +330,7 @@ export class GroupTrait {
}
updateValue(rows: string[], value: unknown) {
this.view.lockRows(false);
const propertyId = this.property$.value?.id;
if (!propertyId) {
return;

View File

@@ -128,6 +128,7 @@ export abstract class SingleViewBase<
);
rowsDelete(rows: string[]): void {
this.lockRows(false);
this.dataSource.rowDelete(rows);
}
@@ -258,6 +259,7 @@ export abstract class SingleViewBase<
abstract propertyGetOrCreate(propertyId: string): Property;
rowAdd(insertPosition: InsertToPosition | number): string {
this.lockRows(false);
return this.dataSource.rowAdd(insertPosition);
}

View File

@@ -61,10 +61,12 @@ export class MobileKanbanGroup extends SignalWatcher(
private readonly clickAddCard = () => {
this.view.addCard('end', this.group.key);
this.requestUpdate();
};
private readonly clickAddCardInStart = () => {
this.view.addCard('start', this.group.key);
this.requestUpdate();
};
private readonly clickGroupOptions = (e: MouseEvent) => {
@@ -79,12 +81,14 @@ export class MobileKanbanGroup extends SignalWatcher(
this.group.rows.forEach(row => {
this.group.manager.removeFromGroup(row.rowId, this.group.key);
});
this.requestUpdate();
},
}),
menu.action({
name: 'Delete Cards',
select: () => {
this.view.rowsDelete(this.group.rows.map(row => row.rowId));
this.requestUpdate();
},
}),
],

View File

@@ -66,7 +66,9 @@ export class MobileKanbanViewUILogic extends DataViewUILogicBase<
addRow = (position: InsertToPosition) => {
if (this.readonly) return;
return this.view.rowAdd(position);
const id = this.view.rowAdd(position);
this.ui$.value?.requestUpdate();
return id;
};
focusFirstCell = () => {};

View File

@@ -83,6 +83,7 @@ export const popCardMenu = (
{ before: true, id: cardId },
groupKey
);
kanbanViewLogic.ui$.value?.requestUpdate();
},
}),
menu.action({
@@ -97,6 +98,7 @@ export const popCardMenu = (
{ before: false, id: cardId },
groupKey
);
kanbanViewLogic.ui$.value?.requestUpdate();
},
}),
],
@@ -111,6 +113,7 @@ export const popCardMenu = (
prefix: DeleteIcon(),
select: () => {
kanbanViewLogic.view.rowsDelete([cardId]);
kanbanViewLogic.ui$.value?.requestUpdate();
},
}),
],

View File

@@ -128,6 +128,7 @@ export class KanbanSelectionController implements ReactiveController {
if (selection.selectionType === 'card') {
this.view.rowsDelete(selection.cards.map(v => v.cardId));
this.selection = undefined;
this.logic.ui$.value?.requestUpdate();
}
}

View File

@@ -110,6 +110,7 @@ export class KanbanGroup extends SignalWatcher(
isEditing: true,
};
});
this.requestUpdate();
};
private readonly clickAddCardInStart = () => {
@@ -127,6 +128,7 @@ export class KanbanGroup extends SignalWatcher(
isEditing: true,
};
});
this.requestUpdate();
};
private readonly clickGroupOptions = (e: MouseEvent) => {
@@ -139,12 +141,14 @@ export class KanbanGroup extends SignalWatcher(
this.group.rows.forEach(row => {
this.group.manager.removeFromGroup(row.rowId, this.group.key);
});
this.requestUpdate();
},
}),
menu.action({
name: 'Delete Cards',
select: () => {
this.view.rowsDelete(this.group.rows.map(row => row.rowId));
this.requestUpdate();
},
}),
]);

View File

@@ -73,6 +73,7 @@ export class KanbanViewUILogic extends DataViewUILogicBase<
rowId,
});
}
this.ui$.value?.requestUpdate();
return rowId;
};

View File

@@ -51,10 +51,12 @@ export class MobileTableGroup extends SignalWatcher(
private readonly clickAddRow = () => {
this.view.rowAdd('end', this.group?.key);
this.requestUpdate();
};
private readonly clickAddRowInStart = () => {
this.view.rowAdd('start', this.group?.key);
this.requestUpdate();
};
private readonly clickGroupOptions = (e: MouseEvent) => {
@@ -77,6 +79,7 @@ export class MobileTableGroup extends SignalWatcher(
name: 'Delete Cards',
select: () => {
this.view.rowsDelete(group.rows.map(row => row.rowId));
this.requestUpdate();
},
}),
]);

View File

@@ -38,6 +38,7 @@ export const popMobileRowMenu = (
prefix: DeleteIcon(),
select: () => {
view.rowsDelete([rowId]);
tableViewLogic.ui$.value?.requestUpdate();
},
}),
],

View File

@@ -44,6 +44,7 @@ export class TableClipboardController implements ReactiveController {
}
if (deleteRows.length) {
this.logic.view.rowsDelete(deleteRows);
this.logic.ui$.value?.requestUpdate();
}
}
this.clipboard

View File

@@ -30,6 +30,7 @@ export class TableHotkeysController implements ReactiveController {
const rows = TableViewRowSelection.rowsIds(selection);
this.selectionController.selection = undefined;
this.logic.view.rowsDelete(rows);
this.logic.ui$.value?.requestUpdate();
return;
}
const {

View File

@@ -376,6 +376,7 @@ export class TableSelectionController implements ReactiveController {
deleteRow(rowId: string) {
this.view.rowsDelete([rowId]);
this.focusToCell('up');
this.logic.ui$.value?.requestUpdate();
}
focusFirstCell() {

View File

@@ -45,6 +45,7 @@ export class TableGroupFooter extends WithDisposable(ShadowlessElement) {
private readonly clickAddRow = () => {
const group = this.group$.value;
const rowId = this.tableViewManager.rowAdd('end', group?.key);
this.requestUpdate();
requestAnimationFrame(() => {
const rowIndex = this.selectionController.getRow(group?.key, rowId)

View File

@@ -58,6 +58,7 @@ export class TableGroupHeader extends SignalWatcher(
return;
}
this.tableViewManager.rowAdd('start', group.key);
this.requestUpdate();
const selectionController = this.selectionController;
selectionController.selection = undefined;
requestAnimationFrame(() => {
@@ -95,6 +96,7 @@ export class TableGroupHeader extends SignalWatcher(
name: 'Delete Cards',
select: () => {
this.tableViewManager.rowsDelete(group.rows.map(row => row.rowId));
this.requestUpdate();
},
}),
]);

View File

@@ -71,6 +71,7 @@ export const popRowMenu = (
prefix: DeleteIcon(),
select: () => {
selectionController.view.rowsDelete(rows);
selectionController.logic.ui$.value?.requestUpdate();
},
}),
],

View File

@@ -43,6 +43,7 @@ export class TableClipboardController implements ReactiveController {
}
if (deleteRows.length) {
this.logic.view.rowsDelete(deleteRows);
this.logic.ui$.value?.requestUpdate();
}
}
this.clipboard

View File

@@ -28,6 +28,7 @@ export class TableHotkeysController implements ReactiveController {
const rows = TableViewRowSelection.rowsIds(selection);
this.selectionController.selection = undefined;
this.logic.view.rowsDelete(rows);
this.logic.ui$.value?.requestUpdate();
return;
}
const {

View File

@@ -351,6 +351,7 @@ export class TableSelectionController implements ReactiveController {
deleteRow(rowId: string) {
this.view.rowsDelete([rowId]);
this.focusToCell('up');
this.logic.ui$.value?.requestUpdate();
}
focusFirstCell() {

View File

@@ -83,6 +83,7 @@ export class TableGroup extends SignalWatcher(
},
isEditing: true,
});
this.requestUpdate();
});
};
@@ -102,6 +103,7 @@ export class TableGroup extends SignalWatcher(
},
isEditing: true,
});
this.requestUpdate();
});
};
@@ -125,6 +127,7 @@ export class TableGroup extends SignalWatcher(
name: 'Delete Cards',
select: () => {
this.view.rowsDelete(group.rows.map(row => row.rowId));
this.requestUpdate();
},
}),
]);

View File

@@ -71,6 +71,7 @@ export const popRowMenu = (
prefix: DeleteIcon(),
select: () => {
selectionController.view.rowsDelete(rows);
selectionController.logic.ui$.value?.requestUpdate();
},
}),
],