@designfever/web-review-kit 0.5.0 → 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
 
@@ -130,7 +131,7 @@ export default defineConfig({
130
131
  });
131
132
  ```
132
133
 
133
- 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. DOM QA cards show a source action when the saved item has source hints. Keep these plugins disabled for production builds because they write 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.
134
135
 
135
136
  ```tsx
136
137
  mountReviewShell({
@@ -1266,6 +1266,14 @@ function createStyleElement() {
1266
1266
  cursor: default;
1267
1267
  }
1268
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
+
1269
1277
  .dfwr-panel {
1270
1278
  position: fixed;
1271
1279
  right: 16px;
@@ -1766,6 +1774,20 @@ function createStyleElement() {
1766
1774
  border-color: rgba(99, 215, 199, 0.56);
1767
1775
  }
1768
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
+
1769
1791
  .dfwr-note-popover.is-dragging,
1770
1792
  .dfwr-area-draft.is-dragging {
1771
1793
  user-select: none;
@@ -1814,6 +1836,50 @@ function createStyleElement() {
1814
1836
  padding: 0;
1815
1837
  }
1816
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
+
1817
1883
  .dfwr-note-actions {
1818
1884
  justify-content: flex-end;
1819
1885
  }
@@ -2138,10 +2204,12 @@ function createStyleElement() {
2138
2204
 
2139
2205
  @media (max-width: 520px) {
2140
2206
  .dfwr-panel {
2207
+ left: 8px;
2141
2208
  right: 8px;
2142
- top: 8px;
2143
- width: calc(100vw - 16px);
2144
- max-height: calc(100vh - 16px);
2209
+ top: auto;
2210
+ bottom: 8px;
2211
+ width: auto;
2212
+ max-height: min(70vh, calc(100vh - 16px));
2145
2213
  }
2146
2214
  }
2147
2215
  `;
@@ -2221,6 +2289,7 @@ var WebReviewKitView = class {
2221
2289
  }
2222
2290
  clearDraftPreview() {
2223
2291
  this.restoreDraftPreview();
2292
+ this.clearShellComposer();
2224
2293
  }
2225
2294
  render(shadow, hiddenItemsStyle) {
2226
2295
  const state = this.state;
@@ -2231,11 +2300,13 @@ var WebReviewKitView = class {
2231
2300
  shadow.append(createStyleElement());
2232
2301
  shadow.append(hiddenItemsStyle);
2233
2302
  const hasDismissableDraft = Boolean(state.noteDraft || state.areaDraft);
2303
+ const shouldDockComposer = this.config.options.ui?.panel === false && hasDismissableDraft && Boolean(this.getShellComposerHost());
2304
+ let dockedComposer;
2234
2305
  const shell = document.createElement("div");
2235
2306
  shell.className = [
2236
2307
  "dfwr-shell",
2237
2308
  state.isOpen ? "is-open" : "",
2238
- hasDismissableDraft ? "has-dismissible-draft" : ""
2309
+ hasDismissableDraft && !shouldDockComposer ? "has-dismissible-draft" : ""
2239
2310
  ].filter(Boolean).join(" ");
2240
2311
  shell.setAttribute("aria-hidden", state.isOpen ? "false" : "true");
2241
2312
  if (this.config.options.ui?.panel !== false) {
@@ -2252,13 +2323,21 @@ var WebReviewKitView = class {
2252
2323
  shell.append(panel);
2253
2324
  }
2254
2325
  shell.append(this.createMarkerLayer());
2255
- if (state.isOpen && hasDismissableDraft) {
2326
+ if (state.isOpen && hasDismissableDraft && !shouldDockComposer) {
2256
2327
  shell.append(this.createDraftCancelLayer());
2257
2328
  }
2258
2329
  if (state.isOpen && (state.mode === "note" || state.mode === "element")) {
2259
- shell.append(
2260
- state.noteDraft ? this.createNotePopover(state.noteDraft) : state.mode === "element" ? this.createElementLayer() : this.createNoteLayer()
2261
- );
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
+ }
2262
2341
  }
2263
2342
  if (state.isOpen && state.mode === "area" && !state.areaDraft) {
2264
2343
  shell.append(this.createAreaLayer());
@@ -2267,32 +2346,73 @@ var WebReviewKitView = class {
2267
2346
  if (state.areaDraft.selection) {
2268
2347
  shell.append(this.createAreaDraftOverlay(state.areaDraft));
2269
2348
  }
2270
- 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
+ }
2271
2357
  }
2272
2358
  shadow.append(shell);
2359
+ this.renderShellComposer(dockedComposer);
2273
2360
  }
2274
2361
  get state() {
2275
2362
  return this.config.getState();
2276
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
+ }
2277
2397
  createDraftCancelLayer() {
2278
2398
  const layer = document.createElement("div");
2279
2399
  layer.className = "dfwr-draft-cancel-layer";
2280
2400
  layer.setAttribute("aria-hidden", "true");
2281
- const cancel = (event) => {
2282
- event.preventDefault();
2283
- event.stopPropagation();
2284
- event.stopImmediatePropagation();
2285
- this.config.actions.setModeState("idle");
2286
- this.config.actions.clearDrafts();
2287
- this.config.actions.setSelectingArea(false);
2288
- this.config.actions.render();
2289
- };
2290
2401
  layer.addEventListener("pointerdown", (event) => {
2291
2402
  if (event.button !== 0) return;
2292
- cancel(event);
2403
+ this.cancelDraft(event);
2293
2404
  });
2294
2405
  return layer;
2295
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
+ }
2296
2416
  getDraftAdjustmentMetrics(draft) {
2297
2417
  const adjustment = draft.adjustment;
2298
2418
  const x = adjustment?.x ?? 0;
@@ -2686,11 +2806,11 @@ ${adjustment}` : adjustment;
2686
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.";
2687
2807
  return empty;
2688
2808
  }
2689
- createNotePopover(draft) {
2809
+ createNotePopover(draft, options = {}) {
2690
2810
  const environment = this.config.getEnvironment();
2691
2811
  const group = document.createElement("div");
2692
2812
  group.className = "dfwr-note-draft";
2693
- if (!environment) return group;
2813
+ if (!environment) return { layer: group, composer: void 0 };
2694
2814
  const isElementDraft = this.state.mode === "element" && Boolean(draft.selection);
2695
2815
  const hostPoint = toHostPoint(
2696
2816
  isElementDraft ? this.getAdjustedDraftPoint(draft.marker.viewport, draft) : draft.marker.viewport,
@@ -2714,8 +2834,14 @@ ${adjustment}` : adjustment;
2714
2834
  pin.style.top = `${hostPoint.y}px`;
2715
2835
  const popover = document.createElement("div");
2716
2836
  const position = getPopoverPosition(hostPoint, environment);
2717
- popover.className = `dfwr-note-popover${isElementDraft ? " is-composer" : ""}`;
2718
- 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) {
2719
2845
  const selection = draft.selection ? toHostSelection(
2720
2846
  this.getAdjustedDraftSelection(
2721
2847
  toViewportSelection(draft.selection.viewport),
@@ -2774,18 +2900,27 @@ ${adjustment}` : adjustment;
2774
2900
  pin,
2775
2901
  popover,
2776
2902
  selectionHighlight,
2777
- textarea
2903
+ textarea,
2904
+ dockToggle: options.dockComposer
2778
2905
  }) : void 0;
2779
- const actions = this.createFormActions("Save note", saveDraft);
2906
+ const actions = this.createFormActions("Save note", saveDraft, {
2907
+ leading: adjustmentControls?.actionButton ? [adjustmentControls.actionButton] : void 0
2908
+ });
2780
2909
  form.append(
2781
2910
  ...meta ? [meta] : [],
2782
2911
  ...adjustmentControls ? [adjustmentControls.panel] : [],
2783
2912
  textarea,
2784
2913
  actions
2785
2914
  );
2786
- const dragHandle = isElementDraft ? this.createDraftDragHandle("Move DOM composer") : void 0;
2787
- popover.append(...dragHandle ? [dragHandle] : [], form);
2788
- 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
+ }
2789
2924
  if (dragHandle) {
2790
2925
  this.attachDraftComposerDrag(popover, dragHandle, (composerPosition) => {
2791
2926
  const noteDraft = this.state.noteDraft ?? draft;
@@ -2798,18 +2933,23 @@ ${adjustment}` : adjustment;
2798
2933
  }
2799
2934
  this.attachDraftPinDrag(
2800
2935
  pin,
2801
- isElementDraft ? void 0 : popover,
2936
+ isElementDraft || options.dockComposer ? void 0 : popover,
2802
2937
  meta,
2803
2938
  textarea
2804
2939
  );
2805
- window.setTimeout(() => {
2806
- if (draft.adjustment?.isActive) {
2807
- adjustmentControls?.focusTarget.focus();
2808
- return;
2809
- }
2810
- textarea.focus();
2811
- }, 0);
2812
- 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
+ };
2813
2953
  }
2814
2954
  createDraftDragHandle(label) {
2815
2955
  const handle = document.createElement("button");
@@ -2901,7 +3041,8 @@ ${adjustment}` : adjustment;
2901
3041
  pin,
2902
3042
  popover,
2903
3043
  selectionHighlight,
2904
- textarea
3044
+ textarea,
3045
+ dockToggle
2905
3046
  }) {
2906
3047
  const panel = document.createElement("div");
2907
3048
  panel.className = "dfwr-adjust-panel is-dom-adjust-panel";
@@ -2977,12 +3118,16 @@ ${adjustment}` : adjustment;
2977
3118
  }
2978
3119
  }));
2979
3120
  });
2980
- header.append(help, adjust);
3121
+ header.append(help);
3122
+ if (!dockToggle) {
3123
+ header.append(adjust);
3124
+ }
2981
3125
  panel.append(header, xyStatus, scaleStatus);
2982
3126
  syncControls(draft);
2983
3127
  return {
2984
3128
  panel,
2985
- focusTarget: adjust
3129
+ focusTarget: adjust,
3130
+ actionButton: dockToggle ? adjust : void 0
2986
3131
  };
2987
3132
  }
2988
3133
  getAdjustmentKeyDelta(event) {
@@ -3108,11 +3253,17 @@ ${adjustment}` : adjustment;
3108
3253
  }
3109
3254
  return layer;
3110
3255
  }
3111
- createAreaDraftPopover(draft) {
3256
+ createAreaDraftPopover(draft, options = {}) {
3112
3257
  const environment = this.config.getEnvironment();
3113
3258
  const popover = document.createElement("div");
3114
- popover.className = "dfwr-area-draft is-composer";
3115
- 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) {
3116
3267
  const selection = toHostSelection(
3117
3268
  toViewportSelection(draft.selection.viewport),
3118
3269
  environment
@@ -3128,15 +3279,20 @@ ${adjustment}` : adjustment;
3128
3279
  popover.style.width = `${composer.width}px`;
3129
3280
  popover.style.right = "auto";
3130
3281
  }
3131
- const dragHandle = this.createDraftDragHandle("Move area composer");
3132
- popover.append(dragHandle, this.createAreaForm());
3133
- this.attachDraftComposerDrag(popover, dragHandle, (composerPosition) => {
3134
- const areaDraft = this.state.areaDraft ?? draft;
3135
- this.config.actions.setAreaDraft({
3136
- ...areaDraft,
3137
- 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
+ });
3138
3294
  });
3139
- });
3295
+ }
3140
3296
  return popover;
3141
3297
  }
3142
3298
  createFormActions(saveLabel, onSave, options) {
@@ -3156,12 +3312,19 @@ ${adjustment}` : adjustment;
3156
3312
  cancel.type = "button";
3157
3313
  cancel.textContent = "Cancel";
3158
3314
  cancel.addEventListener("click", (event) => {
3159
- event.preventDefault();
3160
- event.stopPropagation();
3161
- this.config.actions.setModeState("idle");
3162
- this.config.actions.clearDrafts();
3163
- this.config.actions.render();
3315
+ this.cancelDraft(event);
3164
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
+ }
3165
3328
  if (options?.beforeSave?.length || options?.className) {
3166
3329
  actions.append(cancel, ...options.beforeSave ?? [], save);
3167
3330
  return actions;
@@ -3590,9 +3753,10 @@ var WebReviewKitApp = class {
3590
3753
  this.toggle();
3591
3754
  };
3592
3755
  this.handleViewportChange = () => {
3593
- if (!this.isOpen || this.renderFrame) return;
3756
+ if (!this.isOpen || this.renderFrame || this.isDraftComposerFocused()) return;
3594
3757
  this.renderFrame = window.requestAnimationFrame(() => {
3595
3758
  this.renderFrame = void 0;
3759
+ if (this.isDraftComposerFocused()) return;
3596
3760
  this.render();
3597
3761
  });
3598
3762
  };
@@ -3769,6 +3933,14 @@ var WebReviewKitApp = class {
3769
3933
  this.render();
3770
3934
  return true;
3771
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
+ }
3772
3944
  getEnvironment() {
3773
3945
  const target = typeof this.options.target === "function" ? this.options.target() : this.options.target;
3774
3946
  if (!target) {
@@ -3797,6 +3969,7 @@ var WebReviewKitApp = class {
3797
3969
  height: target.window.innerHeight
3798
3970
  };
3799
3971
  const overlayRect = target.getOverlayRect?.() ?? rect;
3972
+ const composerHost = target.getComposerHost?.();
3800
3973
  return {
3801
3974
  window: target.window,
3802
3975
  document: target.document,
@@ -3811,7 +3984,8 @@ var WebReviewKitApp = class {
3811
3984
  top: overlayRect.top,
3812
3985
  width: overlayRect.width,
3813
3986
  height: overlayRect.height
3814
- }
3987
+ },
3988
+ composerHost
3815
3989
  };
3816
3990
  } catch {
3817
3991
  return void 0;
@@ -4090,4 +4264,4 @@ export {
4090
4264
  runWithAutoScrollBehavior,
4091
4265
  createWebReviewKit
4092
4266
  };
4093
- //# sourceMappingURL=chunk-TWCSIBMY.js.map
4267
+ //# sourceMappingURL=chunk-IN36JHEU.js.map