@beyondwork/docx-react-component 1.0.73 → 1.0.75

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 (59) hide show
  1. package/package.json +1 -1
  2. package/src/api/anchor-conversion.ts +2 -2
  3. package/src/api/public-types.ts +40 -6
  4. package/src/api/v3/_runtime-handle.ts +15 -0
  5. package/src/api/v3/runtime/workflow.ts +130 -1
  6. package/src/api/v3/ui/_types.ts +21 -0
  7. package/src/api/v3/ui/overlays.ts +276 -2
  8. package/src/api/v3/ui/scope.ts +113 -1
  9. package/src/compare/diff-engine.ts +1 -2
  10. package/src/core/commands/index.ts +14 -15
  11. package/src/core/selection/anchor-conversion.ts +2 -2
  12. package/src/core/selection/mapping.ts +10 -8
  13. package/src/core/selection/review-anchors.ts +3 -3
  14. package/src/io/export/export-session.ts +53 -0
  15. package/src/io/export/serialize-comments.ts +4 -4
  16. package/src/io/export/serialize-runtime-revisions.ts +10 -10
  17. package/src/io/export/split-review-boundaries.ts +4 -4
  18. package/src/io/export/split-story-blocks-for-runtime-revisions.ts +2 -2
  19. package/src/io/ooxml/parse-comments.ts +2 -2
  20. package/src/io/ooxml/parse-headers-footers.ts +7 -13
  21. package/src/io/ooxml/parse-main-document.ts +7 -31
  22. package/src/io/ooxml/table-opaque-preservation.ts +171 -0
  23. package/src/model/anchor.ts +9 -1
  24. package/src/model/canonical-document.ts +76 -3
  25. package/src/preservation/store.ts +24 -0
  26. package/src/review/store/comment-anchors.ts +1 -1
  27. package/src/review/store/comment-remapping.ts +1 -1
  28. package/src/review/store/revision-actions.ts +4 -4
  29. package/src/review/store/revision-types.ts +1 -1
  30. package/src/review/store/scope-tag-diff.ts +1 -1
  31. package/src/runtime/collab/map-local-selection-on-remote-replay.ts +7 -7
  32. package/src/runtime/document-runtime.ts +233 -38
  33. package/src/runtime/formatting/formatting-context.ts +1 -1
  34. package/src/runtime/layout/inert-layout-facet.ts +1 -0
  35. package/src/runtime/layout/layout-engine-version.ts +9 -1
  36. package/src/runtime/layout/public-facet.ts +27 -0
  37. package/src/runtime/scopes/evidence.ts +1 -1
  38. package/src/runtime/scopes/review-bundle.ts +1 -1
  39. package/src/runtime/scopes/scope-range.ts +1 -1
  40. package/src/runtime/selection/post-edit-validator.ts +4 -4
  41. package/src/runtime/surface-projection.ts +48 -4
  42. package/src/runtime/workflow/scope-writer.ts +212 -10
  43. package/src/session/import/review-import.ts +12 -12
  44. package/src/session/import/workflow-scope-import.ts +9 -8
  45. package/src/shell/session-bootstrap.ts +4 -0
  46. package/src/ui-tailwind/editor-surface/pm-schema.ts +22 -2
  47. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +5 -2
  48. package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +22 -3
  49. package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +7 -3
  50. package/src/ui-tailwind/page-stack/tw-endnote-area.tsx +5 -1
  51. package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +5 -1
  52. package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +7 -5
  53. package/src/ui-tailwind/page-stack/use-visible-block-range.ts +99 -43
  54. package/src/ui-tailwind/review-workspace/use-page-markers.ts +48 -7
  55. package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +0 -13
  56. package/src/ui-tailwind/tw-review-workspace.tsx +13 -41
  57. package/src/validation/compatibility-engine.ts +1 -1
  58. package/src/ui-tailwind/chrome/tw-layout-panel.tsx +0 -114
  59. package/src/ui-tailwind/review-workspace/tw-review-workspace-page-toolbar.tsx +0 -240
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Unified table-opaque-preservation predicate — shared across
3
+ * `parse-main-document.ts`, `parse-headers-footers.ts`, and
4
+ * `parse-footnotes.ts` so the three parsers agree on which tables can
5
+ * be promoted to the typed `TableNode` shape and which must fall back
6
+ * to `opaque_block` for lossless rawXml round-trip.
7
+ *
8
+ * ## History
9
+ *
10
+ * Before this module existed, each of the three parsers maintained its
11
+ * own divergent allowlist. That divergence drove the coord-01 §11
12
+ * finding (2026-04-24): legacy form fields (`FORMTEXT`,
13
+ * `FORMCHECKBOX`, `FORMDROPDOWN`) classify as `UNKNOWN` under
14
+ * `FIELD_FAMILY_PATTERN` (which targets data-field families), so both
15
+ * the main-story and secondary-story table predicates rejected them
16
+ * even though the body-direct paragraph parser path (via
17
+ * `parseFFDataFromFldChar`) handles them correctly. The result was
18
+ * 58 cell paragraphs lost on APS Short Form because 2 body-direct
19
+ * tables flattened to opaque_block; 5 additional footer tables on
20
+ * the same doc also flatten (those carry `DOCPROPERTY` — a real data
21
+ * field, distinct from the legacy form-field case).
22
+ *
23
+ * ## Contract
24
+ *
25
+ * A `<w:tbl>` is "safe to parse as structured" iff NONE of the
26
+ * following are true:
27
+ *
28
+ * 1. **Revision markup.** `<w:ins>`, `<w:del>`, `<w:moveFrom>`,
29
+ * `<w:moveTo>`, `<w:rPrChange>`, `<w:pPrChange>`,
30
+ * `<w:tblPrChange>`, `<w:trPrChange>`, `<w:tcPrChange>`,
31
+ * `<w:sectPrChange>`, `<w:cellIns>`, `<w:cellDel>`,
32
+ * `<w:cellMerge>`, `<w:smartTag>`.
33
+ * Rationale: tracked-change-aware table editing isn't implemented;
34
+ * flattening preserves fidelity until it is.
35
+ *
36
+ * 2. **Field instruction outside the safe set.** Scan every
37
+ * `w:instr` simple-field attribute + every `<w:instrText>` inside
38
+ * a complex field (`<w:fldChar>begin</w:fldChar>...end`) and
39
+ * classify via `classifyFieldInstruction`. If the classified
40
+ * family isn't in `SAFE_TABLE_FIELD_FAMILIES` AND the instruction
41
+ * isn't a legacy form field (`isLegacyFormFieldInstruction`), the
42
+ * table flattens. The legacy-form-field short-circuit is the
43
+ * coord-01 §11 fix — those tokens are fully supported by the cell
44
+ * paragraph parser but classify as `UNKNOWN`.
45
+ *
46
+ * Extending this contract is a cross-layer architectural decision:
47
+ * widening `SAFE_TABLE_FIELD_FAMILIES` lets more tables parse as
48
+ * structured but commits the edit-command layer (L06/L08) to handle
49
+ * each field family's cell-level edit semantics. Keep this list
50
+ * aligned with the field families supported by the runtime
51
+ * formatting + scope-command pipeline.
52
+ */
53
+
54
+ import { classifyFieldInstruction } from "./parse-fields.ts";
55
+
56
+ /**
57
+ * Field families safe enough to leave a `<w:tbl>` in structured
58
+ * canonical form. Widening this set commits L06 / L08 to cell-level
59
+ * edit semantics for that family — don't expand opportunistically.
60
+ */
61
+ export const SAFE_TABLE_FIELD_FAMILIES: ReadonlySet<string> = new Set([
62
+ "REF",
63
+ "PAGEREF",
64
+ "NOTEREF",
65
+ "TOC",
66
+ "PAGE",
67
+ "NUMPAGES",
68
+ ]);
69
+
70
+ /**
71
+ * Revision + structural markup that forces table flattening.
72
+ */
73
+ const RISKY_TABLE_MARKUP_RE =
74
+ /<w:(ins|del|moveFrom|moveTo|rPrChange|pPrChange|tblPrChange|trPrChange|tcPrChange|sectPrChange|cellIns|cellDel|cellMerge|smartTag)\b/;
75
+
76
+ const SIMPLE_FIELD_INSTR_RE = /\bw:instr="([^"]*)"/g;
77
+
78
+ const COMPLEX_FIELD_TOKEN_RE =
79
+ /<(?:\w+:)?fldChar\b[^>]*?(?:\w+:)?fldCharType="(begin|separate|end)"[^>]*?(?:\/>|>[\s\S]*?<\/(?:\w+:)?fldChar>)|<(?:\w+:)?instrText\b[^>]*>([\s\S]*?)<\/(?:\w+:)?instrText>/gu;
80
+
81
+ /**
82
+ * Decode the minimal set of XML entities that can appear inside an
83
+ * `instrText` payload. `classifyFieldInstruction` consumes the raw
84
+ * instruction text, so we normalize `&amp;` / `&quot;` / `&lt;` /
85
+ * `&gt;` / `&apos;` before handing it off. Matches the pre-existing
86
+ * behavior in `parse-main-document.ts::extractComplexFieldInstructions`.
87
+ */
88
+ function decodeXmlEntities(text: string): string {
89
+ return text
90
+ .replace(/&lt;/g, "<")
91
+ .replace(/&gt;/g, ">")
92
+ .replace(/&quot;/g, '"')
93
+ .replace(/&apos;/g, "'")
94
+ .replace(/&amp;/g, "&");
95
+ }
96
+
97
+ /**
98
+ * Extract every complex-field instruction (`<w:fldChar
99
+ * fldCharType="begin">...<w:instrText>...</w:instrText>...</w:fldChar
100
+ * fldCharType="end">`) as a single concatenated instruction string.
101
+ * Mirrors `parse-main-document.ts::extractComplexFieldInstructions`.
102
+ */
103
+ export function extractComplexFieldInstructionsFromRaw(rawXml: string): string[] {
104
+ const instructions: string[] = [];
105
+ let active = "";
106
+ let capturing = false;
107
+ for (const match of rawXml.matchAll(COMPLEX_FIELD_TOKEN_RE)) {
108
+ const [, fldCharType, instrText] = match;
109
+ if (fldCharType === "begin") {
110
+ active = "";
111
+ capturing = true;
112
+ continue;
113
+ }
114
+ if (fldCharType === "separate" || fldCharType === "end") {
115
+ if (capturing && active.trim().length > 0) instructions.push(active);
116
+ active = "";
117
+ capturing = false;
118
+ continue;
119
+ }
120
+ if (capturing && instrText !== undefined) active += decodeXmlEntities(instrText);
121
+ }
122
+ return instructions;
123
+ }
124
+
125
+ /**
126
+ * Legacy form fields (ECMA-376 §17.16.21) — `FORMTEXT`, `FORMCHECKBOX`,
127
+ * `FORMDROPDOWN`. These are fully supported by the body-direct
128
+ * paragraph parser via `parseFFDataFromFldChar` but classify as
129
+ * `UNKNOWN` under `FIELD_FAMILY_PATTERN` (which targets data-field
130
+ * families like REF / TOC / MERGEFIELD). Short-circuiting them lets
131
+ * form-field cells stay in structured canonical tables instead of
132
+ * flattening the entire table to `opaque_block`. Coord-01 §11, 2026-04-24.
133
+ */
134
+ export function isLegacyFormFieldInstruction(instruction: string): boolean {
135
+ return /^\s*(FORMTEXT|FORMCHECKBOX|FORMDROPDOWN)\b/i.test(instruction);
136
+ }
137
+
138
+ /**
139
+ * Decides whether a single field instruction (either `w:instr`
140
+ * attribute value or concatenated `instrText` run) is safe for
141
+ * structured-table parsing. Used by the shared predicate below;
142
+ * exposed for direct callers (the debug diagnostics script runs
143
+ * this to classify source instructions alongside the canonical).
144
+ */
145
+ export function isSafeTableFieldInstruction(instruction: string): boolean {
146
+ if (isLegacyFormFieldInstruction(instruction)) return true;
147
+ const family = classifyFieldInstruction(instruction).family;
148
+ return SAFE_TABLE_FIELD_FAMILIES.has(family);
149
+ }
150
+
151
+ /**
152
+ * Shared `<w:tbl>` opaque-preservation predicate. Returns `true`
153
+ * when the table must fall back to `opaque_block` for lossless
154
+ * round-trip, `false` when `parseTable` / `parseSimpleTableElement`
155
+ * can promote it to a structured `TableNode`.
156
+ *
157
+ * Callers (`parse-main-document.ts`, `parse-headers-footers.ts`,
158
+ * `parse-footnotes.ts`) pass the raw XML of the `<w:tbl>` element.
159
+ */
160
+ export function tableRequiresOpaquePreservation(rawXml: string): boolean {
161
+ if (RISKY_TABLE_MARKUP_RE.test(rawXml)) return true;
162
+
163
+ const simpleInstructions = [...rawXml.matchAll(SIMPLE_FIELD_INSTR_RE)].map(
164
+ (match) => match[1] ?? "",
165
+ );
166
+ const complexInstructions = extractComplexFieldInstructionsFromRaw(rawXml);
167
+ for (const instruction of [...simpleInstructions, ...complexInstructions]) {
168
+ if (!isSafeTableFieldInstruction(instruction)) return true;
169
+ }
170
+ return false;
171
+ }
@@ -25,9 +25,17 @@ export interface BoundaryAssoc {
25
25
  end: Assoc;
26
26
  }
