@designfever/web-review-kit 0.8.1 → 0.8.3

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 CHANGED
@@ -27,10 +27,10 @@ This package does not own internal operator tools, private admin keys, or produc
27
27
  - [Testing](docs/testing.md): Vitest adapter contract tests and local verification commands.
28
28
  - [Custom adapter sample](docs/adaptor.sample.ts): starting point for host-owned remote adapters.
29
29
  - [DB setup](docs/db-setup.md): optional Supabase `review_items` setup, RLS, presence notes, and validation.
30
- - [Architecture and runtime logic](docs/architecture.md): core runtime, React shell, coordinate, anchor, and extension boundaries.
31
- - [Figma overlay](docs/figma-overlay.md): how the shell toggles a host Figma overlay.
30
+ - [Architecture and runtime logic](docs/architecture.md): core runtime, React shell, coordinate, anchor, sitemap, and feature ownership boundaries.
31
+ - [Figma overlay](docs/figma-overlay.md): host helper behavior and package-managed image overlay state.
32
32
  - [Grid overlay](docs/grid-overlay.md): how the shell toggles a host grid/helper overlay.
33
- - [Release notes 0.8.1](docs/release-notes-0.8.1.md): QA prompt prefix, QA filtering, counters, tooltip, long-content, and outside marker fixes.
33
+ - [Release notes 0.8.3](docs/release-notes-0.8.3.md): code-review bug fixes, sitemap state persistence and status filtering.
34
34
 
35
35
  ## Quick Start
36
36
 
