@beyondwork/docx-react-component 1.0.56 → 1.0.58

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 (113) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/api/public-types.ts +330 -0
  4. package/src/compare/diff-engine.ts +3 -0
  5. package/src/core/commands/formatting-commands.ts +1 -0
  6. package/src/core/commands/index.ts +17 -11
  7. package/src/core/selection/mapping.ts +18 -1
  8. package/src/core/selection/review-anchors.ts +29 -18
  9. package/src/io/chart-preview-resolver.ts +175 -41
  10. package/src/io/docx-session.ts +57 -2
  11. package/src/io/export/serialize-main-document.ts +82 -0
  12. package/src/io/export/serialize-styles.ts +61 -3
  13. package/src/io/export/table-properties-xml.ts +19 -4
  14. package/src/io/normalize/normalize-text.ts +33 -0
  15. package/src/io/ooxml/parse-anchor.ts +182 -0
  16. package/src/io/ooxml/parse-drawing.ts +319 -0
  17. package/src/io/ooxml/parse-fields.ts +115 -2
  18. package/src/io/ooxml/parse-fill.ts +215 -0
  19. package/src/io/ooxml/parse-font-table.ts +190 -0
  20. package/src/io/ooxml/parse-footnotes.ts +52 -1
  21. package/src/io/ooxml/parse-main-document.ts +241 -1
  22. package/src/io/ooxml/parse-numbering.ts +96 -0
  23. package/src/io/ooxml/parse-picture.ts +158 -0
  24. package/src/io/ooxml/parse-settings.ts +34 -0
  25. package/src/io/ooxml/parse-shapes.ts +87 -0
  26. package/src/io/ooxml/parse-solid-fill.ts +11 -0
  27. package/src/io/ooxml/parse-styles.ts +74 -1
  28. package/src/io/ooxml/parse-theme.ts +60 -0
  29. package/src/io/paste/html-clipboard.ts +449 -0
  30. package/src/io/paste/word-clipboard.ts +5 -1
  31. package/src/legal/_document-root.ts +26 -0
  32. package/src/legal/bookmarks.ts +4 -3
  33. package/src/legal/cross-references.ts +3 -2
  34. package/src/legal/defined-terms.ts +2 -1
  35. package/src/legal/signature-blocks.ts +2 -1
  36. package/src/model/canonical-document.ts +421 -3
  37. package/src/runtime/chart/chart-model-store.ts +73 -10
  38. package/src/runtime/document-runtime.ts +760 -41
  39. package/src/runtime/document-search.ts +61 -0
  40. package/src/runtime/edit-ops/index.ts +129 -0
  41. package/src/runtime/event-refresh-hints.ts +7 -0
  42. package/src/runtime/field-resolver.ts +341 -0
  43. package/src/runtime/footnote-resolver.ts +55 -0
  44. package/src/runtime/hyperlink-color-resolver.ts +13 -10
  45. package/src/runtime/object-grab/index.ts +51 -0
  46. package/src/runtime/paragraph-style-resolver.ts +105 -0
  47. package/src/runtime/query-scopes.ts +186 -0
  48. package/src/runtime/resolved-numbering-geometry.ts +12 -0
  49. package/src/runtime/scope-resolver.ts +60 -0
  50. package/src/runtime/selection/cursor-ops.ts +186 -15
  51. package/src/runtime/selection/index.ts +17 -1
  52. package/src/runtime/structure-ops/index.ts +77 -0
  53. package/src/runtime/styles-cascade.ts +33 -0
  54. package/src/runtime/surface-projection.ts +192 -12
  55. package/src/runtime/theme-color-resolver.ts +189 -44
  56. package/src/runtime/units.ts +46 -0
  57. package/src/runtime/view-state.ts +13 -2
  58. package/src/ui/WordReviewEditor.tsx +239 -11
  59. package/src/ui/editor-runtime-boundary.ts +97 -1
  60. package/src/ui/editor-shell-view.tsx +1 -1
  61. package/src/ui/runtime-shortcut-dispatch.ts +17 -3
  62. package/src/ui-tailwind/chart/ChartSurface.tsx +36 -10
  63. package/src/ui-tailwind/chart/layout/plot-area.ts +120 -45
  64. package/src/ui-tailwind/chart/render/area.tsx +22 -4
  65. package/src/ui-tailwind/chart/render/bar-column.tsx +37 -11
  66. package/src/ui-tailwind/chart/render/bubble.tsx +6 -2
  67. package/src/ui-tailwind/chart/render/combo.tsx +37 -4
  68. package/src/ui-tailwind/chart/render/line.tsx +28 -5
  69. package/src/ui-tailwind/chart/render/pie.tsx +36 -16
  70. package/src/ui-tailwind/chart/render/progressive-render.ts +8 -1
  71. package/src/ui-tailwind/chart/render/scatter.tsx +9 -4
  72. package/src/ui-tailwind/chrome/avatar-initials.ts +15 -0
  73. package/src/ui-tailwind/chrome/tw-comment-preview.tsx +3 -1
  74. package/src/ui-tailwind/chrome/tw-context-menu.tsx +14 -0
  75. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +3 -2
  76. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +30 -11
  77. package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +15 -2
  78. package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +1 -1
  79. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +24 -7
  80. package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +31 -12
  81. package/src/ui-tailwind/chrome-overlay/page-border-resolver.ts +211 -0
  82. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +24 -0
  83. package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +74 -0
  84. package/src/ui-tailwind/chrome-overlay/tw-locked-block-layer.tsx +65 -0
  85. package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +157 -0
  86. package/src/ui-tailwind/chrome-overlay/tw-page-border-overlay.tsx +233 -0
  87. package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +135 -13
  88. package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +51 -0
  89. package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +12 -4
  90. package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +32 -12
  91. package/src/ui-tailwind/chrome-overlay/tw-toc-outline-sidebar.tsx +133 -0
  92. package/src/ui-tailwind/editor-surface/chart-node-view.tsx +49 -10
  93. package/src/ui-tailwind/editor-surface/float-wrap-resolver.ts +119 -0
  94. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +236 -9
  95. package/src/ui-tailwind/editor-surface/pm-schema.ts +214 -11
  96. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +32 -2
  97. package/src/ui-tailwind/editor-surface/shape-renderer.ts +206 -0
  98. package/src/ui-tailwind/editor-surface/surface-layer.ts +66 -0
  99. package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +29 -0
  100. package/src/ui-tailwind/editor-surface/tw-segment-view.tsx +7 -1
  101. package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +22 -6
  102. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +10 -16
  103. package/src/ui-tailwind/review/tw-health-panel.tsx +0 -25
  104. package/src/ui-tailwind/review/tw-rail-card.tsx +38 -17
  105. package/src/ui-tailwind/review/tw-review-rail.tsx +2 -2
  106. package/src/ui-tailwind/review/tw-revision-sidebar.tsx +5 -12
  107. package/src/ui-tailwind/review/tw-workflow-tab.tsx +2 -2
  108. package/src/ui-tailwind/theme/editor-theme.css +1 -0
  109. package/src/ui-tailwind/theme/tokens.css +6 -0
  110. package/src/ui-tailwind/theme/tokens.ts +10 -0
  111. package/src/ui-tailwind/tw-review-workspace.tsx +23 -0
  112. package/src/validation/compatibility-engine.ts +2 -0
  113. package/src/validation/docx-comment-proof.ts +12 -3
