@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.
- package/dist/comments/anchor.d.ts +23 -1
- package/dist/comments/anchor.d.ts.map +1 -1
- package/dist/comments/anchor.js +74 -7
- package/dist/comments/comment-mode.d.ts +22 -4
- package/dist/comments/comment-mode.d.ts.map +1 -1
- package/dist/comments/comment-mode.js +22 -5
- package/dist/comments/index.d.ts +9 -3
- package/dist/comments/index.d.ts.map +1 -1
- package/dist/comments/index.js +9 -3
- package/dist/comments/menu.d.ts +33 -0
- package/dist/comments/menu.d.ts.map +1 -0
- package/dist/comments/menu.js +47 -0
- package/dist/comments/open-comment.d.ts +28 -0
- package/dist/comments/open-comment.d.ts.map +1 -0
- package/dist/comments/open-comment.js +9 -0
- package/dist/comments/page-pick.d.ts.map +1 -1
- package/dist/comments/page-pick.js +23 -7
- package/dist/comments/pin-view.d.ts +107 -0
- package/dist/comments/pin-view.d.ts.map +1 -0
- package/dist/comments/pin-view.js +248 -0
- package/dist/comments/pins.d.ts +66 -12
- package/dist/comments/pins.d.ts.map +1 -1
- package/dist/comments/pins.js +132 -39
- package/dist/comments/text-pick.d.ts +11 -1
- package/dist/comments/text-pick.d.ts.map +1 -1
- package/dist/comments/text-pick.js +143 -1
- package/dist/comments/types.d.ts +4 -1
- package/dist/comments/types.d.ts.map +1 -1
- package/dist/comments/use-comment-menu.d.ts +69 -0
- package/dist/comments/use-comment-menu.d.ts.map +1 -0
- package/dist/comments/use-comment-menu.js +140 -0
- package/dist/comments/use-held.d.ts +3 -0
- package/dist/comments/use-held.d.ts.map +1 -0
- package/dist/comments/use-held.js +16 -0
- package/dist/comments/use-thread-scroll.d.ts +23 -0
- package/dist/comments/use-thread-scroll.d.ts.map +1 -0
- package/dist/comments/use-thread-scroll.js +89 -0
- package/dist/iframe-communication/types.d.ts +3 -2
- package/dist/iframe-communication/types.d.ts.map +1 -1
- 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
|
+
};
|
package/dist/comments/pins.d.ts
CHANGED
|
@@ -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
|
-
* -
|
|
18
|
-
*
|
|
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
|
-
/**
|
|
66
|
-
|
|
67
|
-
|
|
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;
|
|
1
|
+
{"version":3,"file":"pins.d.ts","sourceRoot":"","sources":["../../src/comments/pins.ts"],"names":[],"mappings":"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"}
|