Files
AFFiNE-Mirror/blocksuite/affine/data-view/src/view-presets/table/mobile/cell.ts
T
zzj3720 0f1a3c212d refactor(editor): add a layer of ui-logic to enhance type safety (#12511)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced modular UI logic layers for Kanban and Table views, enhancing maintainability and scalability.
  - Added new CSS-in-JS style modules for database blocks and table views, improving visual consistency.
  - Expanded telemetry event tracking for database views, properties, filters, and groups.
  - Added utility functions for lazy initialization and cached computed values.

- **Refactor**
  - Unified logic and state management across Kanban and Table views by replacing direct component dependencies with logic-centric architecture.
  - Updated components and widgets to use the new logic-based approach for state, selection, and event handling.
  - Replaced inline styles with CSS classes; updated class names to align with new component structure.
  - Centralized state access through UI logic instances, eliminating direct DOM queries and simplifying dependencies.
  - Consolidated Kanban and Table view presets effects for streamlined initialization.
  - Replaced Lit reactive state with Preact signals in multiple components for improved reactivity.
  - Split monolithic components into separate logic and UI classes for clearer separation of concerns.
  - Removed obsolete components and consolidated exports for cleaner API surface.

- **Bug Fixes**
  - Enhanced selection and interaction reliability in database cells and views.
  - Fixed scrolling issues on mobile table views for improved compatibility.

- **Chores**
  - Updated end-to-end test selectors to reflect new component names and structure.
  - Removed deprecated utilities and cleaned up unused imports.

- **Documentation**
  - Improved type definitions and public API exports for better developer experience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-27 09:36:44 +00:00

171 lines
4.2 KiB
TypeScript

import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
import { ShadowlessElement } from '@blocksuite/std';
import { computed, effect, signal } from '@preact/signals-core';
import { css } from 'lit';
import { property } from 'lit/decorators.js';
import {
type CellRenderProps,
type DataViewCellLifeCycle,
renderUniLit,
} from '../../../core/index.js';
import { TableViewAreaSelection } from '../selection';
import type { TableProperty } from '../table-view-manager.js';
import type { MobileTableViewUILogic } from './table-view-ui-logic.js';
export class MobileTableCell extends SignalWatcher(
WithDisposable(ShadowlessElement)
) {
static override styles = css`
mobile-table-cell {
display: flex;
align-items: start;
width: 100%;
height: 100%;
border: none;
outline: none;
}
mobile-table-cell * {
box-sizing: border-box;
}
mobile-table-cell uni-lit > *:first-child {
padding: 6px;
}
`;
private readonly _cell = signal<DataViewCellLifeCycle>();
@property({ attribute: false })
accessor column!: TableProperty;
@property({ attribute: false })
accessor rowId!: string;
cell$ = computed(() => {
return this.column.cellGetOrCreate(this.rowId);
});
isSelectionEditing$ = computed(() => {
const selection = this.tableViewLogic.selection$.value;
if (selection?.selectionType !== 'area') {
return false;
}
if (selection.groupKey !== this.groupKey) {
return false;
}
if (selection.focus.columnIndex !== this.columnIndex) {
return false;
}
if (selection.focus.rowIndex !== this.rowIndex) {
return false;
}
return selection.isEditing;
});
selectCurrentCell = (editing: boolean) => {
if (this.view.readonly$.value) {
return;
}
const setSelection = this.tableViewLogic.setSelection;
const viewId = this.tableViewLogic.view.id;
if (setSelection && viewId) {
if (editing && this.cell?.beforeEnterEditMode() === false) {
return;
}
setSelection({
viewId,
type: 'table',
...TableViewAreaSelection.create({
groupKey: this.groupKey,
focus: {
rowIndex: this.rowIndex,
columnIndex: this.columnIndex,
},
isEditing: editing,
}),
});
}
};
get cell(): DataViewCellLifeCycle | undefined {
return this._cell.value;
}
private get groupKey() {
return this.closest('mobile-table-group')?.group?.key;
}
override connectedCallback() {
super.connectedCallback();
if (this.column.readonly$.value) return;
this.disposables.add(
effect(() => {
const isEditing = this.isSelectionEditing$.value;
if (isEditing) {
this.isEditing$.value = true;
const cell = this._cell.value;
requestAnimationFrame(() => {
cell?.afterEnterEditingMode();
});
} else {
this._cell.value?.beforeExitEditingMode();
this.isEditing$.value = false;
}
})
);
this.disposables.addFromEvent(this, 'click', () => {
if (!this.isEditing$.value) {
this.selectCurrentCell(!this.column.readonly$.value);
}
});
}
override render() {
const renderer = this.column.renderer$.value;
if (!renderer) {
return;
}
const { view } = renderer;
this.view.lockRows(this.isEditing$.value);
this.dataset['editing'] = `${this.isEditing$.value}`;
const props: CellRenderProps = {
cell: this.cell$.value,
isEditing$: this.isEditing$,
selectCurrentCell: this.selectCurrentCell,
};
return renderUniLit(view, props, {
ref: this._cell,
style: {
display: 'contents',
},
});
}
@property({ attribute: false })
accessor columnId!: string;
@property({ attribute: false })
accessor columnIndex!: number;
isEditing$ = signal(false);
@property({ attribute: false })
accessor rowIndex!: number;
@property({ attribute: false })
accessor tableViewLogic!: MobileTableViewUILogic;
get view() {
return this.tableViewLogic.view;
}
}
declare global {
interface HTMLElementTagNameMap {
'mobile-table-cell': MobileTableCell;
}
}