@designfever/web-review-kit 0.5.0 → 0.7.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.
Files changed (42) hide show
  1. package/.env.sample +29 -0
  2. package/README.md +36 -10
  3. package/dist/chunk-AB5B6O77.js +584 -0
  4. package/dist/chunk-AB5B6O77.js.map +1 -0
  5. package/dist/{chunk-TWCSIBMY.js → chunk-RPVLRULC.js} +736 -201
  6. package/dist/chunk-RPVLRULC.js.map +1 -0
  7. package/dist/image.types-BmzkFSPX.d.cts +71 -0
  8. package/dist/image.types-BmzkFSPX.d.ts +71 -0
  9. package/dist/index.cjs +1251 -202
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +27 -3
  12. package/dist/index.d.ts +27 -3
  13. package/dist/index.js +50 -3
  14. package/dist/index.js.map +1 -1
  15. package/dist/react-shell.cjs +10135 -4070
  16. package/dist/react-shell.cjs.map +1 -1
  17. package/dist/react-shell.d.cts +37 -4
  18. package/dist/react-shell.d.ts +37 -4
  19. package/dist/react-shell.js +9258 -3707
  20. package/dist/react-shell.js.map +1 -1
  21. package/dist/token-Dt-ZH-YO.d.cts +88 -0
  22. package/dist/token-nJXPPdYX.d.ts +88 -0
  23. package/dist/{types-RvVa5ns-.d.cts → types-DT9Z66mV.d.cts} +14 -1
  24. package/dist/{types-RvVa5ns-.d.ts → types-DT9Z66mV.d.ts} +14 -1
  25. package/dist/vite.cjs +1116 -5
  26. package/dist/vite.cjs.map +1 -1
  27. package/dist/vite.d.cts +45 -1
  28. package/dist/vite.d.ts +45 -1
  29. package/dist/vite.js +800 -5
  30. package/dist/vite.js.map +1 -1
  31. package/docs/README.md +11 -5
  32. package/docs/adapters.md +126 -0
  33. package/docs/adaptor.sample.ts +13 -1
  34. package/docs/architecture.md +6 -1
  35. package/docs/code-review-0.6.0.md +232 -0
  36. package/docs/db-setup.md +4 -2
  37. package/docs/figma-image-mvp-0.7.0.md +327 -0
  38. package/docs/installation.md +44 -8
  39. package/docs/release-notes-0.6.0.md +108 -0
  40. package/docs/release-notes-0.7.0.md +128 -0
  41. package/package.json +6 -2
  42. package/dist/chunk-TWCSIBMY.js.map +0 -1
@@ -111,6 +111,9 @@ function normalizeStoredReviewItem(value) {
111
111
  };
112
112
  }
113
113
 
114
+ // src/figma/image.types.ts
115
+ var DEFAULT_REVIEW_FIGMA_IMAGE_FORMAT = "webp";
116
+
114
117
  // src/core/review/scope.ts