@@ -1,44 +1,112 @@
1
1
  /**
2
- * Resolve a (`<w:color>`-shaped) theme-color reference to a concrete hex
3
- * string, applying the `w:themeTint` / `w:themeShade` HSL-luminance
4
- * modulation per ECMA-376 §17.18.85 / §17.18.83.
2
+ * Unified runtime color resolver for every OOXML theme reference.
5
3
  *
6
- * Usage:
7
- * - The IO layer (`parse-run-formatting.ts`) captures
8
- * `colorThemeSlot` / `colorThemeTint` / `colorThemeShade` on
9
- * `CanonicalRunFormatting` as raw strings (preserved for
10
- * byte-stable round-trip).
11
- * - The render/cascade layer calls `resolveThemeColorHex(rPr, theme)`
12
- * to collapse the reference to the actual hex colour the browser
13
- * should paint. The original fields stay intact so export still
14
- * round-trips the theme reference (not the computed hex).
4
+ * Covers:
5
+ * - `<w:color>` §17.18.85/§17.18.83 byte-form `w:themeTint`/`w:themeShade`
6
+ * (HSL luminance modulation).
7
+ * - `<a:schemeClr>` §20.1.2.3.x DrawingML modifier chain
8
+ * `lumMod → lumOff → satMod → hueMod → tint → shade` (per-100,000 units).
9
+ * - `<w:clrSchemeMapping>` §17.15.1.17 style-slot scheme-slot remap
10
+ * (including the implicit identity default when settings.xml omits it).
11
+ * - `<w:color w:val="auto"/>` the `t1` style slot via the clrMap.
15
12
  *
16
- * Lane 3 L2.c. Mirrors LibreOffice's `Color::ApplyTintOrShade` at
17
- * `vendor/libreoffice/include/tools/color.hxx:200-240` (shape only; no
18
- * code copied our implementation is pure TypeScript against the
19
- * ECMA-376 formulae).
13
+ * Cascade layer entry points:
14
+ * - `ThemeColorResolver` class build once per `CanonicalTheme` snapshot,
15
+ * key the cache on `(themeHash, clrMapHash)` per CLAUDE.md §3.
16
+ * - `concretizeThemeColors(cascade, resolver)` — run-formatting post-pass.
20
17
  *
21
- * **Scope boundary:** this resolver handles the standard tint/shade
22
- * pair. Theme-color-mapping (clrSchemeMapping how "accent1" maps to a
23
- * different `<w:clrScheme>` slot in older Word versions) is NOT applied
24
- * here — `resolveThemeColor` from `src/io/ooxml/parse-theme.ts` is the
25
- * authoritative slot→hex lookup and future work will layer scheme
26
- * mapping into it.
18
+ * LibreOffice parity: port of `vendor/libreoffice/oox/source/drawingml/color.cxx`
19
+ * (shape only; no code copied). Modifier order + HSL/sRGB color-space split
20
+ * match LO; modifiers are applied in declaration order do NOT sort.
21
+ *
22
+ * The original `colorThemeSlot` / `Tint` / `Shade` fields are preserved on
23
+ * returned cascades so the export path can re-emit the original theme
24
+ * reference byte-for-byte.
27
25
  */
