mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
[ACS-10280]: Keyboard Navigation: Search: Unnecessary stops in the results table using the Tab key (#11736)
* [ACS-10280]: move selection handling to row * [ACS-10280]: clean up * [ACS-10280]: rollback title * [ACS-10280]: migrate to conditional aria-hidden * [ACS-10280]: revert to fix hxp * [ACS-10280]: sonar fix * [ACS-10280] Use new Angular control flow syntax * [ACS-10280] Unit test fix --------- Co-authored-by: MichalKinas <michal.kinas@hyland.com>
This commit is contained in:
co-authored by
MichalKinas
parent
565db01819
commit
a3e13da3a4
@@ -493,16 +493,4 @@ describe('ShareDataTableAdapter', () => {
|
|||||||
expect(adapter.getRowByNodeId('fake-node-id-2')).toEqual(fakeShareDataRows[1]);
|
expect(adapter.getRowByNodeId('fake-node-id-2')).toEqual(fakeShareDataRows[1]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should initialize with allowFocusOnRows as true by default', () => {
|
|
||||||
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, null);
|
|
||||||
expect(adapter.allowFocusOnRows).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set allowFocusOnRows value', () => {
|
|
||||||
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, null);
|
|
||||||
expect(adapter.allowFocusOnRows).toBe(true);
|
|
||||||
adapter.setAllowFocusOnTableRows(false);
|
|
||||||
expect(adapter.allowFocusOnRows).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ export class ShareDataTableAdapter implements DataTableAdapter {
|
|||||||
permissionsStyle: PermissionStyleModel[];
|
permissionsStyle: PermissionStyleModel[];
|
||||||
selectedRow: DataRow;
|
selectedRow: DataRow;
|
||||||
allowDropFiles: boolean;
|
allowDropFiles: boolean;
|
||||||
allowFocusOnRows = true;
|
|
||||||
|
|
||||||
set sortingMode(value: string) {
|
set sortingMode(value: string) {
|
||||||
let newValue = (value || 'client').toLowerCase();
|
let newValue = (value || 'client').toLowerCase();
|
||||||
@@ -197,10 +196,6 @@ export class ShareDataTableAdapter implements DataTableAdapter {
|
|||||||
this.imageResolver = resolver;
|
this.imageResolver = resolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAllowFocusOnTableRows(allow: boolean) {
|
|
||||||
this.allowFocusOnRows = allow;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getFolderIcon(node: any) {
|
private getFolderIcon(node: any) {
|
||||||
if (this.isSmartFolder(node)) {
|
if (this.isSmartFolder(node)) {
|
||||||
return this.thumbnailService.getMimeTypeIcon('smartFolder');
|
return this.thumbnailService.getMimeTypeIcon('smartFolder');
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -36,7 +36,6 @@ import { HarnessLoader } from '@angular/cdk/testing';
|
|||||||
import { ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';
|
import { ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';
|
||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
import { DataTableAdapter } from '@alfresco/adf-core';
|
|
||||||
import { MatTooltipHarness } from '@angular/material/tooltip/testing';
|
import { MatTooltipHarness } from '@angular/material/tooltip/testing';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -1668,6 +1667,135 @@ describe('Accessibility', () => {
|
|||||||
expect(datatableBodyCellAttributes['role']).toEqual('gridcell');
|
expect(datatableBodyCellAttributes['role']).toEqual('gridcell');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('isRepresentationContent', () => {
|
||||||
|
const setupTable = (columns: ObjectDataColumn[]) => {
|
||||||
|
dataTable.data = new ObjectDataTableAdapter([{ photo: '', status: '', name: 'test', created: '' }], columns);
|
||||||
|
dataTable.ngOnChanges({});
|
||||||
|
fixture.detectChanges();
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should render image column cell with aria-hidden', () => {
|
||||||
|
setupTable([new ObjectDataColumn({ key: 'photo', type: 'image' })]);
|
||||||
|
|
||||||
|
const cell = testingUtils.getByCSS('.adf-datatable-body .adf-datatable-cell--image');
|
||||||
|
expect(cell.attributes['aria-hidden']).toBe('true');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render icon column cell with aria-hidden', () => {
|
||||||
|
setupTable([new ObjectDataColumn({ key: 'status', type: 'icon' })]);
|
||||||
|
|
||||||
|
const cell = testingUtils.getByCSS('.adf-datatable-body .adf-datatable-cell--icon');
|
||||||
|
expect(cell.attributes['aria-hidden']).toBe('true');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render text column cell without aria-hidden', () => {
|
||||||
|
setupTable([new ObjectDataColumn({ key: 'name', type: 'text' })]);
|
||||||
|
|
||||||
|
const cell = testingUtils.getByCSS('.adf-datatable-body .adf-datatable-cell--text');
|
||||||
|
expect(cell.attributes['aria-hidden']).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render date column cell without aria-hidden', () => {
|
||||||
|
setupTable([new ObjectDataColumn({ key: 'created', type: 'date' })]);
|
||||||
|
|
||||||
|
const cell = testingUtils.getByCSS('.adf-datatable-body .adf-datatable-cell--date');
|
||||||
|
expect(cell.attributes['aria-hidden']).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly set aria-hidden when mixing representation and non-representation columns', () => {
|
||||||
|
setupTable([new ObjectDataColumn({ key: 'photo', type: 'image' }), new ObjectDataColumn({ key: 'name', type: 'text' })]);
|
||||||
|
|
||||||
|
const cells = testingUtils.getAllByCSS('.adf-datatable-body .adf-datatable-cell-data');
|
||||||
|
expect(cells[0].attributes['aria-hidden']).toBe('true');
|
||||||
|
expect(cells[1].attributes['aria-hidden']).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('row aria-description', () => {
|
||||||
|
const rowSelector = '.adf-datatable-body .adf-datatable-row';
|
||||||
|
|
||||||
|
it('should have aria-description on row when multiselect is enabled', () => {
|
||||||
|
const dataRows = [{ name: 'test1' }, { name: 'test2' }];
|
||||||
|
dataTable.multiselect = true;
|
||||||
|
dataTable.data = new ObjectDataTableAdapter([], [new ObjectDataColumn({ key: 'name' })]);
|
||||||
|
|
||||||
|
dataTable.ngOnChanges({
|
||||||
|
rows: new SimpleChange(null, dataRows, false)
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
const rows = testingUtils.getAllByCSS(rowSelector);
|
||||||
|
expect(rows.length).toBeGreaterThan(0);
|
||||||
|
expect(rows[0].attributes['aria-description']).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have aria-description on row when enableDragRows is true', () => {
|
||||||
|
const dataRows = [{ name: 'test1' }, { name: 'test2' }];
|
||||||
|
dataTable.enableDragRows = true;
|
||||||
|
dataTable.data = new ObjectDataTableAdapter([], [new ObjectDataColumn({ key: 'name' })]);
|
||||||
|
|
||||||
|
dataTable.ngOnChanges({
|
||||||
|
rows: new SimpleChange(null, dataRows, false)
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
const rows = testingUtils.getAllByCSS(rowSelector);
|
||||||
|
expect(rows.length).toBeGreaterThan(0);
|
||||||
|
expect(rows[0].attributes['aria-description']).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not have aria-description on row when neither multiselect nor enableDragRows is enabled', () => {
|
||||||
|
const dataRows = [{ name: 'test1' }, { name: 'test2' }];
|
||||||
|
dataTable.multiselect = false;
|
||||||
|
dataTable.enableDragRows = false;
|
||||||
|
dataTable.data = new ObjectDataTableAdapter([], [new ObjectDataColumn({ key: 'name' })]);
|
||||||
|
|
||||||
|
dataTable.ngOnChanges({
|
||||||
|
rows: new SimpleChange(null, dataRows, false)
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
const rows = testingUtils.getAllByCSS(rowSelector);
|
||||||
|
expect(rows.length).toBeGreaterThan(0);
|
||||||
|
expect(rows[0].attributes['aria-description']).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('drag button and multiselect checkbox accessibility', () => {
|
||||||
|
it('should have aria-hidden on drag button', () => {
|
||||||
|
const dataRows = [{ name: 'test1' }];
|
||||||
|
dataTable.enableDragRows = true;
|
||||||
|
dataTable.data = new ObjectDataTableAdapter([], [new ObjectDataColumn({ key: 'name' })]);
|
||||||
|
dataTable.showHeader = ShowHeaderMode.Never;
|
||||||
|
|
||||||
|
dataTable.ngOnChanges({
|
||||||
|
rows: new SimpleChange(null, dataRows, false)
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
const dragButton = testingUtils.getByCSS('.adf-datatable__actions-cell [aria-hidden="true"]');
|
||||||
|
expect(dragButton).toBeTruthy();
|
||||||
|
expect(dragButton.attributes['aria-hidden']).toBe('true');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have aria-hidden and negative tabindex on multiselect checkbox', () => {
|
||||||
|
const dataRows = [{ name: 'test1' }];
|
||||||
|
dataTable.multiselect = true;
|
||||||
|
dataTable.data = new ObjectDataTableAdapter([], [new ObjectDataColumn({ key: 'name' })]);
|
||||||
|
dataTable.showHeader = ShowHeaderMode.Never;
|
||||||
|
|
||||||
|
dataTable.ngOnChanges({
|
||||||
|
rows: new SimpleChange(null, dataRows, false)
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
const checkboxContainer = testingUtils.getByCSS('.adf-checkbox-sr-only');
|
||||||
|
expect(checkboxContainer).toBeTruthy();
|
||||||
|
expect(checkboxContainer.attributes['aria-hidden']).toBe('true');
|
||||||
|
expect(checkboxContainer.nativeElement.tabIndex).toBe(-1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('aria-sort', () => {
|
describe('aria-sort', () => {
|
||||||
let column: DataColumn;
|
let column: DataColumn;
|
||||||
|
|
||||||
@@ -1700,6 +1828,32 @@ describe('Accessibility', () => {
|
|||||||
describe('Focus row', () => {
|
describe('Focus row', () => {
|
||||||
let event: KeyboardEvent;
|
let event: KeyboardEvent;
|
||||||
let dataRows: { name: string }[];
|
let dataRows: { name: string }[];
|
||||||
|
const rowSelector = '.adf-datatable-body .adf-datatable-row';
|
||||||
|
|
||||||
|
const setRows = (): void => {
|
||||||
|
dataTable.ngOnChanges({
|
||||||
|
rows: new SimpleChange(null, dataRows, false)
|
||||||
|
});
|
||||||
|
fixture.detectChanges();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBodyRows = (): DebugElement[] => testingUtils.getAllByCSS(rowSelector);
|
||||||
|
|
||||||
|
const expectRowsTabindex = (expected: string | null): void => {
|
||||||
|
const rowElements = getBodyRows();
|
||||||
|
expect(rowElements.length).toBeGreaterThan(0);
|
||||||
|
expect(rowElements.every((row) => row.nativeElement.getAttribute('tabindex') === expected)).toBeTrue();
|
||||||
|
};
|
||||||
|
|
||||||
|
const activateRow = (rowIndex: number): void => {
|
||||||
|
const rowElement = getBodyRows()[rowIndex];
|
||||||
|
testingUtils.setDebugElement(rowElement);
|
||||||
|
testingUtils.clickByCSS('.adf-datatable-cell-data');
|
||||||
|
};
|
||||||
|
|
||||||
|
const dispatchKeyUp = (keyboardEvent: KeyboardEvent): void => {
|
||||||
|
fixture.debugElement.nativeElement.dispatchEvent(keyboardEvent);
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
event = new KeyboardEvent('keyup', {
|
event = new KeyboardEvent('keyup', {
|
||||||
@@ -1711,6 +1865,26 @@ describe('Accessibility', () => {
|
|||||||
dataTable.data = new ObjectDataTableAdapter([], [new ObjectDataColumn({ key: 'name' })]);
|
dataTable.data = new ObjectDataTableAdapter([], [new ObjectDataColumn({ key: 'name' })]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set tabindex to null (disabled === true) on datatable-body rows when neither multiselect nor enableDragRows is enabled', () => {
|
||||||
|
setRows();
|
||||||
|
|
||||||
|
expectRowsTabindex(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set tabindex to 0 (disabled === false) on datatable-body rows when multiselect is enabled', () => {
|
||||||
|
dataTable.multiselect = true;
|
||||||
|
setRows();
|
||||||
|
|
||||||
|
expectRowsTabindex('0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set tabindex to 0 (disabled === false) on datatable-body rows when enableDragRows is enabled', () => {
|
||||||
|
dataTable.enableDragRows = true;
|
||||||
|
setRows();
|
||||||
|
|
||||||
|
expectRowsTabindex('0');
|
||||||
|
});
|
||||||
|
|
||||||
it('should focus next row on ArrowDown event', () => {
|
it('should focus next row on ArrowDown event', () => {
|
||||||
event = new KeyboardEvent('keyup', {
|
event = new KeyboardEvent('keyup', {
|
||||||
code: 'ArrowDown',
|
code: 'ArrowDown',
|
||||||
@@ -1718,35 +1892,23 @@ describe('Accessibility', () => {
|
|||||||
keyCode: 40
|
keyCode: 40
|
||||||
} as KeyboardEventInit);
|
} as KeyboardEventInit);
|
||||||
|
|
||||||
dataTable.ngOnChanges({
|
dataTable.multiselect = true;
|
||||||
rows: new SimpleChange(null, dataRows, false)
|
setRows();
|
||||||
});
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
dataTable.ngAfterViewInit();
|
dataTable.ngAfterViewInit();
|
||||||
|
|
||||||
const rowElement = testingUtils.getAllByCSS('.adf-datatable-body .adf-datatable-row')[0];
|
activateRow(0);
|
||||||
testingUtils.setDebugElement(rowElement);
|
dispatchKeyUp(event);
|
||||||
testingUtils.clickByCSS('.adf-datatable-cell');
|
|
||||||
|
|
||||||
fixture.debugElement.nativeElement.dispatchEvent(event);
|
|
||||||
|
|
||||||
expect(document.activeElement?.getAttribute('data-automation-id')).toBe('datatable-row-1');
|
expect(document.activeElement?.getAttribute('data-automation-id')).toBe('datatable-row-1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should focus previous row on ArrowUp event', () => {
|
it('should focus previous row on ArrowUp event', () => {
|
||||||
dataTable.ngOnChanges({
|
dataTable.multiselect = true;
|
||||||
rows: new SimpleChange(null, dataRows, false)
|
setRows();
|
||||||
});
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
dataTable.ngAfterViewInit();
|
dataTable.ngAfterViewInit();
|
||||||
|
|
||||||
const rowElement = testingUtils.getAllByCSS('.adf-datatable-body .adf-datatable-row')[1];
|
activateRow(1);
|
||||||
testingUtils.setDebugElement(rowElement);
|
dispatchKeyUp(event);
|
||||||
testingUtils.clickByCSS('.adf-datatable-cell');
|
|
||||||
|
|
||||||
fixture.debugElement.nativeElement.dispatchEvent(event);
|
|
||||||
|
|
||||||
expect(document.activeElement?.getAttribute('data-automation-id')).toBe('datatable-row-0');
|
expect(document.activeElement?.getAttribute('data-automation-id')).toBe('datatable-row-0');
|
||||||
});
|
});
|
||||||
@@ -1818,47 +1980,6 @@ describe('Accessibility', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ShareDatatable adapter allowFocusOnRows', () => {
|
|
||||||
class ShareAdapterMock extends ObjectDataTableAdapter {
|
|
||||||
public allowFocusOnRows = true;
|
|
||||||
|
|
||||||
constructor(data: any[], schema: DataColumn[]) {
|
|
||||||
super(data, schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
setAllowFocusOnTableRows(allow: boolean) {
|
|
||||||
this.allowFocusOnRows = allow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const testAllowFocusOnRows = (expectedTabindex: string | null, adapter: DataTableAdapter) => {
|
|
||||||
const fakeDataRows = [new FakeDataRow(), new FakeDataRow()];
|
|
||||||
|
|
||||||
adapter.setRows(fakeDataRows);
|
|
||||||
dataTable.data = adapter;
|
|
||||||
fixture.detectChanges();
|
|
||||||
const rowElements = testingUtils.getAllByCSS('.adf-datatable-body adf-datatable-row');
|
|
||||||
expect(rowElements.length).toBeGreaterThan(0);
|
|
||||||
expect(rowElements.every((row) => row.nativeElement.getAttribute('tabindex') === expectedTabindex)).toBeTrue();
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should set tabindex to null (disabled === true) on datatable-body rows when allowFocusOnRows is set to false in ShareDatatable adapter', () => {
|
|
||||||
const adapter = new ShareAdapterMock([], []);
|
|
||||||
adapter.setAllowFocusOnTableRows(false);
|
|
||||||
testAllowFocusOnRows(null, adapter);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set tabindex to 0 (disabled === false) on datatable-body rows when allowFocusOnRows is not set explicitly in ShareDatatable adapter and falls back to default value ', () => {
|
|
||||||
const adapter = new ShareAdapterMock([], []);
|
|
||||||
adapter.setAllowFocusOnTableRows(true);
|
|
||||||
testAllowFocusOnRows('0', adapter);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set tabindex to 0 (disabled === false) by default on datatable-body rows when allowFocusOnRows is not defined in Datatable adapter (fallback case)', () => {
|
|
||||||
const adapter = new ObjectDataTableAdapter([], []);
|
|
||||||
testAllowFocusOnRows('0', adapter);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create focus trap on main menu open', () => {
|
it('should create focus trap on main menu open', () => {
|
||||||
dataTable.showHeader = ShowHeaderMode.Always;
|
dataTable.showHeader = ShowHeaderMode.Always;
|
||||||
dataTable.showMainDatatableActions = true;
|
dataTable.showMainDatatableActions = true;
|
||||||
|
|||||||
@@ -853,6 +853,10 @@ export class DataTableComponent implements OnInit, AfterContentInit, OnChanges,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isRepresentationContent(col: DataColumn): boolean {
|
||||||
|
return col.type === 'image' || col.type === 'icon';
|
||||||
|
}
|
||||||
|
|
||||||
asIconValue(row: DataRow, col: DataColumn): string {
|
asIconValue(row: DataRow, col: DataColumn): string {
|
||||||
if (this.isIconValue(row, col)) {
|
if (this.isIconValue(row, col)) {
|
||||||
const value = this.data.getValue(row, col) || '';
|
const value = this.data.getValue(row, col) || '';
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import { Subject } from 'rxjs';
|
|||||||
|
|
||||||
export interface DataTableAdapter {
|
export interface DataTableAdapter {
|
||||||
rowsChanged?: Subject<Array<DataRow>>;
|
rowsChanged?: Subject<Array<DataRow>>;
|
||||||
allowFocusOnRows?: boolean;
|
|
||||||
|
|
||||||
selectedRow: DataRow;
|
selectedRow: DataRow;
|
||||||
getRows(): Array<DataRow>;
|
getRows(): Array<DataRow>;
|
||||||
@@ -34,5 +33,4 @@ export interface DataTableAdapter {
|
|||||||
getSorting(): DataSorting;
|
getSorting(): DataSorting;
|
||||||
setSorting(sorting: DataSorting): void;
|
setSorting(sorting: DataSorting): void;
|
||||||
sort(key?: string, direction?: string): void;
|
sort(key?: string, direction?: string): void;
|
||||||
setAllowFocusOnTableRows?(allow: boolean): void;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -398,6 +398,8 @@
|
|||||||
"ACTIONS": "Actions",
|
"ACTIONS": "Actions",
|
||||||
"SELECT_ALL": "Select all",
|
"SELECT_ALL": "Select all",
|
||||||
"SELECT_FILE": "Select file",
|
"SELECT_FILE": "Select file",
|
||||||
|
"SELECTED": "Selected",
|
||||||
|
"ROW_SELECTION": "Press Space to toggle row selection",
|
||||||
"SORT_ASCENDING": "Ascending",
|
"SORT_ASCENDING": "Ascending",
|
||||||
"SORT_DESCENDING": "Descending",
|
"SORT_DESCENDING": "Descending",
|
||||||
"SORT_NONE": "None",
|
"SORT_NONE": "None",
|
||||||
@@ -408,6 +410,7 @@
|
|||||||
"ICON_DISABLED": "Disabled",
|
"ICON_DISABLED": "Disabled",
|
||||||
"ROW_OPTION_BUTTON": "Actions",
|
"ROW_OPTION_BUTTON": "Actions",
|
||||||
"DRAG": "Drag button",
|
"DRAG": "Drag button",
|
||||||
|
"DRAGGABLE": "Draggable",
|
||||||
"EMPTY_HEADER": "Empty header",
|
"EMPTY_HEADER": "Empty header",
|
||||||
"RESIZE_COLUMN": "Resize {{ column }} column. Use arrow keys to adjust width, Shift for larger steps.",
|
"RESIZE_COLUMN": "Resize {{ column }} column. Use arrow keys to adjust width, Shift for larger steps.",
|
||||||
"COLUMN_WIDTH_CHANGED": "{{ column }} column width: {{ width }} pixels"
|
"COLUMN_WIDTH_CHANGED": "{{ column }} column width: {{ width }} pixels"
|
||||||
|
|||||||
Reference in New Issue
Block a user