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.
Files changed (62) hide show
  1. package/bin/gulpfile.js +1 -1
  2. package/bin/index-custom.js +1 -1
  3. package/bin/localization-builder.js +1 -1
  4. package/bin/nspell-index.js +1 -1
  5. package/bin/nspell.webpack.config.js +1 -1
  6. package/bin/webpack-externals.js +1 -1
  7. package/bin/webpack.config.js +1 -1
  8. package/dist/dx.richedit.css +0 -1
  9. package/dist/dx.richedit.d.ts +1 -1
  10. package/dist/dx.richedit.js +1271 -701
  11. package/dist/dx.richedit.min.js +2 -2
  12. package/index.d.ts +1 -1
  13. package/index.js +1 -1
  14. package/lib/client/client-rich-edit.js +2 -2
  15. package/lib/client/default-localization.js +25 -7
  16. package/lib/client/dialogs/alert-dialog.d.ts +1 -1
  17. package/lib/client/dialogs/bookmark-dialog.d.ts +1 -1
  18. package/lib/client/dialogs/delete-table-cells-dialog.d.ts +1 -1
  19. package/lib/client/dialogs/dialog-base.d.ts +1 -1
  20. package/lib/client/dialogs/find-replace-dialog.d.ts +1 -1
  21. package/lib/client/dialogs/finish-and-merge-dialog.d.ts +1 -1
  22. package/lib/client/dialogs/font-dialog.d.ts +1 -1
  23. package/lib/client/dialogs/hyperlink-dialog.d.ts +1 -1
  24. package/lib/client/dialogs/insert-merge-field-dialog.d.ts +1 -1
  25. package/lib/client/dialogs/insert-table-cells-dialog.d.ts +1 -1
  26. package/lib/client/dialogs/insert-table-dialog.d.ts +1 -1
  27. package/lib/client/dialogs/page-setup-dialog.d.ts +1 -1
  28. package/lib/client/dialogs/paragraph-dialog.d.ts +1 -1
  29. package/lib/client/dialogs/split-table-cells-dialog.d.ts +1 -1
  30. package/lib/client/dialogs/tabs-dialog.d.ts +1 -1
  31. package/lib/client/public/rich-edit.js +2 -0
  32. package/lib/client/public/utils.js +5 -1
  33. package/lib/common/canvas/renderes/common/document-renderer.js +7 -1
  34. package/lib/common/commands/command-manager.d.ts +2 -0
  35. package/lib/common/commands/command-manager.js +6 -0
  36. package/lib/common/commands/layout/switch-view-command.js +2 -1
  37. package/lib/common/commands/selection/go-to-next-word-command.d.ts +3 -4
  38. package/lib/common/commands/selection/go-to-next-word-command.js +2 -9
  39. package/lib/common/commands/selection/go-to-prev-word-command.d.ts +3 -2
  40. package/lib/common/commands/selection/go-to-prev-word-command.js +2 -2
  41. package/lib/common/commands/selection/select-all-document-command.d.ts +2 -0
  42. package/lib/common/commands/selection/select-all-document-command.js +6 -0
  43. package/lib/common/commands/selection/selection-command-base.d.ts +2 -0
  44. package/lib/common/commands/selection/selection-command-base.js +8 -0
  45. package/lib/common/event-manager.js +0 -1
  46. package/lib/common/formats/txt/txt-exporter.js +1 -1
  47. package/lib/common/input-controller.js +2 -0
  48. package/lib/common/interfaces/i-rich-edit-core.d.ts +2 -0
  49. package/lib/common/layout-formatter/formatter/base-formatter.d.ts +1 -0
  50. package/lib/common/layout-formatter/formatter/base-formatter.js +11 -0
  51. package/lib/common/layout-formatter/row/result.d.ts +0 -1
  52. package/lib/common/layout-formatter/row/result.js +4 -19
  53. package/lib/common/layout-formatter/row/size-engine/row-sizes-manager.js +1 -3
  54. package/lib/common/model/sub-document.d.ts +11 -0
  55. package/lib/common/model/sub-document.js +66 -22
  56. package/lib/common/rich-edit-core.d.ts +3 -0
  57. package/lib/common/rich-edit-core.js +18 -0
  58. package/lib/common/screen-reader-manager.d.ts +45 -0
  59. package/lib/common/screen-reader-manager.js +309 -0
  60. package/lib/common/utils/interval-utils.d.ts +4 -0
  61. package/lib/common/utils/interval-utils.js +25 -0
  62. 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
- let result = "";
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
- let nested = 0;
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
- switch (iterator.run.getType()) {
80
- case RunType.FieldCodeStartRun:
81
- nested++;
82
- break;
83
- case RunType.FieldCodeEndRun:
84
- nested--;
85
- break;
86
- case RunType.TextRun:
87
- if (pos >= interval.start && nested == 0)
88
- result += iterator.getCurrentChar();
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.moveToNextChar())
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 result.replace(RichUtils.specialCharacters.LineBreak, " ");
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,4 @@
1
+ import { FixedInterval } from '@devexpress/utils/lib/intervals/fixed';
2
+ export declare class IntervalUtils {
3
+ static subtractIntervals(a: FixedInterval[], b: FixedInterval[]): FixedInterval[];
4
+ }
@@ -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": "25.2.7",
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": "25.2.7",
15
- "devextreme-dist": "25.2.7"
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",