28
26
 
29
- import type { CanonicalRunFormatting, ResolvedTheme } from "../model/canonical-document.ts";
27
+ import type { CanonicalRunFormatting, CanonicalTheme, ClrSchemeMappingSlot, ResolvedTheme } from "../model/canonical-document.ts";
30
28
  import { resolveThemeColor } from "../io/ooxml/parse-theme.ts";
31
29
 
30
+ /**
31
+ * DrawingML color modifier (ECMA-376 §20.1.2.3.x).
32
+ * Values are in per-100,000 units (e.g. 65_000 = 65%).
33
+ * Apply in declaration order — do NOT sort or reorder.
34
+ *
35
+ * Distinct from w:themeTint / w:themeShade (ECMA-376 §17) which use
36
+ * hex-byte 0x00–0xFF encoding.
37
+ */
38
+ export interface DrawingMlColorMod {
39
+ kind: "lumMod" | "lumOff" | "satMod" | "hueMod" | "shade" | "tint" | "alpha";
40
+ value: number;
41
+ }
42
+
43
+ const DML_UNIT = 100_000;
44
+
45
+ /**
46
+ * Unified runtime theme color resolver.
47
+ * Construct once per CanonicalTheme (keyed on themeHash + clrMapHash).
48
+ * All resolution methods apply clrMap remapping before any slot lookup.
49
+ */
50
+ export class ThemeColorResolver {
51
+ private readonly theme: CanonicalTheme;
52
+
53
+ constructor(theme: CanonicalTheme) {
54
+ this.theme = theme;
55
+ }
56
+
57
+ /** Look up a scheme slot with clrMap remapping applied. Returns #RRGGBB or undefined. */
58
+ resolveSchemeSlot(slot: string): string | undefined {
59
+ const mapped = this.theme.clrMap[slot as ClrSchemeMappingSlot] ?? slot;
60
+ const hex = this.theme.clrScheme.colors[mapped];
61
+ return hex ?? undefined;
62
+ }
63
+
64
+ /** Resolve w:color val="auto" → "text 1" style slot (t1) via clrMap. */
65
+ resolveAuto(): string | undefined {
66
+ // "auto" = windowText = "text 1" style slot (t1).
67
+ // Pass "t1" so clrMap remapping is honoured (e.g. <w:clrSchemeMapping w:t1="dk2"/>).
68
+ return this.resolveSchemeSlot("t1");
69
+ }
70
+
71
+ /**
72
+ * Resolve a w:themeColor + w:themeTint/w:themeShade (§17 byte form, 0x00–0xFF).
73
+ * Delegates to the existing applyThemeTintShade for the HSL transform.
74
+ */
75
+ resolveWordThemeColor(
76
+ colorThemeSlot: string,
77
+ colorThemeTint: string | undefined,
78
+ colorThemeShade: string | undefined,
79
+ ): string | undefined {
80
+ const baseHex = this.resolveSchemeSlot(colorThemeSlot);
81
+ if (!baseHex) return undefined;
82
+ return applyThemeTintShade(baseHex, colorThemeTint, colorThemeShade);
83
+ }
84
+
85
+ /**
86
+ * Resolve an a:schemeClr reference with DrawingML modifiers (§20).
87
+ * Modifiers are applied in declaration order. Alpha mods are ignored.
88
+ * Returns undefined when the slot is absent.
89
+ */
90
+ resolveWithModifiers(
91
+ slot: string,
92
+ mods: readonly DrawingMlColorMod[],
93
+ ): string | undefined {
94
+ const baseHex = this.resolveSchemeSlot(slot);
95
+ if (!baseHex) return undefined;
96
+ if (mods.length === 0) return baseHex;
97
+ return applyDmlMods(baseHex.startsWith("#") ? baseHex : `#${baseHex}`, mods);
98
+ }
99
+ }
100
+
32
101
  /**
33
102
  * Collapse `<w:color>`-style theme-slot + tint + shade references into a
34
- * single resolved hex colour. Returns:
35
- * - the raw `colorHex` as-is when it's a direct hex (the theme fields
36
- * are ignored because direct colour wins per ECMA-376 cascade).
37
- * - `"auto"` when the source declared `w:color w:val="auto"` (sentinel
38
- * kept verbatim per our long-standing A.9 round-trip rule).
39
- * - the theme slot's hex tint/shade applied — when the source
40
- * declared `w:themeColor="accent1"` etc. and the theme is available.
41
- * - `undefined` otherwise (no colour declared, or theme slot absent).
103
+ * single resolved hex colour. §17-only byte-form path; does NOT apply
104
+ * `w:clrSchemeMapping` remap.
105
+ *
106
+ * @deprecated Use `ThemeColorResolver.resolveWordThemeColor` instead —
107
+ * it honours clrSchemeMapping. This free function is retained only for
108
+ * its targeted unit tests (`test/io/theme-color-tint-shade.test.ts`) and
109
+ * will be removed once those migrate to the class.
42
110
  */