27
27
 
28
+ /**
29
+ * Range anchor — flat shape `{ kind: "range", from, to, assoc }` after the
30
+ * 2026-04-23 flat-wins collapse. Prior to the collapse this carried a nested
31
+ * `{ range: DocRange, assoc }` shape. See `canonical-document.ts` CanonicalAnchor
32
+ * for the matching canonical shape + `repairCanonicalDocumentEnvelope` for the
33
+ * persisted-snapshot legacy-to-flat migration.
34
+ */
28
35
  export interface RangeAnchor {
29
36
  kind: "range";
30
- range: DocRange;
37
+ from: Position;
38
+ to: Position;
31
39
  assoc: BoundaryAssoc;
32
40
  }
33
41
 
@@ -2013,7 +2013,8 @@ export interface BoundaryAssoc {
2013
2013
  export type CanonicalAnchor =
2014
2014
  | {
2015
2015
  kind: "range";
2016
- range: DocRange;
2016
+ from: number;
2017
+ to: number;
2017
2018
  assoc: BoundaryAssoc;
2018
2019
  }
2019
2020
  | {
@@ -2329,7 +2330,7 @@ export function repairCanonicalDocumentEnvelope(
2329
2330
  review:
2330
2331
  record.review === undefined
2331
2332
  ? base.review
2332
- : (record.review as CanonicalDocument["review"]),
2333
+ : (migrateLegacyReviewAnchors(record.review) as CanonicalDocument["review"]),
2333
2334
  preservation:
2334
2335
  record.preservation === undefined
2335
2336
  ? base.preservation
@@ -2355,6 +2356,67 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
2355
2356
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2356
2357
  }
2357
2358
 
2359
+ /**
2360
+ * Schema-compat shim for the 2026-04-23 flat-wins anchor collapse. Legacy
2361
+ * persisted snapshots carry range anchors as `{kind: "range", range: {from, to},
2362
+ * assoc}`. Rewrite those to the flat shape `{kind: "range", from, to, assoc}`
2363
+ * on rehydration so old snapshots continue to load.
2364
+ */
2365
+ export function migrateLegacyReviewAnchors(review: unknown): unknown {
2366
+ if (!isPlainRecord(review)) {
2367
+ return review;
2368
+ }
2369
+ const migrated: Record<string, unknown> = { ...review };
2370
+ if (isPlainRecord(review.comments)) {
2371
+ migrated.comments = Object.fromEntries(
2372
+ Object.entries(review.comments).map(([id, record]) => [
2373
+ id,
2374
+ migrateReviewRecordAnchor(record),
2375
+ ]),
2376
+ );
2377
+ }
2378
+ if (isPlainRecord(review.revisions)) {
2379
+ migrated.revisions = Object.fromEntries(
2380
+ Object.entries(review.revisions).map(([id, record]) => [
2381
+ id,
2382
+ migrateReviewRecordAnchor(record),
2383
+ ]),
2384
+ );
2385
+ }
2386
+ return migrated;
2387
+ }
2388
+
2389
+ function migrateReviewRecordAnchor(record: unknown): unknown {
2390
+ if (!isPlainRecord(record)) {
2391
+ return record;
2392
+ }
2393
+ const anchor = record.anchor;
2394
+ if (!isPlainRecord(anchor) || anchor.kind !== "range") {
2395
+ return record;
2396
+ }
2397
+ if (typeof anchor.from === "number" && typeof anchor.to === "number") {
2398
+ return record;
2399
+ }
2400
+ const nested = anchor.range;
2401
+ if (
2402
+ isPlainRecord(nested) &&
2403
+ typeof nested.from === "number" &&
2404
+ typeof nested.to === "number"
2405
+ ) {
2406
+ const { range: _range, ...rest } = anchor;
2407
+ void _range;
2408
+ return {
2409
+ ...record,
2410
+ anchor: {
2411
+ ...rest,
2412
+ from: nested.from,
2413
+ to: nested.to,
2414
+ },
2415
+ };
2416
+ }
2417
+ return record;
2418
+ }
2419
+
2358
2420
  export function serializeCanonicalDocument(document: CanonicalDocument): string {
2359
2421
  assertCanonicalDocument(document);
2360
2422
  return stableStringify(document);
@@ -3507,7 +3569,18 @@ function validateAnchor(
3507
3569
  }
3508
3570
 
3509
3571
  if (kind === "range") {
3510
- validateRange(record.range, `${path}.range`, issues);
3572
+ if (typeof record.from !== "number") {
3573
+ issues.push({
3574
+ path: `${path}.from`,
3575
+ message: "range anchor from must be a number.",
3576
+ });
3577
+ }
3578
+ if (typeof record.to !== "number") {
3579
+ issues.push({
3580
+ path: `${path}.to`,
3581
+ message: "range anchor to must be a number.",
3582
+ });
3583
+ }
3511
3584
  validateBoundaryAssoc(record.assoc, `${path}.assoc`, issues);
3512
3585
  } else if (kind === "node") {
3513
3586
  if (typeof record.at !== "number") {
@@ -170,6 +170,15 @@ export function describeStructuredWrapperBlock(
170
170
  };
171
171
  }
172
172
  if (block.properties.sdtType === "docPartObj") {
173
+ // coord-02 §11 P0 — a Template content control that wraps a
174
+ // drawing_frame (CCEP header logos, cover hero photos) must stay
175
+ // recursable so surface-projection emits the inner image segment.
176
+ // Collapsing to opaque_block here silently drops every image
177
+ // inside a docPartObj SDT. Only collapse when the content is
178
+ // genuinely wrapper-heavy with no user-visible media.
179
+ if (sdtContainsDrawingFrame(block)) {
180
+ return null;
181
+ }
173
182
  return {
174
183
  featureKey: "content-controls",
175
184
  label: "Template content control",
@@ -220,6 +229,21 @@ function createDetail(fragment: OpaqueFragmentRecord): string {
220
229
  : "Preserved whole-unit to keep unsupported OOXML intact.";
221
230
  }
222
231
 
232
+ function sdtContainsDrawingFrame(block: Extract<BlockNode, { type: "sdt" }>): boolean {
233
+ const visit = (node: unknown): boolean => {
234
+ if (!node || typeof node !== "object") return false;
235
+ const n = node as { type?: string; children?: readonly unknown[] };
236
+ if (n.type === "drawing_frame") return true;
237
+ if (Array.isArray(n.children)) {
238
+ for (const child of n.children) {
239
+ if (visit(child)) return true;
240
+ }
241
+ }
242
+ return false;
243
+ };
244
+ return block.children.some(visit);
245
+ }
246
+
223
247
  function isTocContentControl(block: Extract<BlockNode, { type: "sdt" }>): boolean {
224
248
  const searchText = [
225
249
  block.properties.alias,
@@ -47,7 +47,7 @@ export function summarizeCommentAnchor(anchor: CommentAnchor): CommentAnchorSumm
47
47
  return {
48
48
  anchor,
49
49
  state: "active",
50
- range: normalizeRange(anchor.range),
50
+ range: normalizeRange({ from: anchor.from, to: anchor.to }),
51
51
  };
52
52
  }
53
53
 
@@ -98,7 +98,7 @@ function normalizeCommentAnchor(
98
98
 
99
99
  if (
100
100
  mappedAnchor.kind === "range" &&
101
- !rangeStaysWithinCommentableStory(nextContent, mappedAnchor.range)
101
+ !rangeStaysWithinCommentableStory(nextContent, { from: mappedAnchor.from, to: mappedAnchor.to })
102
102
  ) {
103
103
  return detachReviewAnchor(previousRange, "invalidatedByStructureChange");
104
104
  }
@@ -153,8 +153,8 @@ export function applyRevisionAction(
153
153
 
154
154
  const story = parseTextStory(options.document.content);
155
155
  const range = normalizeRange(
156
- revision.anchor.range.from,
157
- revision.anchor.range.to,
156
+ revision.anchor.from,
157
+ revision.anchor.to,
158
158
  );
159
159
 
160
160
  if (range.to > story.size) {
@@ -811,8 +811,8 @@ function resolveParagraphMarkDeletionRange(
811
811
 
812
812
  const paragraphs = mapParagraphRanges(story);
813
813
  const anchorPosition = normalizeRange(
814
- revision.anchor.range.from,
815
- revision.anchor.range.to,
814
+ revision.anchor.from,
815
+ revision.anchor.to,
816
816
  ).from;
817
817
  const paragraph = paragraphs.find(
818
818
  (candidate) =>
@@ -77,7 +77,7 @@ export function summarizeRevisionAnchor(anchor: RevisionAnchor): RevisionAnchorS
77
77
  return {
78
78
  anchor,
79
79
  state: "active",
80
- range: normalizeRange(anchor.range),
80
+ range: normalizeRange({ from: anchor.from, to: anchor.to }),
81
81
  };
82
82
  }
83
83
 
@@ -96,7 +96,7 @@ export function collectScopeTagTouches(
96
96
 
97
97
  function anchorRange(anchor: CanonicalAnchor): { from: number; to: number } {
98
98
  if (anchor.kind === "range") {
99
- return { from: anchor.range.from, to: anchor.range.to };
99
+ return { from: anchor.from, to: anchor.to };
100
100
  }
101
101
  if (anchor.kind === "node") {
102
102
  return { from: anchor.at, to: anchor.at };
@@ -88,10 +88,10 @@ export function mapLocalSelectionOnRemoteReplay(
88
88
 
89
89
  if (active.kind === "range") {
90
90
  const directionForward = selection.anchor <= selection.head;
91
- const isCollapsed = active.range.from === active.range.to;
91
+ const isCollapsed = active.from === active.to;
92
92
 
93
93
  if (isCollapsed) {
94
- const collapsedAt = mapPosition(active.range.from, 1, mapping).position;
94
+ const collapsedAt = mapPosition(active.from, 1, mapping).position;
95
95
  return {
96
96
  anchor: collapsedAt,
97
97
  head: collapsedAt,
@@ -107,7 +107,7 @@ export function mapLocalSelectionOnRemoteReplay(
107
107
  const mapped = mapAnchor(active, mapping);
108
108
 
109
109
  if (mapped.kind === "range") {
110
- const { from, to } = mapped.range;
110
+ const { from, to } = mapped;
111
111
  return {
112
112
  anchor: directionForward ? from : to,
113
113
  head: directionForward ? to : from,
@@ -122,7 +122,7 @@ export function mapLocalSelectionOnRemoteReplay(
122
122
  // positions that land inside a deleted span to `step.from +
123
123
  // insertSize`, which is the correct logical "where the selection
124
124
  // used to start" position.
125
- const collapsedAt = mapPosition(active.range.from, 1, mapping).position;
125
+ const collapsedAt = mapPosition(active.from, 1, mapping).position;
126
126
  return {
127
127
  anchor: collapsedAt,
128
128
  head: collapsedAt,
@@ -147,9 +147,9 @@ export function mapLocalSelectionOnRemoteReplay(
147
147
  }
148
148
  if (mapped.kind === "range") {
149
149
  return {
150
- anchor: mapped.range.from,
151
- head: mapped.range.to,
152
- isCollapsed: mapped.range.from === mapped.range.to,
150
+ anchor: mapped.from,
151
+ head: mapped.to,
152
+ isCollapsed: mapped.from === mapped.to,
153
153
  activeRange: mapped,
154
154
  };
155
155
  }