@blocksdiy/react-common 1.71.0 → 1.71.1

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 (40) hide show
  1. package/dist/comments/anchor.d.ts +23 -1
  2. package/dist/comments/anchor.d.ts.map +1 -1
  3. package/dist/comments/anchor.js +74 -7
  4. package/dist/comments/comment-mode.d.ts +22 -4
  5. package/dist/comments/comment-mode.d.ts.map +1 -1
  6. package/dist/comments/comment-mode.js +22 -5
  7. package/dist/comments/index.d.ts +9 -3
  8. package/dist/comments/index.d.ts.map +1 -1
  9. package/dist/comments/index.js +9 -3
  10. package/dist/comments/menu.d.ts +33 -0
  11. package/dist/comments/menu.d.ts.map +1 -0
  12. package/dist/comments/menu.js +47 -0
  13. package/dist/comments/open-comment.d.ts +28 -0
  14. package/dist/comments/open-comment.d.ts.map +1 -0
  15. package/dist/comments/open-comment.js +9 -0
  16. package/dist/comments/page-pick.d.ts.map +1 -1
  17. package/dist/comments/page-pick.js +23 -7
  18. package/dist/comments/pin-view.d.ts +107 -0
  19. package/dist/comments/pin-view.d.ts.map +1 -0
  20. package/dist/comments/pin-view.js +248 -0
  21. package/dist/comments/pins.d.ts +66 -12
  22. package/dist/comments/pins.d.ts.map +1 -1
  23. package/dist/comments/pins.js +132 -39
  24. package/dist/comments/text-pick.d.ts +11 -1
  25. package/dist/comments/text-pick.d.ts.map +1 -1
  26. package/dist/comments/text-pick.js +143 -1
  27. package/dist/comments/types.d.ts +4 -1
  28. package/dist/comments/types.d.ts.map +1 -1
  29. package/dist/comments/use-comment-menu.d.ts +69 -0
  30. package/dist/comments/use-comment-menu.d.ts.map +1 -0
  31. package/dist/comments/use-comment-menu.js +140 -0
  32. package/dist/comments/use-held.d.ts +3 -0
  33. package/dist/comments/use-held.d.ts.map +1 -0
  34. package/dist/comments/use-held.js +16 -0
  35. package/dist/comments/use-thread-scroll.d.ts +23 -0
  36. package/dist/comments/use-thread-scroll.d.ts.map +1 -0
  37. package/dist/comments/use-thread-scroll.js +89 -0
  38. package/dist/iframe-communication/types.d.ts +3 -2
  39. package/dist/iframe-communication/types.d.ts.map +1 -1
  40. package/package.json +1 -1
@@ -66,9 +66,31 @@ export declare const anchorQuote: (anchor: CommentAnchor) => string | undefined;
66
66
  export declare const componentNameOf: (devName: string | null | undefined) => string | undefined;
67
67
  /** The component an anchor's element is (`componentNameOf`), when it has a name. */
68
68
  export declare const anchorComponentName: (anchor: CommentAnchor) => string | undefined;
69
+ /** The kinds of text-less element a thread can name in plain words (the words are the UI's). */
70
+ export type AnchorElementKind = "image" | "icon" | "video" | "field" | "button" | "link";
71
+ /** What a thread is about, as its header names it; see `anchorSubject`. */
72
+ export type AnchorSubject = {
73
+ kind: "text";
74
+ text: string;
75
+ } | {
76
+ kind: "component";
77
+ name: string;
78
+ } | {
79
+ kind: "element";
80
+ element: AnchorElementKind;
81
+ } | {
82
+ kind: "page";
83
+ routePath: string;
84
+ };
85
+ /**
86
+ * What an anchor is about, never a tag: its words (the highlighted text, else the element's text,
87
+ * however long: the UI cuts it), else its component's name, else the kind of a common text-less
88
+ * element (an image, an icon, a video, a field, a button, a link), else its page.
89
+ */
90
+ export declare const anchorSubject: (anchor: CommentAnchor) => AnchorSubject;
69
91
  /**
70
92
  * A short name for what an anchor points at, never a tag: its quote, else its element's text,
71
- * clipped, else its component's name, else its page.
93
+ * clipped, else its component's name, else its page (`anchorSubject`, an element's kind aside).
72
94
  */
73
95
  export declare const anchorLabel: (anchor: CommentAnchor) => string;
74
96
  //# sourceMappingURL=anchor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"anchor.d.ts","sourceRoot":"","sources":["../../src/comments/anchor.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,aAAa,EAElB,KAAK,iBAAiB,EACvB,MAAM,SAAS,CAAC;AAEjB;;;;;GAKG;AAEH,6FAA6F;AAC7F,eAAO,MAAM,cAAc,qBAAqB,CAAC;AACjD,gFAAgF;AAChF,eAAO,MAAM,eAAe,sBAAsB,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,qBAAqB,4BAA4B,CAAC;AAQ/D,2DAA2D;AAC3D,eAAO,MAAM,MAAM,GAAI,SAAS,WAAW,WAA8D,CAAC;AAO1G;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,iBAAiB,GAAG,SAMzF,CAAC;AAyBF,6DAA6D;AAC7D,eAAO,MAAM,aAAa,GAAI,MAAM,IAAI,GAAG,IAAI,KAAG,OAGjD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,IAAI,GAAG,IAAI,KAAG,WAAW,GAAG,IAS5D,CAAC;AAmCF;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,SAAS;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,KAAG,aAgDH,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;AAE9D,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,+EAA+E;IAC/E,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAsED,iGAAiG;AACjG,eAAO,MAAM,qBAAqB,GAAI,QAAQ,aAAa,EAAE,OAAM,UAAqB,KAAG,OAAO,GAAG,IACjC,CAAC;AAErE;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,aAAa,KAAG,cAcrD,CAAC;AAEF,8DAA8D;AAC9D,eAAO,MAAM,SAAS,GAAI,QAAQ,aAAa,EAAE,SAAS,WAAW,KAAG,cAavE,CAAC;AAEF,+FAA+F;AAC/F,eAAO,MAAM,WAAW,GAAI,QAAQ,aAAa,KAAG,MAAM,GAAG,SAM5D,CAAC;AAIF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,MAAM,GAAG,SACrB,CAAC;AAE1D,oFAAoF;AACpF,eAAO,MAAM,mBAAmB,GAAI,QAAQ,aAAa,KAAG,MAAM,GAAG,SAC3B,CAAC;AAE3C;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,QAAQ,aAAa,KAAG,MAMnD,CAAC"}
