@blocksdiy/react-common 1.71.0 → 1.71.2

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 +2 -2
@@ -0,0 +1,107 @@
1
+ import type * as React from "react";
2
+ import { type Rect } from "./layout.js";
3
+ import type { PinPoint } from "./pins.js";
4
+ import type { CommentParticipantDto } from "./types.js";
5
+ /** The window as a placement sees it: its layer's width (less a classic scrollbar), its height, its direction. */
6
+ export interface PlacementViewport {
7
+ width: number;
8
+ height: number;
9
+ isRtl: boolean;
10
+ }
11
+ export interface PanelPositionOptions {
12
+ /**
13
+ * The window, read where the caller renders on each frame: a call the React Compiler caches on
14
+ * its arguments then never keeps a stale place after a resize, a zoom or a change of direction.
15
+ */
16
+ viewport: PlacementViewport;
17
+ /**
18
+ * How far the pin's element has gone past the window's edge (`ThreadLayout.hidden.shift`): the
19
+ * panel is placed as for `point`, where the pin stood as the element left, and moves with it.
20
+ */
21
+ shift?: PinPoint;
22
+ /**
23
+ * Where to stand (`panelSide`, kept per open panel): without it, as `panelSide` opens one.
24
+ */
25
+ side?: PanelSide;
26
+ /**
27
+ * What the comment is on (`ThreadLayout.target`, a new comment's `pickPlacement`), in the same
28
+ * coordinates as the point, before any shift: the panel keeps clear of it where it can
29
+ * (`panelSide`). Without it the panel goes beside the pin only.
30
+ */
31
+ target?: Rect;
32
+ /**
33
+ * The panel's height as last drawn, when the caller measured it (and its `maxHeight` didn't cut
34
+ * it): the room it wants. Without it, `PANEL_MIN_ROOM`.
35
+ */
36
+ panelHeight?: number;
37
+ }
38
+ /** A pin's width: one face fills it, and each more face (or "+N") adds what shows past the overlap. */
39
+ export declare const pinWidth: (faceCount: number) => number;
40
+ /**
41
+ * Where the panel goes by a pin `width` wide whose pointed corner is on `point` (a new comment's:
42
+ * the one-face pin it becomes), keeping clear of `target` where it can. The pin's box is worked out
43
+ * as `Pin` places it: above the point and toward the line's end, slid back inside the window at its
44
+ * edges. Where it stands is `side` (else as `panelSide` opens one): past the pin's end side or
45
+ * before its start, level with the pin's top (`down`) or with its bottom (`up`), or further along
46
+ * when that leaves it less than `PANEL_MIN_ROOM`; or below or above the target, its end edge level
47
+ * with the pin's. It is never taller than the room it has, so its replies scroll and its composer
48
+ * stays on screen. With a `shift` it then moves by it, keeping its height: it goes out of the
49
+ * window with its pin.
50
+ */
51
+ export declare const panelPosition: (point: PinPoint, width: number, panelWidth: number, { viewport: frame, shift, side, target, panelHeight }: PanelPositionOptions) => React.CSSProperties;
52
+ /**
53
+ * Where a panel stands by its pin. `place`, in the order a panel tries them:
54
+ * - `end`: past the pin's end side (the right, or the left on a right-to-left page), level with it;
55
+ * - `below`, `above`: below or above what the comment is on (and its pin), its end edge level with
56
+ * the pin's end edge;
57
+ * - `start`: before the pin's start side, level with it: over what the comment is on, the last
58
+ * resort.
59
+ *
60
+ * `block`, beside the pin (`end`, `start`): `down` hangs it from the pin's top (its top level with
61
+ * the pin's), `up` above the pin's bottom (its bottom level with the pin's).
62
+ *
63
+ * `isMeasured`: chosen knowing the panel's own height (`panelHeight`). One chosen without it is
64
+ * chosen afresh once the height is known, so a panel's first, unmeasured frame doesn't decide
65
+ * where it stays.
66
+ */
67
+ export interface PanelSide {
68
+ block: "up" | "down";
69
+ place: "end" | "below" | "above" | "start";
70
+ isMeasured?: boolean;
71
+ }
72
+ /**
73
+ * Where a panel stands by its pin (`panelPosition`'s `side`), sticky. As it opens: level with the
74
+ * pin's top, hanging down, when it fits between there and the window's bottom margin; hanging up
75
+ * from the pin's bottom only when it doesn't and does fit above (`block`); and the first place
76
+ * (`PanelSide`) that fits inside the window's margins (a panel below or above the target with the
77
+ * room it wants) without covering `target` (`ThreadLayout.target`), else before the pin's start.
78
+ * The room it wants is `panelHeight` when known, else `PANEL_MIN_ROOM`. Then, given where it
79
+ * stood (`previous`): hanging down and standing past the pin's end side are where it rests, kept
80
+ * while they fit within `PANEL_SIDE_SLACK` (a panel kept within the slack slides along for room,
81
+ * still beside the pin); any other side or place is held only until the resting one fits again
82
+ * with no slack, then kept while it fits within the slack. The two switch points are the slack
83
+ * apart, so a page scrolled back and forth by a few pixels never makes it flap. A `previous`
84
+ * chosen before the height was known is not kept once it is (`isMeasured`). Without a target only
85
+ * the places beside the pin are taken.
86
+ */
87
+ export declare const panelSide: (point: PinPoint, width: number, panelWidth: number, previous: PanelSide | undefined, viewport: PlacementViewport, target?: Rect, panelHeight?: number) => PanelSide;
88
+ export declare const participantKey: (participant: CommentParticipantDto) => string;
89
+ /**
90
+ * Everyone in a pin's threads, by first comment, each once: up to three as faces, the working
91
+ * agent's last (on top, so its spinner badge shows), and how many more there are, as "+N".
92
+ */
93
+ export declare const pinParticipants: (threads: readonly {
94
+ participants: readonly CommentParticipantDto[];
95
+ createdByUserId: number;
96
+ }[], workingAgentId: string | null) => {
97
+ shown: CommentParticipantDto[];
98
+ moreCount: number;
99
+ };
100
+ /**
101
+ * Where a pin `width` wide goes for a panel with nothing on the page to stand beside (a thread
102
+ * opened from its link whose element isn't there): so that `panelPosition` from it puts the panel
103
+ * in the window's bottom end corner, 16px in, with the pin just past the panel's start side, its
104
+ * bottom level with the panel's.
105
+ */
106
+ export declare const cornerPoint: (viewport: PlacementViewport, width: number, panelWidth: number) => PinPoint;
107
+ //# sourceMappingURL=pin-view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pin-view.d.ts","sourceRoot":"","sources":["../../src/comments/pin-view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAEpC,OAAO,EAAkB,KAAK,IAAI,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAQrD,kHAAkH;AAClH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,QAAQ,EAAE,iBAAiB,CAAC;IAC5B;;;OAGG;IACH,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,IAAI,CAAC;IACd;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAqBD,uGAAuG;AACvG,eAAO,MAAM,QAAQ,GAAI,WAAW,MAAM,WAA0D,CAAC;AAKrG;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,GACxB,OAAO,QAAQ,EACf,OAAO,MAAM,EACb,YAAY,MAAM,EAClB,uDAAuD,oBAAoB,KAC1E,KAAK,CAAC,aAqBR,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,IAAI,GAAG,MAAM,CAAC;IACrB,KAAK,EAAE,KAAK,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IAC3C,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AASD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,GACpB,OAAO,QAAQ,EACf,OAAO,MAAM,EACb,YAAY,MAAM,EAClB,UAAU,SAAS,GAAG,SAAS,EAC/B,UAAU,iBAAiB,EAC3B,SAAS,IAAI,EACb,cAAc,MAAM,KACnB,SA2CF,CAAC;AA6GF,eAAO,MAAM,cAAc,GAAI,aAAa,qBAAqB,WACmB,CAAC;AAErF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,SAAS;IAChB,YAAY,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAC/C,eAAe,EAAE,MAAM,CAAC;CACzB,EAAE,EACH,gBAAgB,MAAM,GAAG,IAAI,KAC5B;IAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAqBrD,CAAC;AAKF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,UAAU,iBAAiB,EAAE,OAAO,MAAM,EAAE,YAAY,MAAM,KAAG,QAM5F,CAAC"}
@@ -0,0 +1,248 @@
1
+ import { intersectRects } from "./layout.js";
2
+ const PIN_SIZE = 28;
3
+ /** What each face after the first adds to a pin's width: a face, less the overlap. */
4
+ const PIN_FACE_STEP = 14;
5
+ /** How far a pin keeps inside the window when its point is near an edge. */
6
+ const PIN_EDGE_MARGIN = 4;
7
+ /**
8
+ * The gap between a pin (or a new comment's point) and its panel: room for the open pin's glow (7px)
9
+ * and its growth (110%, from its pointed corner), with air past them.
10
+ */
11
+ const PANEL_GAP = 20;
12
+ /**
13
+ * The height a panel is given room for while its own height isn't known (`panelHeight`), the window
14
+ * allowing: its header, its comments' minimum and a composer at its tallest. In a short window the
15
+ * panel slides along the pin's side for it.
16
+ */
17
+ const PANEL_MIN_ROOM = 384;
18
+ /** Faces a pin shows before "+N". */
19
+ const PIN_FACES_MAX = 3;
20
+ /** A pin's width: one face fills it, and each more face (or "+N") adds what shows past the overlap. */
21
+ export const pinWidth = (faceCount) => PIN_SIZE + Math.max(0, faceCount - 1) * PIN_FACE_STEP;
22
+ /** The gap between what a comment is on (or its pin) and a panel above or below it. */
23
+ const TARGET_GAP = 8;
24
+ /**
25
+ * Where the panel goes by a pin `width` wide whose pointed corner is on `point` (a new comment's:
26
+ * the one-face pin it becomes), keeping clear of `target` where it can. The pin's box is worked out
27
+ * as `Pin` places it: above the point and toward the line's end, slid back inside the window at its
28
+ * edges. Where it stands is `side` (else as `panelSide` opens one): past the pin's end side or
29
+ * before its start, level with the pin's top (`down`) or with its bottom (`up`), or further along
30
+ * when that leaves it less than `PANEL_MIN_ROOM`; or below or above the target, its end edge level
31
+ * with the pin's. It is never taller than the room it has, so its replies scroll and its composer
32
+ * stays on screen. With a `shift` it then moves by it, keeping its height: it goes out of the
33
+ * window with its pin.
34
+ */
35
+ export const panelPosition = (point, width, panelWidth, { viewport: frame, shift, side, target, panelHeight }) => {
36
+ const geometry = besidePin(point, width, panelWidth, frame, target, panelHeight);
37
+ const taken = side ?? panelSide(point, width, panelWidth, undefined, frame, target, panelHeight);
38
+ const box = panelBox(taken, geometry);
39
+ const dx = shift?.x ?? 0;
40
+ const dy = shift?.y ?? 0;
41
+ const hangsUp = taken.place === "above" || (taken.place !== "below" && taken.block === "up");
42
+ if (hangsUp) {
43
+ return {
44
+ left: box.left + dx,
45
+ bottom: frame.height - box.bottom - dy,
46
+ width: panelWidth,
47
+ maxHeight: box.bottom - geometry.margin,
48
+ };
49
+ }
50
+ return {
51
+ left: box.left + dx,
52
+ top: box.top + dy,
53
+ width: panelWidth,
54
+ maxHeight: frame.height - box.top - geometry.margin,
55
+ };
56
+ };
57
+ /**
58
+ * How far a panel's place may run short of the room it wants (its height down the window, its
59
+ * width across), or cover what the comment is on, before it moves: the two switch points are this
60
+ * far apart at least, so a page scrolled back and forth by a few pixels never makes it flap.
61
+ */
62
+ const PANEL_SIDE_SLACK = 48;
63
+ /**
64
+ * Where a panel stands by its pin (`panelPosition`'s `side`), sticky. As it opens: level with the
65
+ * pin's top, hanging down, when it fits between there and the window's bottom margin; hanging up
66
+ * from the pin's bottom only when it doesn't and does fit above (`block`); and the first place
67
+ * (`PanelSide`) that fits inside the window's margins (a panel below or above the target with the
68
+ * room it wants) without covering `target` (`ThreadLayout.target`), else before the pin's start.
69
+ * The room it wants is `panelHeight` when known, else `PANEL_MIN_ROOM`. Then, given where it
70
+ * stood (`previous`): hanging down and standing past the pin's end side are where it rests, kept
71
+ * while they fit within `PANEL_SIDE_SLACK` (a panel kept within the slack slides along for room,
72
+ * still beside the pin); any other side or place is held only until the resting one fits again
73
+ * with no slack, then kept while it fits within the slack. The two switch points are the slack
74
+ * apart, so a page scrolled back and forth by a few pixels never makes it flap. A `previous`
75
+ * chosen before the height was known is not kept once it is (`isMeasured`). Without a target only
76
+ * the places beside the pin are taken.
77
+ */
78
+ export const panelSide = (point, width, panelWidth, previous, viewport, target, panelHeight) => {
79
+ const geometry = besidePin(point, width, panelWidth, viewport, target, panelHeight);
80
+ const { margin, pin, room } = geometry;
81
+ const isMeasured = panelHeight !== undefined;
82
+ const kept = previous && (previous.isMeasured || !isMeasured) ? previous : undefined;
83
+ const roomDown = viewport.height - margin - pin.top;
84
+ const roomUp = pin.bottom - margin;
85
+ // Down is where it rests: it hangs up only while down is short by more than the slack, and comes
86
+ // back down as soon as down fits.
87
+ const block = kept?.block === "down"
88
+ ? roomDown < room - PANEL_SIDE_SLACK && roomUp >= room
89
+ ? "up"
90
+ : "down"
91
+ : kept?.block === "up"
92
+ ? roomDown >= room
93
+ ? "down"
94
+ : "up"
95
+ : roomDown < room && roomUp >= room
96
+ ? "up"
97
+ : "down";
98
+ const otherBlock = block === "down" ? "up" : "down";
99
+ const order = [
100
+ { block, place: "end" },
101
+ { block: otherBlock, place: "end" },
102
+ { block, place: "below" },
103
+ { block, place: "above" },
104
+ { block, place: "start" },
105
+ ];
106
+ if (kept) {
107
+ // Beside the pin is where it rests too: a panel below, above or over the target goes back
108
+ // there as soon as it fits; one beside it stays while it fits within the slack.
109
+ const backBeside = kept.place === "end" ? undefined : order.slice(0, 2).find((candidate) => fits(candidate, geometry, 0));
110
+ if (backBeside) {
111
+ return { ...backBeside, isMeasured };
112
+ }
113
+ if (fits({ block, place: kept.place }, geometry, PANEL_SIDE_SLACK)) {
114
+ return { block, place: kept.place, isMeasured };
115
+ }
116
+ }
117
+ const side = order.find((candidate) => fits(candidate, geometry, 0)) ?? { block, place: kept?.place ?? "start" };
118
+ return { ...side, isMeasured };
119
+ };
120
+ /**
121
+ * The pin's box as `Pin` places it, where a panel would go on each side of it, and what the panel
122
+ * keeps clear of: the target and the pin together.
123
+ */
124
+ const besidePin = (point, width, panelWidth, viewport, target, panelHeight) => {
125
+ const margin = 8;
126
+ const pinLeft = viewport.isRtl
127
+ ? Math.max(PIN_EDGE_MARGIN, point.x - width)
128
+ : Math.min(point.x, viewport.width - PIN_EDGE_MARGIN - width);
129
+ const pinRight = pinLeft + width;
130
+ const pinTop = Math.max(PIN_EDGE_MARGIN, point.y - PIN_SIZE);
131
+ const pin = { left: pinLeft, right: pinRight, top: pinTop, bottom: pinTop + PIN_SIZE };
132
+ return {
133
+ margin,
134
+ panelWidth,
135
+ viewport,
136
+ pin,
137
+ target,
138
+ /** The target and the pin together: what a panel below or above them stands clear of. */
139
+ avoid: target
140
+ ? {
141
+ top: Math.min(target.top, pin.top),
142
+ left: Math.min(target.left, pin.left),
143
+ right: Math.max(target.right, pin.right),
144
+ bottom: Math.max(target.bottom, pin.bottom),
145
+ }
146
+ : null,
147
+ pastEnd: viewport.isRtl ? pinLeft - PANEL_GAP - panelWidth : pinRight + PANEL_GAP,
148
+ beforeStart: viewport.isRtl ? pinRight + PANEL_GAP : pinLeft - PANEL_GAP - panelWidth,
149
+ /** A panel's left edge with its end edge level with the pin's. */
150
+ endAligned: viewport.isRtl ? pinLeft : pinRight - panelWidth,
151
+ room: Math.min(panelHeight ?? PANEL_MIN_ROOM, viewport.height - 2 * margin),
152
+ };
153
+ };
154
+ /** The panel's box at `side`, as tall as the room it wants, kept inside the window's margins. */
155
+ const panelBox = (side, geometry) => {
156
+ const { margin, panelWidth, viewport, pin, room } = geometry;
157
+ const avoid = geometry.avoid ?? pin;
158
+ // The lowest top that still leaves the panel its room.
159
+ const farthest = viewport.height - margin - room;
160
+ const inMargins = (left) => Math.min(Math.max(margin, left), viewport.width - margin - panelWidth);
161
+ const box = (left, top) => ({
162
+ left,
163
+ right: left + panelWidth,
164
+ top,
165
+ bottom: top + room,
166
+ });
167
+ switch (side.place) {
168
+ case "below":
169
+ return box(inMargins(geometry.endAligned), Math.max(margin, Math.min(avoid.bottom + TARGET_GAP, farthest)));
170
+ case "above":
171
+ return box(inMargins(geometry.endAligned), Math.min(viewport.height - margin, Math.max(avoid.top - TARGET_GAP, margin + room)) - room);
172
+ default: {
173
+ const left = inMargins(side.place === "end" ? geometry.pastEnd : geometry.beforeStart);
174
+ if (side.block === "up") {
175
+ const bottom = Math.max(margin, Math.min(viewport.height - pin.bottom, farthest));
176
+ return box(left, viewport.height - bottom - room);
177
+ }
178
+ return box(left, Math.max(margin, Math.min(pin.top, farthest)));
179
+ }
180
+ }
181
+ };
182
+ /** How far two boxes overlap: the lesser of their overlaps across and down, zero when apart. */
183
+ const overlapDepth = (a, b) => {
184
+ const overlap = intersectRects(a, b);
185
+ return overlap ? Math.min(overlap.right - overlap.left, overlap.bottom - overlap.top) : 0;
186
+ };
187
+ /**
188
+ * Whether a panel at `side` fits within `slack`: beside the pin, across the window's margins;
189
+ * below or above a target, with the room it wants (a panel short of room slides over the target);
190
+ * and, but for the last resort (`start`), covering the target and its pin (each on its own, not
191
+ * the empty corner between them) by no more than `slack`.
192
+ */
193
+ const fits = (side, geometry, slack) => {
194
+ const { target, pin } = geometry;
195
+ if (side.place === "start") {
196
+ return fitsAt(geometry.beforeStart, geometry, slack);
197
+ }
198
+ if (side.place === "end" ? !fitsAt(geometry.pastEnd, geometry, slack) : !target) {
199
+ return false;
200
+ }
201
+ if (!target) {
202
+ return true;
203
+ }
204
+ const box = panelBox(side, geometry);
205
+ return overlapDepth(box, target) <= slack && overlapDepth(box, pin) <= slack;
206
+ };
207
+ /** Whether a panel whose left edge is at `left` fits across the window, within `slack` past its margins. */
208
+ const fitsAt = (left, geometry, slack) => left >= geometry.margin - slack && left + geometry.panelWidth <= geometry.viewport.width - geometry.margin + slack;
209
+ export const participantKey = (participant) => participant.type === "user" ? `u${participant.userId}` : `a${participant.agentId}`;
210
+ /**
211
+ * Everyone in a pin's threads, by first comment, each once: up to three as faces, the working
212
+ * agent's last (on top, so its spinner badge shows), and how many more there are, as "+N".
213
+ */
214
+ export const pinParticipants = (threads, workingAgentId) => {
215
+ const seen = new Set();
216
+ const others = threads
217
+ // Participants count only comments still there: a thread whose comments were all deleted
218
+ // shows its author.
219
+ .flatMap((thread) => thread.participants.length > 0 ? thread.participants : [{ type: "user", userId: thread.createdByUserId }])
220
+ .filter((participant) => {
221
+ const key = participantKey(participant);
222
+ const isNew = !seen.has(key) && !(participant.type === "agent" && participant.agentId === workingAgentId);
223
+ seen.add(key);
224
+ return isNew;
225
+ });
226
+ const shown = workingAgentId
227
+ ? [...others.slice(0, PIN_FACES_MAX - 1), { type: "agent", agentId: workingAgentId }]
228
+ : others.slice(0, PIN_FACES_MAX);
229
+ return {
230
+ shown,
231
+ moreCount: others.length + (workingAgentId ? 1 : 0) - shown.length,
232
+ };
233
+ };
234
+ /** How far a panel with nothing to stand beside keeps from the window's bottom and end edges. */
235
+ const CORNER_INSET = 16;
236
+ /**
237
+ * Where a pin `width` wide goes for a panel with nothing on the page to stand beside (a thread
238
+ * opened from its link whose element isn't there): so that `panelPosition` from it puts the panel
239
+ * in the window's bottom end corner, 16px in, with the pin just past the panel's start side, its
240
+ * bottom level with the panel's.
241
+ */
242
+ export const cornerPoint = (viewport, width, panelWidth) => {
243
+ const fromEnd = CORNER_INSET + panelWidth + PANEL_GAP + width;
244
+ return {
245
+ x: viewport.isRtl ? fromEnd : viewport.width - fromEnd,
246
+ y: viewport.height - CORNER_INSET,
247
+ };
248
+ };
@@ -1,11 +1,6 @@
1
1
  import { type AnchorState, resolveAnchor } from "./anchor.js";