43
111
  export function resolveThemeColorHex(
44
112
  rPr: Pick<
@@ -81,23 +149,22 @@ export function resolveThemeColorHex(
81
149
  */
82
150
  export function concretizeThemeColors(
83
151
  cascade: CanonicalRunFormatting,
84
- theme: ResolvedTheme | undefined,
152
+ resolver: ThemeColorResolver | undefined,
85
153
  ): CanonicalRunFormatting {
86
154
  if (!cascade.colorThemeSlot) return cascade;
87
155
  if (cascade.colorHex && cascade.colorHex !== "auto") return cascade;
88
- // When colorHex is "auto" we intentionally bypass it during theme
89
- // resolution: Word's cascade treats `<w:color w:val="auto"
90
- // w:themeColor="accent1"/>` as "paint with the theme slot; auto is
91
- // the fallback if theme is missing." Pass a stripped input to the
92
- // resolver so `colorHex === "auto"` doesn't short-circuit.
93
- const resolved = resolveThemeColorHex(
94
- {
95
- colorThemeSlot: cascade.colorThemeSlot,
96
- colorThemeTint: cascade.colorThemeTint,
97
- colorThemeShade: cascade.colorThemeShade,
98
- },
99
- theme,
156
+ if (!resolver) return cascade;
157
+
158
+ // `colorHex === "auto"` with a themeColor slot means "paint via the theme
159
+ // slot; auto is only the fallback when theme is missing" so we always
160
+ // resolve via the slot here. clrSchemeMapping remap is applied inside
161
+ // `resolver.resolveWordThemeColor`.
162
+ const resolved = resolver.resolveWordThemeColor(
163
+ cascade.colorThemeSlot,
164
+ cascade.colorThemeTint,
165
+ cascade.colorThemeShade,
100
166
  );
167
+
101
168
  if (!resolved || resolved === "auto") return cascade;
102
169
  if (resolved === cascade.colorHex) return cascade;
103
170
  return { ...cascade, colorHex: resolved };
@@ -174,6 +241,84 @@ function formatHexColor(rgb: { r: number; g: number; b: number }): string {
174
241
  return `${to2(rgb.r)}${to2(rgb.g)}${to2(rgb.b)}`;
175
242
  }
176
243
 
244
+ // ---------------------------------------------------------------------------
245
+ // DrawingML modifier pipeline (§20 forms — per-100,000 units)
246
+ // ---------------------------------------------------------------------------
247
+
248
+ function applyDmlMods(
249
+ hex: string,
250
+ mods: readonly DrawingMlColorMod[],
251
+ ): string {
252
+ let rgb = parseHexToRgbDml(hex);
253
+ for (const mod of mods) {
254
+ const frac = mod.value / DML_UNIT;
255
+ switch (mod.kind) {
256
+ case "lumMod": {
257
+ const hsl = rgbToHsl(rgb);
258
+ rgb = hslToRgb({ ...hsl, l: clamp01(hsl.l * frac) });
259
+ break;
260
+ }
261
+ case "lumOff": {
262
+ const hsl = rgbToHsl(rgb);
263
+ rgb = hslToRgb({ ...hsl, l: clamp01(hsl.l + frac) });
264
+ break;
265
+ }
266
+ case "satMod": {
267
+ const hsl = rgbToHsl(rgb);
268
+ rgb = hslToRgb({ ...hsl, s: clamp01(hsl.s * frac) });
269
+ break;
270
+ }
271
+ case "hueMod": {
272
+ const hsl = rgbToHsl(rgb);
273
+ // Multiplicative scale per LO color.cxx, not additive rotation
274
+ rgb = hslToRgb({ ...hsl, h: ((hsl.h * frac) % 360 + 360) % 360 });
275
+ break;
276
+ }
277
+ case "shade": {
278
+ // §20.1.2.3.31: sRGB space (NOT HSL) — multiply channels toward black
279
+ rgb = {
280
+ r: clamp255(rgb.r * frac),
281
+ g: clamp255(rgb.g * frac),
282
+ b: clamp255(rgb.b * frac),
283
+ };
284
+ break;
285
+ }
286
+ case "tint": {
287
+ // §20.1.2.3.34: sRGB space — C' = C * frac + 255 * (1 - frac)
288
+ const inv = 1 - frac;
289
+ rgb = {
290
+ r: clamp255(rgb.r * frac + 255 * inv),
291
+ g: clamp255(rgb.g * frac + 255 * inv),
292
+ b: clamp255(rgb.b * frac + 255 * inv),
293
+ };
294
+ break;
295
+ }
296
+ case "alpha":
297
+ break; // opacity not reflected in sRGB output
298
+ }
299
+ }
300
+ return formatDmlHex(rgb);
301
+ }
302
+
303
+ function parseHexToRgbDml(hex: string): { r: number; g: number; b: number } {
304
+ const n = Number.parseInt(hex.slice(1), 16);
305
+ return { r: (n >>> 16) & 0xff, g: (n >>> 8) & 0xff, b: n & 0xff };
306
+ }
307
+
308
+ function formatDmlHex(rgb: { r: number; g: number; b: number }): string {
309
+ const h2 = (n: number): string =>
310
+ Math.round(clamp255(n)).toString(16).padStart(2, "0").toUpperCase();
311
+ return `#${h2(rgb.r)}${h2(rgb.g)}${h2(rgb.b)}`;
312
+ }
313
+
314
+ function clamp01(x: number): number {
315
+ return x < 0 ? 0 : x > 1 ? 1 : x;
316
+ }
317
+
318
+ function clamp255(x: number): number {
319
+ return x < 0 ? 0 : x > 255 ? 255 : x;
320
+ }
321
+
177
322
  function rgbToHsl(rgb: { r: number; g: number; b: number }): {
178
323
  h: number;
179
324
  s: number;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * OOXML unit conversions used across the runtime + UI.
3
+ *
4
+ * Hoisted from per-file constants to deduplicate the magic numbers that
5
+ * appear in `pm-schema.ts`, `chart-model-store.ts`, `float-wrap-resolver.ts`,
6
+ * `shape-renderer.ts`, and several other call sites. Renaming an alias
7
+ * here propagates everywhere; renaming a literal would not.
8
+ *
9
+ * Conventions:
10
+ * - All EMU helpers assume 96 dpi (the default OOXML rendering DPI).
11
+ * - Twip helpers assume 1440 twips per inch (OOXML standard).
12
+ * - Rotation values use OOXML's 60 000ths of a degree (`a:xfrm a:rot`).
13
+ * - Picture crop (`a:srcRect`) values use 1/1000 of a percent per edge.
14
+ */
15
+
16
+ /** EMU (English Metric Units) per CSS pixel at 96 dpi. */
17
+ export const EMU_PER_PX = 9525;
18
+
19
+ /** OOXML rotation units per degree (`a:xfrm a:rot` = 60 000ths°). */
20
+ export const ROTATION_UNITS_PER_DEGREE = 60000;
21
+
22
+ /** OOXML picture-crop units per percent (`a:srcRect` uses 1/1000 of a percent). */
23
+ export const SRCRECT_UNITS_PER_PERCENT = 1000;
24
+
25
+ /** Twips per CSS pixel at 96 dpi (1440 twips/inch ÷ 96 px/inch = 15). */
26
+ export const TWIPS_PER_PX = 15;
27
+
28
+ /** Convert EMU → CSS pixels at 96 dpi. */
29
+ export function emuToPx(emu: number): number {
30
+ return emu / EMU_PER_PX;
31
+ }
32
+
33
+ /** Convert OOXML rotation units → CSS degrees. */
34
+ export function rotationToDeg(units: number): number {
35
+ return units / ROTATION_UNITS_PER_DEGREE;
36
+ }
37
+
38
+ /** Convert an `a:srcRect` edge value → CSS percent. */
39
+ export function srcRectToPercent(units: number): number {
40
+ return units / SRCRECT_UNITS_PER_PERCENT;
41
+ }
42
+
43
+ /** Convert twips → CSS pixels at 96 dpi. */
44
+ export function twipsToPx(twips: number): number {
45
+ return twips / TWIPS_PER_PX;
46
+ }
@@ -403,6 +403,14 @@ function deriveActiveObjectFrame(
403
403
  };
404
404
  }
405
405
 
406
+ if (segment.kind === "shape") {
407
+ return {
408
+ kind: "shape",
409
+ anchorPos: segment.from,
410
+ display: segment.anchor?.display === "floating" ? "floating" : "inline",
411
+ };
412
+ }
413
+
406
414
  const objectKind = inferOpaqueObjectKind(segment);
407
415
  if (!objectKind) {
408
416
  return null;
@@ -491,7 +499,7 @@ function normalizeZoomLevel(
491
499
  function findObjectSegmentAtPosition(
492
500
  segments: readonly SurfaceInlineSegment[],
493
501
  position: number,
494
- ): Extract<SurfaceInlineSegment, { kind: "image" | "opaque_inline" }> | null {
502
+ ): Extract<SurfaceInlineSegment, { kind: "image" | "opaque_inline" | "shape" }> | null {
495
503
  for (const segment of segments) {
496
504
  if (!isObjectLikeSegment(segment)) {
497
505
  continue;
@@ -505,10 +513,13 @@ function findObjectSegmentAtPosition(
505
513
 
506
514
  function isObjectLikeSegment(
507
515
  segment: SurfaceInlineSegment,
508
- ): segment is Extract<SurfaceInlineSegment, { kind: "image" | "opaque_inline" }> {
516
+ ): segment is Extract<SurfaceInlineSegment, { kind: "image" | "opaque_inline" | "shape" }> {
509
517
  if (segment.kind === "image") {
510
518
  return true;
511
519
  }
520
+ if (segment.kind === "shape") {
521
+ return true;
522
+ }
512
523
  if (segment.kind !== "opaque_inline") {
513
524
  return false;
514
525
  }