1
+ {"version":3,"file":"anchor.d.ts","sourceRoot":"","sources":["../../src/comments/anchor.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,aAAa,EAElB,KAAK,iBAAiB,EACvB,MAAM,SAAS,CAAC;AAEjB;;;;;GAKG;AAEH,6FAA6F;AAC7F,eAAO,MAAM,cAAc,qBAAqB,CAAC;AACjD,gFAAgF;AAChF,eAAO,MAAM,eAAe,sBAAsB,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,qBAAqB,4BAA4B,CAAC;AAQ/D,2DAA2D;AAC3D,eAAO,MAAM,MAAM,GAAI,SAAS,WAAW,WAA8D,CAAC;AAO1G;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,iBAAiB,GAAG,SAMzF,CAAC;AAyBF,6DAA6D;AAC7D,eAAO,MAAM,aAAa,GAAI,MAAM,IAAI,GAAG,IAAI,KAAG,OAGjD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,IAAI,GAAG,IAAI,KAAG,WAAW,GAAG,IAS5D,CAAC;AAmCF;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,SAAS;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,KAAG,aAgDH,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;AAE9D,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,+EAA+E;IAC/E,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA6ED,iGAAiG;AACjG,eAAO,MAAM,qBAAqB,GAAI,QAAQ,aAAa,EAAE,OAAM,UAAqB,KAAG,OAAO,GAAG,IACjC,CAAC;AAErE;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,aAAa,KAAG,cA2CrD,CAAC;AAEF,8DAA8D;AAC9D,eAAO,MAAM,SAAS,GAAI,QAAQ,aAAa,EAAE,SAAS,WAAW,KAAG,cAavE,CAAC;AAEF,+FAA+F;AAC/F,eAAO,MAAM,WAAW,GAAI,QAAQ,aAAa,KAAG,MAAM,GAAG,SAM5D,CAAC;AAIF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,MAAM,GAAG,SACrB,CAAC;AAE1D,oFAAoF;AACpF,eAAO,MAAM,mBAAmB,GAAI,QAAQ,aAAa,KAAG,MAAM,GAAG,SAC3B,CAAC;AAE3C,gGAAgG;AAChG,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAgBzF,2EAA2E;AAC3E,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,iBAAiB,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,aAAa,KAAG,aAYrD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,QAAQ,aAAa,KAAG,MAOnD,CAAC"}
@@ -214,6 +214,12 @@ const findTextRange = (element, text) => {
214
214
  }
215
215
  return null;
216
216
  };
217
+ /** Whether `element` is inside the row's tag of a column other than `field` (untagged: unknown, so no). */
218
+ const showsAnotherField = (element, row, field) => {
219
+ const tagged = element.closest(`[${FIELD_ATTRIBUTE}]`);
220
+ const shown = tagged && row.contains(tagged) ? tagged.getAttribute(FIELD_ATTRIBUTE) : null;
221
+ return !!shown && shown.slice(0, COMMENT_ANCHOR_FIELD_MAX_LENGTH) !== field;
222
+ };
217
223
  /** The row's element an anchor names, or null when it has none or the row is not on the page. */
218
224
  export const findAnchorItemElement = (anchor, root = document) => anchor.item ? root.querySelector(itemSelector(anchor.item)) : null;
219
225
  /**
@@ -229,11 +235,40 @@ export const resolveAnchor = (anchor) => {
229
235
  }
230
236
  root = itemElement;
231
237
  }
232
- const element = findElement(anchor, root) ?? (anchor.item ? root : null);
233
- if (!element) {
238
+ const found = findElement(anchor, root);
239
+ // Another view of the row can have an element from the same source at the same place that
240
+ // shows another column: not the one the comment was on.
241
+ const element = found && anchor.item?.field && showsAnotherField(found, root, anchor.item.field) ? null : found;
242
+ if (element) {
243
+ return checkText(anchor, element);
244
+ }
245
+ if (!anchor.item) {
234
246
  return MISSING;
235
247
  }
236
- return checkText(anchor, element);
248
+ // The row is on the page without the element the comment was on: the row's own element, or
249
+ // another view of the same rows (a board's card, a list's line). The thread stays with its row.
250
+ const row = root;
251
+ const inner = anchor.element;
252
+ if (inner?.ordinal === undefined && !inner?.domPath) {
253
+ return checkText(anchor, row);
254
+ }
255
+ // On the column it was about, when this view tags it; else on the part of the row that says
256
+ // what the comment was about (the innermost such element); else on the row, out of date.
257
+ const field = anchor.item.field
258
+ ? row.querySelector(`[${FIELD_ATTRIBUTE}="${escape(anchor.item.field)}"]`)
259
+ : null;
260
+ if (field) {
261
+ return checkText(anchor, field);
262
+ }
263
+ const snapshot = inner?.textSnapshot;
264
+ if (!anchor.text && snapshot) {
265
+ const saying = Array.from(row.querySelectorAll("*")).filter((part) => textOf(part) === snapshot);
266
+ const innermost = saying[saying.length - 1];
267
+ if (innermost) {
268
+ return { state: "anchored", element: innermost, range: null };
269
+ }
270
+ }
271
+ return checkText(anchor, row);
237
272
  };
238
273
  /** Whether `element` still says what the anchor was about. */
