@astryxdesign/lab 0.7.6-canary.fdabeba → 0.7.7-canary.0d1ddcd

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 (55) hide show
  1. package/dist/CodeEditor/CodeEditor.d.ts.map +1 -1
  2. package/dist/CodeEditor/CodeEditor.js +13 -1
  3. package/dist/CommentPin/CommentPin.js +1 -1
  4. package/dist/Comments/CommentComposer.d.ts.map +1 -1
  5. package/dist/Comments/CommentComposer.js +12 -4
  6. package/dist/DiffViewer/DiffViewer.d.ts.map +1 -1
  7. package/dist/DiffViewer/DiffViewer.js +313 -64
  8. package/dist/DiffViewer/createDiffFile.d.ts +0 -5
  9. package/dist/DiffViewer/createDiffFile.d.ts.map +1 -1
  10. package/dist/DiffViewer/createDiffFile.js +230 -21
  11. package/dist/DiffViewer/diffTypes.d.ts +7 -0
  12. package/dist/DiffViewer/diffTypes.d.ts.map +1 -1
  13. package/dist/LogStream/LogStream.d.ts.map +1 -1
  14. package/dist/LogStream/LogStream.js +8 -8
  15. package/dist/MobilePowerSearch/MobilePowerSearch.d.ts +3 -6
  16. package/dist/MobilePowerSearch/MobilePowerSearch.d.ts.map +1 -1
  17. package/dist/MobilePowerSearch/MobilePowerSearch.js +12 -76
  18. package/dist/MobileTokenizer/MobileTokenizer.d.ts +10 -7
  19. package/dist/MobileTokenizer/MobileTokenizer.d.ts.map +1 -1
  20. package/dist/MobileTokenizer/MobileTokenizer.js +191 -114
  21. package/dist/internal/MobileSheetConfirmation.d.ts +12 -0
  22. package/dist/internal/MobileSheetConfirmation.d.ts.map +1 -0
  23. package/dist/internal/MobileSheetConfirmation.js +88 -0
  24. package/dist/internal/useDismissSearchOnTouchDrag.d.ts +21 -0
  25. package/dist/internal/useDismissSearchOnTouchDrag.d.ts.map +1 -0
  26. package/dist/internal/useDismissSearchOnTouchDrag.js +43 -0
  27. package/dist/lab.css +4 -0
  28. package/package.json +5 -4
  29. package/src/CircularProgress/__tests__/CircularProgressMotion.a11y.chromium.spec.ts +106 -62
  30. package/src/CodeEditor/CodeEditor.test.tsx +27 -0
  31. package/src/CodeEditor/CodeEditor.tsx +16 -4
  32. package/src/CommentPin/CommentPin.tsx +1 -1
  33. package/src/Comments/CommentComposer.test.tsx +56 -0
  34. package/src/Comments/CommentComposer.tsx +18 -13
  35. package/src/DiffViewer/DiffViewer.doc.mjs +1 -1
  36. package/src/DiffViewer/DiffViewer.test.tsx +236 -1
  37. package/src/DiffViewer/DiffViewer.tsx +375 -66
  38. package/src/DiffViewer/createDiffFile.test.ts +155 -0
  39. package/src/DiffViewer/createDiffFile.ts +278 -21
  40. package/src/DiffViewer/diffTypes.ts +7 -0
  41. package/src/LogStream/LogStream.tsx +2 -0
  42. package/src/MobilePowerSearch/MobilePowerSearch.doc.mjs +1 -1
  43. package/src/MobilePowerSearch/MobilePowerSearch.test.tsx +7 -2
  44. package/src/MobilePowerSearch/MobilePowerSearch.tsx +16 -83
  45. package/src/MobileTokenizer/MobileTokenizer.doc.mjs +3 -3
  46. package/src/MobileTokenizer/MobileTokenizer.test.tsx +289 -33
  47. package/src/MobileTokenizer/MobileTokenizer.tsx +242 -137
  48. package/src/Schedule/__tests__/ScheduleTimeDirection.a11y.chromium.spec.ts +4 -0
  49. package/src/TransferList/TransferList.doc.mjs +1 -1
  50. package/src/internal/MobileSheetConfirmation.test.tsx +68 -0
  51. package/src/internal/MobileSheetConfirmation.tsx +97 -0
  52. package/src/internal/useDismissSearchOnTouchDrag.test.tsx +94 -0
  53. package/src/internal/useDismissSearchOnTouchDrag.ts +57 -0
  54. package/src/CommentPin/CommentPin.spec.md +0 -319
  55. package/src/Schedule/Schedule.spec.md +0 -590
