@devaccel-labs/comment-service 0.8.0 → 0.9.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.
@@ -208,12 +208,13 @@ function overlayFor(element) {
208
208
  const role = node.getAttribute("role") || (node.tagName === "DIALOG" ? "dialog" : "dialog");
209
209
  return { role, name: labelFor(node) || "dialog" };
210
210
  }
211
- function fiberNames(element) {
211
+ function fiberOf(element) {
212
212
  const key2 = Object.keys(element).find((name) => name.startsWith("__reactFiber$") || name.startsWith("__reactInternalInstance$"));
213
- if (!key2)
214
- return [];
213
+ return key2 ? element[key2] : void 0;
214
+ }
215
+ function fiberNames(element) {
215
216
  const names = [];
216
- let fiber = element[key2];
217
+ let fiber = fiberOf(element);
217
218
  for (let guard = 0; fiber && guard < 40; guard++) {
218
219
  const type = fiber.type;
219
220
  if (typeof type === "function") {
@@ -222,10 +223,30 @@ function fiberNames(element) {
222
223
  if (name && !name.startsWith("_"))
223
224
  names.push(name);
224
225
  }
225
- fiber = fiber.return;
226
+ fiber = fiber.return ?? void 0;
226
227
  }
227
228
  return names.reverse();
228
229
  }
230
+ function fiberStamps(element) {
231
+ const stamps = [];
232
+ let fiber = fiberOf(element);
233
+ for (let guard = 0; fiber && guard < 2e3; guard++) {
234
+ const props = fiber.memoizedProps;
235
+ const name = props && typeof props === "object" ? props["data-cs-cmp"] : void 0;
236
+ if (typeof name === "string" && name) {
237
+ const file = props?.["data-cs-file"];
238
+ stamps.push({ name, file: typeof file === "string" ? file : null });
239
+ }
240
+ fiber = fiber.return ?? void 0;
241
+ }
242
+ return stamps;
243
+ }
244
+ function bodyChild(element) {
245
+ let current = element;
246
+ while (current?.parentElement && current.parentElement !== current.ownerDocument.body)
247
+ current = current.parentElement;
248
+ return current?.parentElement ? current : null;
249
+ }
229
250
  function obviouslyHidden(element) {
230
251
  const dialog = element.closest("dialog");
231
252
  if (dialog instanceof HTMLDialogElement && !dialog.open)
@@ -253,6 +274,18 @@ function ownership(element) {
253
274
  }
254
275
  current = current.parentElement;
255
276
  }
277
+ const top = bodyChild(element);
278
+ if (top && top !== element && (!owner || !top.contains(owner))) {
279
+ const stamps = fiberStamps(element);
280
+ if (stamps.length && (!owner || stamps[0].name !== owner.getAttribute("data-cs-cmp"))) {
281
+ return {
282
+ component: stamps[0],
283
+ stack: stamps.map((stamp) => stamp.name).reverse(),
284
+ componentRoot: false,
285
+ owner: top
286
+ };
287
+ }
288
+ }
256
289
  if (owner) {
257
290
  return {
258
291
  component: { name: owner.getAttribute("data-cs-cmp") || "component", file: owner.getAttribute("data-cs-file") },
@@ -349,7 +382,7 @@ function elementAt(x, y) {
349
382
  return null;
350
383
  }
351
384
  function describeElement(element) {
352
- const name = element.closest("[data-cs-cmp]")?.getAttribute("data-cs-cmp");
385
+ const name = ownership(element).component?.name;
353
386
  const label = labelFor(element);
354
387
  if (name && label)
355
388
  return `${name} \u2014 ${label}`;
@@ -9,7 +9,7 @@ import {
9
9
  resolveInDocument,
10
10
  runtimeVersion,
11
11
  stripHandoffFromUrl
12
- } from "./chunk-QSHFAMXM.js";
12
+ } from "./chunk-KKE43QYT.js";
13
13
 
14
14
  // ../react/dist/runtime.js
15
15
  import { jsx as _jsx7 } from "react/jsx-runtime";
@@ -491,10 +491,7 @@ function MentionTextarea({ value, onValueChange, members, selfEmail, onMention,
491
491
  } }), open && rect && root ? createPortal(_jsx4("div", { className: "cs-people cs-hit", role: "listbox", "data-cs-ui": "mention-list", style: {
492
492
  left: Math.min(rect.left, window.innerWidth - 272),
493
493
  ...below ? { top: rect.bottom + 6 } : { bottom: window.innerHeight - rect.top + 6 }
494
- }, children: matches.map((member) => _jsxs4("div", { role: "option", "aria-selected": member === selected, "data-cs-ui": "mention-option", className: member === selected ? "cs-person is-on" : "cs-person", onMouseDown: (event) => {
495
- event.preventDefault();
496
- insert(member);
497
- }, onMouseEnter: () => setIndex(matches.indexOf(member)), children: [_jsx4("span", { className: "cs-avatar", style: { width: 24, height: 24, fontSize: 10, background: colorFor(member.name) }, children: initials(member.name) }), _jsxs4("span", { className: "cs-person-text", children: [_jsx4("strong", { children: member.name }), _jsx4("small", { children: member.email })] })] }, member.id)) }), root) : null] });
494
+ }, children: matches.map((member) => _jsxs4("div", { role: "option", "aria-selected": member === selected, "data-cs-ui": "mention-option", className: member === selected ? "cs-person is-on" : "cs-person", onClick: () => insert(member), onMouseEnter: () => setIndex(matches.indexOf(member)), children: [_jsx4("span", { className: "cs-avatar", style: { width: 24, height: 24, fontSize: 10, background: colorFor(member.name) }, children: initials(member.name) }), _jsxs4("span", { className: "cs-person-text", children: [_jsx4("strong", { children: member.name }), _jsx4("small", { children: member.email })] })] }, member.id)) }), root) : null] });
498
495
  }
