devexpress-richedit 25.2.8 → 26.1.3
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 +26 -0
- package/dist/dx.richedit.d.ts +1 -1
- package/dist/dx.richedit.js +6559 -5654
- package/dist/dx.richedit.min.js +3 -3
- package/dist/pdfkit.js +21908 -22350
- package/dist/pdfkit.min.js +1 -1
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/lib/client/client-rich-edit.d.ts +0 -1
- package/lib/client/client-rich-edit.js +4 -27
- package/lib/client/default-localization.js +19 -0
- 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/common/canvas/canvas-manager.d.ts +1 -1
- package/lib/common/canvas/canvas-manager.js +7 -3
- package/lib/common/canvas/double-tap-detector.d.ts +10 -0
- package/lib/common/canvas/double-tap-detector.js +28 -0
- package/lib/common/canvas/renderer-class-names.d.ts +2 -0
- package/lib/common/canvas/renderer-class-names.js +2 -0
- package/lib/common/canvas/renderes/common/document-renderer.d.ts +20 -7
- package/lib/common/canvas/renderes/common/document-renderer.js +236 -47
- package/lib/common/canvas/renderes/common/simple-view-renderer.d.ts +3 -1
- package/lib/common/canvas/renderes/common/simple-view-renderer.js +35 -2
- package/lib/common/canvas/renderes/view-manager.d.ts +3 -0
- package/lib/common/canvas/renderes/view-manager.js +34 -7
- package/lib/common/commands/command-manager.d.ts +2 -0
- package/lib/common/commands/command-manager.js +6 -0
- package/lib/common/commands/dialogs/dialog-custom-numbering-list-command.js +1 -1
- 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 +6 -12
- 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/main-structures/layout-boxes/layout-box.js +1 -1
- package/lib/common/layout/main-structures/layout-column.d.ts +1 -0
- package/lib/common/layout/main-structures/layout-column.js +3 -0
- package/lib/common/layout/selection/layout-selection-items.d.ts +3 -0
- package/lib/common/layout/selection/layout-selection-items.js +5 -0
- package/lib/common/layout-engine/selection/selection-formatter.js +7 -3
- package/lib/common/layout-formatter/box/generator/box-infos-generator.js +8 -4
- package/lib/common/model/sub-document.d.ts +18 -0
- package/lib/common/model/sub-document.js +71 -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 +48 -0
- package/lib/common/screen-reader-manager.js +326 -0
- package/lib/common/string-resources.d.ts +23 -0
- package/lib/common/string-resources.js +43 -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
|
@@ -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), this.stringResources);
|
|
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,48 @@
|
|
|
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
|
+
import { StringResources } from "./string-resources";
|
|
7
|
+
export declare class ScreenReaderManager implements ISelectionChangesListener {
|
|
8
|
+
private static _maxAnnouncedTextLength;
|
|
9
|
+
private static _spaceSeparatorRegex;
|
|
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 _announcements;
|
|
17
|
+
private _announceTimeoutId;
|
|
18
|
+
private _pageIndex;
|
|
19
|
+
private _tableInfo;
|
|
20
|
+
private _prevNavInHyperlink;
|
|
21
|
+
constructor(control: IRichEditControl, liveRegion: HTMLDivElement, resources: StringResources);
|
|
22
|
+
private _announce;
|
|
23
|
+
private _clearAnnouncement;
|
|
24
|
+
beginExecute(commandId: RichEditClientCommand): void;
|
|
25
|
+
endExecute(): void;
|
|
26
|
+
NotifySelectionChanged(selection: Selection): void;
|
|
27
|
+
private _processTable;
|
|
28
|
+
private _processExtendSelectionCommand;
|
|
29
|
+
private _getTextByIntervals;
|
|
30
|
+
private _updateHyperlinkState;
|
|
31
|
+
private _getLinkTransitionPrefix;
|
|
32
|
+
private _getCurrentCharacterAnnouncement;
|
|
33
|
+
private _buildContextualAnnouncement;
|
|
34
|
+
private _getCurrentLineAnnouncement;
|
|
35
|
+
private _getCurrentWordAnnouncement;
|
|
36
|
+
private _getCurrentParagraphAnnouncement;
|
|
37
|
+
private _getHyperlinkField;
|
|
38
|
+
private _onHyperlinkFieldStart;
|
|
39
|
+
private _getVisibleText;
|
|
40
|
+
private _tryResolveDocumentElements;
|
|
41
|
+
private _tryResolveSpecialCharacters;
|
|
42
|
+
private _getInlinePictureText;
|
|
43
|
+
private _getPageChangePrefix;
|
|
44
|
+
private _getCharName;
|
|
45
|
+
private _resolveListMarker;
|
|
46
|
+
private _getListItemAnnouncement;
|
|
47
|
+
dispose(): void;
|
|
48
|
+
}
|
|
@@ -0,0 +1,326 @@
|
|
|
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 { RunType } from "./model/runs/run-type";
|
|
12
|
+
import { SubDocumentPosition, TransformAction } from "./model/sub-document";
|
|
13
|
+
class TableInfo {
|
|
14
|
+
constructor(index, columnIndex, rowIndex) {
|
|
15
|
+
this.index = index;
|
|
16
|
+
this.columnIndex = columnIndex;
|
|
17
|
+
this.rowIndex = rowIndex;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class ScreenReaderManager {
|
|
21
|
+
static { this._maxAnnouncedTextLength = 500; }
|
|
22
|
+
static { this._spaceSeparatorRegex = /^\p{Zs}$/u; }
|
|
23
|
+
constructor(control, liveRegion, resources) {
|
|
24
|
+
this._handlers = new Map([
|
|
25
|
+
[RichEditClientCommand.NextCharacter, (selection, lp) => this._getCurrentCharacterAnnouncement(selection, lp)],
|
|
26
|
+
[RichEditClientCommand.PreviousCharacter, (selection, lp) => this._getCurrentCharacterAnnouncement(selection, lp)],
|
|
27
|
+
[RichEditClientCommand.LineUp, (selection, lp) => this._getCurrentLineAnnouncement(selection, lp)],
|
|
28
|
+
[RichEditClientCommand.LineDown, (selection, lp) => this._getCurrentLineAnnouncement(selection, lp)],
|
|
29
|
+
[RichEditClientCommand.GoToNextWord, (selection, lp) => this._getCurrentWordAnnouncement(selection, lp)],
|
|
30
|
+
[RichEditClientCommand.GoToPrevWord, (selection, lp) => this._getCurrentWordAnnouncement(selection, lp)],
|
|
31
|
+
[RichEditClientCommand.GoToStartParagraph, (selection, lp) => this._getCurrentParagraphAnnouncement(selection, lp)],
|
|
32
|
+
[RichEditClientCommand.GoToEndParagraph, (selection, lp) => this._getCurrentParagraphAnnouncement(selection, lp)],
|
|
33
|
+
[RichEditClientCommand.NextPage, (selection, lp) => this._getCurrentLineAnnouncement(selection, lp)],
|
|
34
|
+
[RichEditClientCommand.PreviousPage, (selection, lp) => this._getCurrentLineAnnouncement(selection, lp)],
|
|
35
|
+
[RichEditClientCommand.GoToStartNextPage, (selection, lp) => this._getCurrentLineAnnouncement(selection, lp)],
|
|
36
|
+
[RichEditClientCommand.GoToStartPrevPage, (selection, lp) => this._getCurrentLineAnnouncement(selection, lp)],
|
|
37
|
+
]);
|
|
38
|
+
this._selectionIntervals = [];
|
|
39
|
+
this._commandId = null;
|
|
40
|
+
this._announceTimeoutId = null;
|
|
41
|
+
this._pageIndex = null;
|
|
42
|
+
this._tableInfo = null;
|
|
43
|
+
this._prevNavInHyperlink = false;
|
|
44
|
+
this._control = control;
|
|
45
|
+
this._liveRegion = liveRegion;
|
|
46
|
+
this._announcements = resources.announcements;
|
|
47
|
+
this._charNames = {
|
|
48
|
+
[RichUtils.specialCharacters.TabMark]: this._announcements.tab,
|
|
49
|
+
[RichUtils.specialCharacters.ParagraphMark]: this._announcements.carriageReturn,
|
|
50
|
+
[RichUtils.specialCharacters.LineBreak]: this._announcements.lineBreak,
|
|
51
|
+
[RichUtils.specialCharacters.PageBreak]: this._announcements.pageBreak,
|
|
52
|
+
[RichUtils.specialCharacters.SectionMark]: this._announcements.pageBreak,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
_announce(...announcment) {
|
|
56
|
+
this._clearAnnouncement();
|
|
57
|
+
this._liveRegion.textContent = '';
|
|
58
|
+
this._announceTimeoutId = setTimeout(() => {
|
|
59
|
+
const textContent = announcment.join(' ');
|
|
60
|
+
this._liveRegion.textContent = textContent;
|
|
61
|
+
this._announceTimeoutId = null;
|
|
62
|
+
}, 50);
|
|
63
|
+
}
|
|
64
|
+
_clearAnnouncement() {
|
|
65
|
+
if (this._announceTimeoutId !== null) {
|
|
66
|
+
clearTimeout(this._announceTimeoutId);
|
|
67
|
+
this._announceTimeoutId = null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
beginExecute(commandId) {
|
|
71
|
+
this._commandId = commandId;
|
|
72
|
+
}
|
|
73
|
+
endExecute() {
|
|
74
|
+
this._commandId = null;
|
|
75
|
+
}
|
|
76
|
+
NotifySelectionChanged(selection) {
|
|
77
|
+
const currentIntervals = !selection.isCollapsed() ? [...selection.intervals].sort((x, y) => x.start - y.start) : [];
|
|
78
|
+
if (this._commandId != null) {
|
|
79
|
+
let announcments = [];
|
|
80
|
+
const handler = this._handlers.get(this._commandId);
|
|
81
|
+
if (handler) {
|
|
82
|
+
const pos = selection.intervals[0].start;
|
|
83
|
+
const subDocument = selection.activeSubDocument;
|
|
84
|
+
const lp = LayoutPositionCreator.createLightLayoutPosition(this._control.layout, subDocument, pos, selection.pageIndex, DocumentLayoutDetailsLevel.Box, selection.endOfLine, false);
|
|
85
|
+
const pagePrefix = this._getPageChangePrefix(lp.pageIndex);
|
|
86
|
+
if (pagePrefix)
|
|
87
|
+
announcments.push(pagePrefix);
|
|
88
|
+
const tableAnnouncements = this._processTable(selection.tableInfo);
|
|
89
|
+
if (tableAnnouncements.length > 0)
|
|
90
|
+
announcments.push(...tableAnnouncements);
|
|
91
|
+
const content = handler(selection, lp);
|
|
92
|
+
this._updateHyperlinkState(lp);
|
|
93
|
+
if (content)
|
|
94
|
+
announcments.push(content);
|
|
95
|
+
}
|
|
96
|
+
if ((!handler && currentIntervals.length > 0) || this._selectionIntervals.length > 0)
|
|
97
|
+
announcments.push(...this._processExtendSelectionCommand(currentIntervals));
|
|
98
|
+
if (announcments.length > 0)
|
|
99
|
+
this._announce(...announcments);
|
|
100
|
+
}
|
|
101
|
+
this._selectionIntervals = currentIntervals;
|
|
102
|
+
}
|
|
103
|
+
_processTable(tableInfo) {
|
|
104
|
+
const result = [];
|
|
105
|
+
if (!tableInfo.table) {
|
|
106
|
+
if (this._tableInfo) {
|
|
107
|
+
result.push(this._announcements.outOfTable);
|
|
108
|
+
this._tableInfo = null;
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
const extendedData = tableInfo.extendedData;
|
|
113
|
+
const tableCellGridInfos = tableInfo.gridInfoManager.tableCellGridInfos;
|
|
114
|
+
const cellGridInfo = tableInfo.gridInfoManager.gridInfosByTablePosition(new TablePositionIndexes(extendedData.firstRowInfo.rowIndex, extendedData.firstCellInfo.cellIndex));
|
|
115
|
+
const colIndex = cellGridInfo.getGridCellIndex();
|
|
116
|
+
const rowIndex = cellGridInfo.getStartRowIndex();
|
|
117
|
+
const currentTableInfo = new TableInfo(tableInfo.table.index, colIndex, rowIndex);
|
|
118
|
+
if (!this._tableInfo || this._tableInfo.index !== currentTableInfo.index) {
|
|
119
|
+
if (this._tableInfo) {
|
|
120
|
+
result.push(this._announcements.outOfTable);
|
|
121
|
+
this._tableInfo = null;
|
|
122
|
+
}
|
|
123
|
+
result.push(formatString(this._announcements.inTable, tableCellGridInfos.length, tableCellGridInfos[0].length));
|
|
124
|
+
}
|
|
125
|
+
if (!this._tableInfo || this._tableInfo.rowIndex !== currentTableInfo.rowIndex)
|
|
126
|
+
result.push(formatString(this._announcements.tableRow, rowIndex + 1));
|
|
127
|
+
if (!this._tableInfo || this._tableInfo.columnIndex !== currentTableInfo.columnIndex)
|
|
128
|
+
result.push(formatString(this._announcements.tableColumn, colIndex + 1));
|
|
129
|
+
this._tableInfo = currentTableInfo;
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
_processExtendSelectionCommand(intervals) {
|
|
133
|
+
let announcments = [];
|
|
134
|
+
if (this._commandId === RichEditClientCommand.SelectAll)
|
|
135
|
+
announcments.push(this._announcements.selectAll, "\n");
|
|
136
|
+
const subDocument = this._control.selection.activeSubDocument;
|
|
137
|
+
const selectedIntervals = IntervalUtils.subtractIntervals(intervals, this._selectionIntervals);
|
|
138
|
+
if (selectedIntervals.length > 0)
|
|
139
|
+
announcments.push(formatString(this._announcements.textSelected, this._getTextByIntervals(subDocument, selectedIntervals)));
|
|
140
|
+
const unselectedIntervals = IntervalUtils.subtractIntervals(this._selectionIntervals, intervals);
|
|
141
|
+
if (unselectedIntervals.length > 0)
|
|
142
|
+
announcments.push(formatString(this._announcements.textUnselected, this._getTextByIntervals(subDocument, unselectedIntervals)));
|
|
143
|
+
return announcments;
|
|
144
|
+
}
|
|
145
|
+
_getTextByIntervals(subDocument, intervals) {
|
|
146
|
+
const length = intervals.reduce((total, interval) => total + interval.length, 0);
|
|
147
|
+
if (length > ScreenReaderManager._maxAnnouncedTextLength)
|
|
148
|
+
return `${length} characters`;
|
|
149
|
+
return intervals.map(i => subDocument.getVisibleText(i, {
|
|
150
|
+
includeNumberedListMarks: true,
|
|
151
|
+
transform: this._tryResolveDocumentElements.bind(this)
|
|
152
|
+
})).join('');
|
|
153
|
+
}
|
|
154
|
+
_updateHyperlinkState(lp) {
|
|
155
|
+
this._prevNavInHyperlink = lp.box?.hyperlinkTip != null;
|
|
156
|
+
}
|
|
157
|
+
_getLinkTransitionPrefix(lp) {
|
|
158
|
+
const inHyperlink = lp.box?.hyperlinkTip != null;
|
|
159
|
+
const prevInHyperlink = this._prevNavInHyperlink;
|
|
160
|
+
if (inHyperlink && !prevInHyperlink)
|
|
161
|
+
return this._announcements.link;
|
|
162
|
+
if (!inHyperlink && prevInHyperlink)
|
|
163
|
+
return this._announcements.outOfLink;
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
_getCurrentCharacterAnnouncement(selection, lp) {
|
|
167
|
+
const pos = selection.intervals[0].start;
|
|
168
|
+
const subDocument = selection.activeSubDocument;
|
|
169
|
+
const info = subDocument.getRunAndIndexesByPosition(pos);
|
|
170
|
+
if (info.run instanceof InlinePictureRun)
|
|
171
|
+
return this._getInlinePictureText(info.run);
|
|
172
|
+
const isFieldCodeStartRun = info.run.getType() === RunType.FieldCodeStartRun;
|
|
173
|
+
const charInfo = isFieldCodeStartRun
|
|
174
|
+
? subDocument.getRunAndIndexesByPosition(lp.getLogPosition(DocumentLayoutDetailsLevel.Box))
|
|
175
|
+
: info;
|
|
176
|
+
const char = charInfo.getCurrentChar();
|
|
177
|
+
let content = this._getCharName(char);
|
|
178
|
+
return this._buildContextualAnnouncement(content, new SubDocumentPosition(subDocument, pos), lp);
|
|
179
|
+
}
|
|
180
|
+
_buildContextualAnnouncement(content, pos, lp) {
|
|
181
|
+
const paragraph = pos.subDocument.getParagraphByPosition(pos.position);
|
|
182
|
+
const linkPrefix = this._getLinkTransitionPrefix(lp);
|
|
183
|
+
if (linkPrefix)
|
|
184
|
+
content = `${linkPrefix} ${content}`;
|
|
185
|
+
const listContent = this._getListItemAnnouncement(paragraph, pos.position, lp, content);
|
|
186
|
+
return listContent ? listContent : content;
|
|
187
|
+
}
|
|
188
|
+
_getCurrentLineAnnouncement(selection, lp) {
|
|
189
|
+
const pos = selection.intervals[0].start;
|
|
190
|
+
const subDocument = selection.activeSubDocument;
|
|
191
|
+
const rowStart = lp.getLogPosition(DocumentLayoutDetailsLevel.Row);
|
|
192
|
+
const rowEnd = rowStart + lp.row.getLastBoxEndPositionInRow();
|
|
193
|
+
const intervalStart = rowStart;
|
|
194
|
+
const text = this._getVisibleText(subDocument, FixedInterval.fromPositions(intervalStart, rowEnd));
|
|
195
|
+
const listMarker = lp.row.numberingListBox
|
|
196
|
+
? this._resolveListMarker(subDocument.getParagraphByPosition(pos))
|
|
197
|
+
: null;
|
|
198
|
+
return listMarker ? `${listMarker} ${text}`.trim() : text;
|
|
199
|
+
}
|
|
200
|
+
_getCurrentWordAnnouncement(selection, lp) {
|
|
201
|
+
const pos = selection.intervals[0].start;
|
|
202
|
+
const subDocument = selection.activeSubDocument;
|
|
203
|
+
const wordEnd = LayoutWordBounds.getLayoutWordEndBound(this._control.layout, subDocument, selection, pos, false);
|
|
204
|
+
let text = subDocument.getVisibleText(FixedInterval.fromPositions(pos, wordEnd), {
|
|
205
|
+
transform: this._tryResolveDocumentElements.bind(this)
|
|
206
|
+
});
|
|
207
|
+
return this._buildContextualAnnouncement(text, new SubDocumentPosition(subDocument, pos), lp);
|
|
208
|
+
}
|
|
209
|
+
_getCurrentParagraphAnnouncement(selection, _lp) {
|
|
210
|
+
const pos = selection.intervals[0].start;
|
|
211
|
+
const subDocument = selection.activeSubDocument;
|
|
212
|
+
const paragraph = subDocument.getParagraphByPosition(pos);
|
|
213
|
+
const text = this._getVisibleText(subDocument, FixedInterval.fromPositions(paragraph.startLogPosition.value, paragraph.getEndPosition()), true);
|
|
214
|
+
return text;
|
|
215
|
+
}
|
|
216
|
+
_getHyperlinkField(fieldIndexes, _subDocument) {
|
|
217
|
+
return fieldIndexes
|
|
218
|
+
.map(index => _subDocument.fields[index])
|
|
219
|
+
.find(field => field.isHyperlinkField());
|
|
220
|
+
}
|
|
221
|
+
_onHyperlinkFieldStart(fieldIndexes, subDocument, pos) {
|
|
222
|
+
const field = this._getHyperlinkField(fieldIndexes, subDocument);
|
|
223
|
+
return field && pos === field.getResultStartPosition() ? ` ${this._announcements.link} ` : null;
|
|
224
|
+
}
|
|
225
|
+
_getVisibleText(subDocument, interval, includeNumberedListMarks = false) {
|
|
226
|
+
const result = subDocument.getVisibleText(interval, {
|
|
227
|
+
includeNumberedListMarks,
|
|
228
|
+
transform: (info, fieldIndexes) => {
|
|
229
|
+
const prefix = this._onHyperlinkFieldStart(fieldIndexes, subDocument, info.getAbsolutePosition());
|
|
230
|
+
const text = this._tryResolveDocumentElements(info);
|
|
231
|
+
if (prefix == null && text == null)
|
|
232
|
+
return null;
|
|
233
|
+
return {
|
|
234
|
+
text: (prefix ?? '') + (text ?? ''),
|
|
235
|
+
action: text != null ? TransformAction.Replace : TransformAction.InsertBefore
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
return result === RichUtils.specialCharacters.ParagraphMark
|
|
240
|
+
? this._announcements.blank
|
|
241
|
+
: result;
|
|
242
|
+
}
|
|
243
|
+
_tryResolveDocumentElements(info) {
|
|
244
|
+
return info.run.getType() === RunType.InlinePictureRun
|
|
245
|
+
? this._getInlinePictureText(info.run)
|
|
246
|
+
: this._tryResolveSpecialCharacters(info);
|
|
247
|
+
}
|
|
248
|
+
_tryResolveSpecialCharacters(info) {
|
|
249
|
+
const char = info.getCurrentChar();
|
|
250
|
+
if (char === RichUtils.specialCharacters.PageBreak || char === RichUtils.specialCharacters.SectionMark)
|
|
251
|
+
return ` ${this._announcements.pageBreak} `;
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
_getInlinePictureText(run) {
|
|
255
|
+
const nonVisualProps = run.info.nonVisualDrawingProperties;
|
|
256
|
+
return formatString(this._announcements.picture, nonVisualProps.description ?? nonVisualProps.name ?? '');
|
|
257
|
+
}
|
|
258
|
+
_getPageChangePrefix(pageIndex) {
|
|
259
|
+
if (this._pageIndex === null) {
|
|
260
|
+
this._pageIndex = pageIndex;
|
|
261
|
+
return '';
|
|
262
|
+
}
|
|
263
|
+
if (pageIndex !== this._pageIndex) {
|
|
264
|
+
this._pageIndex = pageIndex;
|
|
265
|
+
return formatString(this._announcements.page, pageIndex + 1);
|
|
266
|
+
}
|
|
267
|
+
return '';
|
|
268
|
+
}
|
|
269
|
+
_getCharName(char) {
|
|
270
|
+
if (char in this._charNames)
|
|
271
|
+
return this._charNames[char];
|
|
272
|
+
return ScreenReaderManager._spaceSeparatorRegex.test(char) ? this._announcements.space : char;
|
|
273
|
+
}
|
|
274
|
+
_resolveListMarker(paragraph) {
|
|
275
|
+
const levelIndex = paragraph.getListLevelIndex();
|
|
276
|
+
const levelType = paragraph.getNumberingList().getLevelType(levelIndex);
|
|
277
|
+
if (levelType === NumberingType.Bullet)
|
|
278
|
+
return this._announcements.bullet;
|
|
279
|
+
return paragraph.getNumberingListText();
|
|
280
|
+
}
|
|
281
|
+
_getListItemAnnouncement(paragraph, pos, lp, content) {
|
|
282
|
+
if (!paragraph.isInList() || pos !== paragraph.startLogPosition.value || !lp?.row?.numberingListBox)
|
|
283
|
+
return null;
|
|
284
|
+
const level = paragraph.getListLevelIndex() + 1;
|
|
285
|
+
const listMarker = this._resolveListMarker(paragraph);
|
|
286
|
+
return `${formatString(this._announcements.listItem, level, listMarker)} ${content}`;
|
|
287
|
+
}
|
|
288
|
+
dispose() {
|
|
289
|
+
this._clearAnnouncement();
|
|
290
|
+
this._liveRegion.remove();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const stringFormatterCache = {};
|
|
294
|
+
function createFormatter(template) {
|
|
295
|
+
const tokens = [];
|
|
296
|
+
let lastIndex = 0;
|
|
297
|
+
template.replace(/\{(\d+)\}/g, (match, p1, offset) => {
|
|
298
|
+
if (lastIndex < offset)
|
|
299
|
+
tokens.push(template.slice(lastIndex, offset));
|
|
300
|
+
tokens.push(Number(p1));
|
|
301
|
+
lastIndex = offset + match.length;
|
|
302
|
+
return match;
|
|
303
|
+
});
|
|
304
|
+
if (lastIndex < template.length)
|
|
305
|
+
tokens.push(template.slice(lastIndex));
|
|
306
|
+
return (args) => {
|
|
307
|
+
let result = '';
|
|
308
|
+
for (const token of tokens) {
|
|
309
|
+
if (typeof token === 'string')
|
|
310
|
+
result += token;
|
|
311
|
+
else {
|
|
312
|
+
const value = args[token];
|
|
313
|
+
result += value ?? '';
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return result;
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function formatString(template, ...args) {
|
|
320
|
+
let formatter = stringFormatterCache[template];
|
|
321
|
+
if (!formatter) {
|
|
322
|
+
formatter = createFormatter(template);
|
|
323
|
+
stringFormatterCache[template] = formatter;
|
|
324
|
+
}
|
|
325
|
+
return formatter(args);
|
|
326
|
+
}
|
|
@@ -3,6 +3,7 @@ export declare class StringResources {
|
|
|
3
3
|
seqCaptionPrefixes: SeqCaptionPrefixes;
|
|
4
4
|
commonLabels: CommonLabels;
|
|
5
5
|
quickSearchPanel: QuickSearchPanelStringResources;
|
|
6
|
+
announcements: IAnnouncementStringResources;
|
|
6
7
|
constructor();
|
|
7
8
|
}
|
|
8
9
|
export declare class HeaderFooterStringResources {
|
|
@@ -31,3 +32,25 @@ export declare class QuickSearchPanelStringResources {
|
|
|
31
32
|
noMatches: string;
|
|
32
33
|
of: string;
|
|
33
34
|
}
|
|
35
|
+
export interface IAnnouncementStringResources {
|
|
36
|
+
inTable?: string;
|
|
37
|
+
outOfTable?: string;
|
|
38
|
+
tableRow?: string;
|
|
39
|
+
tableColumn?: string;
|
|
40
|
+
selectAll?: string;
|
|
41
|
+
textSelected?: string;
|
|
42
|
+
textUnselected?: string;
|
|
43
|
+
tab?: string;
|
|
44
|
+
carriageReturn?: string;
|
|
45
|
+
lineBreak?: string;
|
|
46
|
+
picture?: string;
|
|
47
|
+
page?: string;
|
|
48
|
+
space?: string;
|
|
49
|
+
bullet?: string;
|
|
50
|
+
listItem?: string;
|
|
51
|
+
link?: string;
|
|
52
|
+
outOfLink?: string;
|
|
53
|
+
blank?: string;
|
|
54
|
+
pageBreak?: string;
|
|
55
|
+
}
|
|
56
|
+
export declare function getStringResources(): StringResources;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { formatMessage } from "devextreme/localization";
|
|
1
2
|
export class StringResources {
|
|
2
3
|
constructor() {
|
|
3
4
|
this.headerFooter = new HeaderFooterStringResources();
|
|
4
5
|
this.seqCaptionPrefixes = new SeqCaptionPrefixes();
|
|
5
6
|
this.commonLabels = new CommonLabels();
|
|
6
7
|
this.quickSearchPanel = new QuickSearchPanelStringResources();
|
|
8
|
+
this.announcements = {};
|
|
7
9
|
}
|
|
8
10
|
}
|
|
9
11
|
export class HeaderFooterStringResources {
|
|
@@ -14,3 +16,44 @@ export class CommonLabels {
|
|
|
14
16
|
}
|
|
15
17
|
export class QuickSearchPanelStringResources {
|
|
16
18
|
}
|
|
19
|
+
export function getStringResources() {
|
|
20
|
+
const sr = new StringResources();
|
|
21
|
+
sr.headerFooter.evenPageFooter = formatMessage('XtraRichEditStringId.Caption_EvenPageFooter');
|
|
22
|
+
sr.headerFooter.evenPageHeader = formatMessage('XtraRichEditStringId.Caption_EvenPageHeader');
|
|
23
|
+
sr.headerFooter.firstPageFooter = formatMessage('XtraRichEditStringId.Caption_FirstPageFooter');
|
|
24
|
+
sr.headerFooter.firstPageHeader = formatMessage('XtraRichEditStringId.Caption_FirstPageHeader');
|
|
25
|
+
sr.headerFooter.footer = formatMessage('XtraRichEditStringId.Caption_PageFooter');
|
|
26
|
+
sr.headerFooter.header = formatMessage('XtraRichEditStringId.Caption_PageHeader');
|
|
27
|
+
sr.headerFooter.oddPageFooter = formatMessage('XtraRichEditStringId.Caption_OddPageFooter');
|
|
28
|
+
sr.headerFooter.oddPageHeader = formatMessage('XtraRichEditStringId.Caption_OddPageHeader');
|
|
29
|
+
sr.headerFooter.sameAsPrevious = formatMessage('XtraRichEditStringId.Caption_SameAsPrevious');
|
|
30
|
+
sr.seqCaptionPrefixes.figurePrefix = formatMessage('XtraRichEditStringId.Caption_CaptionPrefixFigure');
|
|
31
|
+
sr.seqCaptionPrefixes.tablePrefix = formatMessage('XtraRichEditStringId.Caption_CaptionPrefixTable');
|
|
32
|
+
sr.seqCaptionPrefixes.equationPrefix = formatMessage('XtraRichEditStringId.Caption_CaptionPrefixEquation');
|
|
33
|
+
sr.commonLabels.noTocEntriesFound = formatMessage('XtraRichEditStringId.Msg_NoTocEntriesFound');
|
|
34
|
+
sr.commonLabels.clickToFollowHyperlink = formatMessage('XtraRichEditStringId.Msg_ClickToFollowHyperlink');
|
|
35
|
+
sr.commonLabels.currentDocumentHyperlinkTooltip = formatMessage('XtraRichEditStringId.Caption_CurrentDocumentHyperlinkTooltip');
|
|
36
|
+
sr.quickSearchPanel.of = formatMessage('ASPxRichEditStringId.FindReplace_Of');
|
|
37
|
+
sr.quickSearchPanel.items = formatMessage('ASPxRichEditStringId.FindReplace_Items');
|
|
38
|
+
sr.quickSearchPanel.noMatches = formatMessage('ASPxRichEditStringId.FindReplace_NoMatches');
|
|
39
|
+
sr.announcements.inTable = formatMessage('AspNetCoreRichEditStringId.InTableAnnouncement');
|
|
40
|
+
sr.announcements.outOfTable = formatMessage('AspNetCoreRichEditStringId.OutOfTableAnnouncement');
|
|
41
|
+
sr.announcements.tableRow = formatMessage('AspNetCoreRichEditStringId.TableRowAnnouncement');
|
|
42
|
+
sr.announcements.tableColumn = formatMessage('AspNetCoreRichEditStringId.TableColumnAnnouncement');
|
|
43
|
+
sr.announcements.selectAll = formatMessage('AspNetCoreRichEditStringId.SelectAllAnnouncement');
|
|
44
|
+
sr.announcements.textSelected = formatMessage('AspNetCoreRichEditStringId.TextSelectedAnnouncement');
|
|
45
|
+
sr.announcements.textUnselected = formatMessage('AspNetCoreRichEditStringId.TextUnselectedAnnouncement');
|
|
46
|
+
sr.announcements.picture = formatMessage('AspNetCoreRichEditStringId.PictureAnnouncement');
|
|
47
|
+
sr.announcements.page = formatMessage('AspNetCoreRichEditStringId.PageAnnouncement');
|
|
48
|
+
sr.announcements.space = formatMessage('AspNetCoreRichEditStringId.SpaceAnnouncement');
|
|
49
|
+
sr.announcements.bullet = formatMessage('AspNetCoreRichEditStringId.BulletAnnouncement');
|
|
50
|
+
sr.announcements.listItem = formatMessage('AspNetCoreRichEditStringId.ListItemAnnouncement');
|
|
51
|
+
sr.announcements.tab = formatMessage('AspNetCoreRichEditStringId.TabAnnouncement');
|
|
52
|
+
sr.announcements.carriageReturn = formatMessage('AspNetCoreRichEditStringId.CarriageReturnAnnouncement');
|
|
53
|
+
sr.announcements.lineBreak = formatMessage('AspNetCoreRichEditStringId.LineBreakAnnouncement');
|
|
54
|
+
sr.announcements.link = formatMessage('AspNetCoreRichEditStringId.LinkAnnouncement');
|
|
55
|
+
sr.announcements.outOfLink = formatMessage('AspNetCoreRichEditStringId.OutOfLinkAnnouncement');
|
|
56
|
+
sr.announcements.blank = formatMessage('AspNetCoreRichEditStringId.BlankAnnouncement');
|
|
57
|
+
sr.announcements.pageBreak = formatMessage('AspNetCoreRichEditStringId.PageBreakAnnouncement');
|
|
58
|
+
return sr;
|
|
59
|
+
}
|
|
@@ -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.3",
|
|
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.3",
|
|
15
|
+
"devextreme-dist": "26.1.3"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"dictionary-en": "3.2.0",
|