- |
+ | just a cell |
+
+
${CELL_TEMPLATE}
@@ -175,9 +188,15 @@ class ElementDataSource extends DataSource {
template: `
+
+
+ just a cell
+
+
+
+ ${POPOVER_EDIT_DIRECTIVE}>
${CELL_TEMPLATE}
@@ -202,7 +221,7 @@ class ElementDataSource extends DataSource {
`
})
class CdkFlexTableInCell extends BaseTestComponent {
- displayedColumns = ['name', 'weight'];
+ displayedColumns = ['before', 'name', 'weight'];
dataSource = new ElementDataSource();
}
@@ -210,9 +229,15 @@ class CdkFlexTableInCell extends BaseTestComponent {
template: `
+
+ |
+ just a cell
+ |
+
+
+ ${POPOVER_EDIT_DIRECTIVE}>
${CELL_TEMPLATE}
@@ -237,7 +262,7 @@ class CdkFlexTableInCell extends BaseTestComponent {
`
})
class CdkTableInCell extends BaseTestComponent {
- displayedColumns = ['name', 'weight'];
+ displayedColumns = ['before', 'name', 'weight'];
dataSource = new ElementDataSource();
}
@@ -316,6 +341,49 @@ describe('CDK Popover Edit', () => {
expect(component.getInput()!.value).toBe('Hydrogen');
}));
+ it('positions the lens at the top left corner and spans the full width of the cell',
+ fakeAsync(() => {
+ component.openLens();
+
+ const paneRect = component.getEditPane()!.getBoundingClientRect();
+ const cellRect = component.getEditCell().getBoundingClientRect();
+
+ expect(paneRect.width).toBe(cellRect.width);
+ expect(paneRect.left).toBe(cellRect.left);
+ expect(paneRect.top).toBe(cellRect.top);
+ }));
+
+ it('adjusts the positioning of the lens based on colspan', fakeAsync(() => {
+ const cellRects = getCells(getRows(component.table.nativeElement)[0])
+ .map(cell => cell.getBoundingClientRect());
+
+ component.colspan = {before: 1};
+ fixture.detectChanges();
+
+ component.openLens();
+
+ let paneRect = component.getEditPane()!.getBoundingClientRect();
+ expect(paneRect.top).toBe(cellRects[0].top);
+ expect(paneRect.left).toBe(cellRects[0].left);
+ expect(paneRect.right).toBe(cellRects[1].right);
+
+ component.colspan = {after: 1};
+ fixture.detectChanges();
+
+ paneRect = component.getEditPane()!.getBoundingClientRect();
+ expect(paneRect.top).toBe(cellRects[1].top);
+ expect(paneRect.left).toBe(cellRects[1].left);
+ expect(paneRect.right).toBe(cellRects[2].right);
+
+ component.colspan = {before: 1, after: 1};
+ fixture.detectChanges();
+
+ paneRect = component.getEditPane()!.getBoundingClientRect();
+ expect(paneRect.top).toBe(cellRects[0].top);
+ expect(paneRect.left).toBe(cellRects[0].left);
+ expect(paneRect.right).toBe(cellRects[2].right);
+ }));
+
it('updates the form and submits, closing the lens', fakeAsync(() => {
component.openLens();
diff --git a/src/cdk-experimental/popover-edit/table-directives.ts b/src/cdk-experimental/popover-edit/table-directives.ts
index f29395a22ee..6bce473490d 100644
--- a/src/cdk-experimental/popover-edit/table-directives.ts
+++ b/src/cdk-experimental/popover-edit/table-directives.ts
@@ -6,8 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {FocusTrap, FocusTrapFactory} from '@angular/cdk/a11y';
-import {Overlay, OverlayRef} from '@angular/cdk/overlay';
+import {Overlay, OverlayRef, PositionStrategy} from '@angular/cdk/overlay';
import {TemplatePortal} from '@angular/cdk/portal';
+import {ScrollDispatcher, ViewportRuler} from '@angular/cdk/scrolling';
import {
AfterViewInit,
Directive,
@@ -19,14 +20,24 @@ import {
TemplateRef,
ViewContainerRef
} from '@angular/core';
-import {fromEvent, ReplaySubject} from 'rxjs';
-import {debounceTime, filter, map, mapTo, takeUntil} from 'rxjs/operators';
+import {fromEvent, merge, ReplaySubject} from 'rxjs';
+import {debounceTime, filter, map, mapTo, startWith, takeUntil} from 'rxjs/operators';
import {CELL_SELECTOR, EDIT_PANE_CLASS, EDIT_PANE_SELECTOR, ROW_SELECTOR} from './constants';
import {EditEventDispatcher} from './edit-event-dispatcher';
import {closest} from './polyfill';
import {PopoverEditPositionStrategyFactory} from './popover-edit-position-strategy-factory';
+/**
+ * Describes the number of columns before and after the originating cell that the
+ * edit popup should span. In left to right locales, before means left and after means
+ * right. In right to left locales before means right and after means left.
+ */
+export interface CdkPopoverEditColspan {
+ before?: number;
+ after?: number;
+}
+
/**
* The delay between the mouse entering a row and the mouse stopping its movement before
* showing on-hover content.
@@ -111,6 +122,28 @@ export class CdkPopoverEdit implements AfterViewInit, OnDestroy {
*/
@Input('cdkPopoverEditContext') context?: C;
+ /**
+ * Specifies that the popup should cover additional table cells before and/or after
+ * this one.
+ */
+ @Input('cdkPopoverEditColspan')
+ get colspan(): CdkPopoverEditColspan {
+ return this._colspan;
+ }
+ set colspan(value: CdkPopoverEditColspan) {
+ this._colspan = value;
+
+ // Recompute positioning when the colspan changes.
+ if (this.overlayRef) {
+ this.overlayRef.updatePositionStrategy(this._getPositionStrategy());
+
+ if (this.overlayRef.hasAttached()) {
+ this._updateOverlaySize();
+ }
+ }
+ }
+ private _colspan: CdkPopoverEditColspan = {};
+
protected focusTrap?: FocusTrap;
protected overlayRef?: OverlayRef;
protected readonly destroyed = new ReplaySubject();
@@ -122,7 +155,9 @@ export class CdkPopoverEdit implements AfterViewInit, OnDestroy {
protected readonly ngZone: NgZone,
protected readonly overlay: Overlay,
protected readonly positionFactory: PopoverEditPositionStrategyFactory,
- protected readonly viewContainerRef: ViewContainerRef) {}
+ protected readonly scrollDispatcher: ScrollDispatcher,
+ protected readonly viewContainerRef: ViewContainerRef,
+ protected readonly viewportRuler: ViewportRuler) {}
ngAfterViewInit(): void {
this._startListeningToEditEvents();
@@ -161,8 +196,8 @@ export class CdkPopoverEdit implements AfterViewInit, OnDestroy {
this.overlayRef = this.overlay.create({
disposeOnNavigation: true,
panelClass: EDIT_PANE_CLASS,
- positionStrategy: this.positionFactory.forElementRef(this.elementRef),
- scrollStrategy: this.overlay.scrollStrategies.reposition({autoClose: true}),
+ positionStrategy: this._getPositionStrategy(),
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
});
this.focusTrap = this.focusTrapFactory.create(this.overlayRef.overlayElement);
@@ -179,6 +214,41 @@ export class CdkPopoverEdit implements AfterViewInit, OnDestroy {
this.viewContainerRef,
{$implicit: this.context}));
this.focusTrap!.focusInitialElementWhenReady();
+
+ // Update the size of the popup initially and on subsequent changes to
+ // scroll position and viewport size.
+ merge(this.scrollDispatcher.scrolled(), this.viewportRuler.change())
+ .pipe(
+ startWith(null),
+ takeUntil(this.overlayRef!.detachments()),
+ takeUntil(this.destroyed),
+ )
+ .subscribe(() => {
+ this._updateOverlaySize();
+ });
+ }
+
+ private _getOverlayCells(): HTMLElement[] {
+ const cell = closest(this.elementRef.nativeElement!, CELL_SELECTOR) as HTMLElement;
+
+ if (!this._colspan.before && !this._colspan.after) {
+ return [cell];
+ }
+
+ const row = closest(this.elementRef.nativeElement!, ROW_SELECTOR)!;
+ const rowCells = Array.from(row.querySelectorAll(CELL_SELECTOR)) as HTMLElement[];
+ const ownIndex = rowCells.indexOf(cell);
+
+ return rowCells.slice(
+ ownIndex - (this._colspan.before || 0), ownIndex + (this._colspan.after || 0) + 1);
+ }
+
+ private _getPositionStrategy(): PositionStrategy {
+ return this.positionFactory.positionStrategyForCells(this._getOverlayCells());
+ }
+
+ private _updateOverlaySize(): void {
+ this.overlayRef!.updateSize(this.positionFactory.sizeConfigForCells(this._getOverlayCells()));
}
private _maybeReturnFocusToCell(): void {
diff --git a/src/material-examples/cdk-popover-edit-cell-span-vanilla-table/cdk-popover-edit-cell-span-vanilla-table-example.css b/src/material-examples/cdk-popover-edit-cell-span-vanilla-table/cdk-popover-edit-cell-span-vanilla-table-example.css
new file mode 100644
index 00000000000..d19b3c187ac
--- /dev/null
+++ b/src/material-examples/cdk-popover-edit-cell-span-vanilla-table/cdk-popover-edit-cell-span-vanilla-table-example.css
@@ -0,0 +1,23 @@
+.example-table {
+ width: 100%;
+}
+
+.example-table th {
+ text-align: left;
+}
+
+.example-table td,
+.example-table th {
+ min-width: 300px;
+ width: 25%;
+}
+
+.example-input-container {
+ display: flex;
+ justify-content: stretch;
+}
+
+.example-input-container input {
+ flex: 1;
+ font-size: 14px;
+}
diff --git a/src/material-examples/cdk-popover-edit-cell-span-vanilla-table/cdk-popover-edit-cell-span-vanilla-table-example.html b/src/material-examples/cdk-popover-edit-cell-span-vanilla-table/cdk-popover-edit-cell-span-vanilla-table-example.html
new file mode 100644
index 00000000000..df7be8b086d
--- /dev/null
+++ b/src/material-examples/cdk-popover-edit-cell-span-vanilla-table/cdk-popover-edit-cell-span-vanilla-table-example.html
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+ | No. |
+ First name |
+ Middle name |
+ Last name |
+
+
+
+ | {{person.id}} |
+
+
+ {{person.firstName}}
+
+
+
+
+ |
+
+
+ {{person.middleName}}
+
+
+
+
+ |
+
+
+ {{person.lastName}}
+
+
+
+
+ |
+
+
diff --git a/src/material-examples/cdk-popover-edit-cell-span-vanilla-table/cdk-popover-edit-cell-span-vanilla-table-example.ts b/src/material-examples/cdk-popover-edit-cell-span-vanilla-table/cdk-popover-edit-cell-span-vanilla-table-example.ts
new file mode 100644
index 00000000000..d419ab3c579
--- /dev/null
+++ b/src/material-examples/cdk-popover-edit-cell-span-vanilla-table/cdk-popover-edit-cell-span-vanilla-table-example.ts
@@ -0,0 +1,45 @@
+import {Component} from '@angular/core';
+import {NgForm} from '@angular/forms';
+
+export interface Person {
+ id: number;
+ firstName: string;
+ middleName: string;
+ lastName: string;
+}
+
+const PERSON_DATA: Person[] = [
+ {id: 1, firstName: 'Terra', middleName: 'Maduin', lastName: 'Branford'},
+ {id: 2, firstName: 'Locke', middleName: '', lastName: 'Cole'},
+ {id: 3, firstName: 'Celes', middleName: 'Gestahl', lastName: 'Chere'},
+ {id: 4, firstName: 'Edgar', middleName: 'Roni', lastName: 'Figaro'},
+ {id: 5, firstName: 'Sabin', middleName: 'Rene', lastName: 'Figaro'},
+ {id: 6, firstName: 'Clyde', middleName: '"Shadow"', lastName: 'Arrowny'},
+ {id: 7, firstName: 'Setzer', middleName: '', lastName: 'Gabbiani'},
+ {id: 8, firstName: 'Cid', middleName: 'Del Norte', lastName: 'Marquez'},
+ {id: 9, firstName: 'Mog', middleName: '', lastName: 'McMoogle'},
+];
+
+/**
+ * @title CDK Popover Edit spanning multiple columns on an HTML data-table
+ */
+@Component({
+ selector: 'cdk-popover-edit-cell-span-vanilla-table-example',
+ styleUrls: ['cdk-popover-edit-cell-span-vanilla-table-example.css'],
+ templateUrl: 'cdk-popover-edit-cell-span-vanilla-table-example.html',
+})
+export class CdkPopoverEditCellSpanVanillaTableExample {
+ readonly preservedValues = new WeakMap();
+
+ readonly persons = PERSON_DATA;
+
+ onSubmit(person: Person, f: NgForm) {
+ if (!f.valid) {
+ return;
+ }
+
+ person.firstName = f.value['firstName'];
+ person.middleName = f.value['middleName'];
+ person.lastName = f.value['lastName'];
+ }
+}
diff --git a/src/material-examples/cdk-popover-edit-vanilla-table/cdk-popover-edit-vanilla-table-example.css b/src/material-examples/cdk-popover-edit-vanilla-table/cdk-popover-edit-vanilla-table-example.css
index eba60b11b33..adb5aa2786f 100644
--- a/src/material-examples/cdk-popover-edit-vanilla-table/cdk-popover-edit-vanilla-table-example.css
+++ b/src/material-examples/cdk-popover-edit-vanilla-table/cdk-popover-edit-vanilla-table-example.css
@@ -8,5 +8,6 @@
.example-table td,
.example-table th {
+ min-width: 300px;
width: 25%;
}
diff --git a/src/material-examples/cdk-popover-edit-vanilla-table/cdk-popover-edit-vanilla-table-example.html b/src/material-examples/cdk-popover-edit-vanilla-table/cdk-popover-edit-vanilla-table-example.html
index fa31d52a25c..fe4d4dbd791 100644
--- a/src/material-examples/cdk-popover-edit-vanilla-table/cdk-popover-edit-vanilla-table-example.html
+++ b/src/material-examples/cdk-popover-edit-vanilla-table/cdk-popover-edit-vanilla-table-example.html
@@ -4,7 +4,7 @@
its context. It could be reused in multiple cells.
-->
- | |