@pierre/diffs 1.3.0-beta.9 → 1.3.0-rc.2

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 (241) hide show
  1. package/README.md +9 -0
  2. package/dist/components/CodeView.d.ts +12 -13
  3. package/dist/components/CodeView.d.ts.map +1 -1
  4. package/dist/components/CodeView.js +50 -58
  5. package/dist/components/CodeView.js.map +1 -1
  6. package/dist/components/File.d.ts +10 -3
  7. package/dist/components/File.d.ts.map +1 -1
  8. package/dist/components/File.js +41 -10
  9. package/dist/components/File.js.map +1 -1
  10. package/dist/components/FileDiff.d.ts +37 -6
  11. package/dist/components/FileDiff.d.ts.map +1 -1
  12. package/dist/components/FileDiff.js +202 -19
  13. package/dist/components/FileDiff.js.map +1 -1
  14. package/dist/components/UnresolvedFile.d.ts.map +1 -1
  15. package/dist/components/UnresolvedFile.js +7 -2
  16. package/dist/components/UnresolvedFile.js.map +1 -1
  17. package/dist/components/VirtualizedFile.d.ts +2 -1
  18. package/dist/components/VirtualizedFile.d.ts.map +1 -1
  19. package/dist/components/VirtualizedFile.js +9 -5
  20. package/dist/components/VirtualizedFile.js.map +1 -1
  21. package/dist/components/VirtualizedFileDiff.d.ts +14 -0
  22. package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
  23. package/dist/components/VirtualizedFileDiff.js +61 -3
  24. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  25. package/dist/components/Virtualizer.d.ts +14 -2
  26. package/dist/components/Virtualizer.d.ts.map +1 -1
  27. package/dist/components/Virtualizer.js +27 -2
  28. package/dist/components/Virtualizer.js.map +1 -1
  29. package/dist/edit/index.d.ts +5 -0
  30. package/dist/edit/index.js +3 -0
  31. package/dist/editor/command.d.ts +1 -1
  32. package/dist/editor/command.d.ts.map +1 -1
  33. package/dist/editor/command.js +11 -0
  34. package/dist/editor/command.js.map +1 -1
  35. package/dist/editor/editStack.d.ts +6 -2
  36. package/dist/editor/editStack.d.ts.map +1 -1
  37. package/dist/editor/editStack.js +53 -12
  38. package/dist/editor/editStack.js.map +1 -1
  39. package/dist/editor/editor.d.ts +51 -23
  40. package/dist/editor/editor.d.ts.map +1 -1
  41. package/dist/editor/editor.js +977 -241
  42. package/dist/editor/editor.js.map +1 -1
  43. package/dist/editor/editor2.js +1 -1
  44. package/dist/editor/languages.d.ts +26 -0
  45. package/dist/editor/languages.d.ts.map +1 -0
  46. package/dist/editor/languages.js +363 -0
  47. package/dist/editor/languages.js.map +1 -0
  48. package/dist/editor/lineAnnotations.d.ts.map +1 -1
  49. package/dist/editor/lineAnnotations.js +25 -26
  50. package/dist/editor/lineAnnotations.js.map +1 -1
  51. package/dist/editor/marker.d.ts +5 -3
  52. package/dist/editor/marker.d.ts.map +1 -1
  53. package/dist/editor/marker.js +111 -91
  54. package/dist/editor/marker.js.map +1 -1
  55. package/dist/editor/matchBrackets.d.ts +3 -2
  56. package/dist/editor/matchBrackets.d.ts.map +1 -1
  57. package/dist/editor/matchBrackets.js +2 -10
  58. package/dist/editor/matchBrackets.js.map +1 -1
  59. package/dist/editor/pieceTable.d.ts +2 -1
  60. package/dist/editor/pieceTable.d.ts.map +1 -1
  61. package/dist/editor/pieceTable.js +103 -39
  62. package/dist/editor/pieceTable.js.map +1 -1
  63. package/dist/editor/platform.js +2 -0
  64. package/dist/editor/platform.js.map +1 -1
  65. package/dist/editor/popover.d.ts +25 -5
  66. package/dist/editor/popover.d.ts.map +1 -1
  67. package/dist/editor/popover.js +60 -14
  68. package/dist/editor/popover.js.map +1 -1
  69. package/dist/editor/searchPanel.d.ts.map +1 -1
  70. package/dist/editor/searchPanel.js +9 -2
  71. package/dist/editor/searchPanel.js.map +1 -1
  72. package/dist/editor/selection.d.ts +31 -11
  73. package/dist/editor/selection.d.ts.map +1 -1
  74. package/dist/editor/selection.js +198 -70
  75. package/dist/editor/selection.js.map +1 -1
  76. package/dist/editor/selectionAction.d.ts +4 -3
  77. package/dist/editor/selectionAction.d.ts.map +1 -1
  78. package/dist/editor/selectionAction.js +9 -5
  79. package/dist/editor/selectionAction.js.map +1 -1
  80. package/dist/editor/stateStorage.d.ts +15 -0
  81. package/dist/editor/stateStorage.d.ts.map +1 -0
  82. package/dist/editor/stateStorage.js +65 -0
  83. package/dist/editor/stateStorage.js.map +1 -0
  84. package/dist/editor/textDocument.d.ts +12 -77
  85. package/dist/editor/textDocument.d.ts.map +1 -1
  86. package/dist/editor/textDocument.js +76 -34
  87. package/dist/editor/textDocument.js.map +1 -1
  88. package/dist/editor/textMeasure.d.ts +7 -12
  89. package/dist/editor/textMeasure.d.ts.map +1 -1
  90. package/dist/editor/textMeasure.js +40 -32
  91. package/dist/editor/textMeasure.js.map +1 -1
  92. package/dist/editor/{tokenzier.d.ts → tokenizer.d.ts} +3 -3
  93. package/dist/editor/tokenizer.d.ts.map +1 -0
  94. package/dist/editor/{tokenzier.js → tokenizer.js} +170 -50
  95. package/dist/editor/tokenizer.js.map +1 -0
  96. package/dist/editor/utils.js +1 -1
  97. package/dist/editor/utils.js.map +1 -1
  98. package/dist/index.d.ts +4 -3
  99. package/dist/index.js +6 -5
  100. package/dist/managers/InteractionManager.d.ts +18 -7
  101. package/dist/managers/InteractionManager.d.ts.map +1 -1
  102. package/dist/managers/InteractionManager.js +154 -99
  103. package/dist/managers/InteractionManager.js.map +1 -1
  104. package/dist/managers/UniversalRenderingManager.d.ts.map +1 -1
  105. package/dist/managers/UniversalRenderingManager.js +1 -2
  106. package/dist/managers/UniversalRenderingManager.js.map +1 -1
  107. package/dist/react/CodeView.d.ts +13 -13
  108. package/dist/react/CodeView.d.ts.map +1 -1
  109. package/dist/react/CodeView.js +37 -28
  110. package/dist/react/CodeView.js.map +1 -1
  111. package/dist/react/EditContext.d.ts +20 -0
  112. package/dist/react/EditContext.d.ts.map +1 -0
  113. package/dist/react/EditContext.js +20 -0
  114. package/dist/react/EditContext.js.map +1 -0
  115. package/dist/react/File.d.ts +2 -1
  116. package/dist/react/File.d.ts.map +1 -1
  117. package/dist/react/File.js +3 -2
  118. package/dist/react/File.js.map +1 -1
  119. package/dist/react/FileDiff.d.ts +2 -2
  120. package/dist/react/FileDiff.d.ts.map +1 -1
  121. package/dist/react/FileDiff.js +3 -2
  122. package/dist/react/FileDiff.js.map +1 -1
  123. package/dist/react/MultiFileDiff.d.ts +2 -2
  124. package/dist/react/MultiFileDiff.d.ts.map +1 -1
  125. package/dist/react/MultiFileDiff.js +3 -2
  126. package/dist/react/MultiFileDiff.js.map +1 -1
  127. package/dist/react/PatchDiff.d.ts +2 -2
  128. package/dist/react/PatchDiff.d.ts.map +1 -1
  129. package/dist/react/PatchDiff.js +3 -2
  130. package/dist/react/PatchDiff.js.map +1 -1
  131. package/dist/react/UnresolvedFile.d.ts +1 -1
  132. package/dist/react/UnresolvedFile.js.map +1 -1
  133. package/dist/react/index.d.ts +4 -4
  134. package/dist/react/index.js +3 -3
  135. package/dist/react/types.d.ts +9 -1
  136. package/dist/react/types.d.ts.map +1 -1
  137. package/dist/react/utils/useFileDiffInstance.d.ts +5 -3
  138. package/dist/react/utils/useFileDiffInstance.d.ts.map +1 -1
  139. package/dist/react/utils/useFileDiffInstance.js +24 -34
  140. package/dist/react/utils/useFileDiffInstance.js.map +1 -1
  141. package/dist/react/utils/useFileInstance.d.ts +5 -3
  142. package/dist/react/utils/useFileInstance.d.ts.map +1 -1
  143. package/dist/react/utils/useFileInstance.js +23 -33
  144. package/dist/react/utils/useFileInstance.js.map +1 -1
  145. package/dist/react/utils/useUnresolvedFileInstance.js +4 -4
  146. package/dist/react/utils/useUnresolvedFileInstance.js.map +1 -1
  147. package/dist/renderers/DiffHunksRenderer.d.ts +41 -1
  148. package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
  149. package/dist/renderers/DiffHunksRenderer.js +167 -29
  150. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  151. package/dist/renderers/FileRenderer.d.ts +20 -1
  152. package/dist/renderers/FileRenderer.d.ts.map +1 -1
  153. package/dist/renderers/FileRenderer.js +119 -29
  154. package/dist/renderers/FileRenderer.js.map +1 -1
  155. package/dist/ssr/index.d.ts +2 -2
  156. package/dist/ssr/preloadDiffs.d.ts.map +1 -1
  157. package/dist/ssr/preloadDiffs.js +3 -1
  158. package/dist/ssr/preloadDiffs.js.map +1 -1
  159. package/dist/ssr/preloadFile.d.ts.map +1 -1
  160. package/dist/ssr/preloadFile.js +2 -0
  161. package/dist/ssr/preloadFile.js.map +1 -1
  162. package/dist/style.js +1 -1
  163. package/dist/types.d.ts +192 -42
  164. package/dist/types.d.ts.map +1 -1
  165. package/dist/utils/annotationHelpers.d.ts +20 -0
  166. package/dist/utils/annotationHelpers.d.ts.map +1 -0
  167. package/dist/utils/annotationHelpers.js +29 -0
  168. package/dist/utils/annotationHelpers.js.map +1 -0
  169. package/dist/utils/computeFileOffsets.d.ts.map +1 -1
  170. package/dist/utils/computeFileOffsets.js +34 -5
  171. package/dist/utils/computeFileOffsets.js.map +1 -1
  172. package/dist/utils/editSessionHunks.d.ts +66 -0
  173. package/dist/utils/editSessionHunks.d.ts.map +1 -0
  174. package/dist/utils/editSessionHunks.js +437 -0
  175. package/dist/utils/editSessionHunks.js.map +1 -0
  176. package/dist/utils/getDiffHunksRendererOptions.d.ts.map +1 -1
  177. package/dist/utils/getDiffHunksRendererOptions.js +2 -1
  178. package/dist/utils/getDiffHunksRendererOptions.js.map +1 -1
  179. package/dist/utils/getFileRendererOptions.d.ts.map +1 -1
  180. package/dist/utils/getFileRendererOptions.js +2 -1
  181. package/dist/utils/getFileRendererOptions.js.map +1 -1
  182. package/dist/utils/getHunkSideBoundaries.d.ts +7 -0
  183. package/dist/utils/getHunkSideBoundaries.d.ts.map +1 -0
  184. package/dist/utils/getHunkSideBoundaries.js +12 -0
  185. package/dist/utils/getHunkSideBoundaries.js.map +1 -0
  186. package/dist/utils/getTotalLineCountFromHunks.d.ts.map +1 -1
  187. package/dist/utils/getTotalLineCountFromHunks.js +2 -1
  188. package/dist/utils/getTotalLineCountFromHunks.js.map +1 -1
  189. package/dist/utils/hydratePartialDiff.d.ts.map +1 -1
  190. package/dist/utils/hydratePartialDiff.js +4 -3
  191. package/dist/utils/hydratePartialDiff.js.map +1 -1
  192. package/dist/utils/isGutterUtilityPath.d.ts +5 -0
  193. package/dist/utils/isGutterUtilityPath.d.ts.map +1 -0
  194. package/dist/utils/isGutterUtilityPath.js +12 -0
  195. package/dist/utils/isGutterUtilityPath.js.map +1 -0
  196. package/dist/utils/iterateOverDiff.d.ts.map +1 -1
  197. package/dist/utils/iterateOverDiff.js +17 -12
  198. package/dist/utils/iterateOverDiff.js.map +1 -1
  199. package/dist/utils/parseMergeConflictDiffFromFile.d.ts.map +1 -1
  200. package/dist/utils/parseMergeConflictDiffFromFile.js +9 -6
  201. package/dist/utils/parseMergeConflictDiffFromFile.js.map +1 -1
  202. package/dist/utils/parsePatchFiles.d.ts.map +1 -1
  203. package/dist/utils/parsePatchFiles.js +6 -3
  204. package/dist/utils/parsePatchFiles.js.map +1 -1
  205. package/dist/utils/realignChangeContent.d.ts +33 -0
  206. package/dist/utils/realignChangeContent.d.ts.map +1 -0
  207. package/dist/utils/realignChangeContent.js +148 -0
  208. package/dist/utils/realignChangeContent.js.map +1 -0
  209. package/dist/utils/renderDiffWithHighlighter.js +1 -1
  210. package/dist/utils/resolveRegion.d.ts.map +1 -1
  211. package/dist/utils/resolveRegion.js +18 -2
  212. package/dist/utils/resolveRegion.js.map +1 -1
  213. package/dist/utils/shouldUseTokenTransformer.d.ts +7 -3
  214. package/dist/utils/shouldUseTokenTransformer.d.ts.map +1 -1
  215. package/dist/utils/shouldUseTokenTransformer.js +1 -1
  216. package/dist/utils/shouldUseTokenTransformer.js.map +1 -1
  217. package/dist/utils/updateDiffHunks.d.ts +8 -2
  218. package/dist/utils/updateDiffHunks.d.ts.map +1 -1
  219. package/dist/utils/updateDiffHunks.js +9 -7
  220. package/dist/utils/updateDiffHunks.js.map +1 -1
  221. package/dist/utils/virtualDiffLayout.d.ts +42 -2
  222. package/dist/utils/virtualDiffLayout.d.ts.map +1 -1
  223. package/dist/utils/virtualDiffLayout.js +101 -7
  224. package/dist/utils/virtualDiffLayout.js.map +1 -1
  225. package/dist/worker/WorkerPoolManager.d.ts +2 -1
  226. package/dist/worker/WorkerPoolManager.d.ts.map +1 -1
  227. package/dist/worker/WorkerPoolManager.js +3 -0
  228. package/dist/worker/WorkerPoolManager.js.map +1 -1
  229. package/dist/worker/worker-portable.js +60 -20
  230. package/dist/worker/worker-portable.js.map +1 -1
  231. package/dist/worker/worker.js +61 -19
  232. package/dist/worker/worker.js.map +1 -1
  233. package/package.json +10 -7
  234. package/dist/editor/index.d.ts +0 -3
  235. package/dist/editor/index.js +0 -3
  236. package/dist/editor/tokenzier.d.ts.map +0 -1
  237. package/dist/editor/tokenzier.js.map +0 -1
  238. package/dist/react/EditorContext.d.ts +0 -15
  239. package/dist/react/EditorContext.d.ts.map +0 -1
  240. package/dist/react/EditorContext.js +0 -23
  241. package/dist/react/EditorContext.js.map +0 -1
