Skip to content

Commit 7a4c80c

Browse files
authored
feat: allow to disable editing for individual cells (#7269)
1 parent 234935b commit 7a4c80c

File tree

4 files changed

+393
-51
lines changed

4 files changed

+393
-51
lines changed

packages/grid-pro/src/vaadin-grid-pro-edit-column-mixin.d.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,24 @@ export declare class GridProEditColumnMixinClass<TItem> {
5858
*/
5959
editorValuePath: string;
6060

61+
/**
62+
* A function to check whether a specific cell of this column can be
63+
* edited. This allows to disable editing of individual rows or cells,
64+
* based on the item.
65+
*
66+
* Receives a `model` object containing the item for an individual row,
67+
* and should return a boolean indicating whether the column's cell in
68+
* that row is editable.
69+
*
70+
* The `model` object contains:
71+
* - `model.index` The index of the item.
72+
* - `model.item` The item.
73+
* - `model.expanded` Sublevel toggle state.
74+
* - `model.level` Level of the tree represented with a horizontal offset of the toggle button.
75+
* - `model.selected` Selected state.
76+
*/
77+
isCellEditable: (model: GridItemModel<TItem>) => boolean;
78+
6179
protected _getEditorComponent(cell: HTMLElement): HTMLElement | null;
6280

6381
protected _getEditorValue(editor: HTMLElement): unknown | null;

packages/grid-pro/src/vaadin-grid-pro-edit-column-mixin.js

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
* See https://vaadin.com/commercial-license-and-service-terms for the full
99
* license.
1010
*/
11-
import { addValueToAttribute } from '@vaadin/component-base/src/dom-utils.js';
1211
import { get, set } from '@vaadin/component-base/src/path-utils.js';
1312

1413
/**
@@ -84,13 +83,36 @@ export const GridProEditColumnMixin = (superClass) =>
8483
sync: true,
8584
},
8685

86+
/**
87+
* A function to check whether a specific cell of this column can be
88+
* edited. This allows to disable editing of individual rows or cells,
89+
* based on the item.
90+
*
91+
* Receives a `model` object containing the item for an individual row,
92+
* and should return a boolean indicating whether the column's cell in
93+
* that row is editable.
94+
*
95+
* The `model` object contains:
96+
* - `model.index` The index of the item.
97+
* - `model.item` The item.
98+
* - `model.expanded` Sublevel toggle state.
99+
* - `model.level` Level of the tree represented with a horizontal offset of the toggle button.
100+
* - `model.selected` Selected state.
101+
*
102+
* @type {(model: GridItemModel) => boolean}
103+
*/
104+
isCellEditable: {
105+
type: Function,
106+
observer: '_isCellEditableChanged',
107+
},
108+
87109
/** @private */
88110
_oldRenderer: Function,
89111
};
90112
}
91113

92114
static get observers() {
93-
return ['_editModeRendererChanged(editModeRenderer, __initialized)', '_cellsChanged(_cells)'];
115+
return ['_editModeRendererChanged(editModeRenderer, __initialized)'];
94116
}
95117

96118
constructor() {
@@ -121,11 +143,9 @@ export const GridProEditColumnMixin = (superClass) =>
121143
}
122144