499
496
 
500
497
  // ../../node_modules/modern-screenshot/dist/index.mjs
@@ -2921,38 +2918,73 @@ function ToolbarApp({ sessionPath = "/api/comment-service/session", pollMs = 4e3
2921
2918
  savePrefs({ ...prefs, pinsHidden: next });
2922
2919
  say(next ? "Pins hidden \xB7 H to show" : "Pins shown");
2923
2920
  }, [pinsHidden, prefs, savePrefs, say]);
2924
- useEffect2(() => {
2925
- const onKey = (event) => {
2926
- if (event.defaultPrevented)
2927
- return;
2928
- if (event.key === "Escape") {
2929
- if (mode !== "browse" || draft || surface !== "none") {
2930
- setMode("browse");
2931
- setDraft(null);
2932
- setReattachId(null);
2933
- setSurface("none");
2934
- setConfirmReset(false);
2935
- setRenaming(null);
2936
- }
2937
- return;
2921
+ const onKey = useRef3(() => {
2922
+ });
2923
+ onKey.current = (event) => {
2924
+ if (event.defaultPrevented)
2925
+ return;
2926
+ if (event.key === "Escape") {
2927
+ if (mode !== "browse" || draft || surface !== "none") {
2928
+ event.stopImmediatePropagation();
2929
+ setMode("browse");
2930
+ setDraft(null);
2931
+ setReattachId(null);
2932
+ setSurface("none");
2933
+ setConfirmReset(false);
2934
+ setRenaming(null);
2938
2935
  }
2939
- if (!member || event.metaKey || event.ctrlKey || event.altKey || isTyping(event.target))
2940
- return;
2941
- const key = event.key.toLowerCase();
2942
- if (key === "c")
2943
- startComment();
2944
- else if (key === "i") {
2945
- setView(prefs.view ?? "list");
2946
- setSurface((current) => current === "inbox" ? "none" : "inbox");
2947
- } else if (key === "h")
2948
- togglePins();
2949
- else
2936
+ return;
2937
+ }
2938
+ if (!member || event.metaKey || event.ctrlKey || event.altKey || isTyping(event.target))
2939
+ return;
2940
+ const key = event.key.toLowerCase();
2941
+ if (key === "c")
2942
+ startComment();
2943
+ else if (key === "i") {
2944
+ setView(prefs.view ?? "list");
2945
+ setSurface((current) => current === "inbox" ? "none" : "inbox");
2946
+ } else if (key === "h")
2947
+ togglePins();
2948
+ else
2949
+ return;
2950
+ event.preventDefault();
2951
+ event.stopImmediatePropagation();
2952
+ };
2953
+ useEffect2(() => {
2954
+ const inToolbar = (event) => event.target instanceof Element && Boolean(event.target.closest("#cs-root"));
2955
+ const fromPage = (event) => {
2956
+ if (!inToolbar(event))
2957
+ onKey.current(event);
2958
+ };
2959
+ const fromToolbar = (event) => {
2960
+ if (inToolbar(event))
2961
+ onKey.current(event);
2962
+ };
2963
+ window.addEventListener("keydown", fromPage, true);
2964
+ document.addEventListener("keydown", fromToolbar);
2965
+ return () => {
2966
+ window.removeEventListener("keydown", fromPage, true);
2967
+ document.removeEventListener("keydown", fromToolbar);
2968
+ };
2969
+ }, []);
2970
+ useEffect2(() => {
2971
+ const textControl = "input, textarea, select, [contenteditable]";
2972
+ const shield = (event) => {
2973
+ const target = event.target;
2974
+ if (!(target instanceof Element) || !target.closest("#cs-root"))
2950
2975
  return;
2951
- event.preventDefault();
2976
+ event.stopPropagation();
2977
+ if (event.type === "mousedown" && !target.closest(textControl))
2978
+ event.preventDefault();
2952
2979
  };
2953
- document.addEventListener("keydown", onKey);
2954
- return () => document.removeEventListener("keydown", onKey);
2955
- }, [draft, member, mode, prefs.view, startComment, surface, togglePins]);
2980
+ const types = ["pointerdown", "mousedown", "touchstart"];
2981
+ for (const type of types)
2982
+ window.addEventListener(type, shield, true);
2983
+ return () => {
2984
+ for (const type of types)
2985
+ window.removeEventListener(type, shield, true);
2986
+ };
2987
+ }, []);
2956
2988
  useEffect2(() => {
2957
2989
  if (mode === "browse") {
2958
2990
  setHighlight(null);
@@ -3009,12 +3041,24 @@ function ToolbarApp({ sessionPath = "/api/comment-service/session", pollMs = 4e3
3009
3041
  setMode("browse");
3010
3042
  setError("");
3011
3043
  };
3044
+ const swallow = (event) => {
3045
+ if (event.target instanceof Element && event.target.closest("#cs-root"))
3046
+ return;
3047
+ if (!event.type.startsWith("touch"))
3048
+ event.preventDefault();
3049
+ event.stopPropagation();
3050
+ };
3051
+ const presses = ["pointerdown", "mousedown", "pointerup", "mouseup", "touchstart", "touchend"];
3012
3052
  document.addEventListener("mousemove", onMove, true);
3013
- document.addEventListener("click", onClick, true);
3053
+ window.addEventListener("click", onClick, true);
3054
+ for (const type of presses)
3055
+ window.addEventListener(type, swallow, true);
3014
3056
  return () => {
3015
3057
  document.documentElement.style.cursor = "";
3016
3058
  document.removeEventListener("mousemove", onMove, true);
3017
- document.removeEventListener("click", onClick, true);
3059
+ window.removeEventListener("click", onClick, true);
3060
+ for (const type of presses)
3061
+ window.removeEventListener(type, swallow, true);
3018
3062
  };
3019
3063
  }, [member, mode, portal, reattachId, refresh, say, session]);
3020
3064
  useEffect2(() => {
package/dist/react.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  captureHandoff,
3
3
  runtimeVersion
4
- } from "./chunks/chunk-QSHFAMXM.js";
4
+ } from "./chunks/chunk-KKE43QYT.js";
5
5
 
6
6
  // ../react/dist/loader.js
7
7
  import { useEffect, useRef } from "react";
8
8
  captureHandoff();
9
- var bundled = () => import("./chunks/runtime-2PTKNDUT.js");
9
+ var bundled = () => import("./chunks/runtime-2ZDPCFHS.js");
10
10
  async function serviceUrl(sessionPath) {
11
11
  try {
12
12
  const response = await fetch(sessionPath, {
@@ -1,7 +1,10 @@
1
1
  export declare function toProjectPath(filename: string, root: string): string;
2
2
  /**
3
- * Stamp the root DOM node of each component with a stable file + component name.
3
+ * Stamp the root node of each component of each component with a stable file + component name.
4
4
  * Line numbers are intentionally not included: moving the component in the file
5
- * or on the page must not change the stamp.
5
+ * or on the page must not change the stamp. A root that is itself a component
6
+ * (`<DropdownMenu>`, `<Menu.Root>`) gets the stamp as a prop: it reaches the DOM when
7
+ * the component spreads its props, and the toolbar reads it from React's tree when
8
+ * the component renders into a portal.
6
9
  */
7
10
  export declare function annotateSource(code: string, filename: string, root: string): string;
package/dist/vite.js CHANGED
@@ -58,6 +58,16 @@ function setAttr(path, name, value) {
58
58
  return;
59
59
  path.node.openingElement.attributes.push(t.jsxAttribute(t.jsxIdentifier(name), t.stringLiteral(value)));
60
60
  }
61
+ var unstampable = /^(?:React\.)?(?:Fragment|Suspense|StrictMode|Profiler|Activity|ViewTransition)$|\.(?:Provider|Consumer)$/;
62
+ function jsxName(name) {
63
+ if (t.isJSXIdentifier(name))
64
+ return name.name;
65
+ if (t.isJSXMemberExpression(name)) {
66
+ const object = jsxName(name.object);
67
+ return object ? `${object}.${name.property.name}` : null;
68
+ }
69
+ return null;
70
+ }
61
71
  function annotateSource(code, filename, root) {
62
72
  if (!filename.match(/\.[jt]sx$/))
63
73
  return code;
@@ -77,10 +87,8 @@ function annotateSource(code, filename, root) {
77
87
  let changed = false;
78
88
  traverse()(ast, {
79
89
  JSXElement(path) {
80
- const opening = path.node.openingElement.name;
81
- if (!t.isJSXIdentifier(opening))
82
- return;
83
- if (opening.name[0] !== opening.name[0].toLowerCase())
90
+ const tag = jsxName(path.node.openingElement.name);
91
+ if (!tag || unstampable.test(tag))
84
92
  return;
85
93
  if (!isRootJsx(path))
86
94
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devaccel-labs/comment-service",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "In-UI component comments for TanStack Start on Cloudflare. Anchors survive redeploys.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -43,8 +43,8 @@
43
43
  },
44
44
  "devDependencies": {
45
45
  "esbuild": "^0.28.1",
46
- "@devaccel/comment-core": "0.1.0",
47
46
  "@devaccel/comment-compiler": "0.1.0",
47
+ "@devaccel/comment-core": "0.1.0",
48
48
  "@devaccel/comment-react": "0.1.0",
49
49
  "@devaccel/comment-server": "0.1.0",
50
50
  "@devaccel/comment-start": "0.1.0"