devexpress-richedit 25.2.7 → 26.1.2-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/gulpfile.js +1 -1
- package/bin/index-custom.js +1 -1
- package/bin/localization-builder.js +1 -1
- package/bin/nspell-index.js +1 -1
- package/bin/nspell.webpack.config.js +1 -1
- package/bin/webpack-externals.js +1 -1
- package/bin/webpack.config.js +1 -1
- package/dist/dx.richedit.css +0 -1
- package/dist/dx.richedit.d.ts +1 -1
- package/dist/dx.richedit.js +1271 -701
- package/dist/dx.richedit.min.js +2 -2
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/lib/client/client-rich-edit.js +2 -2
- package/lib/client/default-localization.js +25 -7
- package/lib/client/dialogs/alert-dialog.d.ts +1 -1
- package/lib/client/dialogs/bookmark-dialog.d.ts +1 -1
- package/lib/client/dialogs/delete-table-cells-dialog.d.ts +1 -1
- package/lib/client/dialogs/dialog-base.d.ts +1 -1
- package/lib/client/dialogs/find-replace-dialog.d.ts +1 -1
- package/lib/client/dialogs/finish-and-merge-dialog.d.ts +1 -1
- package/lib/client/dialogs/font-dialog.d.ts +1 -1
- package/lib/client/dialogs/hyperlink-dialog.d.ts +1 -1
- package/lib/client/dialogs/insert-merge-field-dialog.d.ts +1 -1
- package/lib/client/dialogs/insert-table-cells-dialog.d.ts +1 -1
- package/lib/client/dialogs/insert-table-dialog.d.ts +1 -1
- package/lib/client/dialogs/page-setup-dialog.d.ts +1 -1
- package/lib/client/dialogs/paragraph-dialog.d.ts +1 -1
- package/lib/client/dialogs/split-table-cells-dialog.d.ts +1 -1
- package/lib/client/dialogs/tabs-dialog.d.ts +1 -1
- package/lib/client/public/rich-edit.js +2 -0
- package/lib/client/public/utils.js +5 -1
- package/lib/common/canvas/renderes/common/document-renderer.js +7 -1
- package/lib/common/commands/command-manager.d.ts +2 -0
- package/lib/common/commands/command-manager.js +6 -0
- package/lib/common/commands/layout/switch-view-command.js +2 -1
- package/lib/common/commands/selection/go-to-next-word-command.d.ts +3 -4
- package/lib/common/commands/selection/go-to-next-word-command.js +2 -9
- package/lib/common/commands/selection/go-to-prev-word-command.d.ts +3 -2
- package/lib/common/commands/selection/go-to-prev-word-command.js +2 -2
- package/lib/common/commands/selection/select-all-document-command.d.ts +2 -0
- package/lib/common/commands/selection/select-all-document-command.js +6 -0
- package/lib/common/commands/selection/selection-command-base.d.ts +2 -0
- package/lib/common/commands/selection/selection-command-base.js +8 -0
- package/lib/common/event-manager.js +0 -1
- package/lib/common/formats/txt/txt-exporter.js +1 -1
- package/lib/common/input-controller.js +2 -0
- package/lib/common/interfaces/i-rich-edit-core.d.ts +2 -0
- package/lib/common/layout-formatter/formatter/base-formatter.d.ts +1 -0
- package/lib/common/layout-formatter/formatter/base-formatter.js +11 -0
- package/lib/common/layout-formatter/row/result.d.ts +0 -1
- package/lib/common/layout-formatter/row/result.js +4 -19
- package/lib/common/layout-formatter/row/size-engine/row-sizes-manager.js +1 -3
- package/lib/common/model/sub-document.d.ts +11 -0
- package/lib/common/model/sub-document.js +66 -22
- package/lib/common/rich-edit-core.d.ts +3 -0
- package/lib/common/rich-edit-core.js +18 -0
- package/lib/common/screen-reader-manager.d.ts +45 -0
- package/lib/common/screen-reader-manager.js +309 -0
- package/lib/common/utils/interval-utils.d.ts +4 -0
- package/lib/common/utils/interval-utils.js +25 -0
- package/package.json +3 -3
|
@@ -5,13 +5,15 @@ import { SearchUtils } from '@devexpress/utils/lib/utils/search';
|
|
|
5
5
|
import { SpellCheckerIntervalsManager } from '../spelling/intervals-manager';
|
|
6
6
|
import { RunIterator } from './character/run-iterator';
|
|
7
7
|
import { ResetFormattingCacheType } from './document-model';
|
|
8
|
-
import { Field } from './fields/field';
|
|
9
8
|
import { FullChunkAndRunInfo } from './full-chunk-and-run-info';
|
|
10
9
|
import { BookmarksManipulator } from './manipulators/bookmarks-manipulator';
|
|
11
10
|
import { ModelIterator } from './model-iterator';
|
|
12
11
|
import { PositionManager } from './position/position-manager';
|
|
13
12
|
import { RichUtils } from './rich-utils';
|
|
14
13
|
import { RunType } from './runs/run-type';
|
|
14
|
+
import { ParagraphIterator } from '../layout-formatter/box/generator/one-dimension-itertors';
|
|
15
|
+
import { isDefined } from '@devexpress/utils/lib/utils/common';
|
|
16
|
+
import { FieldIterator } from '../layout-formatter/box/generator/recursive-objects-iterators';
|
|
15
17
|
export class SubDocument {
|
|
16
18
|
static { this.AUTOGENERATE_ID = -1; }
|
|
17
19
|
static { this.MAIN_SUBDOCUMENT_ID = 0; }
|
|
@@ -63,36 +65,78 @@ export class SubDocument {
|
|
|
63
65
|
return buffer;
|
|
64
66
|
}
|
|
65
67
|
getSimpleText(interval) {
|
|
66
|
-
|
|
68
|
+
return this.getVisibleText(interval, {
|
|
69
|
+
useFieldResult: true,
|
|
70
|
+
charMap: {
|
|
71
|
+
[RichUtils.specialCharacters.LineBreak]: ' ',
|
|
72
|
+
[RichUtils.specialCharacters.SectionMark]: '\r\n',
|
|
73
|
+
[RichUtils.specialCharacters.ParagraphMark]: '\r\n'
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
getVisibleText(interval, options = {}) {
|
|
78
|
+
let result = [];
|
|
67
79
|
let pos = interval.start;
|
|
68
|
-
|
|
69
|
-
const fieldIndex = Field.normedBinaryIndexOf(this.fields, pos);
|
|
70
|
-
if (fieldIndex > -1) {
|
|
71
|
-
let field = this.fields[fieldIndex];
|
|
72
|
-
while (field.parent)
|
|
73
|
-
field = field.parent;
|
|
74
|
-
pos = field.getFieldStartPosition();
|
|
75
|
-
}
|
|
80
|
+
const { charMap = {}, useFieldResult = false, includeNumberedListMarks = false, inlinePictureAltText = null, resolveAdditionalText = undefined, } = options;
|
|
76
81
|
const iterator = new ModelIterator(this, true);
|
|
77
82
|
iterator.setPosition(pos);
|
|
83
|
+
const fieldIterator = new FieldIterator(this.fields);
|
|
84
|
+
fieldIterator.init(pos);
|
|
85
|
+
const paragraphIterator = new ParagraphIterator(this.paragraphs);
|
|
86
|
+
paragraphIterator.init(pos);
|
|
78
87
|
while (pos < interval.end) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
if (this.isPositionVisible(pos, fieldIterator.indexes, useFieldResult)) {
|
|
89
|
+
const paragraph = this.paragraphs[paragraphIterator.index];
|
|
90
|
+
if (includeNumberedListMarks && pos === paragraph.startLogPosition.value && paragraph.isInList())
|
|
91
|
+
result.push(paragraph.getNumberingListText() + paragraph.getNumberingListSeparatorChar());
|
|
92
|
+
if (resolveAdditionalText) {
|
|
93
|
+
const resolved = resolveAdditionalText(fieldIterator.indexes, pos);
|
|
94
|
+
if (resolved)
|
|
95
|
+
result.push(resolved);
|
|
96
|
+
}
|
|
97
|
+
switch (iterator.run.getType()) {
|
|
98
|
+
case RunType.InlinePictureRun:
|
|
99
|
+
if (inlinePictureAltText != null) {
|
|
100
|
+
let placeholder;
|
|
101
|
+
if (typeof inlinePictureAltText === "function")
|
|
102
|
+
placeholder = inlinePictureAltText(iterator.run);
|
|
103
|
+
else
|
|
104
|
+
placeholder = inlinePictureAltText;
|
|
105
|
+
if (isDefined(placeholder))
|
|
106
|
+
result.push(placeholder);
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
case RunType.SectionRun:
|
|
110
|
+
case RunType.ParagraphRun:
|
|
111
|
+
case RunType.FieldCodeStartRun:
|
|
112
|
+
case RunType.FieldCodeEndRun:
|
|
113
|
+
case RunType.TextRun:
|
|
114
|
+
const char = iterator.getCurrentChar();
|
|
115
|
+
result.push(charMap[char] ?? char);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
if (!iterator.moveToNextChar())
|
|
89
119
|
break;
|
|
90
120
|
}
|
|
91
|
-
if (!iterator.
|
|
121
|
+
else if (!iterator.moveToNextRun())
|
|
92
122
|
break;
|
|
93
123
|
pos = iterator.getAbsolutePosition();
|
|
124
|
+
paragraphIterator.update(pos);
|
|
125
|
+
fieldIterator.update(pos);
|
|
126
|
+
}
|
|
127
|
+
return result.join("");
|
|
128
|
+
}
|
|
129
|
+
isPositionVisible(pos, fieldIndexes, useFieldResult) {
|
|
130
|
+
for (const index of fieldIndexes) {
|
|
131
|
+
const field = this.fields[index];
|
|
132
|
+
const isCodeView = !useFieldResult && field.showCode;
|
|
133
|
+
const invisibleInterval = isCodeView
|
|
134
|
+
? field.getResultIntervalWithBorders()
|
|
135
|
+
: field.getCodeIntervalWithBorders();
|
|
136
|
+
if (invisibleInterval.contains(pos))
|
|
137
|
+
return false;
|
|
94
138
|
}
|
|
95
|
-
return
|
|
139
|
+
return true;
|
|
96
140
|
}
|
|
97
141
|
splitRun(position) {
|
|
98
142
|
var info = this.getRunAndIndexesByPosition(position);
|
|
@@ -38,6 +38,7 @@ import { RulerSettings } from './ui/ruler/settings';
|
|
|
38
38
|
import { SearchManager } from './ui/search-manager';
|
|
39
39
|
import { IExportModelOptions } from './formats/i-document-exporter';
|
|
40
40
|
import { ModelScrollManager } from './scroll/model-scroll-manager';
|
|
41
|
+
import { ScreenReaderManager } from './screen-reader-manager';
|
|
41
42
|
export declare abstract class RichEditCore implements IRichEditControl {
|
|
42
43
|
modelManager: IModelManager;
|
|
43
44
|
barHolder: IBarHolder;
|
|
@@ -71,6 +72,7 @@ export declare abstract class RichEditCore implements IRichEditControl {
|
|
|
71
72
|
owner: IControlOwner;
|
|
72
73
|
selectionModelChangesListener: SelectionModelChangesListener;
|
|
73
74
|
readonly pdfHelperFrame: PdfHelperFrame;
|
|
75
|
+
screenReaderManager: ScreenReaderManager;
|
|
74
76
|
private closed;
|
|
75
77
|
sessionGuid: string;
|
|
76
78
|
private clientGuid;
|
|
@@ -119,6 +121,7 @@ export declare abstract class RichEditCore implements IRichEditControl {
|
|
|
119
121
|
getExportModelOptions(initOptions?: Partial<IExportModelOptions>): IExportModelOptions;
|
|
120
122
|
ensureDocumentIsFullyFormatted(): void;
|
|
121
123
|
protected abstract createSpellChecker(): any;
|
|
124
|
+
protected createAriaLiveElement(element: HTMLElement): HTMLDivElement;
|
|
122
125
|
protected createViewElement(id: string, element: HTMLElement): HTMLDivElement;
|
|
123
126
|
private isUsedInnerClipboard;
|
|
124
127
|
getExportDocumentFormat(): DocumentFormat;
|
|
@@ -44,6 +44,7 @@ import { HorizontalRulerControl } from './ui/ruler/ruler';
|
|
|
44
44
|
import { SearchManager } from './ui/search-manager';
|
|
45
45
|
import { isDefined } from '@devexpress/utils/lib/utils/common';
|
|
46
46
|
import { ModelScrollManager } from './scroll/model-scroll-manager';
|
|
47
|
+
import { ScreenReaderManager } from './screen-reader-manager';
|
|
47
48
|
export class RichEditCore {
|
|
48
49
|
get isReadOnlyPersistent() { return this.readOnly === ReadOnlyMode.Persistent; }
|
|
49
50
|
get model() { return this.modelManager.model; }
|
|
@@ -84,6 +85,7 @@ export class RichEditCore {
|
|
|
84
85
|
this.commandManager = this.createCommandManager();
|
|
85
86
|
this.shortcutManager = this.createShortcutManager();
|
|
86
87
|
this.searchManager = new SearchManager(this);
|
|
88
|
+
this.screenReaderManager = new ScreenReaderManager(this, this.createAriaLiveElement(element));
|
|
87
89
|
this.boxVisualizerManager.initListeners(this.viewManager);
|
|
88
90
|
this.autoCorrectService = new AutoCorrectService(this, this.modelManager.richOptions.autoCorrect);
|
|
89
91
|
this.clientSideEvents = new ClientSideEvents(this.owner);
|
|
@@ -142,6 +144,7 @@ export class RichEditCore {
|
|
|
142
144
|
this.selection.onChanged.add(this.boxVisualizerManager.resizeBoxVisualizer);
|
|
143
145
|
this.selection.onChanged.add(this.boxVisualizerManager.anchorVisualizer);
|
|
144
146
|
this.selection.onChanged.add(this.barHolder.contextMenu);
|
|
147
|
+
this.selection.onChanged.add(this.screenReaderManager);
|
|
145
148
|
if (this.barHolder.ribbon)
|
|
146
149
|
this.selection.onChanged.add(this.barHolder.ribbon);
|
|
147
150
|
this.selection.onSearchChanged.add(this.selectionFormatter);
|
|
@@ -191,6 +194,7 @@ export class RichEditCore {
|
|
|
191
194
|
this.spellChecker.dispose();
|
|
192
195
|
this.selection.dispose();
|
|
193
196
|
this.barHolder.dispose?.();
|
|
197
|
+
this.screenReaderManager.dispose?.();
|
|
194
198
|
this.modelManager = null;
|
|
195
199
|
this.commandManager = null;
|
|
196
200
|
this.shortcutManager = null;
|
|
@@ -401,6 +405,20 @@ export class RichEditCore {
|
|
|
401
405
|
this.layoutFormatterManager.forceFormatPage(this.layout.validPageCount + 1);
|
|
402
406
|
}
|
|
403
407
|
}
|
|
408
|
+
createAriaLiveElement(element) {
|
|
409
|
+
const ariaLiveElement = document.createElement("DIV");
|
|
410
|
+
ariaLiveElement.setAttribute('aria-live', 'polite');
|
|
411
|
+
ariaLiveElement.setAttribute('aria-atomic', 'true');
|
|
412
|
+
ariaLiveElement.style.position = 'absolute';
|
|
413
|
+
ariaLiveElement.style.width = '1px';
|
|
414
|
+
ariaLiveElement.style.height = '1px';
|
|
415
|
+
ariaLiveElement.style.overflow = 'hidden';
|
|
416
|
+
ariaLiveElement.style.whiteSpace = 'nowrap';
|
|
417
|
+
ariaLiveElement.style.border = '0';
|
|
418
|
+
ariaLiveElement.style.whiteSpace = 'pre-line';
|
|
419
|
+
element.appendChild(ariaLiveElement);
|
|
420
|
+
return ariaLiveElement;
|
|
421
|
+
}
|
|
404
422
|
createViewElement(id, element) {
|
|
405
423
|
const viewElement = document.createElement("DIV");
|
|
406
424
|
viewElement.id = id + "_View";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { RichEditClientCommand } from "./commands/client-command";
|
|
2
|
+
import { IRichEditControl } from "./interfaces/i-rich-edit-core";
|
|
3
|
+
import { LayoutPosition } from "./layout/layout-position";
|
|
4
|
+
import { ISelectionChangesListener } from "./selection/i-selection-changes-listener";
|
|
5
|
+
import { Selection } from "./selection/selection";
|
|
6
|
+
export declare class ScreenReaderManager implements ISelectionChangesListener {
|
|
7
|
+
private static _maxAnnouncedTextLength;
|
|
8
|
+
private static _spaceSeparatorRegex;
|
|
9
|
+
private static _specialCharSubstitutionTable;
|
|
10
|
+
private _charNames;
|
|
11
|
+
readonly _handlers: Map<RichEditClientCommand, (selection: Selection, lp: LayoutPosition) => string>;
|
|
12
|
+
private _selectionIntervals;
|
|
13
|
+
private _commandId;
|
|
14
|
+
private _control;
|
|
15
|
+
private _liveRegion;
|
|
16
|
+
private _announceTimeoutId;
|
|
17
|
+
private _pageIndex;
|
|
18
|
+
private _tableInfo;
|
|
19
|
+
private _prevNavInHyperlink;
|
|
20
|
+
constructor(control: IRichEditControl, liveRegion: HTMLDivElement);
|
|
21
|
+
private _announce;
|
|
22
|
+
private _clearAnnouncement;
|
|
23
|
+
beginExecute(commandId: RichEditClientCommand): void;
|
|
24
|
+
endExecute(): void;
|
|
25
|
+
NotifySelectionChanged(selection: Selection): void;
|
|
26
|
+
private _processTable;
|
|
27
|
+
private _processExtendSelectionCommand;
|
|
28
|
+
private _getTextByIntervals;
|
|
29
|
+
private _updateHyperlinkState;
|
|
30
|
+
private _getLinkTransitionPrefix;
|
|
31
|
+
private _getCurrentCharacterAnnouncement;
|
|
32
|
+
private _buildContextualAnnouncement;
|
|
33
|
+
private _getCurrentLineAnnouncement;
|
|
34
|
+
private _getCurrentWordAnnouncement;
|
|
35
|
+
private _getCurrentParagraphAnnouncement;
|
|
36
|
+
private _getHyperlinkField;
|
|
37
|
+
private _onHyperlinkFieldStart;
|
|
38
|
+
private _getVisibleText;
|
|
39
|
+
private static _getInlinePictureText;
|
|
40
|
+
private _getPageChangePrefix;
|
|
41
|
+
private _getCharName;
|
|
42
|
+
private _resolveListMarker;
|
|
43
|
+
private _getListItemAnnouncement;
|
|
44
|
+
dispose(): void;
|
|
45
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { FixedInterval } from "@devexpress/utils/lib/intervals/fixed";
|
|
2
|
+
import { RichEditClientCommand } from "./commands/client-command";
|
|
3
|
+
import { LayoutPositionCreator } from "./layout-engine/layout-position-creator";
|
|
4
|
+
import { DocumentLayoutDetailsLevel } from "./layout/document-layout-details-level";
|
|
5
|
+
import { LayoutWordBounds } from "./word-bounds-engine/layout-word-bounds";
|
|
6
|
+
import { IntervalUtils } from "./utils/interval-utils";
|
|
7
|
+
import { NumberingType } from "./model/numbering-lists/numbering-list";
|
|
8
|
+
import { TablePositionIndexes } from "./model/tables/main-structures/table";
|
|
9
|
+
import { InlinePictureRun } from "./model/runs/inline-picture-run";
|
|
10
|
+
import { RichUtils } from "./model/rich-utils";
|
|
11
|
+
import { formatMessage } from "devextreme/localization";
|
|
12
|
+
import { RunType } from "./model/runs/run-type";
|
|
13
|
+
import { SubDocumentPosition } from "./model/sub-document";
|
|
14
|
+
class TableInfo {
|
|
15
|
+
constructor(index, columnIndex, rowIndex) {
|
|
16
|
+
this.index = index;
|
|
17
|
+
this.columnIndex = columnIndex;
|
|
18
|
+
this.rowIndex = rowIndex;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class ScreenReaderManager {
|
|
22
|
+
static { this._maxAnnouncedTextLength = 500; }
|
|
23
|
+
static { this._spaceSeparatorRegex = /^\p{Zs}$/u; }
|
|
24
|
+
static { this._specialCharSubstitutionTable = {
|
|
25
|
+
[RichUtils.specialCharacters.SectionMark]: RichUtils.specialCharacters.PageBreak + "\u200C",
|
|
26
|
+
[RichUtils.specialCharacters.PageBreak]: RichUtils.specialCharacters.PageBreak + "\u200C",
|
|
27
|
+
}; }
|
|
28
|
+
constructor(control, liveRegion) {
|
|
29
|
+
this._charNames = Object.assign({
|
|
30
|
+
[RichUtils.specialCharacters.TabMark]: getString("TabAnnouncement"),
|
|
31
|
+
[RichUtils.specialCharacters.ParagraphMark]: getString("CarriageReturnAnnouncement"),
|
|
32
|
+
[RichUtils.specialCharacters.LineBreak]: getString("LineBreakAnnouncement"),
|
|
33
|
+
}, ScreenReaderManager._specialCharSubstitutionTable);
|
|
34
|
+
this._handlers = new Map([
|
|
35
|
+
[RichEditClientCommand.NextCharacter, (selection, lp) => this._getCurrentCharacterAnnouncement(selection, lp)],
|
|
36
|
+
[RichEditClientCommand.PreviousCharacter, (selection, lp) => this._getCurrentCharacterAnnouncement(selection, lp)],
|
|
37
|
+
[RichEditClientCommand.LineUp, (selection, lp) => this._getCurrentLineAnnouncement(selection, lp)],
|
|
38
|
+
[RichEditClientCommand.LineDown, (selection, lp) => this._getCurrentLineAnnouncement(selection, lp)],
|
|
39
|
+
[RichEditClientCommand.GoToNextWord, (selection, lp) => this._getCurrentWordAnnouncement(selection, lp)],
|
|
40
|
+
[RichEditClientCommand.GoToPrevWord, (selection, lp) => this._getCurrentWordAnnouncement(selection, lp)],
|
|
41
|
+
[RichEditClientCommand.GoToStartParagraph, (selection, lp) => this._getCurrentParagraphAnnouncement(selection, lp)],
|
|
42
|
+
[RichEditClientCommand.GoToEndParagraph, (selection, lp) => this._getCurrentParagraphAnnouncement(selection, lp)],
|
|
43
|
+
[RichEditClientCommand.NextPage, (selection, lp) => this._getCurrentLineAnnouncement(selection, lp)],
|
|
44
|
+
[RichEditClientCommand.PreviousPage, (selection, lp) => this._getCurrentLineAnnouncement(selection, lp)],
|
|
45
|
+
[RichEditClientCommand.GoToStartNextPage, (selection, lp) => this._getCurrentLineAnnouncement(selection, lp)],
|
|
46
|
+
[RichEditClientCommand.GoToStartPrevPage, (selection, lp) => this._getCurrentLineAnnouncement(selection, lp)],
|
|
47
|
+
]);
|
|
48
|
+
this._selectionIntervals = [];
|
|
49
|
+
this._commandId = null;
|
|
50
|
+
this._announceTimeoutId = null;
|
|
51
|
+
this._pageIndex = null;
|
|
52
|
+
this._tableInfo = null;
|
|
53
|
+
this._prevNavInHyperlink = false;
|
|
54
|
+
this._control = control;
|
|
55
|
+
this._liveRegion = liveRegion;
|
|
56
|
+
}
|
|
57
|
+
_announce(...announcment) {
|
|
58
|
+
this._clearAnnouncement();
|
|
59
|
+
this._liveRegion.textContent = '';
|
|
60
|
+
this._announceTimeoutId = setTimeout(() => {
|
|
61
|
+
const textContent = announcment.join(' ');
|
|
62
|
+
this._liveRegion.textContent = textContent;
|
|
63
|
+
this._announceTimeoutId = null;
|
|
64
|
+
}, 50);
|
|
65
|
+
}
|
|
66
|
+
_clearAnnouncement() {
|
|
67
|
+
if (this._announceTimeoutId !== null) {
|
|
68
|
+
clearTimeout(this._announceTimeoutId);
|
|
69
|
+
this._announceTimeoutId = null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
beginExecute(commandId) {
|
|
73
|
+
this._commandId = commandId;
|
|
74
|
+
}
|
|
75
|
+
endExecute() {
|
|
76
|
+
this._commandId = null;
|
|
77
|
+
}
|
|
78
|
+
NotifySelectionChanged(selection) {
|
|
79
|
+
const currentIntervals = !selection.isCollapsed() ? [...selection.intervals].sort((x, y) => x.start - y.start) : [];
|
|
80
|
+
if (this._commandId != null) {
|
|
81
|
+
let announcments = [];
|
|
82
|
+
const handler = this._handlers.get(this._commandId);
|
|
83
|
+
if (handler) {
|
|
84
|
+
const pos = selection.intervals[0].start;
|
|
85
|
+
const subDocument = selection.activeSubDocument;
|
|
86
|
+
const lp = LayoutPositionCreator.createLightLayoutPosition(this._control.layout, subDocument, pos, selection.pageIndex, DocumentLayoutDetailsLevel.Box, selection.endOfLine, false);
|
|
87
|
+
const pagePrefix = this._getPageChangePrefix(lp.pageIndex);
|
|
88
|
+
if (pagePrefix)
|
|
89
|
+
announcments.push(pagePrefix);
|
|
90
|
+
const tableAnnouncements = this._processTable(selection.tableInfo);
|
|
91
|
+
if (tableAnnouncements.length > 0)
|
|
92
|
+
announcments.push(...tableAnnouncements);
|
|
93
|
+
const content = handler(selection, lp);
|
|
94
|
+
this._updateHyperlinkState(lp);
|
|
95
|
+
if (content)
|
|
96
|
+
announcments.push(content);
|
|
97
|
+
}
|
|
98
|
+
if ((!handler && currentIntervals.length > 0) || this._selectionIntervals.length > 0)
|
|
99
|
+
announcments.push(...this._processExtendSelectionCommand(currentIntervals));
|
|
100
|
+
if (announcments.length > 0)
|
|
101
|
+
this._announce(...announcments);
|
|
102
|
+
}
|
|
103
|
+
this._selectionIntervals = currentIntervals;
|
|
104
|
+
}
|
|
105
|
+
_processTable(tableInfo) {
|
|
106
|
+
const result = [];
|
|
107
|
+
if (!tableInfo.table) {
|
|
108
|
+
if (this._tableInfo) {
|
|
109
|
+
result.push(getString("OutOfTableAnnouncement"));
|
|
110
|
+
this._tableInfo = null;
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
const extendedData = tableInfo.extendedData;
|
|
115
|
+
const tableCellGridInfos = tableInfo.gridInfoManager.tableCellGridInfos;
|
|
116
|
+
const cellGridInfo = tableInfo.gridInfoManager.gridInfosByTablePosition(new TablePositionIndexes(extendedData.firstRowInfo.rowIndex, extendedData.firstCellInfo.cellIndex));
|
|
117
|
+
const colIndex = cellGridInfo.getGridCellIndex();
|
|
118
|
+
const rowIndex = cellGridInfo.getStartRowIndex();
|
|
119
|
+
const currentTableInfo = new TableInfo(tableInfo.table.index, colIndex, rowIndex);
|
|
120
|
+
if (!this._tableInfo || this._tableInfo.index !== currentTableInfo.index)
|
|
121
|
+
result.push(getString("InTableAnnouncement", tableCellGridInfos.length, tableCellGridInfos[0].length));
|
|
122
|
+
if (!this._tableInfo || this._tableInfo.rowIndex !== currentTableInfo.rowIndex)
|
|
123
|
+
result.push(getString("TableRowAnnouncement", rowIndex + 1));
|
|
124
|
+
if (!this._tableInfo || this._tableInfo.columnIndex !== currentTableInfo.columnIndex)
|
|
125
|
+
result.push(getString("TableColumnAnnouncement", colIndex + 1));
|
|
126
|
+
this._tableInfo = currentTableInfo;
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
_processExtendSelectionCommand(intervals) {
|
|
130
|
+
let announcments = [];
|
|
131
|
+
if (this._commandId === RichEditClientCommand.SelectAll)
|
|
132
|
+
announcments.push(getString("SelectAllAnnouncement"), "\n");
|
|
133
|
+
const selectedIntervals = IntervalUtils.subtractIntervals(intervals, this._selectionIntervals);
|
|
134
|
+
if (selectedIntervals.length > 0)
|
|
135
|
+
announcments.push(getString("TextSelectedAnnouncement", this._getTextByIntervals(selectedIntervals)));
|
|
136
|
+
const unselectedIntervals = IntervalUtils.subtractIntervals(this._selectionIntervals, intervals);
|
|
137
|
+
if (unselectedIntervals.length > 0)
|
|
138
|
+
announcments.push(getString("TextUnselectedAnnouncement", this._getTextByIntervals(unselectedIntervals)));
|
|
139
|
+
return announcments;
|
|
140
|
+
}
|
|
141
|
+
_getTextByIntervals(intervals) {
|
|
142
|
+
const length = intervals.reduce((total, interval) => total + interval.length, 0);
|
|
143
|
+
if (length > ScreenReaderManager._maxAnnouncedTextLength)
|
|
144
|
+
return `${length} characters`;
|
|
145
|
+
const subDocument = this._control.selection.activeSubDocument;
|
|
146
|
+
return intervals.map(i => subDocument.getVisibleText(i, {
|
|
147
|
+
includeNumberedListMarks: true,
|
|
148
|
+
charMap: ScreenReaderManager._specialCharSubstitutionTable
|
|
149
|
+
})).join('');
|
|
150
|
+
}
|
|
151
|
+
_updateHyperlinkState(lp) {
|
|
152
|
+
this._prevNavInHyperlink = lp.box?.hyperlinkTip != null;
|
|
153
|
+
}
|
|
154
|
+
_getLinkTransitionPrefix(lp) {
|
|
155
|
+
const inHyperlink = lp.box?.hyperlinkTip != null;
|
|
156
|
+
const prevInHyperlink = this._prevNavInHyperlink;
|
|
157
|
+
if (inHyperlink && !prevInHyperlink)
|
|
158
|
+
return getString("LinkAnnouncement");
|
|
159
|
+
if (!inHyperlink && prevInHyperlink)
|
|
160
|
+
return getString("OutOfLinkAnnouncement");
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
_getCurrentCharacterAnnouncement(selection, lp) {
|
|
164
|
+
const pos = selection.intervals[0].start;
|
|
165
|
+
const subDocument = selection.activeSubDocument;
|
|
166
|
+
const info = subDocument.getRunAndIndexesByPosition(pos);
|
|
167
|
+
if (info.run instanceof InlinePictureRun)
|
|
168
|
+
return ScreenReaderManager._getInlinePictureText(info.run);
|
|
169
|
+
const isFieldCodeStartRun = info.run.getType() === RunType.FieldCodeStartRun;
|
|
170
|
+
const charInfo = isFieldCodeStartRun
|
|
171
|
+
? subDocument.getRunAndIndexesByPosition(lp.getLogPosition(DocumentLayoutDetailsLevel.Box))
|
|
172
|
+
: info;
|
|
173
|
+
const char = charInfo.getCurrentChar();
|
|
174
|
+
let content = this._getCharName(char);
|
|
175
|
+
return this._buildContextualAnnouncement(content, new SubDocumentPosition(subDocument, pos), lp);
|
|
176
|
+
}
|
|
177
|
+
_buildContextualAnnouncement(content, pos, lp) {
|
|
178
|
+
const paragraph = pos.subDocument.getParagraphByPosition(pos.position);
|
|
179
|
+
const linkPrefix = this._getLinkTransitionPrefix(lp);
|
|
180
|
+
if (linkPrefix)
|
|
181
|
+
content = `${linkPrefix} ${content}`;
|
|
182
|
+
const listContent = this._getListItemAnnouncement(paragraph, pos.position, lp, content);
|
|
183
|
+
return listContent ? listContent : content;
|
|
184
|
+
}
|
|
185
|
+
_getCurrentLineAnnouncement(selection, lp) {
|
|
186
|
+
const pos = selection.intervals[0].start;
|
|
187
|
+
const subDocument = selection.activeSubDocument;
|
|
188
|
+
const rowStart = lp.getLogPosition(DocumentLayoutDetailsLevel.Row);
|
|
189
|
+
const rowEnd = rowStart + lp.row.getLastBoxEndPositionInRow();
|
|
190
|
+
const intervalStart = rowStart;
|
|
191
|
+
const text = this._getVisibleText(subDocument, FixedInterval.fromPositions(intervalStart, rowEnd));
|
|
192
|
+
const listMarker = lp.row.numberingListBox
|
|
193
|
+
? this._resolveListMarker(subDocument.getParagraphByPosition(pos))
|
|
194
|
+
: null;
|
|
195
|
+
return listMarker ? `${listMarker} ${text}`.trim() : text;
|
|
196
|
+
}
|
|
197
|
+
_getCurrentWordAnnouncement(selection, lp) {
|
|
198
|
+
const pos = selection.intervals[0].start;
|
|
199
|
+
const subDocument = selection.activeSubDocument;
|
|
200
|
+
const wordEnd = LayoutWordBounds.getLayoutWordEndBound(this._control.layout, subDocument, selection, pos, false);
|
|
201
|
+
let text = subDocument.getVisibleText(FixedInterval.fromPositions(pos, wordEnd), {
|
|
202
|
+
inlinePictureAltText: ScreenReaderManager._getInlinePictureText,
|
|
203
|
+
charMap: ScreenReaderManager._specialCharSubstitutionTable
|
|
204
|
+
});
|
|
205
|
+
return this._buildContextualAnnouncement(text, new SubDocumentPosition(subDocument, pos), lp);
|
|
206
|
+
}
|
|
207
|
+
_getCurrentParagraphAnnouncement(selection, _lp) {
|
|
208
|
+
const pos = selection.intervals[0].start;
|
|
209
|
+
const subDocument = selection.activeSubDocument;
|
|
210
|
+
const paragraph = subDocument.getParagraphByPosition(pos);
|
|
211
|
+
const text = this._getVisibleText(subDocument, FixedInterval.fromPositions(paragraph.startLogPosition.value, paragraph.getEndPosition()), true);
|
|
212
|
+
return text;
|
|
213
|
+
}
|
|
214
|
+
_getHyperlinkField(fieldIndexes, _subDocument) {
|
|
215
|
+
return fieldIndexes
|
|
216
|
+
.map(index => _subDocument.fields[index])
|
|
217
|
+
.find(field => field.isHyperlinkField());
|
|
218
|
+
}
|
|
219
|
+
_onHyperlinkFieldStart(fieldIndexes, subDocument, pos) {
|
|
220
|
+
const field = this._getHyperlinkField(fieldIndexes, subDocument);
|
|
221
|
+
return field && pos === field.getResultStartPosition() ? ` ${getString("LinkAnnouncement")} ` : null;
|
|
222
|
+
}
|
|
223
|
+
_getVisibleText(subDocument, interval, includeNumberedListMarks = false) {
|
|
224
|
+
const result = subDocument.getVisibleText(interval, {
|
|
225
|
+
includeNumberedListMarks,
|
|
226
|
+
inlinePictureAltText: ScreenReaderManager._getInlinePictureText,
|
|
227
|
+
charMap: ScreenReaderManager._specialCharSubstitutionTable,
|
|
228
|
+
resolveAdditionalText: (fieldIndexes, pos) => this._onHyperlinkFieldStart(fieldIndexes, subDocument, pos)
|
|
229
|
+
});
|
|
230
|
+
return result === RichUtils.specialCharacters.ParagraphMark
|
|
231
|
+
? getString("BlankAnnouncement")
|
|
232
|
+
: result;
|
|
233
|
+
}
|
|
234
|
+
static _getInlinePictureText(run) {
|
|
235
|
+
const nonVisualProps = run.info.nonVisualDrawingProperties;
|
|
236
|
+
return getString("PictureAnnouncement", nonVisualProps.description ?? nonVisualProps.name ?? '');
|
|
237
|
+
}
|
|
238
|
+
_getPageChangePrefix(pageIndex) {
|
|
239
|
+
if (this._pageIndex === null) {
|
|
240
|
+
this._pageIndex = pageIndex;
|
|
241
|
+
return '';
|
|
242
|
+
}
|
|
243
|
+
if (pageIndex !== this._pageIndex) {
|
|
244
|
+
this._pageIndex = pageIndex;
|
|
245
|
+
return getString("PageAnnouncement", pageIndex + 1);
|
|
246
|
+
}
|
|
247
|
+
return '';
|
|
248
|
+
}
|
|
249
|
+
_getCharName(char) {
|
|
250
|
+
if (char in this._charNames)
|
|
251
|
+
return this._charNames[char];
|
|
252
|
+
return ScreenReaderManager._spaceSeparatorRegex.test(char) ? getString("SpaceAnnouncement") : char;
|
|
253
|
+
}
|
|
254
|
+
_resolveListMarker(paragraph) {
|
|
255
|
+
const levelIndex = paragraph.getListLevelIndex();
|
|
256
|
+
const levelType = paragraph.getNumberingList().getLevelType(levelIndex);
|
|
257
|
+
if (levelType === NumberingType.Bullet)
|
|
258
|
+
return getString("BulletAnnouncement");
|
|
259
|
+
return paragraph.getNumberingListText();
|
|
260
|
+
}
|
|
261
|
+
_getListItemAnnouncement(paragraph, pos, lp, content) {
|
|
262
|
+
if (!paragraph.isInList() || pos !== paragraph.startLogPosition.value || !lp?.row?.numberingListBox)
|
|
263
|
+
return null;
|
|
264
|
+
const level = paragraph.getListLevelIndex() + 1;
|
|
265
|
+
const listMarker = this._resolveListMarker(paragraph);
|
|
266
|
+
return `${getString("ListItemAnnouncement", level, listMarker)} ${content}`;
|
|
267
|
+
}
|
|
268
|
+
dispose() {
|
|
269
|
+
this._clearAnnouncement();
|
|
270
|
+
this._liveRegion.remove();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const stringCache = {};
|
|
274
|
+
function createFormatter(template) {
|
|
275
|
+
const tokens = [];
|
|
276
|
+
let lastIndex = 0;
|
|
277
|
+
template.replace(/\{(\d+)\}/g, (match, p1, offset) => {
|
|
278
|
+
if (lastIndex < offset)
|
|
279
|
+
tokens.push(template.slice(lastIndex, offset));
|
|
280
|
+
tokens.push(Number(p1));
|
|
281
|
+
lastIndex = offset + match.length;
|
|
282
|
+
return match;
|
|
283
|
+
});
|
|
284
|
+
if (lastIndex < template.length)
|
|
285
|
+
tokens.push(template.slice(lastIndex));
|
|
286
|
+
return (args) => {
|
|
287
|
+
let result = '';
|
|
288
|
+
for (const token of tokens) {
|
|
289
|
+
if (typeof token === 'string')
|
|
290
|
+
result += token;
|
|
291
|
+
else {
|
|
292
|
+
const value = args[token];
|
|
293
|
+
result += value ?? '';
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return result;
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function getString(key, ...args) {
|
|
300
|
+
let entry = stringCache[key];
|
|
301
|
+
if (!entry) {
|
|
302
|
+
const str = formatMessage(`AspNetCoreRichEditStringId.${key}`);
|
|
303
|
+
entry = args.length > 0 ? createFormatter(str) : str;
|
|
304
|
+
stringCache[key] = entry;
|
|
305
|
+
}
|
|
306
|
+
if (typeof entry === "function")
|
|
307
|
+
return entry(args);
|
|
308
|
+
return entry;
|
|
309
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { FixedInterval } from '@devexpress/utils/lib/intervals/fixed';
|
|
2
|
+
export class IntervalUtils {
|
|
3
|
+
static subtractIntervals(a, b) {
|
|
4
|
+
const result = [];
|
|
5
|
+
let bIndex = 0;
|
|
6
|
+
for (const interval of a) {
|
|
7
|
+
let currentFrom = interval.start;
|
|
8
|
+
const currentTo = interval.end;
|
|
9
|
+
for (; bIndex < b.length && b[bIndex].start < currentTo; bIndex++) {
|
|
10
|
+
if (b[bIndex].end <= currentFrom)
|
|
11
|
+
continue;
|
|
12
|
+
if (b[bIndex].start > currentFrom)
|
|
13
|
+
result.push(FixedInterval.fromPositions(currentFrom, Math.min(b[bIndex].start, currentTo)));
|
|
14
|
+
currentFrom = Math.max(currentFrom, b[bIndex].end);
|
|
15
|
+
if (currentFrom >= currentTo) {
|
|
16
|
+
bIndex++;
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (currentFrom < currentTo)
|
|
21
|
+
result.push(FixedInterval.fromPositions(currentFrom, currentTo));
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devexpress-richedit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "26.1.2-beta",
|
|
4
4
|
"homepage": "https://www.devexpress.com/",
|
|
5
5
|
"bugs": "https://www.devexpress.com/support/",
|
|
6
6
|
"author": "Developer Express Inc.",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"build-nspell": "webpack --mode production --config=bin/nspell.webpack.config.js"
|
|
12
12
|
},
|
|
13
13
|
"peerDependencies": {
|
|
14
|
-
"devextreme": "
|
|
15
|
-
"devextreme-dist": "
|
|
14
|
+
"devextreme": "26.1.2-beta",
|
|
15
|
+
"devextreme-dist": "26.1.2-beta"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"dictionary-en": "3.2.0",
|