@designfever/web-review-kit 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,6 +26,7 @@ This package does not own internal operator tools, private admin keys, or produc
26
26
  - [Architecture and runtime logic](docs/architecture.md): core runtime, React shell, coordinate, anchor, and extension boundaries.
27
27
  - [Figma overlay](docs/figma-overlay.md): how the shell toggles a host Figma overlay.
28
28
  - [Grid overlay](docs/grid-overlay.md): how the shell toggles a host grid/helper overlay.
29
+ - [Release notes 0.6.0](docs/release-notes-0.6.0.md): latest shell/source tree changes and validation notes.
29
30
 
30
31
  ## Quick Start
31
32
 
@@ -92,12 +93,15 @@ Browser env must use a Supabase `anon` key only. Do not put `service_role` or Op
92
93
  ```ts
93
94
  import { createWebReviewKit, localAdapter } from '@designfever/web-review-kit';
94
95
  import { mountReviewShell } from '@designfever/web-review-kit/react-shell';
95
- import { reviewSourceLocator } from '@designfever/web-review-kit/vite';
96
+ import {
97
+ reviewDataLocator,
98
+ reviewSourceLocator,
99
+ } from '@designfever/web-review-kit/vite';
96
100
  ```
97
101
 
98
102
  - `@designfever/web-review-kit`: core API, adapters, shared types.
99
103
  - `@designfever/web-review-kit/react-shell`: review shell UI, presence adapters, page glob helper.
100
- - `@designfever/web-review-kit/vite`: dev-only JSX source locator for source file hints.
104
+ - `@designfever/web-review-kit/vite`: dev-only source/data locators for review hints.
101
105
  - `src/*` is not a public import path.
102
106
 
103
107
  ## Optional Source Locator
@@ -106,7 +110,10 @@ For local QA, add the Vite plugin to inject source hints into rendered DOM nodes
106
110
 
107
111
  ```ts
108
112
  import { defineConfig } from 'vite';
109
- import { reviewSourceLocator } from '@designfever/web-review-kit/vite';
113
+ import {
114
+ reviewDataLocator,
115
+ reviewSourceLocator,
116
+ } from '@designfever/web-review-kit/vite';
110
117
 
111
118
  export default defineConfig({
112
119
  plugins: [
@@ -115,11 +122,16 @@ export default defineConfig({
115
122
  include: ['src'],
116
123
  filePath: 'absolute',
117
124
  }),
125
+ reviewDataLocator({
126
+ enabled: true,
127
+ include: ['src/data'],
128
+ filePath: 'absolute',
129
+ }),
118
130
  ],
119
131
  });
120
132
  ```
121
133
 
122
- 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. DOM QA cards also show a source action when the saved item has source hints. Keep this plugin disabled for production builds because it writes source paths into the DOM.
134
+ 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 side rail can also open a Source Tree panel with section/source/data 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.
123
135
 
