@devaccel-labs/app-comments-toolbar 1.0.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -18
- package/dist/react.js +516 -61
- package/dist/server.js +342 -14
- package/dist/types/capture/api.d.ts +30 -0
- package/dist/types/capture/editor.d.ts +10 -0
- package/dist/types/capture/ignore.d.ts +19 -0
- package/dist/types/capture/index.d.ts +1 -0
- package/dist/types/capture/picker.d.ts +9 -0
- package/dist/types/capture/picture.d.ts +29 -0
- package/dist/types/capture/surface.d.ts +39 -0
- package/dist/types/core/index.d.ts +2 -2
- package/dist/types/core/resolve.d.ts +2 -1
- package/dist/types/core/types.d.ts +11 -0
- package/dist/types/debug-context.d.ts +7 -0
- package/dist/types/dictation.d.ts +9 -0
- package/dist/types/embed/controller.d.ts +61 -0
- package/dist/types/embed/index.d.ts +1 -0
- package/dist/types/embed/options.d.ts +35 -0
- package/dist/types/help/index.d.ts +1 -0
- package/dist/types/help/panel-index.d.ts +1 -0
- package/dist/types/help/panel-styles.d.ts +1 -0
- package/dist/types/help/panel.d.ts +4 -0
- package/dist/types/help/pill-styles.d.ts +11 -0
- package/dist/types/help/pill.d.ts +44 -0
- package/dist/types/help/react.d.ts +12 -0
- package/dist/types/help/requests.d.ts +2 -0
- package/dist/types/help/types.d.ts +119 -0
- package/dist/types/help/wire.d.ts +61 -0
- package/dist/types/native-wire.d.ts +63 -0
- package/dist/types/native.d.ts +43 -0
- package/dist/types/react.d.ts +8 -1
- package/dist/types/replay.d.ts +6 -0
- package/dist/types/runtime/api.d.ts +250 -0
- package/dist/types/runtime/attachment-model.d.ts +16 -0
- package/dist/types/runtime/attachments.d.ts +43 -7
- package/dist/types/runtime/boot.d.ts +39 -0
- package/dist/types/runtime/browser-label.d.ts +5 -0
- package/dist/types/runtime/build-id.d.ts +7 -0
- package/dist/types/runtime/capture-ui.d.ts +45 -21
- package/dist/types/runtime/clarity-model.d.ts +260 -0
- package/dist/types/runtime/clarity-ui.d.ts +62 -0
- package/dist/types/runtime/client.d.ts +6 -197
- package/dist/types/runtime/composer-size.d.ts +30 -0
- package/dist/types/runtime/dashboard.d.ts +17 -2
- package/dist/types/runtime/debug-context-core.d.ts +88 -0
- package/dist/types/runtime/debug-context-host.d.ts +39 -0
- package/dist/types/runtime/debug-context-model.d.ts +352 -0
- package/dist/types/runtime/debug-context-redact.d.ts +48 -0
- package/dist/types/runtime/debug-context.d.ts +36 -0
- package/dist/types/runtime/details-model.d.ts +7 -0
- package/dist/types/runtime/details.d.ts +7 -5
- package/dist/types/runtime/dictation-engine.d.ts +98 -0
- package/dist/types/runtime/dictation-model.d.ts +28 -0
- package/dist/types/runtime/dictation-ui.d.ts +56 -0
- package/dist/types/runtime/dictation.d.ts +66 -0
- package/dist/types/runtime/draft-dock.d.ts +32 -0
- package/dist/types/runtime/draft-model.d.ts +104 -0
- package/dist/types/runtime/draft-store.d.ts +15 -0
- package/dist/types/runtime/edits.d.ts +1 -1
- package/dist/types/runtime/font.d.ts +16 -0
- package/dist/types/runtime/format.d.ts +30 -1
- package/dist/types/runtime/host-watch.d.ts +20 -0
- package/dist/types/runtime/icons.d.ts +16 -2
- package/dist/types/runtime/index.d.ts +3 -1
- package/dist/types/runtime/ink.d.ts +8 -0
- package/dist/types/runtime/kind-model.d.ts +14 -0
- package/dist/types/runtime/live-model.d.ts +25 -0
- package/dist/types/runtime/loader.d.ts +5 -11
- package/dist/types/runtime/markup-editor.d.ts +54 -0
- package/dist/types/runtime/markup-model.d.ts +126 -0
- package/dist/types/runtime/markup.d.ts +69 -0
- package/dist/types/runtime/mention-parse.d.ts +18 -0
- package/dist/types/runtime/mentions.d.ts +4 -4
- package/dist/types/runtime/page-background.d.ts +9 -0
- package/dist/types/runtime/page-image.d.ts +123 -0
- package/dist/types/runtime/placement.d.ts +6 -3
- package/dist/types/runtime/record.d.ts +18 -2
- package/dist/types/runtime/related.d.ts +1 -1
- package/dist/types/runtime/replay-model.d.ts +80 -0
- package/dist/types/runtime/replay.d.ts +5 -38
- package/dist/types/runtime/runtime.d.ts +6 -0
- package/dist/types/runtime/screen.d.ts +47 -0
- package/dist/types/runtime/screenshot.d.ts +60 -6
- package/dist/types/runtime/session-source.d.ts +45 -0
- package/dist/types/runtime/session.d.ts +20 -18
- package/dist/types/runtime/shield.d.ts +9 -0
- package/dist/types/runtime/shot.d.ts +45 -0
- package/dist/types/runtime/sketch.d.ts +26 -0
- package/dist/types/runtime/styles.d.ts +1 -1
- package/dist/types/runtime/sync.d.ts +1 -0
- package/dist/types/runtime/thread-model.d.ts +18 -0
- package/dist/types/runtime/ticket-form-model.d.ts +57 -0
- package/dist/types/runtime/ticket-form.d.ts +9 -0
- package/dist/types/runtime/toolbar.d.ts +37 -1
- package/dist/types/runtime/ui-scale.d.ts +17 -0
- package/dist/types/runtime/update.d.ts +37 -0
- package/dist/types/runtime/use-debug-context.d.ts +27 -0
- package/dist/types/server/index.d.ts +22 -0
- package/dist/types/server/support.d.ts +20 -0
- package/dist/types/server.d.ts +2 -0
- package/dist/types/vite/plugin.d.ts +15 -0
- package/dist/vite.js +121 -41
- package/package.json +26 -13
- package/src/capture/api.ts +128 -0
- package/src/capture/editor.tsx +178 -0
- package/src/capture/ignore.ts +38 -0
- package/src/capture/index.ts +27 -0
- package/src/capture/picker.tsx +171 -0
- package/src/capture/picture.ts +90 -0
- package/src/capture/surface.tsx +185 -0
- package/src/compiler/annotate.ts +202 -0
- package/src/compiler/babel.d.ts +11 -0
- package/src/compiler/enabled.ts +55 -0
- package/src/compiler/index.ts +2 -0
- package/src/core/index.ts +8 -0
- package/src/core/resolve.ts +307 -0
- package/src/core/types.ts +58 -0
- package/src/debug-context.ts +47 -0
- package/src/dictation.ts +17 -0
- package/src/embed/controller.ts +215 -0
- package/src/embed/index.ts +68 -0
- package/src/embed/options.ts +100 -0
- package/src/help/index.ts +38 -0
- package/src/help/panel-index.ts +17 -0
- package/src/help/panel-styles.ts +123 -0
- package/src/help/panel.ts +840 -0
- package/src/help/pill-styles.ts +75 -0
- package/src/help/pill.ts +338 -0
- package/src/help/react.tsx +143 -0
- package/src/help/requests.ts +111 -0
- package/src/help/types.ts +124 -0
- package/src/help/wire.ts +55 -0
- package/src/native-wire.ts +124 -0
- package/src/native.ts +282 -0
- package/src/react.ts +27 -0
- package/src/replay.ts +13 -0
- package/src/runtime/api.ts +531 -0
- package/src/runtime/attachment-model.ts +42 -0
- package/src/runtime/attachments.tsx +1260 -0
- package/src/runtime/boot.ts +95 -0
- package/src/runtime/browser-label.ts +31 -0
- package/src/runtime/build-id.ts +10 -0
- package/src/runtime/capture-ui.tsx +306 -0
- package/src/runtime/capture.ts +508 -0
- package/src/runtime/clarity-model.ts +634 -0
- package/src/runtime/clarity-ui.tsx +454 -0
- package/src/runtime/client.ts +151 -0
- package/src/runtime/composer-size.tsx +146 -0
- package/src/runtime/dashboard.tsx +1130 -0
- package/src/runtime/debug-context-core.ts +681 -0
- package/src/runtime/debug-context-host.ts +56 -0
- package/src/runtime/debug-context-model.ts +1319 -0
- package/src/runtime/debug-context-redact.ts +259 -0
- package/src/runtime/debug-context.ts +613 -0
- package/src/runtime/details-model.ts +30 -0
- package/src/runtime/details.tsx +395 -0
- package/src/runtime/dictation-engine.ts +616 -0
- package/src/runtime/dictation-model.ts +79 -0
- package/src/runtime/dictation-ui.tsx +363 -0
- package/src/runtime/dictation.ts +271 -0
- package/src/runtime/dock.ts +108 -0
- package/src/runtime/draft-dock.tsx +124 -0
- package/src/runtime/draft-model.ts +251 -0
- package/src/runtime/draft-store.ts +149 -0
- package/src/runtime/edits.tsx +165 -0
- package/src/runtime/font.ts +42 -0
- package/src/runtime/format.ts +129 -0
- package/src/runtime/freshness.ts +23 -0
- package/src/runtime/handoff.ts +76 -0
- package/src/runtime/host-watch.ts +64 -0
- package/src/runtime/icons.tsx +295 -0
- package/src/runtime/idle.ts +20 -0
- package/src/runtime/index.ts +11 -0
- package/src/runtime/ink.ts +6 -0
- package/src/runtime/kind-model.ts +20 -0
- package/src/runtime/live-model.ts +32 -0
- package/src/runtime/loader.tsx +87 -0
- package/src/runtime/markup-editor.tsx +1333 -0
- package/src/runtime/markup-model.ts +485 -0
- package/src/runtime/markup.ts +382 -0
- package/src/runtime/members.ts +58 -0
- package/src/runtime/mention-parse.ts +56 -0
- package/src/runtime/mentions.tsx +205 -0
- package/src/runtime/page-background.ts +51 -0
- package/src/runtime/page-image.ts +1592 -0
- package/src/runtime/placement.ts +336 -0
- package/src/runtime/record.ts +402 -0
- package/src/runtime/redact.ts +30 -0
- package/src/runtime/related.ts +34 -0
- package/src/runtime/render-page.ts +2 -0
- package/src/runtime/replay-model.ts +232 -0
- package/src/runtime/replay.ts +173 -0
- package/src/runtime/runtime-version.ts +7 -0
- package/src/runtime/runtime.tsx +51 -0
- package/src/runtime/screen.ts +182 -0
- package/src/runtime/screenshot.ts +260 -0
- package/src/runtime/session-source.ts +112 -0
- package/src/runtime/session.ts +108 -0
- package/src/runtime/shield.ts +34 -0
- package/src/runtime/shot.tsx +422 -0
- package/src/runtime/sketch.ts +139 -0
- package/src/runtime/styles.ts +1541 -0
- package/src/runtime/sync.ts +300 -0
- package/src/runtime/thread-model.ts +71 -0
- package/src/runtime/ticket-form-model.ts +128 -0
- package/src/runtime/ticket-form.tsx +342 -0
- package/src/runtime/toolbar.tsx +4427 -0
- package/src/runtime/ui-scale.ts +75 -0
- package/src/runtime/update.ts +98 -0
- package/src/runtime/use-debug-context.ts +81 -0
- package/src/server/index.ts +133 -0
- package/src/server/support.ts +499 -0
- package/src/server.ts +4 -0
- package/src/vite/plugin.ts +102 -0
- package/src/vite.ts +1 -0
- package/dist/chunks/chunk-CSKXLF33.js +0 -153
- package/dist/chunks/chunk-HUM5FQII.js +0 -160
- package/dist/chunks/render-page-76MLIT6M.js +0 -1421
- package/dist/chunks/runtime-JX75TKER.js +0 -9267
- package/dist/chunks/screenshot-66ARTBGR.js +0 -12
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { createResolver, type Resolved } from "./capture.js";
|
|
3
|
+
import type { Thread } from "./client.js";
|
|
4
|
+
import type { PlacedStatus } from "./format.js";
|
|
5
|
+
import { whenIdle, type IdleDeadline } from "./idle.js";
|
|
6
|
+
import type { Here } from "./screen.js";
|
|
7
|
+
import { sketchBox } from "./sketch.js";
|
|
8
|
+
|
|
9
|
+
export type Box = { top: number; left: number; width: number; height: number };
|
|
10
|
+
/** `box` is the anchored element's box, set while the thread is placed. */
|
|
11
|
+
export type Placed = {
|
|
12
|
+
thread: Thread;
|
|
13
|
+
status: PlacedStatus;
|
|
14
|
+
top?: number;
|
|
15
|
+
left?: number;
|
|
16
|
+
box?: Box;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Above this many elements, the page is only watched with a MutationObserver while the comments panel is open. */
|
|
20
|
+
const LARGE_PAGE_ELEMENTS = 3000;
|
|
21
|
+
/** A resolve someone is waiting on (panel opened, page or threads changed) starts within this many ms. */
|
|
22
|
+
const URGENT_MS = 100;
|
|
23
|
+
/** A resolve after the page changed underneath starts within this many ms. */
|
|
24
|
+
const BACKGROUND_MS = 1000;
|
|
25
|
+
/** Changes are batched this long before anchors are resolved again. */
|
|
26
|
+
const SETTLE_MS = 150;
|
|
27
|
+
/** Resolving may run this long per slice even when the page never goes idle and the slice was forced by its timeout. */
|
|
28
|
+
const SLICE_MS = 8;
|
|
29
|
+
/** Without a MutationObserver, anchors are re-checked this often while pins are on screen. */
|
|
30
|
+
const RECHECK_MS = 10_000;
|
|
31
|
+
/** Attributes that can change which element an anchor resolves to, or whether it counts as hidden. */
|
|
32
|
+
const RESOLVE_ATTRIBUTES = [
|
|
33
|
+
"data-cs-cmp",
|
|
34
|
+
"data-cs-file",
|
|
35
|
+
"data-cs-anchor",
|
|
36
|
+
"data-testid",
|
|
37
|
+
"data-test",
|
|
38
|
+
"id",
|
|
39
|
+
"role",
|
|
40
|
+
"aria-label",
|
|
41
|
+
"aria-labelledby",
|
|
42
|
+
"aria-hidden",
|
|
43
|
+
"hidden",
|
|
44
|
+
"open",
|
|
45
|
+
"type",
|
|
46
|
+
"placeholder",
|
|
47
|
+
"alt",
|
|
48
|
+
];
|
|
49
|
+
/** Attributes that only move or resize elements. */
|
|
50
|
+
const LAYOUT_ATTRIBUTES = ["class", "style"];
|
|
51
|
+
|
|
52
|
+
export function overlayBounds(host: HTMLElement) {
|
|
53
|
+
if (host !== document.body) return { width: host.clientWidth, height: host.clientHeight };
|
|
54
|
+
const view = window.visualViewport;
|
|
55
|
+
return view
|
|
56
|
+
? { width: view.width, height: view.height }
|
|
57
|
+
: { width: window.innerWidth, height: window.innerHeight };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function overlayHost() {
|
|
61
|
+
const dialogs = [...document.querySelectorAll("dialog")].filter(
|
|
62
|
+
(node): node is HTMLDialogElement =>
|
|
63
|
+
node instanceof HTMLDialogElement && node.open && !node.closest("#cs-root"),
|
|
64
|
+
);
|
|
65
|
+
return dialogs.at(-1) ?? document.body;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function placeAgainst(element: Element, host: HTMLElement): Box | null {
|
|
69
|
+
const rect = element.getBoundingClientRect();
|
|
70
|
+
if (rect.width < 1 && rect.height < 1) return null;
|
|
71
|
+
if (host === document.body)
|
|
72
|
+
return { top: rect.top, left: rect.left, width: rect.width, height: rect.height };
|
|
73
|
+
const hostRect = host.getBoundingClientRect();
|
|
74
|
+
return {
|
|
75
|
+
top: rect.top - hostRect.top + host.scrollTop,
|
|
76
|
+
left: rect.left - hostRect.left + host.scrollLeft,
|
|
77
|
+
width: rect.width,
|
|
78
|
+
height: rect.height,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function sameBoxes(a: Map<string, Box | null>, b: Map<string, Box | null>) {
|
|
83
|
+
if (a.size !== b.size) return false;
|
|
84
|
+
for (const [id, box] of a) {
|
|
85
|
+
const other = b.get(id);
|
|
86
|
+
if (box === other) continue;
|
|
87
|
+
if (
|
|
88
|
+
!box ||
|
|
89
|
+
!other ||
|
|
90
|
+
box.top !== other.top ||
|
|
91
|
+
box.left !== other.left ||
|
|
92
|
+
box.width !== other.width ||
|
|
93
|
+
box.height !== other.height
|
|
94
|
+
) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function sameResolved(a: Map<string, Resolved>, b: Map<string, Resolved>) {
|
|
102
|
+
if (a.size !== b.size) return false;
|
|
103
|
+
for (const [id, { resolution, element }] of a) {
|
|
104
|
+
const other = b.get(id);
|
|
105
|
+
if (!other || other.element !== element || other.resolution.status !== resolution.status)
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The open <dialog> the toolbar renders inside, since a modal's top layer covers everything else, or <body>.
|
|
113
|
+
* Instead of a MutationObserver on the whole page: showModal() moves focus into the dialog synchronously, so
|
|
114
|
+
* focusin moves the toolbar before the click that opened it returns; toggle/close cover the rest, and clicks and
|
|
115
|
+
* keys re-check once their handlers have run.
|
|
116
|
+
*/
|
|
117
|
+
export function useOverlayHost(enabled: boolean): HTMLElement | null {
|
|
118
|
+
const [host, setHost] = useState<HTMLElement | null>(null);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (!enabled) return;
|
|
121
|
+
let timer = 0;
|
|
122
|
+
const sync = () => setHost(overlayHost());
|
|
123
|
+
const afterHandlers = () => {
|
|
124
|
+
window.clearTimeout(timer);
|
|
125
|
+
timer = window.setTimeout(sync, 0);
|
|
126
|
+
};
|
|
127
|
+
sync();
|
|
128
|
+
const documentEvents = ["focusin", "toggle", "close"];
|
|
129
|
+
const windowEvents = ["pointerup", "keyup"];
|
|
130
|
+
for (const type of documentEvents) document.addEventListener(type, sync, true);
|
|
131
|
+
for (const type of windowEvents)
|
|
132
|
+
window.addEventListener(type, afterHandlers, { capture: true, passive: true });
|
|
133
|
+
return () => {
|
|
134
|
+
window.clearTimeout(timer);
|
|
135
|
+
for (const type of documentEvents) document.removeEventListener(type, sync, true);
|
|
136
|
+
for (const type of windowEvents) window.removeEventListener(type, afterHandlers, true);
|
|
137
|
+
};
|
|
138
|
+
}, [enabled]);
|
|
139
|
+
return host;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
type View = { key: string; resolved: Map<string, Resolved>; boxes: Map<string, Box | null> };
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Where each thread's anchor is on this page. Anchors are resolved only while `active` (the panel is open or this
|
|
146
|
+
* page's pins are shown) and the tab is visible, only for open threads on this screen, in idle-time slices. The page
|
|
147
|
+
* is watched with a MutationObserver only while the panel is open or the page is small; otherwise clicks, keys,
|
|
148
|
+
* focus and a slow re-check trigger re-resolves. Scrolling and resizing only re-measure elements already found.
|
|
149
|
+
*/
|
|
150
|
+
export function usePlacement({
|
|
151
|
+
threads,
|
|
152
|
+
here,
|
|
153
|
+
host,
|
|
154
|
+
active,
|
|
155
|
+
panelOpen,
|
|
156
|
+
visible,
|
|
157
|
+
}: {
|
|
158
|
+
threads: Thread[];
|
|
159
|
+
here: Here;
|
|
160
|
+
host: HTMLElement;
|
|
161
|
+
active: boolean;
|
|
162
|
+
panelOpen: boolean;
|
|
163
|
+
visible: boolean;
|
|
164
|
+
}) {
|
|
165
|
+
const [view, setView] = useState<View>(() => ({
|
|
166
|
+
key: here.key,
|
|
167
|
+
resolved: new Map(),
|
|
168
|
+
boxes: new Map(),
|
|
169
|
+
}));
|
|
170
|
+
const elements = useRef(new Map<string, Element>());
|
|
171
|
+
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (!active || !visible) return;
|
|
174
|
+
let cancelPass: (() => void) | null = null;
|
|
175
|
+
let dirty = false;
|
|
176
|
+
let settle = 0;
|
|
177
|
+
let frame = 0;
|
|
178
|
+
let recheck = 0;
|
|
179
|
+
|
|
180
|
+
const measure = () => {
|
|
181
|
+
const boxes = new Map<string, Box | null>();
|
|
182
|
+
let lost = false;
|
|
183
|
+
for (const [id, element] of elements.current) {
|
|
184
|
+
if (!element.isConnected) lost = true;
|
|
185
|
+
boxes.set(id, element.isConnected ? placeAgainst(element, host) : null);
|
|
186
|
+
}
|
|
187
|
+
return { boxes, lost };
|
|
188
|
+
};
|
|
189
|
+
const reposition = () => {
|
|
190
|
+
cancelAnimationFrame(frame);
|
|
191
|
+
frame = requestAnimationFrame(() => {
|
|
192
|
+
const { boxes, lost } = measure();
|
|
193
|
+
setView((current) => (sameBoxes(current.boxes, boxes) ? current : { ...current, boxes }));
|
|
194
|
+
if (lost) schedule();
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
const pass = (timeout: number) => {
|
|
198
|
+
const queue = threads.filter((thread) => thread.status === "open" && here.has(thread));
|
|
199
|
+
const resolved = new Map<string, Resolved>();
|
|
200
|
+
let index = 0;
|
|
201
|
+
const slice = (deadline: IdleDeadline) => {
|
|
202
|
+
if (index === 0) dirty = false;
|
|
203
|
+
const started = performance.now();
|
|
204
|
+
const resolver = createResolver(here.route);
|
|
205
|
+
while (index < queue.length) {
|
|
206
|
+
const thread = queue[index];
|
|
207
|
+
index += 1;
|
|
208
|
+
if (!thread) break;
|
|
209
|
+
resolved.set(thread.id, resolver.resolve(thread.anchor));
|
|
210
|
+
if (deadline.timeRemaining() <= 1 && performance.now() - started >= SLICE_MS) break;
|
|
211
|
+
}
|
|
212
|
+
if (index < queue.length) {
|
|
213
|
+
cancelPass = whenIdle(slice, timeout);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
cancelPass = null;
|
|
217
|
+
elements.current = new Map();
|
|
218
|
+
for (const [id, { resolution, element }] of resolved) {
|
|
219
|
+
if (element && (resolution.status === "pinned" || resolution.status === "drifted"))
|
|
220
|
+
elements.current.set(id, element);
|
|
221
|
+
}
|
|
222
|
+
const { boxes } = measure();
|
|
223
|
+
setView((current) =>
|
|
224
|
+
current.key === here.key &&
|
|
225
|
+
sameResolved(current.resolved, resolved) &&
|
|
226
|
+
sameBoxes(current.boxes, boxes)
|
|
227
|
+
? current
|
|
228
|
+
: { key: here.key, resolved, boxes },
|
|
229
|
+
);
|
|
230
|
+
watch();
|
|
231
|
+
if (dirty) schedule();
|
|
232
|
+
};
|
|
233
|
+
cancelPass = whenIdle(slice, timeout);
|
|
234
|
+
};
|
|
235
|
+
const schedule = () => {
|
|
236
|
+
if (cancelPass) {
|
|
237
|
+
dirty = true;
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
window.clearTimeout(settle);
|
|
241
|
+
settle = window.setTimeout(() => pass(BACKGROUND_MS), SETTLE_MS);
|
|
242
|
+
};
|
|
243
|
+
const onMutations = (records: MutationRecord[]) => {
|
|
244
|
+
const toolbar = document.getElementById("cs-root");
|
|
245
|
+
let move = false;
|
|
246
|
+
for (const record of records) {
|
|
247
|
+
if (toolbar?.contains(record.target)) continue;
|
|
248
|
+
if (
|
|
249
|
+
record.type === "attributes" &&
|
|
250
|
+
LAYOUT_ATTRIBUTES.includes(record.attributeName ?? "")
|
|
251
|
+
) {
|
|
252
|
+
move = true;
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
schedule();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (move) reposition();
|
|
259
|
+
};
|
|
260
|
+
const watch = () => {
|
|
261
|
+
const large = !panelOpen && document.getElementsByTagName("*").length > LARGE_PAGE_ELEMENTS;
|
|
262
|
+
if (large && !recheck) recheck = window.setInterval(schedule, RECHECK_MS);
|
|
263
|
+
if (!large && recheck) {
|
|
264
|
+
window.clearInterval(recheck);
|
|
265
|
+
recheck = 0;
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const observer = new MutationObserver(onMutations);
|
|
269
|
+
const watchingAll =
|
|
270
|
+
panelOpen || document.getElementsByTagName("*").length <= LARGE_PAGE_ELEMENTS;
|
|
271
|
+
if (watchingAll) {
|
|
272
|
+
observer.observe(document.body, {
|
|
273
|
+
childList: true,
|
|
274
|
+
subtree: true,
|
|
275
|
+
attributes: true,
|
|
276
|
+
attributeFilter: [...RESOLVE_ATTRIBUTES, ...LAYOUT_ATTRIBUTES],
|
|
277
|
+
});
|
|
278
|
+
} else if (!recheck) {
|
|
279
|
+
recheck = window.setInterval(schedule, RECHECK_MS);
|
|
280
|
+
}
|
|
281
|
+
const onInteraction = (event: Event) => {
|
|
282
|
+
if (!(event.target instanceof Element && event.target.closest("#cs-root"))) schedule();
|
|
283
|
+
};
|
|
284
|
+
const resizes = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(reposition);
|
|
285
|
+
resizes?.observe(document.documentElement);
|
|
286
|
+
const passive = { capture: true, passive: true };
|
|
287
|
+
window.addEventListener("scroll", reposition, passive);
|
|
288
|
+
window.addEventListener("resize", reposition, passive);
|
|
289
|
+
window.addEventListener("pointerup", onInteraction, passive);
|
|
290
|
+
window.addEventListener("keyup", onInteraction, passive);
|
|
291
|
+
document.addEventListener("focusin", onInteraction, true);
|
|
292
|
+
pass(URGENT_MS);
|
|
293
|
+
watch();
|
|
294
|
+
return () => {
|
|
295
|
+
cancelPass?.();
|
|
296
|
+
window.clearTimeout(settle);
|
|
297
|
+
window.clearInterval(recheck);
|
|
298
|
+
cancelAnimationFrame(frame);
|
|
299
|
+
observer.disconnect();
|
|
300
|
+
resizes?.disconnect();
|
|
301
|
+
window.removeEventListener("scroll", reposition, true);
|
|
302
|
+
window.removeEventListener("resize", reposition, true);
|
|
303
|
+
window.removeEventListener("pointerup", onInteraction, true);
|
|
304
|
+
window.removeEventListener("keyup", onInteraction, true);
|
|
305
|
+
document.removeEventListener("focusin", onInteraction, true);
|
|
306
|
+
};
|
|
307
|
+
}, [active, here, host, panelOpen, threads, visible]);
|
|
308
|
+
|
|
309
|
+
const placed = useMemo(
|
|
310
|
+
() =>
|
|
311
|
+
threads.map((thread): Placed => {
|
|
312
|
+
if (thread.status === "resolved") return { thread, status: "resolved" };
|
|
313
|
+
if (!here.has(thread)) return { thread, status: "unavailable" };
|
|
314
|
+
const found = view.key === here.key ? view.resolved.get(thread.id) : undefined;
|
|
315
|
+
if (!found) return { thread, status: "pending" };
|
|
316
|
+
const { status } = found.resolution;
|
|
317
|
+
if (status !== "pinned" && status !== "drifted") return { thread, status };
|
|
318
|
+
const box = view.boxes.get(thread.id);
|
|
319
|
+
if (!box) return { thread, status: "hidden" };
|
|
320
|
+
// A drawing's pin sits at the drawing's top-right corner, not the element's.
|
|
321
|
+
const ink = thread.anchor.sketch ? sketchBox(thread.anchor.sketch) : null;
|
|
322
|
+
return ink
|
|
323
|
+
? { thread, status, top: box.top + ink.top, left: box.left + ink.right, box }
|
|
324
|
+
: { thread, status, top: box.top, left: box.left + box.width, box };
|
|
325
|
+
}),
|
|
326
|
+
[here, threads, view],
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
/** The element a thread was last found on, while it's still on the page. */
|
|
330
|
+
const elementFor = useCallback((threadId: string) => {
|
|
331
|
+
const element = elements.current.get(threadId);
|
|
332
|
+
return element?.isConnected ? element : null;
|
|
333
|
+
}, []);
|
|
334
|
+
|
|
335
|
+
return { placed, elementFor };
|
|
336
|
+
}
|