@@ -12,6 +12,12 @@ interface PopoverPlacementBounds {
12
12
  /** The bottom of the popover in overlay coordinate space. */
13
13
  bottom: number;
14
14
  }
15
+ interface PopoverViewportBounds extends PopoverPlacementBounds {
16
+ /** The left edge of the visible viewport in overlay coordinate space. */
17
+ left: number;
18
+ /** The right edge of the visible viewport in overlay coordinate space. */
19
+ right: number;
20
+ }
15
21
  interface PopoverManagerOptions {
16
22
  hasActivePopover: () => boolean;
17
23
  updateActivePopover: () => void;
@@ -26,20 +32,34 @@ declare class PopoverManager {
26
32
  constructor(options: PopoverManagerOptions);
27
33
  setViewportElements(fileContainer: HTMLElement, codeElement: HTMLElement): void;
28
34
  cleanUp(): void;
29
- resetPlacement(): void;
30
- setPlacement(placement: 'preferred' | 'fallback'): void;
35
+ resetPlacement(placementKey?: string): void;
36
+ setPlacement(placement: 'preferred' | 'fallback', placementKey?: string): void;
31
37
  choosePlacement(input: {
32
38
  /** Bounds for the anchor's preferred side (e.g. above for backward, below for forward). */preferred: PopoverPlacementBounds; /** Bounds for the opposite edge we flip to when the preferred side has no room. */
33
39
  fallback: PopoverPlacementBounds; /** The visible scrollport in overlay coordinate space, or undefined without layout geometry (e.g. a detached test DOM). */
34
40
  viewport: PopoverPlacementBounds | undefined; /** The popover's measured height; 0 before it has laid out. */
35
41
  popoverHeight: number; /** Whether the anchor is within the document's first/last rows; only used as a fallback signal when `viewport` is unavailable. */
36
- atDocumentEdge: boolean;
42
+ atDocumentEdge: boolean; /** Keeps hysteresis independent when more than one popover kind is active. */
43
+ placementKey?: string;
37
44
  }): 'preferred' | 'fallback';
38
45
  /**
39
46
  * Returns the bounds of the popover in overlay coordinate space.
40
47
  */
41
- getPlacementBounds(): PopoverPlacementBounds | undefined;
48
+ getPlacementBounds(): PopoverViewportBounds | undefined;
42
49
  }
50
+ declare function setPopoverPositionStyles(popover: HTMLElement, {
51
+ gutterWidth,
52
+ placeAbove,
53
+ viewport,
54
+ x,
55
+ y
56
+ }: {
57
+ gutterWidth: number;
58
+ placeAbove: boolean;
59
+ viewport: PopoverViewportBounds | undefined;
60
+ x: number;
61
+ y: number;
62
+ }): void;
43
63
  //#endregion
44
- export { POPOVER_BOUNDARY_LINES, POPOVER_FLIP_HYSTERESIS_PX, PopoverManager, PopoverManagerOptions, PopoverPlacementBounds };
64
+ export { POPOVER_BOUNDARY_LINES, POPOVER_FLIP_HYSTERESIS_PX, PopoverManager, PopoverManagerOptions, PopoverPlacementBounds, PopoverViewportBounds, setPopoverPositionStyles };
45
65
  //# sourceMappingURL=popover.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"popover.d.ts","names":[],"sources":["../../src/editor/popover.ts"],"mappings":";cAIa,sBAAA;AAAA,cAKA,0BAAA;;;;AALsB;AAKnC;UAOiB,sBAAA;;EAEf,GAAA;EATqC;EAWrC,MAAM;AAAA;AAAA,UAGS,qBAAA;EACf,gBAAA;EACA,mBAAmB;AAAA;;;;AAAA;AAQrB;cAAa,cAAA;EAAA;EAcX,WAAA,CAAY,OAAA,EAAS,qBAAA;EAKrB,mBAAA,CACE,aAAA,EAAe,WAAA,EACf,WAAA,EAAa,WAAA;EAaf,OAAA;EAaA,cAAA;EAIA,YAAA,CAAa,SAAA;EAOb,eAAA,CAAgB,KAAA;IAwCM,2FAtCpB,SAAA,EAAW,sBAAA,EAsC+B;IApC1C,QAAA,EAAU,sBAAA,EAhDZ;IAkDE,QAAA,EAAU,sBAAA,cAlDA;IAoDV,aAAA,UA9Ce;IAgDf,cAAA;EAAA;EA/CA;;;EA6EF,kBAAA,IAAsB,sBAAA;AAAA"}
1
+ {"version":3,"file":"popover.d.ts","names":[],"sources":["../../src/editor/popover.ts"],"mappings":";cAIa,sBAAA;AAAA,cAKA,0BAAA;;;;AALsB;AAKnC;UAOiB,sBAAA;;EAEf,GAAA;EATqC;EAWrC,MAAM;AAAA;AAAA,UAGS,qBAAA,SAA8B,sBAAsB;EALnE;EAOA,IAAA;EAFe;EAIf,KAAA;AAAA;AAAA,UAGe,qBAAA;EACf,gBAAA;EACA,mBAAmB;AAAA;;AALd;AAGP;;;cAUa,cAAA;EAAA;EAiBX,WAAA,CAAY,OAAA,EAAS,qBAAA;EAKrB,mBAAA,CACE,aAAA,EAAe,WAAA,EACf,WAAA,EAAa,WAAA;EAkBf,OAAA;EAgBA,cAAA,CAAe,YAAA;EAIf,YAAA,CACE,SAAA,4BACA,YAAA;EAQF,eAAA,CAAgB,KAAA;IAEH,2FAAX,SAAA,EAAW,sBAAA,EAID;IAFV,QAAA,EAAU,sBAAA,EA6C+B;IA3CzC,QAAA,EAAU,sBAAA;IAEV,aAAA,UA/DmB;IAiEnB,cAAA,WA5DF;IA8DE,YAAA;EAAA;EA5Da;;;EAiGf,kBAAA,IAAsB,qBAAA;AAAA;AAAA,iBAoLR,wBAAA,CACd,OAAA,EAAS,WAAA;EAEP,WAAA;EACA,UAAA;EACA,QAAA;EACA,CAAA;EACA;AAAA;EAEA,WAAA;EACA,UAAA;EACA,QAAA,EAAU,qBAAA;EACV,CAAA;EACA,CAAA;AAAA"}
@@ -14,9 +14,12 @@ var PopoverManager = class {
14
14
  #scrollContainerSource;
15
15
  #cachedCodeRect;
16
16
  #cachedScrollContainerRect;
17
+ #cachedCodeScrollLeft = 0;
18
+ #cachedCodeScrollTop = 0;
19
+ #cachedCodeClientWidth = 0;
17
20
  #viewportRectsDirty = true;
18
21
  #viewportRectListenersDisposes;
19
- #placement;
22
+ #placements = /* @__PURE__ */ new Map();
20
23
  #hasActivePopover;
21
24
  #updateActivePopover;
22
25
  constructor(options) {
@@ -24,10 +27,13 @@ var PopoverManager = class {
24
27
  this.#updateActivePopover = options.updateActivePopover;
25
28
  }
26
29
  setViewportElements(fileContainer, codeElement) {
30
+ const viewportElementChanged = this.#fileContainer !== fileContainer || this.#codeElement !== codeElement;
27
31
  this.#fileContainer = fileContainer;
28
- if (this.#codeElement === codeElement) return;
32
+ if (!viewportElementChanged) return;
29
33
  this.#codeElement = codeElement;
30
34
  this.#viewportRectsDirty = true;
35
+ this.#viewportRectListenersDisposes?.forEach((dispose) => dispose());
36
+ this.#viewportRectListenersDisposes = void 0;
31
37
  }
32
38
  cleanUp() {
33
39
  this.#codeElement = void 0;
@@ -36,27 +42,31 @@ var PopoverManager = class {
36
42
  this.#scrollContainerSource = void 0;
37
43
  this.#cachedCodeRect = void 0;
38
44
  this.#cachedScrollContainerRect = void 0;
45
+ this.#cachedCodeScrollLeft = 0;
46
+ this.#cachedCodeScrollTop = 0;
47
+ this.#cachedCodeClientWidth = 0;
39
48
  this.#viewportRectsDirty = true;
40
49
  this.#viewportRectListenersDisposes?.forEach((dispose) => dispose());
41
50
  this.#viewportRectListenersDisposes = void 0;
42
- this.#placement = void 0;
51
+ this.#placements.clear();
43
52
  }
44
- resetPlacement() {
45
- this.#placement = void 0;
53
+ resetPlacement(placementKey = "default") {
54
+ this.#placements.delete(placementKey);
46
55
  }
47
- setPlacement(placement) {
48
- this.#placement = placement;
56
+ setPlacement(placement, placementKey = "default") {
57
+ this.#placements.set(placementKey, placement);
49
58
  }
50
59
  choosePlacement(input) {
51
- const { preferred, fallback, viewport, popoverHeight, atDocumentEdge } = input;
60
+ const { preferred, fallback, viewport, popoverHeight, atDocumentEdge, placementKey = "default" } = input;
61
+ const previousPlacement = this.#placements.get(placementKey);
52
62
  let placement;
53
63
  if (viewport !== void 0 && popoverHeight > 0) {
54
64
  const fits = (bounds, margin = 0) => bounds.top >= viewport.top + margin && bounds.bottom <= viewport.bottom - margin;
55
- if (this.#placement === "fallback" && fits(fallback) && !fits(preferred, 4)) placement = "fallback";
65
+ if (previousPlacement === "fallback" && fits(fallback) && !fits(preferred, 4)) placement = "fallback";
56
66
  else if (!fits(preferred) && fits(fallback)) placement = "fallback";
57
67
  else placement = "preferred";
58
68
  } else placement = atDocumentEdge ? "fallback" : "preferred";
59
- this.#placement = placement;
69
+ this.#placements.set(placementKey, placement);
60
70
  return placement;
61
71
  }
62
72
  /**
@@ -75,10 +85,23 @@ var PopoverManager = class {
75
85
  topScreen = 0;
76
86
  bottomScreen = window.innerHeight;
77
87
  }
88
+ topScreen = Math.max(topScreen, codeRect.top);
89
+ bottomScreen = Math.min(bottomScreen, codeRect.bottom);
78
90
  if (bottomScreen <= topScreen) return;
91
+ const codeScrollLeft = this.#cachedCodeScrollLeft;
92
+ const codeScrollTop = this.#cachedCodeScrollTop;
93
+ const codeViewportWidth = this.#cachedCodeClientWidth > 0 ? this.#cachedCodeClientWidth : codeRect.width;
94
+ const leftScreen = scrollContainerRect !== void 0 ? scrollContainerRect.left : 0;
95
+ const rightScreen = scrollContainerRect !== void 0 ? scrollContainerRect.right : window.innerWidth;
96
+ const codeViewportLeft = codeScrollLeft;
97
+ const codeViewportRight = codeScrollLeft + codeViewportWidth;
98
+ const visibleLeft = Math.max(codeViewportLeft, leftScreen - codeRect.left + codeScrollLeft);
99
+ const visibleRight = Math.min(codeViewportRight, rightScreen - codeRect.left + codeScrollLeft);
79
100
  return {
80
- top: topScreen - codeRect.top,
81
- bottom: bottomScreen - codeRect.top
101
+ top: topScreen - codeRect.top + codeScrollTop,
102
+ bottom: bottomScreen - codeRect.top + codeScrollTop,
103
+ left: visibleLeft,
104
+ right: Math.max(visibleLeft, visibleRight)
82
105
  };
83
106
  }
84
107
  #getScrollContainer() {
@@ -103,7 +126,11 @@ var PopoverManager = class {
103
126
  #refreshViewportRectsIfNeeded() {
104
127
  if (!this.#viewportRectsDirty) return;
105
128
  this.#ensureViewportRectListeners();
106
- this.#cachedCodeRect = this.#codeElement?.getBoundingClientRect();
129
+ const codeElement = this.#codeElement;
130
+ this.#cachedCodeRect = codeElement?.getBoundingClientRect();
131
+ this.#cachedCodeScrollLeft = codeElement?.scrollLeft ?? 0;
132
+ this.#cachedCodeScrollTop = codeElement?.scrollTop ?? 0;
133
+ this.#cachedCodeClientWidth = codeElement?.clientWidth ?? 0;
107
134
  this.#cachedScrollContainerRect = this.#getScrollContainer()?.getBoundingClientRect();
108
135
  this.#viewportRectsDirty = false;
109
136
  }
@@ -127,8 +154,10 @@ var PopoverManager = class {
127
154
  });
128
155
  };
129
156
  const scroller = this.#getScrollContainer();
157
+ const codeElement = this.#codeElement;
130
158
  this.#viewportRectListenersDisposes = [
131
159
  scroller !== void 0 ? addEventListener(scroller, "scroll", markDirty, { passive: true }) : addEventListener(window, "scroll", markDirty, { passive: true }),
160
+ ...codeElement !== void 0 ? [addEventListener(codeElement, "scroll", markDirty, { passive: true })] : [],
132
161
  addEventListener(window, "resize", markDirty, { passive: true }),
133
162
  () => {
134
163
  if (repositionRafId !== void 0) {
@@ -139,7 +168,24 @@ var PopoverManager = class {
139
168
  ];
140
169
  }
141
170
  };
171
+ function setPopoverPositionStyles(popover, { gutterWidth, placeAbove, viewport, x, y }) {
172
+ popover.style.setProperty("--gutter-width", gutterWidth + "px");
173
+ popover.style.setProperty("--popover-x", x + "px");
174
+ popover.style.setProperty("--popover-y", y + "px");
175
+ popover.style.setProperty("--popover-y-shift", placeAbove ? "-100%" : "0px");
176
+ if (viewport === void 0) {
177
+ popover.style.removeProperty("--popover-viewport-left");
178
+ popover.style.removeProperty("--popover-viewport-right");
179
+ popover.style.removeProperty("--popover-viewport-top");
180
+ popover.style.removeProperty("--popover-viewport-bottom");
181
+ return;
182
+ }
183
+ popover.style.setProperty("--popover-viewport-left", viewport.left + "px");
184
+ popover.style.setProperty("--popover-viewport-right", viewport.right + "px");
185
+ popover.style.setProperty("--popover-viewport-top", viewport.top + "px");
186
+ popover.style.setProperty("--popover-viewport-bottom", viewport.bottom + "px");
187
+ }
142
188
  //#endregion
143
- export { POPOVER_BOUNDARY_LINES, POPOVER_FLIP_HYSTERESIS_PX, PopoverManager };
189
+ export { POPOVER_BOUNDARY_LINES, POPOVER_FLIP_HYSTERESIS_PX, PopoverManager, setPopoverPositionStyles };
144
190
 
145
191
  //# sourceMappingURL=popover.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"popover.js","names":["#hasActivePopover","#updateActivePopover","#fileContainer","#codeElement","#viewportRectsDirty","#scrollContainer","#scrollContainerSource","#cachedCodeRect","#cachedScrollContainerRect","#viewportRectListenersDisposes","#placement","#getCodeRect","#getScrollContainerRect","#ensureViewportRectListeners","#getScrollContainer","#refreshViewportRectsIfNeeded"],"sources":["../../src/editor/popover.ts"],"sourcesContent":["import { addEventListener } from './utils';\n\n// Lines from the document edge at which the no-viewport fallback heuristic\n// (see `atDocumentEdge` below) flips placement.\nexport const POPOVER_BOUNDARY_LINES = 3;\n\n// Extra clearance (px) `preferred` must regain before a popover that flipped\n// to `fallback` flips back, so an anchor sitting right at the flip boundary\n// doesn't flicker between sides on every recompute.\nexport const POPOVER_FLIP_HYSTERESIS_PX = 4;\n\n/**\n * The visible scroll viewport expressed in the overlay's coordinate space (the\n * same space --popover-y uses), or undefined when no usable layout geometry\n * exists (e.g. a detached unit-test DOM, where every rect is zero).\n */\nexport interface PopoverPlacementBounds {\n /** The top of the popover in overlay coordinate space. */\n top: number;\n /** The bottom of the popover in overlay coordinate space. */\n bottom: number;\n}\n\nexport interface PopoverManagerOptions {\n hasActivePopover: () => boolean;\n updateActivePopover: () => void;\n}\n\n/**\n * Shared placement logic for the editor's overlay popovers: each anchors below\n * or above a document position, flipping to the opposite side only when the\n * preferred side would be clipped by the visible scrollport.\n */\nexport class PopoverManager {\n #fileContainer?: HTMLElement;\n #codeElement?: HTMLElement;\n #scrollContainer?: HTMLElement;\n #scrollContainerSource?: HTMLElement;\n #cachedCodeRect?: DOMRect;\n #cachedScrollContainerRect?: DOMRect;\n #viewportRectsDirty = true;\n #viewportRectListenersDisposes?: (() => void)[];\n #placement?: 'preferred' | 'fallback';\n\n readonly #hasActivePopover: () => boolean;\n readonly #updateActivePopover: () => void;\n\n constructor(options: PopoverManagerOptions) {\n this.#hasActivePopover = options.hasActivePopover;\n this.#updateActivePopover = options.updateActivePopover;\n }\n\n setViewportElements(\n fileContainer: HTMLElement,\n codeElement: HTMLElement\n ): void {\n this.#fileContainer = fileContainer;\n if (this.#codeElement === codeElement) {\n return;\n }\n this.#codeElement = codeElement;\n // A new [data-code] element invalidates the cached viewport rects (e.g. a\n // full re-render loaded a different file, possibly at a different on-screen\n // position) even without an intervening scroll/resize event.\n this.#viewportRectsDirty = true;\n }\n\n cleanUp(): void {\n this.#codeElement = undefined;\n this.#fileContainer = undefined;\n this.#scrollContainer = undefined;\n this.#scrollContainerSource = undefined;\n this.#cachedCodeRect = undefined;\n this.#cachedScrollContainerRect = undefined;\n this.#viewportRectsDirty = true;\n this.#viewportRectListenersDisposes?.forEach((dispose) => dispose());\n this.#viewportRectListenersDisposes = undefined;\n this.#placement = undefined;\n }\n\n resetPlacement(): void {\n this.#placement = undefined;\n }\n\n setPlacement(placement: 'preferred' | 'fallback'): void {\n this.#placement = placement;\n }\n\n // Flips to the opposite side only when the preferred side would be clipped by\n // the real scrollport and the fallback side fits; without viewport geometry,\n // falls back to the document-edge signal so the first/last rows still flip.\n choosePlacement(input: {\n /** Bounds for the anchor's preferred side (e.g. above for backward, below for forward). */\n preferred: PopoverPlacementBounds;\n /** Bounds for the opposite edge we flip to when the preferred side has no room. */\n fallback: PopoverPlacementBounds;\n /** The visible scrollport in overlay coordinate space, or undefined without layout geometry (e.g. a detached test DOM). */\n viewport: PopoverPlacementBounds | undefined;\n /** The popover's measured height; 0 before it has laid out. */\n popoverHeight: number;\n /** Whether the anchor is within the document's first/last rows; only used as a fallback signal when `viewport` is unavailable. */\n atDocumentEdge: boolean;\n }): 'preferred' | 'fallback' {\n const { preferred, fallback, viewport, popoverHeight, atDocumentEdge } =\n input;\n let placement: 'preferred' | 'fallback';\n if (viewport !== undefined && popoverHeight > 0) {\n const fits = (bounds: PopoverPlacementBounds, margin = 0): boolean =>\n bounds.top >= viewport.top + margin &&\n bounds.bottom <= viewport.bottom - margin;\n if (\n this.#placement === 'fallback' &&\n fits(fallback) &&\n !fits(preferred, POPOVER_FLIP_HYSTERESIS_PX)\n ) {\n placement = 'fallback';\n } else if (!fits(preferred) && fits(fallback)) {\n placement = 'fallback';\n } else {\n placement = 'preferred';\n }\n } else {\n placement = atDocumentEdge ? 'fallback' : 'preferred';\n }\n this.#placement = placement;\n return placement;\n }\n\n /**\n * Returns the bounds of the popover in overlay coordinate space.\n */\n getPlacementBounds(): PopoverPlacementBounds | undefined {\n const codeRect = this.#getCodeRect();\n if (codeRect === undefined) {\n return undefined;\n }\n const scrollContainerRect = this.#getScrollContainerRect();\n let topScreen: number;\n let bottomScreen: number;\n if (scrollContainerRect !== undefined) {\n topScreen = scrollContainerRect.top;\n bottomScreen = scrollContainerRect.bottom;\n } else {\n // No scrollable ancestor: the page itself scrolls, so the window is the\n // clip region.\n topScreen = 0;\n bottomScreen = window.innerHeight;\n }\n if (bottomScreen <= topScreen) {\n return undefined;\n }\n return {\n top: topScreen - codeRect.top,\n bottom: bottomScreen - codeRect.top,\n };\n }\n\n // The nearest scrollable ancestor of the file container -- the element whose\n // overflow clips overlay widgets. Walks the light-DOM ancestor chain (the\n // overlay lives in the file container's shadow root) and caches the result per\n // file container so it is not re-resolved on every render.\n //\n // Known limitations: this only inspects light-DOM ancestors outside the\n // shadow root, so it cannot see a clip boundary inside it (e.g. `[data-code]`\n // itself uses `overflow-y: clip`, which also is not one of the values matched\n // below) and cannot cross into an outer shadow root if the file container is\n // itself nested in another web component. It also only considers the\n // *nearest* scrollable ancestor, not the intersection of every clipping\n // ancestor, so a popover could still be clipped by an outer scroll context\n // even when it fits within this nearer one.\n #getScrollContainer(): HTMLElement | undefined {\n const fileContainer = this.#fileContainer;\n if (fileContainer === undefined) {\n return undefined;\n }\n if (this.#scrollContainerSource === fileContainer) {\n // A cached element can detach across re-mounts; re-resolve in that case\n // but otherwise trust the cache (including a cached \"no scroller\" result).\n if (\n this.#scrollContainer === undefined ||\n this.#scrollContainer.isConnected\n ) {\n return this.#scrollContainer;\n }\n }\n let element: HTMLElement | null = fileContainer.parentElement;\n while (element !== null) {\n const overflowY = getComputedStyle(element).overflowY;\n if (\n overflowY === 'auto' ||\n overflowY === 'scroll' ||\n overflowY === 'overlay'\n ) {\n this.#scrollContainer = element;\n this.#scrollContainerSource = fileContainer;\n return element;\n }\n element = element.parentElement;\n }\n this.#scrollContainer = undefined;\n this.#scrollContainerSource = fileContainer;\n return undefined;\n }\n\n // Lazily measures and caches the screen-space rects getOverlayViewport needs,\n // refreshing only when a scroll or resize event marks them dirty rather than\n // on every call. Selection-action placement can run once per keystroke while a\n // ranged selection stays open, so re-measuring via getBoundingClientRect on\n // every call would force a synchronous layout reflow on that hot path.\n #refreshViewportRectsIfNeeded(): void {\n if (!this.#viewportRectsDirty) {\n return;\n }\n this.#ensureViewportRectListeners();\n this.#cachedCodeRect = this.#codeElement?.getBoundingClientRect();\n this.#cachedScrollContainerRect =\n this.#getScrollContainer()?.getBoundingClientRect();\n this.#viewportRectsDirty = false;\n }\n\n #getCodeRect(): DOMRect | undefined {\n this.#refreshViewportRectsIfNeeded();\n return this.#cachedCodeRect;\n }\n\n #getScrollContainerRect(): DOMRect | undefined {\n this.#refreshViewportRectsIfNeeded();\n return this.#cachedScrollContainerRect;\n }\n\n // Subscribes once to events that can move or resize the cached rects: a scroll\n // on the resolved scroll container (or the window, when the page itself\n // scrolls) and a window resize. Scrolling/resizing can change whether the open\n // popover still fits, so this re-runs placement rAF-throttled.\n #ensureViewportRectListeners(): void {\n if (this.#viewportRectListenersDisposes !== undefined) {\n return;\n }\n let repositionRafId: number | undefined;\n const markDirty = () => {\n this.#viewportRectsDirty = true;\n if (!this.#hasActivePopover() || repositionRafId !== undefined) {\n return;\n }\n repositionRafId = requestAnimationFrame(() => {\n repositionRafId = undefined;\n this.#updateActivePopover();\n });\n };\n const scroller = this.#getScrollContainer();\n this.#viewportRectListenersDisposes = [\n scroller !== undefined\n ? addEventListener(scroller, 'scroll', markDirty, { passive: true })\n : addEventListener(window, 'scroll', markDirty, { passive: true }),\n addEventListener(window, 'resize', markDirty, { passive: true }),\n () => {\n if (repositionRafId !== undefined) {\n cancelAnimationFrame(repositionRafId);\n repositionRafId = undefined;\n }\n },\n ];\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"popover.js","names":["#hasActivePopover","#updateActivePopover","#fileContainer","#codeElement","#viewportRectsDirty","#viewportRectListenersDisposes","#scrollContainer","#scrollContainerSource","#cachedCodeRect","#cachedScrollContainerRect","#cachedCodeScrollLeft","#cachedCodeScrollTop","#cachedCodeClientWidth","#placements","#getCodeRect","#getScrollContainerRect","#ensureViewportRectListeners","#getScrollContainer","#refreshViewportRectsIfNeeded"],"sources":["../../src/editor/popover.ts"],"sourcesContent":["import { addEventListener } from './utils';\n\n// Lines from the document edge at which the no-viewport fallback heuristic\n// (see `atDocumentEdge` below) flips placement.\nexport const POPOVER_BOUNDARY_LINES = 3;\n\n// Extra clearance (px) `preferred` must regain before a popover that flipped\n// to `fallback` flips back, so an anchor sitting right at the flip boundary\n// doesn't flicker between sides on every recompute.\nexport const POPOVER_FLIP_HYSTERESIS_PX = 4;\n\n/**\n * The visible scroll viewport expressed in the overlay's coordinate space (the\n * same space --popover-y uses), or undefined when no usable layout geometry\n * exists (e.g. a detached unit-test DOM, where every rect is zero).\n */\nexport interface PopoverPlacementBounds {\n /** The top of the popover in overlay coordinate space. */\n top: number;\n /** The bottom of the popover in overlay coordinate space. */\n bottom: number;\n}\n\nexport interface PopoverViewportBounds extends PopoverPlacementBounds {\n /** The left edge of the visible viewport in overlay coordinate space. */\n left: number;\n /** The right edge of the visible viewport in overlay coordinate space. */\n right: number;\n}\n\nexport interface PopoverManagerOptions {\n hasActivePopover: () => boolean;\n updateActivePopover: () => void;\n}\n\n/**\n * Shared placement logic for the editor's overlay popovers: each anchors below\n * or above a document position, flipping to the opposite side only when the\n * preferred side would be clipped by the visible scrollport.\n */\nexport class PopoverManager {\n #fileContainer?: HTMLElement;\n #codeElement?: HTMLElement;\n #scrollContainer?: HTMLElement;\n #scrollContainerSource?: HTMLElement;\n #cachedCodeRect?: DOMRect;\n #cachedScrollContainerRect?: DOMRect;\n #cachedCodeScrollLeft = 0;\n #cachedCodeScrollTop = 0;\n #cachedCodeClientWidth = 0;\n #viewportRectsDirty = true;\n #viewportRectListenersDisposes?: (() => void)[];\n #placements = new Map<string, 'preferred' | 'fallback'>();\n\n readonly #hasActivePopover: () => boolean;\n readonly #updateActivePopover: () => void;\n\n constructor(options: PopoverManagerOptions) {\n this.#hasActivePopover = options.hasActivePopover;\n this.#updateActivePopover = options.updateActivePopover;\n }\n\n setViewportElements(\n fileContainer: HTMLElement,\n codeElement: HTMLElement\n ): void {\n const viewportElementChanged =\n this.#fileContainer !== fileContainer ||\n this.#codeElement !== codeElement;\n this.#fileContainer = fileContainer;\n if (!viewportElementChanged) {\n return;\n }\n this.#codeElement = codeElement;\n // A new [data-code] element invalidates the cached viewport rects (e.g. a\n // full re-render loaded a different file, possibly at a different on-screen\n // position) even without an intervening scroll/resize event.\n this.#viewportRectsDirty = true;\n this.#viewportRectListenersDisposes?.forEach((dispose) => dispose());\n this.#viewportRectListenersDisposes = undefined;\n }\n\n cleanUp(): void {\n this.#codeElement = undefined;\n this.#fileContainer = undefined;\n this.#scrollContainer = undefined;\n this.#scrollContainerSource = undefined;\n this.#cachedCodeRect = undefined;\n this.#cachedScrollContainerRect = undefined;\n this.#cachedCodeScrollLeft = 0;\n this.#cachedCodeScrollTop = 0;\n this.#cachedCodeClientWidth = 0;\n this.#viewportRectsDirty = true;\n this.#viewportRectListenersDisposes?.forEach((dispose) => dispose());\n this.#viewportRectListenersDisposes = undefined;\n this.#placements.clear();\n }\n\n resetPlacement(placementKey = 'default'): void {\n this.#placements.delete(placementKey);\n }\n\n setPlacement(\n placement: 'preferred' | 'fallback',\n placementKey = 'default'\n ): void {\n this.#placements.set(placementKey, placement);\n }\n\n // Flips to the opposite side only when the preferred side would be clipped by\n // the real scrollport and the fallback side fits; without viewport geometry,\n // falls back to the document-edge signal so the first/last rows still flip.\n choosePlacement(input: {\n /** Bounds for the anchor's preferred side (e.g. above for backward, below for forward). */\n preferred: PopoverPlacementBounds;\n /** Bounds for the opposite edge we flip to when the preferred side has no room. */\n fallback: PopoverPlacementBounds;\n /** The visible scrollport in overlay coordinate space, or undefined without layout geometry (e.g. a detached test DOM). */\n viewport: PopoverPlacementBounds | undefined;\n /** The popover's measured height; 0 before it has laid out. */\n popoverHeight: number;\n /** Whether the anchor is within the document's first/last rows; only used as a fallback signal when `viewport` is unavailable. */\n atDocumentEdge: boolean;\n /** Keeps hysteresis independent when more than one popover kind is active. */\n placementKey?: string;\n }): 'preferred' | 'fallback' {\n const {\n preferred,\n fallback,\n viewport,\n popoverHeight,\n atDocumentEdge,\n placementKey = 'default',\n } = input;\n const previousPlacement = this.#placements.get(placementKey);\n let placement: 'preferred' | 'fallback';\n if (viewport !== undefined && popoverHeight > 0) {\n const fits = (bounds: PopoverPlacementBounds, margin = 0): boolean =>\n bounds.top >= viewport.top + margin &&\n bounds.bottom <= viewport.bottom - margin;\n if (\n previousPlacement === 'fallback' &&\n fits(fallback) &&\n !fits(preferred, POPOVER_FLIP_HYSTERESIS_PX)\n ) {\n placement = 'fallback';\n } else if (!fits(preferred) && fits(fallback)) {\n placement = 'fallback';\n } else {\n placement = 'preferred';\n }\n } else {\n placement = atDocumentEdge ? 'fallback' : 'preferred';\n }\n this.#placements.set(placementKey, placement);\n return placement;\n }\n\n /**\n * Returns the bounds of the popover in overlay coordinate space.\n */\n getPlacementBounds(): PopoverViewportBounds | undefined {\n const codeRect = this.#getCodeRect();\n if (codeRect === undefined) {\n return undefined;\n }\n const scrollContainerRect = this.#getScrollContainerRect();\n let topScreen: number;\n let bottomScreen: number;\n if (scrollContainerRect !== undefined) {\n topScreen = scrollContainerRect.top;\n bottomScreen = scrollContainerRect.bottom;\n } else {\n // No scrollable ancestor: the page itself scrolls, so the window is the\n // clip region.\n topScreen = 0;\n bottomScreen = window.innerHeight;\n }\n // The overlay that hosts the popover lives inside `[data-code]`, which uses\n // `overflow-y: clip` (see style.css). A short file smaller than the\n // scrollport therefore clips the popover at its own top/bottom even when the\n // window/scroll container still has room beyond those edges. Intersect the\n // scrollport with the code element's own box so a first/last-line popover\n // flips to the side that actually fits within that clip, not just within the\n // outer viewport.\n topScreen = Math.max(topScreen, codeRect.top);\n bottomScreen = Math.min(bottomScreen, codeRect.bottom);\n if (bottomScreen <= topScreen) {\n return undefined;\n }\n const codeScrollLeft = this.#cachedCodeScrollLeft;\n const codeScrollTop = this.#cachedCodeScrollTop;\n const codeViewportWidth =\n this.#cachedCodeClientWidth > 0\n ? this.#cachedCodeClientWidth\n : codeRect.width;\n const leftScreen =\n scrollContainerRect !== undefined ? scrollContainerRect.left : 0;\n const rightScreen =\n scrollContainerRect !== undefined\n ? scrollContainerRect.right\n : window.innerWidth;\n const codeViewportLeft = codeScrollLeft;\n const codeViewportRight = codeScrollLeft + codeViewportWidth;\n const visibleLeft = Math.max(\n codeViewportLeft,\n leftScreen - codeRect.left + codeScrollLeft\n );\n const visibleRight = Math.min(\n codeViewportRight,\n rightScreen - codeRect.left + codeScrollLeft\n );\n return {\n top: topScreen - codeRect.top + codeScrollTop,\n bottom: bottomScreen - codeRect.top + codeScrollTop,\n left: visibleLeft,\n right: Math.max(visibleLeft, visibleRight),\n };\n }\n\n // The nearest scrollable ancestor of the file container -- the element whose\n // overflow clips overlay widgets. Walks the light-DOM ancestor chain (the\n // overlay lives in the file container's shadow root) and caches the result per\n // file container so it is not re-resolved on every render.\n //\n // Known limitations: this only inspects light-DOM ancestors outside the\n // shadow root, so it cannot see arbitrary clip boundaries inside it and cannot\n // cross into an outer shadow root if the file container is itself nested in\n // another web component. It also only considers the *nearest* scrollable\n // ancestor, not the intersection of every clipping ancestor, so a popover\n // could still be clipped by an outer scroll context even when it fits within\n // this nearer one. The `[data-code]` element's own `overflow-y: clip` box is\n // handled separately in getPlacementBounds, which intersects it with whatever\n // scrollport this method resolves.\n #getScrollContainer(): HTMLElement | undefined {\n const fileContainer = this.#fileContainer;\n if (fileContainer === undefined) {\n return undefined;\n }\n if (this.#scrollContainerSource === fileContainer) {\n // A cached element can detach across re-mounts; re-resolve in that case\n // but otherwise trust the cache (including a cached \"no scroller\" result).\n if (\n this.#scrollContainer === undefined ||\n this.#scrollContainer.isConnected\n ) {\n return this.#scrollContainer;\n }\n }\n let element: HTMLElement | null = fileContainer.parentElement;\n while (element !== null) {\n const overflowY = getComputedStyle(element).overflowY;\n if (\n overflowY === 'auto' ||\n overflowY === 'scroll' ||\n overflowY === 'overlay'\n ) {\n this.#scrollContainer = element;\n this.#scrollContainerSource = fileContainer;\n return element;\n }\n element = element.parentElement;\n }\n this.#scrollContainer = undefined;\n this.#scrollContainerSource = fileContainer;\n return undefined;\n }\n\n // Lazily measures and caches the screen-space rects getOverlayViewport needs,\n // refreshing only when a scroll or resize event marks them dirty rather than\n // on every call. Selection-action placement can run once per keystroke while a\n // ranged selection stays open, so re-measuring via getBoundingClientRect on\n // every call would force a synchronous layout reflow on that hot path.\n #refreshViewportRectsIfNeeded(): void {\n if (!this.#viewportRectsDirty) {\n return;\n }\n this.#ensureViewportRectListeners();\n const codeElement = this.#codeElement;\n this.#cachedCodeRect = codeElement?.getBoundingClientRect();\n this.#cachedCodeScrollLeft = codeElement?.scrollLeft ?? 0;\n this.#cachedCodeScrollTop = codeElement?.scrollTop ?? 0;\n this.#cachedCodeClientWidth = codeElement?.clientWidth ?? 0;\n this.#cachedScrollContainerRect =\n this.#getScrollContainer()?.getBoundingClientRect();\n this.#viewportRectsDirty = false;\n }\n\n #getCodeRect(): DOMRect | undefined {\n this.#refreshViewportRectsIfNeeded();\n return this.#cachedCodeRect;\n }\n\n #getScrollContainerRect(): DOMRect | undefined {\n this.#refreshViewportRectsIfNeeded();\n return this.#cachedScrollContainerRect;\n }\n\n // Subscribes once to events that can move or resize the cached rects: a scroll\n // on the resolved scroll container (or the window, when the page itself\n // scrolls) and a window resize. Scrolling/resizing can change whether the open\n // popover still fits, so this re-runs placement rAF-throttled.\n #ensureViewportRectListeners(): void {\n if (this.#viewportRectListenersDisposes !== undefined) {\n return;\n }\n let repositionRafId: number | undefined;\n const markDirty = () => {\n this.#viewportRectsDirty = true;\n if (!this.#hasActivePopover() || repositionRafId !== undefined) {\n return;\n }\n repositionRafId = requestAnimationFrame(() => {\n repositionRafId = undefined;\n this.#updateActivePopover();\n });\n };\n const scroller = this.#getScrollContainer();\n const codeElement = this.#codeElement;\n this.#viewportRectListenersDisposes = [\n scroller !== undefined\n ? addEventListener(scroller, 'scroll', markDirty, { passive: true })\n : addEventListener(window, 'scroll', markDirty, { passive: true }),\n ...(codeElement !== undefined\n ? [\n addEventListener(codeElement, 'scroll', markDirty, {\n passive: true,\n }),\n ]\n : []),\n addEventListener(window, 'resize', markDirty, { passive: true }),\n () => {\n if (repositionRafId !== undefined) {\n cancelAnimationFrame(repositionRafId);\n repositionRafId = undefined;\n }\n },\n ];\n }\n}\n\nexport function setPopoverPositionStyles(\n popover: HTMLElement,\n {\n gutterWidth,\n placeAbove,\n viewport,\n x,\n y,\n }: {\n gutterWidth: number;\n placeAbove: boolean;\n viewport: PopoverViewportBounds | undefined;\n x: number;\n y: number;\n }\n): void {\n popover.style.setProperty('--gutter-width', gutterWidth + 'px');\n popover.style.setProperty('--popover-x', x + 'px');\n popover.style.setProperty('--popover-y', y + 'px');\n popover.style.setProperty('--popover-y-shift', placeAbove ? '-100%' : '0px');\n if (viewport === undefined) {\n popover.style.removeProperty('--popover-viewport-left');\n popover.style.removeProperty('--popover-viewport-right');\n popover.style.removeProperty('--popover-viewport-top');\n popover.style.removeProperty('--popover-viewport-bottom');\n return;\n }\n popover.style.setProperty('--popover-viewport-left', viewport.left + 'px');\n popover.style.setProperty('--popover-viewport-right', viewport.right + 'px');\n // Vertical bounds let the CSS clamp keep the popover on-screen when neither\n // side fits (e.g. a select-all spanning the first and last rows).\n popover.style.setProperty('--popover-viewport-top', viewport.top + 'px');\n popover.style.setProperty(\n '--popover-viewport-bottom',\n viewport.bottom + 'px'\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"searchPanel.d.ts","names":[],"sources":["../../src/editor/searchPanel.ts"],"mappings":";;;KAOY,UAAA,IAAc,WAAA,UAAqB,SAAS;AAAA,KAE5C,eAAA;AAAA,UAEK,YAAA;EACf,IAAA;EACA,WAAA;EACA,aAAA;EACA,SAAA;EACA,KAAA;AAAA;AAAA,UAGe,kBAAA;EACf,YAAA,EAAc,YAAA;EACd,gBAAA,EAAkB,WAAA;EAClB,YAAA;EACA,IAAA,GAAO,eAAA;EACP,YAAA,GAAe,UAAA;EACf,aAAA,GAAgB,SAAA,EAAW,UAAA,EAAY,WAAA;EACvC,YAAA,GAAe,KAAA,EAAO,gBAAA;EACtB,QAAA,GACE,OAAA,EAAS,UAAA,IACT,OAAA;IAAY,aAAA;EAAA,MACT,UAAA;EACL,OAAA;AAAA;AAAA,cAGW,iBAAA;EAAA;EAQX,WAAA,CAAY,OAAA,EAAS,kBAAA;EAqYrB,KAAA;EAIA,QAAA,CAAS,YAAA;EAIT,aAAA,CAAc,OAAA;IAAY,aAAA;EAAA;EAI1B,OAAA,CAAQ,IAAA,EAAM,eAAe;EAO7B,KAAA;EAOA,OAAA;AAAA"}
1
+ {"version":3,"file":"searchPanel.d.ts","names":[],"sources":["../../src/editor/searchPanel.ts"],"mappings":";;;KAOY,UAAA,IAAc,WAAA,UAAqB,SAAS;AAAA,KAE5C,eAAA;AAAA,UAEK,YAAA;EACf,IAAA;EACA,WAAA;EACA,aAAA;EACA,SAAA;EACA,KAAA;AAAA;AAAA,UAGe,kBAAA;EACf,YAAA,EAAc,YAAA;EACd,gBAAA,EAAkB,WAAA;EAClB,YAAA;EACA,IAAA,GAAO,eAAA;EACP,YAAA,GAAe,UAAA;EACf,aAAA,GAAgB,SAAA,EAAW,UAAA,EAAY,WAAA;EACvC,YAAA,GAAe,KAAA,EAAO,gBAAA;EACtB,QAAA,GACE,OAAA,EAAS,UAAA,IACT,OAAA;IAAY,aAAA;EAAA,MACT,UAAA;EACL,OAAA;AAAA;AAAA,cAGW,iBAAA;EAAA;EAQX,WAAA,CAAY,OAAA,EAAS,kBAAA;EA8YrB,KAAA;EAIA,QAAA,CAAS,YAAA;EAIT,aAAA,CAAc,OAAA;IAAY,aAAA;EAAA;EAI1B,OAAA,CAAQ,IAAA,EAAM,eAAe;EAO7B,KAAA;EAOA,OAAA;AAAA"}
@@ -153,8 +153,15 @@ var SearchPanelWidget = class {
153
153
  searchParams.replaceText = e.target.value;
154
154
  },
155
155
  onkeydown: (e) => {
156
+ if (e.isComposing) return;
156
157
  const findAgain = resolveFindAgainShortcut(e);
157
- if (findAgain !== void 0) {
158
+ if (e.key === "Escape") {
159
+ e.preventDefault();
160
+ close();
161
+ } else if (e.key === "Enter") {
162
+ e.preventDefault();
163
+ replace();
164
+ } else if (findAgain !== void 0) {
158
165
  e.preventDefault();
159
166
  findNextMatch(findAgain === "previous", true);
160
167
  }
@@ -177,7 +184,7 @@ var SearchPanelWidget = class {
177
184
  close();
178
185
  } else if (e.key === "Enter") {
179
186
  e.preventDefault();
180
- findNextMatch(false, true);
187
+ findNextMatch(e.shiftKey, true);
181
188
  } else if (findAgain !== void 0) {
182
189
  e.preventDefault();
183
190
  findNextMatch(findAgain === "previous", true);
@@ -1 +1 @@
1
- {"version":3,"file":"searchPanel.js","names":["#updateMatches","#navigate","#close","#inputElement","#applyMode","#container"],"sources":["../../src/editor/searchPanel.ts"],"sourcesContent":["import { resolveFindAgainShortcut } from './command';\nimport { buildSearchReplacementText } from './pieceTable';\nimport { isPrimaryModifier } from './platform';\nimport { getEditorIconSvg, type SVGSpriteNames } from './sprite';\nimport type { ResolvedTextEdit, TextDocument } from './textDocument';\nimport { h } from './utils';\n\nexport type MatchRange = [startOffset: number, endOffset: number];\n\nexport type SearchPanelMode = 'find' | 'replace';\n\nexport interface SearchParams {\n text: string;\n replaceText: string;\n caseSensitive: boolean;\n wholeWord: boolean;\n regex: boolean;\n}\n\nexport interface SearchPanelOptions {\n textDocument: TextDocument<unknown>;\n containerElement: HTMLElement;\n defaultQuery: string;\n mode?: SearchPanelMode;\n initialMatch?: MatchRange;\n scrollToMatch: (nextMatch: MatchRange, retainFocus: boolean) => void;\n applyReplace: (edits: ResolvedTextEdit[]) => void;\n onUpdate: (\n matches: MatchRange[],\n options?: { syncSelection?: boolean }\n ) => MatchRange | undefined;\n onClose: () => void;\n}\n\nexport class SearchPanelWidget {\n #container: HTMLDivElement;\n #inputElement: HTMLInputElement;\n #updateMatches?: (options?: { syncSelection?: boolean }) => void;\n #applyMode?: (mode: SearchPanelMode) => void;\n #navigate?: (findPrevious: boolean) => void;\n #close?: () => void;\n\n constructor(options: SearchPanelOptions) {\n const {\n textDocument,\n containerElement,\n defaultQuery,\n mode = 'find',\n initialMatch,\n scrollToMatch,\n applyReplace,\n onUpdate,\n onClose,\n } = options;\n\n const searchParams: SearchParams = {\n text: defaultQuery,\n replaceText: '',\n caseSensitive: false,\n wholeWord: false,\n regex: false,\n };\n\n const matches = {\n all: [] as MatchRange[],\n current: undefined as MatchRange | undefined,\n };\n\n // Default to the empty-query \"no results\" state so it shows on open before\n // any search runs.\n const matchResultElement = h('div', {\n dataset: { matches: '', noMatches: '' },\n textContent: 'No results',\n });\n const updateMatches = (options?: { syncSelection?: boolean }) => {\n matches.all =\n searchParams.text !== '' ? textDocument.search(searchParams) : [];\n const noMatches = matches.all.length === 0;\n prevButton.disabled = noMatches;\n nextButton.disabled = noMatches;\n\n // An empty query and a query with zero hits are the same \"no results\"\n // state. Both push an empty match set through onUpdate so the editor\n // drops #matches and clears the painted highlights.\n if (noMatches) {\n matchResultElement.textContent = 'No results';\n matchResultElement.dataset.noMatches = '';\n matches.current = undefined;\n onUpdate([]);\n return;\n }\n\n delete matchResultElement.dataset.noMatches;\n if (options?.syncSelection === false) {\n const currentMatch = onUpdate(matches.all, { syncSelection: false });\n updateCurrentMatch(currentMatch);\n return;\n }\n\n updateCurrentMatch(onUpdate(matches.all));\n };\n this.#updateMatches = updateMatches;\n\n const updateCurrentMatch = (currentMatch: MatchRange | undefined) => {\n if (currentMatch === undefined) {\n matchResultElement.textContent = `${matches.all.length} results`;\n } else {\n const [start, end] = currentMatch;\n const index = matches.all.findIndex(\n (m) => m[0] === start && m[1] === end\n );\n matchResultElement.textContent = `${index + 1} of ${matches.all.length}`;\n }\n matches.current = currentMatch;\n };\n\n const updateSearchParam = <K extends keyof SearchParams>(\n key: K,\n value: SearchParams[K]\n ) => {\n searchParams[key] = value;\n updateMatches();\n };\n\n const findNextMatch = (\n findPrevious: boolean = false,\n retainFocus: boolean = false\n ) => {\n const allMatches = matches.all;\n let nextMatch: MatchRange | undefined = allMatches[0];\n if (allMatches.length > 0) {\n if (findPrevious) {\n const searchOffset = matches.current?.[0] ?? 0;\n nextMatch = allMatches.at(-1);\n for (const m of allMatches) {\n if (m[1] <= searchOffset) {\n nextMatch = m;\n } else {\n break;\n }\n }\n } else {\n const searchOffset = matches.current?.[1] ?? 0;\n for (const m of allMatches) {\n if (m[0] >= searchOffset) {\n nextMatch = m;\n break;\n }\n }\n }\n }\n if (nextMatch !== undefined) {\n updateCurrentMatch(nextMatch);\n scrollToMatch(nextMatch, retainFocus);\n }\n matches.current = nextMatch;\n };\n // Keep focus in the panel when stepping through matches from the inputs.\n this.#navigate = (findPrevious: boolean) =>\n findNextMatch(findPrevious, true);\n\n const buildReplacementEdit = (\n matchStart: number,\n matchEnd: number\n ): ResolvedTextEdit => ({\n start: matchStart,\n end: matchEnd,\n text: buildSearchReplacementText(\n (offset) => textDocument.positionAt(offset),\n (position) => textDocument.offsetAt(position),\n (line) => textDocument.getLineText(line),\n searchParams,\n matchStart,\n matchEnd\n ),\n });\n\n const replace = () => {\n if (searchParams.text === '' || matches.all.length === 0) {\n return;\n }\n\n let currentMatch = matches.current;\n if (currentMatch === undefined) {\n findNextMatch(false, true);\n currentMatch = matches.current;\n if (currentMatch === undefined) {\n return;\n }\n }\n\n const [start, end] = currentMatch;\n const edit = buildReplacementEdit(start, end);\n applyReplace([edit]);\n\n // Collapse after the replacement so the next search pass advances.\n scrollToMatch([start + edit.text.length, start + edit.text.length], true);\n matches.current = undefined;\n updateMatches();\n };\n\n const replaceAll = () => {\n if (searchParams.text === '' || matches.all.length === 0) {\n return;\n }\n\n applyReplace(\n matches.all.map(([start, end]) => buildReplacementEdit(start, end))\n );\n matches.current = undefined;\n updateMatches();\n };\n\n const close = () => {\n this.cleanup();\n onClose();\n };\n this.#close = close;\n\n // Shared builder for the panel's icon controls. Every clickable control is a\n // real <button> so it gets focus, keyboard activation, and native disabled\n // handling for free; `getEditorIconSvg` marks the glyph aria-hidden, so the\n // accessible name comes from `label` (also used as the hover title).\n const iconButton = (opts: {\n icon: SVGSpriteNames;\n label: string;\n size?: number;\n dataset?: Record<string, string>;\n onClick: () => void;\n }) =>\n h('button', {\n type: 'button',\n title: opts.label,\n ariaLabel: opts.label,\n dataset: { searchIcon: '', ...opts.dataset },\n innerHTML: getEditorIconSvg(opts.icon, opts.size ?? 16),\n onclick: opts.onClick,\n });\n\n // Builds an always-visible icon button that toggles one boolean search\n // option (case/whole-word/regex). The button reflects its on/off state via\n // `aria-pressed` so the stylesheet can highlight it and assistive tech can\n // announce it.\n const makeToggle = (\n icon: SVGSpriteNames,\n title: string,\n key: 'caseSensitive' | 'wholeWord' | 'regex'\n ) => {\n const button = iconButton({\n icon,\n label: title,\n size: 14,\n onClick: () => {\n const next = !searchParams[key];\n button.ariaPressed = String(next);\n updateSearchParam(key, next);\n },\n });\n button.ariaPressed = String(searchParams[key]);\n return button;\n };\n\n const caseSensitiveToggle = makeToggle(\n 'case',\n 'Match Case',\n 'caseSensitive'\n );\n const wholeWordToggle = makeToggle('whole-word', 'Whole Word', 'wholeWord');\n const regexToggle = makeToggle('regex', 'Regexp', 'regex');\n\n const replaceInputElement = h('input', {\n type: 'text',\n placeholder: 'Replace',\n dataset: 'replace',\n value: '',\n oninput: (e: Event) => {\n searchParams.replaceText = (e.target as HTMLInputElement).value;\n },\n onkeydown: (e: KeyboardEvent) => {\n const findAgain = resolveFindAgainShortcut(e);\n if (findAgain !== undefined) {\n e.preventDefault();\n findNextMatch(findAgain === 'previous', true);\n }\n },\n });\n\n this.#inputElement = h('input', {\n type: 'text',\n placeholder: 'Search',\n dataset: 'search',\n value: defaultQuery,\n oninput: (e: Event) => {\n searchParams.text = (e.target as HTMLInputElement).value;\n matches.current = undefined;\n updateMatches();\n },\n onkeydown: (e: KeyboardEvent) => {\n const findAgain = resolveFindAgainShortcut(e);\n if (e.key === 'Escape') {\n e.preventDefault();\n close();\n } else if (e.key === 'Enter') {\n e.preventDefault();\n findNextMatch(false, true);\n } else if (findAgain !== undefined) {\n // Override the native browser find-again shortcut so cmd+g steps to\n // the next match and cmd+shift+g to the previous one.\n e.preventDefault();\n findNextMatch(findAgain === 'previous', true);\n } else if (\n isPrimaryModifier(e) &&\n (e.key === 'f' || e.code === 'KeyF')\n ) {\n // Prevent the default browser search panel and switch the panel mode\n // in place (cmd+f -> find, cmd+opt+f -> find/replace).\n e.preventDefault();\n applyMode(e.altKey ? 'replace' : 'find');\n }\n },\n });\n\n // The three search-option toggles are overlaid on the right edge of the\n // find input instead of sitting beside it.\n const searchTogglesElement = h('div', {\n dataset: 'searchToggles',\n children: [caseSensitiveToggle, wholeWordToggle, regexToggle],\n });\n\n const findInputBox = h('div', {\n dataset: { inputBox: '', find: '' },\n children: [this.#inputElement, searchTogglesElement],\n });\n\n // The replace input and its action buttons are tagged as replace cells so\n // they can be hidden together when the panel is in find-only mode.\n const replaceInputBox = h('div', {\n dataset: { inputBox: '', replace: '', replaceCell: '' },\n children: [replaceInputElement],\n });\n\n const replaceActionsElement = h('div', {\n dataset: { replaceActions: '', replaceCell: '' },\n children: [\n iconButton({ icon: 'replace', label: 'Replace', onClick: replace }),\n iconButton({\n icon: 'replace-all',\n label: 'Replace All',\n onClick: replaceAll,\n }),\n ],\n });\n\n // Held so the no-results state can toggle their native disabled flag.\n const prevButton = iconButton({\n icon: 'arrow-up',\n label: 'Previous',\n onClick: () => {\n findNextMatch(true);\n },\n });\n const nextButton = iconButton({\n icon: 'arrow-down',\n label: 'Next',\n onClick: () => {\n findNextMatch();\n },\n });\n prevButton.disabled = true;\n nextButton.disabled = true;\n\n const navElement = h('div', {\n dataset: 'searchNav',\n children: [prevButton, nextButton],\n });\n\n const closeElement = iconButton({\n icon: 'close',\n label: 'Close',\n onClick: close,\n dataset: { searchClose: '' },\n });\n\n // Cells are positioned by CSS grid-template-areas (see editor.css), so DOM\n // order here only drives tab/reading order, not layout. Keep the replace\n // input directly after the find input (and its toggles) so Tab walks\n // find -> replace before reaching the nav arrows and close button.\n const gridElement = h('div', {\n dataset: { searchGrid: '', mode },\n children: [\n findInputBox,\n replaceInputBox,\n replaceActionsElement,\n matchResultElement,\n navElement,\n closeElement,\n ],\n });\n\n // Toggles the panel between find and find/replace modes by showing or\n // hiding the replace cells, then returns focus to the find input.\n const applyMode = (next: SearchPanelMode) => {\n gridElement.dataset.mode = next;\n this.#inputElement.focus();\n this.#inputElement.select();\n };\n this.#applyMode = applyMode;\n\n this.#container = h('div', {\n dataset: 'searchPanel',\n children: [\n h('div', {\n dataset: 'editorWidget',\n children: [gridElement],\n }),\n ],\n });\n\n matches.current = initialMatch;\n containerElement.before(this.#container);\n\n requestAnimationFrame(() => {\n if (initialMatch !== undefined) {\n updateMatches();\n } else {\n onUpdate([]);\n }\n this.#inputElement.select();\n });\n }\n\n focus(): void {\n this.#inputElement.focus();\n }\n\n navigate(findPrevious: boolean): void {\n this.#navigate?.(findPrevious);\n }\n\n updateMatches(options?: { syncSelection?: boolean }): void {\n this.#updateMatches?.(options);\n }\n\n setMode(mode: SearchPanelMode): void {\n this.#applyMode?.(mode);\n }\n\n // Tears the panel down the way the close button and Escape-in-input do:\n // removes the DOM and runs onClose so the editor clears its match highlights.\n // Prefer this over cleanup() for any user-initiated dismissal.\n close(): void {\n this.#close?.();\n }\n\n // DOM-only teardown. Does NOT run onClose, so it leaves the editor's match\n // highlights in place; only use it when the caller clears that state itself\n // (e.g. a full editor reset). User-initiated dismissals should call close().\n cleanup(): void {\n this.#container.remove();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"searchPanel.js","names":["#updateMatches","#navigate","#close","#inputElement","#applyMode","#container"],"sources":["../../src/editor/searchPanel.ts"],"sourcesContent":["import { resolveFindAgainShortcut } from './command';\nimport { buildSearchReplacementText } from './pieceTable';\nimport { isPrimaryModifier } from './platform';\nimport { getEditorIconSvg, type SVGSpriteNames } from './sprite';\nimport type { ResolvedTextEdit, TextDocument } from './textDocument';\nimport { h } from './utils';\n\nexport type MatchRange = [startOffset: number, endOffset: number];\n\nexport type SearchPanelMode = 'find' | 'replace';\n\nexport interface SearchParams {\n text: string;\n replaceText: string;\n caseSensitive: boolean;\n wholeWord: boolean;\n regex: boolean;\n}\n\nexport interface SearchPanelOptions {\n textDocument: TextDocument<unknown>;\n containerElement: HTMLElement;\n defaultQuery: string;\n mode?: SearchPanelMode;\n initialMatch?: MatchRange;\n scrollToMatch: (nextMatch: MatchRange, retainFocus: boolean) => void;\n applyReplace: (edits: ResolvedTextEdit[]) => void;\n onUpdate: (\n matches: MatchRange[],\n options?: { syncSelection?: boolean }\n ) => MatchRange | undefined;\n onClose: () => void;\n}\n\nexport class SearchPanelWidget {\n #container: HTMLDivElement;\n #inputElement: HTMLInputElement;\n #updateMatches?: (options?: { syncSelection?: boolean }) => void;\n #applyMode?: (mode: SearchPanelMode) => void;\n #navigate?: (findPrevious: boolean) => void;\n #close?: () => void;\n\n constructor(options: SearchPanelOptions) {\n const {\n textDocument,\n containerElement,\n defaultQuery,\n mode = 'find',\n initialMatch,\n scrollToMatch,\n applyReplace,\n onUpdate,\n onClose,\n } = options;\n\n const searchParams: SearchParams = {\n text: defaultQuery,\n replaceText: '',\n caseSensitive: false,\n wholeWord: false,\n regex: false,\n };\n\n const matches = {\n all: [] as MatchRange[],\n current: undefined as MatchRange | undefined,\n };\n\n // Default to the empty-query \"no results\" state so it shows on open before\n // any search runs.\n const matchResultElement = h('div', {\n dataset: { matches: '', noMatches: '' },\n textContent: 'No results',\n });\n const updateMatches = (options?: { syncSelection?: boolean }) => {\n matches.all =\n searchParams.text !== '' ? textDocument.search(searchParams) : [];\n const noMatches = matches.all.length === 0;\n prevButton.disabled = noMatches;\n nextButton.disabled = noMatches;\n\n // An empty query and a query with zero hits are the same \"no results\"\n // state. Both push an empty match set through onUpdate so the editor\n // drops #matches and clears the painted highlights.\n if (noMatches) {\n matchResultElement.textContent = 'No results';\n matchResultElement.dataset.noMatches = '';\n matches.current = undefined;\n onUpdate([]);\n return;\n }\n\n delete matchResultElement.dataset.noMatches;\n if (options?.syncSelection === false) {\n const currentMatch = onUpdate(matches.all, { syncSelection: false });\n updateCurrentMatch(currentMatch);\n return;\n }\n\n updateCurrentMatch(onUpdate(matches.all));\n };\n this.#updateMatches = updateMatches;\n\n const updateCurrentMatch = (currentMatch: MatchRange | undefined) => {\n if (currentMatch === undefined) {\n matchResultElement.textContent = `${matches.all.length} results`;\n } else {\n const [start, end] = currentMatch;\n const index = matches.all.findIndex(\n (m) => m[0] === start && m[1] === end\n );\n matchResultElement.textContent = `${index + 1} of ${matches.all.length}`;\n }\n matches.current = currentMatch;\n };\n\n const updateSearchParam = <K extends keyof SearchParams>(\n key: K,\n value: SearchParams[K]\n ) => {\n searchParams[key] = value;\n updateMatches();\n };\n\n const findNextMatch = (\n findPrevious: boolean = false,\n retainFocus: boolean = false\n ) => {\n const allMatches = matches.all;\n let nextMatch: MatchRange | undefined = allMatches[0];\n if (allMatches.length > 0) {\n if (findPrevious) {\n const searchOffset = matches.current?.[0] ?? 0;\n nextMatch = allMatches.at(-1);\n for (const m of allMatches) {\n if (m[1] <= searchOffset) {\n nextMatch = m;\n } else {\n break;\n }\n }\n } else {\n const searchOffset = matches.current?.[1] ?? 0;\n for (const m of allMatches) {\n if (m[0] >= searchOffset) {\n nextMatch = m;\n break;\n }\n }\n }\n }\n if (nextMatch !== undefined) {\n updateCurrentMatch(nextMatch);\n scrollToMatch(nextMatch, retainFocus);\n }\n matches.current = nextMatch;\n };\n // Keep focus in the panel when stepping through matches from the inputs.\n this.#navigate = (findPrevious: boolean) =>\n findNextMatch(findPrevious, true);\n\n const buildReplacementEdit = (\n matchStart: number,\n matchEnd: number\n ): ResolvedTextEdit => ({\n start: matchStart,\n end: matchEnd,\n text: buildSearchReplacementText(\n (offset) => textDocument.positionAt(offset),\n (position) => textDocument.offsetAt(position),\n (line) => textDocument.getLineText(line),\n searchParams,\n matchStart,\n matchEnd\n ),\n });\n\n const replace = () => {\n if (searchParams.text === '' || matches.all.length === 0) {\n return;\n }\n\n let currentMatch = matches.current;\n if (currentMatch === undefined) {\n findNextMatch(false, true);\n currentMatch = matches.current;\n if (currentMatch === undefined) {\n return;\n }\n }\n\n const [start, end] = currentMatch;\n const edit = buildReplacementEdit(start, end);\n applyReplace([edit]);\n\n // Collapse after the replacement so the next search pass advances.\n scrollToMatch([start + edit.text.length, start + edit.text.length], true);\n matches.current = undefined;\n updateMatches();\n };\n\n const replaceAll = () => {\n if (searchParams.text === '' || matches.all.length === 0) {\n return;\n }\n\n applyReplace(\n matches.all.map(([start, end]) => buildReplacementEdit(start, end))\n );\n matches.current = undefined;\n updateMatches();\n };\n\n const close = () => {\n this.cleanup();\n onClose();\n };\n this.#close = close;\n\n // Shared builder for the panel's icon controls. Every clickable control is a\n // real <button> so it gets focus, keyboard activation, and native disabled\n // handling for free; `getEditorIconSvg` marks the glyph aria-hidden, so the\n // accessible name comes from `label` (also used as the hover title).\n const iconButton = (opts: {\n icon: SVGSpriteNames;\n label: string;\n size?: number;\n dataset?: Record<string, string>;\n onClick: () => void;\n }) =>\n h('button', {\n type: 'button',\n title: opts.label,\n ariaLabel: opts.label,\n dataset: { searchIcon: '', ...opts.dataset },\n innerHTML: getEditorIconSvg(opts.icon, opts.size ?? 16),\n onclick: opts.onClick,\n });\n\n // Builds an always-visible icon button that toggles one boolean search\n // option (case/whole-word/regex). The button reflects its on/off state via\n // `aria-pressed` so the stylesheet can highlight it and assistive tech can\n // announce it.\n const makeToggle = (\n icon: SVGSpriteNames,\n title: string,\n key: 'caseSensitive' | 'wholeWord' | 'regex'\n ) => {\n const button = iconButton({\n icon,\n label: title,\n size: 14,\n onClick: () => {\n const next = !searchParams[key];\n button.ariaPressed = String(next);\n updateSearchParam(key, next);\n },\n });\n button.ariaPressed = String(searchParams[key]);\n return button;\n };\n\n const caseSensitiveToggle = makeToggle(\n 'case',\n 'Match Case',\n 'caseSensitive'\n );\n const wholeWordToggle = makeToggle('whole-word', 'Whole Word', 'wholeWord');\n const regexToggle = makeToggle('regex', 'Regexp', 'regex');\n\n const replaceInputElement = h('input', {\n type: 'text',\n placeholder: 'Replace',\n dataset: 'replace',\n value: '',\n oninput: (e: Event) => {\n searchParams.replaceText = (e.target as HTMLInputElement).value;\n },\n onkeydown: (e: KeyboardEvent) => {\n if (e.isComposing) {\n return;\n }\n const findAgain = resolveFindAgainShortcut(e);\n if (e.key === 'Escape') {\n e.preventDefault();\n close();\n } else if (e.key === 'Enter') {\n e.preventDefault();\n replace();\n } else if (findAgain !== undefined) {\n e.preventDefault();\n findNextMatch(findAgain === 'previous', true);\n }\n },\n });\n\n this.#inputElement = h('input', {\n type: 'text',\n placeholder: 'Search',\n dataset: 'search',\n value: defaultQuery,\n oninput: (e: Event) => {\n searchParams.text = (e.target as HTMLInputElement).value;\n matches.current = undefined;\n updateMatches();\n },\n onkeydown: (e: KeyboardEvent) => {\n const findAgain = resolveFindAgainShortcut(e);\n if (e.key === 'Escape') {\n e.preventDefault();\n close();\n } else if (e.key === 'Enter') {\n e.preventDefault();\n findNextMatch(e.shiftKey, true);\n } else if (findAgain !== undefined) {\n // Override the native browser find-again shortcut so cmd+g steps to\n // the next match and cmd+shift+g to the previous one.\n e.preventDefault();\n findNextMatch(findAgain === 'previous', true);\n } else if (\n isPrimaryModifier(e) &&\n (e.key === 'f' || e.code === 'KeyF')\n ) {\n // Prevent the default browser search panel and switch the panel mode\n // in place (cmd+f -> find, cmd+opt+f -> find/replace).\n e.preventDefault();\n applyMode(e.altKey ? 'replace' : 'find');\n }\n },\n });\n\n // The three search-option toggles are overlaid on the right edge of the\n // find input instead of sitting beside it.\n const searchTogglesElement = h('div', {\n dataset: 'searchToggles',\n children: [caseSensitiveToggle, wholeWordToggle, regexToggle],\n });\n\n const findInputBox = h('div', {\n dataset: { inputBox: '', find: '' },\n children: [this.#inputElement, searchTogglesElement],\n });\n\n // The replace input and its action buttons are tagged as replace cells so\n // they can be hidden together when the panel is in find-only mode.\n const replaceInputBox = h('div', {\n dataset: { inputBox: '', replace: '', replaceCell: '' },\n children: [replaceInputElement],\n });\n\n const replaceActionsElement = h('div', {\n dataset: { replaceActions: '', replaceCell: '' },\n children: [\n iconButton({ icon: 'replace', label: 'Replace', onClick: replace }),\n iconButton({\n icon: 'replace-all',\n label: 'Replace All',\n onClick: replaceAll,\n }),\n ],\n });\n\n // Held so the no-results state can toggle their native disabled flag.\n const prevButton = iconButton({\n icon: 'arrow-up',\n label: 'Previous',\n onClick: () => {\n findNextMatch(true);\n },\n });\n const nextButton = iconButton({\n icon: 'arrow-down',\n label: 'Next',\n onClick: () => {\n findNextMatch();\n },\n });\n prevButton.disabled = true;\n nextButton.disabled = true;\n\n const navElement = h('div', {\n dataset: 'searchNav',\n children: [prevButton, nextButton],\n });\n\n const closeElement = iconButton({\n icon: 'close',\n label: 'Close',\n onClick: close,\n dataset: { searchClose: '' },\n });\n\n // Cells are positioned by CSS grid-template-areas (see editor.css), so DOM\n // order here only drives tab/reading order, not layout. Keep the replace\n // input directly after the find input (and its toggles) so Tab walks\n // find -> replace before reaching the nav arrows and close button.\n const gridElement = h('div', {\n dataset: { searchGrid: '', mode },\n children: [\n findInputBox,\n replaceInputBox,\n replaceActionsElement,\n matchResultElement,\n navElement,\n closeElement,\n ],\n });\n\n // Toggles the panel between find and find/replace modes by showing or\n // hiding the replace cells, then returns focus to the find input.\n const applyMode = (next: SearchPanelMode) => {\n gridElement.dataset.mode = next;\n this.#inputElement.focus();\n this.#inputElement.select();\n };\n this.#applyMode = applyMode;\n\n this.#container = h('div', {\n dataset: 'searchPanel',\n children: [\n h('div', {\n dataset: 'editorWidget',\n children: [gridElement],\n }),\n ],\n });\n\n matches.current = initialMatch;\n containerElement.before(this.#container);\n\n requestAnimationFrame(() => {\n if (initialMatch !== undefined) {\n updateMatches();\n } else {\n onUpdate([]);\n }\n this.#inputElement.select();\n });\n }\n\n focus(): void {\n this.#inputElement.focus();\n }\n\n navigate(findPrevious: boolean): void {\n this.#navigate?.(findPrevious);\n }\n\n updateMatches(options?: { syncSelection?: boolean }): void {\n this.#updateMatches?.(options);\n }\n\n setMode(mode: SearchPanelMode): void {\n this.#applyMode?.(mode);\n }\n\n // Tears the panel down the way the close button and Escape-in-input do:\n // removes the DOM and runs onClose so the editor clears its match highlights.\n // Prefer this over cleanup() for any user-initiated dismissal.\n close(): void {\n this.#close?.();\n }\n\n // DOM-only teardown. Does NOT run onClose, so it leaves the editor's match\n // highlights in place; only use it when the caller clears that state itself\n // (e.g. a full editor reset). User-initiated dismissals should call close().\n cleanup(): void {\n this.#container.remove();\n }\n}\n"],"mappings":"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"}
@@ -1,13 +1,20 @@
1
- import { DiffLineAnnotation } from "../types.js";
2
- import { Position, Range, ResolvedTextEdit, TextDocument, TextDocumentChange, TextEdit } from "./textDocument.js";
1
+ import { DiffLineAnnotation, EditorSelection, Position, Range, SelectionDirection, TextEdit } from "../types.js";
2
+ import { ResolvedTextEdit, TextDocument, TextDocumentChange } from "./textDocument.js";
3
3
 
4
4
  //#region src/editor/selection.d.ts
5
5
  declare const DirectionBackward = -1;
6
6
  declare const DirectionNone = 0;
7
7
  declare const DirectionForward = 1;
8
- type SelectionDirection = typeof DirectionBackward | typeof DirectionNone | typeof DirectionForward;
9
- interface EditorSelection extends Range {
10
- direction: SelectionDirection;
8
+ interface CursorMoveOptions {
9
+ getSoftLineOffsets?: (line: number) => ArrayLike<number> | undefined;
10
+ /**
11
+ * Fold-skip resolver for vertical motion crossing document lines: the
12
+ * nearest renderable line at or beyond the target in the move direction,
13
+ * or undefined when everything that way is hidden inside a collapsed
14
+ * region (the caret then stays put). Soft-line moves within one document
15
+ * line never consult it.
16
+ */
17
+ resolveRenderableLine?: ResolveRenderableLine;
11
18
  }
12
19
  /**
13
20
  * Converts a selection from a web selection to an editor selection.
@@ -17,14 +24,21 @@ declare function convertSelection(range: StaticRange, direction?: SelectionDirec
17
24
  * Resolves the indent edits for a selection.
18
25
  */
19
26
  declare function resolveIndentEdits(textDocument: TextDocument<unknown>, selection: EditorSelection, tabSize: number, outdent: boolean): [edits: TextEdit[], nextSelection: EditorSelection];
27
+ /**
28
+ * The nearest zero-based document line at or beyond `line` in `direction`
29
+ * that has a rendered row, or undefined when everything that way is hidden
30
+ * inside a collapsed region. Vertical caret motion uses it to skip collapsed
31
+ * regions atomically, like code folds.
32
+ */
33
+ type ResolveRenderableLine = (line: number, direction: 'up' | 'down') => number | undefined;
20
34
  /**
21
35
  * Maps the cursor move to all selections.
22
36
  */
23
- declare function mapCursorMove(textDocument: TextDocument<unknown>, selections: EditorSelection[], shortcut: 'textStart' | 'start' | 'end' | 'up' | 'down' | 'left' | 'right'): EditorSelection[];
37
+ declare function mapCursorMove(textDocument: TextDocument<unknown>, selections: EditorSelection[], shortcut: 'textStart' | 'start' | 'end' | 'up' | 'down' | 'left' | 'right', options?: CursorMoveOptions): EditorSelection[];
24
38
  /**
25
39
  * Same as mapCursorMove, but with shift key pressed.
26
40
  */
27
- declare function mapSelectionShift(textDocument: TextDocument<unknown>, selections: EditorSelection[], shortcut: 'textStart' | 'start' | 'end' | 'up' | 'down' | 'left' | 'right'): EditorSelection[];
41
+ declare function mapSelectionShift(textDocument: TextDocument<unknown>, selections: EditorSelection[], shortcut: 'textStart' | 'start' | 'end' | 'up' | 'down' | 'left' | 'right', options?: CursorMoveOptions): EditorSelection[];
28
42
  /**
29
43
  * Applies a text change to the given text document
30
44
  */
@@ -33,9 +47,10 @@ declare function applyTextChangeToSelections<LAnnotation>(textDocument: TextDocu
33
47
  change?: TextDocumentChange;
34
48
  };
35
49
  /**
36
- * Applies a text replace to multiple selections.
50
+ * Applies text replacements to multiple selections. Texts pair by selection
51
+ * index unless they are explicitly marked as document ordered.
37
52
  */
38
- declare function applyTextReplaceToSelections<LAnnotation>(textDocument: TextDocument<LAnnotation>, selections: EditorSelection[], texts: string[], lineAnnotations?: DiffLineAnnotation<LAnnotation>[], undoBoundary?: boolean): {
53
+ declare function applyTextReplaceToSelections<LAnnotation>(textDocument: TextDocument<LAnnotation>, selections: EditorSelection[], texts: string[], lineAnnotations?: DiffLineAnnotation<LAnnotation>[], undoBoundary?: boolean, textOrder?: 'selection' | 'document'): {
39
54
  nextSelections: EditorSelection[];
40
55
  change?: TextDocumentChange;
41
56
  };
@@ -156,7 +171,7 @@ declare function shiftSelectionLines(selection: EditorSelection, direction: -1 |
156
171
  /**
157
172
  * Finds the next matching word and updates the selections.
158
173
  */
159
- declare function findNexMatch(textDocument: TextDocument<unknown>, selections: EditorSelection[]): EditorSelection[] | undefined;
174
+ declare function findNextMatch(textDocument: TextDocument<unknown>, selections: EditorSelection[]): EditorSelection[] | undefined;
160
175
  /**
161
176
  * Get the full selection of the document.
162
177
  */
@@ -165,6 +180,11 @@ declare function getDocumentFullSelection(textDocument: TextDocument<unknown>):
165
180
  * Get the boundary selection of the document.
166
181
  */
167
182
  declare function getDocumentBoundarySelection(textDocument: TextDocument<unknown>, atEnd: boolean, trimmedEndNewLine?: boolean): EditorSelection;
183
+ /**
184
+ * Gets the text contributed by each selection in document order, preserving
185
+ * the pairing needed to paste the values into another set of selections.
186
+ */
187
+ declare function getSelectionClipboardTexts(textDocument: TextDocument<unknown>, selections: EditorSelection[]): string[];
168
188
  /**
169
189
  * Get the clipboard text of the selections for the given text document. Used by
170
190
  * both copy and cut so the two stay in sync. Overlapping regions (e.g. several
@@ -187,5 +207,5 @@ declare function getSelectionAnchor(lineElement: HTMLElement, character: number)
187
207
  */
188
208
  declare function expandCollapsedSelectionToWord(textDocument: TextDocument<unknown>, selection: EditorSelection): EditorSelection;
189
209
  //#endregion
190
- export { AutoSurround, DirectionBackward, DirectionForward, DirectionNone, EditorSelection, SelectionDirection, applyDeleteCharacterToSelections, applyDeleteHardLineForwardToSelections, applyDeleteSoftLineBackwardToSelections, applyDeleteWordBackwardToSelections, applyTextChangeToSelections, applyTextReplaceToSelections, applyTransposeToSelections, comparePosition, convertSelection, createSelectionFrom, createSelectionFromAnchorAndFocusOffsets, expandCollapsedSelectionToWord, extendSelection, extendSelections, findNexMatch, getAutoSurroundReplacementTexts, getCaretPosition, getDocumentBoundarySelection, getDocumentFullSelection, getSelectedLineBlocks, getSelectionAnchor, getSelectionText, isCollapsedSelection, isLineEditable, mapCursorMove, mapSelectionShift, mergeOverlappingSelections, remapSelectionsAfterEdits, resolveDeleteCharacterRange, resolveIndentEdits, resolveSelectionCut, selectionIntersects, shiftSelectionLines };
210
+ export { AutoSurround, CursorMoveOptions, DirectionBackward, DirectionForward, DirectionNone, ResolveRenderableLine, applyDeleteCharacterToSelections, applyDeleteHardLineForwardToSelections, applyDeleteSoftLineBackwardToSelections, applyDeleteWordBackwardToSelections, applyTextChangeToSelections, applyTextReplaceToSelections, applyTransposeToSelections, comparePosition, convertSelection, createSelectionFrom, createSelectionFromAnchorAndFocusOffsets, expandCollapsedSelectionToWord, extendSelection, extendSelections, findNextMatch, getAutoSurroundReplacementTexts, getCaretPosition, getDocumentBoundarySelection, getDocumentFullSelection, getSelectedLineBlocks, getSelectionAnchor, getSelectionClipboardTexts, getSelectionText, isCollapsedSelection, isLineEditable, mapCursorMove, mapSelectionShift, mergeOverlappingSelections, remapSelectionsAfterEdits, resolveDeleteCharacterRange, resolveIndentEdits, resolveSelectionCut, selectionIntersects, shiftSelectionLines };
191
211
  //# sourceMappingURL=selection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"selection.d.ts","names":[],"sources":["../../src/editor/selection.ts"],"mappings":"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"}
1
+ {"version":3,"file":"selection.d.ts","names":[],"sources":["../../src/editor/selection.ts"],"mappings":";;;;cAoBa,iBAAA;AAAA,cACA,aAAA;AAAA,cACA,gBAAA;AAAA,UAgBI,iBAAA;EACf,kBAAA,IAAsB,IAAA,aAAiB,SAAA;EAnBX;AAAA;AAC9B;;;;AAA0B;EA0BxB,qBAAA,GAAwB,qBAAqB;AAAA;;;AAzBlB;iBAsCb,gBAAA,CACd,KAAA,EAAO,WAAA,EACP,SAAA,GAAW,kBAAA,GACV,eAAA;;;;iBAgBa,kBAAA,CACd,YAAA,EAAc,YAAA,WACd,SAAA,EAAW,eAAA,EACX,OAAA,UACA,OAAA,aACE,KAAA,EAAO,QAAA,IAAY,aAAA,EAAe,eAAA;;;;;;AArCS;KA6GnC,qBAAA,IACV,IAAA,UACA,SAAwB;;;;iBAMV,aAAA,CACd,YAAA,EAAc,YAAA,WACd,UAAA,EAAY,eAAA,IACZ,QAAA,oEACA,OAAA,GAAS,iBAAA,GACR,eAAA;;;;iBAsRa,iBAAA,CACd,YAAA,EAAc,YAAA,WACd,UAAA,EAAY,eAAA,IACZ,QAAA,oEACA,OAAA,GAAS,iBAAA,GACR,eAAA;;;;iBAyBa,2BAAA,cACd,YAAA,EAAc,YAAA,CAAa,WAAA,GAC3B,UAAA,EAAY,eAAA,IACZ,IAAA,EAAM,gBAAA,EACN,eAAA,GAAkB,kBAAA,CAAmB,WAAA,KACrC,OAAA,WACA,YAAA;EAEA,cAAA,EAAgB,eAAA;EAChB,MAAA,GAAS,kBAAA;AAAA;;;;;iBAoNK,4BAAA,cACd,YAAA,EAAc,YAAA,CAAa,WAAA,GAC3B,UAAA,EAAY,eAAA,IACZ,KAAA,YACA,eAAA,GAAkB,kBAAA,CAAmB,WAAA,KACrC,YAAA,YACA,SAAA;EAEA,cAAA,EAAgB,eAAA;EAChB,MAAA,GAAS,kBAAA;AAAA;AAAA,KAkKC,YAAA;;;;;iBA2BI,+BAAA,cACd,YAAA,EAAc,YAAA,CAAa,WAAA,GAC3B,UAAA,EAAY,eAAA,IACZ,IAAA,UACA,YAAA,GAAe,YAAA;;;;;iBAuBD,0BAAA,cACd,YAAA,EAAc,YAAA,CAAa,WAAA,GAC3B,UAAA,EAAY,eAAA,IACZ,eAAA,GAAkB,kBAAA,CAAmB,WAAA;EAErC,cAAA,EAAgB,eAAA;EAChB,MAAA,GAAS,kBAAA;AAAA;;;AAnwBe;AAM1B;;iBAi4BgB,sCAAA,cACd,YAAA,EAAc,YAAA,CAAa,WAAA,GAC3B,UAAA,EAAY,eAAA,IACZ,eAAA,GAAkB,kBAAA,CAAmB,WAAA;EAErC,cAAA,EAAgB,eAAA;EAChB,MAAA,GAAS,kBAAA;AAAA;;;;;iBAsBK,uCAAA,cACd,YAAA,EAAc,YAAA,CAAa,WAAA,GAC3B,UAAA,EAAY,eAAA,IACZ,gBAAA,IAAoB,IAAA,UAAc,SAAA,qBAClC,eAAA,GAAkB,kBAAA,CAAmB,WAAA;EAErC,cAAA,EAAgB,eAAA;EAChB,MAAA,GAAS,kBAAA;AAAA;;;;;iBA8CK,mCAAA,cACd,YAAA,EAAc,YAAA,CAAa,WAAA,GAC3B,UAAA,EAAY,eAAA,IACZ,eAAA,GAAkB,kBAAA,CAAmB,WAAA;EAErC,cAAA,EAAgB,eAAA;EAChB,MAAA,GAAS,kBAAA;AAAA;;;;;iBAyBK,2BAAA,CACd,YAAA,EAAc,YAAA,WACd,SAAA,EAAW,eAAA,EACX,OAAA,aACE,KAAA,EAAO,QAAA,EAAU,GAAA,EAAK,QAAA;;;;iBAiEV,gCAAA,cACd,YAAA,EAAc,YAAA,CAAa,WAAA,GAC3B,UAAA,EAAY,eAAA,IACZ,OAAA,WACA,eAAA,GAAkB,kBAAA,CAAmB,WAAA,KACrC,OAAA;EAEA,cAAA,EAAgB,eAAA;EAChB,MAAA,GAAS,kBAAA;AAAA;;;;iBAyCK,oBAAA,CACd,SAAA,EAAW,eAAA,GAAkB,KAAK;AA/yBpC;;;AAAA,iBA0zBgB,gBAAA,CAAiB,SAAA,EAAW,eAAA,GAAkB,QAAQ;;;;iBAQtD,cAAA,CAAe,QAAgB;;;;iBAW/B,mBAAA,CACd,CAAA,EAAG,eAAA,GAAkB,KAAA,EACrB,CAAA,EAAG,eAAA,GAAkB,KAAA;;;;iBA2BP,eAAA,CAAgB,CAAA,EAAG,QAAA,EAAU,CAAA,EAAG,QAAQ;;;;iBAUxC,wCAAA,CACd,YAAA,EAAc,YAAA,WACd,YAAA,UACA,WAAA,WACC,eAAe;;;;;;;;;;iBAmDF,yBAAA,CACd,YAAA,EAAc,YAAA,WACd,UAAA,WAAqB,eAAA,IACrB,gBAAA,EAAkB,aAAA,6BAClB,KAAA,WAAgB,gBAAA,KACf,eAAA;;AAv6B0B;AAoN7B;iBAuuBgB,mBAAA,CACd,eAAA,EAAiB,eAAA,EACjB,cAAA,EAAgB,eAAA,GACf,eAAA;;;;;iBAqCa,eAAA,CACd,QAAA,EAAU,eAAA,EACV,MAAA,EAAQ,eAAA,GACP,eAAA;;;;iBA4Ca,gBAAA,CACd,UAAA,EAAY,eAAA,IACZ,MAAA,EAAQ,eAAA,GACP,eAAA;;;;iBAUa,0BAAA,CACd,UAAA,EAAY,eAAA,KACX,eAAe;;;;iBAuDF,qBAAA,CACd,UAAA,EAAY,eAAe;EACxB,SAAA;EAAmB,OAAA;AAAA;;;;;iBAoCR,mBAAA,CACd,SAAA,EAAW,eAAA,EACX,SAAA,UACA,SAAA,UACA,aAAA,GAAgB,IAAA,sBACf,eAAe;;;AAt6BW;iBAm8Bb,aAAA,CACd,YAAA,EAAc,YAAA,WACd,UAAA,EAAY,eAAA,KACX,eAAA;;;;iBAkDa,wBAAA,CACd,YAAA,EAAc,YAAA,YACb,eAAe;AA7zBlB;;;AAAA,iBA00BgB,4BAAA,CACd,YAAA,EAAc,YAAA,WACd,KAAA,WACA,iBAAA,aACC,eAAe;;;;;iBAoEF,0BAAA,CACd,YAAA,EAAc,YAAA,WACd,UAAA,EAAY,eAAe;;;;;;;;iBAab,gBAAA,CACd,YAAA,EAAc,YAAA,WACd,UAAA,EAAY,eAAe;AAAA,iBA2Ib,mBAAA,CACd,YAAA,EAAc,YAAA,WACd,UAAA,EAAY,eAAA;EAEZ,IAAA;EACA,KAAA,EAAO,gBAAA;EACP,oBAAA;AAAA;;;;iBA4Bc,kBAAA,CACd,WAAA,EAAa,WAAA,EACb,SAAA,YACE,IAAI;;;;iBA+DQ,8BAAA,CACd,YAAA,EAAc,YAAA,WACd,SAAA,EAAW,eAAA,GACV,eAAA"}