124
136
  ```tsx
125
137
  mountReviewShell({
@@ -129,6 +141,10 @@ mountReviewShell({
129
141
  sourceRoot: import.meta.env.VITE_REVIEW_SOURCE_ROOT,
130
142
  sourceInspector: {
131
143
  editor: 'cursor', // 'vscode' | 'cursor' | 'webstorm' | 'custom'
144
+ maxDepth: 9,
145
+ hoverOutline: true,
146
+ includePlacer: false,
147
+ ignore: ['core.section', 'control.render'],
132
148
  // urlTemplate: 'my-editor://open?file={encodedPath}&line={line}&column={column}',
133
149
  },
134
150
  });
@@ -400,6 +400,20 @@ function getDomAnchorFromPoint(point, configuredAttribute = "data-qa-id", enviro
400
400
  source: getDomSourceHint(target)
401
401
  };
402
402
  }
403
+ function getDomAnchorFromElement(target, configuredAttribute = "data-qa-id", environment) {
404
+ if (target.ownerDocument !== environment.document) return void 0;
405
+ const candidates = createAnchorCandidates(target, configuredAttribute);
406
+ const primary = candidates[0];
407
+ if (!primary) return void 0;
408
+ return {
409
+ ...primary,
410
+ candidates,
411
+ htmlSnippet: getElementHtmlSnippet(
412
+ getAnchorSourceElement(target, primary, configuredAttribute) ?? target
413
+ ),
414
+ source: getDomSourceHint(target)
415
+ };
416
+ }
403
417
  function getElementViewportSelection(anchor, environment, preferredSelection) {
404
418
  const element = getAnchorElement(anchor, environment, preferredSelection);
405
419
  if (!element) return void 0;
@@ -1252,6 +1266,14 @@ function createStyleElement() {
1252
1266
  cursor: default;
1253
1267
  }
1254
1268
 
1269
+ .dfwr-shell.is-docked-composer {
1270
+ position: relative;
1271
+ inset: auto;
1272
+ z-index: auto;
1273
+ padding: 0;
1274
+ pointer-events: auto;
1275
+ }
1276
+
1255
1277
  .dfwr-panel {
1256
1278
  position: fixed;
1257
1279
  right: 16px;
@@ -1752,6 +1774,20 @@ function createStyleElement() {
1752
1774
  border-color: rgba(99, 215, 199, 0.56);
1753
1775
  }
1754
1776
 
1777
+ .dfwr-shell.is-docked-composer .dfwr-note-popover.is-docked-composer,
1778
+ .dfwr-shell.is-docked-composer .dfwr-area-draft.is-docked-composer {
1779
+ position: relative;
1780
+ left: auto;
1781
+ right: auto;
1782
+ top: auto;
1783
+ z-index: auto;
1784
+ max-height: none;
1785
+ }
1786
+
1787
+ .dfwr-shell.is-docked-composer .dfwr-textarea {
1788
+ min-height: 184px;
1789
+ }
1790
+
1755
1791
  .dfwr-note-popover.is-dragging,
1756
1792
  .dfwr-area-draft.is-dragging {
1757
1793
  user-select: none;
@@ -1800,6 +1836,50 @@ function createStyleElement() {
1800
1836
  padding: 0;
1801
1837
  }
1802
1838
 
1839
+ .dfwr-actions.has-leading {
1840
+ align-items: center;
1841
+ justify-content: space-between;
1842
+ gap: 12px;
1843
+ }
1844
+
1845
+ .dfwr-actions-leading,
1846
+ .dfwr-actions-primary {
1847
+ display: flex;
1848
+ align-items: center;
1849
+ gap: 8px;
1850
+ min-width: 0;
1851
+ }
1852
+
1853
+ .dfwr-actions-primary {
1854
+ margin-left: auto;
1855
+ }
1856
+
1857
+ .dfwr-shell.is-docked-composer .dfwr-actions.has-leading {
1858
+ align-items: stretch;
1859
+ }
1860
+
1861
+ .dfwr-shell.is-docked-composer .dfwr-actions.has-leading .dfwr-button,
1862
+ .dfwr-shell.is-docked-composer .dfwr-actions.has-leading .dfwr-adjust-toggle {
1863
+ height: var(--df-review-control-height-md);
1864
+ min-height: var(--df-review-control-height-md);
1865
+ border-radius: var(--df-review-radius-sm);
1866
+ }
1867
+
1868
+ .dfwr-shell.is-docked-composer .dfwr-actions.has-leading .dfwr-button {
1869
+ min-width: 96px;
1870
+ padding: 0 12px;
1871
+ font-size: var(--df-review-font-size-sm);
1872
+ }
1873
+
1874
+ .dfwr-shell.is-docked-composer .dfwr-actions.has-leading .dfwr-adjust-toggle {
1875
+ width: var(--df-review-control-height-md);
1876
+ }
1877
+
1878
+ .dfwr-shell.is-docked-composer .dfwr-actions.has-leading .dfwr-adjust-toggle svg {
1879
+ width: 18px;
1880
+ height: 18px;
1881
+ }
1882
+
1803
1883
  .dfwr-note-actions {
1804
1884
  justify-content: flex-end;
1805
1885
  }
@@ -1836,6 +1916,12 @@ function createStyleElement() {
1836
1916
  outline-offset: 1px;
1837
1917
  }
1838
1918
 
1919
+ @media (hover: none) and (pointer: coarse) {
1920
+ .dfwr-textarea {
1921
+ font-size: 16px;
1922
+ }
1923
+ }
1924
+
1839
1925
  .dfwr-adjust-panel {
1840
1926
  display: grid;
1841
1927
  gap: 4px;
@@ -2118,10 +2204,12 @@ function createStyleElement() {
2118
2204
 
2119
2205
  @media (max-width: 520px) {
2120
2206
  .dfwr-panel {
2207
+ left: 8px;
2121
2208
  right: 8px;
2122
- top: 8px;
2123
- width: calc(100vw - 16px);
2124
- max-height: calc(100vh - 16px);
2209
+ top: auto;
2210
+ bottom: 8px;
2211
+ width: auto;
2212
+ max-height: min(70vh, calc(100vh - 16px));
2125
2213
  }
2126
2214
  }
2127
2215
  `;
