grid updates to header focus and edit behavior

This commit is contained in:
Sarah Higley 2020-09-14 23:21:07 -07:00
Родитель bc588ba037
Коммит 16bffee95d
7 изменённых файлов: 77 добавлений и 53 удалений

Просмотреть файл

@ -1,5 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>cross</title>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="currentColor">
<title>cancel</title>
<path d="M31.708 25.708c-0-0-0-0-0-0l-9.708-9.708 9.708-9.708c0-0 0-0 0-0 0.105-0.105 0.18-0.227 0.229-0.357 0.133-0.356 0.057-0.771-0.229-1.057l-4.586-4.586c-0.286-0.286-0.702-0.361-1.057-0.229-0.13 0.048-0.252 0.124-0.357 0.228 0 0-0 0-0 0l-9.708 9.708-9.708-9.708c-0-0-0-0-0-0-0.105-0.104-0.227-0.18-0.357-0.228-0.356-0.133-0.771-0.057-1.057 0.229l-4.586 4.586c-0.286 0.286-0.361 0.702-0.229 1.057 0.049 0.13 0.124 0.252 0.229 0.357 0 0 0 0 0 0l9.708 9.708-9.708 9.708c-0 0-0 0-0 0-0.104 0.105-0.18 0.227-0.229 0.357-0.133 0.355-0.057 0.771 0.229 1.057l4.586 4.586c0.286 0.286 0.702 0.361 1.057 0.229 0.13-0.049 0.252-0.124 0.357-0.229 0-0 0-0 0-0l9.708-9.708 9.708 9.708c0 0 0 0 0 0 0.105 0.105 0.227 0.18 0.357 0.229 0.356 0.133 0.771 0.057 1.057-0.229l4.586-4.586c0.286-0.286 0.362-0.702 0.229-1.057-0.049-0.13-0.124-0.252-0.229-0.357z"></path>
</svg>

До

Ширина:  |  Высота:  |  Размер: 1010 B

После

Ширина:  |  Высота:  |  Размер: 1.0 KiB

Просмотреть файл

@ -1,5 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="currentColor">
<title>pencil</title>
<path d="M27 0c2.761 0 5 2.239 5 5 0 1.126-0.372 2.164-1 3l-2 2-7-7 2-2c0.836-0.628 1.874-1 3-1zM2 23l-2 9 9-2 18.5-18.5-7-7-18.5 18.5zM22.362 11.362l-14 14-1.724-1.724 14-14 1.724 1.724z"></path>
</svg>

До

Ширина:  |  Высота:  |  Размер: 357 B

После

Ширина:  |  Высота:  |  Размер: 377 B

Просмотреть файл

@ -1,5 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="currentColor">
<title>checkmark</title>
<path d="M27 4l-15 15-7-7-5 5 12 12 20-20z"></path>
</svg>

До

Ширина:  |  Высота:  |  Размер: 215 B

После

Ширина:  |  Высота:  |  Размер: 235 B

2
src/components.d.ts поставляемый
Просмотреть файл

