Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions demo/scripts/controlsV2/mainPane/MainPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
Snapshots,
} from 'roosterjs-content-model-types';
import {
AnnouncePlugin,
AutoFormatPlugin,
CustomReplacePlugin,
EditPlugin,
Expand Down Expand Up @@ -575,6 +576,7 @@ export class MainPane extends React.Component<{}, MainPaneState> {
undeletableLinkChecker: undeletableLinkChecker,
}),
pluginList.touch && new TouchPlugin(),
pluginList.announce && new AnnouncePlugin(),
].filter(x => !!x);
}
}
Expand All @@ -589,6 +591,8 @@ const AnnounceStringMap: Record<KnownAnnounceStrings, string> = {
announceItalicOff: 'Italic Off',
announceUnderlineOn: 'Underline On',
announceUnderlineOff: 'Underline Off',
selected: '{0}, selected',
unselected: '{0}, unselected',
};

function getAnnouncingString(key: KnownAnnounceStrings) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const initialState: OptionState = {
customReplace: true,
hiddenProperty: true,
touch: true,
announce: true,
},
defaultFormat: {
fontFamily: 'Calibri',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface BuildInPluginList {
customReplace: boolean;
hiddenProperty: boolean;
touch: boolean;
announce: boolean;
}

export interface OptionState {
Expand Down
1 change: 1 addition & 0 deletions demo/scripts/controlsV2/sidePane/editorOptions/Plugins.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ export class Plugins extends PluginsBase<keyof BuildInPluginList> {
)}
{this.renderPluginItem('hiddenProperty', 'Hidden Property')}
{this.renderPluginItem('touch', 'Touch')}
{this.renderPluginItem('announce', 'Announce')}
</tbody>
</table>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export const setDOMSelection: SetDOMSelection = (core, selection, skipSelectionC
firstColumn: firstCell.col,
lastRow: lastCell.row,
lastColumn: lastCell.col,
tableSelectionInfo: selection.tableSelectionInfo,
};

const tableId = ensureUniqueId(table, TABLE_ID);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,6 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {

if (oldCo || firstCo.row != lastCo.row || firstCo.col != lastCo.col) {
this.state.tableSelection.lastCo = lastCo;

this.setDOMSelection(
{
type: 'table',
Expand All @@ -762,6 +761,7 @@ class SelectionPlugin implements PluginWithState<SelectionPluginState> {
firstColumn: firstCo.col,
lastRow: lastCo.row,
lastColumn: lastCo.col,
tableSelectionInfo: this.state.tableSelection,
},
{ table, firstCo, lastCo, parsedTable, startNode }
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@ describe('setDOMSelection', () => {
firstRow,
lastColumn,
lastRow,
tableSelectionInfo: undefined,
} as any;
const selectNodeSpy = jasmine.createSpy('selectNode');
const collapseSpy = jasmine.createSpy('collapse');
Expand Down Expand Up @@ -948,13 +949,14 @@ describe('setDOMSelection', () => {

setDOMSelection(core, mockedSelection);

const resultSelection = {
const resultSelection: DOMSelection = {
type: 'table',
table: table,
firstColumn: 0,
firstRow: 0,
lastColumn: 3,
lastRow: 3,
tableSelectionInfo: undefined,
};

expect(core.selection).toEqual({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,7 @@ describe('SelectionPlugin handle table selection', () => {
firstColumn: 0,
lastRow: 0,
lastColumn: 0,
tableSelectionInfo: jasmine.any(Object),
});
});

Expand Down Expand Up @@ -1155,6 +1156,7 @@ describe('SelectionPlugin handle table selection', () => {
firstColumn: 0,
lastRow: 0,
lastColumn: 1,
tableSelectionInfo: jasmine.any(Object),
});
expect(state.tableSelection).toEqual({
table,
Expand All @@ -1178,6 +1180,7 @@ describe('SelectionPlugin handle table selection', () => {
firstColumn: 0,
lastRow: 0,
lastColumn: 1,
tableSelectionInfo: jasmine.any(Object),
});
expect(state.tableSelection).toEqual({
table,
Expand Down Expand Up @@ -1259,6 +1262,7 @@ describe('SelectionPlugin handle table selection', () => {
firstColumn: 0,
lastRow: 0,
lastColumn: 0,
tableSelectionInfo: jasmine.any(Object),
});
expect(state.tableSelection).toEqual({
table: table1,
Expand Down Expand Up @@ -1290,6 +1294,7 @@ describe('SelectionPlugin handle table selection', () => {
firstColumn: 0,
lastRow: 0,
lastColumn: 0,
tableSelectionInfo: jasmine.any(Object),
});
expect(state.tableSelection).toEqual({
table: table2,
Expand Down Expand Up @@ -2167,8 +2172,8 @@ describe('SelectionPlugin handle table selection', () => {
firstColumn: 1,
lastRow: 0,
lastColumn: 1,
tableSelectionInfo: jasmine.any(Object),
});
expect(announceSpy).not.toHaveBeenCalled();
});

it('From Range, Press Shift+Down', () => {
Expand Down Expand Up @@ -2235,8 +2240,8 @@ describe('SelectionPlugin handle table selection', () => {
firstColumn: 1,
lastRow: 1,
lastColumn: 1,
tableSelectionInfo: jasmine.any(Object),
});
expect(announceSpy).not.toHaveBeenCalled();
});

it('From Range, Press Shift+Down to ouside of table', () => {
Expand Down Expand Up @@ -2457,9 +2462,9 @@ describe('SelectionPlugin handle table selection', () => {
firstColumn: 1,
lastRow: 1,
lastColumn: 0,
tableSelectionInfo: jasmine.any(Object),
});
expect(preventDefaultSpy).toHaveBeenCalled();
expect(announceSpy).not.toHaveBeenCalled();
});

it('From Table, Press Shift+Up', () => {
Expand Down Expand Up @@ -2516,9 +2521,9 @@ describe('SelectionPlugin handle table selection', () => {
firstColumn: 0,
lastRow: 0,
lastColumn: 1,
tableSelectionInfo: jasmine.any(Object),
});
expect(preventDefaultSpy).toHaveBeenCalled();
expect(announceSpy).not.toHaveBeenCalled();
});

it('From Table, Format, Press Shift+Left', () => {
Expand All @@ -2529,6 +2534,16 @@ describe('SelectionPlugin handle table selection', () => {
lastColumn: 1,
lastRow: 1,
table,
tableSelectionInfo: {
table,
parsedTable: [
[td1, td2],
[td3, td4],
],
startNode: td2,
firstCo: { row: 0, col: 1 },
lastCo: { row: 1, col: 1 },
},
});

spyOn(parseTableCells, 'parseTableCells').and.returnValue([
Expand Down Expand Up @@ -2595,9 +2610,9 @@ describe('SelectionPlugin handle table selection', () => {
firstColumn: 1,
lastRow: 1,
lastColumn: 0,
tableSelectionInfo: jasmine.any(Object),
});
expect(preventDefaultSpy).toHaveBeenCalled();
expect(announceSpy).not.toHaveBeenCalled();
});

it('From Table, Format, Press Shift+Up', () => {
Expand All @@ -2608,6 +2623,16 @@ describe('SelectionPlugin handle table selection', () => {
lastColumn: 1,
lastRow: 1,
table,
tableSelectionInfo: {
table,
parsedTable: [
[td1, td2],
[td3, td4],
],
startNode: td3,
firstCo: { row: 1, col: 0 },
lastCo: { row: 1, col: 1 },
},
});

spyOn(parseTableCells, 'parseTableCells').and.returnValue([
Expand Down Expand Up @@ -2674,9 +2699,9 @@ describe('SelectionPlugin handle table selection', () => {
firstColumn: 0,
lastRow: 0,
lastColumn: 1,
tableSelectionInfo: jasmine.any(Object),
});
expect(preventDefaultSpy).toHaveBeenCalled();
expect(announceSpy).not.toHaveBeenCalled();
});
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { getIsSelectingOrUnselecting, retrieveStringFromParsedTable } from './tableSelectionUtils';
import type {
IEditor,
PluginEvent,
EditorPlugin,
DOMSelection,
} from 'roosterjs-content-model-types';

/**
* AnnouncePlugin helps editor announce table selection changes for accessibility
*/
export class AnnouncePlugin implements EditorPlugin {
private editor: IEditor | null = null;
private previousSelection: DOMSelection | null = null;

/**
* Get name of this plugin
*/
getName() {
return 'Announce';
}

/**
* The first method that editor will call to a plugin when editor is initializing.
* It will pass in the editor instance, plugin should take this chance to save the
* editor reference so that it can call to any editor method or format API later.
* @param editor The editor object
*/
initialize(editor: IEditor) {
this.editor = editor;
}

/**
* The last method that editor will call to a plugin before it is disposed.
* Plugin can take this chance to clear the reference to editor. After this method is
* called, plugin should not call to any editor method since it will result in error.
*/
dispose() {
this.editor = null;
this.previousSelection = null;
}

/**
* Core method for a plugin. Once an event happens in editor, editor will call this
* method of each plugin to handle the event as long as the event is not handled
* exclusively by another plugin.
* @param event The event to handle:
*/
onPluginEvent(event: PluginEvent) {
if (!this.editor) {
return;
}

if (event.eventType == 'selectionChanged') {
if (event.newSelection?.type == 'table') {
const action = getIsSelectingOrUnselecting(
this.previousSelection?.type == 'table' ? this.previousSelection : null,
event.newSelection
);
if (action && event.newSelection.tableSelectionInfo) {
this.editor.announce({
defaultStrings: action === 'unselecting' ? 'unselected' : 'selected',
formatStrings: [
retrieveStringFromParsedTable(event.newSelection.tableSelectionInfo),
],
});
}
}

this.previousSelection = event.newSelection;
}
}
}
Loading
Loading