115
118
  var DEFAULT_REVIEW_VIEWPORTS = [
116
119
  { label: "Mobile", width: 390, height: 720, scope: "mobile" },
@@ -389,13 +392,13 @@ function getDomAnchorFromPoint(point, configuredAttribute = "data-qa-id", enviro
389
392
  const target = environment.document.elementFromPoint(point.x, point.y);
390
393
  if (!target) return void 0;
391
394
  const candidates = createAnchorCandidates(target, configuredAttribute);
392
- const primary = candidates[0];
393
- if (!primary) return void 0;
395
+ const primaryCandidate = candidates[0];
396
+ if (!primaryCandidate) return void 0;
394
397
  return {
395
- ...primary,
398
+ ...primaryCandidate,
396
399
  candidates,
397
400
  htmlSnippet: getElementHtmlSnippet(
398
- getAnchorSourceElement(target, primary, configuredAttribute) ?? target
401
+ getAnchorSourceElement(target, primaryCandidate, configuredAttribute) ?? target
399
402
  ),
400
403
  source: getDomSourceHint(target)
401
404
  };
@@ -403,13 +406,13 @@ function getDomAnchorFromPoint(point, configuredAttribute = "data-qa-id", enviro
403
406
  function getDomAnchorFromElement(target, configuredAttribute = "data-qa-id", environment) {
404
407
  if (target.ownerDocument !== environment.document) return void 0;
405
408
  const candidates = createAnchorCandidates(target, configuredAttribute);
406
- const primary = candidates[0];
407
- if (!primary) return void 0;
409
+ const primaryCandidate = candidates[0];
410
+ if (!primaryCandidate) return void 0;
408
411
  return {
409
- ...primary,
412
+ ...primaryCandidate,
410
413
  candidates,
411
414
  htmlSnippet: getElementHtmlSnippet(
412
- getAnchorSourceElement(target, primary, configuredAttribute) ?? target
415
+ getAnchorSourceElement(target, primaryCandidate, configuredAttribute) ?? target
413
416
  ),
414
417
  source: getDomSourceHint(target)
415
418
  };
@@ -580,7 +583,7 @@ function createAnchorCandidates(target, configuredAttribute) {
580
583
  function findClosestAttributeAnchor(target, attributeNames, confidence, options) {
581
584
  for (const attributeName of attributeNames) {
582
585
  const selector = `[${attributeName}]`;
583
- const element = safeClosest(target, selector);
586
+ const element = tryClosest(target, selector);
584
587
  if (!element) continue;
585
588
  const value = getStableAttributeValue(element, attributeName);
586
589
  if (!value) continue;
@@ -686,7 +689,7 @@ function getAnchorSourceElement(target, candidate, configuredAttribute) {
686
689
  return target;
687
690
  }
688
691
  }
689
- function safeClosest(element, selector) {
692
+ function tryClosest(element, selector) {
690
693
  try {
691
694
  return element.closest(selector);
692
695
  } catch {
@@ -1190,18 +1193,90 @@ function setDocumentScrollInstantly(environment, position) {
1190
1193
  );
1191
1194
  }
1192
1195
 
1196
+ // src/core/draft.metrics.ts
1197
+ function getDraftViewportScale(viewport, presets) {
1198
+ const preset = findReviewViewportPreset(viewport, presets);
1199
+ const designWidth = typeof preset.designWidth === "number" && preset.designWidth > 0 ? preset.designWidth : viewport.width;
1200
+ const scale = designWidth > 0 ? viewport.width / designWidth : 1;
1201
+ return { scale, designWidth, presetLabel: preset.label };
1202
+ }
1203
+ function getDraftAdjustmentMetrics(draft, presets) {
1204
+ const adjustment = draft.adjustment;
1205
+ const x = adjustment?.x ?? 0;
1206
+ const y = adjustment?.y ?? 0;
1207
+ const scale = adjustment?.scale ?? 0;
1208
+ const {
1209
+ scale: viewportScale,
1210
+ designWidth,
1211
+ presetLabel
1212
+ } = getDraftViewportScale(draft.viewport, presets);
1213
+ const selection = draft.selection ? toViewportSelection(draft.selection.viewport) : void 0;
1214
+ const scaleCssDelta = scale * viewportScale;
1215
+ const scaleFactor = selection && selection.width > 0 ? Math.max(
1216
+ 1 / selection.width,
1217
+ (selection.width + scaleCssDelta) / selection.width
1218
+ ) : 1;
1219
+ return {
1220
+ x,
1221
+ y,
1222
+ scale,
1223
+ cssX: x * viewportScale,
1224
+ cssY: y * viewportScale,
1225
+ scaleFactor,
1226
+ viewportScale,
1227
+ designWidth,
1228
+ presetLabel,
1229
+ viewportWidth: draft.viewport.width
1230
+ };
1231
+ }
1232
+ function hasDraftAdjustment(draft, presets) {
1233
+ const metrics = getDraftAdjustmentMetrics(draft, presets);
1234
+ return metrics.x !== 0 || metrics.y !== 0 || metrics.scale !== 0;
1235
+ }
1236
+ function getAdjustedDraftPoint(point, draft, presets) {
1237
+ const metrics = getDraftAdjustmentMetrics(draft, presets);
1238
+ return {
1239
+ x: point.x + metrics.cssX,
1240
+ y: point.y + metrics.cssY
1241
+ };
1242
+ }
1243
+ function getAdjustedDraftSelection(selection, draft, presets) {
1244
+ const metrics = getDraftAdjustmentMetrics(draft, presets);
1245
+ return {
1246
+ ...selection,
1247
+ left: selection.left + metrics.cssX,
1248
+ top: selection.top + metrics.cssY,
1249
+ width: selection.width * metrics.scaleFactor,
1250
+ height: selection.height * metrics.scaleFactor
1251
+ };
1252
+ }
1253
+
1254
+ // src/core/typography.tokens.ts
1255
+ var reviewTypographyTokens = `
1256
+ --df-review-font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1257
+ --df-review-font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
1258
+ --df-review-font-size-3xs: 9px;
1259
+ --df-review-font-size-2xs: 10px;
1260
+ --df-review-font-size-xs: 11px;
1261
+ --df-review-font-size-sm: 12px;
1262
+ --df-review-font-size-md: 13px;
1263
+ --df-review-font-size-lg: 14px;
1264
+ --df-review-font-size-xl: 15px;
1265
+ --df-review-font-size-2xl: 18px;
1266
+ --df-review-font-weight-normal: 400;
1267
+ --df-review-font-weight-emphasis: 500;
1268
+ --df-review-line-height-tight: 1.25;
1269
+ --df-review-line-height-base: 1.42;
1270
+ --df-review-line-height-relaxed: 1.55;
1271
+ `;
1272
+
1193
1273
  // src/core/overlay.style.ts
1194
1274
  function createStyleElement() {
1195
1275
  const style = document.createElement("style");
1196
1276
  style.textContent = `
1197
1277
  :host {
1198
1278
  color-scheme: dark;
1199
- --df-review-font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1200
- --df-review-font-size-2xs: 10px;
1201
- --df-review-font-size-xs: 11px;
1202
- --df-review-font-size-sm: 12px;
1203
- --df-review-font-size-md: 13px;
1204
- --df-review-font-size-xl: 15px;
1279
+ ${reviewTypographyTokens}
1205
1280
  --df-review-space-1: 4px;
1206
1281
  --df-review-space-1-5: 6px;
1207
1282
  --df-review-space-2: 8px;
@@ -1266,6 +1341,14 @@ function createStyleElement() {
1266
1341
  cursor: default;
1267
1342
  }
1268
1343
 
1344
+ .dfwr-shell.is-docked-composer {
1345
+ position: relative;
1346
+ inset: auto;
1347
+ z-index: auto;
1348
+ padding: 0;
1349
+ pointer-events: auto;
1350
+ }
1351
+
1269
1352
  .dfwr-panel {
1270
1353
  position: fixed;
1271
1354
  right: 16px;
@@ -1292,8 +1375,8 @@ function createStyleElement() {
1292
1375
  }
1293
1376
 
1294
1377
  .dfwr-title {
1295
- font-size: 15px;
1296
- font-weight: 700;
1378
+ font-size: var(--df-review-font-size-xl);
1379
+ font-weight: var(--df-review-font-weight-emphasis);
1297
1380
  line-height: 1.25;
1298
1381
  }
1299
1382
 
@@ -1337,11 +1420,15 @@ function createStyleElement() {
1337
1420
  }
1338
1421
 
1339
1422
  .dfwr-button {
1423
+ display: inline-flex;
1424
+ align-items: center;
1425
+ justify-content: center;
1426
+ gap: 6px;
1340
1427
  min-height: var(--df-review-control-height-md);
1341
1428
  padding: 0 12px;
1342
1429
  border-radius: var(--df-review-radius-sm);
1343
1430
  font-size: var(--df-review-font-size-sm);
1344
- font-weight: 650;
1431
+ font-weight: var(--df-review-font-weight-emphasis);
1345
1432
  }
1346
1433
 
1347
1434
  .dfwr-button:hover,
@@ -1357,6 +1444,21 @@ function createStyleElement() {
1357
1444
  color: var(--df-review-color-accent-contrast);
1358
1445
  }
1359
1446
 
1447
+ .dfwr-button:disabled {
1448
+ cursor: default;
1449
+ opacity: 0.62;
1450
+ }
1451
+
1452
+ .dfwr-spinner {
1453
+ display: inline-block;
1454
+ width: 12px;
1455
+ height: 12px;
1456
+ border: 2px solid currentColor;
1457
+ border-right-color: transparent;
1458
+ border-radius: 999px;
1459
+ animation: dfwr-spin 720ms linear infinite;
1460
+ }
1461
+
1360
1462
  .dfwr-icon-button {
1361
1463
  display: inline-flex;
1362
1464
  align-items: center;
@@ -1366,7 +1468,7 @@ function createStyleElement() {
1366
1468
  padding: 0 8px;
1367
1469
  border-radius: var(--df-review-radius-sm);
1368
1470
  font-size: var(--df-review-font-size-xs);
1369
- font-weight: 700;
1471
+ font-weight: var(--df-review-font-weight-emphasis);
1370
1472
  line-height: 1;
1371
1473
  text-transform: uppercase;
1372
1474
  }
@@ -1521,7 +1623,7 @@ function createStyleElement() {
1521
1623
  0 8px 18px rgba(0, 0, 0, 0.28);
1522
1624
  color: #111820;
1523
1625
  font-size: var(--df-review-font-size-2xs);
1524
- font-weight: 900;
1626
+ font-weight: var(--df-review-font-weight-emphasis);
1525
1627
  line-height: 1;
1526
1628
  pointer-events: none;
1527
1629
  }
@@ -1547,7 +1649,7 @@ function createStyleElement() {
1547
1649
  box-shadow: 0 0 0 4px rgba(var(--dfwr-scope-rgb), 0.18);
1548
1650
  color: var(--dfwr-scope);
1549
1651
  font-size: var(--df-review-font-size-2xs);
1550
- font-weight: 800;
1652
+ font-weight: var(--df-review-font-weight-emphasis);
1551
1653
  }
1552
1654
 
1553
1655
  .dfwr-bound-marker.is-highlighted {
@@ -1766,6 +1868,20 @@ function createStyleElement() {
1766
1868
  border-color: rgba(99, 215, 199, 0.56);
1767
1869
  }
1768
1870
 
1871
+ .dfwr-shell.is-docked-composer .dfwr-note-popover.is-docked-composer,
1872
+ .dfwr-shell.is-docked-composer .dfwr-area-draft.is-docked-composer {
1873
+ position: relative;
1874
+ left: auto;
1875
+ right: auto;
1876
+ top: auto;
1877
+ z-index: auto;
1878
+ max-height: none;
1879
+ }
1880
+
1881
+ .dfwr-shell.is-docked-composer .dfwr-textarea {
1882
+ min-height: 184px;
1883
+ }
1884
+
1769
1885
  .dfwr-note-popover.is-dragging,
1770
1886
  .dfwr-area-draft.is-dragging {
1771
1887
  user-select: none;
@@ -1814,6 +1930,50 @@ function createStyleElement() {
1814
1930
  padding: 0;
1815
1931
  }
1816
1932
 
1933
+ .dfwr-actions.has-leading {
1934
+ align-items: center;
1935
+ justify-content: space-between;
1936
+ gap: 12px;
1937
+ }
1938
+
1939
+ .dfwr-actions-leading,
1940
+ .dfwr-actions-primary {
1941
+ display: flex;
1942
+ align-items: center;
1943
+ gap: 8px;
1944
+ min-width: 0;
1945
+ }
1946
+
1947
+ .dfwr-actions-primary {
1948
+ margin-left: auto;
1949
+ }
1950
+
1951
+ .dfwr-shell.is-docked-composer .dfwr-actions.has-leading {
1952
+ align-items: stretch;
1953
+ }
1954
+
1955
+ .dfwr-shell.is-docked-composer .dfwr-actions.has-leading .dfwr-button,
1956
+ .dfwr-shell.is-docked-composer .dfwr-actions.has-leading .dfwr-adjust-toggle {
1957
+ height: var(--df-review-control-height-md);
1958
+ min-height: var(--df-review-control-height-md);
1959
+ border-radius: var(--df-review-radius-sm);
1960
+ }
1961
+
1962
+ .dfwr-shell.is-docked-composer .dfwr-actions.has-leading .dfwr-button {
1963
+ min-width: 96px;
1964
+ padding: 0 12px;
1965
+ font-size: var(--df-review-font-size-sm);
1966
+ }
1967
+
1968
+ .dfwr-shell.is-docked-composer .dfwr-actions.has-leading .dfwr-adjust-toggle {
1969
+ width: var(--df-review-control-height-md);
1970
+ }
1971
+
1972
+ .dfwr-shell.is-docked-composer .dfwr-actions.has-leading .dfwr-adjust-toggle svg {
1973
+ width: 18px;
1974
+ height: 18px;
1975
+ }
1976
+
1817
1977
  .dfwr-note-actions {
1818
1978
  justify-content: flex-end;
1819
1979
  }
@@ -1831,10 +1991,18 @@ function createStyleElement() {
1831
1991
  gap: 10px;
1832
1992
  }
1833
1993
 
1994
+ .dfwr-form-error {
1995
+ margin: 0;
1996
+ color: #ff8f61;
1997
+ font-size: var(--df-review-font-size-sm);
1998
+ line-height: 1.4;
1999
+ overflow-wrap: anywhere;
2000
+ }
2001
+
2002
+ .dfwr-input,
2003
+ .dfwr-select,
1834
2004
  .dfwr-textarea {
1835
2005
  width: 100%;
1836
- min-height: 92px;
1837
- resize: vertical;
1838
2006
  border: 1px solid rgba(255, 255, 255, 0.16);
1839
2007
  border-radius: var(--df-review-radius-sm);
1840
2008
  padding: 10px;
@@ -1845,14 +2013,39 @@ function createStyleElement() {
1845
2013
  line-height: 1.45;
1846
2014
  }
1847
2015
 
2016
+ .dfwr-input,
2017
+ .dfwr-select {
2018
+ min-height: 38px;
2019
+ }
2020
+
2021
+ .dfwr-select {
2022
+ appearance: none;
2023
+ cursor: pointer;
2024
+ }
2025
+
2026
+ .dfwr-textarea {
2027
+ min-height: 92px;
2028
+ resize: vertical;
2029
+ }
2030
+
2031
+ .dfwr-input:focus,
2032
+ .dfwr-select:focus,
1848
2033
  .dfwr-textarea:focus {
1849
2034
  outline: 2px solid var(--df-review-color-accent-ring);
1850
2035
  outline-offset: 1px;
1851
2036
  }
1852
2037
 
1853
2038
  @media (hover: none) and (pointer: coarse) {
2039
+ .dfwr-input,
2040
+ .dfwr-select,
1854
2041
  .dfwr-textarea {
1855
- font-size: 16px;
2042
+ font-size: var(--df-review-font-size-xl);
2043
+ }
2044
+ }
2045
+
2046
+ @keyframes dfwr-spin {
2047
+ to {
2048
+ transform: rotate(360deg);
1856
2049
  }
1857
2050
  }
1858
2051
 
@@ -1910,8 +2103,8 @@ function createStyleElement() {
1910
2103
  color: var(--df-review-color-text);
1911
2104
  cursor: pointer;
1912
2105
  font: inherit;
1913
- font-size: 14px;
1914
- font-weight: 800;
2106
+ font-size: var(--df-review-font-size-lg);
2107
+ font-weight: var(--df-review-font-weight-emphasis);
1915
2108
  line-height: 1;
1916
2109
  }
1917
2110
 
@@ -1964,7 +2157,7 @@ function createStyleElement() {
1964
2157
  margin-bottom: 10px;
1965
2158
  color: rgba(247, 247, 242, 0.74);
1966
2159
  font-size: var(--df-review-font-size-sm);
1967
- font-weight: 700;
2160
+ font-weight: var(--df-review-font-weight-emphasis);
1968
2161
  }
1969
2162
 
1970
2163
  .dfwr-item {
@@ -1986,6 +2179,8 @@ function createStyleElement() {
1986
2179
  }
1987
2180
 
1988
2181
  .dfwr-item-body {
2182
+ display: grid;
2183
+ gap: 4px;
1989
2184
  min-width: 0;
1990
2185
  flex: 1;
1991
2186
  }
@@ -2005,7 +2200,7 @@ function createStyleElement() {
2005
2200
  border-radius: var(--df-review-radius-pill);
2006
2201
  padding: 0 7px;
2007
2202
  font-size: var(--df-review-font-size-2xs);
2008
- font-weight: 800;
2203
+ font-weight: var(--df-review-font-weight-emphasis);
2009
2204
  line-height: 1;
2010
2205
  letter-spacing: 0;
2011
2206
  text-transform: uppercase;
@@ -2023,13 +2218,29 @@ function createStyleElement() {
2023
2218
  color: rgba(247, 247, 242, 0.64);
2024
2219
  }
2025
2220
 
2221
+ .dfwr-item-title {
2222
+ margin: 4px 0 0;
2223
+ color: var(--df-review-color-text);
2224
+ font-size: var(--df-review-font-size-md);
2225
+ font-weight: var(--df-review-font-weight-normal);
2226
+ line-height: 1.35;
2227
+ overflow-wrap: anywhere;
2228
+ }
2229
+
2026
2230
  .dfwr-item-comment {
2231
+ margin: 0;
2232
+ color: var(--df-review-color-text-muted);
2233
+ font-size: var(--df-review-font-size-sm);
2234
+ line-height: 1.45;
2235
+ overflow-wrap: anywhere;
2236
+ white-space: pre-wrap;
2237
+ }
2238
+
2239
+ .dfwr-item-comment.is-primary {
2027
2240
  margin: 4px 0;
2028
2241
  color: var(--df-review-color-text);
2029
2242
  font-size: var(--df-review-font-size-md);
2030
2243
  line-height: 1.42;
2031
- overflow-wrap: anywhere;
2032
- white-space: pre-wrap;
2033
2244
  }
2034
2245
 
2035
2246
  .dfwr-item-date {
@@ -2138,10 +2349,12 @@ function createStyleElement() {
2138
2349
 
2139
2350
  @media (max-width: 520px) {
2140
2351
  .dfwr-panel {
2352
+ left: 8px;
2141
2353
  right: 8px;
2142
- top: 8px;
2143
- width: calc(100vw - 16px);
2144
- max-height: calc(100vh - 16px);
2354
+ top: auto;
2355
+ bottom: 8px;
2356
+ width: auto;
2357
+ max-height: min(70vh, calc(100vh - 16px));
2145
2358
  }
2146
2359
  }
2147
2360
  `;
@@ -2221,6 +2434,7 @@ var WebReviewKitView = class {
2221
2434
  }
2222
2435
  clearDraftPreview() {
2223
2436
  this.restoreDraftPreview();
2437
+ this.clearShellComposer();
2224
2438
  }
2225
2439
  render(shadow, hiddenItemsStyle) {
2226
2440
  const state = this.state;
@@ -2231,11 +2445,13 @@ var WebReviewKitView = class {
2231
2445
  shadow.append(createStyleElement());
2232
2446
  shadow.append(hiddenItemsStyle);
2233
2447
  const hasDismissableDraft = Boolean(state.noteDraft || state.areaDraft);
2448
+ const shouldDockComposer = this.config.options.ui?.panel === false && hasDismissableDraft && Boolean(this.getShellComposerHost());
2449
+ let dockedComposer;
2234
2450
  const shell = document.createElement("div");
2235
2451
  shell.className = [
2236
2452
  "dfwr-shell",
2237
2453
  state.isOpen ? "is-open" : "",
2238
- hasDismissableDraft ? "has-dismissible-draft" : ""
2454
+ hasDismissableDraft && !shouldDockComposer ? "has-dismissible-draft" : ""
2239
2455
  ].filter(Boolean).join(" ");
2240
2456
  shell.setAttribute("aria-hidden", state.isOpen ? "false" : "true");
2241
2457
  if (this.config.options.ui?.panel !== false) {
@@ -2252,105 +2468,119 @@ var WebReviewKitView = class {
2252
2468
  shell.append(panel);
2253
2469
  }
2254
2470
  shell.append(this.createMarkerLayer());
2255
- if (state.isOpen && hasDismissableDraft) {
2471
+ if (state.isOpen && hasDismissableDraft && !shouldDockComposer) {
2256
2472
  shell.append(this.createDraftCancelLayer());
2257
2473
  }
2258
2474
  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
- );
2475
+ if (state.noteDraft) {
2476
+ const noteDraft = this.createNotePopover(state.noteDraft, {
2477
+ dockComposer: shouldDockComposer
2478
+ });
2479
+ shell.append(noteDraft.layer);
2480
+ dockedComposer = noteDraft.composer;
2481
+ } else {
2482
+ shell.append(
2483
+ state.mode === "element" ? this.createElementLayer() : this.createNoteLayer()
2484
+ );
2485
+ }
2262
2486
  }
2263
- if (state.isOpen && state.mode === "area" && !state.areaDraft) {
2487
+ if (state.isOpen && state.mode === "area" && !state.areaDraft && !state.isSelectingArea) {
2264
2488
  shell.append(this.createAreaLayer());
2265
2489
  }
2266
2490
  if (state.isOpen && state.mode === "area" && state.areaDraft && this.config.options.ui?.panel === false) {
2267
2491
  if (state.areaDraft.selection) {
2268
2492
  shell.append(this.createAreaDraftOverlay(state.areaDraft));
2269
2493
  }
2270
- shell.append(this.createAreaDraftPopover(state.areaDraft));
2494
+ const areaComposer = this.createAreaDraftPopover(state.areaDraft, {
2495
+ dockComposer: shouldDockComposer
2496
+ });
2497
+ if (shouldDockComposer) {
2498
+ dockedComposer = areaComposer;
2499
+ } else {
2500
+ shell.append(areaComposer);
2501
+ }
2271
2502
  }
2272
2503
  shadow.append(shell);
2504
+ this.renderShellComposer(dockedComposer);
2273
2505
  }
2274
2506
  get state() {
2275
2507
  return this.config.getState();
2276
2508
  }
2509
+ getShellComposerHost() {
2510
+ const environment = this.config.getEnvironment();
2511
+ if (this.config.options.ui?.panel !== false) return void 0;
2512
+ return environment?.composerHost ?? void 0;
2513
+ }
2514
+ renderShellComposer(composer) {
2515
+ const host = composer ? this.getShellComposerHost() : void 0;
2516
+ if (!host || !composer) {
2517
+ this.clearShellComposer();
2518
+ return;
2519
+ }
2520
+ if (this.shellComposerHost && this.shellComposerHost !== host) {
2521
+ this.clearShellComposer();
2522
+ }
2523
+ this.shellComposerHost = host;
2524
+ host.dataset.hasDraftComposer = "true";
2525
+ if (host.parentElement) {
2526
+ host.parentElement.dataset.hasDraftComposer = "true";
2527
+ }
2528
+ const shell = document.createElement("div");
2529
+ shell.className = "dfwr-shell is-open is-shell-draft is-docked-composer";
2530
+ shell.append(composer);
2531
+ host.replaceChildren(createStyleElement(), shell);
2532
+ }
2533
+ clearShellComposer() {
2534
+ const host = this.shellComposerHost;
2535
+ host?.replaceChildren();
2536
+ if (host) {
2537
+ delete host.dataset.hasDraftComposer;
2538
+ delete host.parentElement?.dataset.hasDraftComposer;
2539
+ }
2540
+ this.shellComposerHost = void 0;
2541
+ }
2277
2542
  createDraftCancelLayer() {
2278
2543
  const layer = document.createElement("div");
2279
2544
  layer.className = "dfwr-draft-cancel-layer";
2280
2545
  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
2546
  layer.addEventListener("pointerdown", (event) => {
2291
2547
  if (event.button !== 0) return;
2292
- cancel(event);
2548
+ this.cancelDraft(event);
2293
2549
  });
2294
2550
  return layer;
2295
2551
  }
2552
+ cancelDraft(event) {
2553
+ event?.preventDefault();
2554
+ event?.stopPropagation();
2555
+ event?.stopImmediatePropagation();
2556
+ this.config.actions.setModeState("idle");
2557
+ this.config.actions.clearDrafts();
2558
+ this.config.actions.setSelectingArea(false);
2559
+ this.config.actions.render();
2560
+ }
2561
+ // Draft adjustment geometry lives in draft.metrics.ts; these thin wrappers
2562
+ // supply the configured viewport presets so call sites stay unchanged.
2563
+ get viewportPresets() {
2564
+ return this.config.options.viewports?.presets;
2565
+ }
2296
2566
  getDraftAdjustmentMetrics(draft) {
2297
- const adjustment = draft.adjustment;
2298
- const x = adjustment?.x ?? 0;
2299
- const y = adjustment?.y ?? 0;
2300
- const scale = adjustment?.scale ?? 0;
2301
- const {
2302
- scale: viewportScale,
2303
- designWidth,
2304
- presetLabel
2305
- } = this.getDraftViewportScale(draft.viewport);
2306
- const selection = draft.selection ? toViewportSelection(draft.selection.viewport) : void 0;
2307
- const scaleCssDelta = scale * viewportScale;
2308
- const scaleFactor = selection && selection.width > 0 ? Math.max(
2309
- 1 / selection.width,
2310
- (selection.width + scaleCssDelta) / selection.width
2311
- ) : 1;
2312
- return {
2313
- x,
2314
- y,
2315
- scale,
2316
- cssX: x * viewportScale,
2317
- cssY: y * viewportScale,
2318
- scaleFactor,
2319
- viewportScale,
2320
- designWidth,
2321
- presetLabel,
2322
- viewportWidth: draft.viewport.width
2323
- };
2567
+ return getDraftAdjustmentMetrics(draft, this.viewportPresets);
2324
2568
  }
2325
2569
  hasDraftAdjustment(draft) {
2326
- const metrics = this.getDraftAdjustmentMetrics(draft);
2327
- return metrics.x !== 0 || metrics.y !== 0 || metrics.scale !== 0;
2570
+ return hasDraftAdjustment(draft, this.viewportPresets);
2328
2571
  }
2329
2572
  getAdjustedDraftPoint(point, draft) {
2330
- const metrics = this.getDraftAdjustmentMetrics(draft);
2331
- return {
2332
- x: point.x + metrics.cssX,
2333
- y: point.y + metrics.cssY
2334
- };
2573
+ return getAdjustedDraftPoint(point, draft, this.viewportPresets);
2335
2574
  }
2336
2575
  getAdjustedDraftSelection(selection, draft) {
2337
- const metrics = this.getDraftAdjustmentMetrics(draft);
2338
- return {
2339
- ...selection,
2340
- left: selection.left + metrics.cssX,
2341
- top: selection.top + metrics.cssY,
2342
- width: selection.width * metrics.scaleFactor,
2343
- height: selection.height * metrics.scaleFactor
2344
- };
2576
+ return getAdjustedDraftSelection(
2577
+ selection,
2578
+ draft,
2579
+ this.viewportPresets
2580
+ );
2345
2581
  }
2346
2582
  getDraftViewportScale(viewport) {
2347
- const preset = findReviewViewportPreset(
2348
- viewport,
2349
- this.config.options.viewports?.presets
2350
- );
2351
- const designWidth = typeof preset.designWidth === "number" && preset.designWidth > 0 ? preset.designWidth : viewport.width;
2352
- const scale = designWidth > 0 ? viewport.width / designWidth : 1;
2353
- return { scale, designWidth, presetLabel: preset.label };
2583
+ return getDraftViewportScale(viewport, this.viewportPresets);
2354
2584
  }
2355
2585
  getDraftComposerWidth(environment) {
2356
2586
  const bounds = environment.overlayRect;
@@ -2496,6 +2726,67 @@ var WebReviewKitView = class {
2496
2726
  return trimmedComment ? `${trimmedComment}
2497
2727
  ${adjustment}` : adjustment;
2498
2728
  }
2729
+ getAssigneeOption(assigneeId) {
2730
+ if (!assigneeId) return void 0;
2731
+ return this.config.options.assigneeOptions?.find(
2732
+ (option) => option.value === assigneeId
2733
+ );
2734
+ }
2735
+ getAssigneeName(assigneeId) {
2736
+ return this.getAssigneeOption(assigneeId)?.label;
2737
+ }
2738
+ createDraftTitleInput(value, onInput) {
2739
+ const input = document.createElement("input");
2740
+ input.className = "dfwr-input";
2741
+ input.placeholder = "Title";
2742
+ input.type = "text";
2743
+ input.value = value ?? "";
2744
+ input.addEventListener("input", () => onInput(input.value));
2745
+ return input;
2746
+ }
2747
+ isTitleFieldEnabled() {
2748
+ return this.config.options.fields?.title === true;
2749
+ }
2750
+ createDraftAssigneeSelect(value, fallbackLabel, onChange) {
2751
+ const assigneeOptions = this.config.options.assigneeOptions ?? [];
2752
+ if (assigneeOptions.length === 0) return void 0;
2753
+ const assigneeTitle = this.config.options.assigneeTitle?.trim() || "Assignee";
2754
+ const select = document.createElement("select");
2755
+ select.className = "dfwr-select";
2756
+ const emptyOption = document.createElement("option");
2757
+ emptyOption.value = "";
2758
+ emptyOption.textContent = assigneeTitle;
2759
+ select.append(emptyOption);
2760
+ const hasUnknownAssignee = Boolean(value) && !assigneeOptions.some((option) => option.value === value);
2761
+ if (hasUnknownAssignee && value) {
2762
+ const option = document.createElement("option");
2763
+ option.value = value;
2764
+ option.textContent = fallbackLabel ?? value;
2765
+ select.append(option);
2766
+ }
2767
+ assigneeOptions.forEach((assigneeOption) => {
2768
+ const option = document.createElement("option");
2769
+ option.value = assigneeOption.value;
2770
+ option.textContent = assigneeOption.label;
2771
+ select.append(option);
2772
+ });
2773
+ select.value = value ?? "";
2774
+ select.addEventListener("change", () => {
2775
+ onChange(select.value || null, this.getAssigneeName(select.value));
2776
+ });
2777
+ return select;
2778
+ }
2779
+ getDraftFields(titleInput, textarea, assigneeSelect) {
2780
+ const title = titleInput?.value.trim();
2781
+ const comment = textarea.value.trim();
2782
+ const assigneeId = assigneeSelect?.value.trim() || void 0;
2783
+ return {
2784
+ title: title || void 0,
2785
+ comment,
2786
+ assigneeId,
2787
+ assigneeName: this.getAssigneeName(assigneeId)
2788
+ };
2789
+ }
2499
2790
  getStyleableDraftElement(draft, environment) {
2500
2791
  if (draft.previewElement && draft.previewElement.ownerDocument === environment.document && "style" in draft.previewElement) {
2501
2792
  return draft.previewElement;
@@ -2686,11 +2977,14 @@ ${adjustment}` : adjustment;
2686
2977
  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
2978
  return empty;
2688
2979
  }
2689
- createNotePopover(draft) {
2980
+ // Builds the note draft layer: the on-page marker/highlight plus its composer
2981
+ // popover. When dockComposer is set the composer renders into the side panel
2982
+ // instead of floating next to the marker (used for the docked review mode).
2983
+ createNotePopover(draft, options = {}) {
2690
2984
  const environment = this.config.getEnvironment();
2691
2985
  const group = document.createElement("div");
2692
2986
  group.className = "dfwr-note-draft";
2693
- if (!environment) return group;
2987
+ if (!environment) return { layer: group, composer: void 0 };
2694
2988
  const isElementDraft = this.state.mode === "element" && Boolean(draft.selection);
2695
2989
  const hostPoint = toHostPoint(
2696
2990
  isElementDraft ? this.getAdjustedDraftPoint(draft.marker.viewport, draft) : draft.marker.viewport,
@@ -2714,8 +3008,14 @@ ${adjustment}` : adjustment;
2714
3008
  pin.style.top = `${hostPoint.y}px`;
2715
3009
  const popover = document.createElement("div");
2716
3010
  const position = getPopoverPosition(hostPoint, environment);
2717
- popover.className = `dfwr-note-popover${isElementDraft ? " is-composer" : ""}`;
2718
- if (isElementDraft) {
3011
+ popover.className = [
3012
+ "dfwr-note-popover",
3013
+ isElementDraft ? "is-composer" : "",
3014
+ options.dockComposer ? "is-docked-composer" : ""
3015
+ ].filter(Boolean).join(" ");
3016
+ if (options.dockComposer) {
3017
+ popover.style.width = "100%";
3018
+ } else if (isElementDraft) {
2719
3019
  const selection = draft.selection ? toHostSelection(
2720
3020
  this.getAdjustedDraftSelection(
2721
3021
  toViewportSelection(draft.selection.viewport),
@@ -2743,6 +3043,14 @@ ${adjustment}` : adjustment;
2743
3043
  meta.className = "dfwr-item-date";
2744
3044
  meta.textContent = formatNoteDraftMeta(draft);
2745
3045
  }
3046
+ const titleInput = this.isTitleFieldEnabled() ? this.createDraftTitleInput(draft.title, (title) => {
3047
+ const noteDraft = this.state.noteDraft;
3048
+ if (!noteDraft) return;
3049
+ this.config.actions.setNoteDraft({
3050
+ ...noteDraft,
3051
+ title
3052
+ });
3053
+ }) : void 0;
2746
3054
  const textarea = document.createElement("textarea");
2747
3055
  textarea.className = "dfwr-textarea";
2748
3056
  textarea.placeholder = "Review comment";
@@ -2756,13 +3064,30 @@ ${adjustment}` : adjustment;
2756
3064
  comment: textarea.value
2757
3065
  });
2758
3066
  });
3067
+ const assigneeSelect = this.createDraftAssigneeSelect(
3068
+ draft.assigneeId,
3069
+ draft.assigneeName,
3070
+ (assigneeId, assigneeName) => {
3071
+ const noteDraft = this.state.noteDraft;
3072
+ if (!noteDraft) return;
3073
+ this.config.actions.setNoteDraft({
3074
+ ...noteDraft,
3075
+ assigneeId,
3076
+ assigneeName
3077
+ });
3078
+ }
3079
+ );
2759
3080
  const saveDraft = () => {
2760
- const comment = textarea.value.trim();
2761
3081
  const currentDraft = this.state.noteDraft ?? draft;
3082
+ const fields = this.getDraftFields(titleInput, textarea, assigneeSelect);
3083
+ const comment = fields.comment;
2762
3084
  if (!comment && !this.hasDraftAdjustment(currentDraft)) return;
2763
3085
  void this.config.actions.createItem({
2764
3086
  kind: "note",
3087
+ title: fields.title,
2765
3088
  comment: this.withDraftAdjustmentComment(comment, currentDraft),
3089
+ assigneeId: fields.assigneeId,
3090
+ assigneeName: fields.assigneeName,
2766
3091
  viewport: currentDraft.viewport,
2767
3092
  anchor: currentDraft.anchor,
2768
3093
  marker: currentDraft.marker,
@@ -2774,18 +3099,31 @@ ${adjustment}` : adjustment;
2774
3099
  pin,
2775
3100
  popover,
2776
3101
  selectionHighlight,
2777
- textarea
3102
+ textarea,
3103
+ dockToggle: options.dockComposer
2778
3104
  }) : void 0;
2779
- const actions = this.createFormActions("Save note", saveDraft);
3105
+ const actions = this.createFormActions("Save note", saveDraft, {
3106
+ leading: adjustmentControls?.actionButton ? [adjustmentControls.actionButton] : void 0
3107
+ });
3108
+ const error = this.createDraftError();
2780
3109
  form.append(
2781
3110
  ...meta ? [meta] : [],
2782
3111
  ...adjustmentControls ? [adjustmentControls.panel] : [],
3112
+ ...titleInput ? [titleInput] : [],
2783
3113
  textarea,
3114
+ ...assigneeSelect ? [assigneeSelect] : [],
3115
+ ...error ? [error] : [],
2784
3116
  actions
2785
3117
  );
2786
- const dragHandle = isElementDraft ? this.createDraftDragHandle("Move DOM composer") : void 0;
2787
- popover.append(...dragHandle ? [dragHandle] : [], form);
2788
- group.append(pin, popover);
3118
+ const dragHandle = isElementDraft && !options.dockComposer ? this.createDraftDragHandle("Move DOM composer") : void 0;
3119
+ popover.append(
3120
+ ...dragHandle ? [dragHandle] : [],
3121
+ form
3122
+ );
3123
+ group.append(pin);
3124
+ if (!options.dockComposer) {
3125
+ group.append(popover);
3126
+ }
2789
3127
  if (dragHandle) {
2790
3128
  this.attachDraftComposerDrag(popover, dragHandle, (composerPosition) => {
2791
3129
  const noteDraft = this.state.noteDraft ?? draft;
@@ -2798,18 +3136,23 @@ ${adjustment}` : adjustment;
2798
3136
  }
2799
3137
  this.attachDraftPinDrag(
2800
3138
  pin,
2801
- isElementDraft ? void 0 : popover,
3139
+ isElementDraft || options.dockComposer ? void 0 : popover,
2802
3140
  meta,
2803
3141
  textarea
2804
3142
  );
2805
- window.setTimeout(() => {
2806
- if (draft.adjustment?.isActive) {
2807
- adjustmentControls?.focusTarget.focus();
2808
- return;
2809
- }
2810
- textarea.focus();
2811
- }, 0);
2812
- return group;
3143
+ if (!options.dockComposer) {
3144
+ window.setTimeout(() => {
3145
+ if (draft.adjustment?.isActive) {
3146
+ adjustmentControls?.focusTarget.focus();
3147
+ return;
3148
+ }
3149
+ textarea.focus();
3150
+ }, 0);
3151
+ }
3152
+ return {
3153
+ layer: group,
3154
+ composer: options.dockComposer ? popover : void 0
3155
+ };
2813
3156
  }
2814
3157
  createDraftDragHandle(label) {
2815
3158
  const handle = document.createElement("button");
@@ -2896,12 +3239,16 @@ ${adjustment}` : adjustment;
2896
3239
  handle.addEventListener("pointerup", stopDrag);
2897
3240
  handle.addEventListener("pointercancel", stopDrag);
2898
3241
  }
3242
+ // Builds the element-adjustment controls (nudge the previewed element via
3243
+ // arrow keys / buttons). Wires keyboard deltas to the draft transform and
3244
+ // keeps the pin, popover, highlight and textarea in sync as the value changes.
2899
3245
  createAdjustmentControls({
2900
3246
  draft,
2901
3247
  pin,
2902
3248
  popover,
2903
3249
  selectionHighlight,
2904
- textarea
3250
+ textarea,
3251
+ dockToggle
2905
3252
  }) {
2906
3253
  const panel = document.createElement("div");
2907
3254
  panel.className = "dfwr-adjust-panel is-dom-adjust-panel";
@@ -2977,12 +3324,16 @@ ${adjustment}` : adjustment;
2977
3324
  }
2978
3325
  }));
2979
3326
  });
2980
- header.append(help, adjust);
3327
+ header.append(help);
3328
+ if (!dockToggle) {
3329
+ header.append(adjust);
3330
+ }
2981
3331
  panel.append(header, xyStatus, scaleStatus);
2982
3332
  syncControls(draft);
2983
3333
  return {
2984
3334
  panel,
2985
- focusTarget: adjust
3335
+ focusTarget: adjust,
3336
+ actionButton: dockToggle ? adjust : void 0
2986
3337
  };
2987
3338
  }
2988
3339
  getAdjustmentKeyDelta(event) {
@@ -3035,6 +3386,14 @@ ${adjustment}` : adjustment;
3035
3386
  return form;
3036
3387
  }
3037
3388
  form.append(this.createAreaMetricsPanel(areaDraft));
3389
+ const titleInput = this.isTitleFieldEnabled() ? this.createDraftTitleInput(areaDraft.title, (title) => {
3390
+ const draft = this.state.areaDraft;
3391
+ if (!draft) return;
3392
+ this.config.actions.setAreaDraft({
3393
+ ...draft,
3394
+ title
3395
+ });
3396
+ }) : void 0;
3038
3397
  const textarea = document.createElement("textarea");
3039
3398
  textarea.className = "dfwr-textarea";
3040
3399
  textarea.placeholder = "Area comment";
@@ -3048,20 +3407,44 @@ ${adjustment}` : adjustment;
3048
3407
  comment: textarea.value
3049
3408
  });
3050
3409
  });
3410
+ const assigneeSelect = this.createDraftAssigneeSelect(
3411
+ areaDraft.assigneeId,
3412
+ areaDraft.assigneeName,
3413
+ (assigneeId, assigneeName) => {
3414
+ const draft = this.state.areaDraft;
3415
+ if (!draft) return;
3416
+ this.config.actions.setAreaDraft({
3417
+ ...draft,
3418
+ assigneeId,
3419
+ assigneeName
3420
+ });
3421
+ }
3422
+ );
3051
3423
  const actions = this.createFormActions("Save area", () => {
3052
- const comment = textarea.value.trim();
3053
3424
  const draft = this.state.areaDraft;
3425
+ const fields = this.getDraftFields(titleInput, textarea, assigneeSelect);
3426
+ const comment = fields.comment;
3054
3427
  if (!comment || !draft) return;
3055
3428
  void this.config.actions.createItem({
3056
3429
  kind: "area",
3430
+ title: fields.title,
3057
3431
  comment,
3432
+ assigneeId: fields.assigneeId,
3433
+ assigneeName: fields.assigneeName,
3058
3434
  viewport: draft.viewport,
3059
3435
  anchor: draft.anchor,
3060
3436
  marker: draft.marker,
3061
3437
  selection: draft.selection
3062
3438
  });
3063
3439
  });
3064
- form.append(textarea, actions);
3440
+ const error = this.createDraftError();
3441
+ form.append(
3442
+ ...titleInput ? [titleInput] : [],
3443
+ textarea,
3444
+ ...assigneeSelect ? [assigneeSelect] : [],
3445
+ ...error ? [error] : [],
3446
+ actions
3447
+ );
3065
3448
  return form;
3066
3449
  }
3067
3450
  createAreaMetricsPanel(draft) {
@@ -3108,11 +3491,17 @@ ${adjustment}` : adjustment;
3108
3491
  }
3109
3492
  return layer;
3110
3493
  }
3111
- createAreaDraftPopover(draft) {
3494
+ createAreaDraftPopover(draft, options = {}) {
3112
3495
  const environment = this.config.getEnvironment();
3113
3496
  const popover = document.createElement("div");
3114
- popover.className = "dfwr-area-draft is-composer";
3115
- if (environment && draft.selection) {
3497
+ popover.className = [
3498
+ "dfwr-area-draft",
3499
+ "is-composer",
3500
+ options.dockComposer ? "is-docked-composer" : ""
3501
+ ].filter(Boolean).join(" ");
3502
+ if (options.dockComposer) {
3503
+ popover.style.width = "100%";
3504
+ } else if (environment && draft.selection) {
3116
3505
  const selection = toHostSelection(
3117
3506
  toViewportSelection(draft.selection.viewport),
3118
3507
  environment
@@ -3128,40 +3517,61 @@ ${adjustment}` : adjustment;
3128
3517
  popover.style.width = `${composer.width}px`;
3129
3518
  popover.style.right = "auto";
3130
3519
  }
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
3520
+ const dragHandle = options.dockComposer ? void 0 : this.createDraftDragHandle("Move area composer");
3521
+ popover.append(
3522
+ ...dragHandle ? [dragHandle] : [],
3523
+ this.createAreaForm()
3524
+ );
3525
+ if (dragHandle) {
3526
+ this.attachDraftComposerDrag(popover, dragHandle, (composerPosition) => {
3527
+ const areaDraft = this.state.areaDraft ?? draft;
3528
+ this.config.actions.setAreaDraft({
3529
+ ...areaDraft,
3530
+ composerPosition
3531
+ });
3138
3532
  });
3139
- });
3533
+ }
3140
3534
  return popover;
3141
3535
  }
3142
3536
  createFormActions(saveLabel, onSave, options) {
3143
3537
  const actions = document.createElement("div");
3144
3538
  actions.className = ["dfwr-actions", options?.className].filter(Boolean).join(" ");
3539
+ const isSaving = this.state.isCreatingItem;
3145
3540
  const save = document.createElement("button");
3146
3541
  save.className = "dfwr-button is-primary";
3147
3542
  save.type = "button";
3148
- save.textContent = saveLabel;
3543
+ save.disabled = isSaving;
3544
+ save.setAttribute("aria-busy", isSaving ? "true" : "false");
3545
+ if (isSaving) {
3546
+ save.append(this.createSpinner("dfwr-spinner"), "Saving...");
3547
+ } else {
3548
+ save.textContent = saveLabel;
3549
+ }
3149
3550
  save.addEventListener("click", (event) => {
3150
3551
  event.preventDefault();
3151
3552
  event.stopPropagation();
3553
+ if (this.state.isCreatingItem) return;
3152
3554
  onSave();
3153
3555
  });
3154
3556
  const cancel = document.createElement("button");
3155
3557
  cancel.className = "dfwr-button";
3156
3558
  cancel.type = "button";
3559
+ cancel.disabled = isSaving;
3157
3560
  cancel.textContent = "Cancel";
3158
3561
  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();
3562
+ this.cancelDraft(event);
3164
3563
  });
3564
+ if (options?.leading?.length) {
3565
+ actions.classList.add("has-leading");
3566
+ const leading = document.createElement("div");
3567
+ leading.className = "dfwr-actions-leading";
3568
+ leading.append(...options.leading);
3569
+ const primary = document.createElement("div");
3570
+ primary.className = "dfwr-actions-primary";
3571
+ primary.append(save, cancel);
3572
+ actions.append(leading, primary);
3573
+ return actions;
3574
+ }
3165
3575
  if (options?.beforeSave?.length || options?.className) {
3166
3576
  actions.append(cancel, ...options.beforeSave ?? [], save);
3167
3577
  return actions;
@@ -3169,6 +3579,20 @@ ${adjustment}` : adjustment;
3169
3579
  actions.append(save, cancel);
3170
3580
  return actions;
3171
3581
  }
3582
+ createSpinner(className) {
3583
+ const spinner = document.createElement("span");
3584
+ spinner.className = className;
3585
+ spinner.setAttribute("aria-hidden", "true");
3586
+ return spinner;
3587
+ }
3588
+ createDraftError() {
3589
+ if (!this.state.draftError) return void 0;
3590
+ const error = document.createElement("p");
3591
+ error.className = "dfwr-form-error";
3592
+ error.setAttribute("role", "alert");
3593
+ error.textContent = this.state.draftError;
3594
+ return error;
3595
+ }
3172
3596
  createList() {
3173
3597
  const section = document.createElement("div");
3174
3598
  section.className = "dfwr-list";
@@ -3221,14 +3645,20 @@ ${adjustment}` : adjustment;
3221
3645
  kind.className = "dfwr-item-kind";
3222
3646
  kind.textContent = item.kind;
3223
3647
  badges.append(scope, kind);
3648
+ const title = this.isTitleFieldEnabled() ? item.title?.trim() : "";
3649
+ const titleElement = title ? document.createElement("strong") : void 0;
3650
+ if (title && titleElement) {
3651
+ titleElement.className = "dfwr-item-title";
3652
+ titleElement.textContent = title;
3653
+ }
3224
3654
  const comment = document.createElement("p");
3225
- comment.className = "dfwr-item-comment";
3655
+ comment.className = `dfwr-item-comment${title ? "" : " is-primary"}`;
3226
3656
  comment.textContent = item.comment;
3227
3657
  const date = document.createElement("time");
3228
3658
  date.className = "dfwr-item-date";
3229
3659
  date.dateTime = item.createdAt;
3230
3660
  date.textContent = formatItemMeta(item);
3231
- body.append(badges, comment, date);
3661
+ body.append(badges, ...titleElement ? [titleElement] : [], comment, date);
3232
3662
  const actions = document.createElement("div");
3233
3663
  actions.className = "dfwr-item-actions";
3234
3664
  actions.addEventListener("click", (event) => event.stopPropagation());
@@ -3417,7 +3847,14 @@ ${formatItemMeta(item)}`;
3417
3847
  event,
3418
3848
  this.config.getEnvironment()
3419
3849
  );
3420
- void (this.state.mode === "element" ? this.config.actions.bindElementDraftToPoint(nextPoint, textarea.value) : this.config.actions.bindNoteDraftToPoint(nextPoint, textarea.value));
3850
+ const currentDraft = this.state.noteDraft;
3851
+ const fields = {
3852
+ title: currentDraft?.title,
3853
+ comment: textarea.value,
3854
+ assigneeId: currentDraft?.assigneeId,
3855
+ assigneeName: currentDraft?.assigneeName
3856
+ };
3857
+ void (this.state.mode === "element" ? this.config.actions.bindElementDraftToPoint(nextPoint, fields) : this.config.actions.bindNoteDraftToPoint(nextPoint, fields));
3421
3858
  });
3422
3859
  }
3423
3860
  createNoteLayer() {
@@ -3495,10 +3932,14 @@ ${formatItemMeta(item)}`;
3495
3932
  let startX = 0;
3496
3933
  let startY = 0;
3497
3934
  let selection;
3935
+ let activePointerId;
3936
+ let isDragging = false;
3937
+ const ownerWindow = layer.ownerDocument.defaultView ?? window;
3498
3938
  const updateBox = (event) => {
3939
+ const nextEnvironment = this.config.getEnvironment();
3499
3940
  const nextPoint = toTargetPointFromHostEvent(
3500
3941
  event,
3501
- this.config.getEnvironment()
3942
+ nextEnvironment
3502
3943
  );
3503
3944
  const left = Math.min(startX, nextPoint.x);
3504
3945
  const top = Math.min(startY, nextPoint.y);
@@ -3506,7 +3947,7 @@ ${formatItemMeta(item)}`;
3506
3947
  const height = Math.abs(nextPoint.y - startY);
3507
3948
  const hostPoint = toHostPoint(
3508
3949
  { x: left, y: top },
3509
- this.config.getEnvironment()
3950
+ nextEnvironment
3510
3951
  );
3511
3952
  selection = { left, top, width, height };
3512
3953
  box.style.left = `${hostPoint.x}px`;
@@ -3514,9 +3955,68 @@ ${formatItemMeta(item)}`;
3514
3955
  box.style.width = `${width}px`;
3515
3956
  box.style.height = `${height}px`;
3516
3957
  };
3958
+ const addDragListeners = () => {
3959
+ ownerWindow.addEventListener("pointermove", handlePointerMove, true);
3960
+ ownerWindow.addEventListener("pointerup", handlePointerUp, true);
3961
+ ownerWindow.addEventListener("pointercancel", handlePointerCancel, true);
3962
+ };
3963
+ const removeDragListeners = () => {
3964
+ ownerWindow.removeEventListener("pointermove", handlePointerMove, true);
3965
+ ownerWindow.removeEventListener("pointerup", handlePointerUp, true);
3966
+ ownerWindow.removeEventListener(
3967
+ "pointercancel",
3968
+ handlePointerCancel,
3969
+ true
3970
+ );
3971
+ };
3972
+ const releasePointerCapture = (event) => {
3973
+ try {
3974
+ if (layer.hasPointerCapture(event.pointerId)) {
3975
+ layer.releasePointerCapture(event.pointerId);
3976
+ }
3977
+ } catch {
3978
+ }
3979
+ };
3980
+ function isActivePointer(event) {
3981
+ return isDragging && event.pointerId === activePointerId;
3982
+ }
3983
+ const finishAreaSelection = (event) => {
3984
+ if (!isActivePointer(event)) return;
3985
+ event.preventDefault();
3986
+ updateBox(event);
3987
+ releasePointerCapture(event);
3988
+ removeDragListeners();
3989
+ isDragging = false;
3990
+ activePointerId = void 0;
3991
+ if (!selection || selection.width < 8 || selection.height < 8) return;
3992
+ this.config.actions.setSelectingArea(true);
3993
+ this.config.actions.render();
3994
+ void this.config.actions.createAreaDraft(selection);
3995
+ };
3996
+ function handlePointerMove(event) {
3997
+ if (!isActivePointer(event)) return;
3998
+ event.preventDefault();
3999
+ updateBox(event);
4000
+ }
4001
+ const handlePointerUp = (event) => {
4002
+ finishAreaSelection(event);
4003
+ };
4004
+ const handlePointerCancel = (event) => {
4005
+ if (!isActivePointer(event)) return;
4006
+ releasePointerCapture(event);
4007
+ removeDragListeners();
4008
+ isDragging = false;
4009
+ activePointerId = void 0;
4010
+ };
3517
4011
  layer.addEventListener("pointerdown", (event) => {
4012
+ if (event.button !== 0) return;
3518
4013
  event.preventDefault();
3519
- layer.setPointerCapture(event.pointerId);
4014
+ activePointerId = event.pointerId;
4015
+ isDragging = true;
4016
+ try {
4017
+ layer.setPointerCapture(event.pointerId);
4018
+ } catch {
4019
+ }
3520
4020
  const startPoint = toTargetPointFromHostEvent(
3521
4021
  event,
3522
4022
  this.config.getEnvironment()
@@ -3524,20 +4024,11 @@ ${formatItemMeta(item)}`;
3524
4024
  startX = startPoint.x;
3525
4025
  startY = startPoint.y;
3526
4026
  updateBox(event);
4027
+ addDragListeners();
3527
4028
  });
3528
- layer.addEventListener("pointermove", (event) => {
3529
- if (!layer.hasPointerCapture(event.pointerId)) return;
3530
- updateBox(event);
3531
- });
3532
- layer.addEventListener("pointerup", (event) => {
3533
- if (!layer.hasPointerCapture(event.pointerId)) return;
3534
- layer.releasePointerCapture(event.pointerId);
3535
- updateBox(event);
3536
- if (!selection || selection.width < 8 || selection.height < 8) return;
3537
- this.config.actions.setSelectingArea(true);
3538
- this.config.actions.render();
3539
- void this.config.actions.createAreaDraft(selection);
3540
- });
4029
+ layer.addEventListener("pointermove", handlePointerMove);
4030
+ layer.addEventListener("pointerup", handlePointerUp);
4031
+ layer.addEventListener("pointercancel", handlePointerCancel);
3541
4032
  return layer;
3542
4033
  }
3543
4034
  };
@@ -3577,6 +4068,8 @@ var WebReviewKitApp = class {
3577
4068
  this.isOpen = false;
3578
4069
  this.mode = "idle";
3579
4070
  this.items = [];
4071
+ this.draftError = "";
4072
+ this.isCreatingItem = false;
3580
4073
  this.isSelectingArea = false;
3581
4074
  this.handleKeyDown = (event) => {
3582
4075
  if (event.key === "Escape" && this.cancelMode()) {
@@ -3590,9 +4083,10 @@ var WebReviewKitApp = class {
3590
4083
  this.toggle();
3591
4084
  };
3592
4085
  this.handleViewportChange = () => {
3593
- if (!this.isOpen || this.renderFrame) return;
4086
+ if (!this.isOpen || this.renderFrame || this.isDraftComposerFocused()) return;
3594
4087
  this.renderFrame = window.requestAnimationFrame(() => {
3595
4088
  this.renderFrame = void 0;
4089
+ if (this.isDraftComposerFocused()) return;
3596
4090
  this.render();
3597
4091
  });
3598
4092
  };
@@ -3606,6 +4100,8 @@ var WebReviewKitApp = class {
3606
4100
  items: this.items,
3607
4101
  noteDraft: this.noteDraft,
3608
4102
  areaDraft: this.areaDraft,
4103
+ draftError: this.draftError,
4104
+ isCreatingItem: this.isCreatingItem,
3609
4105
  isSelectingArea: this.isSelectingArea,
3610
4106
  highlightedItemId: this.highlightedItemId
3611
4107
  }),
@@ -3620,19 +4116,22 @@ var WebReviewKitApp = class {
3620
4116
  clearDrafts: () => {
3621
4117
  this.noteDraft = void 0;
3622
4118
  this.areaDraft = void 0;
4119
+ this.draftError = "";
3623
4120
  },
3624
4121
  setNoteDraft: (draft) => {
3625
4122
  this.noteDraft = draft;
4123
+ this.draftError = "";
3626
4124
  },
3627
4125
  setAreaDraft: (draft) => {
3628
4126
  this.areaDraft = draft;
4127
+ this.draftError = "";
3629
4128
  },
3630
4129
  setSelectingArea: (isSelectingArea) => {
3631
4130
  this.isSelectingArea = isSelectingArea;
3632
4131
  },
3633
4132
  createItem: (input) => this.createItem(input),
3634
- bindNoteDraftToPoint: (point, comment) => this.bindNoteDraftToPoint(point, comment),
3635
- bindElementDraftToPoint: (point, comment) => this.bindElementDraftToPoint(point, comment),
4133
+ bindNoteDraftToPoint: (point, fields) => this.bindNoteDraftToPoint(point, fields),
4134
+ bindElementDraftToPoint: (point, fields) => this.bindElementDraftToPoint(point, fields),
3636
4135
  createAreaDraft: (selection) => this.createAreaDraft(selection)
3637
4136
  }
3638
4137
  });
@@ -3701,7 +4200,7 @@ var WebReviewKitApp = class {
3701
4200
  this.noteDraft = void 0;
3702
4201
  this.areaDraft = void 0;
3703
4202
  this.isSelectingArea = false;
3704
- await this.bindElementDraftToElement(element, comment);
4203
+ await this.bindElementDraftToElement(element, { comment });
3705
4204
  }
3706
4205
  getMode() {
3707
4206
  return this.mode;
@@ -3769,6 +4268,14 @@ var WebReviewKitApp = class {
3769
4268
  this.render();
3770
4269
  return true;
3771
4270
  }
4271
+ isDraftComposerFocused() {
4272
+ if (!this.noteDraft && !this.areaDraft) return false;
4273
+ const composerHost = this.getEnvironment()?.composerHost;
4274
+ const activeElement = composerHost?.ownerDocument.activeElement;
4275
+ return Boolean(
4276
+ composerHost && activeElement && composerHost.contains(activeElement)
4277
+ );
4278
+ }
3772
4279
  getEnvironment() {
3773
4280
  const target = typeof this.options.target === "function" ? this.options.target() : this.options.target;
3774
4281
  if (!target) {
@@ -3797,6 +4304,7 @@ var WebReviewKitApp = class {
3797
4304
  height: target.window.innerHeight
3798
4305
  };
3799
4306
  const overlayRect = target.getOverlayRect?.() ?? rect;
4307
+ const composerHost = target.getComposerHost?.();
3800
4308
  return {
3801
4309
  window: target.window,
3802
4310
  document: target.document,
@@ -3811,7 +4319,8 @@ var WebReviewKitApp = class {
3811
4319
  top: overlayRect.top,
3812
4320
  width: overlayRect.width,
3813
4321
  height: overlayRect.height
3814
- }
4322
+ },
4323
+ composerHost
3815
4324
  };
3816
4325
  } catch {
3817
4326
  return void 0;
@@ -3834,7 +4343,7 @@ var WebReviewKitApp = class {
3834
4343
  if (!this.shadow) return;
3835
4344
  this.view.render(this.shadow, this.createHiddenItemsStyleElement());
3836
4345
  }
3837
- async bindNoteDraftToPoint(point, comment) {
4346
+ async bindNoteDraftToPoint(point, fields = {}) {
3838
4347
  const environment = this.getEnvironment();
3839
4348
  if (!environment) return;
3840
4349
  const viewport = getViewportSize(environment);
@@ -3854,13 +4363,13 @@ var WebReviewKitApp = class {
3854
4363
  viewport,
3855
4364
  anchor,
3856
4365
  marker,
3857
- comment
4366
+ ...fields
3858
4367
  };
3859
4368
  });
3860
4369
  this.noteDraft = draft;
3861
4370
  this.render();
3862
4371
  }
3863
- async bindElementDraftToPoint(point, comment) {
4372
+ async bindElementDraftToPoint(point, fields = {}) {
3864
4373
  const environment = this.getEnvironment();
3865
4374
  if (!environment) return;
3866
4375
  const viewport = getViewportSize(environment);
@@ -3904,14 +4413,14 @@ var WebReviewKitApp = class {
3904
4413
  anchor,
3905
4414
  marker,
3906
4415
  selection: reviewSelection,
3907
- comment,
4416
+ ...fields,
3908
4417
  previewElement
3909
4418
  };
3910
4419
  });
3911
4420
  this.noteDraft = draft;
3912
4421
  this.render();
3913
4422
  }
3914
- async bindElementDraftToElement(element, comment) {
4423
+ async bindElementDraftToElement(element, fields = {}) {
3915
4424
  const environment = this.getEnvironment();
3916
4425
  if (!environment || element.ownerDocument !== environment.document) return;
3917
4426
  const viewport = getViewportSize(environment);
@@ -3944,7 +4453,7 @@ var WebReviewKitApp = class {
3944
4453
  anchor,
3945
4454
  marker,
3946
4455
  selection: reviewSelection,
3947
- comment,
4456
+ ...fields,
3948
4457
  previewElement
3949
4458
  };
3950
4459
  });
@@ -3954,26 +4463,33 @@ var WebReviewKitApp = class {
3954
4463
  }
3955
4464
  async createAreaDraft(selection) {
3956
4465
  const environment = this.getEnvironment();
3957
- if (!environment) return;
3958
- const viewport = getViewportSize(environment);
3959
- this.areaDraft = await this.withOverlayHidden(() => {
3960
- const marker = createSelectionCenterMarker(
3961
- selection,
3962
- void 0,
3963
- environment
3964
- );
3965
- const reviewSelection = {
3966
- viewport: toPublicSelection(selection)
3967
- };
3968
- return {
3969
- viewport,
3970
- marker,
3971
- selection: reviewSelection
3972
- };
3973
- });
3974
- this.isSelectingArea = false;
3975
- this.setModeState("area");
3976
- this.render();
4466
+ if (!environment) {
4467
+ this.isSelectingArea = false;
4468
+ this.render();
4469
+ return;
4470
+ }
4471
+ try {
4472
+ const viewport = getViewportSize(environment);
4473
+ this.areaDraft = await this.withOverlayHidden(() => {
4474
+ const marker = createSelectionCenterMarker(
4475
+ selection,
4476
+ void 0,
4477
+ environment
4478
+ );
4479
+ const reviewSelection = {
4480
+ viewport: toPublicSelection(selection)
4481
+ };
4482
+ return {
4483
+ viewport,
4484
+ marker,
4485
+ selection: reviewSelection
4486
+ };
4487
+ });
4488
+ this.setModeState("area");
4489
+ } finally {
4490
+ this.isSelectingArea = false;
4491
+ this.render();
4492
+ }
3977
4493
  }
3978
4494
  async withOverlayHidden(callback) {
3979
4495
  if (!this.root) return callback();
@@ -3987,11 +4503,16 @@ var WebReviewKitApp = class {
3987
4503
  }
3988
4504
  async createItem(input) {
3989
4505
  const environment = this.getEnvironment();
3990
- if (!environment) return;
4506
+ if (!environment || this.isCreatingItem) return;
3991
4507
  const now = (/* @__PURE__ */ new Date()).toISOString();
3992
4508
  const routeKey = getRouteKey(environment);
3993
4509
  const viewport = input.viewport ?? getViewportSize(environment);
3994
4510
  const createdBy = this.options.userId?.trim();
4511
+ const title = input.title?.trim();
4512
+ const assigneeId = input.assigneeId?.trim() || void 0;
4513
+ const assigneeOption = this.options.assigneeOptions?.find(
4514
+ (option) => option.value === assigneeId
4515
+ );
3995
4516
  const item = {
3996
4517
  id: createId(),
3997
4518
  projectId: this.options.projectId,
@@ -4001,8 +4522,10 @@ var WebReviewKitApp = class {
4001
4522
  normalizedPath: routeKey,
4002
4523
  scope: input.scope ?? getReviewViewportScope(viewport, this.options.viewports?.presets),
4003
4524
  kind: input.kind,
4004
- title: input.comment.split("\n")[0]?.slice(0, 80),
4525
+ title: title || void 0,
4005
4526
  comment: input.comment,
4527
+ assigneeId,
4528
+ assigneeName: input.assigneeName ?? assigneeOption?.label,
4006
4529
  createdBy: createdBy || void 0,
4007
4530
  status: "todo",
4008
4531
  viewport,
@@ -4017,13 +4540,23 @@ var WebReviewKitApp = class {
4017
4540
  createdAt: now,
4018
4541
  updatedAt: now
4019
4542
  };
4020
- const createdItem = await this.adapter.create(item);
4021
- this.setModeState("idle");
4022
- this.noteDraft = void 0;
4023
- this.areaDraft = void 0;
4024
- this.highlightItem(createdItem.id);
4025
- await this.reload();
4026
- await this.options.onCreateItem?.(createdItem);
4543
+ this.draftError = "";
4544
+ this.isCreatingItem = true;
4545
+ this.render();
4546
+ try {
4547
+ const createdItem = await this.adapter.create(item);
4548
+ this.setModeState("idle");
4549
+ this.noteDraft = void 0;
4550
+ this.areaDraft = void 0;
4551
+ this.highlightItem(createdItem.id);
4552
+ await this.reload();
4553
+ await this.options.onCreateItem?.(createdItem);
4554
+ } catch (error) {
4555
+ this.draftError = error instanceof Error ? error.message : "Failed to save QA.";
4556
+ } finally {
4557
+ this.isCreatingItem = false;
4558
+ this.render();
4559
+ }
4027
4560
  }
4028
4561
  async restoreItem(item) {
4029
4562
  this.setModeState("idle");
@@ -4080,6 +4613,7 @@ export {
4080
4613
  REVIEW_WORKFLOW_STATUS_OPTIONS,
4081
4614
  normalizeReviewItemStatus,
4082
4615
  localAdapter,
4616
+ DEFAULT_REVIEW_FIGMA_IMAGE_FORMAT,
4083
4617
  clamp,
4084
4618
  DEFAULT_REVIEW_VIEWPORTS,
4085
4619
  findReviewViewportPreset,
@@ -4088,6 +4622,7 @@ export {
4088
4622
  getReviewItemScopeLabel,
4089
4623
  getNumberedReviewItems,
4090
4624
  runWithAutoScrollBehavior,
4625
+ reviewTypographyTokens,
4091
4626
  createWebReviewKit
4092
4627
  };
4093
- //# sourceMappingURL=chunk-TWCSIBMY.js.map
4628
+ //# sourceMappingURL=chunk-RPVLRULC.js.map