239
274
  export const checkText = (anchor, element) => {
@@ -266,14 +301,46 @@ const LABEL_MAX = 40;
266
301
  export const componentNameOf = (devName) => devName && /^[A-Z]/.test(devName) ? devName : undefined;
267
302
  /** The component an anchor's element is (`componentNameOf`), when it has a name. */
268
303
  export const anchorComponentName = (anchor) => componentNameOf(anchor.element?.devName);
304
+ /** Tags (HTML ones stored in capitals, SVG ones as written, so compared in capitals) by kind. */
305
+ const ELEMENT_KINDS = new Map([
306
+ ["IMG", "image"],
307
+ ["PICTURE", "image"],
308
+ ["CANVAS", "image"],
309
+ ["SVG", "icon"],
310
+ ["VIDEO", "video"],
311
+ ["INPUT", "field"],
312
+ ["TEXTAREA", "field"],
313
+ ["SELECT", "field"],
314
+ ["BUTTON", "button"],
315
+ ["A", "link"],
316
+ ]);
269
317
  /**
270
- * A short name for what an anchor points at, never a tag: its quote, else its element's text,
271
- * clipped, else its component's name, else its page.
318
+ * What an anchor is about, never a tag: its words (the highlighted text, else the element's text,
319
+ * however long: the UI cuts it), else its component's name, else the kind of a common text-less
320
+ * element (an image, an icon, a video, a field, a button, a link), else its page.
272
321
  */
273
- export const anchorLabel = (anchor) => {
322
+ export const anchorSubject = (anchor) => {
274
323
  const text = anchorQuote(anchor) ?? anchor.element?.textSnapshot;
275
324
  if (text) {
325
+ return { kind: "text", text };
326
+ }
327
+ const name = anchorComponentName(anchor);
328
+ if (name) {
329
+ return { kind: "component", name };
330
+ }
331
+ const tag = anchor.element?.tagName?.toUpperCase();
332
+ const element = tag ? ELEMENT_KINDS.get(tag) : undefined;
333
+ return element ? { kind: "element", element } : { kind: "page", routePath: anchor.routePath };
334
+ };
335
+ /**
336
+ * A short name for what an anchor points at, never a tag: its quote, else its element's text,
337
+ * clipped, else its component's name, else its page (`anchorSubject`, an element's kind aside).
338
+ */
339
+ export const anchorLabel = (anchor) => {
340
+ const subject = anchorSubject(anchor);
341
+ if (subject.kind === "text") {
342
+ const { text } = subject;
276
343
  return text.length > LABEL_MAX ? `${text.slice(0, LABEL_MAX - 1)}…` : text;
277
344
  }
278
- return anchorComponentName(anchor) ?? anchor.routePath;
345
+ return subject.kind === "component" ? subject.name : anchor.routePath;
279
346
  };
@@ -11,6 +11,7 @@
11
11
  *
12
12
  * The app can also open one thread itself (a comment's link in the agent chat): `openCommentThread`
13
13
  * asks the host for comment mode, as its Comment button would, and the surface goes to the thread.
14
+ * Only where the platform offers comments (`CommentOptions.isOffered`): not in its builder.
14
15
  */
15
16
  /**
16
17
  * What the app's comments show, as the platform's Comment button sets it. `mentionableAgents`: the
@@ -21,6 +22,13 @@ export interface CommentOptions {
21
22
  /** Pins of resolved threads and outdated ones (their text changed) show too. */
22
23
  showResolved: boolean;
23
24
  mentionableAgents?: readonly string[];
25
+ /**
26
+ * Whether the platform offers comments here (its `app-comments` flag on, the app having the
27
+ * comments component, and not in its builder): when `false` no thread opens from inside the app,
28
+ * so its links back to one hide. Absent means offered: a platform from before the field sends
29
+ * none, and an app-render from before it ignores it.
30
+ */
31
+ isOffered?: boolean;
24
32
  }
25
33
  export declare const DEFAULT_COMMENT_OPTIONS: CommentOptions;
26
34
  /** A thread to open in the app: on its page, scrolled to, open. */
@@ -46,14 +54,17 @@ export interface CommentModeStore {
46
54
  exit(): void;
47
55
  /** The host: hears when the app turned comment mode off itself. */
48
56
  onExit(listener: () => void): () => void;
49
- /** The host: what the comments show (resolved and outdated pins, the agents to mention). */
57
+ /** The host: what the comments show (resolved and outdated pins, agents to mention) and if they're offered. */
50
58
  setOptions(options: CommentOptions): void;
51
59
  options(): CommentOptions;
52
60
  /** The app's surface: opens the threads asked for while the returned function hasn't been called. */
53
61
  handleOpenThread(handler: (request: CommentThreadOpenRequest) => void): () => void;
54
62
  /** The host: hears the app ask for comment mode (to show a thread it opens). */
55
63
  onRequestOn(listener: () => void): () => void;
56
- /** Whether a thread can be opened from inside the app: a host listens and a surface opens it. */
64
+ /**
65
+ * Whether a thread can be opened from inside the app: the host offers comments here (the options'
66
+ * `isOffered` isn't `false`), listens, and a surface opens it.
67
+ */
57
68
  canOpenThread(): boolean;
58
69
  /** The app: asks the host for comment mode and the surface for the thread. False when it can't here. */
59
70
  openThread(request: CommentThreadOpenRequest): boolean;
@@ -65,17 +76,24 @@ export declare const commentMode: CommentModeStore;
65
76
  export declare const setCommentMode: (isOn: boolean) => Promise<boolean>;
66
77
  /** The host: hears when the app turned comment mode off itself. Returns the unsubscribe. */
67
78
  export declare const onCommentModeExit: (listener: () => void) => (() => void);
68
- /** The host: what the app's comments show (the Comment button's options). */
79
+ /** The host: what the app's comments show (the Comment button's options), and whether they're offered. */
69
80
  export declare const setCommentOptions: (next: CommentOptions) => void;
70
81
  /** The host: hears the app ask for comment mode. Returns the unsubscribe. */
71
82
  export declare const onCommentModeRequest: (listener: () => void) => (() => void);
72
83
  /**
73
84
  * The app (the agent chat's comment message): opens a thread — its page, comment mode on, the
74
- * thread open. False when it can't here: outside the platform, or without the comments component.
85
+ * thread open. False when it can't here: outside the platform, where it doesn't offer comments (its
86
+ * builder), or without the comments component.
75
87
  */
76
88
  export declare const openCommentThread: (request: CommentThreadOpenRequest, store?: CommentModeStore) => boolean;
77
89
  /** Whether `openCommentThread` can open a thread here: the link back to it shows only then. */
78
90
  export declare const canOpenCommentThread: (store?: CommentModeStore) => boolean;
91
+ /**
92
+ * `canOpenCommentThread`, kept current: the link back to a thread re-renders when the host stops
93
+ * offering comments (its builder) or offers them again, rather than staying until something else
94
+ * renders it.
95
+ */
96
+ export declare const useCanOpenCommentThread: (store?: CommentModeStore) => boolean;
79
97
  export interface UseCommentsSurfaceOptions {
80
98
  /**
81
99
  * Opens a thread `openCommentThread` asked for: navigate to its page with the thread's link
@@ -1 +1 @@
1
- {"version":3,"file":"comment-mode.d.ts","sourceRoot":"","sources":["../../src/comments/comment-mode.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"comment-mode.d.ts","sourceRoot":"","sources":["../../src/comments/comment-mode.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,8 @@
1
1
  import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
2
2
  export const DEFAULT_COMMENT_OPTIONS = { showResolved: false };
3
- const sameOptions = (a, b) => a.showResolved === b.showResolved && a.mentionableAgents?.join("\n") === b.mentionableAgents?.join("\n");
3
+ const sameOptions = (a, b) => a.showResolved === b.showResolved &&
4
+ a.mentionableAgents?.join("\n") === b.mentionableAgents?.join("\n") &&
5
+ a.isOffered === b.isOffered;
4
6
  /** How long turning comment mode on waits for a comments surface to mount. */
5
7
  export const COMMENT_MODE_CLAIM_TIMEOUT_MS = 5000;
6
8
  const defaultTimers = {
@@ -17,7 +19,7 @@ export const createCommentModeStore = (timers = defaultTimers) => {
17
19
  let openThreadHandler = null;
18
20
  const requestOnListeners = new Set();
19
21
  const emit = () => listeners.forEach((listener) => listener());
20
- const canOpenThread = () => requestOnListeners.size > 0 && openThreadHandler !== null;
22
+ const canOpenThread = () => options.isOffered !== false && requestOnListeners.size > 0 && openThreadHandler !== null;
21
23
  const settlePending = (claimed) => {
22
24
  if (pending) {
23
25
  timers.clearTimeout(pending.timer);
@@ -99,17 +101,25 @@ export const createCommentModeStore = (timers = defaultTimers) => {
99
101
  }
100
102
  },
101
103
  options: () => options,
104
+ // Both decide `canOpenThread`: its subscribers hear each change.
102
105
  handleOpenThread(handler) {
103
106
  openThreadHandler = handler;
107
+ emit();
104
108
  return () => {
105
109
  if (openThreadHandler === handler) {
106
110
  openThreadHandler = null;
111
+ emit();
107
112
  }
108
113
  };
109
114
  },
110
115
  onRequestOn(listener) {
111
116
  requestOnListeners.add(listener);
112
- return () => requestOnListeners.delete(listener);
117
+ emit();
118
+ return () => {
119
+ if (requestOnListeners.delete(listener)) {
120
+ emit();
121
+ }
122
+ };
113
123
  },
114
124
  canOpenThread,
115
125
  openThread(request) {
@@ -128,17 +138,24 @@ export const commentMode = createCommentModeStore();
128
138
  export const setCommentMode = (isOn) => commentMode.set(isOn);
129
139
  /** The host: hears when the app turned comment mode off itself. Returns the unsubscribe. */
130
140
  export const onCommentModeExit = (listener) => commentMode.onExit(listener);
131
- /** The host: what the app's comments show (the Comment button's options). */
141
+ /** The host: what the app's comments show (the Comment button's options), and whether they're offered. */
132
142
  export const setCommentOptions = (next) => commentMode.setOptions(next);
133
143
  /** The host: hears the app ask for comment mode. Returns the unsubscribe. */
134
144
  export const onCommentModeRequest = (listener) => commentMode.onRequestOn(listener);
135
145
  /**
136
146
  * The app (the agent chat's comment message): opens a thread — its page, comment mode on, the
137
- * thread open. False when it can't here: outside the platform, or without the comments component.
147
+ * thread open. False when it can't here: outside the platform, where it doesn't offer comments (its
148
+ * builder), or without the comments component.
138
149
  */
139
150
  export const openCommentThread = (request, store = commentMode) => store.openThread(request);
140
151
  /** Whether `openCommentThread` can open a thread here: the link back to it shows only then. */
141
152
  export const canOpenCommentThread = (store = commentMode) => store.canOpenThread();
153
+ /**
154
+ * `canOpenCommentThread`, kept current: the link back to a thread re-renders when the host stops
155
+ * offering comments (its builder) or offers them again, rather than staying until something else
156
+ * renders it.
157
+ */
158
+ export const useCanOpenCommentThread = (store = commentMode) => useSyncExternalStore(store.subscribe, store.canOpenThread, () => false);
142
159
  /**
143
160
  * The app's comments surface: registers itself while mounted and reads the mode. `exit` turns it off
144
161
  * from inside the app and tells the host. It opens the threads the app asks for (a comment's link in
@@ -1,11 +1,17 @@
1
1
  export * from "./types.js";
2
- export { ITEM_ATTRIBUTE, FIELD_ATTRIBUTE, PICK_IGNORE_ATTRIBUTE, anchorQuote, anchorComponentName, type AnchorState, } from "./anchor.js";
2
+ export { ITEM_ATTRIBUTE, FIELD_ATTRIBUTE, PICK_IGNORE_ATTRIBUTE, anchorQuote, anchorComponentName, anchorSubject, type AnchorElementKind, type AnchorState, type AnchorSubject, } from "./anchor.js";
3
3
  export { initials, type Mention, type MentionNames } from "./mentions.js";
4
4
  export { toRect, type Rect } from "./layout.js";
5
- export { COMMENTS_LAYER_ATTRIBUTE, pinPoint, groupPins, useCommentPins, type PinPoint, type PinGroup, type ThreadLayout, } from "./pins.js";
5
+ export { COMMENTS_LAYER_ATTRIBUTE, pinPoint, pickPlacement, groupPins, useCommentPins, type PinPoint, type PinGroup, type ThreadLayout, } from "./pins.js";
6
+ export { participantKey, pinParticipants, pinWidth, panelPosition, cornerPoint, panelSide, type PanelSide, type PanelPositionOptions, type PlacementViewport, } from "./pin-view.js";
7
+ export { menuKeyTarget, type MenuPosition } from "./menu.js";
8
+ export { COMMENT_MENU_ATTRIBUTE, useCommentMenu, type CommentMenuState, type UseCommentMenuOptions, } from "./use-comment-menu.js";
9
+ export { isOnScreen, openCommentResponse, type OpenCommentResponse } from "./open-comment.js";
10
+ export { useHeld } from "./use-held.js";
11
+ export { useThreadScroll } from "./use-thread-scroll.js";
6
12
  export { usePagePick, type PagePick, type PagePickHover, type UsePagePickOptions } from "./page-pick.js";
7
13
  export { createCommentsApi, type CommentsApi, type CreateCommentsApiOptions } from "./api.js";
8
- export { setCommentMode, onCommentModeExit, setCommentOptions, onCommentModeRequest, openCommentThread, canOpenCommentThread, useCommentsSurface, type CommentThreadOpenRequest, type CommentOptions, type UseCommentsSurfaceOptions, } from "./comment-mode.js";
14
+ export { setCommentMode, onCommentModeExit, setCommentOptions, onCommentModeRequest, openCommentThread, canOpenCommentThread, useCanOpenCommentThread, useCommentsSurface, type CommentThreadOpenRequest, type CommentOptions, type UseCommentsSurfaceOptions, } from "./comment-mode.js";
9
15
  export { commentPingsFor, useCommentThreads, type CommentThreadsStore, type CommentThreadsSnapshot, type CommentThreadLoadState, type SubscribeToCommentPings, } from "./threads-store.js";
10
16
  export { useCommentAgents, useRegisterCommentAgents, type CommentAgent, type CommentAgentsSnapshot, type OpenCommentAgentThread, } from "./comment-agents.js";
11
17
  export { pagePickComposerMeta, parsePagePickPart, pagePickOf } from "./pick-message.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/comments/index.ts"],"names":[],"mappings":"AAMA,cAAc,SAAS,CAAC;AACxB,OAAO,EACL,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,WAAW,EACX,mBAAmB,EACnB,KAAK,WAAW,GACjB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,QAAQ,EAAE,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EACL,wBAAwB,EACxB,QAAQ,EACR,SAAS,EACT,cAAc,EACd,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,YAAY,GAClB,MAAM,QAAQ,CAAC;AAChB,OAAO,EAAE,WAAW,EAAE,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtG,OAAO,EAAE,iBAAiB,EAAE,KAAK,WAAW,EAAE,KAAK,wBAAwB,EAAE,MAAM,OAAO,CAAC;AAC3F,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,KAAK,wBAAwB,EAC7B,KAAK,cAAc,EACnB,KAAK,yBAAyB,GAC/B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,GAC7B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,gBAAgB,EAChB,wBAAwB,EACxB,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACrF,OAAO,EACL,kBAAkB,EAClB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,yBAAyB,GAC/B,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/comments/index.ts"],"names":[],"mappings":"AAMA,cAAc,SAAS,CAAC;AACxB,OAAO,EACL,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,WAAW,EACX,mBAAmB,EACnB,aAAa,EACb,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,aAAa,GACnB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,QAAQ,EAAE,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EACL,wBAAwB,EACxB,QAAQ,EACR,aAAa,EACb,SAAS,EACT,cAAc,EACd,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,YAAY,GAClB,MAAM,QAAQ,CAAC;AAChB,OAAO,EACL,cAAc,EACd,eAAe,EACf,QAAQ,EACR,aAAa,EACb,WAAW,EACX,SAAS,EACT,KAAK,SAAS,EACd,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,GACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtG,OAAO,EAAE,iBAAiB,EAAE,KAAK,WAAW,EAAE,KAAK,wBAAwB,EAAE,MAAM,OAAO,CAAC;AAC3F,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,KAAK,wBAAwB,EAC7B,KAAK,cAAc,EACnB,KAAK,yBAAyB,GAC/B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,GAC7B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,gBAAgB,EAChB,wBAAwB,EACxB,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACrF,OAAO,EACL,kBAAkB,EAClB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,yBAAyB,GAC/B,MAAM,YAAY,CAAC"}
@@ -4,13 +4,19 @@
4
4
  // never remove, rename or change the meaning of one. It exports what the registry items and
5
5
  // app-render use (and the types their signatures show); add an export when an item needs one.
6
6
  export * from "./types.js";
7
- export { ITEM_ATTRIBUTE, FIELD_ATTRIBUTE, PICK_IGNORE_ATTRIBUTE, anchorQuote, anchorComponentName, } from "./anchor.js";
7
+ export { ITEM_ATTRIBUTE, FIELD_ATTRIBUTE, PICK_IGNORE_ATTRIBUTE, anchorQuote, anchorComponentName, anchorSubject, } from "./anchor.js";
8
8
  export { initials } from "./mentions.js";
9
9
  export { toRect } from "./layout.js";
10
- export { COMMENTS_LAYER_ATTRIBUTE, pinPoint, groupPins, useCommentPins, } from "./pins.js";
10
+ export { COMMENTS_LAYER_ATTRIBUTE, pinPoint, pickPlacement, groupPins, useCommentPins, } from "./pins.js";
11
+ export { participantKey, pinParticipants, pinWidth, panelPosition, cornerPoint, panelSide, } from "./pin-view.js";
12
+ export { menuKeyTarget } from "./menu.js";
13
+ export { COMMENT_MENU_ATTRIBUTE, useCommentMenu, } from "./use-comment-menu.js";
14
+ export { isOnScreen, openCommentResponse } from "./open-comment.js";
15
+ export { useHeld } from "./use-held.js";
16
+ export { useThreadScroll } from "./use-thread-scroll.js";
11
17
  export { usePagePick } from "./page-pick.js";
12
18
  export { createCommentsApi } from "./api.js";
13
- export { setCommentMode, onCommentModeExit, setCommentOptions, onCommentModeRequest, openCommentThread, canOpenCommentThread, useCommentsSurface, } from "./comment-mode.js";
19
+ export { setCommentMode, onCommentModeExit, setCommentOptions, onCommentModeRequest, openCommentThread, canOpenCommentThread, useCanOpenCommentThread, useCommentsSurface, } from "./comment-mode.js";
14
20
  export { commentPingsFor, useCommentThreads, } from "./threads-store.js";
15
21
  export { useCommentAgents, useRegisterCommentAgents, } from "./comment-agents.js";
16
22
  export { pagePickComposerMeta, parsePagePickPart, pagePickOf } from "./pick-message.js";
@@ -0,0 +1,33 @@
1
+ import type { Rect } from "./layout.js";
2
+ /**
3
+ * The item a key moves focus to in a menu of `count` items whose item `index` has focus (-1: none):
4
+ * ↓ and ↑ the next and the previous, wrapping round, Home and End the first and the last; null for
5
+ * any other key, which the menu leaves alone.
6
+ */
7
+ export declare const menuKeyTarget: (key: string, index: number, count: number) => number | null;
8
+ /** Where a menu fixed in the window goes: one of `left`/`right`, one of `top`/`bottom`, and its tallest. */
9
+ export interface MenuPosition {
10
+ side: "bottom" | "top";
11
+ /** From the window's top (`bottom`) or its bottom (`top`). */
12
+ top?: number;
13
+ bottom?: number;
14
+ left?: number;
15
+ right?: number;
16
+ /** The room it has on that side: taller than this, it scrolls. */
17
+ maxHeight: number;
18
+ }
19
+ /**
20
+ * Where a menu `height` tall opens beside its trigger (in window coordinates): below it when it
21
+ * fits there, else above when it fits there or there is more room above (a composer at the bottom
22
+ * of the window, a sheet on a phone), else below; never taller than the room on its side. Its start
23
+ * edge lines up with the trigger's start (a picker) or its end edge with the trigger's end (a ⋯),
24
+ * as the page reads (`isRtl`), from the window's own edges.
25
+ */
26
+ export declare const menuPosition: (trigger: Rect, { height, align, isRtl, viewportWidth, viewportHeight, }: {
27
+ height: number;
28
+ align: "start" | "end";
29
+ isRtl: boolean;
30
+ viewportWidth: number;
31
+ viewportHeight: number;
32
+ }) => MenuPosition;
33
+ //# sourceMappingURL=menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../src/comments/menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAQrC;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,KAAK,MAAM,EAAE,OAAO,MAAM,EAAE,OAAO,MAAM,KAAG,MAAM,GAAG,IAgBlF,CAAC;AAMF,4GAA4G;AAC5G,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC;IACvB,8DAA8D;IAC9D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GACvB,SAAS,IAAI,EACb,0DAMG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,KAC3G,YAUF,CAAC"}
@@ -0,0 +1,47 @@
1
+ /*
2
+ * What the comments' own menus share: which item a key moves to (a comment's ⋯, the composer's
3
+ * agent picker, a grouped pin's chooser), and where a menu button's menu opens beside its trigger
4
+ * (the ⋯ and the picker). Pure: the caller passes the window. `useCommentMenu` puts them to work.
5
+ */
6
+ /**
7
+ * The item a key moves focus to in a menu of `count` items whose item `index` has focus (-1: none):
8
+ * ↓ and ↑ the next and the previous, wrapping round, Home and End the first and the last; null for
9
+ * any other key, which the menu leaves alone.
10
+ */
11
+ export const menuKeyTarget = (key, index, count) => {
12
+ if (count <= 0) {
13
+ return null;
14
+ }
15
+ switch (key) {
16
+ case "ArrowDown":
17
+ return (index + 1) % count;
18
+ case "ArrowUp":
19
+ return index < 0 ? count - 1 : (index - 1 + count) % count;
20
+ case "Home":
21
+ return 0;
22
+ case "End":
23
+ return count - 1;
24
+ default:
25
+ return null;
26
+ }
27
+ };
28
+ /** The gap between a menu and its trigger, and how far a menu keeps from the window's edge. */
29
+ const MENU_GAP = 4;
30
+ const MENU_EDGE = 8;
31
+ /**
32
+ * Where a menu `height` tall opens beside its trigger (in window coordinates): below it when it
33
+ * fits there, else above when it fits there or there is more room above (a composer at the bottom
34
+ * of the window, a sheet on a phone), else below; never taller than the room on its side. Its start
35
+ * edge lines up with the trigger's start (a picker) or its end edge with the trigger's end (a ⋯),
36
+ * as the page reads (`isRtl`), from the window's own edges.
37
+ */
38
+ export const menuPosition = (trigger, { height, align, isRtl, viewportWidth, viewportHeight, }) => {
39
+ const roomBelow = viewportHeight - trigger.bottom - MENU_GAP;
40
+ const roomAbove = trigger.top - MENU_GAP;
41
+ const vertical = height < roomBelow || (height > roomAbove && roomBelow >= roomAbove)
42
+ ? { side: "bottom", top: trigger.bottom + MENU_GAP, maxHeight: Math.max(0, roomBelow - MENU_EDGE) }
43
+ : { side: "top", bottom: viewportHeight - trigger.top + MENU_GAP, maxHeight: Math.max(0, roomAbove - MENU_EDGE) };
44
+ return (align === "start") !== isRtl
45
+ ? { ...vertical, left: trigger.left }
46
+ : { ...vertical, right: viewportWidth - trigger.right };
47
+ };
@@ -0,0 +1,28 @@
1
+ import { type Rect } from "./layout.js";
2
+ /**
3
+ * - `close`: close it.
4
+ * - `refocus`: keep it, with focus back in its composer (a click while it holds something unsent).
5
+ * - `return`: take the page back to it and focus its composer; it is out of sight with something
6
+ * unsent. Tried once: when a return has had its chance (its scroll has ended) and the panel is
7
+ * still out of sight (its element has no box, or scrolling can't reveal it), the click or Esc
8
+ * closes it instead, as Esc does in sight, so nobody is held by a panel they can't see. While the
9
+ * return is still under way, another click or Esc answers `return` again: nothing is lost.
10
+ * - `pass`: it is out of sight with nothing unsent: the click or Esc acts as if nothing were open
11
+ * (a click starts a new comment, Esc ends comment mode), and it closes with that.
12
+ */
13
+ export type OpenCommentResponse = "close" | "refocus" | "return" | "pass";
14
+ export declare const openCommentResponse: ({ input, isOnScreen, hasUnsent, hasReturnFailed, }: {
15
+ input: "click" | "escape";
16
+ /** Whether any of its panel is in the window and drawn (`isOnScreen`). */
17
+ isOnScreen: boolean;
18
+ /** Whether its composer holds something that would be sent. */
19
+ hasUnsent: boolean;
20
+ /** A `return` was answered, its scroll has had time to end, and the panel hasn't been in sight since. */
21
+ hasReturnFailed?: boolean;
22
+ }) => OpenCommentResponse;
23
+ /** Whether any of `rect` (null: not drawn) is in the window; an empty box is not. */
24
+ export declare const isOnScreen: (rect: Rect | null, viewport: {
25
+ width: number;
26
+ height: number;
27
+ }) => boolean;
28
+ //# sourceMappingURL=open-comment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-comment.d.ts","sourceRoot":"","sources":["../../src/comments/open-comment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,IAAI,EAAE,MAAM,UAAU,CAAC;AASrD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE1E,eAAO,MAAM,mBAAmB,GAAI,oDAKjC;IACD,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC1B,0EAA0E;IAC1E,UAAU,EAAE,OAAO,CAAC;IACpB,+DAA+D;IAC/D,SAAS,EAAE,OAAO,CAAC;IACnB,yGAAyG;IACzG,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,KAAG,mBAKH,CAAC;AAEF,qFAAqF;AACrF,eAAO,MAAM,UAAU,GAAI,MAAM,IAAI,GAAG,IAAI,EAAE,UAAU;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,KAAG,OACW,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { intersectRects } from "./layout.js";
2
+ export const openCommentResponse = ({ input, isOnScreen, hasUnsent, hasReturnFailed = false, }) => {
3
+ if (!isOnScreen) {
4
+ return !hasUnsent ? "pass" : hasReturnFailed ? "close" : "return";
5
+ }
6
+ return input === "click" && hasUnsent ? "refocus" : "close";
7
+ };
8
+ /** Whether any of `rect` (null: not drawn) is in the window; an empty box is not. */
9
+ export const isOnScreen = (rect, viewport) => !!rect && !!intersectRects(rect, { top: 0, left: 0, right: viewport.width, bottom: viewport.height });
@@ -1 +1 @@
1
- {"version":3,"file":"page-pick.d.ts","sourceRoot":"","sources":["../../src/comments/page-pick.ts"],"names":[],"mappings":"AAGA,OAAO,EAAe,KAAK,IAAI,EAA0C,MAAM,UAAU,CAAC;AAC1F,OAAO,EAOL,KAAK,SAAS,EAGf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,OAAO,CAAC;IAClB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACjC,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,KAAK,CAAC;IACb,iEAAiE;IACjE,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,WAAW,CAAC;IACrB,IAAI,EAAE,IAAI,CAAC;IACX,0HAA0H;IAC1H,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,IAAI,CAAC,EAAE;QACL,KAAK,EAAE,IAAI,EAAE,CAAC;QACd,KAAK,EAAE;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC;QACzD,WAAW,EAAE,OAAO,CAAC;KACtB,CAAC;CACH;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,GAAI,SAAS,WAAW,EAAE,WAAW,MAAM,KAAG,MAQhE,CAAC;AAeL,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,SAAS,EAAE,MAAM,MAAM,CAAC;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACjC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,0FAA0F;IAC1F,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IAC/C,mEAAmE;IACnE,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,CAAC;CAC9C;AAED,qGAAqG;AACrG,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,SAAS,GAAG,IAAI,CAAC;IACxD,8EAA8E;IAC9E,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,SAAS,GAAG,IAAI,CAAC;CACrD;AAQD,oGAAoG;AACpG,eAAO,MAAM,iBAAiB,GAC5B,KAAK,QAAQ,EACb,WAAW,iBAAiB,EAC5B,SAAQ,kBAGP,KACA,CAAC,MAAM,IAAI,CAmLb,CAAC;AAcF,eAAO,MAAM,WAAW,GAAI,2CAA2C,kBAAkB,KAAG,aAAa,GAAG,IA4C3G,CAAC"}
1
+ {"version":3,"file":"page-pick.d.ts","sourceRoot":"","sources":["../../src/comments/page-pick.ts"],"names":[],"mappings":"AAGA,OAAO,EAAe,KAAK,IAAI,EAA0C,MAAM,UAAU,CAAC;AAC1F,OAAO,EAOL,KAAK,SAAS,EAGf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,OAAO,CAAC;IAClB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACjC,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,KAAK,CAAC;IACb,iEAAiE;IACjE,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,WAAW,CAAC;IACrB,IAAI,EAAE,IAAI,CAAC;IACX,0HAA0H;IAC1H,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,IAAI,CAAC,EAAE;QACL,KAAK,EAAE,IAAI,EAAE,CAAC;QACd,KAAK,EAAE;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC;QACzD,WAAW,EAAE,OAAO,CAAC;KACtB,CAAC;CACH;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,GAAI,SAAS,WAAW,EAAE,WAAW,MAAM,KAAG,MAQhE,CAAC;AAeL,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,SAAS,EAAE,MAAM,MAAM,CAAC;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACjC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,0FAA0F;IAC1F,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IAC/C,mEAAmE;IACnE,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,CAAC;CAC9C;AAED,qGAAqG;AACrG,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,SAAS,GAAG,IAAI,CAAC;IACxD,8EAA8E;IAC9E,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,SAAS,GAAG,IAAI,CAAC;CACrD;AAQD,oGAAoG;AACpG,eAAO,MAAM,iBAAiB,GAC5B,KAAK,QAAQ,EACb,WAAW,iBAAiB,EAC5B,SAAQ,kBAGP,KACA,CAAC,MAAM,IAAI,CAkMb,CAAC;AAcF,eAAO,MAAM,WAAW,GAAI,2CAA2C,kBAAkB,KAAG,aAAa,GAAG,IA4C3G,CAAC"}
@@ -108,7 +108,14 @@ export const listenForPagePick = (doc, listeners, lookup = {
108
108
  };
109
109
  const onPress = (event) => {
110
110
  isSwallowingClick = false;
111
- if (isPaused() || event.button !== 0 || isPickIgnored(event.target)) {
111
+ if (isPaused()) {
112
+ // The picker in front takes this press. Should it stop before the click the press ends in
113
+ // (a drag's pick, Esc during a drag), that click is still part of its pointing, not a pick
114
+ // of this one's.
115
+ isSwallowingClick = event.button === 0 && !isPickIgnored(event.target);
116
+ return;
117
+ }
118
+ if (event.button !== 0 || isPickIgnored(event.target)) {
112
119
  return;
113
120
  }
114
121
  const point = lookup.textPointAt(event.clientX, event.clientY);
@@ -122,9 +129,6 @@ export const listenForPagePick = (doc, listeners, lookup = {
122
129
  };
123
130
  const onRelease = (event) => {
124
131
  if (isPaused()) {
125
- // The picker in front may pick on this release (a drag) and stop before the click it makes:
126
- // that click is part of its pick, not a pick of this one's.
127
- isSwallowingClick = !isPickIgnored(event.target);
128
132
  return;
129
133
  }
130
134
  const finished = drag;
@@ -198,16 +202,28 @@ export const listenForPagePick = (doc, listeners, lookup = {
198
202
  doc.removeEventListener("click", onClick, true);
199
203
  presses.forEach((type) => doc.removeEventListener(type, swallow, true));
200
204
  doc.removeEventListener("keydown", onKeyDown, true);
201
- if (isSwallowingClick) {
202
- // Stopped by its own drag's pick (the chat's pointer ends on a pick): the click that release
203
- // makes comes right after, and still isn't the app's. Only that click: gone a moment later.
205
+ // The click a release makes comes right after it, and still isn't the app's when the press
206
+ // was part of picking. Only that click: the listener is gone a moment later.
207
+ const swallowNextClick = () => {
204
208
  const swallowClick = (event) => {
205
209
  event.preventDefault();
206
210
  event.stopPropagation();
207
211
  };
208
212
  doc.addEventListener("click", swallowClick, { capture: true, once: true });
209
213
  setTimeout(() => doc.removeEventListener("click", swallowClick, true), 0);
214
+ };
215
+ if (isSwallowingClick) {
216
+ // Stopped by its own drag's pick (the chat's pointer ends on a pick), before the click.
210
217
  isSwallowingClick = false;
218
+ swallowNextClick();
219
+ }
220
+ else if (drag) {
221
+ // Stopped during a press (Esc ends the chat's pointing mid-drag): the click comes after the
222
+ // release. A new press first means that release never came here: nothing to swallow.
223
+ const forget = () => doc.removeEventListener("mouseup", swallowNextClick, true);
224
+ doc.addEventListener("mouseup", swallowNextClick, { capture: true, once: true });
225
+ doc.addEventListener("mousedown", forget, { capture: true, once: true });
226
+ drag = null;
211
227
  }
212
228
  };
213
229
  };