@ -627,7 +627,6 @@ export namespace Components {
*/
'renderCustomCell': (content: string, colIndex: number, rowIndex: number) => string | HTMLElement;
'rowSelection': RowSelectionPattern;
'simpleEditable': boolean;
/**
* Index of the column that best labels a row
*/
@ -686,7 +685,6 @@ export namespace Components {
*/
'renderCustomCell'?: (content: string, colIndex: number, rowIndex: number) => string | HTMLElement;
'rowSelection'?: RowSelectionPattern;
'simpleEditable'?: boolean;
/**
* Index of the column that best labels a row
*/

Просмотреть файл

@ -130,6 +130,19 @@ input:checked ~ .selection-indicator::after,
white-space: nowrap;
}
.cell .edit-button,
.cell .confirm-button {
background: transparent;
border: 1px solid transparent;
color: inherit;
padding: 0.25em;
margin-right: 0.5em;
}
.cell .confirm-button {
margin: 0;
}
.cell .grid-button {
margin: 0.2em;
background-color: #fff;
@ -138,10 +151,6 @@ input:checked ~ .selection-indicator::after,
padding: 0.5em;
}
.cell .grid-button + .grid-button {
margin-left: 0;
}
.cell .grid-stepper {
width: 50%;
background-color: #fff;
@ -150,9 +159,15 @@ input:checked ~ .selection-indicator::after,
padding: 0.5em;
}
.grid-button img {
width: 1.75em;
height: 1.75em;
.grid-button svg,
.edit-button svg {
width: 1.25em;
height: 1.25em;
}
.confirm-button svg {
width: 1em;
height: 1em;
}
.heading-cell {
@ -191,6 +206,8 @@ input:checked ~ .selection-indicator::after,
}
.grid-button:focus,
.edit-button:focus,
.confirm-button:focus,
.cell input:focus {
box-shadow: 0 0 1px 3px #0067b8;
outline: 4px solid transparent;

Просмотреть файл

@ -3,7 +3,7 @@
* Licensed under the MIT license. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Component, Event, EventEmitter, Listen, Prop, State, Watch } from '@stencil/core';
import { Component, Event, EventEmitter, Prop, State, Watch } from '@stencil/core';
import { Column } from './grid-helpers';
import { renderRow, RowOptions, RowSelectionPattern } from './row';
import { renderHeaderCell, Sort } from './header-cell';
@ -58,7 +58,6 @@ export class SuiGridv2 {
@Prop() editOnClick: boolean;
@Prop() headerActionsMenu: boolean;
@Prop() rowSelection: RowSelectionPattern;
@Prop() simpleEditable = false;
@Prop() useApplicationRole = false;
/**
@ -152,6 +151,7 @@ export class SuiGridv2 {
}
componentDidUpdate() {
this.callFocus && console.log('calling focus on', this.focusRef);
// handle focus
this.callFocus && this.focusRef && this.focusRef.focus();
this.callFocus = false;
@ -161,13 +161,6 @@ export class SuiGridv2 {
this.callInputSelection = false;
}
@Listen('focusout')
onBlur(event: FocusEvent) {
if (this.isEditing && event.relatedTarget && event.relatedTarget !== this.focusRef && !this.simpleEditable) {
this.updateEditing(false, false);
}
}
render() {
const {
columns = [],
@ -185,17 +178,22 @@ export class SuiGridv2 {
const rowSelectionState = this.getSelectionState();
const tableRole = useApplicationRole ? 'application' : gridType;
return <table role={tableRole} aria-roledescription={useApplicationRole ? 'editable data grid' : null} class="grid" aria-labelledby={this.labelledBy} aria-readonly={editable ? null : 'true'}>
return <table role={tableRole} aria-roledescription={useApplicationRole ? 'editable data grid' : null} class="grid" aria-labelledby={this.labelledBy} aria-readonly={editable ? null : 'true'} onKeyDown={this.onCellKeydown.bind(this)}>
{description ? <caption>{description}</caption> : null}
<thead role="rowgroup" class="grid-header">
<tr role="row" class="row">
{rowSelection !== RowSelectionPattern.None ?
<th role="columnheader" aria-labelledby="select-all-header" class={{'checkbox-cell': true, 'indeterminate': rowSelectionState === 'indeterminate'}}>
<th
role="columnheader"
aria-labelledby="select-all-header"
class={{'checkbox-cell': true, 'indeterminate': rowSelectionState === 'indeterminate'}}
>
<span class="visuallyHidden" id="select-all-header">select row</span>
<input
type="checkbox"
aria-label="select all rows"
checked={!!rowSelectionState}
tabIndex={this.gridType === 'grid' ? this.activeCell.join('-') === '0-0' ? 0 : -1 : null}
ref={(el) => {
if (rowSelectionState === 'indeterminate') {
el.indeterminate = true;
@ -210,7 +208,9 @@ export class SuiGridv2 {
column,
colIndex: index,
actionsMenu: headerActionsMenu,
isActiveCell: this.activeCell.join('-') === `${index}-0`,
isSortedColumn: sortedColumn === index,
setFocusRef: (el) => this.focusRef = el,
sortDirection: sortState,
onSort: this.onSortColumn.bind(this),
onFilter: this.onFilterInput.bind(this)
@ -218,12 +218,12 @@ export class SuiGridv2 {
})}
</tr>
</thead>
<tbody role="rowgroup" class="grid-body" onKeyDown={this.onCellKeydown.bind(this)}>
<tbody role="rowgroup" class="grid-body">
{sortedCells.map((cells = [], index) => {
const isSelected = !!selectedRows.get(cells);
let rowOptions: RowOptions = {
cells,
index,
index: index + 1,
isSelected,
selection: rowSelection,
renderCell: this.renderCell.bind(this),
@ -232,7 +232,7 @@ export class SuiGridv2 {
};
if (this.rowSelection === RowSelectionPattern.Aria) {
const isActiveRow = this.activeCell[1] === index;
const isActiveRow = this.activeCell[1] === index + 1;
rowOptions = {
...rowOptions,
isActiveRow,
@ -275,17 +275,20 @@ export class SuiGridv2 {
}
private onCellClick(row, column) {
if (this.simpleEditable || this.columns[column].actionsColumn) return;
// always edit on click if clicking the active cell
if (this.editOnClick || (this.activeCell[0] === column && this.activeCell[1] === row)) {
this.updateEditing(true, true);
}
const previousCell = this.activeCell;
this.activeCell = [column, row];
const isActiveCellClick = previousCell[0] === column && previousCell[1] === row;
// exit editing if not clicking on active cell
if (!isActiveCellClick && !this.editOnClick && this.isEditing) {
this.saveCell(previousCell[0], previousCell[1], (this.focusRef as HTMLInputElement).value);
this.updateEditing(false, false);
}
}
private onCellDoubleClick(column) {
if (!this.editOnClick && !this.simpleEditable && !this.columns[column].actionsColumn) {
if (!this.editOnClick && !this.columns[column].actionsColumn) {
console.log('double click, editing')
this.updateEditing(true, true);
event.preventDefault();
}
@ -325,7 +328,7 @@ export class SuiGridv2 {
break;
case 'Enter':
case ' ':
if (this.simpleEditable || this.columns[colIndex].actionsColumn) return;
if (this.columns[colIndex].actionsColumn) return;
event.preventDefault();
this.updateEditing(true, true);
break;
@ -367,13 +370,6 @@ export class SuiGridv2 {
this.filterEvent.emit(filters);
}
private onInputBlur(event: FocusEvent) {
if (!this.simpleEditable) {
const cellIndex = this.rowSelection === RowSelectionPattern.Checkbox ? this.activeCell[0] - 1 : this.activeCell[0];
this.saveCell(cellIndex, this.activeCell[1], (event.target as HTMLInputElement).value);
}
}
private onInputKeyDown(event: KeyboardEvent) {
// allow input to handle its own keystrokes
event.stopPropagation();
@ -482,22 +478,33 @@ export class SuiGridv2 {
onMouseDown={() => { this.mouseDown = true; }}
>
{this.isEditing && isActiveCell && !isActionsColumn
? <input type="text" value={content} class="cell-edit" onKeyDown={this.onInputKeyDown.bind(this)} onBlur={this.onInputBlur.bind(this)} ref={(el) => this.focusRef = el} />
? <input type="text" value={content} class="cell-edit" onKeyDown={this.onInputKeyDown.bind(this)} ref={(el) => this.focusRef = el} />
: <span class="cell-content">{this.renderCellContent(content, cellIndex, rowIndex)}</span>
}
{this.simpleEditable && !cellColumn.actionsColumn ?
{!cellColumn.actionsColumn ?
this.isEditing && isActiveCell ?
[
<button class="grid-button" key={`${currentCellKey}-save`} type="button" onClick={(event) => { this.onEditButtonClick(event, rowIndex, cellIndex, false, true) }}><img src="/assets/ok.svg" alt="Save" role="img" /></button>,
<button class="grid-button" key={`${currentCellKey}-cancel`} type="button" onClick={(event) => { this.onEditButtonClick(event, rowIndex, cellIndex, false) }}><img src="/assets/cancel.svg" alt="Cancel" role="img" /></button>
<button class="confirm-button" key={`${currentCellKey}-save`} type="button" onClick={(event) => { this.onEditButtonClick(event, rowIndex, cellIndex, false, true) }}>
<svg role="img" aria-label="Save" width="32" height="32" viewBox="0 0 32 32" fill="currentColor">
<path d="M27 4l-15 15-7-7-5 5 12 12 20-20z"></path>
</svg>
</button>,
<button class="confirm-button" key={`${currentCellKey}-cancel`} type="button" onClick={(event) => { this.onEditButtonClick(event, rowIndex, cellIndex, false) }}>
<svg role="img" aria-label="Cancel" width="32" height="32" viewBox="0 0 32 32" fill="currentColor">
<path d="M31.708 25.708c-0-0-0-0-0-0l-9.708-9.708 9.708-9.708c0-0 0-0 0-0 0.105-0.105 0.18-0.227 0.229-0.357 0.133-0.356 0.057-0.771-0.229-1.057l-4.586-4.586c-0.286-0.286-0.702-0.361-1.057-0.229-0.13 0.048-0.252 0.124-0.357 0.228 0 0-0 0-0 0l-9.708 9.708-9.708-9.708c-0-0-0-0-0-0-0.105-0.104-0.227-0.18-0.357-0.228-0.356-0.133-0.771-0.057-1.057 0.229l-4.586 4.586c-0.286 0.286-0.361 0.702-0.229 1.057 0.049 0.13 0.124 0.252 0.229 0.357 0 0 0 0 0 0l9.708 9.708-9.708 9.708c-0 0-0 0-0 0-0.104 0.105-0.18 0.227-0.229 0.357-0.133 0.355-0.057 0.771 0.229 1.057l4.586 4.586c0.286 0.286 0.702 0.361 1.057 0.229 0.13-0.049 0.252-0.124 0.357-0.229 0-0 0-0 0-0l9.708-9.708 9.708 9.708c0 0 0 0 0 0 0.105 0.105 0.227 0.18 0.357 0.229 0.356 0.133 0.771 0.057 1.057-0.229l4.586-4.586c0.286-0.286 0.362-0.702 0.229-1.057-0.049-0.13-0.124-0.252-0.229-0.357z"></path>
</svg>
</button>
]
: <button
class="grid-button"
class="edit-button"
key={`${currentCellKey}-edit`}
type="button"
ref={isActiveCell ? (el) => { this.focusRef = el; } : null}
tabIndex={isActiveCell ? null : -1}
// ref={isActiveCell ? (el) => { this.focusRef = el; } : null}
onClick={(event) => { this.onEditButtonClick(event, rowIndex, cellIndex, true) }}>
<img src="/assets/edit.svg" alt="Edit" role="img" />
<svg role="img" aria-label="Edit" width="32" height="32" viewBox="0 0 32 32" fill="currentColor">
<path d="M27 0c2.761 0 5 2.239 5 5 0 1.126-0.372 2.164-1 3l-2 2-7-7 2-2c0.836-0.628 1.874-1 3-1zM2 23l-2 9 9-2 18.5-18.5-7-7-18.5 18.5zM22.362 11.362l-14 14-1.724-1.724 14-14 1.724 1.724z"></path>
</svg>
</button>
: null
}
@ -535,7 +542,7 @@ export class SuiGridv2 {
id={`action-${rowIndex}-${colIndex}`}
aria-labelledby={`action-${rowIndex}-${colIndex} cell-${rowIndex}-${this.titleColumn}`}
tabIndex={this.gridType === 'grid' ? isActiveCell ? 0 : -1 : null}
onClick={(() => alert(`This is just a test, you successfully activated the ${content} button`))}
onClick={(() => alert(`This is just a test, you successfully activated the ${button} button`))}
>{button}</button>
));
}
@ -614,7 +621,7 @@ export class SuiGridv2 {
}
private updateEditing(editing: boolean, callFocus: boolean) {
if (!this.editable && !this.simpleEditable) {
if (!this.editable) {
return
};

Просмотреть файл

@ -14,17 +14,19 @@ export interface HeaderOptions {
actionsMenu?: boolean;
column: Column;
colIndex: number;
isActiveCell: boolean;
isSortedColumn: boolean;
setFocusRef?: (elem: HTMLElement) => void;
sortDirection: Sort;
onSort: (index: number) => void;
onFilter: (value: string, column: Column) => void;
}
export function renderHeaderCell(options: HeaderOptions): JSX.Element {
const { actionsMenu = false, colIndex, column, isSortedColumn = false, sortDirection } = options;
const { actionsMenu = false, colIndex, column, isActiveCell = false, isSortedColumn = false, setFocusRef, sortDirection } = options;
const idBase = `col-${colIndex}`;
// @ts-ignore
return <th role="columnheader" abbr={column.name} class="cell heading-cell" aria-labelledby={idBase} aria-sort={column.sortable ? isSortedColumn ? sortDirection : 'none' : null}>
return <th role="columnheader" abbr={column.name} class="cell heading-cell" aria-labelledby={idBase} aria-sort={column.sortable ? isSortedColumn ? sortDirection : 'none' : null} tabIndex={isActiveCell ? 0 : -1} ref={isActiveCell ? (el) => { setFocusRef(el); } : null}>
<span id={idBase} class="column-title">{column.name}</span>
{actionsMenu ? renderActionsMenu(options, idBase) : renderStaticActions(options, idBase)}
</th>