diff --git a/packages/qgrid-core/public-api.d.ts b/packages/qgrid-core/public-api.d.ts index 50070ae7b..c628067a9 100644 --- a/packages/qgrid-core/public-api.d.ts +++ b/packages/qgrid-core/public-api.d.ts @@ -24,6 +24,7 @@ export { BoolColumn, BoolColumnModel } from './src/column-type/bool.column'; export { CohortColumn, CohortColumnModel } from './src/column-type/cohort.column'; //column-type export { ColumnModel, ColumnModelCategory, ColumnModelPin, ColumnModelType, ColumnModelWidthMode } from './src/column-type/column.model'; +export { RowModelPin } from './src/row/row.model'; export { CurrencyColumn, CurrencyColumnModel } from './src/column-type/currency.column'; export { DataColumnModel } from './src/column-type/data.column.model'; export { DateColumn, DateColumnModel } from './src/column-type/date.column'; diff --git a/packages/qgrid-core/src/cell/cell.selector.js b/packages/qgrid-core/src/cell/cell.selector.js index bf4dd9511..b0f5a00f6 100644 --- a/packages/qgrid-core/src/cell/cell.selector.js +++ b/packages/qgrid-core/src/cell/cell.selector.js @@ -51,7 +51,7 @@ export class CellSelector { } mapFromCells(items) { - const { table } = this; + const { model, table } = this; const result = []; const rows = table.data.rows(); const columns = table.data.columns(); diff --git a/packages/qgrid-core/src/dom/selector/selector.factory.js b/packages/qgrid-core/src/dom/selector/selector.factory.js index 5c381dc7c..e4743a08b 100644 --- a/packages/qgrid-core/src/dom/selector/selector.factory.js +++ b/packages/qgrid-core/src/dom/selector/selector.factory.js @@ -17,31 +17,33 @@ export class SelectorFactory { const entries = selectorMark .select() - .map(({ element, rowRange, columnRange }) => ({ + .map(({ name, element, rowRange, columnRange }) => ({ + name, matrix: matrix.build(element), rowRange, columnRange })); const selectorFactory = context => { - return entries.map(entry => ({ - invoke: f => { - const unitFactory = new UnitFactory(entry.rowRange, entry.columnRange); - const selector = new Selector(entry.matrix, bag, unitFactory); + return entries.map(entry => ({ + invoke: f => { + const unitFactory = new UnitFactory(entry.rowRange, entry.columnRange); + const selector = new Selector(entry.matrix, bag, unitFactory); - const args = []; - args.push(selector); + const args = []; + args.push(selector); - if (context.hasOwnProperty('row')) { - args.push(context.row - entry.rowRange.start); - } + if (context.hasOwnProperty('row')) { + args.push(context.row - entry.rowRange.start); + } - if (context.hasOwnProperty('column')) { - args.push(context.column - entry.columnRange.start); - } + if (context.hasOwnProperty('column')) { + args.push(context.column - entry.columnRange.start); + } - return f(...args); - } + return f(...args); + }, + type: entry.name })); }; diff --git a/packages/qgrid-core/src/dom/selector/selector.mark.js b/packages/qgrid-core/src/dom/selector/selector.mark.js index 6e311c553..ecfdb1948 100644 --- a/packages/qgrid-core/src/dom/selector/selector.mark.js +++ b/packages/qgrid-core/src/dom/selector/selector.mark.js @@ -10,30 +10,35 @@ export class SelectorMark { select() { const result = []; const addNext = this.addFactory(result); - - addNext('left'); - addNext('mid'); - addNext('right'); - + ['top', 'mid', 'bottom'].forEach(pos => (['left', 'mid', 'right'].forEach(pin => addNext(pin, pos)))); return result; } addFactory(result) { const { model } = this; - const { rows } = model.scene(); const columnArea = model.scene().column.area; - return pin => { - const name = pin ? `${this.name}-${pin}` : this.name; + return (pinLR, pinTB) => { + + const { pinTop, pinBottom } = model.row(); + const pinned = new Set([...pinTop, ...pinBottom]); + + const rows = pinTB == 'top' ? pinTop : pinTB == 'bottom' ? pinBottom : + (pinned.size ? model.scene().rows.filter(row => !pinned.has(row)) : model.scene().rows); + + const name = this.name + ((pinTB && pinTB !== 'mid') ? `-${pinTB}` : '') + (pinLR ? `-${pinLR}` : ''); const element = this.markup[name]; if (element) { - const prev = result[result.length - 1]; + const prev = result[result.length - 1] && result[result.length - 1].name === (this.name + ((pinTB == 'top' || pinTB == 'bottom') ? `-${pinTB}` : '')) ? + result[result.length - 1] : null; const columnStart = prev ? prev.columnRange.end : 0; - const columnCount = columnArea[pin].length; - const rowStart = 0; + const columnCount = columnArea[pinLR].length; + const rowStart = pinTB === 'mid' ? pinTop.length : pinTB === 'bottom' ? + model.scene().rows.length - pinBottom.length : 0; const rowCount = rows.length; result.push({ + name: this.name + ((pinTB == 'top' || pinTB == 'bottom') ? `-${pinTB}` : ''), element, columnRange: new Range(columnStart, columnStart + columnCount), rowRange: new Range(rowStart, rowStart + rowCount) diff --git a/packages/qgrid-core/src/dom/selector/selector.mediate.js b/packages/qgrid-core/src/dom/selector/selector.mediate.js index 6fe4dbb3f..388abb5d0 100644 --- a/packages/qgrid-core/src/dom/selector/selector.mediate.js +++ b/packages/qgrid-core/src/dom/selector/selector.mediate.js @@ -9,7 +9,7 @@ export class SelectorMediator { } columnCount(rowIndex) { - const selectors = this.buildSelectors({ row: rowIndex }); + const selectors = this.buildSelectors({ row: rowIndex }).filter(x => x.type === 'body'); if (!selectors.length) { return 0; } @@ -34,8 +34,12 @@ export class SelectorMediator { if (!selectors.length) { return 0; } + + const rowCountTop = max(selectors.filter(x => x.type === 'body-top')?.map(s => s.invoke((s, columnIndex) => s.rowCount(columnIndex)))) ?? 0; + const rowCount = max(selectors.filter(x => x.type === 'body')?.map(s => s.invoke((s, columnIndex) => s.rowCount(columnIndex)))) ?? 0; + const rowCountBottom = max(selectors.filter(x => x.type === 'body-bottom')?.map(s => s.invoke((s, columnIndex) => s.rowCount(columnIndex)))) ?? 0; - return max(selectors.map(s => s.invoke((s, columnIndex) => s.rowCount(columnIndex)))); + return rowCountTop + rowCount + rowCountBottom; } rows(columnIndex) { diff --git a/packages/qgrid-core/src/dom/view.js b/packages/qgrid-core/src/dom/view.js index 164a39f7f..f7ff6246a 100644 --- a/packages/qgrid-core/src/dom/view.js +++ b/packages/qgrid-core/src/dom/view.js @@ -44,8 +44,9 @@ export class View extends Unit { } isFocused() { - return this.getElementsCore('body') - .some(element => this.isFocusedCore(element)); + return this.getElementsCore('body').some(element => this.isFocusedCore(element)) || + this.getElementsCore('body-top').some(element => this.isFocusedCore(element)) || + this.getElementsCore('body-bottom').some(element => this.isFocusedCore(element)); } addLayer(name) { @@ -107,12 +108,12 @@ export class View extends Unit { bodyMid.scrollLeft = value; } - const bodyTop = markup['body-top']; + const bodyTop = markup['body-top-mid']; if (bodyTop) { bodyTop.scrollLeft = value; } - const bodyBottom = markup['body-bottom']; + const bodyBottom = markup['body-bottom-mid']; if (bodyBottom) { bodyBottom.scrollLeft = value; } @@ -157,7 +158,16 @@ export class View extends Unit { break; } case 'top': { - return true; + target = target.element; + if (target) { + const { markup } = this.context; + const bodyLeft = markup['body-left']; + const bodyMid = markup['body-mid']; + const bodyRight = markup['body-right']; + return (bodyLeft && isParentOf(bodyLeft, target)) || + (bodyMid && isParentOf(bodyMid, target)) || + (bodyRight && isParentOf(bodyRight, target)); + } } } } diff --git a/packages/qgrid-core/src/navigation/navigation.js b/packages/qgrid-core/src/navigation/navigation.js index 16efbb0c6..e770dd664 100644 --- a/packages/qgrid-core/src/navigation/navigation.js +++ b/packages/qgrid-core/src/navigation/navigation.js @@ -123,12 +123,14 @@ export class Navigation { const cell = this.table.body.cell(rowIndex, columnIndex); const model = cell.model(); if (model) { - const { row, column } = model; + const { row, column, td } = model; + const rowPin = td.pin; return { rowIndex, columnIndex, row, - column + column, + rowPin }; } diff --git a/packages/qgrid-core/src/navigation/navigation.let.js b/packages/qgrid-core/src/navigation/navigation.let.js index c72e20cea..e7baceaac 100644 --- a/packages/qgrid-core/src/navigation/navigation.let.js +++ b/packages/qgrid-core/src/navigation/navigation.let.js @@ -11,9 +11,9 @@ export class NavigationLet { this.plugin = plugin; const navigation = new Navigation(model, table); - let focusBlurs = []; + let focusBlurs = []; - shortcut.register(navigation.commands); + shortcut.register(navigation.commands); this.focus = new Command({ source: 'navigation.view', @@ -30,12 +30,14 @@ export class NavigationLet { const td = table.body.cell(rowIndex, columnIndex).model(); if (td) { const { row, column } = td; + const rowPin = td.td.pin; model.navigation({ cell: { rowIndex, columnIndex, row, - column + column, + rowPin } }, { source: 'navigation.view', @@ -168,10 +170,11 @@ export class NavigationLet { } scroll(view, target) { - const { model } = this.plugin; + const { model, table } = this.plugin; const { scroll } = model; Fastdom.measure(() => { - const tr = target.rect(); + const tr = target.rect(); + const vr = view.rect('body-mid'); const state = {}; diff --git a/packages/qgrid-core/src/pipe/pagination.pipe.js b/packages/qgrid-core/src/pipe/pagination.pipe.js index fabb437de..4bf64b5d8 100644 --- a/packages/qgrid-core/src/pipe/pagination.pipe.js +++ b/packages/qgrid-core/src/pipe/pagination.pipe.js @@ -43,6 +43,6 @@ function paginate(model, rows) { const start = current * size; model.pagination({ count, current }, { source: 'pagination.pipe', behavior: 'core' }); - return mode === 'virtual' ? rows : rows.slice(start, start + size); + return mode === 'virtual' ? rows : pinTop.concat(rows.slice(start, start + size)).concat(pinBottom); } diff --git a/packages/qgrid-core/src/row/row.model.d.ts b/packages/qgrid-core/src/row/row.model.d.ts new file mode 100644 index 000000000..d2cfe3ed8 --- /dev/null +++ b/packages/qgrid-core/src/row/row.model.d.ts @@ -0,0 +1,8 @@ +/** + * Indicates if row should be frozen. + * - `'top'` - freeze a row on the grid's top. + * - `'body'` - do not freeze + * - `'bottom'` - freeze a row on the grid's bottom. + */ +export declare type RowModelPin = 'top' | 'body' | 'bottom'; + diff --git a/packages/qgrid-core/src/scene/render/render.js b/packages/qgrid-core/src/scene/render/render.js index ca8acad06..1f1d03192 100644 --- a/packages/qgrid-core/src/scene/render/render.js +++ b/packages/qgrid-core/src/scene/render/render.js @@ -82,15 +82,19 @@ export class Renderer { }; this.rows = { - left: [], - right: [], - mid: [] + top: [], + body: [], + bottom: [] }; const invalidateRows = () => { - const { rows } = model.scene(); + let { rows } = model.scene(); const { pinTop, pinBottom } = model.row(); + const pinned = new Set([...pinTop, ...pinBottom]); + if (pinned.size) { + rows = rows.filter(row => !pinned.has(row)) + } this.rows = { top: pinTop, body: rows, diff --git a/packages/qgrid-core/src/scene/view/cell.view.d.ts b/packages/qgrid-core/src/scene/view/cell.view.d.ts index 813e5f79e..3e1ab7d88 100644 --- a/packages/qgrid-core/src/scene/view/cell.view.d.ts +++ b/packages/qgrid-core/src/scene/view/cell.view.d.ts @@ -8,4 +8,5 @@ export interface CellViewPosition { export interface CellView extends CellViewPosition { readonly row: any; readonly column: ColumnModel; + readonly rowPin: string; } diff --git a/packages/qgrid-core/src/selection/selection.let.js b/packages/qgrid-core/src/selection/selection.let.js index bc5f198c2..412afad2e 100644 --- a/packages/qgrid-core/src/selection/selection.let.js +++ b/packages/qgrid-core/src/selection/selection.let.js @@ -427,13 +427,15 @@ export class SelectionLet { navigateTo(rowIndex, columnIndex) { const { table, model } = this.plugin; - const { row, column } = table.body.cell(rowIndex, columnIndex).model(); + const { row, column, td } = table.body.cell(rowIndex, columnIndex).model(); + const rowPin = td.pin; model.navigation({ cell: { rowIndex, columnIndex, row, - column + column, + rowPin } }, { source: 'selection.view' }); } diff --git a/packages/qgrid-core/src/view/view.host.js b/packages/qgrid-core/src/view/view.host.js index c29c00b88..b56cd2655 100644 --- a/packages/qgrid-core/src/view/view.host.js +++ b/packages/qgrid-core/src/view/view.host.js @@ -206,14 +206,12 @@ export class ViewHost { } const tr = this.findRow(e); - if (tr) { + if (tr) { const { index } = tr; - if (highlight.row.canExecute(index)) { rows .filter(i => i !== index) .forEach(i => highlight.row.execute(i, false)); - highlight.row.execute(index, true); } } diff --git a/packages/qgrid-ngx-theme-material/src/lib/templates/plugin-pager-target.tpl.html b/packages/qgrid-ngx-theme-material/src/lib/templates/plugin-pager-target.tpl.html index 41daa18f1..7eaeec1f3 100644 --- a/packages/qgrid-ngx-theme-material/src/lib/templates/plugin-pager-target.tpl.html +++ b/packages/qgrid-ngx-theme-material/src/lib/templates/plugin-pager-target.tpl.html @@ -3,7 +3,8 @@ - + - diff --git a/packages/qgrid-ngx/src/lib/body/body-core.component.ts b/packages/qgrid-ngx/src/lib/body/body-core.component.ts index fb29b670a..6664bade9 100644 --- a/packages/qgrid-ngx/src/lib/body/body-core.component.ts +++ b/packages/qgrid-ngx/src/lib/body/body-core.component.ts @@ -1,22 +1,24 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input, NgZone, OnInit } from '@angular/core'; -import { BodyHost, ColumnView, EventListener, EventManager, SelectionState } from '@qgrid/core'; +import { BodyHost, ColumnView, EventListener, EventManager, RowModelPin, SelectionState } from '@qgrid/core'; import { GridLet } from '../grid/grid-let'; import { GridModel } from '../grid/grid-model'; import { GridPlugin } from '../plugin/grid-plugin'; import { TableCoreService } from '../table/table-core.service'; -@Component({ +import { BodyCoreService } from './body-core.service'; +@Component({ // tslint:disable-next-line selector: 'tbody[q-grid-core-body]', templateUrl: './body-core.component.html', - providers: [GridPlugin], + providers: [GridPlugin, BodyCoreService], changeDetection: ChangeDetectionStrategy.OnPush }) export class BodyCoreComponent implements OnInit { - @Input() pin = 'body'; + @Input() pin: RowModelPin = 'body'; constructor( public $view: GridLet, public $table: TableCoreService, + public $body: BodyCoreService, private elementRef: ElementRef, private zone: NgZone, private cd: ChangeDetectorRef, @@ -30,6 +32,8 @@ export class BodyCoreComponent implements OnInit { const host = new BodyHost(this.plugin); + this.$body.pin = this.pin; + const listener = new EventListener(this.elementRef.nativeElement, new EventManager(this)); this.zone.runOutsideAngular(() => { const scrollSettings = { passive: true }; @@ -37,7 +41,7 @@ export class BodyCoreComponent implements OnInit { listener.on('scroll', () => host.scroll({ scrollLeft: this.$table.pin === 'mid' ? nativeElement.scrollLeft : model.scroll().left, - scrollTop: nativeElement.scrollTop + scrollTop: this.$body.pin === 'body' ? nativeElement.scrollTop : model.scroll().top }), scrollSettings )); diff --git a/packages/qgrid-ngx/src/lib/body/body-core.service.ts b/packages/qgrid-ngx/src/lib/body/body-core.service.ts new file mode 100644 index 000000000..68792b430 --- /dev/null +++ b/packages/qgrid-ngx/src/lib/body/body-core.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { RowModelPin } from '@qgrid/core'; +import { GridPlugin } from '../plugin/grid-plugin'; + +@Injectable() +export class BodyCoreService { + + constructor(private plugin: GridPlugin) { + } + + pin: RowModelPin; + + get offsetIndex() { + switch (this.pin) { + case "body": return this.plugin.model.row().pinTop.length; + case "bottom": return this.plugin.model.scene().rows.length - this.plugin.model.row().pinBottom.length; + default: return 0; + } + } +} \ No newline at end of file diff --git a/packages/qgrid-ngx/src/lib/body/td-core.directive.ts b/packages/qgrid-ngx/src/lib/body/td-core.directive.ts index 5aa62dc88..de6c92f16 100644 --- a/packages/qgrid-ngx/src/lib/body/td-core.directive.ts +++ b/packages/qgrid-ngx/src/lib/body/td-core.directive.ts @@ -11,6 +11,7 @@ import { DomTd } from '../dom/dom'; import { GridLet } from '../grid/grid-let'; import { GridRoot } from '../grid/grid-root'; import { TrCoreDirective } from '../row/tr-core.directive'; +import { BodyCoreService } from './body-core.service'; @Directive({ selector: '[q-grid-core-td]', @@ -22,6 +23,8 @@ export class TdCoreDirective implements DomTd, OnInit, OnDestroy, OnChanges { @Input('q-grid-core-label') actualLabel: any; @Input('q-grid-core-td') columnView: ColumnView; + + pin: string; element: HTMLElement; changes: SimpleChange; @@ -32,15 +35,20 @@ export class TdCoreDirective implements DomTd, OnInit, OnDestroy, OnChanges { private viewContainerRef: ViewContainerRef, private cellTemplate: CellTemplateService, private cellClass: CellClassService, + private $body: BodyCoreService, private tr: TrCoreDirective, private cd: ChangeDetectorRef, elementRef: ElementRef ) { this.element = elementRef.nativeElement.parentNode; + this.pin = $body.pin; } ngOnInit() { const { table } = this.root; + + this.pin = this.$body.pin; + table.box.bag.body.addCell(this); this.cellClass.toBody(this.element, this.column); diff --git a/packages/qgrid-ngx/src/lib/cell-handler/cell-handler.component.ts b/packages/qgrid-ngx/src/lib/cell-handler/cell-handler.component.ts index 1d20f2bed..5220dc0c1 100644 --- a/packages/qgrid-ngx/src/lib/cell-handler/cell-handler.component.ts +++ b/packages/qgrid-ngx/src/lib/cell-handler/cell-handler.component.ts @@ -1,5 +1,6 @@ import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { CellView, EditService, Fastdom, jobLine, NavigationState, RowDetails } from '@qgrid/core'; +import { CellView, EditService, Fastdom, jobLine, NavigationState, RowDetails, Td } from '@qgrid/core'; +import { TdCoreDirective } from '../body/td-core.directive'; import { GridEventArg } from '../grid/grid-model'; import { GridPlugin } from '../plugin/grid-plugin'; @@ -107,7 +108,7 @@ export class CellHandlerComponent implements OnInit, AfterViewInit { const headHeight = table.view.height('head-mid'); - const top = Math.max(headHeight, (target.offsetTop - scrollState.top)); + const top = Math.max(headHeight, target.offsetTop - (cell.rowPin === "body" ? scrollState.top : 0)); const left = (target.offsetLeft - (cell.column.pin === 'mid' ? scrollState.left : 0)); const width = target.offsetWidth; const height = target.offsetHeight; diff --git a/packages/qgrid-ngx/src/lib/row/tr-core.directive.ts b/packages/qgrid-ngx/src/lib/row/tr-core.directive.ts index 5cf13f411..af20958ca 100644 --- a/packages/qgrid-ngx/src/lib/row/tr-core.directive.ts +++ b/packages/qgrid-ngx/src/lib/row/tr-core.directive.ts @@ -1,4 +1,5 @@ import { Directive, ElementRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { BodyCoreService } from '../body/body-core.service'; import { DomTr } from '../dom/dom'; import { GridLet } from '../grid/grid-let'; import { GridPlugin } from '../plugin/grid-plugin'; @@ -11,18 +12,22 @@ export class TrCoreDirective implements DomTr, OnInit, OnDestroy { @Input('q-grid-core-tr') model: any; @Input('q-grid-core-source') source; + pin: string; + element: HTMLElement; constructor( public $view: GridLet, + private $body: BodyCoreService, private plugin: GridPlugin, elementRef: ElementRef ) { this.element = elementRef.nativeElement; + this.pin = $body.pin; } get index() { - return this.$view.scroll.y.container.position + this.viewIndex; + return this.$view.scroll.y.container.position + this.viewIndex + this.$body.offsetIndex; } ngOnInit() { diff --git a/packages/qgrid-ngx/src/public-api.ts b/packages/qgrid-ngx/src/public-api.ts index d8186dbad..fa30126cc 100644 --- a/packages/qgrid-ngx/src/public-api.ts +++ b/packages/qgrid-ngx/src/public-api.ts @@ -62,6 +62,7 @@ export { ScrollService } from './lib/scroll/scroll.service'; export { StateAccessor } from './lib/state/state-accessor'; export { TableCoreComponent } from './lib/table/table-core.component'; export { TableCoreService } from './lib/table/table-core.service'; +export { BodyCoreService } from './lib/body/body-core.service'; export { TableModule } from './lib/table/table.module'; export { TemplateCacheDirective } from './lib/template/template-cache.directive'; export { TemplateCacheService } from './lib/template/template-cache.service'; diff --git a/packages/qgrid-styles/view.scss b/packages/qgrid-styles/view.scss index d24795c40..be4eac1c2 100644 --- a/packages/qgrid-styles/view.scss +++ b/packages/qgrid-styles/view.scss @@ -41,7 +41,7 @@ .q-grid-with-bottom-pin { >.q-grid-view { - >.q-grid-table-mid { + >.q-grid-table-left, >.q-grid-table-mid, >.q-grid-table-right { >table { >tbody:not(.q-grid-body-bottom) { overflow-x: hidden;