2
2
  import { type Rect } from "./layout.js";
3
3
  import type { CommentAnchor } from "./types.js";
4
- /**
5
- * Where each thread's pin goes on the page as it is now. Never stored: worked out from the anchor
6
- * every time the page moves, so a pin follows its element across screen sizes, re-sorted lists and
7
- * scrolling.
8
- */
9
4
  /** How far a pin keeps from the viewport's edges. */
10
5
  export declare const PIN_EDGE_MARGIN = 12;
11
6
  export interface PinPoint {
@@ -13,13 +8,19 @@ export interface PinPoint {
13
8
  y: number;
14
9
  }
15
10
  /**
16
- * The pin's point, in viewport coordinates.
17
- * - A quote: the end of its last line.
18
- * - An element: where it was clicked, scaled to the element's box now; else its top-right corner.
11
+ * The pin's point, in viewport coordinates: the top-end corner of what the comment is on (top-right
12
+ * on a left-to-right page, top-left with `isRtl`; without it, as the page reads), so the pin,
13
+ * standing above its point and past the end side (`Pin`), stays outside it.
14
+ * - A quote: its first line's corner (`firstLine`), the pin above the words, never over them;
15
+ * given only `lastLine` (a copy from before `firstLine`), that line's corner.
16
+ * - An element: its box's corner. Where it was clicked (`point`, stored on the anchor) doesn't
17
+ * place the pin; it is taken so a copy that passes it keeps working.
19
18
  */
20
19
  export declare const pinPoint: (target: {
21
20
  kind: "quote";
22
21
  lastLine: Rect;
22
+ firstLine?: Rect;
23
+ isRtl?: boolean;
23
24
  } | {
24
25
  kind: "element";
25
26
  rect: Rect;
@@ -27,6 +28,7 @@ export declare const pinPoint: (target: {
27
28
  x: number;
28
29
  y: number;
29
30
  };
31
+ isRtl?: boolean;
30
32
  }) => PinPoint;
31
33
  /** `point` kept `margin` inside the viewport. */
32
34
  export declare const keepInViewport: (point: PinPoint, viewport: {
@@ -56,17 +58,69 @@ export interface ThreadLayout {
56
58
  groupKey: string | null;
57
59
  /** What the anchored text says now, when it changed. */
58
60
  currentText?: string;
61
+ /**
62
+ * What the comment is on, in window coordinates, for its panel to keep clear of: the quote's
63
+ * lines' bounding box, else the element's box, cut to the part of it in view (to the window's
64
+ * edge, a line with no height or width, while it is out of view). Absent when the element is
65
+ * missing, and on a platform from before it.
66
+ */
67
+ target?: Rect;
68
+ /**
69
+ * Why `point` is null though the element is on the page, and where its pin would go: what an
70
+ * open thread needs to keep its pin and panel with the element. Absent while the pin shows, when
71
+ * the element is missing, and on a platform from before it.
72
+ * - `offscreen`: the element is outside the window. `point` is where the pin stood as the element
73
+ * left (kept inside the window, as a shown pin is), and `shift` is how far the element has gone
74
+ * past the window's edge since: the pin and its panel move by it, so they slide out with the
75
+ * page instead of jumping or staying at the edge.
76
+ * - `clipped`: the element's box is in the window but a scroll container hides it (or it has no
77
+ * box): nothing of it can be seen, and nothing should float over what is there instead.
78
+ * - `covered`: it is in view under other UI (a dialog, a menu). `shift` is zero then and for
79
+ * `clipped`.
80
+ */
81
+ hidden?: {
82
+ by: "offscreen" | "clipped" | "covered";
83
+ point: PinPoint;
84
+ shift: PinPoint;
85
+ };
59
86
  }
60
87
  /**
61
88
  * The comments' own layer (pins, threads) marks its root with this attribute: what it draws over an
62
89
  * element doesn't hide the element. Other ignored UI (the agent chat's panel) does.
63
90
  */
64
91
  export declare const COMMENTS_LAYER_ATTRIBUTE = "data-blocks-comments-layer";
65
- /** Lays out one anchor whose element is known. */
66
- export declare const layoutAnchor: (anchor: CommentAnchor, resolved: ReturnType<typeof resolveAnchor>) => ThreadLayout;
67
- /** Where each thread is on the page now, re-measured at most once per frame while `isActive`. */
92
+ /**
93
+ * How far `box` lies past the window's edges: negative past the start (left, top), positive past
94
+ * the end, zero along an axis where it overlaps the window.
95
+ */
96
+ export declare const pastWindow: (box: Rect, viewport: {
97
+ width: number;
98
+ height: number;
99
+ }) => PinPoint;
100
+ /** Lays out one anchor whose element is known, on a page that reads right to left with `isRtl`. */
101
+ export declare const layoutAnchor: (resolved: ReturnType<typeof resolveAnchor>, isRtl: boolean) => ThreadLayout;
102
+ /**
103
+ * Where a new comment's pin goes and what its panel keeps clear of, as it is picked (`usePagePick`'s
104
+ * `onPick`): the same corner and target as a thread on it gets (`layoutAnchor`), its quote found in
105
+ * `element`. Reads the page; call it from the pick, not while rendering.
106
+ */
107
+ export declare const pickPlacement: (anchor: CommentAnchor, element: HTMLElement, isRtl?: boolean) => {
108
+ point: PinPoint;
109
+ target: Rect;
110
+ };
111
+ /**
112
+ * Finds an anchor again, re-checking only its text while its element is still in the page and
113
+ * still inside the same row. One found on the row itself is found anew each time: what the row
114
+ * must say depends on how the thread came to be on it (`resolveAnchor`).
115
+ */
116
+ export declare const resolveAnchorCached: (anchor: CommentAnchor) => import("./anchor.js").ResolvedAnchor;
117
+ /**
118
+ * Where each thread is on the page now, re-measured at most once per frame while `isActive`, on a
119
+ * page that reads right to left with `isRtl` (the caller's window as it read it; read here on each
120
+ * render when absent).
121
+ */
68
122
  export declare const useCommentPins: (anchors: readonly {
69
123
  id: string | number;
70
124
  anchor: CommentAnchor;
71
- }[], isActive: boolean) => ReadonlyMap<string | number, ThreadLayout>;
125
+ }[], isActive: boolean, isRtl?: boolean) => ReadonlyMap<string | number, ThreadLayout>;
72
126
  //# sourceMappingURL=pins.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pins.d.ts","sourceRoot":"","sources":["../../src/comments/pins.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,WAAW,EAAoC,aAAa,EAAE,MAAM,UAAU,CAAC;AAC7F,OAAO,EAAe,KAAK,IAAI,EAA0C,MAAM,UAAU,CAAC;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C;;;;GAIG;AAEH,qDAAqD;AACrD,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC,MAAM,WAAW,QAAQ;IACvB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GACnB,QAAQ;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,KAC5G,QAYF,CAAC;AAEF,iDAAiD;AACjD,eAAO,MAAM,cAAc,GACzB,OAAO,QAAQ,EACf,UAAU;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAC3C,eAAwB;;;CAIxB,CAAC;AAEH,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,QAAQ,CAAC;IAChB,KAAK,EAAE,CAAC,EAAE,CAAC;CACZ;AAED,kGAAkG;AAClG,eAAO,MAAM,SAAS,GAAI,CAAC,EAAE,MAAM,SAAS;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,EAAE,KAAG,QAAQ,CAAC,CAAC,CAAC,EAWpG,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,4GAA4G;IAC5G,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAID;;;GAGG;AACH,eAAO,MAAM,wBAAwB,+BAA+B,CAAC;AAmBrE,kDAAkD;AAClD,eAAO,MAAM,YAAY,GAAI,QAAQ,aAAa,EAAE,UAAU,UAAU,CAAC,OAAO,aAAa,CAAC,KAAG,YAsBhG,CAAC;AAkBF,iGAAiG;AACjG,eAAO,MAAM,cAAc,GACzB,SAAS,SAAS;IAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,MAAM,EAAE,aAAa,CAAA;CAAE,EAAE,EAClE,UAAU,OAAO,KAChB,WAAW,CAAC,MAAM,GAAG,MAAM,EAAE,YAAY,CAgB3C,CAAC"}
1
+ {"version":3,"file":"pins.d.ts","sourceRoot":"","sources":["../../src/comments/pins.ts"],"names":[],"mappings":"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"}