import type { ComponentType } from 'react'; import type { Translate } from '@wix/bex-core'; import type { RichTextStyleVariables } from '@wix/patterns-fields'; import type { TableColumn } from '../../model/tableColumn'; import type { ReferenceTypeConfig } from '../../components/EditableTable/cellTypes/reference/types'; import type { AddressCellTypeConfig } from '../../components/EditableTable/cellTypes/address'; import type { CellViewProps, CellEditProps, CellEditWrapperProps, } from '../../components/EditableTable/types'; export interface RichTextCellTypeConfig { textStyle?: RichTextStyleVariables; } export interface Cell { rowKey: string; columnId: string; } export interface CellBorder { top: boolean; bottom: boolean; left: boolean; right: boolean; } export type ValidationRule = | { type: 'required'; message: string } | { type: 'min' | 'max'; value: number; message: string } | { type: 'minLength' | 'maxLength'; value: number; message: string } | { type: 'pattern'; value: RegExp; message: string } | { type: 'custom'; validate: (value: unknown) => boolean; message: string } | { type: 'specificValues'; values: { id: string; value: string }[]; message: string; }; export interface ValidationResult { valid: boolean; errors?: string[]; } export interface CellType { type: keyof CellTypeConfigMap; /** Render cell in view mode (read-only display) */ ViewComponent: ComponentType>; /** Render cell in edit mode (inline editor). Optional — omit for view-only cells. */ EditComponent?: ComponentType>; /** Serialize value to clipboard string */ serialize?: (value: any) => string; /** Deserialize clipboard string to value */ deserialize?: (raw: string) => any; /** Validate a cell value. Return null if valid, or error message string. * `translate` is optional so cell types whose validate doesn't need * localization (text/number/date/…) can keep their existing call sites. * Cell types that DO localize (array/object) must be called with a translator. */ validate?: ( value: any, rules?: ValidationRule[], translate?: Translate, ) => string | null; /** Can this cell type be edited? (default: true) */ editable?: boolean; /** Can this cell type be cleared with Delete key? (default: true) */ clearable?: boolean; /** Toggle value on single click (e.g., boolean checkbox). Skips focus→edit flow. */ toggleValue?: (value: any) => any; /** Enter edit mode on single click (e.g., image cell with no value). */ editOnClick?: (value: any) => boolean; /** Show the EditComponent when the cell is focused, without entering edit mode. * Keyboard navigation remains active. Similar to toggleValue but without toggling. */ showEditWhenFocused?: boolean; /** Optional wrapper component the cell type defines to own behavior that must * outlive the inline editor. EditableCell mounts it around the cell content * and keeps it mounted across the edit↔view transition (e.g. a reference cell * navigating to an overlay and refreshing the table on return). The wrapper * hands anything the editor needs down via its own context. */ EditWrapper?: ComponentType; /** Cell content can grow past row height when focused (e.g. multi-document * wraps chips into multiple rows). The framework releases the bottom anchor * and lays an opaque base under the selection tint so the overflow doesn't * bleed adjacent rows through. * Only applies to showEditWhenFocused cells — others release the bottom * anchor via :editing already. */ growable?: boolean; /** Called on type-to-edit. Return object with cleared value and/or prevent flag. * CMS-web pattern: text clears to '', number clears only for digits, date blocks non-digits. * Return undefined to block the key. Return {} to enter edit mode without changing the value. */ onTypeToEdit?: (params: { key: string; validation?: ValidationRule[]; }) => { value?: any } | undefined; /** Optional hook to normalize the committed value (e.g. auto-prepend https://). * Called before commitEdit() from EditableCell (blur/Enter) and useKeyboardNavigation (Tab). * The value is written back to editingValue via runInAction before committing. */ normalizeOnCommit?: (value: any) => any; /** When true, type-to-edit flushes the edit-mode render synchronously and * focuses the input within the same keydown event, then lets the browser's * default action deliver the keystroke to the now-focused input naturally * (no preventDefault, no programmatic value injection). * Mirrors cms-web's `focusOnTyping` pattern. Use for cells that wrap a * controlled input whose value can't be seeded via props (e.g. WSR TimeInput). */ nativeTypeToEdit?: boolean; } export interface CellTypeConfigMap { text: never; number: never; boolean: never; url: never; email: never; color: never; date: never; time: never; datetime: never; object: never; array: never; image: never; document: never; multiDocument: never; richContent: never; video: never; audio: never; richText: RichTextCellTypeConfig; mediaGallery: never; reference: ReferenceTypeConfig; multiReference: ReferenceTypeConfig; address: AddressCellTypeConfig; custom: Omit; } interface EditableColumnBase extends Omit, 'render'> { id: string; /** Column type — maps to a CellType in the registry */ cellType: keyof CellTypeConfigMap; /** Extract the cell value from the row data */ getValue: (item: T) => any; /** Return updated item with new cell value (immutable) */ setValue?: (item: T, value: any) => T; /** Column-level validation rules */ validation?: ValidationRule[]; /** Is this column editable? (default: true if setValue exists) */ editable?: boolean | ((item: T) => boolean); /** True if this column represents the schema's primary field. Drives header-action disable rules (e.g. blocks Delete, blocks Make Primary). */ isPrimary?: boolean; /** True if this column represents a system field. Drives header-action disable rules (e.g. blocks Edit, Delete, Duplicate). */ isSystem?: boolean; } export type EditableColumn< T, C extends keyof CellTypeConfigMap = keyof CellTypeConfigMap, > = C extends C ? EditableColumnBase & { cellType: C; typeConfig?: CellTypeConfigMap[C] extends never ? never : CellTypeConfigMap[C]; } : never;