@@ -2201,6 +2289,7 @@ var WebReviewKitView = class {
2201
2289
  }
2202
2290
  clearDraftPreview() {
2203
2291
  this.restoreDraftPreview();
2292
+ this.clearShellComposer();
2204
2293
  }
2205
2294
  render(shadow, hiddenItemsStyle) {
2206
2295
  const state = this.state;
@@ -2211,11 +2300,13 @@ var WebReviewKitView = class {
2211
2300
  shadow.append(createStyleElement());
2212
2301
  shadow.append(hiddenItemsStyle);
2213
2302
  const hasDismissableDraft = Boolean(state.noteDraft || state.areaDraft);
2303
+ const shouldDockComposer = this.config.options.ui?.panel === false && hasDismissableDraft && Boolean(this.getShellComposerHost());
2304
+ let dockedComposer;
2214
2305
  const shell = document.createElement("div");
2215
2306
  shell.className = [
2216
2307
  "dfwr-shell",
2217
2308
  state.isOpen ? "is-open" : "",
2218
- hasDismissableDraft ? "has-dismissible-draft" : ""
2309
+ hasDismissableDraft && !shouldDockComposer ? "has-dismissible-draft" : ""
2219
2310
  ].filter(Boolean).join(" ");
2220
2311
  shell.setAttribute("aria-hidden", state.isOpen ? "false" : "true");
2221
2312
  if (this.config.options.ui?.panel !== false) {
@@ -2232,13 +2323,21 @@ var WebReviewKitView = class {
2232
2323
  shell.append(panel);
2233
2324
  }
2234
2325
  shell.append(this.createMarkerLayer());
2235
- if (state.isOpen && hasDismissableDraft) {
2326
+ if (state.isOpen && hasDismissableDraft && !shouldDockComposer) {
2236
2327
  shell.append(this.createDraftCancelLayer());
2237
2328
  }
2238
2329
  if (state.isOpen && (state.mode === "note" || state.mode === "element")) {
2239
- shell.append(
2240
- state.noteDraft ? this.createNotePopover(state.noteDraft) : state.mode === "element" ? this.createElementLayer() : this.createNoteLayer()
2241
- );
2330
+ if (state.noteDraft) {
2331
+ const noteDraft = this.createNotePopover(state.noteDraft, {
2332
+ dockComposer: shouldDockComposer
2333
+ });
2334
+ shell.append(noteDraft.layer);
2335
+ dockedComposer = noteDraft.composer;
2336
+ } else {
2337
+ shell.append(
2338
+ state.mode === "element" ? this.createElementLayer() : this.createNoteLayer()
2339
+ );
2340
+ }
2242
2341
  }
2243
2342
  if (state.isOpen && state.mode === "area" && !state.areaDraft) {
2244
2343
  shell.append(this.createAreaLayer());
@@ -2247,32 +2346,73 @@ var WebReviewKitView = class {
2247
2346
  if (state.areaDraft.selection) {
2248
2347
  shell.append(this.createAreaDraftOverlay(state.areaDraft));
2249
2348
  }
2250
- shell.append(this.createAreaDraftPopover(state.areaDraft));
2349
+ const areaComposer = this.createAreaDraftPopover(state.areaDraft, {
2350
+ dockComposer: shouldDockComposer
2351
+ });
2352
+ if (shouldDockComposer) {
2353
+ dockedComposer = areaComposer;
2354
+ } else {
2355
+ shell.append(areaComposer);
2356
+ }
2251
2357
  }
2252
2358
  shadow.append(shell);
2359
+ this.renderShellComposer(dockedComposer);
2253
2360
  }
2254
2361
  get state() {
2255
2362
  return this.config.getState();
2256
2363
  }
2364
+ getShellComposerHost() {
2365
+ const environment = this.config.getEnvironment();
2366
+ if (this.config.options.ui?.panel !== false) return void 0;
2367
+ return environment?.composerHost ?? void 0;
2368
+ }
2369
+ renderShellComposer(composer) {
2370
+ const host = composer ? this.getShellComposerHost() : void 0;
2371
+ if (!host || !composer) {
2372
+ this.clearShellComposer();
2373
+ return;
2374
+ }
2375
+ if (this.shellComposerHost && this.shellComposerHost !== host) {
2376
+ this.clearShellComposer();
2377
+ }
2378
+ this.shellComposerHost = host;
2379
+ host.dataset.hasDraftComposer = "true";
2380
+ if (host.parentElement) {
2381
+ host.parentElement.dataset.hasDraftComposer = "true";
2382
+ }
2383
+ const shell = document.createElement("div");
2384
+ shell.className = "dfwr-shell is-open is-shell-draft is-docked-composer";
2385
+ shell.append(composer);
2386
+ host.replaceChildren(createStyleElement(), shell);
2387
+ }
2388
+ clearShellComposer() {
2389
+ const host = this.shellComposerHost;
2390
+ host?.replaceChildren();
2391
+ if (host) {
2392
+ delete host.dataset.hasDraftComposer;
2393
+ delete host.parentElement?.dataset.hasDraftComposer;
2394
+ }
2395
+ this.shellComposerHost = void 0;
2396
+ }
2257
2397
  createDraftCancelLayer() {
2258
2398
  const layer = document.createElement("div");
2259
2399
  layer.className = "dfwr-draft-cancel-layer";
2260
2400
  layer.setAttribute("aria-hidden", "true");
2261
- const cancel = (event) => {
2262
- event.preventDefault();
2263
- event.stopPropagation();
2264
- event.stopImmediatePropagation();
2265
- this.config.actions.setModeState("idle");
2266
- this.config.actions.clearDrafts();
2267
- this.config.actions.setSelectingArea(false);
2268
- this.config.actions.render();
2269
- };
2270
2401
  layer.addEventListener("pointerdown", (event) => {
2271
2402
  if (event.button !== 0) return;
2272
- cancel(event);
2403
+ this.cancelDraft(event);
2273
2404
  });
2274
2405
  return layer;
2275
2406
  }
2407
+ cancelDraft(event) {
2408
+ event?.preventDefault();
2409
+ event?.stopPropagation();
2410
+ event?.stopImmediatePropagation();
2411
+ this.config.actions.setModeState("idle");
2412
+ this.config.actions.clearDrafts();
2413
+ this.config.actions.setSelectingArea(false);
2414
+ this.config.actions.render();
2415
+ }
2276
2416
  getDraftAdjustmentMetrics(draft) {
2277
2417
  const adjustment = draft.adjustment;
2278
2418
  const x = adjustment?.x ?? 0;
@@ -2666,11 +2806,11 @@ ${adjustment}` : adjustment;
2666
2806
  empty.textContent = this.state.noteDraft ? "Write the note in the page box." : this.state.mode === "element" ? "Click an element to add QA." : "Click on the page to place a note.";
2667
2807
  return empty;
2668
2808
  }
2669
- createNotePopover(draft) {
2809
+ createNotePopover(draft, options = {}) {
2670
2810
  const environment = this.config.getEnvironment();
2671
2811
  const group = document.createElement("div");
2672
2812
  group.className = "dfwr-note-draft";
2673
- if (!environment) return group;
2813
+ if (!environment) return { layer: group, composer: void 0 };
2674
2814
  const isElementDraft = this.state.mode === "element" && Boolean(draft.selection);
2675
2815
  const hostPoint = toHostPoint(
2676
2816
  isElementDraft ? this.getAdjustedDraftPoint(draft.marker.viewport, draft) : draft.marker.viewport,
@@ -2694,8 +2834,14 @@ ${adjustment}` : adjustment;
2694
2834
  pin.style.top = `${hostPoint.y}px`;
2695
2835
  const popover = document.createElement("div");
2696
2836
  const position = getPopoverPosition(hostPoint, environment);
2697
- popover.className = `dfwr-note-popover${isElementDraft ? " is-composer" : ""}`;
2698
- if (isElementDraft) {
2837
+ popover.className = [
2838
+ "dfwr-note-popover",
2839
+ isElementDraft ? "is-composer" : "",
2840
+ options.dockComposer ? "is-docked-composer" : ""
2841
+ ].filter(Boolean).join(" ");
2842
+ if (options.dockComposer) {
2843
+ popover.style.width = "100%";
2844
+ } else if (isElementDraft) {
2699
2845
  const selection = draft.selection ? toHostSelection(
2700
2846
  this.getAdjustedDraftSelection(
2701
2847
  toViewportSelection(draft.selection.viewport),
@@ -2754,18 +2900,27 @@ ${adjustment}` : adjustment;
2754
2900
  pin,
2755
2901
  popover,
2756
2902
  selectionHighlight,
2757
- textarea
2903
+ textarea,
2904
+ dockToggle: options.dockComposer
2758
2905
  }) : void 0;
2759
- const actions = this.createFormActions("Save note", saveDraft);
2906
+ const actions = this.createFormActions("Save note", saveDraft, {
2907
+ leading: adjustmentControls?.actionButton ? [adjustmentControls.actionButton] : void 0
2908
+ });
2760
2909
  form.append(
2761
2910
  ...meta ? [meta] : [],
2762
2911
  ...adjustmentControls ? [adjustmentControls.panel] : [],
2763
2912
  textarea,
2764
2913
  actions
2765
2914
  );
2766
- const dragHandle = isElementDraft ? this.createDraftDragHandle("Move DOM composer") : void 0;
2767
- popover.append(...dragHandle ? [dragHandle] : [], form);
2768
- group.append(pin, popover);
2915
+ const dragHandle = isElementDraft && !options.dockComposer ? this.createDraftDragHandle("Move DOM composer") : void 0;
2916
+ popover.append(
2917
+ ...dragHandle ? [dragHandle] : [],
2918
+ form
2919
+ );
2920
+ group.append(pin);
2921
+ if (!options.dockComposer) {
2922
+ group.append(popover);
2923
+ }
2769
2924
  if (dragHandle) {
2770
2925
  this.attachDraftComposerDrag(popover, dragHandle, (composerPosition) => {
2771
2926
  const noteDraft = this.state.noteDraft ?? draft;
@@ -2778,18 +2933,23 @@ ${adjustment}` : adjustment;
2778
2933
  }
2779
2934
  this.attachDraftPinDrag(
2780
2935
  pin,
2781
- isElementDraft ? void 0 : popover,
2936
+ isElementDraft || options.dockComposer ? void 0 : popover,
2782
2937
  meta,
2783
2938
  textarea
2784
2939
  );
2785
- window.setTimeout(() => {
2786
- if (draft.adjustment?.isActive) {
2787
- adjustmentControls?.focusTarget.focus();
2788
- return;
2789
- }
2790
- textarea.focus();
2791
- }, 0);
2792
- return group;
2940
+ if (!options.dockComposer) {
2941
+ window.setTimeout(() => {
2942
+ if (draft.adjustment?.isActive) {
2943
+ adjustmentControls?.focusTarget.focus();
2944
+ return;
2945
+ }
2946
+ textarea.focus();
2947
+ }, 0);
2948
+ }
2949
+ return {
2950
+ layer: group,
2951
+ composer: options.dockComposer ? popover : void 0
2952
+ };
2793
2953
  }
2794
2954
  createDraftDragHandle(label) {
2795
2955
  const handle = document.createElement("button");
@@ -2881,7 +3041,8 @@ ${adjustment}` : adjustment;
2881
3041
  pin,
2882
3042
  popover,
2883
3043
  selectionHighlight,
2884
- textarea
3044
+ textarea,
3045
+ dockToggle
2885
3046
  }) {
2886
3047
  const panel = document.createElement("div");
2887
3048
  panel.className = "dfwr-adjust-panel is-dom-adjust-panel";
@@ -2957,12 +3118,16 @@ ${adjustment}` : adjustment;
2957
3118
  }
2958
3119
  }));
2959
3120
  });
2960
- header.append(help, adjust);
3121
+ header.append(help);
3122
+ if (!dockToggle) {
3123
+ header.append(adjust);
3124
+ }
2961
3125
  panel.append(header, xyStatus, scaleStatus);
2962
3126
  syncControls(draft);
2963
3127
  return {
2964
3128
  panel,
2965
- focusTarget: adjust
3129
+ focusTarget: adjust,
3130
+ actionButton: dockToggle ? adjust : void 0
2966
3131
  };
2967
3132
  }
2968
3133
  getAdjustmentKeyDelta(event) {
@@ -3088,11 +3253,17 @@ ${adjustment}` : adjustment;
3088
3253
  }
3089
3254
  return layer;
3090
3255
  }
3091
- createAreaDraftPopover(draft) {
3256
+ createAreaDraftPopover(draft, options = {}) {
3092
3257
  const environment = this.config.getEnvironment();
3093
3258
  const popover = document.createElement("div");
3094
- popover.className = "dfwr-area-draft is-composer";
3095
- if (environment && draft.selection) {
3259
+ popover.className = [
3260
+ "dfwr-area-draft",
3261
+ "is-composer",
3262
+ options.dockComposer ? "is-docked-composer" : ""
3263
+ ].filter(Boolean).join(" ");
3264
+ if (options.dockComposer) {
3265
+ popover.style.width = "100%";
3266
+ } else if (environment && draft.selection) {
3096
3267
  const selection = toHostSelection(
3097
3268
  toViewportSelection(draft.selection.viewport),
3098
3269
  environment
@@ -3108,15 +3279,20 @@ ${adjustment}` : adjustment;
3108
3279
  popover.style.width = `${composer.width}px`;
3109
3280
  popover.style.right = "auto";
3110
3281
  }
3111
- const dragHandle = this.createDraftDragHandle("Move area composer");
3112
- popover.append(dragHandle, this.createAreaForm());
3113
- this.attachDraftComposerDrag(popover, dragHandle, (composerPosition) => {
3114
- const areaDraft = this.state.areaDraft ?? draft;
3115
- this.config.actions.setAreaDraft({
3116
- ...areaDraft,
3117
- composerPosition
3282
+ const dragHandle = options.dockComposer ? void 0 : this.createDraftDragHandle("Move area composer");
3283
+ popover.append(
3284
+ ...dragHandle ? [dragHandle] : [],
3285
+ this.createAreaForm()
3286
+ );
3287
+ if (dragHandle) {
3288
+ this.attachDraftComposerDrag(popover, dragHandle, (composerPosition) => {
3289
+ const areaDraft = this.state.areaDraft ?? draft;
3290
+ this.config.actions.setAreaDraft({
3291
+ ...areaDraft,
3292
+ composerPosition
3293
+ });
3118
3294
  });
3119
- });
3295
+ }
3120
3296
  return popover;
3121
3297
  }
3122
3298
  createFormActions(saveLabel, onSave, options) {
@@ -3136,12 +3312,19 @@ ${adjustment}` : adjustment;
3136
3312
  cancel.type = "button";
3137
3313
  cancel.textContent = "Cancel";
3138
3314
  cancel.addEventListener("click", (event) => {
3139
- event.preventDefault();
3140
- event.stopPropagation();
3141
- this.config.actions.setModeState("idle");
3142
- this.config.actions.clearDrafts();
3143
- this.config.actions.render();
3315
+ this.cancelDraft(event);
3144
3316
  });
3317
+ if (options?.leading?.length) {
3318
+ actions.classList.add("has-leading");
3319
+ const leading = document.createElement("div");
3320
+ leading.className = "dfwr-actions-leading";
3321
+ leading.append(...options.leading);
3322
+ const primary = document.createElement("div");
3323
+ primary.className = "dfwr-actions-primary";
3324
+ primary.append(save, cancel);
3325
+ actions.append(leading, primary);
3326
+ return actions;
3327
+ }
3145
3328
  if (options?.beforeSave?.length || options?.className) {
3146
3329
  actions.append(cancel, ...options.beforeSave ?? [], save);
3147
3330
  return actions;
@@ -3542,6 +3725,7 @@ function createWebReviewKit(options) {
3542
3725
  close: () => app.close(),
3543
3726
  toggle: () => app.toggle(),
3544
3727
  setMode: (mode) => app.setMode(mode),
3728
+ startElementReview: (element, comment) => app.startElementReview(element, comment),
3545
3729
  getMode: () => app.getMode(),
3546
3730
  highlightItem: (itemId) => app.highlightItem(itemId),
3547
3731
  setHiddenItemIds: (itemIds) => app.setHiddenItemIds(itemIds),
@@ -3569,9 +3753,10 @@ var WebReviewKitApp = class {
3569
3753
  this.toggle();
3570
3754
  };
3571
3755
  this.handleViewportChange = () => {
3572
- if (!this.isOpen || this.renderFrame) return;
3756
+ if (!this.isOpen || this.renderFrame || this.isDraftComposerFocused()) return;
3573
3757
  this.renderFrame = window.requestAnimationFrame(() => {
3574
3758
  this.renderFrame = void 0;
3759
+ if (this.isDraftComposerFocused()) return;
3575
3760
  this.render();
3576
3761
  });
3577
3762
  };
@@ -3672,6 +3857,16 @@ var WebReviewKitApp = class {
3672
3857
  this.areaDraft = void 0;
3673
3858
  this.render();
3674
3859
  }
3860
+ async startElementReview(element, comment) {
3861
+ if (!this.isOpen) {
3862
+ this.isOpen = true;
3863
+ }
3864
+ this.setModeState("element");
3865
+ this.noteDraft = void 0;
3866
+ this.areaDraft = void 0;
3867
+ this.isSelectingArea = false;
3868
+ await this.bindElementDraftToElement(element, comment);
3869
+ }
3675
3870
  getMode() {
3676
3871
  return this.mode;
3677
3872
  }
@@ -3738,6 +3933,14 @@ var WebReviewKitApp = class {
3738
3933
  this.render();
3739
3934
  return true;
3740
3935
  }
3936
+ isDraftComposerFocused() {
3937
+ if (!this.noteDraft && !this.areaDraft) return false;
3938
+ const composerHost = this.getEnvironment()?.composerHost;
3939
+ const activeElement = composerHost?.ownerDocument.activeElement;
3940
+ return Boolean(
3941
+ composerHost && activeElement && composerHost.contains(activeElement)
3942
+ );
3943
+ }
3741
3944
  getEnvironment() {
3742
3945
  const target = typeof this.options.target === "function" ? this.options.target() : this.options.target;
3743
3946
  if (!target) {
@@ -3766,6 +3969,7 @@ var WebReviewKitApp = class {
3766
3969
  height: target.window.innerHeight
3767
3970
  };
3768
3971
  const overlayRect = target.getOverlayRect?.() ?? rect;
3972
+ const composerHost = target.getComposerHost?.();
3769
3973
  return {
3770
3974
  window: target.window,
3771
3975
  document: target.document,
@@ -3780,7 +3984,8 @@ var WebReviewKitApp = class {
3780
3984
  top: overlayRect.top,
3781
3985
  width: overlayRect.width,
3782
3986
  height: overlayRect.height
3783
- }
3987
+ },
3988
+ composerHost
3784
3989
  };
3785
3990
  } catch {
3786
3991
  return void 0;
@@ -3880,6 +4085,47 @@ var WebReviewKitApp = class {
3880
4085
  this.noteDraft = draft;
3881
4086
  this.render();
3882
4087
  }
4088
+ async bindElementDraftToElement(element, comment) {
4089
+ const environment = this.getEnvironment();
4090
+ if (!environment || element.ownerDocument !== environment.document) return;
4091
+ const viewport = getViewportSize(environment);
4092
+ const draft = await this.withOverlayHidden(() => {
4093
+ const rect = element.getBoundingClientRect();
4094
+ if (rect.width <= 0 || rect.height <= 0) return void 0;
4095
+ const selection = {
4096
+ left: rect.left,
4097
+ top: rect.top,
4098
+ width: rect.width,
4099
+ height: rect.height
4100
+ };
4101
+ const anchor = getDomAnchorFromElement(
4102
+ element,
4103
+ this.options.anchors?.attribute,
4104
+ environment
4105
+ );
4106
+ const markerPoint = { x: selection.left, y: selection.top };
4107
+ const marker = {
4108
+ viewport: roundPoint(markerPoint),
4109
+ relative: anchor ? getRelativePoint(markerPoint, anchor, environment) : void 0
4110
+ };
4111
+ const reviewSelection = {
4112
+ viewport: toPublicSelection(selection),
4113
+ relative: anchor ? getRelativeSelection(selection, anchor, environment) : void 0
4114
+ };
4115
+ const previewElement = "style" in element ? element : void 0;
4116
+ return {
4117
+ viewport,
4118
+ anchor,
4119
+ marker,
4120
+ selection: reviewSelection,
4121
+ comment,
4122
+ previewElement
4123
+ };
4124
+ });
4125
+ if (!draft) return;
4126
+ this.noteDraft = draft;
4127
+ this.render();
4128
+ }
3883
4129
  async createAreaDraft(selection) {
3884
4130
  const environment = this.getEnvironment();
3885
4131
  if (!environment) return;
@@ -3984,6 +4230,8 @@ function createNoopController() {
3984
4230
  },
3985
4231
  setMode() {
3986
4232
  },
4233
+ async startElementReview() {
4234
+ },
3987
4235
  getMode() {
3988
4236
  return "idle";
3989
4237
  },
@@ -4006,12 +4254,14 @@ export {
4006
4254
  REVIEW_WORKFLOW_STATUS_OPTIONS,
4007
4255
  normalizeReviewItemStatus,
4008
4256
  localAdapter,
4257
+ clamp,
4009
4258
  DEFAULT_REVIEW_VIEWPORTS,
4010
4259
  findReviewViewportPreset,
4011
4260
  getReviewViewportScope,
4012
4261
  getReviewItemScope,
4013
4262
  getReviewItemScopeLabel,
4014
4263
  getNumberedReviewItems,
4264
+ runWithAutoScrollBehavior,
4015
4265
  createWebReviewKit
4016
4266
  };
4017
- //# sourceMappingURL=chunk-6L2KJ7XL.js.map
4267
+ //# sourceMappingURL=chunk-IN36JHEU.js.map