fix(editor): editor behavior and styles (#14498)

fix #14269 
fix #13920
fix #13977
fix #13953
fix #13895
fix #13905
fix #14136
fix #14357
fix #14491

#### PR Dependency Tree


* **PR #14498** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

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

* **Bug Fixes**
  * Callout and toolbar defaults now reliably show grey backgrounds
  * Keyboard shortcuts behave better across layouts and non-ASCII input
  * Deleted workspaces no longer appear in local listings

* **New Features**
  * Cell editing now respects pre-entry validation hooks
* Scrollbars use themeable variables and include Chromium compatibility
fixes

* **Style**
  * Minor UI color adjustment for hidden properties

* **Tests**
  * Added unit tests for table column handling and keymap behavior
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-02-23 06:37:16 +08:00
committed by GitHub
parent ad988dbd1e
commit ef6717e59a
13 changed files with 309 additions and 13 deletions

View File

@@ -6,10 +6,12 @@ import {
NumberFormatSchema,
parseNumber,
} from '../property-presets/number/utils/formatter.js';
import { DEFAULT_COLUMN_WIDTH } from '../view-presets/table/consts.js';
import { mobileEffects } from '../view-presets/table/mobile/effect.js';
import type { MobileTableGroup } from '../view-presets/table/mobile/group.js';
import { pcEffects } from '../view-presets/table/pc/effect.js';
import type { TableGroup } from '../view-presets/table/pc/group.js';
import { materializeTableColumns } from '../view-presets/table/table-view-manager.js';
/** @vitest-environment happy-dom */
@@ -41,6 +43,56 @@ describe('TableGroup', () => {
});
});
describe('table column materialization', () => {
test('appends missing properties while preserving existing order and state', () => {
const columns = [
{ id: 'status', width: 240, hide: true },
{ id: 'title', width: 320 },
];
const next = materializeTableColumns(columns, ['title', 'status', 'date']);
expect(next).toEqual([
{ id: 'status', width: 240, hide: true },
{ id: 'title', width: 320 },
{ id: 'date', width: DEFAULT_COLUMN_WIDTH },
]);
});
test('drops stale columns that no longer exist in data source', () => {
const columns = [
{ id: 'title', width: 320 },
{ id: 'removed', width: 200, hide: true },
];
const next = materializeTableColumns(columns, ['title']);
expect(next).toEqual([{ id: 'title', width: 320 }]);
});
test('returns original reference when columns are already materialized', () => {
const columns = [
{ id: 'title', width: 320 },
{ id: 'status', width: 240, hide: true },
];
const next = materializeTableColumns(columns, ['title', 'status']);
expect(next).toBe(columns);
});
test('supports type-aware default width when materializing missing columns', () => {
const next = materializeTableColumns([], ['title', 'status'], id =>
id === 'title' ? 260 : DEFAULT_COLUMN_WIDTH
);
expect(next).toEqual([
{ id: 'title', width: 260 },
{ id: 'status', width: DEFAULT_COLUMN_WIDTH },
]);
});
});
describe('number formatter', () => {
test('number format menu should expose all schema formats', () => {
const menuFormats = numberFormats.map(format => format.type);

View File

@@ -54,7 +54,9 @@ export class DatabaseCellContainer extends SignalWatcher(
const selectionView = this.selectionView;
if (selectionView) {
const selection = selectionView.selection;
if (selection && this.isSelected(selection) && editing) {
const shouldEnterEditMode =
editing && this.cell?.beforeEnterEditMode() !== false;
if (selection && this.isSelected(selection) && shouldEnterEditMode) {
selectionView.selection = TableViewAreaSelection.create({
groupKey: this.groupKey,
focus: {

View File

@@ -57,7 +57,9 @@ export class TableViewCellContainer extends SignalWatcher(
const selectionView = this.selectionController;
if (selectionView) {
const selection = selectionView.selection;
if (selection && this.isSelected(selection) && editing) {
const shouldEnterEditMode =
editing && this.cell?.beforeEnterEditMode() !== false;
if (selection && this.isSelected(selection) && shouldEnterEditMode) {
selectionView.selection = TableViewAreaSelection.create({
groupKey: this.groupKey,
focus: {

View File

@@ -26,6 +26,52 @@ import type { ViewManager } from '../../core/view-manager/view-manager.js';
import { DEFAULT_COLUMN_MIN_WIDTH, DEFAULT_COLUMN_WIDTH } from './consts.js';
import type { TableViewData } from './define.js';
export const materializeColumnsByPropertyIds = (
columns: TableColumnData[],
propertyIds: string[],
getDefaultWidth: (id: string) => number = () => DEFAULT_COLUMN_WIDTH
) => {
const needShow = new Set(propertyIds);
const orderedColumns: TableColumnData[] = [];
for (const column of columns) {
if (needShow.has(column.id)) {
orderedColumns.push(column);
needShow.delete(column.id);
}
}
for (const id of needShow) {
orderedColumns.push({ id, width: getDefaultWidth(id), hide: undefined });
}
return orderedColumns;
};
export const materializeTableColumns = (
columns: TableColumnData[],
propertyIds: string[],
getDefaultWidth?: (id: string) => number
) => {
const nextColumns = materializeColumnsByPropertyIds(
columns,
propertyIds,
getDefaultWidth
);
const unchanged =
columns.length === nextColumns.length &&
columns.every((column, index) => {
const nextColumn = nextColumns[index];
return (
nextColumn != null &&
column.id === nextColumn.id &&
column.hide === nextColumn.hide
);
});
return unchanged ? columns : nextColumns;
};
export class TableSingleView extends SingleViewBase<TableViewData> {
propertiesRaw$ = computed(() => {
const needShow = new Set(this.dataSource.properties$.value);
@@ -220,10 +266,6 @@ export class TableSingleView extends SingleViewBase<TableViewData> {
return this.data$.value?.mode ?? 'table';
}
constructor(viewManager: ViewManager, viewId: string) {
super(viewManager, viewId);
}
isShow(rowId: string): boolean {
if (this.filter$.value?.conditions.length) {
const rowMap = Object.fromEntries(
@@ -290,6 +332,33 @@ export class TableSingleView extends SingleViewBase<TableViewData> {
});
}
);
private materializeColumns() {
const data = this.data$.value;
if (!data) {
return;
}
const nextColumns = materializeTableColumns(
data.columns,
this.dataSource.properties$.value,
id => this.propertyGetOrCreate(id).width$.value
);
if (nextColumns === data.columns) {
return;
}
this.dataUpdate(() => ({ columns: nextColumns }));
}
constructor(viewManager: ViewManager, viewId: string) {
super(viewManager, viewId);
// Materialize view columns on view activation so newly added properties
// can participate in hide/order operations in table.
queueMicrotask(() => {
this.materializeColumns();
});
}
}
type TableColumnData = TableViewData['columns'][number];