review-lens-react 0.1.0 → 0.1.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.
@@ -1,97 +1,97 @@
1
- import { jsx as c, jsxs as k, Fragment as q } from "react/jsx-runtime";
2
- import { createContext as D, useMemo as x, useState as E, useCallback as L, useEffect as R, useContext as H } from "react";
3
- const W = [
1
+ import { jsx as i, jsxs as f, Fragment as Y } from "react/jsx-runtime";
2
+ import { createContext as V, useMemo as M, useState as R, useCallback as P, useEffect as T, useContext as Z, useRef as ee, useLayoutEffect as te } from "react";
3
+ const ne = [
4
4
  "https://www.googleapis.com/auth/spreadsheets",
5
5
  "https://www.googleapis.com/auth/userinfo.email"
6
- ].join(" "), _ = "https://www.googleapis.com/oauth2/v3/userinfo";
7
- function X(e) {
6
+ ].join(" "), oe = "https://www.googleapis.com/oauth2/v3/userinfo";
7
+ function re(e) {
8
8
  const n = e.feedbackSheetName ?? "Feedback", t = e.usersSheetName ?? "Users";
9
9
  let r, o;
10
- async function s() {
11
- return r ?? (r = Z(e.googleClientId)), r;
10
+ async function c() {
11
+ return r ?? (r = ce(e.googleClientId)), r;
12
12
  }
13
- async function v(i, l) {
14
- const a = await s(), d = await fetch(
15
- `https://sheets.googleapis.com/v4/spreadsheets/${e.spreadsheetId}${i}`,
13
+ async function h(a, d) {
14
+ const l = await c(), u = await fetch(
15
+ `https://sheets.googleapis.com/v4/spreadsheets/${e.spreadsheetId}${a}`,
16
16
  {
17
- ...l,
17
+ ...d,
18
18
  headers: {
19
- Authorization: `Bearer ${a}`,
19
+ Authorization: `Bearer ${l}`,
20
20
  "Content-Type": "application/json",
21
- ...l == null ? void 0 : l.headers
21
+ ...d == null ? void 0 : d.headers
22
22
  }
23
23
  }
24
24
  );
25
- if (!d.ok)
26
- throw new Error(`Google Sheets request failed with ${d.status}`);
27
- return d.json();
25
+ if (!u.ok)
26
+ throw new Error(`Google Sheets request failed with ${u.status}`);
27
+ return u.json();
28
28
  }
29
- async function b(i) {
30
- return (await v(
31
- `/values/${encodeURIComponent(i)}`
29
+ async function w(a) {
30
+ return (await h(
31
+ `/values/${encodeURIComponent(a)}`
32
32
  )).values ?? [];
33
33
  }
34
34
  return {
35
35
  async getCurrentUser() {
36
36
  if (!o) {
37
- const i = await s(), l = await fetch(_, {
38
- headers: { Authorization: `Bearer ${i}` }
37
+ const a = await c(), d = await fetch(oe, {
38
+ headers: { Authorization: `Bearer ${a}` }
39
39
  });
40
- if (!l.ok)
41
- throw new Error(`Google userinfo request failed with ${l.status}`);
42
- o = (await l.json()).email;
40
+ if (!d.ok)
41
+ throw new Error(`Google userinfo request failed with ${d.status}`);
42
+ o = (await d.json()).email;
43
43
  }
44
44
  if (!o)
45
45
  throw new Error("Google account did not return an email address");
46
46
  return { email: o };
47
47
  },
48
- async getPermissions(i) {
49
- const [{ email: l }, a] = await Promise.all([this.getCurrentUser(), b(t)]), d = U(a), y = l.toLowerCase(), u = d.find(
50
- (f) => {
51
- var p;
52
- return ((p = f.email) == null ? void 0 : p.toLowerCase()) === y && f.active !== "false" && (!f.projectKey || f.projectKey === i);
48
+ async getPermissions(a) {
49
+ const [{ email: d }, l] = await Promise.all([this.getCurrentUser(), w(t)]), u = z(l), y = d.toLowerCase(), p = u.find(
50
+ (b) => {
51
+ var m;
52
+ return ((m = b.email) == null ? void 0 : m.toLowerCase()) === y && b.active !== "false" && (!b.projectKey || b.projectKey === a);
53
53
  }
54
54
  );
55
- return V((u == null ? void 0 : u.role) ?? "designer");
55
+ return de((p == null ? void 0 : p.role) ?? "designer");
56
56
  },
57
- async listFeedback(i) {
58
- return U(await b(n)).map(T).filter((a) => a !== null).filter(
59
- (a) => a.projectKey === i.projectKey && a.contentId === i.contentId && a.normalizedPath === i.normalizedPath
60
- ).sort((a, d) => d.createdAt.localeCompare(a.createdAt));
57
+ async listFeedback(a) {
58
+ return z(await w(n)).map(K).filter((l) => l !== null).filter(
59
+ (l) => l.projectKey === a.projectKey && l.contentId === a.contentId && l.normalizedPath === a.normalizedPath
60
+ ).sort((l, u) => u.createdAt.localeCompare(l.createdAt));
61
61
  },
62
- async createFeedback(i) {
63
- const l = (/* @__PURE__ */ new Date()).toISOString(), a = {
64
- ...i,
62
+ async createFeedback(a) {
63
+ const d = (/* @__PURE__ */ new Date()).toISOString(), l = {
64
+ ...a,
65
65
  id: crypto.randomUUID(),
66
66
  status: "open",
67
- createdAt: l,
68
- updatedAt: l
67
+ createdAt: d,
68
+ updatedAt: d
69
69
  };
70
- return await v(`/values/${encodeURIComponent(n)}:append?valueInputOption=RAW`, {
70
+ return await h(`/values/${encodeURIComponent(n)}:append?valueInputOption=RAW`, {
71
71
  method: "POST",
72
- body: JSON.stringify({ values: [Q(a)] })
73
- }), a;
72
+ body: JSON.stringify({ values: [se(l)] })
73
+ }), l;
74
74
  },
75
- async resolveFeedback(i, l) {
76
- const a = await b(n), d = a[0] ?? Y, y = d.indexOf("id"), u = d.indexOf("status"), f = d.indexOf("updatedAt"), p = d.indexOf("resolvedAt"), w = d.indexOf("resolvedBy"), m = a.findIndex((N, A) => A > 0 && N[y] === i);
77
- if (m < 1)
78
- throw new Error(`Feedback ${i} was not found`);
79
- const g = [...a[m]], C = (/* @__PURE__ */ new Date()).toISOString();
80
- g[u] = "resolved", g[f] = C, g[p] = C, g[w] = l, await v(
81
- `/values/${encodeURIComponent(n)}!A${m + 1}:Q${m + 1}?valueInputOption=RAW`,
75
+ async resolveFeedback(a, d) {
76
+ const l = await w(n), u = l[0] ?? ie, y = u.indexOf("id"), p = u.indexOf("status"), b = u.indexOf("updatedAt"), m = u.indexOf("resolvedAt"), k = u.indexOf("resolvedBy"), v = l.findIndex((N, x) => x > 0 && N[y] === a);
77
+ if (v < 1)
78
+ throw new Error(`Feedback ${a} was not found`);
79
+ const S = [...l[v]], F = (/* @__PURE__ */ new Date()).toISOString();
80
+ S[p] = "resolved", S[b] = F, S[m] = F, S[k] = d, await h(
81
+ `/values/${encodeURIComponent(n)}!A${v + 1}:Q${v + 1}?valueInputOption=RAW`,
82
82
  {
83
83
  method: "PUT",
84
- body: JSON.stringify({ values: [g] })
84
+ body: JSON.stringify({ values: [S] })
85
85
  }
86
86
  );
87
- const $ = T(M(d, g));
88
- if (!$)
89
- throw new Error(`Feedback ${i} could not be parsed after resolving`);
90
- return $;
87
+ const C = K(q(u, S));
88
+ if (!C)
89
+ throw new Error(`Feedback ${a} could not be parsed after resolving`);
90
+ return C;
91
91
  }
92
92
  };
93
93
  }
94
- const Y = [
94
+ const ie = [
95
95
  "id",
96
96
  "projectKey",
97
97
  "contentId",
@@ -109,7 +109,7 @@ const Y = [
109
109
  "resolvedAt",
110
110
  "resolvedBy"
111
111
  ];
112
- function Q(e) {
112
+ function se(e) {
113
113
  return [
114
114
  e.id,
115
115
  e.projectKey,
@@ -129,14 +129,14 @@ function Q(e) {
129
129
  e.resolvedBy ?? ""
130
130
  ];
131
131
  }
132
- function U(e) {
132
+ function z(e) {
133
133
  const [n, ...t] = e;
134
- return n ? t.map((r) => M(n, r)) : [];
134
+ return n ? t.map((r) => q(n, r)) : [];
135
135
  }
136
- function M(e, n) {
136
+ function q(e, n) {
137
137
  return Object.fromEntries(e.map((t, r) => [t, n[r] ?? ""]));
138
138
  }
139
- function T(e) {
139
+ function K(e) {
140
140
  return e.id ? {
141
141
  id: e.id,
142
142
  projectKey: e.projectKey,
@@ -145,23 +145,12 @@ function T(e) {
145
145
  originalUrl: e.originalUrl,
146
146
  selector: e.selector,
147
147
  selectorStrategy: e.selectorStrategy === "stable-attribute" ? "stable-attribute" : "css-path",
148
- elementFingerprint: O(e.elementFingerprintJson, {
148
+ elementFingerprint: G(e.elementFingerprintJson, {
149
149
  tagName: "",
150
150
  width: 0,
151
151
  height: 0
152
152
  }),
153
- cssSnapshot: O(e.cssSnapshotJson, {
154
- margin: "",
155
- padding: "",
156
- border: "",
157
- fontFamily: "",
158
- fontSize: "",
159
- lineHeight: "",
160
- color: "",
161
- backgroundColor: "",
162
- width: 0,
163
- height: 0
164
- }),
153
+ cssSnapshot: ae(e.cssSnapshotJson),
165
154
  comment: e.comment,
166
155
  status: e.status === "resolved" ? "resolved" : "open",
167
156
  authorEmail: e.authorEmail,
@@ -171,34 +160,61 @@ function T(e) {
171
160
  resolvedBy: e.resolvedBy || void 0
172
161
  } : null;
173
162
  }
174
- function O(e, n) {
163
+ function G(e, n) {
175
164
  try {
176
165
  return e ? JSON.parse(e) : n;
177
166
  } catch {
178
167
  return n;
179
168
  }
180
169
  }
181
- function V(e) {
170
+ function ae(e) {
171
+ const n = G(e, {});
172
+ return {
173
+ margin: n.margin ?? "",
174
+ marginTop: n.marginTop ?? "",
175
+ marginRight: n.marginRight ?? "",
176
+ marginBottom: n.marginBottom ?? "",
177
+ marginLeft: n.marginLeft ?? "",
178
+ padding: n.padding ?? "",
179
+ paddingTop: n.paddingTop ?? "",
180
+ paddingRight: n.paddingRight ?? "",
181
+ paddingBottom: n.paddingBottom ?? "",
182
+ paddingLeft: n.paddingLeft ?? "",
183
+ border: n.border ?? "",
184
+ borderTopWidth: n.borderTopWidth ?? "",
185
+ borderRightWidth: n.borderRightWidth ?? "",
186
+ borderBottomWidth: n.borderBottomWidth ?? "",
187
+ borderLeftWidth: n.borderLeftWidth ?? "",
188
+ fontFamily: n.fontFamily ?? "",
189
+ fontSize: n.fontSize ?? "",
190
+ lineHeight: n.lineHeight ?? "",
191
+ color: n.color ?? "",
192
+ backgroundColor: n.backgroundColor ?? "",
193
+ width: n.width ?? 0,
194
+ height: n.height ?? 0
195
+ };
196
+ }
197
+ function de(e) {
182
198
  return e === "admin" ? ["create", "read", "resolve"] : e === "developer" ? ["read", "resolve"] : ["create", "read"];
183
199
  }
184
- async function Z(e) {
185
- return await ee(), new Promise((n, t) => {
200
+ async function ce(e) {
201
+ return await le(), new Promise((n, t) => {
186
202
  var o;
187
203
  const r = (o = window.google) == null ? void 0 : o.accounts.oauth2.initTokenClient({
188
204
  client_id: e,
189
- scope: W,
190
- callback: (s) => {
191
- if (s.error || !s.access_token) {
192
- t(new Error(s.error ?? "Google OAuth did not return an access token"));
205
+ scope: ne,
206
+ callback: (c) => {
207
+ if (c.error || !c.access_token) {
208
+ t(new Error(c.error ?? "Google OAuth did not return an access token"));
193
209
  return;
194
210
  }
195
- n(s.access_token);
211
+ n(c.access_token);
196
212
  }
197
213
  });
198
214
  r == null || r.requestAccessToken({ prompt: "" });
199
215
  });
200
216
  }
201
- function ee() {
217
+ function le() {
202
218
  var e;
203
219
  return (e = window.google) != null && e.accounts.oauth2 ? Promise.resolve() : new Promise((n, t) => {
204
220
  const r = document.querySelector(
@@ -214,116 +230,116 @@ function ee() {
214
230
  o.src = "https://accounts.google.com/gsi/client", o.async = !0, o.defer = !0, o.onload = () => n(), o.onerror = () => t(new Error("Google Identity failed to load")), document.head.append(o);
215
231
  });
216
232
  }
217
- function te(e) {
233
+ function he(e) {
218
234
  return new URL(e, window.location.href).pathname.replace(/\/+$/, "") || "/";
219
235
  }
220
- const J = D(null);
221
- function me({ config: e, children: n }) {
222
- const t = x(() => e.adapter ? e.adapter : X({
223
- googleClientId: z(e.googleClientId, "googleClientId"),
224
- spreadsheetId: z(e.spreadsheetId, "spreadsheetId"),
236
+ const J = V(null);
237
+ function Ce({ config: e, children: n }) {
238
+ const t = M(() => e.adapter ? e.adapter : re({
239
+ googleClientId: D(e.googleClientId, "googleClientId"),
240
+ spreadsheetId: D(e.spreadsheetId, "spreadsheetId"),
225
241
  feedbackSheetName: e.sheetName ?? "Feedback"
226
- }), [e.adapter, e.googleClientId, e.sheetName, e.spreadsheetId]), r = e.currentUrl ?? window.location.href, o = (e.normalizeUrl ?? te)(r), [s, v] = E(), [b, i] = E([]), [l, a] = E([]), d = L(async () => {
227
- const p = await t.listFeedback({
242
+ }), [e.adapter, e.googleClientId, e.sheetName, e.spreadsheetId]), r = e.currentUrl ?? window.location.href, o = (e.normalizeUrl ?? he)(r), [c, h] = R(), [w, a] = R([]), [d, l] = R([]), u = P(async () => {
243
+ const m = await t.listFeedback({
228
244
  projectKey: e.projectKey,
229
245
  contentId: e.contentId,
230
246
  normalizedPath: o
231
247
  });
232
- a(p);
248
+ l(m);
233
249
  }, [t, e.contentId, e.projectKey, o]);
234
- R(() => {
235
- let p = !0;
236
- async function w() {
237
- const [m, g] = await Promise.all([
250
+ T(() => {
251
+ let m = !0;
252
+ async function k() {
253
+ const [v, S] = await Promise.all([
238
254
  t.getCurrentUser(),
239
255
  t.getPermissions(e.projectKey)
240
256
  ]);
241
- p && (v(m), i(g), await d());
257
+ m && (h(v), a(S), await u());
242
258
  }
243
- return w(), () => {
244
- p = !1;
259
+ return k(), () => {
260
+ m = !1;
245
261
  };
246
- }, [t, e.projectKey, d]);
247
- const y = L(
248
- async (p) => {
249
- const w = await t.createFeedback(p);
250
- return a((m) => [w, ...m]), w;
262
+ }, [t, e.projectKey, u]);
263
+ const y = P(
264
+ async (m) => {
265
+ const k = await t.createFeedback(m);
266
+ return l((v) => [k, ...v]), k;
251
267
  },
252
268
  [t]
253
- ), u = L(
254
- async (p) => {
255
- const w = await t.resolveFeedback(p, (s == null ? void 0 : s.email) ?? "");
256
- return a(
257
- (m) => m.map((g) => g.id === p ? w : g)
258
- ), w;
269
+ ), p = P(
270
+ async (m) => {
271
+ const k = await t.resolveFeedback(m, (c == null ? void 0 : c.email) ?? "");
272
+ return l(
273
+ (v) => v.map((S) => S.id === m ? k : S)
274
+ ), k;
259
275
  },
260
- [t, s == null ? void 0 : s.email]
261
- ), f = x(
276
+ [t, c == null ? void 0 : c.email]
277
+ ), b = M(
262
278
  () => ({
263
279
  config: e,
264
280
  adapter: t,
265
- currentUser: s,
266
- permissions: b,
267
- feedback: l,
281
+ currentUser: c,
282
+ permissions: w,
283
+ feedback: d,
268
284
  normalizedPath: o,
269
- refreshFeedback: d,
285
+ refreshFeedback: u,
270
286
  createFeedback: y,
271
- resolveFeedback: u
287
+ resolveFeedback: p
272
288
  }),
273
289
  [
274
290
  t,
275
291
  e,
276
292
  y,
277
- s,
278
- l,
279
- o,
280
- b,
293
+ c,
281
294
  d,
282
- u
295
+ o,
296
+ w,
297
+ u,
298
+ p
283
299
  ]
284
300
  );
285
- return /* @__PURE__ */ c(J.Provider, { value: f, children: n });
301
+ return /* @__PURE__ */ i(J.Provider, { value: b, children: n });
286
302
  }
287
- function ne() {
288
- const e = H(J);
303
+ function ue() {
304
+ const e = Z(J);
289
305
  if (!e)
290
306
  throw new Error("useReviewLens must be used inside ReviewLensProvider");
291
307
  return e;
292
308
  }
293
- function z(e, n) {
309
+ function D(e, n) {
294
310
  if (!e)
295
311
  throw new Error(`review-lens-react requires config.${n} when no adapter is provided`);
296
312
  return e;
297
313
  }
298
- const re = [
314
+ const pe = [
299
315
  "data-review-id",
300
316
  "data-testid",
301
317
  "data-test-id",
302
318
  "aria-label",
303
319
  "name"
304
320
  ];
305
- function B(e) {
306
- const n = e.getBoundingClientRect(), t = oe(e);
321
+ function _(e) {
322
+ const n = e.getBoundingClientRect(), t = me(e);
307
323
  return {
308
324
  selector: t.selector,
309
325
  selectorStrategy: t.strategy,
310
- fingerprint: ae(e, n),
311
- cssSnapshot: ie(e, n),
326
+ fingerprint: fe(e, n),
327
+ cssSnapshot: we(e, n),
312
328
  rect: n
313
329
  };
314
330
  }
315
- function oe(e) {
316
- for (const n of re) {
331
+ function me(e) {
332
+ for (const n of pe) {
317
333
  const t = e.getAttribute(n);
318
334
  if (t)
319
335
  return {
320
- selector: `[${n}="${K(t)}"]`,
336
+ selector: `[${n}="${O(t)}"]`,
321
337
  strategy: "stable-attribute"
322
338
  };
323
339
  }
324
- return e.id ? { selector: `#${K(e.id)}`, strategy: "stable-attribute" } : { selector: se(e), strategy: "css-path" };
340
+ return e.id ? { selector: `#${O(e.id)}`, strategy: "stable-attribute" } : { selector: ge(e), strategy: "css-path" };
325
341
  }
326
- function se(e) {
342
+ function ge(e) {
327
343
  const n = [];
328
344
  let t = e;
329
345
  for (; t && t.nodeType === Node.ELEMENT_NODE && t !== document.body; ) {
@@ -332,14 +348,14 @@ function se(e) {
332
348
  n.unshift(o);
333
349
  break;
334
350
  }
335
- const s = t.tagName, v = Array.from(r.children).filter(
336
- (i) => i.tagName === s
337
- ), b = v.indexOf(t) + 1;
338
- n.unshift(v.length > 1 ? `${o}:nth-of-type(${b})` : o), t = r;
351
+ const c = t.tagName, h = Array.from(r.children).filter(
352
+ (a) => a.tagName === c
353
+ ), w = h.indexOf(t) + 1;
354
+ n.unshift(h.length > 1 ? `${o}:nth-of-type(${w})` : o), t = r;
339
355
  }
340
356
  return n.join(" > ");
341
357
  }
342
- function ae(e, n) {
358
+ function fe(e, n) {
343
359
  var t;
344
360
  return {
345
361
  tagName: e.tagName.toLowerCase(),
@@ -351,22 +367,34 @@ function ae(e, n) {
351
367
  height: Math.round(n.height)
352
368
  };
353
369
  }
354
- function ie(e, n) {
370
+ function we(e, n) {
355
371
  const t = window.getComputedStyle(e);
356
372
  return {
357
373
  margin: j(t.marginTop, t.marginRight, t.marginBottom, t.marginLeft),
374
+ marginTop: t.marginTop,
375
+ marginRight: t.marginRight,
376
+ marginBottom: t.marginBottom,
377
+ marginLeft: t.marginLeft,
358
378
  padding: j(
359
379
  t.paddingTop,
360
380
  t.paddingRight,
361
381
  t.paddingBottom,
362
382
  t.paddingLeft
363
383
  ),
384
+ paddingTop: t.paddingTop,
385
+ paddingRight: t.paddingRight,
386
+ paddingBottom: t.paddingBottom,
387
+ paddingLeft: t.paddingLeft,
364
388
  border: j(
365
389
  t.borderTopWidth,
366
390
  t.borderRightWidth,
367
391
  t.borderBottomWidth,
368
392
  t.borderLeftWidth
369
393
  ),
394
+ borderTopWidth: t.borderTopWidth,
395
+ borderRightWidth: t.borderRightWidth,
396
+ borderBottomWidth: t.borderBottomWidth,
397
+ borderLeftWidth: t.borderLeftWidth,
370
398
  fontFamily: t.fontFamily,
371
399
  fontSize: t.fontSize,
372
400
  lineHeight: t.lineHeight,
@@ -379,10 +407,10 @@ function ie(e, n) {
379
407
  function j(e, n, t, r) {
380
408
  return e === n && n === t && t === r ? e : `${e} ${n} ${t} ${r}`;
381
409
  }
382
- function K(e) {
410
+ function O(e) {
383
411
  return typeof CSS < "u" && typeof CSS.escape == "function" ? CSS.escape(e) : e.replace(/["\\]/g, "\\$&");
384
412
  }
385
- function fe({
413
+ function Le({
386
414
  open: e,
387
415
  onOpenChange: n,
388
416
  placement: t = "top-right",
@@ -390,159 +418,302 @@ function fe({
390
418
  }) {
391
419
  const {
392
420
  config: o,
393
- currentUser: s,
394
- feedback: v,
395
- normalizedPath: b,
396
- permissions: i,
397
- createFeedback: l,
398
- resolveFeedback: a
399
- } = ne(), [d, y] = E(), [u, f] = E(), [p, w] = E(""), [m, g] = E(), C = i.includes("create"), $ = i.includes("resolve"), N = x(
400
- () => v.filter((h) => r || h.status !== "resolved"),
401
- [v, r]
421
+ currentUser: c,
422
+ feedback: h,
423
+ normalizedPath: w,
424
+ permissions: a,
425
+ createFeedback: d,
426
+ resolveFeedback: l
427
+ } = ue(), [u, y] = R(), [p, b] = R(), [m, k] = R(""), [v, S] = R(), [F, C] = R("review"), N = !!c, x = a.includes("create"), Q = a.includes("resolve"), B = M(
428
+ () => h.filter((s) => r || s.status !== "resolved"),
429
+ [h, r]
402
430
  );
403
- R(() => {
404
- e || (y(void 0), f(void 0), w(""));
405
- }, [e]);
406
- const A = L((h) => {
407
- const I = h.target instanceof Element ? h.target : null;
408
- if (I)
409
- return I.closest("[data-review-lens-ui]") ? null : I;
410
- const S = document.elementFromPoint(h.clientX, h.clientY);
411
- return !S || S.closest("[data-review-lens-ui]") ? null : S;
431
+ T(() => {
432
+ e || (y(void 0), b(void 0), k(""), C("review"));
433
+ }, [e]), T(() => {
434
+ N || (y(void 0), b(void 0));
435
+ }, [N]), T(() => {
436
+ if (!e)
437
+ return;
438
+ function s(g) {
439
+ g.key === "Escape" && (g.preventDefault(), n == null || n(!1));
440
+ }
441
+ return window.addEventListener("keydown", s), () => {
442
+ window.removeEventListener("keydown", s);
443
+ };
444
+ }, [n, e]);
445
+ const $ = P((s) => {
446
+ const g = s.target instanceof Element ? s.target : null;
447
+ if (g)
448
+ return g.closest("[data-review-lens-ui]") ? null : g;
449
+ const L = document.elementFromPoint(s.clientX, s.clientY);
450
+ return !L || L.closest("[data-review-lens-ui]") ? null : L;
412
451
  }, []);
413
- if (R(() => {
414
- if (!e || u)
452
+ if (T(() => {
453
+ if (!e || !N)
415
454
  return;
416
- function h(S) {
417
- const F = A(S);
418
- F && y(B(F));
455
+ function s(L) {
456
+ const A = $(L);
457
+ y(A ? _(A) : void 0);
419
458
  }
420
- function I(S) {
421
- const F = A(S);
422
- F && (S.preventDefault(), S.stopPropagation(), f(B(F)));
459
+ function g(L) {
460
+ const A = $(L);
461
+ A && (L.preventDefault(), L.stopPropagation(), b(_(A)), C("review"));
423
462
  }
424
- return window.addEventListener("mousemove", h, !0), window.addEventListener("click", I, !0), () => {
425
- window.removeEventListener("mousemove", h, !0), window.removeEventListener("click", I, !0);
463
+ return window.addEventListener("mousemove", s, !0), window.addEventListener("click", g, !0), () => {
464
+ window.removeEventListener("mousemove", s, !0), window.removeEventListener("click", g, !0);
426
465
  };
427
- }, [A, u, e]), !e)
466
+ }, [N, $, p, e]), !e)
428
467
  return null;
429
- const P = u ?? d;
430
- async function G() {
431
- !u || !p.trim() || !s || !C || (await l({
468
+ const I = u ?? p, X = !!p;
469
+ function W(s) {
470
+ S(s), b(void 0), C("feedback");
471
+ const g = H(s.selector);
472
+ g && (g.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" }), window.requestAnimationFrame(() => {
473
+ y(_(g));
474
+ }));
475
+ }
476
+ async function U() {
477
+ !p || !m.trim() || !c || !x || (await d({
432
478
  projectKey: o.projectKey,
433
479
  contentId: o.contentId,
434
- normalizedPath: b,
480
+ normalizedPath: w,
435
481
  originalUrl: o.currentUrl ?? window.location.href,
436
- selector: u.selector,
437
- selectorStrategy: u.selectorStrategy,
438
- elementFingerprint: u.fingerprint,
439
- cssSnapshot: u.cssSnapshot,
440
- comment: p.trim(),
441
- authorEmail: s.email
442
- }), w(""), f(void 0));
482
+ selector: p.selector,
483
+ selectorStrategy: p.selectorStrategy,
484
+ elementFingerprint: p.fingerprint,
485
+ cssSnapshot: p.cssSnapshot,
486
+ comment: m.trim(),
487
+ authorEmail: c.email
488
+ }), k(""), b(void 0), y(void 0), C("feedback"));
443
489
  }
444
- return /* @__PURE__ */ k("div", { className: "review-lens-root", "data-review-lens-ui": !0, children: [
445
- P ? /* @__PURE__ */ c(ce, { target: P, locked: !!u }) : null,
446
- /* @__PURE__ */ c(
447
- le,
490
+ return /* @__PURE__ */ f("div", { className: "review-lens-root", "data-review-lens-ui": !0, children: [
491
+ N && I ? /* @__PURE__ */ i(ve, { target: I, locked: !!p }) : null,
492
+ N ? /* @__PURE__ */ i(
493
+ be,
448
494
  {
449
- feedback: N,
450
- selectedFeedback: m,
451
- onSelect: g
495
+ feedback: B,
496
+ selectedFeedback: v,
497
+ onSelect: W
452
498
  }
453
- ),
454
- /* @__PURE__ */ k("aside", { className: `review-lens-panel review-lens-panel--${t}`, "data-review-lens-ui": !0, children: [
455
- /* @__PURE__ */ k("header", { className: "review-lens-panel__header", children: [
456
- /* @__PURE__ */ k("div", { children: [
457
- /* @__PURE__ */ c("p", { className: "review-lens-kicker", children: "Review Lens" }),
458
- /* @__PURE__ */ c("h2", { children: u ? "Element locked" : "Inspecting" })
499
+ ) : null,
500
+ /* @__PURE__ */ f("aside", { className: `review-lens-panel review-lens-panel--${t}`, "data-review-lens-ui": !0, children: [
501
+ /* @__PURE__ */ f("header", { className: "review-lens-panel__header", children: [
502
+ /* @__PURE__ */ f("div", { children: [
503
+ /* @__PURE__ */ i("p", { className: "review-lens-kicker", children: "Review Lens" }),
504
+ /* @__PURE__ */ i("h2", { children: F === "feedback" ? "Feedback" : p ? "Element locked" : "Inspecting" })
459
505
  ] }),
460
- /* @__PURE__ */ c("button", { type: "button", onClick: () => n == null ? void 0 : n(!1), children: "Close" })
506
+ /* @__PURE__ */ i("button", { type: "button", onClick: () => n == null ? void 0 : n(!1), children: "Close" })
461
507
  ] }),
462
- P ? /* @__PURE__ */ c(de, { target: P }) : /* @__PURE__ */ c("p", { children: "Move over the app to inspect." }),
463
- u ? /* @__PURE__ */ k(
464
- "form",
465
- {
466
- className: "review-lens-feedback-form",
467
- onSubmit: (h) => {
468
- h.preventDefault(), G();
469
- },
470
- children: [
471
- /* @__PURE__ */ c("label", { htmlFor: "review-lens-comment", children: "Feedback" }),
472
- /* @__PURE__ */ c(
473
- "textarea",
508
+ /* @__PURE__ */ f("div", { className: "review-lens-panel__body", children: [
509
+ /* @__PURE__ */ f("div", { className: "review-lens-mode-switch", role: "tablist", "aria-label": "Review Lens mode", children: [
510
+ /* @__PURE__ */ i(
511
+ "button",
512
+ {
513
+ type: "button",
514
+ role: "tab",
515
+ "aria-selected": F === "review",
516
+ onClick: () => C("review"),
517
+ children: "Review"
518
+ }
519
+ ),
520
+ /* @__PURE__ */ f(
521
+ "button",
522
+ {
523
+ type: "button",
524
+ role: "tab",
525
+ "aria-selected": F === "feedback",
526
+ onClick: () => C("feedback"),
527
+ children: [
528
+ "Feedback ",
529
+ /* @__PURE__ */ i("span", { children: B.length })
530
+ ]
531
+ }
532
+ )
533
+ ] }),
534
+ F === "review" ? /* @__PURE__ */ f("div", { className: "review-lens-review-pane", role: "tabpanel", children: [
535
+ /* @__PURE__ */ f("div", { className: "review-lens-inspection", children: [
536
+ N ? null : /* @__PURE__ */ i("p", { children: "Authenticate with Google to inspect this page." }),
537
+ N && I ? /* @__PURE__ */ i(ke, { target: I }) : null,
538
+ N && !I ? /* @__PURE__ */ i("p", { children: "Move over the app to inspect." }) : null
539
+ ] }),
540
+ X ? /* @__PURE__ */ f(
541
+ "form",
542
+ {
543
+ className: "review-lens-feedback-form",
544
+ onSubmit: (s) => {
545
+ s.preventDefault(), U();
546
+ },
547
+ children: [
548
+ /* @__PURE__ */ i("label", { htmlFor: "review-lens-comment", children: "New feedback" }),
549
+ /* @__PURE__ */ i(
550
+ "textarea",
551
+ {
552
+ id: "review-lens-comment",
553
+ value: m,
554
+ disabled: !x,
555
+ onChange: (s) => k(s.target.value),
556
+ onKeyDown: (s) => {
557
+ s.key === "Enter" && s.metaKey && (s.preventDefault(), U());
558
+ },
559
+ placeholder: x ? "Describe the UX issue..." : "You do not have permission to comment."
560
+ }
561
+ ),
562
+ x ? /* @__PURE__ */ f("p", { className: "review-lens-feedback-form__hint", children: [
563
+ "Press ",
564
+ /* @__PURE__ */ i("kbd", { children: "Command" }),
565
+ " + ",
566
+ /* @__PURE__ */ i("kbd", { children: "Enter" }),
567
+ " to submit."
568
+ ] }) : null,
569
+ /* @__PURE__ */ i("div", { className: "review-lens-actions", children: /* @__PURE__ */ i("button", { type: "submit", disabled: !m.trim() || !x, children: "Save feedback" }) })
570
+ ]
571
+ }
572
+ ) : null
573
+ ] }) : /* @__PURE__ */ f("div", { className: "review-lens-comments", children: [
574
+ /* @__PURE__ */ f("div", { className: "review-lens-comments__header", children: [
575
+ /* @__PURE__ */ i("h3", { children: "Page feedback" }),
576
+ /* @__PURE__ */ i("span", { children: B.length })
577
+ ] }),
578
+ /* @__PURE__ */ f("div", { className: "review-lens-comments__list", children: [
579
+ B.length === 0 ? /* @__PURE__ */ i("p", { children: "No feedback for this view." }) : null,
580
+ B.map((s) => /* @__PURE__ */ f(
581
+ "article",
474
582
  {
475
- id: "review-lens-comment",
476
- value: p,
477
- disabled: !C,
478
- onChange: (h) => w(h.target.value),
479
- placeholder: C ? "Describe the UX issue..." : "You do not have permission to comment."
480
- }
481
- ),
482
- /* @__PURE__ */ k("div", { className: "review-lens-actions", children: [
483
- /* @__PURE__ */ c("button", { type: "button", onClick: () => f(void 0), children: "Unlock" }),
484
- /* @__PURE__ */ c("button", { type: "submit", disabled: !p.trim() || !C, children: "Save feedback" })
485
- ] })
486
- ]
487
- }
488
- ) : null,
489
- /* @__PURE__ */ k("section", { className: "review-lens-comments", children: [
490
- /* @__PURE__ */ c("h3", { children: "Page feedback" }),
491
- N.length === 0 ? /* @__PURE__ */ c("p", { children: "No feedback for this view." }) : null,
492
- N.map((h) => /* @__PURE__ */ k(
493
- "article",
494
- {
495
- className: (m == null ? void 0 : m.id) === h.id ? "review-lens-comment review-lens-comment--selected" : "review-lens-comment",
496
- children: [
497
- /* @__PURE__ */ c("p", { children: h.comment }),
498
- /* @__PURE__ */ c("span", { children: h.authorEmail }),
499
- h.status === "open" && $ ? /* @__PURE__ */ c("button", { type: "button", onClick: () => void a(h.id), children: "Resolve" }) : null
500
- ]
501
- },
502
- h.id
503
- ))
583
+ tabIndex: 0,
584
+ className: (v == null ? void 0 : v.id) === s.id ? "review-lens-comment review-lens-comment--selected" : "review-lens-comment",
585
+ onClick: () => W(s),
586
+ onKeyDown: (g) => {
587
+ (g.key === "Enter" || g.key === " ") && (g.preventDefault(), W(s));
588
+ },
589
+ children: [
590
+ /* @__PURE__ */ f("div", { className: "review-lens-comment__content", children: [
591
+ /* @__PURE__ */ i("p", { children: s.comment }),
592
+ /* @__PURE__ */ i("span", { children: s.authorEmail })
593
+ ] }),
594
+ s.status === "open" && Q ? /* @__PURE__ */ i("div", { className: "review-lens-comment__actions", children: /* @__PURE__ */ i(
595
+ "button",
596
+ {
597
+ type: "button",
598
+ onClick: (g) => {
599
+ g.stopPropagation(), l(s.id);
600
+ },
601
+ children: "Resolve"
602
+ }
603
+ ) }) : null
604
+ ]
605
+ },
606
+ s.id
607
+ ))
608
+ ] })
609
+ ] })
504
610
  ] })
505
611
  ] })
506
612
  ] });
507
613
  }
508
- function ce({ target: e, locked: n }) {
509
- return /* @__PURE__ */ c(
614
+ function ve({ target: e, locked: n }) {
615
+ const t = Se(e);
616
+ return /* @__PURE__ */ f(
510
617
  "div",
511
618
  {
512
619
  className: n ? "review-lens-highlight review-lens-highlight--locked" : "review-lens-highlight",
513
620
  style: {
514
- top: e.rect.top + window.scrollY,
515
- left: e.rect.left + window.scrollX,
516
- width: e.rect.width,
517
- height: e.rect.height
518
- }
621
+ top: t.margin.top,
622
+ left: t.margin.left,
623
+ width: t.margin.width,
624
+ height: t.margin.height
625
+ },
626
+ children: [
627
+ /* @__PURE__ */ i(
628
+ "div",
629
+ {
630
+ className: "review-lens-highlight__border",
631
+ style: {
632
+ top: t.border.top - t.margin.top,
633
+ left: t.border.left - t.margin.left,
634
+ width: t.border.width,
635
+ height: t.border.height
636
+ }
637
+ }
638
+ ),
639
+ /* @__PURE__ */ i(
640
+ "div",
641
+ {
642
+ className: "review-lens-highlight__padding",
643
+ style: {
644
+ top: t.padding.top - t.margin.top,
645
+ left: t.padding.left - t.margin.left,
646
+ width: t.padding.width,
647
+ height: t.padding.height
648
+ }
649
+ }
650
+ ),
651
+ /* @__PURE__ */ i(
652
+ "div",
653
+ {
654
+ className: "review-lens-highlight__content",
655
+ style: {
656
+ top: t.content.top - t.margin.top,
657
+ left: t.content.left - t.margin.left,
658
+ width: t.content.width,
659
+ height: t.content.height
660
+ }
661
+ }
662
+ ),
663
+ /* @__PURE__ */ f("div", { className: "review-lens-highlight__label", children: [
664
+ Math.round(e.rect.width),
665
+ " x ",
666
+ Math.round(e.rect.height)
667
+ ] })
668
+ ]
519
669
  }
520
670
  );
521
671
  }
522
- function le({
672
+ function be({
523
673
  feedback: e,
524
674
  selectedFeedback: n,
525
675
  onSelect: t
526
676
  }) {
527
- return /* @__PURE__ */ c(q, { children: e.map((r) => {
528
- const o = ue(r.selector), s = o == null ? void 0 : o.getBoundingClientRect();
529
- return s ? /* @__PURE__ */ c(
530
- "button",
531
- {
532
- type: "button",
533
- className: (n == null ? void 0 : n.id) === r.id ? "review-lens-marker review-lens-marker--selected" : "review-lens-marker",
534
- style: {
535
- top: s.top + window.scrollY,
536
- left: s.left + window.scrollX + s.width
537
- },
538
- onClick: () => t(r),
539
- "aria-label": `Open feedback from ${r.authorEmail}`
540
- },
541
- r.id
542
- ) : null;
543
- }) });
677
+ return /* @__PURE__ */ i(Y, { children: e.map((r) => /* @__PURE__ */ i(
678
+ ye,
679
+ {
680
+ feedback: r,
681
+ selected: (n == null ? void 0 : n.id) === r.id,
682
+ onSelect: t
683
+ },
684
+ r.id
685
+ )) });
686
+ }
687
+ function ye({
688
+ feedback: e,
689
+ selected: n,
690
+ onSelect: t
691
+ }) {
692
+ const r = ee(null);
693
+ return te(() => {
694
+ let o = 0;
695
+ const c = () => {
696
+ o = 0;
697
+ const w = r.current, a = H(e.selector), d = a == null ? void 0 : a.getBoundingClientRect();
698
+ !w || !d || (w.style.top = `${d.top}px`, w.style.left = `${d.right}px`, w.hidden = d.bottom < 0 || d.top > window.innerHeight);
699
+ }, h = () => {
700
+ o || (o = window.requestAnimationFrame(c));
701
+ };
702
+ return c(), window.addEventListener("scroll", h, !0), window.addEventListener("resize", h), () => {
703
+ o && window.cancelAnimationFrame(o), window.removeEventListener("scroll", h, !0), window.removeEventListener("resize", h);
704
+ };
705
+ }, [e.selector]), /* @__PURE__ */ i(
706
+ "button",
707
+ {
708
+ ref: r,
709
+ type: "button",
710
+ className: n ? "review-lens-marker review-lens-marker--selected" : "review-lens-marker",
711
+ onClick: () => t(e),
712
+ "aria-label": `Open feedback from ${e.authorEmail}`
713
+ }
714
+ );
544
715
  }
545
- function de({ target: e }) {
716
+ function ke({ target: e }) {
546
717
  const n = [
547
718
  ["Selector", e.selector],
548
719
  ["Size", `${e.cssSnapshot.width} x ${e.cssSnapshot.height}`],
@@ -554,23 +725,71 @@ function de({ target: e }) {
554
725
  ["Color", e.cssSnapshot.color],
555
726
  ["Background", e.cssSnapshot.backgroundColor]
556
727
  ];
557
- return /* @__PURE__ */ c("dl", { className: "review-lens-metrics", children: n.map(([t, r]) => /* @__PURE__ */ k("div", { children: [
558
- /* @__PURE__ */ c("dt", { children: t }),
559
- /* @__PURE__ */ c("dd", { children: r })
728
+ return /* @__PURE__ */ i("dl", { className: "review-lens-metrics", children: n.map(([t, r]) => /* @__PURE__ */ f("div", { children: [
729
+ /* @__PURE__ */ i("dt", { children: t }),
730
+ /* @__PURE__ */ i("dd", { children: r })
560
731
  ] }, t)) });
561
732
  }
562
- function ue(e) {
733
+ function H(e) {
563
734
  try {
564
735
  return document.querySelector(e);
565
736
  } catch {
566
737
  return null;
567
738
  }
568
739
  }
740
+ function Se(e) {
741
+ const n = {
742
+ top: E(e.cssSnapshot.marginTop),
743
+ right: E(e.cssSnapshot.marginRight),
744
+ bottom: E(e.cssSnapshot.marginBottom),
745
+ left: E(e.cssSnapshot.marginLeft)
746
+ }, t = {
747
+ top: E(e.cssSnapshot.borderTopWidth),
748
+ right: E(e.cssSnapshot.borderRightWidth),
749
+ bottom: E(e.cssSnapshot.borderBottomWidth),
750
+ left: E(e.cssSnapshot.borderLeftWidth)
751
+ }, r = {
752
+ top: E(e.cssSnapshot.paddingTop),
753
+ right: E(e.cssSnapshot.paddingRight),
754
+ bottom: E(e.cssSnapshot.paddingBottom),
755
+ left: E(e.cssSnapshot.paddingLeft)
756
+ }, o = {
757
+ top: e.rect.top,
758
+ left: e.rect.left,
759
+ width: Math.max(e.rect.width, 0),
760
+ height: Math.max(e.rect.height, 0)
761
+ }, c = {
762
+ top: o.top - n.top,
763
+ left: o.left - n.left,
764
+ width: o.width + n.left + n.right,
765
+ height: o.height + n.top + n.bottom
766
+ }, h = {
767
+ top: o.top + t.top,
768
+ left: o.left + t.left,
769
+ width: Math.max(o.width - t.left - t.right, 0),
770
+ height: Math.max(o.height - t.top - t.bottom, 0)
771
+ }, w = {
772
+ top: h.top + r.top,
773
+ left: h.left + r.left,
774
+ width: Math.max(h.width - r.left - r.right, 0),
775
+ height: Math.max(h.height - r.top - r.bottom, 0)
776
+ };
777
+ return {
778
+ margin: c,
779
+ border: o,
780
+ padding: h,
781
+ content: w
782
+ };
783
+ }
784
+ function E(e) {
785
+ const n = Number.parseFloat(e || "0");
786
+ return Number.isFinite(n) ? n : 0;
787
+ }
569
788
  export {
570
- fe as ReviewLensOverlay,
571
- me as ReviewLensProvider,
572
- B as buildElementTarget,
573
- X as createGoogleSheetsAdapter,
574
- te as normalizeReviewUrl,
575
- ne as useReviewLens
789
+ Le as ReviewLensOverlay,
790
+ Ce as ReviewLensProvider,
791
+ _ as buildElementTarget,
792
+ re as createGoogleSheetsAdapter,
793
+ he as normalizeReviewUrl,
794
+ ue as useReviewLens
576
795
  };
@@ -1 +1 @@
1
- (function(w,r){typeof exports=="object"&&typeof module<"u"?r(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],r):(w=typeof globalThis<"u"?globalThis:w||self,r(w.ReviewLensReact={},w.jsxRuntime,w.React))})(this,(function(w,r,p){"use strict";const J=["https://www.googleapis.com/auth/spreadsheets","https://www.googleapis.com/auth/userinfo.email"].join(" "),D="https://www.googleapis.com/oauth2/v3/userinfo";function T(e){const n=e.feedbackSheetName??"Feedback",t=e.usersSheetName??"Users";let o,s;async function a(){return o??(o=X(e.googleClientId)),o}async function y(c,l){const i=await a(),d=await fetch(`https://sheets.googleapis.com/v4/spreadsheets/${e.spreadsheetId}${c}`,{...l,headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json",...l==null?void 0:l.headers}});if(!d.ok)throw new Error(`Google Sheets request failed with ${d.status}`);return d.json()}async function S(c){return(await y(`/values/${encodeURIComponent(c)}`)).values??[]}return{async getCurrentUser(){if(!s){const c=await a(),l=await fetch(D,{headers:{Authorization:`Bearer ${c}`}});if(!l.ok)throw new Error(`Google userinfo request failed with ${l.status}`);s=(await l.json()).email}if(!s)throw new Error("Google account did not return an email address");return{email:s}},async getPermissions(c){const[{email:l},i]=await Promise.all([this.getCurrentUser(),S(t)]),d=U(i),k=l.toLowerCase(),u=d.find(g=>{var f;return((f=g.email)==null?void 0:f.toLowerCase())===k&&g.active!=="false"&&(!g.projectKey||g.projectKey===c)});return _((u==null?void 0:u.role)??"designer")},async listFeedback(c){return U(await S(n)).map(x).filter(i=>i!==null).filter(i=>i.projectKey===c.projectKey&&i.contentId===c.contentId&&i.normalizedPath===c.normalizedPath).sort((i,d)=>d.createdAt.localeCompare(i.createdAt))},async createFeedback(c){const l=new Date().toISOString(),i={...c,id:crypto.randomUUID(),status:"open",createdAt:l,updatedAt:l};return await y(`/values/${encodeURIComponent(n)}:append?valueInputOption=RAW`,{method:"POST",body:JSON.stringify({values:[W(i)]})}),i},async resolveFeedback(c,l){const i=await S(n),d=i[0]??H,k=d.indexOf("id"),u=d.indexOf("status"),g=d.indexOf("updatedAt"),f=d.indexOf("resolvedAt"),v=d.indexOf("resolvedBy"),m=i.findIndex((A,N)=>N>0&&A[k]===c);if(m<1)throw new Error(`Feedback ${c} was not found`);const b=[...i[m]],E=new Date().toISOString();b[u]="resolved",b[g]=E,b[f]=E,b[v]=l,await y(`/values/${encodeURIComponent(n)}!A${m+1}:Q${m+1}?valueInputOption=RAW`,{method:"PUT",body:JSON.stringify({values:[b]})});const P=x(O(d,b));if(!P)throw new Error(`Feedback ${c} could not be parsed after resolving`);return P}}}const H=["id","projectKey","contentId","normalizedPath","originalUrl","selector","selectorStrategy","elementFingerprintJson","cssSnapshotJson","comment","status","authorEmail","createdAt","updatedAt","resolvedAt","resolvedBy"];function W(e){return[e.id,e.projectKey,e.contentId,e.normalizedPath,e.originalUrl,e.selector,e.selectorStrategy,JSON.stringify(e.elementFingerprint),JSON.stringify(e.cssSnapshot),e.comment,e.status,e.authorEmail,e.createdAt,e.updatedAt,e.resolvedAt??"",e.resolvedBy??""]}function U(e){const[n,...t]=e;return n?t.map(o=>O(n,o)):[]}function O(e,n){return Object.fromEntries(e.map((t,o)=>[t,n[o]??""]))}function x(e){return e.id?{id:e.id,projectKey:e.projectKey,contentId:e.contentId,normalizedPath:e.normalizedPath,originalUrl:e.originalUrl,selector:e.selector,selectorStrategy:e.selectorStrategy==="stable-attribute"?"stable-attribute":"css-path",elementFingerprint:z(e.elementFingerprintJson,{tagName:"",width:0,height:0}),cssSnapshot:z(e.cssSnapshotJson,{margin:"",padding:"",border:"",fontFamily:"",fontSize:"",lineHeight:"",color:"",backgroundColor:"",width:0,height:0}),comment:e.comment,status:e.status==="resolved"?"resolved":"open",authorEmail:e.authorEmail,createdAt:e.createdAt,updatedAt:e.updatedAt,resolvedAt:e.resolvedAt||void 0,resolvedBy:e.resolvedBy||void 0}:null}function z(e,n){try{return e?JSON.parse(e):n}catch{return n}}function _(e){return e==="admin"?["create","read","resolve"]:e==="developer"?["read","resolve"]:["create","read"]}async function X(e){return await Y(),new Promise((n,t)=>{var s;const o=(s=window.google)==null?void 0:s.accounts.oauth2.initTokenClient({client_id:e,scope:J,callback:a=>{if(a.error||!a.access_token){t(new Error(a.error??"Google OAuth did not return an access token"));return}n(a.access_token)}});o==null||o.requestAccessToken({prompt:""})})}function Y(){var e;return(e=window.google)!=null&&e.accounts.oauth2?Promise.resolve():new Promise((n,t)=>{const o=document.querySelector('script[src="https://accounts.google.com/gsi/client"]');if(o){o.addEventListener("load",()=>n(),{once:!0}),o.addEventListener("error",()=>t(new Error("Google Identity failed to load")),{once:!0});return}const s=document.createElement("script");s.src="https://accounts.google.com/gsi/client",s.async=!0,s.defer=!0,s.onload=()=>n(),s.onerror=()=>t(new Error("Google Identity failed to load")),document.head.append(s)})}function B(e){return new URL(e,window.location.href).pathname.replace(/\/+$/,"")||"/"}const K=p.createContext(null);function Q({config:e,children:n}){const t=p.useMemo(()=>e.adapter?e.adapter:T({googleClientId:q(e.googleClientId,"googleClientId"),spreadsheetId:q(e.spreadsheetId,"spreadsheetId"),feedbackSheetName:e.sheetName??"Feedback"}),[e.adapter,e.googleClientId,e.sheetName,e.spreadsheetId]),o=e.currentUrl??window.location.href,s=(e.normalizeUrl??B)(o),[a,y]=p.useState(),[S,c]=p.useState([]),[l,i]=p.useState([]),d=p.useCallback(async()=>{const f=await t.listFeedback({projectKey:e.projectKey,contentId:e.contentId,normalizedPath:s});i(f)},[t,e.contentId,e.projectKey,s]);p.useEffect(()=>{let f=!0;async function v(){const[m,b]=await Promise.all([t.getCurrentUser(),t.getPermissions(e.projectKey)]);f&&(y(m),c(b),await d())}return v(),()=>{f=!1}},[t,e.projectKey,d]);const k=p.useCallback(async f=>{const v=await t.createFeedback(f);return i(m=>[v,...m]),v},[t]),u=p.useCallback(async f=>{const v=await t.resolveFeedback(f,(a==null?void 0:a.email)??"");return i(m=>m.map(b=>b.id===f?v:b)),v},[t,a==null?void 0:a.email]),g=p.useMemo(()=>({config:e,adapter:t,currentUser:a,permissions:S,feedback:l,normalizedPath:s,refreshFeedback:d,createFeedback:k,resolveFeedback:u}),[t,e,k,a,l,s,S,d,u]);return r.jsx(K.Provider,{value:g,children:n})}function M(){const e=p.useContext(K);if(!e)throw new Error("useReviewLens must be used inside ReviewLensProvider");return e}function q(e,n){if(!e)throw new Error(`review-lens-react requires config.${n} when no adapter is provided`);return e}const V=["data-review-id","data-testid","data-test-id","aria-label","name"];function L(e){const n=e.getBoundingClientRect(),t=Z(e);return{selector:t.selector,selectorStrategy:t.strategy,fingerprint:ee(e,n),cssSnapshot:te(e,n),rect:n}}function Z(e){for(const n of V){const t=e.getAttribute(n);if(t)return{selector:`[${n}="${G(t)}"]`,strategy:"stable-attribute"}}return e.id?{selector:`#${G(e.id)}`,strategy:"stable-attribute"}:{selector:R(e),strategy:"css-path"}}function R(e){const n=[];let t=e;for(;t&&t.nodeType===Node.ELEMENT_NODE&&t!==document.body;){const o=t.parentElement,s=t.tagName.toLowerCase();if(!o){n.unshift(s);break}const a=t.tagName,y=Array.from(o.children).filter(c=>c.tagName===a),S=y.indexOf(t)+1;n.unshift(y.length>1?`${s}:nth-of-type(${S})`:s),t=o}return n.join(" > ")}function ee(e,n){var t;return{tagName:e.tagName.toLowerCase(),id:e.id||void 0,className:e.getAttribute("class")||void 0,textSnippet:((t=e.textContent)==null?void 0:t.trim().slice(0,80))||void 0,ariaLabel:e.getAttribute("aria-label")||void 0,width:Math.round(n.width),height:Math.round(n.height)}}function te(e,n){const t=window.getComputedStyle(e);return{margin:j(t.marginTop,t.marginRight,t.marginBottom,t.marginLeft),padding:j(t.paddingTop,t.paddingRight,t.paddingBottom,t.paddingLeft),border:j(t.borderTopWidth,t.borderRightWidth,t.borderBottomWidth,t.borderLeftWidth),fontFamily:t.fontFamily,fontSize:t.fontSize,lineHeight:t.lineHeight,color:t.color,backgroundColor:t.backgroundColor,width:Math.round(n.width),height:Math.round(n.height)}}function j(e,n,t,o){return e===n&&n===t&&t===o?e:`${e} ${n} ${t} ${o}`}function G(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/["\\]/g,"\\$&")}function ne({open:e,onOpenChange:n,placement:t="top-right",showResolved:o=!1}){const{config:s,currentUser:a,feedback:y,normalizedPath:S,permissions:c,createFeedback:l,resolveFeedback:i}=M(),[d,k]=p.useState(),[u,g]=p.useState(),[f,v]=p.useState(""),[m,b]=p.useState(),E=c.includes("create"),P=c.includes("resolve"),A=p.useMemo(()=>y.filter(h=>o||h.status!=="resolved"),[y,o]);p.useEffect(()=>{e||(k(void 0),g(void 0),v(""))},[e]);const N=p.useCallback(h=>{const I=h.target instanceof Element?h.target:null;if(I)return I.closest("[data-review-lens-ui]")?null:I;const C=document.elementFromPoint(h.clientX,h.clientY);return!C||C.closest("[data-review-lens-ui]")?null:C},[]);if(p.useEffect(()=>{if(!e||u)return;function h(C){const F=N(C);F&&k(L(F))}function I(C){const F=N(C);F&&(C.preventDefault(),C.stopPropagation(),g(L(F)))}return window.addEventListener("mousemove",h,!0),window.addEventListener("click",I,!0),()=>{window.removeEventListener("mousemove",h,!0),window.removeEventListener("click",I,!0)}},[N,u,e]),!e)return null;const $=u??d;async function ie(){!u||!f.trim()||!a||!E||(await l({projectKey:s.projectKey,contentId:s.contentId,normalizedPath:S,originalUrl:s.currentUrl??window.location.href,selector:u.selector,selectorStrategy:u.selectorStrategy,elementFingerprint:u.fingerprint,cssSnapshot:u.cssSnapshot,comment:f.trim(),authorEmail:a.email}),v(""),g(void 0))}return r.jsxs("div",{className:"review-lens-root","data-review-lens-ui":!0,children:[$?r.jsx(re,{target:$,locked:!!u}):null,r.jsx(oe,{feedback:A,selectedFeedback:m,onSelect:b}),r.jsxs("aside",{className:`review-lens-panel review-lens-panel--${t}`,"data-review-lens-ui":!0,children:[r.jsxs("header",{className:"review-lens-panel__header",children:[r.jsxs("div",{children:[r.jsx("p",{className:"review-lens-kicker",children:"Review Lens"}),r.jsx("h2",{children:u?"Element locked":"Inspecting"})]}),r.jsx("button",{type:"button",onClick:()=>n==null?void 0:n(!1),children:"Close"})]}),$?r.jsx(se,{target:$}):r.jsx("p",{children:"Move over the app to inspect."}),u?r.jsxs("form",{className:"review-lens-feedback-form",onSubmit:h=>{h.preventDefault(),ie()},children:[r.jsx("label",{htmlFor:"review-lens-comment",children:"Feedback"}),r.jsx("textarea",{id:"review-lens-comment",value:f,disabled:!E,onChange:h=>v(h.target.value),placeholder:E?"Describe the UX issue...":"You do not have permission to comment."}),r.jsxs("div",{className:"review-lens-actions",children:[r.jsx("button",{type:"button",onClick:()=>g(void 0),children:"Unlock"}),r.jsx("button",{type:"submit",disabled:!f.trim()||!E,children:"Save feedback"})]})]}):null,r.jsxs("section",{className:"review-lens-comments",children:[r.jsx("h3",{children:"Page feedback"}),A.length===0?r.jsx("p",{children:"No feedback for this view."}):null,A.map(h=>r.jsxs("article",{className:(m==null?void 0:m.id)===h.id?"review-lens-comment review-lens-comment--selected":"review-lens-comment",children:[r.jsx("p",{children:h.comment}),r.jsx("span",{children:h.authorEmail}),h.status==="open"&&P?r.jsx("button",{type:"button",onClick:()=>void i(h.id),children:"Resolve"}):null]},h.id))]})]})]})}function re({target:e,locked:n}){return r.jsx("div",{className:n?"review-lens-highlight review-lens-highlight--locked":"review-lens-highlight",style:{top:e.rect.top+window.scrollY,left:e.rect.left+window.scrollX,width:e.rect.width,height:e.rect.height}})}function oe({feedback:e,selectedFeedback:n,onSelect:t}){return r.jsx(r.Fragment,{children:e.map(o=>{const s=ae(o.selector),a=s==null?void 0:s.getBoundingClientRect();return a?r.jsx("button",{type:"button",className:(n==null?void 0:n.id)===o.id?"review-lens-marker review-lens-marker--selected":"review-lens-marker",style:{top:a.top+window.scrollY,left:a.left+window.scrollX+a.width},onClick:()=>t(o),"aria-label":`Open feedback from ${o.authorEmail}`},o.id):null})})}function se({target:e}){const n=[["Selector",e.selector],["Size",`${e.cssSnapshot.width} x ${e.cssSnapshot.height}`],["Margin",e.cssSnapshot.margin],["Padding",e.cssSnapshot.padding],["Border",e.cssSnapshot.border],["Font",`${e.cssSnapshot.fontSize} / ${e.cssSnapshot.lineHeight}`],["Family",e.cssSnapshot.fontFamily],["Color",e.cssSnapshot.color],["Background",e.cssSnapshot.backgroundColor]];return r.jsx("dl",{className:"review-lens-metrics",children:n.map(([t,o])=>r.jsxs("div",{children:[r.jsx("dt",{children:t}),r.jsx("dd",{children:o})]},t))})}function ae(e){try{return document.querySelector(e)}catch{return null}}w.ReviewLensOverlay=ne,w.ReviewLensProvider=Q,w.buildElementTarget=L,w.createGoogleSheetsAdapter=T,w.normalizeReviewUrl=B,w.useReviewLens=M,Object.defineProperty(w,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(y,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],n):(y=typeof globalThis<"u"?globalThis:y||self,n(y.ReviewLensReact={},y.jsxRuntime,y.React))})(this,(function(y,n,h){"use strict";const X=["https://www.googleapis.com/auth/spreadsheets","https://www.googleapis.com/auth/userinfo.email"].join(" "),Y="https://www.googleapis.com/oauth2/v3/userinfo";function W(e){const o=e.feedbackSheetName??"Feedback",t=e.usersSheetName??"Users";let s,r;async function c(){return s??(s=te(e.googleClientId)),s}async function u(a,d){const l=await c(),p=await fetch(`https://sheets.googleapis.com/v4/spreadsheets/${e.spreadsheetId}${a}`,{...d,headers:{Authorization:`Bearer ${l}`,"Content-Type":"application/json",...d==null?void 0:d.headers}});if(!p.ok)throw new Error(`Google Sheets request failed with ${p.status}`);return p.json()}async function w(a){return(await u(`/values/${encodeURIComponent(a)}`)).values??[]}return{async getCurrentUser(){if(!r){const a=await c(),d=await fetch(Y,{headers:{Authorization:`Bearer ${a}`}});if(!d.ok)throw new Error(`Google userinfo request failed with ${d.status}`);r=(await d.json()).email}if(!r)throw new Error("Google account did not return an email address");return{email:r}},async getPermissions(a){const[{email:d},l]=await Promise.all([this.getCurrentUser(),w(t)]),p=_(l),k=d.toLowerCase(),f=p.find(b=>{var g;return((g=b.email)==null?void 0:g.toLowerCase())===k&&b.active!=="false"&&(!b.projectKey||b.projectKey===a)});return ee((f==null?void 0:f.role)??"designer")},async listFeedback(a){return _(await w(o)).map(z).filter(l=>l!==null).filter(l=>l.projectKey===a.projectKey&&l.contentId===a.contentId&&l.normalizedPath===a.normalizedPath).sort((l,p)=>p.createdAt.localeCompare(l.createdAt))},async createFeedback(a){const d=new Date().toISOString(),l={...a,id:crypto.randomUUID(),status:"open",createdAt:d,updatedAt:d};return await u(`/values/${encodeURIComponent(o)}:append?valueInputOption=RAW`,{method:"POST",body:JSON.stringify({values:[Z(l)]})}),l},async resolveFeedback(a,d){const l=await w(o),p=l[0]??V,k=p.indexOf("id"),f=p.indexOf("status"),b=p.indexOf("updatedAt"),g=p.indexOf("resolvedAt"),S=p.indexOf("resolvedBy"),v=l.findIndex((C,B)=>B>0&&C[k]===a);if(v<1)throw new Error(`Feedback ${a} was not found`);const E=[...l[v]],T=new Date().toISOString();E[f]="resolved",E[b]=T,E[g]=T,E[S]=d,await u(`/values/${encodeURIComponent(o)}!A${v+1}:Q${v+1}?valueInputOption=RAW`,{method:"PUT",body:JSON.stringify({values:[E]})});const N=z(U(p,E));if(!N)throw new Error(`Feedback ${a} could not be parsed after resolving`);return N}}}const V=["id","projectKey","contentId","normalizedPath","originalUrl","selector","selectorStrategy","elementFingerprintJson","cssSnapshotJson","comment","status","authorEmail","createdAt","updatedAt","resolvedAt","resolvedBy"];function Z(e){return[e.id,e.projectKey,e.contentId,e.normalizedPath,e.originalUrl,e.selector,e.selectorStrategy,JSON.stringify(e.elementFingerprint),JSON.stringify(e.cssSnapshot),e.comment,e.status,e.authorEmail,e.createdAt,e.updatedAt,e.resolvedAt??"",e.resolvedBy??""]}function _(e){const[o,...t]=e;return o?t.map(s=>U(o,s)):[]}function U(e,o){return Object.fromEntries(e.map((t,s)=>[t,o[s]??""]))}function z(e){return e.id?{id:e.id,projectKey:e.projectKey,contentId:e.contentId,normalizedPath:e.normalizedPath,originalUrl:e.originalUrl,selector:e.selector,selectorStrategy:e.selectorStrategy==="stable-attribute"?"stable-attribute":"css-path",elementFingerprint:K(e.elementFingerprintJson,{tagName:"",width:0,height:0}),cssSnapshot:R(e.cssSnapshotJson),comment:e.comment,status:e.status==="resolved"?"resolved":"open",authorEmail:e.authorEmail,createdAt:e.createdAt,updatedAt:e.updatedAt,resolvedAt:e.resolvedAt||void 0,resolvedBy:e.resolvedBy||void 0}:null}function K(e,o){try{return e?JSON.parse(e):o}catch{return o}}function R(e){const o=K(e,{});return{margin:o.margin??"",marginTop:o.marginTop??"",marginRight:o.marginRight??"",marginBottom:o.marginBottom??"",marginLeft:o.marginLeft??"",padding:o.padding??"",paddingTop:o.paddingTop??"",paddingRight:o.paddingRight??"",paddingBottom:o.paddingBottom??"",paddingLeft:o.paddingLeft??"",border:o.border??"",borderTopWidth:o.borderTopWidth??"",borderRightWidth:o.borderRightWidth??"",borderBottomWidth:o.borderBottomWidth??"",borderLeftWidth:o.borderLeftWidth??"",fontFamily:o.fontFamily??"",fontSize:o.fontSize??"",lineHeight:o.lineHeight??"",color:o.color??"",backgroundColor:o.backgroundColor??"",width:o.width??0,height:o.height??0}}function ee(e){return e==="admin"?["create","read","resolve"]:e==="developer"?["read","resolve"]:["create","read"]}async function te(e){return await oe(),new Promise((o,t)=>{var r;const s=(r=window.google)==null?void 0:r.accounts.oauth2.initTokenClient({client_id:e,scope:X,callback:c=>{if(c.error||!c.access_token){t(new Error(c.error??"Google OAuth did not return an access token"));return}o(c.access_token)}});s==null||s.requestAccessToken({prompt:""})})}function oe(){var e;return(e=window.google)!=null&&e.accounts.oauth2?Promise.resolve():new Promise((o,t)=>{const s=document.querySelector('script[src="https://accounts.google.com/gsi/client"]');if(s){s.addEventListener("load",()=>o(),{once:!0}),s.addEventListener("error",()=>t(new Error("Google Identity failed to load")),{once:!0});return}const r=document.createElement("script");r.src="https://accounts.google.com/gsi/client",r.async=!0,r.defer=!0,r.onload=()=>o(),r.onerror=()=>t(new Error("Google Identity failed to load")),document.head.append(r)})}function O(e){return new URL(e,window.location.href).pathname.replace(/\/+$/,"")||"/"}const D=h.createContext(null);function ne({config:e,children:o}){const t=h.useMemo(()=>e.adapter?e.adapter:W({googleClientId:G(e.googleClientId,"googleClientId"),spreadsheetId:G(e.spreadsheetId,"spreadsheetId"),feedbackSheetName:e.sheetName??"Feedback"}),[e.adapter,e.googleClientId,e.sheetName,e.spreadsheetId]),s=e.currentUrl??window.location.href,r=(e.normalizeUrl??O)(s),[c,u]=h.useState(),[w,a]=h.useState([]),[d,l]=h.useState([]),p=h.useCallback(async()=>{const g=await t.listFeedback({projectKey:e.projectKey,contentId:e.contentId,normalizedPath:r});l(g)},[t,e.contentId,e.projectKey,r]);h.useEffect(()=>{let g=!0;async function S(){const[v,E]=await Promise.all([t.getCurrentUser(),t.getPermissions(e.projectKey)]);g&&(u(v),a(E),await p())}return S(),()=>{g=!1}},[t,e.projectKey,p]);const k=h.useCallback(async g=>{const S=await t.createFeedback(g);return l(v=>[S,...v]),S},[t]),f=h.useCallback(async g=>{const S=await t.resolveFeedback(g,(c==null?void 0:c.email)??"");return l(v=>v.map(E=>E.id===g?S:E)),S},[t,c==null?void 0:c.email]),b=h.useMemo(()=>({config:e,adapter:t,currentUser:c,permissions:w,feedback:d,normalizedPath:r,refreshFeedback:p,createFeedback:k,resolveFeedback:f}),[t,e,k,c,d,r,w,p,f]);return n.jsx(D.Provider,{value:b,children:o})}function q(){const e=h.useContext(D);if(!e)throw new Error("useReviewLens must be used inside ReviewLensProvider");return e}function G(e,o){if(!e)throw new Error(`review-lens-react requires config.${o} when no adapter is provided`);return e}const re=["data-review-id","data-testid","data-test-id","aria-label","name"];function P(e){const o=e.getBoundingClientRect(),t=se(e);return{selector:t.selector,selectorStrategy:t.strategy,fingerprint:ae(e,o),cssSnapshot:de(e,o),rect:o}}function se(e){for(const o of re){const t=e.getAttribute(o);if(t)return{selector:`[${o}="${J(t)}"]`,strategy:"stable-attribute"}}return e.id?{selector:`#${J(e.id)}`,strategy:"stable-attribute"}:{selector:ie(e),strategy:"css-path"}}function ie(e){const o=[];let t=e;for(;t&&t.nodeType===Node.ELEMENT_NODE&&t!==document.body;){const s=t.parentElement,r=t.tagName.toLowerCase();if(!s){o.unshift(r);break}const c=t.tagName,u=Array.from(s.children).filter(a=>a.tagName===c),w=u.indexOf(t)+1;o.unshift(u.length>1?`${r}:nth-of-type(${w})`:r),t=s}return o.join(" > ")}function ae(e,o){var t;return{tagName:e.tagName.toLowerCase(),id:e.id||void 0,className:e.getAttribute("class")||void 0,textSnippet:((t=e.textContent)==null?void 0:t.trim().slice(0,80))||void 0,ariaLabel:e.getAttribute("aria-label")||void 0,width:Math.round(o.width),height:Math.round(o.height)}}function de(e,o){const t=window.getComputedStyle(e);return{margin:$(t.marginTop,t.marginRight,t.marginBottom,t.marginLeft),marginTop:t.marginTop,marginRight:t.marginRight,marginBottom:t.marginBottom,marginLeft:t.marginLeft,padding:$(t.paddingTop,t.paddingRight,t.paddingBottom,t.paddingLeft),paddingTop:t.paddingTop,paddingRight:t.paddingRight,paddingBottom:t.paddingBottom,paddingLeft:t.paddingLeft,border:$(t.borderTopWidth,t.borderRightWidth,t.borderBottomWidth,t.borderLeftWidth),borderTopWidth:t.borderTopWidth,borderRightWidth:t.borderRightWidth,borderBottomWidth:t.borderBottomWidth,borderLeftWidth:t.borderLeftWidth,fontFamily:t.fontFamily,fontSize:t.fontSize,lineHeight:t.lineHeight,color:t.color,backgroundColor:t.backgroundColor,width:Math.round(o.width),height:Math.round(o.height)}}function $(e,o,t,s){return e===o&&o===t&&t===s?e:`${e} ${o} ${t} ${s}`}function J(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/["\\]/g,"\\$&")}function ce({open:e,onOpenChange:o,placement:t="top-right",showResolved:s=!1}){const{config:r,currentUser:c,feedback:u,normalizedPath:w,permissions:a,createFeedback:d,resolveFeedback:l}=q(),[p,k]=h.useState(),[f,b]=h.useState(),[g,S]=h.useState(""),[v,E]=h.useState(),[T,N]=h.useState("review"),C=!!c,B=a.includes("create"),ge=a.includes("resolve"),I=h.useMemo(()=>u.filter(i=>s||i.status!=="resolved"),[u,s]);h.useEffect(()=>{e||(k(void 0),b(void 0),S(""),N("review"))},[e]),h.useEffect(()=>{C||(k(void 0),b(void 0))},[C]),h.useEffect(()=>{if(!e)return;function i(m){m.key==="Escape"&&(m.preventDefault(),o==null||o(!1))}return window.addEventListener("keydown",i),()=>{window.removeEventListener("keydown",i)}},[o,e]);const j=h.useCallback(i=>{const m=i.target instanceof Element?i.target:null;if(m)return m.closest("[data-review-lens-ui]")?null:m;const F=document.elementFromPoint(i.clientX,i.clientY);return!F||F.closest("[data-review-lens-ui]")?null:F},[]);if(h.useEffect(()=>{if(!e||!C)return;function i(F){const x=j(F);k(x?P(x):void 0)}function m(F){const x=j(F);x&&(F.preventDefault(),F.stopPropagation(),b(P(x)),N("review"))}return window.addEventListener("mousemove",i,!0),window.addEventListener("click",m,!0),()=>{window.removeEventListener("mousemove",i,!0),window.removeEventListener("click",m,!0)}},[C,j,f,e]),!e)return null;const A=p??f,me=!!f;function M(i){E(i),b(void 0),N("feedback");const m=H(i.selector);m&&(m.scrollIntoView({behavior:"smooth",block:"center",inline:"center"}),window.requestAnimationFrame(()=>{k(P(m))}))}async function Q(){!f||!g.trim()||!c||!B||(await d({projectKey:r.projectKey,contentId:r.contentId,normalizedPath:w,originalUrl:r.currentUrl??window.location.href,selector:f.selector,selectorStrategy:f.selectorStrategy,elementFingerprint:f.fingerprint,cssSnapshot:f.cssSnapshot,comment:g.trim(),authorEmail:c.email}),S(""),b(void 0),k(void 0),N("feedback"))}return n.jsxs("div",{className:"review-lens-root","data-review-lens-ui":!0,children:[C&&A?n.jsx(le,{target:A,locked:!!f}):null,C?n.jsx(he,{feedback:I,selectedFeedback:v,onSelect:M}):null,n.jsxs("aside",{className:`review-lens-panel review-lens-panel--${t}`,"data-review-lens-ui":!0,children:[n.jsxs("header",{className:"review-lens-panel__header",children:[n.jsxs("div",{children:[n.jsx("p",{className:"review-lens-kicker",children:"Review Lens"}),n.jsx("h2",{children:T==="feedback"?"Feedback":f?"Element locked":"Inspecting"})]}),n.jsx("button",{type:"button",onClick:()=>o==null?void 0:o(!1),children:"Close"})]}),n.jsxs("div",{className:"review-lens-panel__body",children:[n.jsxs("div",{className:"review-lens-mode-switch",role:"tablist","aria-label":"Review Lens mode",children:[n.jsx("button",{type:"button",role:"tab","aria-selected":T==="review",onClick:()=>N("review"),children:"Review"}),n.jsxs("button",{type:"button",role:"tab","aria-selected":T==="feedback",onClick:()=>N("feedback"),children:["Feedback ",n.jsx("span",{children:I.length})]})]}),T==="review"?n.jsxs("div",{className:"review-lens-review-pane",role:"tabpanel",children:[n.jsxs("div",{className:"review-lens-inspection",children:[C?null:n.jsx("p",{children:"Authenticate with Google to inspect this page."}),C&&A?n.jsx(pe,{target:A}):null,C&&!A?n.jsx("p",{children:"Move over the app to inspect."}):null]}),me?n.jsxs("form",{className:"review-lens-feedback-form",onSubmit:i=>{i.preventDefault(),Q()},children:[n.jsx("label",{htmlFor:"review-lens-comment",children:"New feedback"}),n.jsx("textarea",{id:"review-lens-comment",value:g,disabled:!B,onChange:i=>S(i.target.value),onKeyDown:i=>{i.key==="Enter"&&i.metaKey&&(i.preventDefault(),Q())},placeholder:B?"Describe the UX issue...":"You do not have permission to comment."}),B?n.jsxs("p",{className:"review-lens-feedback-form__hint",children:["Press ",n.jsx("kbd",{children:"Command"})," + ",n.jsx("kbd",{children:"Enter"})," to submit."]}):null,n.jsx("div",{className:"review-lens-actions",children:n.jsx("button",{type:"submit",disabled:!g.trim()||!B,children:"Save feedback"})})]}):null]}):n.jsxs("div",{className:"review-lens-comments",children:[n.jsxs("div",{className:"review-lens-comments__header",children:[n.jsx("h3",{children:"Page feedback"}),n.jsx("span",{children:I.length})]}),n.jsxs("div",{className:"review-lens-comments__list",children:[I.length===0?n.jsx("p",{children:"No feedback for this view."}):null,I.map(i=>n.jsxs("article",{tabIndex:0,className:(v==null?void 0:v.id)===i.id?"review-lens-comment review-lens-comment--selected":"review-lens-comment",onClick:()=>M(i),onKeyDown:m=>{(m.key==="Enter"||m.key===" ")&&(m.preventDefault(),M(i))},children:[n.jsxs("div",{className:"review-lens-comment__content",children:[n.jsx("p",{children:i.comment}),n.jsx("span",{children:i.authorEmail})]}),i.status==="open"&&ge?n.jsx("div",{className:"review-lens-comment__actions",children:n.jsx("button",{type:"button",onClick:m=>{m.stopPropagation(),l(i.id)},children:"Resolve"})}):null]},i.id))]})]})]})]})]})}function le({target:e,locked:o}){const t=fe(e);return n.jsxs("div",{className:o?"review-lens-highlight review-lens-highlight--locked":"review-lens-highlight",style:{top:t.margin.top,left:t.margin.left,width:t.margin.width,height:t.margin.height},children:[n.jsx("div",{className:"review-lens-highlight__border",style:{top:t.border.top-t.margin.top,left:t.border.left-t.margin.left,width:t.border.width,height:t.border.height}}),n.jsx("div",{className:"review-lens-highlight__padding",style:{top:t.padding.top-t.margin.top,left:t.padding.left-t.margin.left,width:t.padding.width,height:t.padding.height}}),n.jsx("div",{className:"review-lens-highlight__content",style:{top:t.content.top-t.margin.top,left:t.content.left-t.margin.left,width:t.content.width,height:t.content.height}}),n.jsxs("div",{className:"review-lens-highlight__label",children:[Math.round(e.rect.width)," x ",Math.round(e.rect.height)]})]})}function he({feedback:e,selectedFeedback:o,onSelect:t}){return n.jsx(n.Fragment,{children:e.map(s=>n.jsx(ue,{feedback:s,selected:(o==null?void 0:o.id)===s.id,onSelect:t},s.id))})}function ue({feedback:e,selected:o,onSelect:t}){const s=h.useRef(null);return h.useLayoutEffect(()=>{let r=0;const c=()=>{r=0;const w=s.current,a=H(e.selector),d=a==null?void 0:a.getBoundingClientRect();!w||!d||(w.style.top=`${d.top}px`,w.style.left=`${d.right}px`,w.hidden=d.bottom<0||d.top>window.innerHeight)},u=()=>{r||(r=window.requestAnimationFrame(c))};return c(),window.addEventListener("scroll",u,!0),window.addEventListener("resize",u),()=>{r&&window.cancelAnimationFrame(r),window.removeEventListener("scroll",u,!0),window.removeEventListener("resize",u)}},[e.selector]),n.jsx("button",{ref:s,type:"button",className:o?"review-lens-marker review-lens-marker--selected":"review-lens-marker",onClick:()=>t(e),"aria-label":`Open feedback from ${e.authorEmail}`})}function pe({target:e}){const o=[["Selector",e.selector],["Size",`${e.cssSnapshot.width} x ${e.cssSnapshot.height}`],["Margin",e.cssSnapshot.margin],["Padding",e.cssSnapshot.padding],["Border",e.cssSnapshot.border],["Font",`${e.cssSnapshot.fontSize} / ${e.cssSnapshot.lineHeight}`],["Family",e.cssSnapshot.fontFamily],["Color",e.cssSnapshot.color],["Background",e.cssSnapshot.backgroundColor]];return n.jsx("dl",{className:"review-lens-metrics",children:o.map(([t,s])=>n.jsxs("div",{children:[n.jsx("dt",{children:t}),n.jsx("dd",{children:s})]},t))})}function H(e){try{return document.querySelector(e)}catch{return null}}function fe(e){const o={top:L(e.cssSnapshot.marginTop),right:L(e.cssSnapshot.marginRight),bottom:L(e.cssSnapshot.marginBottom),left:L(e.cssSnapshot.marginLeft)},t={top:L(e.cssSnapshot.borderTopWidth),right:L(e.cssSnapshot.borderRightWidth),bottom:L(e.cssSnapshot.borderBottomWidth),left:L(e.cssSnapshot.borderLeftWidth)},s={top:L(e.cssSnapshot.paddingTop),right:L(e.cssSnapshot.paddingRight),bottom:L(e.cssSnapshot.paddingBottom),left:L(e.cssSnapshot.paddingLeft)},r={top:e.rect.top,left:e.rect.left,width:Math.max(e.rect.width,0),height:Math.max(e.rect.height,0)},c={top:r.top-o.top,left:r.left-o.left,width:r.width+o.left+o.right,height:r.height+o.top+o.bottom},u={top:r.top+t.top,left:r.left+t.left,width:Math.max(r.width-t.left-t.right,0),height:Math.max(r.height-t.top-t.bottom,0)},w={top:u.top+s.top,left:u.left+s.left,width:Math.max(u.width-s.left-s.right,0),height:Math.max(u.height-s.top-s.bottom,0)};return{margin:c,border:r,padding:u,content:w}}function L(e){const o=Number.parseFloat(e||"0");return Number.isFinite(o)?o:0}y.ReviewLensOverlay=ce,y.ReviewLensProvider=ne,y.buildElementTarget=P,y.createGoogleSheetsAdapter=W,y.normalizeReviewUrl=O,y.useReviewLens=q,Object.defineProperty(y,Symbol.toStringTag,{value:"Module"})}));
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- .review-lens-root{color:#171717;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;position:fixed;z-index:2147483647}.review-lens-highlight{border:2px solid #2563eb;box-shadow:0 0 0 9999px #0f172a14,0 0 0 4px #2563eb2e;pointer-events:none;position:absolute}.review-lens-highlight--locked{border-color:#f97316;box-shadow:0 0 0 9999px #0f172a1f,0 0 0 4px #f9731638}.review-lens-panel{background:#fafafa;border:1px solid #d4d4d4;border-radius:8px;box-shadow:0 24px 70px #0f172a38;box-sizing:border-box;max-height:calc(100vh - 32px);overflow:auto;padding:16px;position:fixed;width:min(380px,calc(100vw - 32px))}.review-lens-panel--top-left{left:16px;top:16px}.review-lens-panel--top-right{right:16px;top:16px}.review-lens-panel--bottom-left{bottom:16px;left:16px}.review-lens-panel--bottom-right{bottom:16px;right:16px}.review-lens-panel__header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.review-lens-panel h2,.review-lens-panel h3,.review-lens-panel p{margin:0}.review-lens-panel h2{font-size:18px;line-height:1.25}.review-lens-panel h3{font-size:14px;margin-top:18px}.review-lens-kicker{color:#525252;font-size:11px;font-weight:700;letter-spacing:0;text-transform:uppercase}.review-lens-panel button{background:#171717;border:1px solid #171717;border-radius:6px;color:#fff;cursor:pointer;font:inherit;font-size:13px;min-height:32px;padding:6px 10px}.review-lens-panel button:disabled{cursor:not-allowed;opacity:.45}.review-lens-metrics{border:1px solid #e5e5e5;border-radius:8px;display:grid;gap:0;margin:16px 0 0;overflow:hidden}.review-lens-metrics div{display:grid;grid-template-columns:96px minmax(0,1fr)}.review-lens-metrics dt,.review-lens-metrics dd{border-bottom:1px solid #e5e5e5;font-size:12px;margin:0;min-width:0;padding:8px}.review-lens-metrics dt{background:#f5f5f5;color:#525252;font-weight:700}.review-lens-metrics dd{overflow-wrap:anywhere}.review-lens-feedback-form{display:grid;gap:8px;margin-top:16px}.review-lens-feedback-form label{font-size:13px;font-weight:700}.review-lens-feedback-form textarea{border:1px solid #d4d4d4;border-radius:8px;box-sizing:border-box;font:inherit;min-height:96px;padding:10px;resize:vertical;width:100%}.review-lens-actions{display:flex;gap:8px;justify-content:flex-end}.review-lens-comments{display:grid;gap:8px}.review-lens-comment{border:1px solid #e5e5e5;border-radius:8px;display:grid;gap:6px;padding:10px}.review-lens-comment--selected{border-color:#2563eb}.review-lens-comment span{color:#525252;font-size:12px}.review-lens-marker{background:#f97316;border:2px solid #ffffff;border-radius:999px;box-shadow:0 8px 20px #0f172a3d;cursor:pointer;height:18px;position:absolute;transform:translate(-50%,-50%);width:18px}.review-lens-marker--selected{background:#2563eb}
1
+ .review-lens-root{color:#171717;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;top:0;right:0;bottom:0;left:0;pointer-events:none;position:fixed;z-index:2147483647}.review-lens-highlight{background:#f9731633;box-shadow:0 0 0 9999px #0f172a14;box-sizing:border-box;pointer-events:none;position:fixed}.review-lens-highlight--locked{box-shadow:0 0 0 9999px #0f172a1f}.review-lens-highlight__border,.review-lens-highlight__padding,.review-lens-highlight__content{box-sizing:border-box;position:absolute}.review-lens-highlight__border{border:2px solid #facc15;background:#facc1538}.review-lens-highlight__padding{border:2px solid #22c55e;background:#22c55e38}.review-lens-highlight__content{border:2px solid #2563eb;background:#2563eb29}.review-lens-highlight--locked .review-lens-highlight__content{border-color:#f97316}.review-lens-highlight__label{background:#171717;border-radius:4px;color:#fff;font-size:11px;font-weight:700;left:0;line-height:1;padding:4px 6px;position:absolute;top:-24px;white-space:nowrap}.review-lens-panel{background:#fafafa;border:1px solid #d4d4d4;border-radius:8px;box-shadow:0 24px 70px #0f172a38;box-sizing:border-box;display:flex;flex-direction:column;height:min(680px,calc(100vh - 32px));min-height:min(520px,calc(100vh - 32px));overflow:hidden;padding:16px;pointer-events:auto;position:fixed;width:min(380px,calc(100vw - 32px))}.review-lens-panel--top-left{left:16px;top:16px}.review-lens-panel--top-right{right:16px;top:16px}.review-lens-panel--bottom-left{bottom:16px;left:16px}.review-lens-panel--bottom-right{bottom:16px;right:16px}.review-lens-panel__header{flex:0 0 auto;align-items:flex-start;display:flex;gap:16px;justify-content:space-between;margin-bottom:12px}.review-lens-panel__body{display:flex;flex:1 1 auto;flex-direction:column;gap:16px;min-height:0;overflow:hidden}.review-lens-panel h2,.review-lens-panel h3,.review-lens-panel p{margin:0}.review-lens-panel h2{font-size:18px;line-height:1.25}.review-lens-panel h3{font-size:14px;margin-top:18px}.review-lens-kicker{color:#525252;font-size:11px;font-weight:700;letter-spacing:0;text-transform:uppercase}.review-lens-panel button{background:#171717;border:1px solid #171717;border-radius:6px;color:#fff;cursor:pointer;font:inherit;font-size:13px;min-height:32px;padding:6px 10px;transition:transform .14s cubic-bezier(.23,1,.32,1)}.review-lens-panel button:active{transform:scale(.97)}.review-lens-panel button:disabled{cursor:not-allowed;opacity:.45}.review-lens-mode-switch{background:#eee;border:1px solid #d4d4d4;border-radius:7px;display:grid;flex:0 0 auto;gap:2px;grid-template-columns:repeat(2,minmax(0,1fr));padding:2px}.review-lens-mode-switch button{background:transparent;border:0;color:#525252;min-height:30px}.review-lens-mode-switch button[aria-selected=true]{background:#fff;box-shadow:0 1px 4px #0f172a1f;color:#171717}.review-lens-mode-switch span{color:#737373;font-size:11px}.review-lens-metrics{border:1px solid #e5e5e5;border-radius:8px;display:grid;gap:0;margin:0;overflow:hidden}.review-lens-metrics div{display:grid;grid-template-columns:96px minmax(0,1fr)}.review-lens-metrics dt,.review-lens-metrics dd{border-bottom:1px solid #e5e5e5;font-size:11px;margin:0;min-width:0;padding:6px 8px}.review-lens-metrics dt{background:#f5f5f5;color:#525252;font-weight:700}.review-lens-metrics dd{overflow-wrap:anywhere}.review-lens-inspection{display:grid;flex:0 0 auto;gap:16px}.review-lens-review-pane{display:flex;flex:1 1 auto;flex-direction:column;gap:16px;min-height:0}.review-lens-feedback-form{display:grid;flex:1 1 auto;gap:8px;grid-template-rows:auto minmax(96px,1fr) auto auto;margin-top:0;min-height:0}.review-lens-feedback-form label{font-size:13px;font-weight:700}.review-lens-feedback-form textarea{border:1px solid #d4d4d4;border-radius:8px;box-sizing:border-box;flex:1 1 auto;font:inherit;min-height:96px;padding:10px;resize:vertical;width:100%}.review-lens-feedback-form__hint{color:#737373;font-size:12px;line-height:1.4}.review-lens-feedback-form__hint kbd{background:#f5f5f5;border:1px solid #d4d4d4;border-radius:4px;color:#404040;font-family:inherit;font-size:11px;padding:1px 4px}.review-lens-actions{display:flex;gap:8px;justify-content:flex-end}.review-lens-comments{border-top:1px solid #e5e5e5;display:flex;flex:1 1 auto;flex-direction:column;gap:8px;min-height:0;padding-top:12px}.review-lens-comments__header{align-items:center;color:#171717;display:flex;gap:8px;justify-content:space-between}.review-lens-comments__header h3{font-size:13px;margin:0}.review-lens-comments__header span{align-items:center;background:#e5e5e5;border-radius:999px;color:#404040;display:inline-flex;font-size:11px;justify-content:center;min-width:22px;padding:2px 7px}.review-lens-comments__list{align-content:start;display:grid;gap:10px;min-height:0;overflow:auto;padding-right:4px}.review-lens-comment{background:linear-gradient(180deg,#fffffffa,#fafafafa);border:1px solid #e5e5e5;border-radius:8px;box-shadow:0 1px 2px #0f172a0a;cursor:pointer;display:grid;gap:10px;padding:12px;position:relative;text-align:left}.review-lens-comment:focus-visible{outline:2px solid #2563eb;outline-offset:2px}.review-lens-comment--selected{background:#eff6ff;border-color:#2563eb;box-shadow:inset 3px 0 #2563eb,0 8px 18px #2563eb1f}.review-lens-comment__content{display:grid;gap:6px}.review-lens-comment__content p{color:#171717;font-size:14px;line-height:1.35}.review-lens-comment__content span{color:#525252;font-size:11px;line-height:1}.review-lens-comment__actions{display:flex;justify-content:flex-end}.review-lens-comment__actions button{background:transparent;border-color:#d4d4d4;color:#404040;min-height:28px;padding:4px 10px}.review-lens-comment__actions button:hover{background:#171717;border-color:#171717;color:#fff}.review-lens-marker{background:#f97316;border:2px solid #ffffff;border-radius:999px;box-shadow:0 8px 20px #0f172a3d;cursor:pointer;height:18px;pointer-events:auto;position:fixed;transform:translate(-50%,-50%);width:18px}.review-lens-marker--selected{background:#2563eb}
package/dist/types.d.ts CHANGED
@@ -4,8 +4,20 @@ export type ReviewLensRole = "designer" | "developer" | "admin";
4
4
  export type ReviewLensPermission = "create" | "read" | "resolve";
5
5
  export type CssSnapshot = {
6
6
  margin: string;
7
+ marginTop: string;
8
+ marginRight: string;
9
+ marginBottom: string;
10
+ marginLeft: string;
7
11
  padding: string;
12
+ paddingTop: string;
13
+ paddingRight: string;
14
+ paddingBottom: string;
15
+ paddingLeft: string;
8
16
  border: string;
17
+ borderTopWidth: string;
18
+ borderRightWidth: string;
19
+ borderBottomWidth: string;
20
+ borderLeftWidth: string;
9
21
  fontFamily: string;
10
22
  fontSize: string;
11
23
  lineHeight: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "review-lens-react",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "React overlay for UX review feedback backed by Google Sheets.",
5
5
  "type": "module",
6
6
  "main": "./dist/review-lens-react.umd.cjs",