@beyondwork/docx-react-component 1.0.45 → 1.0.46

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/README.md CHANGED
@@ -708,7 +708,21 @@ Fired when a comment is resolved (via `resolveComment` or the sidebar).
708
708
  }
709
709
  ```
710
710
 
711
- There is no separate `comment_removed` event deletions are silent. Query `getComments()` after a `dirty_changed` event if you need to detect deletions.
711
+ For a general-purpose "something about comments changed" signal (covering deletions, reply appends, body edits, reopen, and every other mutation including Yjs-driven mutations that route through the editor's imperative ref), subscribe to `comments_changed`.
712
+
713
+ #### `comments_changed`
714
+
715
+ Fired once per commit whenever the runtime's comment snapshot differs from the previous commit. Use this when you render comments in a sibling component and need to know when to re-fetch via `editorRef.current.getComments()`.
716
+
717
+ ```ts
718
+ {
719
+ type: "comments_changed";
720
+ documentId: string;
721
+ changedCommentIds: string[]; // always non-empty
722
+ }
723
+ ```
724
+
725
+ Covers: `addComment`, `deleteComment`, `resolveComment`, `reopenComment`, `addCommentReply`, `editCommentBody`, and any remote-origin mutation that funnels through the same runtime APIs. Does NOT fire on selection/focus changes.
712
726
 
713
727
  #### `change_accepted`
714
728
 
@@ -944,6 +958,9 @@ function CommentButton({ editorRef }: { editorRef: React.RefObject<WordReviewEdi
944
958
  case "comment_resolved":
945
959
  console.log("comment resolved:", event.commentId);
946
960
  break;
961
+ case "comments_changed":
962
+ console.log("comments changed:", event.changedCommentIds);
963
+ break;
947
964
  case "change_accepted":
948
965
  console.log("change accepted:", event.changeId);
949
966
  break;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@beyondwork/docx-react-component",
3
3
  "publisher": "beyondwork",
4
- "version": "1.0.45",
4
+ "version": "1.0.46",
5
5
  "description": "Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.",
6
6
  "packageManager": "pnpm@10.30.3",
7
7
  "type": "module",
@@ -2416,6 +2416,23 @@ export type WordReviewEditorEvent =
2416
2416
  documentId: string;
2417
2417
  commentId: string;
2418
2418
  }
2419
+ | {
2420
+ type: "comments_changed";
2421
+ documentId: string;
2422
+ /**
2423
+ * Stable ids of the comment threads whose state differs between
2424
+ * the previous and current runtime render snapshot. Includes
2425
+ * additions, deletions, body edits, reply appends, and
2426
+ * resolution-state transitions. Consumers can call
2427
+ * `editorRef.current.getComments()` to obtain the full updated
2428
+ * `CommentSidebarSnapshot`.
2429
+ *
2430
+ * The array is always non-empty when this event fires. When the
2431
+ * snapshot's `comments` reference is unchanged between commits,
2432
+ * no event fires.
2433
+ */
2434
+ changedCommentIds: string[];
2435
+ }
2419
2436
  | {
2420
2437
  type: "change_accepted";
2421
2438
  documentId: string;
@@ -3071,6 +3071,20 @@ export function createDocumentRuntime(
3071
3071
  });
3072
3072
  }
3073
3073
 
3074
+ if (previous.document.review.comments !== next.document.review.comments) {
3075
+ const changedCommentIds = diffCommentMapKeys(
3076
+ previous.document.review.comments,
3077
+ next.document.review.comments,
3078
+ );
3079
+ if (changedCommentIds.length > 0) {
3080
+ emit({
3081
+ type: "comments_changed",
3082
+ documentId: next.documentId,
3083
+ changedCommentIds,
3084
+ });
3085
+ }
3086
+ }
3087
+
3074
3088
  if (transaction.effects.changeAccepted) {
3075
3089
  emit({
3076
3090
  type: "change_accepted",
@@ -3988,6 +4002,27 @@ function createSelectionFromPublicAnchor(
3988
4002
  }
3989
4003
  }
3990
4004
 
4005
+ /**
4006
+ * Collect the stable ids of comment threads whose entry differs
4007
+ * (present in one side but not the other, OR present in both but
4008
+ * referencing a different object — which indicates any mutation to
4009
+ * the thread, since the runtime treats `review.comments` as
4010
+ * immutable per commit). Used by the `comments_changed` event.
4011
+ */
4012
+ function diffCommentMapKeys(
4013
+ previous: CanonicalDocumentEnvelope["review"]["comments"],
4014
+ next: CanonicalDocumentEnvelope["review"]["comments"],
4015
+ ): string[] {
4016
+ const changed = new Set<string>();
4017
+ for (const id of Object.keys(previous)) {
4018
+ if (previous[id] !== next[id]) changed.add(id);
4019
+ }
4020
+ for (const id of Object.keys(next)) {
4021
+ if (previous[id] !== next[id]) changed.add(id);
4022
+ }
4023
+ return Array.from(changed);
4024
+ }
4025
+
3991
4026
  function toPublicCompatibilityReport(
3992
4027
  report: InternalCompatibilityReport,
3993
4028
  ): CompatibilityReport {
@@ -43,6 +43,7 @@ export function describeEventImpact(
43
43
  case "suggestion_updated":
44
44
  case "comment_added":
45
45
  case "comment_resolved":
46
+ case "comments_changed":
46
47
  return {
47
48
  invalidate: [
48
49
  "render",
@@ -45,12 +45,35 @@ export interface DocxFontLoader {
45
45
  refresh(input: FontLoaderInput): void;
46
46
  }
47
47
 
48
+ interface MinimalFontFace {
49
+ load(): Promise<MinimalFontFace>;
50
+ }
51
+
52
+ interface MinimalFontFaceDescriptors {
53
+ weight?: string;
54
+ style?: string;
55
+ }
56
+
57
+ interface MinimalFontFaceConstructor {
58
+ new (
59
+ family: string,
60
+ source: ArrayBuffer | ArrayBufferView | string,
61
+ descriptors?: MinimalFontFaceDescriptors,
62
+ ): MinimalFontFace;
63
+ }
64
+
65
+ interface MinimalFontFaceSet {
66
+ add(face: MinimalFontFace): void;
67
+ check(font: string): boolean;
68
+ ready: Promise<MinimalFontFaceSet>;
69
+ }
70
+
48
71
  export function createDocxFontLoader(initial: FontLoaderInput): DocxFontLoader {
72
+ const globalDocument = (globalThis as unknown as { document?: { fonts?: unknown } }).document;
49
73
  const supported =
50
- typeof document !== "undefined" &&
74
+ globalDocument !== undefined &&
51
75
  typeof (globalThis as { FontFace?: unknown }).FontFace !== "undefined" &&
52
- // Guard against jsdom which exposes FontFace but not document.fonts
53
- Boolean((document as Document & { fonts?: FontFaceSet }).fonts);
76
+ Boolean(globalDocument.fonts);
54
77
 
55
78
  let current: FontLoaderInput = initial;
56
79
  let readyPromise: Promise<void>;
@@ -58,7 +81,7 @@ export function createDocxFontLoader(initial: FontLoaderInput): DocxFontLoader {
58
81
 
59
82
  function run(input: FontLoaderInput): Promise<void> {
60
83
  if (!supported) return Promise.resolve();
61
- const fontSet = (document as Document & { fonts?: FontFaceSet }).fonts;
84
+ const fontSet = globalDocument?.fonts as MinimalFontFaceSet | undefined;
62
85
  if (!fontSet) return Promise.resolve();
63
86
 
64
87
  const pending: Array<Promise<unknown>> = [];
@@ -70,10 +93,8 @@ export function createDocxFontLoader(initial: FontLoaderInput): DocxFontLoader {
70
93
 
71
94
  for (const [descriptor, data] of variantsOf(variants)) {
72
95
  try {
73
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
- const FontFaceCtor = (globalThis as any).FontFace as {
75
- new (family: string, source: BufferSource, descriptors?: FontFaceDescriptors): FontFace;
76
- };
96
+ const FontFaceCtor = (globalThis as { FontFace?: MinimalFontFaceConstructor }).FontFace;
97
+ if (!FontFaceCtor) continue;
77
98
  const face = new FontFaceCtor(family, data, descriptor);
78
99
  pending.push(
79
100
  face.load().then((loaded) => {
@@ -88,8 +109,6 @@ export function createDocxFontLoader(initial: FontLoaderInput): DocxFontLoader {
88
109
  }
89
110
  }
90
111
 
91
- // Mark declared families as registered if the browser already resolves
92
- // them (e.g. system fonts like Calibri, Arial).
93
112
  for (const family of input.families) {
94
113
  try {
95
114
  const probe = `12px "${family.replace(/"/g, "'")}", serif`;
@@ -127,7 +146,7 @@ export function createDocxFontLoader(initial: FontLoaderInput): DocxFontLoader {
127
146
 
128
147
  function* variantsOf(
129
148
  variants: EmbeddedFontBytes,
130
- ): IterableIterator<[FontFaceDescriptors, ArrayBuffer]> {
149
+ ): IterableIterator<[MinimalFontFaceDescriptors, ArrayBuffer]> {
131
150
  if (variants.regular) {
132
151
  yield [{ weight: "400", style: "normal" }, variants.regular];
133
152
  }