diff --git a/packages/grid/src/grid.component.html b/packages/grid/src/grid.component.html index c865700e..f66cbdbe 100644 --- a/packages/grid/src/grid.component.html +++ b/packages/grid/src/grid.component.html @@ -1,9 +1,9 @@
- +
@for (field of gridData().fields; track field.id) { -
+ @for (field of gridData().fields; track $index) { -
+
@switch (field.type) { @case (AITableFieldType.SingleSelect) { @if (record.value[field.id] | selectOption: field['options']; as selectedOption) { diff --git a/packages/grid/src/grid.component.ts b/packages/grid/src/grid.component.ts index ee940675..73c3df3c 100644 --- a/packages/grid/src/grid.component.ts +++ b/packages/grid/src/grid.component.ts @@ -1,23 +1,11 @@ -import { - ChangeDetectionStrategy, - Component, - computed, - effect, - ElementRef, - input, - model, - OnInit, - output, - signal, - viewChild -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, ElementRef, input, model, OnInit, output, signal, viewChild } from '@angular/core'; import { CommonModule, NgClass, NgComponentOutlet, NgForOf } from '@angular/common'; -import { SelectedOneFieldPipe, SelectOptionPipe } from './pipes/grid'; +import { SelectOptionPipe } from './pipes/grid'; import { ThyTag } from 'ngx-tethys/tag'; -import { ThyPopover, ThyPopoverModule } from 'ngx-tethys/popover'; +import { ThyPopoverModule } from 'ngx-tethys/popover'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { buildGridData } from './utils'; -import { AIFieldConfig, AITableFieldMenu, AITableRowHeight } from './types'; +import { AIFieldConfig, AITableFieldMenu, AITableRowHeight, AITableSelection } from './types'; import { Actions, createAITable, @@ -27,8 +15,6 @@ import { AITableFields, AITableFieldType, AITableRecords, - AITableField, - AITableRecord, createDefaultField } from './core'; import { ThyIcon } from 'ngx-tethys/icon'; @@ -97,10 +83,8 @@ export class AITableGrid implements OnInit { aiTable!: AITable; - isSelectedAll = false; - - get selection() { - return this.aiTableGridSelectionService.selection(); + get isSelectedAll() { + return this.aiTable.selection().selectedRecords.size === this.aiRecords().length; } onChange = output(); @@ -110,29 +94,25 @@ export class AITableGrid implements OnInit { fieldMenus!: AITableFieldMenu[]; gridData = computed(() => { - return buildGridData(this.aiRecords(), this.aiFields()); + return buildGridData(this.aiRecords(), this.aiFields(), this.aiTable.selection()); }); constructor( private elementRef: ElementRef, private aiTableGridEventService: AITableGridEventService, - private thyPopover: ThyPopover, public aiTableGridSelectionService: AITableGridSelectionService, private aiTableGridFieldService: AITableGridFieldService - ) { - effect( - () => { - this.aiTable.selection.set(this.aiTableGridSelectionService.selection()); - console.log('跟新啦', this.aiTable.selection()); - }, - { allowSignalWrites: true } - ); - } + ) {} ngOnInit(): void { this.initAITable(); this.initService(); this.buildFieldMenus(); + this.aiTableGridEventService.mousedownEvent$.subscribe((event) => { + if (event?.target) { + this.aiTableGridSelectionService.updateSelect(event); + } + }); } initAITable() { @@ -149,6 +129,7 @@ export class AITableGrid implements OnInit { initService() { this.aiTableGridEventService.initialize(this.aiTable, this.aiFieldConfig()?.fieldPropertyEditor); + this.aiTableGridSelectionService.initialize(this.aiTable); this.aiTableGridEventService.registerEvents(this.elementRef.nativeElement); this.aiTableGridFieldService.initAIFieldConfig(this.aiFieldConfig()); AI_TABLE_GRID_FIELD_SERVICE_MAP.set(this.aiTable, this.aiTableGridFieldService); @@ -162,40 +143,12 @@ export class AITableGrid implements OnInit { Actions.addRecord(this.aiTable, getDefaultRecord(this.aiFields()), [this.aiRecords().length]); } - toggleAllCheckbox(checked: boolean) { - const data = this.gridData().records.map((item) => { - return { ...item, checked: checked }; - }); - this.gridData().records = data; - } - - selectCell(recordId: string, fieldId: string) { - this.toggleAllCheckbox(false); - this.isSelectedAll = false; - this.aiTableGridSelectionService.selectCell(recordId, fieldId); - } - - selectCol(field: any) { - this.toggleAllCheckbox(false); - this.isSelectedAll = false; - this.aiTableGridSelectionService.selectCol(field.id); + selectRow(recordId: string) { + this.aiTableGridSelectionService.selectRow(recordId); } - selectRow(record: AITableRecord) { - this.aiTableGridSelectionService.selectRow(record.id); - this.isSelectedAll = this.selection.selectedRecords.size === this.aiRecords().length; - } - - toggleSelectAll() { - this.aiTableGridSelectionService.clearSelection(); - if (this.isSelectedAll) { - this.toggleAllCheckbox(true); - this.aiRecords().forEach((item) => { - this.selectRow(item); - }); - } else { - this.toggleAllCheckbox(false); - } + toggleSelectAll(checked: boolean) { + this.aiTableGridSelectionService.toggleSelectAll(checked); } addField(gridColumnBlank: HTMLElement) { diff --git a/packages/grid/src/services/event.service.ts b/packages/grid/src/services/event.service.ts index 32644e82..6fdf829c 100644 --- a/packages/grid/src/services/event.service.ts +++ b/packages/grid/src/services/event.service.ts @@ -1,6 +1,6 @@ -import { Injectable, Renderer2, Signal } from '@angular/core'; +import { Injectable, Renderer2, Signal, WritableSignal, signal } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { fromEvent } from 'rxjs'; +import { fromEvent, Subject } from 'rxjs'; import { DBL_CLICK_EDIT_TYPE } from '../constants'; import { getRecordOrField } from '../utils'; import { AITable, AITableField, AITableFieldType, AITableRecord } from '../core'; @@ -16,12 +16,9 @@ export class AITableGridEventService { takeUntilDestroyed = takeUntilDestroyed(); - lastClickCellElement?: HTMLElement; + mousedownEvent$ = new Subject(); - constructor( - private thyPopover: ThyPopover, - private renderer: Renderer2 - ) {} + constructor(private thyPopover: ThyPopover) {} initialize(aiTable: AITable, aiFieldRenderers?: Partial>) { this.aiTable = aiTable; @@ -38,114 +35,10 @@ export class AITableGridEventService { fromEvent(element, 'click') .pipe(this.takeUntilDestroyed) .subscribe((event) => { - this.click(event as MouseEvent); + this.mousedownEvent$.next(event as MouseEvent); }); } - updateCellClass(dom: Element, operation: string) { - const rowDom = dom.closest('.grid-row'); - const highlightCells = rowDom?.querySelectorAll('.grid-cell'); - highlightCells?.forEach((cell) => { - operation === 'remove' ? this.renderer.removeClass(cell, 'highlight') : this.renderer.addClass(cell, 'highlight'); - }); - operation === 'remove' ? this.renderer.removeClass(dom, 'isSelected') : this.renderer.addClass(dom, 'isSelected'); - } - - updateColClass(dom: Element, operation: string) { - const fieldId = dom.getAttribute('fieldid'); - const tableElement = dom.closest('ai-table-grid'); - const cells = tableElement?.querySelectorAll(`[fieldid="${fieldId}"]`); - cells?.forEach((cell) => { - operation === 'add' ? this.renderer.addClass(cell, 'highlight') : this.renderer.removeClass(cell, 'highlight'); - }); - } - - updateAllClass(dom: Element, operation: string, checked: boolean) { - const tableElement = dom.closest('ai-table-grid'); - const rows = tableElement?.querySelectorAll('.grid-row'); - if (checked && operation === 'add') { - rows?.forEach((row) => { - this.renderer.addClass(row, 'highlight'); - }); - } - if (operation === 'remove') { - rows?.forEach((row) => { - this.renderer.removeClass(row, 'highlight'); - }); - } - } - - private click(event: MouseEvent) { - const cell = this.cellType(event.target as HTMLElement); - if (this.lastClickCellElement) { - const lastCell = this.cellType(this.lastClickCellElement); - if (lastCell?.type === 'cell') { - this.updateCellClass(this.lastClickCellElement, 'remove'); - } - - if (lastCell?.type === 'row' && cell?.type !== 'row' && cell.type) { - const tableElement = lastCell.element.closest('ai-table-grid'); - const checkboxes = tableElement?.querySelectorAll('.checked-box'); - checkboxes?.forEach((box) => { - const row = box.closest('.grid-row'); - this.renderer.removeClass(row, 'highlight'); - }); - } - - if (lastCell.type === 'col') { - this.updateColClass(lastCell.element, 'remove'); - } - - if (lastCell?.type === 'all' && cell?.type !== 'row' && cell.type) { - this.updateAllClass(cell.element, 'remove', false); - } - } - - if (cell?.type === 'cell') { - this.updateCellClass(cell.element, 'add'); - } - - if (cell?.type === 'row') { - const rowDom = cell.element.closest('.grid-row'); - if ((event.target as HTMLInputElement).checked) { - this.renderer.addClass(rowDom, 'highlight'); - } else { - this.renderer.removeClass(rowDom, 'highlight'); - } - } - - if (cell?.type === 'col') { - this.updateColClass(cell.element, 'add'); - } - - if (cell?.type === 'all') { - this.updateAllClass(cell.element, 'add', (event.target as HTMLInputElement).checked); - } - - this.lastClickCellElement = event.target as HTMLElement; - } - - private cellType(cell: HTMLElement) { - const cellDom = cell.closest('.grid-cell'); - const rowDom = cell.closest('.grid-row-index'); - const colDom = cell.closest('.grid-column-field'); - const checkAllDom = cell.closest('.grid-column-checkbox'); - if (cellDom && cellDom.getAttribute('fieldid') && cellDom.getAttribute('recordid')) { - return { type: 'cell', element: cellDom }; - } - if (rowDom && cell.tagName === 'INPUT') { - return { type: 'row', element: rowDom }; - } - if (colDom) { - return { type: 'col', element: colDom }; - } - - if (checkAllDom && cell.tagName === 'INPUT') { - return { type: 'all', element: checkAllDom }; - } - return {}; - } - private dblClick(event: MouseEvent) { const cellDom = (event.target as HTMLElement).closest('.grid-cell') as HTMLElement; const type = cellDom && cellDom.getAttribute('type')!; diff --git a/packages/grid/src/services/selection.servive.ts b/packages/grid/src/services/selection.servive.ts index eeab16b3..7f6750be 100644 --- a/packages/grid/src/services/selection.servive.ts +++ b/packages/grid/src/services/selection.servive.ts @@ -1,16 +1,21 @@ -import { Injectable, WritableSignal, signal } from '@angular/core'; +import { Injectable, Renderer2, WritableSignal, signal } from '@angular/core'; import { AITableSelection } from '../types'; +import { AITable } from '../core'; @Injectable() export class AITableGridSelectionService { - selection: WritableSignal = signal({ - selectedRecords: new Map(), - selectedFields: new Map(), - selectedCells: new Map() - }); + aiTable!: AITable; + + lastClickCellElement?: HTMLElement; + + constructor(private renderer: Renderer2) {} + + initialize(aiTable: AITable) { + this.aiTable = aiTable; + } clearSelection() { - this.selection.set({ + this.aiTable.selection.set({ selectedRecords: new Map(), selectedFields: new Map(), selectedCells: new Map() @@ -19,24 +24,147 @@ export class AITableGridSelectionService { selectCell(recordId: string, fieldId: string) { this.clearSelection(); - this.selection().selectedCells.set(recordId, { [fieldId]: true }); + this.aiTable.selection().selectedCells.set(recordId, { [fieldId]: true }); } selectCol(fieldId: string) { this.clearSelection(); - this.selection().selectedFields.set(fieldId, true); + this.aiTable.selection().selectedFields.set(fieldId, true); } selectRow(recordId: string) { - if (this.selection().selectedRecords.has(recordId)) { - this.selection().selectedRecords.delete(recordId); + if (this.aiTable.selection().selectedRecords.has(recordId)) { + this.aiTable.selection().selectedRecords.delete(recordId); } else { - this.selection().selectedRecords.set(recordId, true); + this.aiTable.selection().selectedRecords.set(recordId, true); } - this.selection.set({ - selectedRecords: this.selection().selectedRecords, + this.aiTable.selection.set({ + selectedRecords: this.aiTable.selection().selectedRecords, selectedFields: new Map(), selectedCells: new Map() }); } + + toggleSelectAll(checked: boolean) { + this.clearSelection(); + if (checked) { + this.aiTable.records().forEach((item) => { + this.selectRow(item.id); + }); + } + } + + cellType(cell: HTMLElement) { + const cellDom = cell.closest('.grid-cell'); + const rowDom = cell.closest('.grid-row-index'); + const colDom = cell.closest('.grid-column-field'); + const checkAllDom = cell.closest('.grid-column-checkbox'); + if (cellDom && cellDom.getAttribute('fieldid') && cellDom.getAttribute('recordid')) { + return { type: 'cell', element: cellDom }; + } + if (rowDom && cell.tagName === 'INPUT') { + return { type: 'row', element: rowDom }; + } + if (colDom) { + return { type: 'col', element: colDom }; + } + + if (checkAllDom && cell.tagName === 'INPUT') { + return { type: 'all', element: checkAllDom }; + } + return {}; + } + + updateCellClass(dom: Element, operation: string) { + const rowDom = dom.closest('.grid-row'); + const highlightCells = rowDom?.querySelectorAll('.grid-cell'); + highlightCells?.forEach((cell) => { + operation === 'remove' ? this.renderer.removeClass(cell, 'highlight') : this.renderer.addClass(cell, 'highlight'); + }); + operation === 'remove' ? this.renderer.removeClass(dom, 'isSelected') : this.renderer.addClass(dom, 'isSelected'); + } + + updateColClass(dom: Element, operation: string) { + const fieldId = dom.getAttribute('fieldid'); + const tableElement = dom.closest('ai-table-grid'); + const cells = tableElement?.querySelectorAll(`[fieldid="${fieldId}"]`); + cells?.forEach((cell) => { + operation === 'add' ? this.renderer.addClass(cell, 'highlight') : this.renderer.removeClass(cell, 'highlight'); + }); + } + + updateAllClass(dom: Element, operation: string, checked: boolean) { + const tableElement = dom.closest('ai-table-grid'); + const rows = tableElement?.querySelectorAll('.grid-row'); + if (checked && operation === 'add') { + rows?.forEach((row) => { + this.renderer.addClass(row, 'highlight'); + }); + } + if (operation === 'remove') { + rows?.forEach((row) => { + this.renderer.removeClass(row, 'highlight'); + }); + } + } + + updateSelect(event: MouseEvent) { + const cell = this.cellType(event.target as HTMLElement); + let fieldId = ''; + let recordId = ''; + if (cell.type) { + fieldId = cell.element.getAttribute('fieldId')!; + recordId = cell.element.getAttribute('recordId')!; + } + if (this.lastClickCellElement) { + const lastCell = this.cellType(this.lastClickCellElement); + if (lastCell?.type === 'cell') { + this.updateCellClass(this.lastClickCellElement, 'remove'); + } + + if (lastCell?.type === 'row' && cell?.type !== 'row' && cell.type) { + const tableElement = lastCell.element.closest('ai-table-grid'); + const checkboxes = tableElement?.querySelectorAll('.checked-box'); + checkboxes?.forEach((box) => { + const row = box.closest('.grid-row'); + this.renderer.removeClass(row, 'highlight'); + }); + } + + if (lastCell.type === 'col') { + this.updateColClass(lastCell.element, 'remove'); + } + + if (lastCell?.type === 'all' && cell?.type !== 'row' && cell.type) { + this.updateAllClass(cell.element, 'remove', false); + } + } + + if (cell?.type === 'cell') { + this.selectCell(recordId, fieldId); + this.updateCellClass(cell.element, 'add'); + } + + if (cell?.type === 'row') { + const rowDom = cell.element.closest('.grid-row'); + if ((event.target as HTMLInputElement).checked) { + this.renderer.addClass(rowDom, 'highlight'); + } else { + this.renderer.removeClass(rowDom, 'highlight'); + } + } + + if (cell?.type === 'col') { + this.selectCol(fieldId); + this.updateColClass(cell.element, 'add'); + } + + if (cell?.type === 'all') { + this.updateAllClass(cell.element, 'add', (event.target as HTMLInputElement).checked); + } + + if (cell.type) { + this.lastClickCellElement = event.target as HTMLElement; + } + } } diff --git a/packages/grid/src/utils/build.ts b/packages/grid/src/utils/build.ts index 590eadb0..568f6638 100644 --- a/packages/grid/src/utils/build.ts +++ b/packages/grid/src/utils/build.ts @@ -1,12 +1,12 @@ import { AITableFields, AITableRecords } from '../core'; -import { AITableGridData } from '../types'; +import { AITableGridData, AITableSelection } from '../types'; -export const buildGridData = (recordValue: AITableRecords, fieldsValue: AITableFields): AITableGridData => { +export const buildGridData = (recordValue: AITableRecords, fieldsValue: AITableFields, selection: AITableSelection): AITableGridData => { return { type: 'grid', fields: fieldsValue, records: recordValue.map((item) => { - return { ...item, checked: false }; + return { ...item, checked: selection.selectedRecords.has(item.id) }; }) }; };