@@ -1 +1 @@
1
- {"version":3,"file":"CodeEditor.d.ts","sourceRoot":"","sources":["../../src/CodeEditor/CodeEditor.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAkBlD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,8BAA8B,CAAC;AAuF5D,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,CACX;IACC,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,qCAAqC;IACrC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,6BAA6B;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,+BAA+B;IAC/B,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,uBAAuB;IACvB,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;IAC5D;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAsED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,QAAsB,EACtB,cAAsB,EACtB,UAAkB,EAClB,WAAW,EACX,SAAS,EACT,IAAW,EACX,SAAS,EAAE,eAAe,EAC1B,KAAK,EACL,UAAqE,EACrE,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,eAAe,+BA8UjB;yBA/Ve,UAAU"}
1
+ {"version":3,"file":"CodeEditor.d.ts","sourceRoot":"","sources":["../../src/CodeEditor/CodeEditor.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAkBlD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,8BAA8B,CAAC;AA2F5D,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,CACX;IACC,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,qCAAqC;IACrC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,6BAA6B;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,+BAA+B;IAC/B,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,uBAAuB;IACvB,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;IAC5D;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAsED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,QAAsB,EACtB,cAAsB,EACtB,UAAkB,EAClB,WAAW,EACX,SAAS,EACT,IAAW,EACX,SAAS,EAAE,eAAe,EAC1B,KAAK,EACL,UAAqE,EACrE,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,eAAe,+BAsVjB;yBAvWe,UAAU"}
@@ -35,6 +35,9 @@ const styles = {
35
35
  k1xSpc: "x78zum5",
36
36
  kaIpWk: "xh6dtrn",
37
37
  kWkggS: "xwmxj5m",
38
+ kMzoRj: "x1litavf",
39
+ ksu8eU: "x1y0btm7",
40
+ kVAM5u: "x14i3s5s",
38
41
  kVQacm: "xb3r6kr",
39
42
  $$css: true
40
43
  },
@@ -51,6 +54,9 @@ const styles = {
51
54
  k9WMMc: "xp4054r",
52
55
  kfSwDN: "x87ps6o",
53
56
  kMwMTN: "xnbbluu",
57
+ kWqL5O: "x1pcaw5z",
58
+ kSWEuD: "x32b0ac",
59
+ k26BEO: "xtgwc6q",
54
60
  $$css: true
55
61
  },
56
62
  editor: {
@@ -450,6 +456,7 @@ export function CodeEditor({
450
456
  }), stylex.props(styles.root, focused && styles.rootFocused, xstyle), className, style),
451
457
  ...props,
452
458
  children: /*#__PURE__*/_jsxs("div", {
459
+ dir: "ltr",
453
460
  ...{
454
461
  className: "x78zum5 x98rzlu xysyzu8"
455
462
  },
@@ -468,7 +475,12 @@ export function CodeEditor({
468
475
  position: 'relative',
469
476
  flex: 1
470
477
  },
471
- children: [showPlaceholder && /*#__PURE__*/_jsx("div", {
478
+ children: [showPlaceholder &&
479
+ /*#__PURE__*/
480
+ // The placeholder is prose, not code: it takes its own text's
481
+ // direction, so an Arabic or Hebrew hint still reads correctly.
482
+ _jsx("div", {
483
+ dir: "auto",
472
484
  ...stylex.props(styles.placeholder, sizeStyle),
473
485
  children: placeholder
474
486
  }), /*#__PURE__*/_jsx("code", {
@@ -15,7 +15,7 @@
15
15
  *
16
16
  * SYNC: When modified, update these files to stay in sync:
17
17
  * - /packages/lab/src/CommentPin/CommentPin.doc.mjs
18
- * - /packages/lab/src/CommentPin/CommentPin.spec.md
18
+ * - /docs/specs/components/CommentPin/CommentPin.spec.md
19
19
  * - /packages/lab/src/CommentPin/CommentPin.test.tsx
20
20
  * - /packages/lab/src/CommentPin/index.ts
21
21
  * - /apps/storybook/stories/CommentPin.stories.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"CommentComposer.d.ts","sourceRoot":"","sources":["../../src/Comments/CommentComposer.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CommentComposer.d.ts","sourceRoot":"","sources":["../../src/Comments/CommentComposer.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAOL,KAAK,SAAS,EACd,KAAK,GAAG,EACT,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAGlD,OAAO,EAKL,KAAK,mBAAmB,EAEzB,MAAM,yBAAyB,CAAC;AAqBjC,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EACb,sBAAsB,EACtB,0BAA0B,EAC1B,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,SAAS,CAAC;AAkGjB,0DAA0D;AAC1D,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,sBAAsB,CAAC;IAChC,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,cAAc,EAAE,qBAAqB,CAAC;IACtC,cAAc,CAAC,EAAE,CACf,KAAK,EAAE,SAAS,IAAI,EAAE,KAEtB,SAAS,oBAAoB,EAAE,GAAG,OAAO,CAAC,SAAS,oBAAoB,EAAE,CAAC,CAAC;IAC7E,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACzC,aAAa,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC9C,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,qBAAqB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IACrD,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,SAAS,CAAC;IAClE,OAAO,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;IAC3B,SAAS,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC,CAAC;CACnE;AAED,kDAAkD;AAClD,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,aAAa,EACb,OAAO,EACP,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,cAAc,EACd,cAAc,EACd,YAAY,EACZ,KAAK,EACL,qBAAqB,EACrB,eAAe,EACf,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,cAAc,EACd,WAAW,EACX,cAAc,EACd,SAAS,EACT,OAAO,EACP,QAAQ,EAAE,YAAY,EACtB,KAAK,GACN,EAAE,wBAAwB,+BAkX1B;AAED,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAChD,SAAS,CAAC,WAAW,CAAC,EACtB,UAAU,GAAG,UAAU,CACxB;IACC,yCAAyC;IACzC,GAAG,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;IACvB,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,2EAA2E;IAC3E,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,qDAAqD;IACrD,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,2EAA2E;IAC3E,cAAc,CAAC,EAAE,qBAAqB,CAAC;IACvC;;;OAGG;IACH,cAAc,CAAC,EAAE,CACf,KAAK,EAAE,SAAS,IAAI,EAAE,KAEtB,SAAS,oBAAoB,EAAE,GAAG,OAAO,CAAC,SAAS,oBAAoB,EAAE,CAAC,CAAC;IAC7E,wDAAwD;IACxD,QAAQ,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC1C,6DAA6D;IAC7D,aAAa,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,2DAA2D;IAC3D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,KAAK,EACL,YAAoB,EACpB,KAAK,EACL,OAAO,EACP,MAAM,EACN,eAAe,EACf,cAAyB,EACzB,cAAc,EACd,QAAsB,EACtB,aAAiC,EACjC,cAAqB,EACrB,QAAQ,EACR,GAAG,EACH,GAAG,SAAS,EACb,EAAE,oBAAoB,+BAyBtB;yBA5Ce,eAAe"}
@@ -152,6 +152,11 @@ export function CommentComposerBase({
152
152
  latestValueRef.current = value;
153
153
  const isMountedRef = useRef(true);
154
154
  const actionPendingRef = useRef(false);
155
+ // The latest callback, read when reporting. An inline callback is new on
156
+ // every render; depending on it would re-run the unmount cleanup below on
157
+ // each render and leave the composer busy after its upload ends.
158
+ const onActionPendingChangeRef = useRef(onActionPendingChange);
159
+ onActionPendingChangeRef.current = onActionPendingChange;
155
160
  const isSubmittingRef = useRef(isSubmitting);
156
161
  isSubmittingRef.current = isSubmitting;
157
162
  const imageInputRef = useRef(null);
@@ -184,18 +189,21 @@ export function CommentComposerBase({
184
189
  }
185
190
  actionPendingRef.current = nextPending;
186
191
  setIsActionPending(nextPending);
187
- onActionPendingChange?.(nextPending);
188
- }, [onActionPendingChange]);
192
+ onActionPendingChangeRef.current?.(nextPending);
193
+ }, []);
194
+ // Tracks mount for the upload's async completion, and on unmount releases
195
+ // the host's pending-action hold, which the upload's finally skips once
196
+ // unmounted.
189
197
  useEffect(() => {
190
198
  isMountedRef.current = true;
191
199
  return () => {
192
200
  isMountedRef.current = false;
193
201
  if (actionPendingRef.current) {
194
202
  actionPendingRef.current = false;
195
- onActionPendingChange?.(false);
203
+ onActionPendingChangeRef.current?.(false);
196
204
  }
197
205
  };
198
- }, [onActionPendingChange]);
206
+ }, []);
199
207
  const uploadImages = async files => {
200
208
  if (onUploadImages == null || files.length === 0) {
201
209
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"DiffViewer.d.ts","sourceRoot":"","sources":["../../src/DiffViewer/DiffViewer.tsx"],"names":[],"mappings":"AAqCA,OAAO,EAQL,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,GAAG,EAET,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAGL,KAAK,YAAY,EAGlB,MAAM,8BAA8B,CAAC;AAOtC,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,iCAAiC,CAAC;AAqBzC,OAAO,KAAK,EACV,cAAc,EAGd,iBAAiB,EAElB,MAAM,aAAa,CAAC;AAGrB,uEAAuE;AACvE,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,OAAO,CAAC;AAErD;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAEpE;;;GAGG;AACH,MAAM,MAAM,yBAAyB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAEjE,MAAM,WAAW,eAAgB,SAAQ,SAAS,CAAC,cAAc,CAAC;IAChE,yCAAyC;IACzC,GAAG,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC1B;;;OAGG;IACH,IAAI,EAAE,cAAc,CAAC;IACrB;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IACjC;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IAC5C;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,eAAe,CAAC,EAAE,yBAAyB,CAAC;IAC5C;;;;;OAKG;IACH,eAAe,CAAC,EAAE,yBAAyB,CAAC;IAC5C;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,uEAAuE;IACvE,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,KAAK,IAAI,CAAC;IACnD;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,SAAS,CAAC;IAC7D;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,CAClB,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,KACjC,IAAI,CAAC;IACV,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B;;;OAGG;IACH,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACrC;AAi2BD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,EACzB,GAAG,EACH,IAAI,EACJ,QAAoB,EACpB,QAAQ,EAAE,YAAY,EACtB,aAAa,EACb,cAAqB,EACrB,SAAiB,EACjB,eAA2B,EAC3B,eAAwB,EACxB,mBAA0B,EAC1B,aAAoB,EACpB,WAAW,EAAE,eAAe,EAC5B,kBAA0B,EAC1B,iBAAiB,EACjB,SAAS,EACT,oBAAoB,EACpB,iBAAiB,EACjB,SAAS,EACT,WAAW,EACX,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,eAAe,+BAopBjB;yBA5qBe,UAAU"}
1
+ {"version":3,"file":"DiffViewer.d.ts","sourceRoot":"","sources":["../../src/DiffViewer/DiffViewer.tsx"],"names":[],"mappings":"AAwCA,OAAO,EAQL,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,GAAG,EAET,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAGL,KAAK,YAAY,EAGlB,MAAM,8BAA8B,CAAC;AAQtC,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,iCAAiC,CAAC;AAsBzC,OAAO,KAAK,EACV,cAAc,EAGd,iBAAiB,EAElB,MAAM,aAAa,CAAC;AAGrB,uEAAuE;AACvE,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,OAAO,CAAC;AAErD;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAEpE;;;GAGG;AACH,MAAM,MAAM,yBAAyB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAEjE,MAAM,WAAW,eAAgB,SAAQ,SAAS,CAAC,cAAc,CAAC;IAChE,yCAAyC;IACzC,GAAG,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC1B;;;OAGG;IACH,IAAI,EAAE,cAAc,CAAC;IACrB;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IACjC;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IAC5C;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,eAAe,CAAC,EAAE,yBAAyB,CAAC;IAC5C;;;;;OAKG;IACH,eAAe,CAAC,EAAE,yBAAyB,CAAC;IAC5C;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,uEAAuE;IACvE,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,KAAK,IAAI,CAAC;IACnD;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,SAAS,CAAC;IAC7D;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,CAClB,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,KACjC,IAAI,CAAC;IACV,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B;;;OAGG;IACH,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACrC;AAw/BD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,EACzB,GAAG,EACH,IAAI,EACJ,QAAoB,EACpB,QAAQ,EAAE,YAAY,EACtB,aAAa,EACb,cAAqB,EACrB,SAAiB,EACjB,eAA2B,EAC3B,eAAwB,EACxB,mBAA0B,EAC1B,aAAoB,EACpB,WAAW,EAAE,eAAe,EAC5B,kBAA0B,EAC1B,iBAAiB,EACjB,SAAS,EACT,oBAAoB,EACpB,iBAAiB,EACjB,SAAS,EACT,WAAW,EACX,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,eAAe,+BA6yBjB;yBAr0Be,UAAU"}
@@ -4,7 +4,7 @@
4
4
  * @file DiffViewer.tsx
5
5
  * @input Uses React, StyleX, theme tokens, the shared CodeBlock tokenizer and
6
6
  * syntax-theme slots (incl. the diff row slots), useScrollableArea,
7
- * Token, Icon, VisuallyHidden, and the i18n translator
7
+ * Token, Icon, Button, VisuallyHidden, and the i18n translator
8
8
  * @output Exports DiffViewer component, DiffViewerProps, DiffViewerViewMode,
9
9
  * DiffViewerChangeIndicator, DiffViewerInlineHighlight
10
10
  * @position Lab implementation; read-only, syntax-highlighted file diff for
@@ -12,6 +12,9 @@
12
12
  *
13
13
  * Renders one DiffViewerFile: a file header (path, status, +/- counts, a
14
14
  * caller slot, optional disclosure) over its hunks in unified or split view.
15
+ * A hunk marked `isReplacedBlock`, and a file with more than
16
+ * MAX_UNFOLDED_CHANGED_LINES other changed lines, show a labeled fold until
17
+ * the reader opens it.
15
18
  * Unified view is one table. Split view is two side-by-side panes (each an
16
19
  * ARIA table) whose rows share one grid's row tracks through `subgrid`, so a
17
20
  * row is as tall as its taller side while each pane scrolls sideways on its
@@ -39,12 +42,13 @@ import { Fragment, useId, useInsertionEffect, useMemo, useRef, useState } from '
39
42
  import * as stylex from '@stylexjs/stylex';
40
43
  import { ensureHighlightStyles, tokenize } from '@astryxdesign/core/CodeBlock';
41
44
  import { useScrollableArea } from '@astryxdesign/core/hooks';
45
+ import { Button } from '@astryxdesign/core/Button';
42
46
  import { useTranslator } from '@astryxdesign/core/i18n';
43
47
  import { Icon } from '@astryxdesign/core/Icon';
44
48
  import { SyntaxTheme } from '@astryxdesign/core/theme/syntax';
45
49
  import '@astryxdesign/core/theme/tokens.stylex';
46
50
  import { borderVars, colorVars, durationVars, easeVars, fontWeightVars, radiusVars, spacingVars, typeScaleVars, typographyVars } from '@astryxdesign/core/theme/tokens.stylex';
47
- import { focusOutlineProps, mergeProps, rtlStyles, themeProps } from '@astryxdesign/core/utils';
51
+ import { focusOutlineProps, isRenderable, mergeProps, rtlStyles, themeProps } from '@astryxdesign/core/utils';
48
52
  import { Token } from '@astryxdesign/core/Token';
49
53
  import { VisuallyHidden } from '@astryxdesign/core/VisuallyHidden';
50
54
  import { diffInlineRanges } from "./inlineDiff.js";
@@ -134,6 +138,17 @@ function inferLanguage(path, codeLanguages) {
134
138
  // Row model
135
139
  // ---------------------------------------------------------------------------
136
140
 
141
+ /** The control that folds a replaced block's changed lines. */
142
+
143
+ /**
144
+ * The caller's annotation for a line, or null when it renders nothing. An
145
+ * annotation row is drawn only for a note that renders, so
146
+ * `hasThread && <Thread />` adds no empty row.
147
+ */
148
+ function lineAnnotation(renderLineAnnotation, ref) {
149
+ const note = renderLineAnnotation(ref);
150
+ return isRenderable(note) ? note : null;
151
+ }
137
152
  function lineRef(line, hunkIndex) {
138
153
  if (line.kind === 'removed') {
139
154
  return line.oldLineNumber == null ? null : {
@@ -181,44 +196,129 @@ function addInlineEmphasis(cells, granularity) {
181
196
  }
182
197
 
183
198
  /**
184
- * Tokenize each hunk's old and new side as continuous text, so constructs
185
- * that span lines (block comments, template strings) keep their paint within
186
- * the hunk, then hand each line the tokens of the side it renders from.
199
+ * Most changed lines a file shows before the reader asks for them. Rows are
200
+ * not virtualized, and each row costs render time. A file with more changed
201
+ * lines starts folded. Lines in replaced blocks do not count, because those
202
+ * blocks fold on their own.
187
203
  */
188
- function buildCells(file, language, inlineHighlight) {
189
- return file.hunks.map((hunk, hunkIndex) => {
190
- const oldLines = hunk.lines.filter(line => line.kind !== 'added');
191
- const newLines = hunk.lines.filter(line => line.kind !== 'removed');
192
- const highlight = language !== 'plaintext';
193
- const oldTokens = highlight ? tokenize(oldLines.map(line => line.content).join('\n'), language) : [];
194
- const newTokens = highlight ? tokenize(newLines.map(line => line.content).join('\n'), language) : [];
195
- let oldIndex = 0;
196
- let newIndex = 0;
197
- const cells = [];
198
- for (const line of hunk.lines) {
199
- let tokens;
200
- if (line.kind === 'removed') {
201
- tokens = oldTokens[oldIndex++] ?? [];
202
- } else if (line.kind === 'added') {
203
- tokens = newTokens[newIndex++] ?? [];
204
- } else {
205
- oldIndex++;
206
- tokens = newTokens[newIndex++] ?? [];
204
+ const MAX_UNFOLDED_CHANGED_LINES = 2000;
205
+
206
+ /**
207
+ * Hunks with an annotation on one of their lines. A fold that would hide such
208
+ * a line starts open, so a review comment is never hidden.
209
+ */
210
+ function findAnnotatedHunks(file, renderLineAnnotation) {
211
+ const hunks = new Set();
212
+ if (renderLineAnnotation == null) {
213
+ return hunks;
214
+ }
215
+ file.hunks.forEach((hunk, hunkIndex) => {
216
+ const isAnnotated = hunk.lines.some(line => {
217
+ // A replaced block's fold keeps its context lines in view.
218
+ if (hunk.isReplacedBlock === true && line.kind === 'context') {
219
+ return false;
207
220
  }
208
221
  const ref = lineRef(line, hunkIndex);
209
- if (ref != null) {
210
- cells.push({
211
- line,
212
- ref,
213
- tokens,
214
- emphasis: []
215
- });
222
+ return ref != null && lineAnnotation(renderLineAnnotation, ref) != null;
223
+ });
224
+ if (isAnnotated) {
225
+ hunks.add(hunkIndex);
226
+ }
227
+ });
228
+ return hunks;
229
+ }
230
+
231
+ /**
232
+ * Tokenize a run of hunk lines with its old and new side each as continuous
233
+ * text, so constructs that span lines (block comments, template strings) keep
234
+ * their paint within the run, and give each line the tokens of the side it
235
+ * renders from.
236
+ */
237
+ function tokenizeRun(lines, language) {
238
+ if (language === 'plaintext') {
239
+ return lines.map(() => []);
240
+ }
241
+ const sideText = kind => lines.filter(line => line.kind !== kind).map(line => line.content).join('\n');
242
+ const oldTokens = tokenize(sideText('added'), language);
243
+ const newTokens = tokenize(sideText('removed'), language);
244
+ let oldIndex = 0;
245
+ let newIndex = 0;
246
+ return lines.map(line => {
247
+ if (line.kind === 'removed') {
248
+ return oldTokens[oldIndex++] ?? [];
249
+ }
250
+ if (line.kind === 'added') {
251
+ return newTokens[newIndex++] ?? [];
252
+ }
253
+ oldIndex++;
254
+ return newTokens[newIndex++] ?? [];
255
+ });
256
+ }
257
+
258
+ /**
259
+ * Build each hunk's line cells. A folded replaced block keeps only the
260
+ * context around it, and its changed lines are not tokenized.
261
+ */
262
+ function buildCells(file, language, inlineHighlight, expandedBlocks) {
263
+ return file.hunks.map((hunk, hunkIndex) => {
264
+ const cells = [];
265
+ const addRun = lines => {
266
+ const tokens = tokenizeRun(lines, language);
267
+ lines.forEach((line, i) => {
268
+ const ref = lineRef(line, hunkIndex);
269
+ if (ref != null) {
270
+ cells.push({
271
+ line,
272
+ ref,
273
+ tokens: tokens[i],
274
+ emphasis: []
275
+ });
276
+ }
277
+ });
278
+ };
279
+ const changeStart = hunk.isReplacedBlock === true ? hunk.lines.findIndex(line => line.kind !== 'context') : -1;
280
+ if (changeStart === -1) {
281
+ addRun(hunk.lines);
282
+ if (inlineHighlight !== 'none') {
283
+ addInlineEmphasis(cells, inlineHighlight);
216
284
  }
285
+ return {
286
+ cells,
287
+ fold: null
288
+ };
217
289
  }
218
- if (inlineHighlight !== 'none') {
219
- addInlineEmphasis(cells, inlineHighlight);
290
+
291
+ // A replaced block's removed and added lines are not edits of each
292
+ // other, so they get no inline highlight.
293
+ const isExpanded = expandedBlocks.has(hunkIndex);
294
+ const changedLines = hunk.lines.filter(line => line.kind !== 'context').length;
295
+ if (isExpanded) {
296
+ addRun(hunk.lines);
297
+ const at = cells.findIndex(cell => cell.line.kind !== 'context');
298
+ return {
299
+ cells,
300
+ fold: {
301
+ hunkIndex,
302
+ at,
303
+ changedLines,
304
+ isExpanded
305
+ }
306
+ };
220
307
  }
221
- return cells;
308
+ // Leading and trailing context are tokenized apart: the hidden lines
309
+ // between them can open or close a comment or a string.
310
+ addRun(hunk.lines.slice(0, changeStart));
311
+ const at = cells.length;
312
+ addRun(hunk.lines.slice(changeStart + changedLines));
313
+ return {
314
+ cells,
315
+ fold: {
316
+ hunkIndex,
317
+ at,
318
+ changedLines,
319
+ isExpanded
320
+ }
321
+ };
222
322
  });
223
323
  }
224
324
  function buildUnifiedRows(file, cells) {
@@ -229,13 +329,31 @@ function buildUnifiedRows(file, cells) {
229
329
  key: `h${h}`,
230
330
  header: hunk.header
231
331
  });
232
- cells[h].forEach((cell, i) => {
332
+ const {
333
+ cells: hunkCells,
334
+ fold
335
+ } = cells[h];
336
+ hunkCells.forEach((cell, i) => {
337
+ if (fold != null && fold.at === i) {
338
+ rows.push({
339
+ type: 'fold',
340
+ key: `h${h}f`,
341
+ fold
342
+ });
343
+ }
233
344
  rows.push({
234
345
  type: 'line',
235
346
  key: `h${h}l${i}`,
236
347
  cell
237
348
  });
238
349
  });
350
+ if (fold != null && fold.at === hunkCells.length) {
351
+ rows.push({
352
+ type: 'fold',
353
+ key: `h${h}f`,
354
+ fold
355
+ });
356
+ }
239
357
  });
240
358
  return rows;
241
359
  }
@@ -252,9 +370,20 @@ function buildSplitRows(file, cells) {
252
370
  key: `h${h}`,
253
371
  header: hunk.header
254
372
  });
255
- const hunkCells = cells[h];
373
+ const {
374
+ cells: hunkCells,
375
+ fold
376
+ } = cells[h];
256
377
  let i = 0;
257
378
  while (i < hunkCells.length) {
379
+ // The fold sits where a run of changes starts or ends, never inside one.
380
+ if (fold != null && fold.at === i) {
381
+ rows.push({
382
+ type: 'fold',
383
+ key: `h${h}f`,
384
+ fold
385
+ });
386
+ }
258
387
  const cell = hunkCells[i];
259
388
  if (cell.line.kind === 'context') {
260
389
  rows.push({
@@ -284,6 +413,13 @@ function buildSplitRows(file, cells) {
284
413
  });
285
414
  }
286
415
  }
416
+ if (fold != null && fold.at === hunkCells.length) {
417
+ rows.push({
418
+ type: 'fold',
419
+ key: `h${h}f`,
420
+ fold
421
+ });
422
+ }
287
423
  });
288
424
  return rows;
289
425
  }
@@ -524,6 +660,7 @@ const styles = {
524
660
  khDVqt: "x126k92a",
525
661
  kTgw9: "x1lldw8n",
526
662
  kHjlTd: "xj0a0fe",
663
+ "--_astryx-text-overflow-wrap": "x5ujdhh",
527
664
  $$css: true
528
665
  },
529
666
  added: {
@@ -763,9 +900,53 @@ export function DiffViewer({
763
900
  }
764
901
  onCollapsedChange?.(next);
765
902
  };
766
- const cells = useMemo(() => buildCells(file, language, inlineHighlight), [file, language, inlineHighlight]);
767
- const unifiedRows = useMemo(() => viewMode === 'unified' ? buildUnifiedRows(file, cells) : [], [viewMode, file, cells]);
768
- const splitRows = useMemo(() => viewMode === 'split' ? buildSplitRows(file, cells) : [], [viewMode, file, cells]);
903
+ let unfoldedChangedLines = 0;
904
+ let hasReplacedBlock = false;
905
+ for (const hunk of file.hunks) {
906
+ if (hunk.isReplacedBlock === true) {
907
+ hasReplacedBlock = true;
908
+ continue;
909
+ }
910
+ for (const line of hunk.lines) {
911
+ if (line.kind !== 'context') {
912
+ unfoldedChangedLines++;
913
+ }
914
+ }
915
+ }
916
+ const isLargeFile = unfoldedChangedLines > MAX_UNFOLDED_CHANGED_LINES;
917
+ const annotatedHunks = isLargeFile || hasReplacedBlock ? findAnnotatedHunks(file, renderLineAnnotation) : null;
918
+
919
+ // A large file and a replaced block start folded unless a line they would
920
+ // hide is annotated. The reader's toggles are kept per file; a new file
921
+ // starts over.
922
+ const [fileToggle, setFileToggle] = useState(null);
923
+ const isFileFolded = isLargeFile && !(fileToggle?.file === file ? fileToggle.isExpanded : (annotatedHunks?.size ?? 0) > 0);
924
+ const toggleFile = () => {
925
+ setFileToggle({
926
+ file,
927
+ isExpanded: isFileFolded
928
+ });
929
+ };
930
+ const [blockToggles, setBlockToggles] = useState(null);
931
+ const toggles = blockToggles?.file === file ? blockToggles.isExpanded : null;
932
+ const isBlockExpanded = hunkIndex => toggles?.get(hunkIndex) ?? annotatedHunks?.has(hunkIndex) ?? false;
933
+ const toggleBlock = hunkIndex => {
934
+ const next = new Map(toggles);
935
+ next.set(hunkIndex, !isBlockExpanded(hunkIndex));
936
+ setBlockToggles({
937
+ file,
938
+ isExpanded: next
939
+ });
940
+ };
941
+ // A key of the open blocks keeps the set stable across renders, so the
942
+ // cells are built again only when a block opens or closes.
943
+ const expandedKey = file.hunks.flatMap((hunk, hunkIndex) => hunk.isReplacedBlock === true && isBlockExpanded(hunkIndex) ? [hunkIndex] : []).join(',');
944
+ const expandedBlocks = useMemo(() => new Set(expandedKey === '' ? [] : expandedKey.split(',').map(Number)), [expandedKey]);
945
+
946
+ // A folded file builds no cells, so its lines are not tokenized.
947
+ const cells = useMemo(() => isFileFolded ? [] : buildCells(file, language, inlineHighlight, expandedBlocks), [isFileFolded, file, language, inlineHighlight, expandedBlocks]);
948
+ const unifiedRows = useMemo(() => viewMode === 'unified' && !isFileFolded ? buildUnifiedRows(file, cells) : [], [viewMode, isFileFolded, file, cells]);
949
+ const splitRows = useMemo(() => viewMode === 'split' && !isFileFolded ? buildSplitRows(file, cells) : [], [viewMode, isFileFolded, file, cells]);
769
950
  let addedCount = 0;
770
951
  let removedCount = 0;
771
952
  let maxLineNumber = 1;
@@ -874,6 +1055,28 @@ export function DiffViewer({
874
1055
  children: cell != null && buildSpans(cell.line.content, cell.tokens, cell.emphasis, [styles.change, kind === 'added' ? styles.changeAdded : styles.changeRemoved])
875
1056
  }, key);
876
1057
  };
1058
+ const renderFoldControl = (changedLines, isExpanded, onToggle, isSticky) => /*#__PURE__*/_jsxs("span", {
1059
+ ...{
1060
+ 0: {
1061
+ className: "x3nfvp2 x6s0dn4 x1a02dak xjcht0a"
1062
+ },
1063
+ 1: {
1064
+ className: "x3nfvp2 x6s0dn4 x1a02dak xjcht0a x7wzq59 x1ybfrjj"
1065
+ }
1066
+ }[!!isSticky << 0],
1067
+ children: [/*#__PURE__*/_jsx("span", {
1068
+ children: t('@astryx.diffViewer.largeChange', {
1069
+ count: changedLines
1070
+ })
1071
+ }), /*#__PURE__*/_jsx(Button, {
1072
+ size: "sm",
1073
+ variant: "secondary",
1074
+ label: isExpanded ? t('@astryx.diffViewer.hideFullDiff') : t('@astryx.diffViewer.showFullDiff'),
1075
+ "aria-expanded": isExpanded,
1076
+ onClick: onToggle
1077
+ })]
1078
+ });
1079
+ const renderFold = fold => renderFoldControl(fold.changedLines, fold.isExpanded, () => toggleBlock(fold.hunkIndex), isPinned);
877
1080
  const renderHunkText = header => /*#__PURE__*/_jsx("span", {
878
1081
  ...{
879
1082
  0: {
@@ -885,16 +1088,7 @@ export function DiffViewer({
885
1088
  }[!!isPinned << 0],
886
1089
  children: header
887
1090
  });
888
-
889
- // `null`, `undefined` and booleans render nothing in React, so they draw no
890
- // row either; that keeps `hasThread && <Thread />` from adding empty rows.
891
- const annotationFor = cell => {
892
- if (cell == null || renderLineAnnotation == null) {
893
- return null;
894
- }
895
- const note = renderLineAnnotation(cell.ref);
896
- return typeof note === 'boolean' ? null : note;
897
- };
1091
+ const annotationFor = cell => cell == null || renderLineAnnotation == null ? null : lineAnnotation(renderLineAnnotation, cell.ref);
898
1092
 
899
1093
  // Name every column so a screen reader moving across a row hears which
900
1094
  // number is which (and, in split view, which side's code it is in).
@@ -954,6 +1148,17 @@ export function DiffViewer({
954
1148
  })
955
1149
  }, row.key);
956
1150
  }
1151
+ if (row.type === 'fold') {
1152
+ return /*#__PURE__*/_jsx("tr", {
1153
+ children: /*#__PURE__*/_jsx("td", {
1154
+ colSpan: columns,
1155
+ ...{
1156
+ className: "xce4md1 xrrkdod x1yc453h x11ds9ch xv1l7n4 x9ynric x141an7d x1ltkj2j xszpfcc xykt701 xgtxpae"
1157
+ },
1158
+ children: renderFold(row.fold)
1159
+ })
1160
+ }, row.key);
1161
+ }
957
1162
  const note = annotationFor(row.cell);
958
1163
  return /*#__PURE__*/_jsxs(Fragment, {
959
1164
  children: [/*#__PURE__*/_jsxs("tr", {
@@ -966,7 +1171,7 @@ export function DiffViewer({
966
1171
  },
967
1172
  children: /*#__PURE__*/_jsx("div", {
968
1173
  ...{
969
- className: "x1q06l6i xj0a0fe x41o525"
1174
+ className: "x1q06l6i xj0a0fe x5ujdhh x41o525"
970
1175
  },
971
1176
  children: note
972
1177
  })
@@ -1020,7 +1225,7 @@ export function DiffViewer({
1020
1225
  };
1021
1226
  };
1022
1227
  const renderSplit = () => {
1023
- const items = splitRows.map(row => row.type === 'hunk' ? {
1228
+ const items = splitRows.map(row => row.type !== 'pair' ? {
1024
1229
  row,
1025
1230
  oldNote: null,
1026
1231
  newNote: null
@@ -1032,7 +1237,7 @@ export function DiffViewer({
1032
1237
  newNote: annotationFor(row.new)
1033
1238
  });
1034
1239
  const hasSizer = !isWrapped;
1035
- const sizerText = hasSizer ? widestLine(cells.flatMap(hunkCells => hunkCells.map(cell => cell.line.content))) : '';
1240
+ const sizerText = hasSizer ? widestLine(cells.flatMap(hunk => hunk.cells.map(cell => cell.line.content))) : '';
1036
1241
  const rowCount = 1 + (hasSizer ? 1 : 0) + items.reduce((count, item) => count + 1 + (item.oldNote != null || item.newNote != null ? 1 : 0), 0);
1037
1242
  const columns = `${hasLineNumbers ? `${gutter} ` : ''}${markerWidth} ${isWrapped ? 'minmax(0, 1fr)' : '1fr'}`;
1038
1243
  const markerPin = hasLineNumbers ? gutter : '0px';
@@ -1065,7 +1270,7 @@ export function DiffViewer({
1065
1270
  className: "xu0wf1k xrrkdod x1yc453h x11ds9ch xt5rlkq x1sdyfia xszpfcc xykt701 xgtxpae x1osaytk"
1066
1271
  },
1067
1272
  1: {
1068
- className: "xu0wf1k xrrkdod x1yc453h x11ds9ch xt5rlkq xszpfcc xykt701 xgtxpae x1osaytk x126k92a x1lldw8n xj0a0fe"
1273
+ className: "xu0wf1k xrrkdod x1yc453h x11ds9ch xt5rlkq xszpfcc xykt701 xgtxpae x1osaytk x126k92a x1lldw8n xj0a0fe x5ujdhh"
1069
1274
  }
1070
1275
  }[!!isWrapped << 0],
1071
1276
  children: isOld ? renderHunkText(row.header) : /*#__PURE__*/_jsxs(_Fragment, {
@@ -1083,6 +1288,41 @@ export function DiffViewer({
1083
1288
  }, row.key));
1084
1289
  continue;
1085
1290
  }
1291
+ if (row.type === 'fold') {
1292
+ // Only the old side holds the control; the new side keeps its row
1293
+ // as tall and gives its own table's readers the label.
1294
+ rows.push(/*#__PURE__*/_jsx("div", {
1295
+ role: "row",
1296
+ ...{
1297
+ className: "xrvj5dj x1osaytk x4w6khx"
1298
+ },
1299
+ children: /*#__PURE__*/_jsx("div", {
1300
+ role: "cell",
1301
+ ...{
1302
+ 0: {
1303
+ className: "xce4md1 xrrkdod x1yc453h x11ds9ch xv1l7n4 x9ynric x141an7d x1ltkj2j xszpfcc xykt701 xgtxpae x1osaytk"
1304
+ },
1305
+ 1: {
1306
+ className: "xce4md1 xrrkdod x1yc453h x11ds9ch xv1l7n4 x9ynric x141an7d x1ltkj2j xszpfcc xykt701 xgtxpae x1osaytk x126k92a x1lldw8n xj0a0fe x5ujdhh"
1307
+ }
1308
+ }[!!isWrapped << 0],
1309
+ children: isOld ? renderFold(row.fold) : /*#__PURE__*/_jsxs(_Fragment, {
1310
+ children: [/*#__PURE__*/_jsx("span", {
1311
+ "aria-hidden": "true",
1312
+ ...{
1313
+ className: "xlshs6z"
1314
+ },
1315
+ children: renderFold(row.fold)
1316
+ }), /*#__PURE__*/_jsx(VisuallyHidden, {
1317
+ children: t('@astryx.diffViewer.largeChange', {
1318
+ count: row.fold.changedLines
1319
+ })
1320
+ })]
1321
+ })
1322
+ })
1323
+ }, row.key));
1324
+ continue;
1325
+ }
1086
1326
  const cell = isOld ? row.old : row.new;
1087
1327
  rows.push(/*#__PURE__*/_jsxs("div", {
1088
1328
  role: "row"
@@ -1109,7 +1349,7 @@ export function DiffViewer({
1109
1349
  },
1110
1350
  children: note != null && /*#__PURE__*/_jsx("div", {
1111
1351
  ...{
1112
- className: "x1q06l6i xj0a0fe x41o525"
1352
+ className: "x1q06l6i xj0a0fe x5ujdhh x41o525"
1113
1353
  },
1114
1354
  children: note
1115
1355
  })
@@ -1148,7 +1388,6 @@ export function DiffViewer({
1148
1388
  children: [renderSide('old'), renderSide('new')]
1149
1389
  });
1150
1390
  };
1151
- const table = viewMode === 'split' ? renderSplit() : renderUnified();
1152
1391
  let body;
1153
1392
  if (file.isBinary) {
1154
1393
  body = /*#__PURE__*/_jsx("div", {
@@ -1165,17 +1404,27 @@ export function DiffViewer({
1165
1404
  children: t('@astryx.diffViewer.noChanges')
1166
1405
  });
1167
1406
  } else {
1168
- body = /*#__PURE__*/_jsx("div", {
1169
- ...withoutInlineOverscroll(getViewportProps({
1407
+ // The fold row stays first whether the file is folded or not, so its
1408
+ // button keeps focus when it toggles.
1409
+ body = /*#__PURE__*/_jsxs(_Fragment, {
1410
+ children: [isLargeFile && /*#__PURE__*/_jsx("div", {
1411
+ "data-diff-fold": "",
1170
1412
  ...{
1171
- className: "xeuugli xzt5al7"
1413
+ className: "xce4md1 xrrkdod x1yc453h x11ds9ch xv1l7n4 x9ynric x141an7d x1ltkj2j xszpfcc xykt701 xgtxpae"
1172
1414
  },
1173
- style: scrollStyle
1174
- })),
1175
- children: /*#__PURE__*/_jsx("div", {
1176
- ...getContentProps({}),
1177
- children: table
1178
- })
1415
+ children: renderFoldControl(unfoldedChangedLines, !isFileFolded, toggleFile, false)
1416
+ }), !isFileFolded && /*#__PURE__*/_jsx("div", {
1417
+ ...withoutInlineOverscroll(getViewportProps({
1418
+ ...{
1419
+ className: "xeuugli xzt5al7"
1420
+ },
1421
+ style: scrollStyle
1422
+ })),
1423
+ children: /*#__PURE__*/_jsx("div", {
1424
+ ...getContentProps({}),
1425
+ children: viewMode === 'split' ? renderSplit() : renderUnified()
1426
+ })
1427
+ })]
1179
1428
  });
1180
1429
  }
1181
1430