@@ -146,12 +146,10 @@ import {
146
146
  export default defineConfig({
147
147
  plugins: [
148
148
  reviewSourceLocator({
149
- enabled: true,
150
149
  include: ['src'],
151
150
  filePath: 'absolute',
152
151
  }),
153
152
  reviewDataLocator({
154
- enabled: true,
155
153
  include: ['src/data'],
156
154
  filePath: 'absolute',
157
155
  }),
@@ -159,7 +157,7 @@ export default defineConfig({
159
157
  });
160
158
  ```
161
159
 
162
- When source hints are available, hold `Option` over the review target to inspect source candidates from the DOM ancestry. Click the target to pin the candidate list, then choose a file to open. The source locator also reads TSX/JSX with the TypeScript parser when available and writes `data-wrk-source-component` for intrinsic JSX nodes, which helps the inspector prefer real component candidates over repeated wrapper primitives. For function component render paths, it also records the parent JSX call site so Source Tree can show where a component was used. The side rail can open a Source Tree panel with section/source/data links, parent usage links, live box metrics, text/font/media metadata, and class tags. DOM QA cards show a source action when the saved item has source hints. Source Tree filter/options, QA panel mode, and QA status filter are stored in browser localStorage. Keep these plugins disabled for production builds because they write source paths into the DOM.
160
+ The locator plugins run automatically on the Vite dev server and stay disabled in production builds. Source Tree and the `Option` shortcut stay available without `sourceRoot` or an `enabled` option; `sourceRoot` is only needed to open relative source paths. When source hints are available, hold `Option` over the review target to inspect its source outline, then click the target to open the closest component in Source Tree. The source locator also reads TSX/JSX with the TypeScript parser when available and writes `data-wrk-source-component` for intrinsic JSX nodes, which helps the inspector prefer real component candidates over repeated wrapper primitives. For function component render paths, it also records the parent JSX call site so Source Tree can show where a component was used. The side rail can open a Source Tree panel with section/source/data links, parent usage links, live box metrics, text/font/media metadata, and class tags. DOM QA cards show a source action when the saved item has source hints. Source Tree filter/options, QA panel mode, and QA status filter are stored in browser localStorage.
163
161
 
164
162
  In Vite/ESM hosts, source opening reads `VITE_REVIEW_SOURCE_ROOT`, `VITE_REVIEW_SOURCE_EDITOR`, and `VITE_REVIEW_SOURCE_URL_TEMPLATE` from the host env. Env values override matching `sourceRoot`, `sourceInspector.editor`, and `sourceInspector.urlTemplate` init values; init values still work as a fallback for existing projects and CommonJS consumers. Use `VITE_REVIEW_SOURCE_URL_TEMPLATE` only with `VITE_REVIEW_SOURCE_EDITOR=custom`; the template supports `{path}`, `{encodedPath}`, `{line}`, and `{column}`.
165
163
 
@@ -931,6 +931,15 @@ function isMeaningfulClass(value) {
931
931
  ) && !normalized.startsWith("mq-");
932
932
  }
933
933
 
934
+ // src/core/error.ts
935
+ function getErrorMessage(error, fallback) {
936
+ if (error instanceof Error) return error.message;
937
+ if (error && typeof error === "object" && "message" in error && typeof error.message === "string") {
938
+ return error.message;
939
+ }
940
+ return fallback;
941
+ }
942
+
934
943
  // src/core/id.ts
935
944
  function createId() {
936
945
  if ("randomUUID" in crypto) return crypto.randomUUID();
@@ -966,6 +975,13 @@ var HOTKEY_KEY_ALIASES = {
966
975
  n: ["\u315C"],
967
976
  m: ["\u3161"]
968
977
  };
978
+ function isEditableEventTarget(event) {
979
+ const path = event.composedPath?.() ?? [];
980
+ const element = path[0] ?? event.target;
981
+ if (!element || typeof element.tagName !== "string") return false;
982
+ const tag = element.tagName;
983
+ return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || element.isContentEditable === true;
984
+ }
969
985
  function isHotkey(event, hotkey) {
970
986
  const parts = hotkey.split("+").map((part) => part.trim().toLowerCase()).filter(Boolean);
971
987
  const key = parts.find(
@@ -1321,6 +1337,7 @@ var DraftPreviewController = class {
1321
1337
  };
1322
1338
  element.style.visibility = "hidden";
1323
1339
  }
1340
+ syncDraftPreviewCloneRect(this.snapshot.clone, element);
1324
1341
  const metrics = this.config.getMetrics(draft);
1325
1342
  const translate = `translate(${toCssNumber(metrics.cssX)}px, ${toCssNumber(
1326
1343
  metrics.cssY
@@ -1345,16 +1362,11 @@ var DraftPreviewController = class {
1345
1362
  }
1346
1363
  };
1347
1364
  function positionDraftPreviewClone(clone, element, computedStyle) {
1348
- const rect = element.getBoundingClientRect();
1349
1365
  clone.setAttribute("data-dfwr-adjust-preview", "true");
1350
1366
  clone.setAttribute("aria-hidden", "true");
1351
1367
  clone.style.position = "fixed";
1352
- clone.style.left = `${toCssNumber(rect.left)}px`;
1353
- clone.style.top = `${toCssNumber(rect.top)}px`;
1354
1368
  clone.style.right = "auto";
1355
1369
  clone.style.bottom = "auto";
1356
- clone.style.width = `${toCssNumber(rect.width)}px`;
1357
- clone.style.height = `${toCssNumber(rect.height)}px`;
1358
1370
  clone.style.maxWidth = "none";
1359
1371
  clone.style.maxHeight = "none";
1360
1372
  clone.style.margin = "0";
@@ -1366,6 +1378,14 @@ function positionDraftPreviewClone(clone, element, computedStyle) {
1366
1378
  clone.style.willChange = "transform";
1367
1379
  clone.style.transformOrigin = "top left";
1368
1380
  clone.style.transform = "none";
1381
+ syncDraftPreviewCloneRect(clone, element);
1382
+ }
1383
+ function syncDraftPreviewCloneRect(clone, element) {
1384
+ const rect = element.getBoundingClientRect();
1385
+ clone.style.left = `${toCssNumber(rect.left)}px`;
1386
+ clone.style.top = `${toCssNumber(rect.top)}px`;
1387
+ clone.style.width = `${toCssNumber(rect.width)}px`;
1388
+ clone.style.height = `${toCssNumber(rect.height)}px`;
1369
1389
  }
1370
1390
  function getDraftPreviewDisplay(display) {
1371
1391
  if (display === "inline" || display === "contents") return "inline-block";
@@ -1441,11 +1461,11 @@ function createStyleElement() {
1441
1461
  --df-review-color-text: #f7f7f2;
1442
1462
  --df-review-color-text-muted: rgba(247, 247, 242, 0.62);
1443
1463
  --df-review-color-text-subtle: rgba(247, 247, 242, 0.46);
1444
- --df-review-color-accent: #d7ff5f;
1464
+ --df-review-color-accent: #7cc7ff;
1445
1465
  --df-review-color-accent-contrast: #171b1e;
1446
- --df-review-color-accent-soft: rgba(215, 255, 95, 0.16);
1447
- --df-review-color-accent-ring: rgba(215, 255, 95, 0.6);
1448
- --df-review-color-area: #63d7c7;
1466
+ --df-review-color-accent-soft: rgba(124, 199, 255, 0.16);
1467
+ --df-review-color-accent-ring: rgba(124, 199, 255, 0.6);
1468
+ --df-review-color-area: #7cc7ff;
1449
1469
  --df-review-color-error: #ffb7a7;
1450
1470
  --df-review-shadow-panel: 0 18px 48px rgba(0, 0, 0, 0.34);
1451
1471
  --df-review-shadow-popover: 0 16px 38px rgba(0, 0, 0, 0.32);
@@ -1631,9 +1651,9 @@ function createStyleElement() {
1631
1651
  .dfwr-selection-highlight {
1632
1652
  position: fixed;
1633
1653
  z-index: 1;
1634
- border: 2px solid #d7ff5f;
1654
+ border: 2px solid #7cc7ff;
1635
1655
  border-radius: var(--df-review-radius-xs);
1636
- background: rgba(215, 255, 95, 0.08);
1656
+ background: rgba(124, 199, 255, 0.08);
1637
1657
  box-shadow:
1638
1658
  0 0 0 1px rgba(31, 36, 40, 0.72),
1639
1659
  0 0 0 9999px rgba(0, 0, 0, 0.12),
@@ -1642,8 +1662,8 @@ function createStyleElement() {
1642
1662
  }
1643
1663
 
1644
1664
  .dfwr-selection-highlight.is-draft {
1645
- border-color: #63d7c7;
1646
- background: rgba(99, 215, 199, 0.1);
1665
+ border-color: #7cc7ff;
1666
+ background: rgba(124, 199, 255, 0.1);
1647
1667
  box-shadow:
1648
1668
  0 0 0 1px rgba(31, 36, 40, 0.72),
1649
1669
  0 0 0 9999px rgba(0, 0, 0, 0.08),
@@ -1654,9 +1674,9 @@ function createStyleElement() {
1654
1674
  .dfwr-dom-hover {
1655
1675
  position: fixed;
1656
1676
  z-index: 2;
1657
- border: 1px solid #d7ff5f;
1677
+ border: 1px solid #7cc7ff;
1658
1678
  border-radius: var(--df-review-radius-xs);
1659
- background: rgba(215, 255, 95, 0.1);
1679
+ background: rgba(124, 199, 255, 0.1);
1660
1680
  box-shadow:
1661
1681
  0 0 0 1px rgba(31, 36, 40, 0.72),
1662
1682
  0 0 0 9999px rgba(0, 0, 0, 0.08);
@@ -1837,12 +1857,12 @@ function createStyleElement() {
1837
1857
  }
1838
1858
 
1839
1859
  .dfwr-area-preview-layer .dfwr-bound-marker {
1840
- border-color: #63d7c7;
1860
+ border-color: #7cc7ff;
1841
1861
  background: var(--df-review-color-panel);
1842
1862
  box-shadow:
1843
- 0 0 0 5px rgba(99, 215, 199, 0.2),
1863
+ 0 0 0 5px rgba(124, 199, 255, 0.2),
1844
1864
  0 12px 26px rgba(0, 0, 0, 0.3);
1845
- color: #63d7c7;
1865
+ color: #7cc7ff;
1846
1866
  }
1847
1867
 
1848
1868
  .dfwr-bound-marker-icon {
@@ -1925,7 +1945,7 @@ function createStyleElement() {
1925
1945
  border-radius: var(--df-review-radius-pill);
1926
1946
  background: var(--df-review-color-accent);
1927
1947
  box-shadow:
1928
- 0 0 0 4px rgba(215, 255, 95, 0.22),
1948
+ 0 0 0 4px rgba(124, 199, 255, 0.22),
1929
1949
  0 8px 18px rgba(0, 0, 0, 0.28);
1930
1950
  cursor: grab;
1931
1951
  pointer-events: auto;
@@ -1943,7 +1963,7 @@ function createStyleElement() {
1943
1963
  pointer-events: auto;
1944
1964
  color: var(--df-review-color-text);
1945
1965
  background: var(--df-review-color-panel);
1946
- border: 1px solid rgba(215, 255, 95, 0.56);
1966
+ border: 1px solid rgba(124, 199, 255, 0.56);
1947
1967
  border-radius: var(--df-review-radius-md);
1948
1968
  box-shadow: var(--df-review-shadow-popover);
1949
1969
  }
@@ -1952,7 +1972,7 @@ function createStyleElement() {
1952
1972
  .dfwr-area-draft.is-composer {
1953
1973
  max-height: min(360px, calc(100vh - 32px));
1954
1974
  overflow: auto;
1955
- border-color: rgba(99, 215, 199, 0.56);
1975
+ border-color: rgba(124, 199, 255, 0.56);
1956
1976
  }
1957
1977
 
1958
1978
  .dfwr-shell.is-docked-composer .dfwr-dom-popover.is-docked-composer,
@@ -1989,7 +2009,7 @@ function createStyleElement() {
1989
2009
 
1990
2010
  .dfwr-draft-drag-handle:hover,
1991
2011
  .dfwr-draft-drag-handle:focus-visible {
1992
- background: rgba(215, 255, 95, 0.62);
2012
+ background: rgba(124, 199, 255, 0.62);
1993
2013
  }
1994
2014
 
1995
2015
  .dfwr-draft-drag-handle:active {
@@ -2008,7 +2028,7 @@ function createStyleElement() {
2008
2028
  pointer-events: auto;
2009
2029
  color: var(--df-review-color-text);
2010
2030
  background: var(--df-review-color-panel);
2011
- border: 1px solid rgba(215, 255, 95, 0.56);
2031
+ border: 1px solid rgba(124, 199, 255, 0.56);
2012
2032
  border-radius: var(--df-review-radius-md);
2013
2033
  box-shadow: var(--df-review-shadow-popover);
2014
2034
  }
@@ -2227,7 +2247,7 @@ function createStyleElement() {
2227
2247
  }
2228
2248
 
2229
2249
  .dfwr-adjust-panel.is-active {
2230
- border-color: rgba(215, 255, 95, 0.5);
2250
+ border-color: rgba(124, 199, 255, 0.5);
2231
2251
  background: var(--df-review-color-accent-soft);
2232
2252
  }
2233
2253
 
@@ -2266,7 +2286,7 @@ function createStyleElement() {
2266
2286
  .dfwr-adjust-toggle:hover,
2267
2287
  .dfwr-adjust-toggle:focus-visible,
2268
2288
  .dfwr-adjust-toggle.is-active {
2269
- border-color: rgba(215, 255, 95, 0.68);
2289
+ border-color: rgba(124, 199, 255, 0.68);
2270
2290
  background: var(--df-review-color-accent-soft);
2271
2291
  outline: none;
2272
2292
  }
@@ -2329,7 +2349,7 @@ function createStyleElement() {
2329
2349
  }
2330
2350
 
2331
2351
  .dfwr-item:focus-visible {
2332
- outline: 2px solid rgba(215, 255, 95, 0.72);
2352
+ outline: 2px solid rgba(124, 199, 255, 0.72);
2333
2353
  outline-offset: 4px;
2334
2354
  }
2335
2355
 
@@ -2439,8 +2459,8 @@ function createStyleElement() {
2439
2459
  z-index: 2;
2440
2460
  width: 0;
2441
2461
  height: 0;
2442
- border: 1px solid #d7ff5f;
2443
- background: rgba(215, 255, 95, 0.16);
2462
+ border: 1px solid #7cc7ff;
2463
+ background: rgba(124, 199, 255, 0.16);
2444
2464
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.18);
2445
2465
  }
2446
2466
 
@@ -3517,6 +3537,12 @@ function createDomDraftLayer(context, draft, options = {}) {
3517
3537
  pin.setAttribute("aria-label", "Move DOM point");
3518
3538
  pin.style.left = `${hostPoint.x}px`;
3519
3539
  pin.style.top = `${hostPoint.y}px`;
3540
+ if (draft.isSelectionOnly) {
3541
+ pin.classList.add("is-selection-only");
3542
+ pin.tabIndex = -1;
3543
+ group.append(pin);
3544
+ return { layer: group, composer: void 0 };
3545
+ }
3520
3546
  const popover = document.createElement("div");
3521
3547
  const position = getPopoverPosition(hostPoint, environment);
3522
3548
  popover.className = [
@@ -3639,7 +3665,7 @@ function createDomDraftLayer(context, draft, options = {}) {
3639
3665
  attachments: currentDraft.attachments
3640
3666
  });
3641
3667
  };
3642
- const adjustmentControls = isElementDraft ? createAdjustmentControls(context, {
3668
+ const adjustmentControls = isElementDraft && !options.dockComposer ? createAdjustmentControls(context, {
3643
3669
  draft,
3644
3670
  pin,
3645
3671
  popover,
@@ -4285,7 +4311,7 @@ var WebReviewKitView = class {
4285
4311
  this.draftPreview = new DraftPreviewController({
4286
4312
  getEnvironment: () => this.config.getEnvironment(),
4287
4313
  getMetrics: (draft) => getDraftAdjustmentMetrics(draft, presets()),
4288
- hasAdjustment: (draft) => hasDraftAdjustment(draft, presets())
4314
+ hasAdjustment: (draft) => draft.adjustment?.preview !== false && hasDraftAdjustment(draft, presets())
4289
4315
  });
4290
4316
  this.draftContext = {
4291
4317
  config: this.config,
@@ -4306,8 +4332,11 @@ var WebReviewKitView = class {
4306
4332
  shadow.replaceChildren();
4307
4333
  shadow.append(createStyleElement());
4308
4334
  shadow.append(hiddenItemsStyle);
4309
- const hasDismissableDraft = Boolean(state.domDraft || state.areaDraft);
4310
- const shouldDockComposer = this.config.options.ui?.panel === false && hasDismissableDraft && Boolean(this.getShellComposerHost());
4335
+ const hasSelectionOnlyDraft = state.domDraft?.isSelectionOnly === true;
4336
+ const hasDismissableDraft = Boolean(
4337
+ state.domDraft && !hasSelectionOnlyDraft || state.areaDraft
4338
+ );
4339
+ const shouldDockComposer = this.config.options.ui?.panel === false && hasDismissableDraft && !hasSelectionOnlyDraft && Boolean(this.getShellComposerHost());
4311
4340
  let dockedComposer;
4312
4341
  const shell = document.createElement("div");
4313
4342
  shell.className = [
@@ -4429,13 +4458,7 @@ var WebReviewKitView = class {
4429
4458
 
4430
4459
  // src/core/web.review.kit.app.ts
4431
4460
  var ROOT_ID = "df-web-review-kit-root";
4432
- function isEditableEventTarget(event) {
4433
- const path = event.composedPath?.() ?? [];
4434
- const element = path[0] ?? event.target;
4435
- if (!element || typeof element.tagName !== "string") return false;
4436
- const tag = element.tagName;
4437
- return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || element.isContentEditable === true;
4438
- }
4461
+ var VIEWPORT_SCROLL_OPTIONS = { capture: true, passive: true };
4439
4462
  function createWebReviewKit(options) {
4440
4463
  if (typeof window === "undefined" || typeof document === "undefined") {
4441
4464
  return createNoopController();
@@ -4448,6 +4471,8 @@ function createWebReviewKit(options) {
4448
4471
  toggle: () => app.toggle(),
4449
4472
  setMode: (mode) => app.setMode(mode),
4450
4473
  startElementReview: (element, comment) => app.startElementReview(element, comment),
4474
+ selectElement: (element) => app.selectElement(element),
4475
+ adjustElementSelection: (delta, adjustmentOptions) => app.adjustElementSelection(delta, adjustmentOptions),
4451
4476
  getMode: () => app.getMode(),
4452
4477
  highlightItem: (itemId) => app.highlightItem(itemId),
4453
4478
  setHiddenItemIds: (itemIds) => app.setHiddenItemIds(itemIds),
@@ -4468,6 +4493,12 @@ var WebReviewKitApp = class {
4468
4493
  this.isSelectingArea = false;
4469
4494
  this.handleKeyDown = (event) => {
4470
4495
  if (event.key === "Escape" && this.cancelMode()) {
4496
+ const activeElement = document.activeElement;
4497
+ if (activeElement instanceof HTMLButtonElement && activeElement.matches(
4498
+ ".df-review-mode-button, .df-review-section-outline-link.is-dom-select"
4499
+ )) {
4500
+ activeElement.blur();
4501
+ }
4471
4502
  event.preventDefault();
4472
4503
  event.stopPropagation();
4473
4504
  return;
@@ -4482,6 +4513,7 @@ var WebReviewKitApp = class {
4482
4513
  this.renderFrame = window.requestAnimationFrame(() => {
4483
4514
  this.renderFrame = void 0;
4484
4515
  if (this.isDraftComposerFocused()) return;
4516
+ this.syncDomDraftViewportGeometry();
4485
4517
  this.render();
4486
4518
  });
4487
4519
  };
@@ -4539,16 +4571,48 @@ var WebReviewKitApp = class {
4539
4571
  this.shadow = this.root.attachShadow({ mode: "open" });
4540
4572
  document.body.appendChild(this.root);
4541
4573
  document.addEventListener("keydown", this.handleKeyDown, true);
4542
- window.addEventListener("scroll", this.handleViewportChange, true);
4574
+ window.addEventListener(
4575
+ "scroll",
4576
+ this.handleViewportChange,
4577
+ VIEWPORT_SCROLL_OPTIONS
4578
+ );
4543
4579
  window.addEventListener("resize", this.handleViewportChange);
4580
+ this.targetViewportWindow = this.getEnvironment()?.window;
4581
+ if (this.targetViewportWindow && this.targetViewportWindow !== window) {
4582
+ this.targetViewportWindow.addEventListener(
4583
+ "scroll",
4584
+ this.handleViewportChange,
4585
+ VIEWPORT_SCROLL_OPTIONS
4586
+ );
4587
+ this.targetViewportWindow.addEventListener(
4588
+ "resize",
4589
+ this.handleViewportChange
4590
+ );
4591
+ }
4544
4592
  this.render();
4545
4593
  }
4546
4594
  destroy() {
4547
4595
  this.view.clearDraftPreview();
4548
4596
  this.clearDrafts();
4549
4597
  document.removeEventListener("keydown", this.handleKeyDown, true);
4550
- window.removeEventListener("scroll", this.handleViewportChange, true);
4598
+ window.removeEventListener(
4599
+ "scroll",
4600
+ this.handleViewportChange,
4601
+ VIEWPORT_SCROLL_OPTIONS
4602
+ );
4551
4603
  window.removeEventListener("resize", this.handleViewportChange);
4604
+ if (this.targetViewportWindow && this.targetViewportWindow !== window) {
4605
+ this.targetViewportWindow.removeEventListener(
4606
+ "scroll",
4607
+ this.handleViewportChange,
4608
+ VIEWPORT_SCROLL_OPTIONS
4609
+ );
4610
+ this.targetViewportWindow.removeEventListener(
4611
+ "resize",
4612
+ this.handleViewportChange
4613
+ );
4614
+ }
4615
+ this.targetViewportWindow = void 0;
4552
4616
  if (this.renderFrame) {
4553
4617
  window.cancelAnimationFrame(this.renderFrame);
4554
4618
  this.renderFrame = void 0;
@@ -4593,6 +4657,33 @@ var WebReviewKitApp = class {
4593
4657
  this.isSelectingArea = false;
4594
4658
  await this.bindElementDraftToElement(element, { comment });
4595
4659
  }
4660
+ async selectElement(element) {
4661
+ if (!this.isOpen) {
4662
+ this.isOpen = true;
4663
+ }
4664
+ this.setModeState("element");
4665
+ this.clearDrafts();
4666
+ this.isSelectingArea = false;
4667
+ await this.bindElementDraftToElement(element, {
4668
+ isSelectionOnly: true
4669
+ });
4670
+ }
4671
+ adjustElementSelection(delta, options) {
4672
+ if (!this.domDraft?.selection) return false;
4673
+ const current = this.domDraft.adjustment;
4674
+ this.domDraft = {
4675
+ ...this.domDraft,
4676
+ adjustment: {
4677
+ x: (current?.x ?? 0) + (delta.x ?? 0),
4678
+ y: (current?.y ?? 0) + (delta.y ?? 0),
4679
+ scale: (current?.scale ?? 0) + (delta.scale ?? 0),
4680
+ isActive: true,
4681
+ preview: options?.preview ?? current?.preview
4682
+ }
4683
+ };
4684
+ this.render();
4685
+ return true;
4686
+ }
4596
4687
  getMode() {
4597
4688
  return this.mode;
4598
4689
  }
@@ -4672,6 +4763,33 @@ var WebReviewKitApp = class {
4672
4763
  this.render();
4673
4764
  return true;
4674
4765
  }
4766
+ syncDomDraftViewportGeometry() {
4767
+ const draft = this.domDraft;
4768
+ const element = draft?.previewElement;
4769
+ const environment = this.getEnvironment();
4770
+ if (!draft?.selection || !element?.isConnected || !environment || element.ownerDocument !== environment.document) {
4771
+ return;
4772
+ }
4773
+ const rect = element.getBoundingClientRect();
4774
+ if (rect.width <= 0 || rect.height <= 0) return;
4775
+ const selection = {
4776
+ left: rect.left,
4777
+ top: rect.top,
4778
+ width: rect.width,
4779
+ height: rect.height
4780
+ };
4781
+ this.domDraft = {
4782
+ ...draft,
4783
+ marker: {
4784
+ ...draft.marker,
4785
+ viewport: roundPoint({ x: rect.left, y: rect.top })
4786
+ },
4787
+ selection: {
4788
+ ...draft.selection,
4789
+ viewport: toPublicSelection(selection)
4790
+ }
4791
+ };
4792
+ }
4675
4793
  isDraftComposerFocused() {
4676
4794
  if (!this.domDraft && !this.areaDraft) return false;
4677
4795
  const composerHost = this.getEnvironment()?.composerHost;
@@ -4895,46 +5013,28 @@ var WebReviewKitApp = class {
4895
5013
  this.root.style.display = previousDisplay;
4896
5014
  }
4897
5015
  }
4898
- async captureDomDraft(input) {
4899
- if (this.isCapturingViewport) return;
4900
- const environment = this.getEnvironment();
4901
- const draft = this.domDraft;
4902
- if (!draft) return;
4903
- if (!environment?.captureViewport) {
4904
- this.draftError = "Viewport capture helper is not available.";
4905
- this.render();
4906
- return;
4907
- }
4908
- const captureInput = this.createViewportCaptureInput(
4909
- environment,
5016
+ captureDomDraft(input) {
5017
+ return this.captureDraft(
4910
5018
  input,
4911
- input.selection?.viewport
5019
+ () => this.domDraft,
5020
+ (draft) => {
5021
+ this.domDraft = draft;
5022
+ }
4912
5023
  );
4913
- this.draftError = "";
4914
- this.isCapturingViewport = true;
4915
- this.render();
4916
- try {
4917
- const result = await environment.captureViewport(captureInput);
4918
- const attachment = this.createCaptureDraftAttachment(result, captureInput);
4919
- const currentDraft = this.domDraft ?? draft;
4920
- this.domDraft = {
4921
- ...currentDraft,
4922
- attachments: [...currentDraft.attachments ?? [], attachment]
4923
- };
4924
- } catch (error) {
4925
- this.draftError = this.getErrorMessage(
4926
- error,
4927
- "Failed to capture viewport."
4928
- );
4929
- } finally {
4930
- this.isCapturingViewport = false;
4931
- this.render();
4932
- }
4933
5024
  }
4934
- async captureAreaDraft(input) {
5025
+ captureAreaDraft(input) {
5026
+ return this.captureDraft(
5027
+ input,
5028
+ () => this.areaDraft,
5029
+ (draft) => {
5030
+ this.areaDraft = draft;
5031
+ }
5032
+ );
5033
+ }
5034
+ async captureDraft(input, getDraft, setDraft) {
4935
5035
  if (this.isCapturingViewport) return;
4936
5036
  const environment = this.getEnvironment();
4937
- const draft = this.areaDraft;
5037
+ const draft = getDraft();
4938
5038
  if (!draft) return;
4939
5039
  if (!environment?.captureViewport) {
4940
5040
  this.draftError = "Viewport capture helper is not available.";
@@ -4952,16 +5052,13 @@ var WebReviewKitApp = class {
4952
5052
  try {
4953
5053
  const result = await environment.captureViewport(captureInput);
4954
5054
  const attachment = this.createCaptureDraftAttachment(result, captureInput);
4955
- const currentDraft = this.areaDraft ?? draft;
4956
- this.areaDraft = {
5055
+ const currentDraft = getDraft() ?? draft;
5056
+ setDraft({
4957
5057
  ...currentDraft,
4958
5058
  attachments: [...currentDraft.attachments ?? [], attachment]
4959
- };
5059
+ });
4960
5060
  } catch (error) {
4961
- this.draftError = this.getErrorMessage(
4962
- error,
4963
- "Failed to capture viewport."
4964
- );
5061
+ this.draftError = getErrorMessage(error, "Failed to capture viewport.");
4965
5062
  } finally {
4966
5063
  this.isCapturingViewport = false;
4967
5064
  this.render();
@@ -5100,17 +5197,10 @@ var WebReviewKitApp = class {
5100
5197
  );
5101
5198
  }
5102
5199
  getCreateItemErrorMessage(error, wasUploadingAttachments) {
5103
- const message = this.getErrorMessage(error, "Failed to save QA.");
5200
+ const message = getErrorMessage(error, "Failed to save QA.");
5104
5201
  const reason = error && typeof error === "object" && "reason" in error && typeof error.reason === "string" ? ` (${error.reason})` : "";
5105
5202
  return wasUploadingAttachments && reason ? `Attachment upload failed${reason}: ${message}` : message;
5106
5203
  }
5107
- getErrorMessage(error, fallback) {
5108
- if (error instanceof Error) return error.message;
5109
- if (error && typeof error === "object" && "message" in error && typeof error.message === "string") {
5110
- return error.message;
5111
- }
5112
- return fallback;
5113
- }
5114
5204
  async restoreItem(item) {
5115
5205
  this.setModeState("idle");
5116
5206
  this.clearDrafts();
@@ -5143,6 +5233,11 @@ function createNoopController() {
5143
5233
  },
5144
5234
  async startElementReview() {
5145
5235
  },
5236
+ async selectElement() {
5237
+ },
5238
+ adjustElementSelection() {
5239
+ return false;
5240
+ },
5146
5241
  getMode() {
5147
5242
  return "idle";
5148
5243
  },
@@ -5169,6 +5264,8 @@ export {
5169
5264
  isPointInViewport,
5170
5265
  toHostPoint,
5171
5266
  clamp,
5267
+ getErrorMessage,
5268
+ isEditableEventTarget,
5172
5269
  isHotkey,
5173
5270
  getHotkeyActionKey,
5174
5271
  DEFAULT_REVIEW_VIEWPORTS,
@@ -5181,7 +5278,8 @@ export {
5181
5278
  getItemHighlightSelection,
5182
5279
  shouldShowMarkerForScope,
5183
5280
  runWithAutoScrollBehavior,
5281
+ getDraftViewportScale,
5184
5282
  reviewTypographyTokens,
5185
5283
  createWebReviewKit
5186
5284
  };
5187
- //# sourceMappingURL=chunk-4ZP7B7R6.js.map
5285
+ //# sourceMappingURL=chunk-FWN7RQCW.js.map