123145
/** @private */
124-
_cellsChanged() {
125-
this._cells.forEach((cell) => {
126-
const target = cell._focusButton || cell;
127-
addValueToAttribute(target, 'part', 'editable-cell');
128-
});
146+
_isCellEditableChanged(newValue, oldValue) {
147+
// Re-render grid to update editable-cell part names
148+
this._grid.requestContentUpdate();
129149
}
130150

131151
/** @private */

packages/grid-pro/src/vaadin-grid-pro-inline-editing-mixin.js

Lines changed: 90 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import { animationFrame } from '@vaadin/component-base/src/async.js';
1212
import { Debouncer } from '@vaadin/component-base/src/debounce.js';
1313
import { get, set } from '@vaadin/component-base/src/path-utils.js';
14+
import { iterateRowCells, updatePart } from '@vaadin/grid/src/vaadin-grid-helpers.js';
1415

1516
/**
1617
* @polymerMixin
@@ -218,6 +219,10 @@ export const InlineEditingMixin = (superClass) =>
218219
return;
219220
}
220221

222+
if (!this._isCellEditable(cell)) {
223+
return;
224+
}
225+
221226
callback(e);
222227
}
223228
});
@@ -325,8 +330,10 @@ export const InlineEditingMixin = (superClass) =>
325330

326331
/** @private */
327332
_startEdit(cell, column) {
333+
const isCellEditable = this._isCellEditable(cell);
334+
328335
// TODO: remove `_editingDisabled` after Flow counterpart is updated.
329-
if (this.disabled || this._editingDisabled) {
336+
if (this.disabled || this._editingDisabled || !isCellEditable) {
330337
return;
331338
}
332339
// Cancel debouncer enqueued on focusout
@@ -421,59 +428,67 @@ export const InlineEditingMixin = (superClass) =>
421428

422429
this._cancelStopEdit();
423430

424-
const cols = this._getEditColumns();
425-
431+
const editableColumns = this._getEditColumns();
426432
const { cell, column, model } = this.__edited;
427-
const colIndex = cols.indexOf(column);
428-
const { index } = model;
429433

430-
let nextCol = null;
431-
let nextIdx = index;
432-
433-
// Enter key
434-
if (e.keyCode === 13) {
435-
nextCol = column;
436-
437-
// Move up / down
438-
if (this.enterNextRow) {
439-
nextIdx = e.shiftKey ? index - 1 : index + 1;
440-
}
434+
this._stopEdit();
435+
e.preventDefault();
436+
// Prevent vaadin-grid handler from being called
437+
e.stopImmediatePropagation();
438+
439+
// Try to find the next editable cell
440+
let nextIndex = model.index;
441+
let nextColumn = column;
442+
let nextCell = cell;
443+
let directionX = 0;
444+
let directionY = 0;
445+
446+
// Enter key: move up / down
447+
if (e.keyCode === 13 && this.enterNextRow) {
448+
directionY = e.shiftKey ? -1 : 1;
441449
}
442450

443451
// Tab: move right / left
444452
if (e.keyCode === 9) {
445-
if (e.shiftKey) {
446-
if (cols[colIndex - 1]) {
447-
nextCol = cols[colIndex - 1];
448-
} else if (index > 0) {
449-
nextIdx = index - 1;
450-
nextCol = cols[cols.length - 1];
453+
directionX = e.shiftKey ? -1 : 1;
454+
}
455+
456+
if (directionX || directionY) {
457+
while (nextCell) {
458+
if (directionX) {
459+
// Move horizontally
460+
nextColumn = editableColumns[editableColumns.indexOf(nextColumn) + directionX];
461+
if (!nextColumn) {
462+
// Wrap to the next or previous row
463+
nextIndex += directionX;
464+
nextColumn = editableColumns[directionX > 0 ? 0 : editableColumns.length - 1];
465+
}
466+
}
467+
// Move vertically
468+
if (directionY) {
469+
nextIndex += directionY;
470+
}
471+
// Stop looking if the next cell is editable
472+
const nextRow = this._getRowByIndex(nextIndex);
473+
// eslint-disable-next-line @typescript-eslint/no-loop-func
474+
nextCell = nextRow && Array.from(nextRow.children).find((cell) => cell._column === nextColumn);
475+
if (nextCell && this._isCellEditable(nextCell)) {
476+
break;
451477
}
452-
} else if (cols[colIndex + 1]) {
453-
nextCol = cols[colIndex + 1];
454-
} else {
455-
nextIdx = index + 1;
456-
nextCol = cols[0];
457478
}
458479
}
459480

460-
const nextRow = nextIdx === index ? cell.parentNode : this._getRowByIndex(nextIdx) || null;
461-
462-
this._stopEdit();
463-
464-
if (nextRow && nextCol) {
465-
const nextCell = Array.from(nextRow.children).find((cell) => cell._column === nextCol);
466-
e.preventDefault();
467-
468-
// Prevent vaadin-grid handler from being called
469-
e.stopImmediatePropagation();
481+
// Focus current cell as fallback
482+
if (!nextCell) {
483+
nextCell = cell;
484+
nextIndex = model.index;
485+
}
470486

471-
if (!this.singleCellEdit && nextCell !== cell) {
472-
this._startEdit(nextCell, nextCol);
473-
} else {
474-
this._ensureScrolledToIndex(nextIdx);
475-
nextCell.focus();
476-
}
487+
if (!this.singleCellEdit && nextCell !== cell) {
488+
this._startEdit(nextCell, nextColumn);
489+
} else {
490+
this._ensureScrolledToIndex(nextIndex);
491+
nextCell.focus();
477492
}
478493
}
479494

@@ -492,6 +507,38 @@ export const InlineEditingMixin = (superClass) =>
492507
super._updateItem(row, item);
493508
}
494509

510+
/**
511+
* Override method from `StylingMixin` to apply `editable-cell` part to the
512+
* cells of edit columns.
513+
*
514+
* @override
515+
*/
516+
_generateCellPartNames(row, model) {
517+
super._generateCellPartNames(row, model);
518+
519+
iterateRowCells(row, (cell) => {
520+
const isEditable = this._isCellEditable(cell);
521+
const target = cell._focusButton || cell;
522+
updatePart(target, isEditable, 'editable-cell');
523+
});
524+
}
525+
526+
/** @private */
527+
_isCellEditable(cell) {
528+
const column = cell._column;
529+
// Not editable if the column is not an edit column
530+
if (!this._isEditColumn(column)) {
531+
return false;
532+
}
533+
// Cell is editable by default if isCellEditable is not configured
534+
if (!column.isCellEditable) {
535+
return true;
536+
}
537+
// Otherwise, check isCellEditable function
538+
const model = this.__getRowModel(cell.parentElement);
539+
return column.isCellEditable(model);
540+
}
541+
495542
/**
496543
* Fired before exiting the cell edit mode, if the value has been changed.
497544
* If the default is prevented, value change would not be applied.

0 commit comments

Comments
 (0)