@kingkoo1985/ink 6.6.6

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 (225) hide show
  1. package/build/colorize.d.ts +4 -0
  2. package/build/colorize.js +59 -0
  3. package/build/colorize.js.map +1 -0
  4. package/build/components/AccessibilityContext.d.ts +3 -0
  5. package/build/components/AccessibilityContext.js +5 -0
  6. package/build/components/AccessibilityContext.js.map +1 -0
  7. package/build/components/App.d.ts +68 -0
  8. package/build/components/App.js +290 -0
  9. package/build/components/App.js.map +1 -0
  10. package/build/components/AppContext.d.ts +52 -0
  11. package/build/components/AppContext.js +16 -0
  12. package/build/components/AppContext.js.map +1 -0
  13. package/build/components/BackgroundContext.d.ts +4 -0
  14. package/build/components/BackgroundContext.js +3 -0
  15. package/build/components/BackgroundContext.js.map +1 -0
  16. package/build/components/Box.d.ts +171 -0
  17. package/build/components/Box.js +40 -0
  18. package/build/components/Box.js.map +1 -0
  19. package/build/components/ErrorOverview.d.ts +6 -0
  20. package/build/components/ErrorOverview.js +84 -0
  21. package/build/components/ErrorOverview.js.map +1 -0
  22. package/build/components/FocusContext.d.ts +16 -0
  23. package/build/components/FocusContext.js +16 -0
  24. package/build/components/FocusContext.js.map +1 -0
  25. package/build/components/Newline.d.ts +13 -0
  26. package/build/components/Newline.js +8 -0
  27. package/build/components/Newline.js.map +1 -0
  28. package/build/components/Spacer.d.ts +7 -0
  29. package/build/components/Spacer.js +11 -0
  30. package/build/components/Spacer.js.map +1 -0
  31. package/build/components/Static.d.ts +24 -0
  32. package/build/components/Static.js +29 -0
  33. package/build/components/Static.js.map +1 -0
  34. package/build/components/StaticRender.d.ts +8 -0
  35. package/build/components/StaticRender.js +19 -0
  36. package/build/components/StaticRender.js.map +1 -0
  37. package/build/components/StderrContext.d.ts +15 -0
  38. package/build/components/StderrContext.js +12 -0
  39. package/build/components/StderrContext.js.map +1 -0
  40. package/build/components/StdinContext.d.ts +22 -0
  41. package/build/components/StdinContext.js +16 -0
  42. package/build/components/StdinContext.js.map +1 -0
  43. package/build/components/StdoutContext.d.ts +15 -0
  44. package/build/components/StdoutContext.js +12 -0
  45. package/build/components/StdoutContext.js.map +1 -0
  46. package/build/components/Text.d.ts +63 -0
  47. package/build/components/Text.js +50 -0
  48. package/build/components/Text.js.map +1 -0
  49. package/build/components/Transform.d.ts +16 -0
  50. package/build/components/Transform.js +15 -0
  51. package/build/components/Transform.js.map +1 -0
  52. package/build/data-limited-lru-map.d.ts +20 -0
  53. package/build/data-limited-lru-map.js +65 -0
  54. package/build/data-limited-lru-map.js.map +1 -0
  55. package/build/debug-log.d.ts +2 -0
  56. package/build/debug-log.js +44 -0
  57. package/build/debug-log.js.map +1 -0
  58. package/build/devtools-window-polyfill.d.ts +1 -0
  59. package/build/devtools-window-polyfill.js +65 -0
  60. package/build/devtools-window-polyfill.js.map +1 -0
  61. package/build/devtools.d.ts +1 -0
  62. package/build/devtools.js +8 -0
  63. package/build/devtools.js.map +1 -0
  64. package/build/dom.d.ts +114 -0
  65. package/build/dom.js +169 -0
  66. package/build/dom.js.map +1 -0
  67. package/build/get-max-width.d.ts +3 -0
  68. package/build/get-max-width.js +10 -0
  69. package/build/get-max-width.js.map +1 -0
  70. package/build/hooks/use-app.d.ts +5 -0
  71. package/build/hooks/use-app.js +8 -0
  72. package/build/hooks/use-app.js.map +1 -0
  73. package/build/hooks/use-focus-manager.d.ts +28 -0
  74. package/build/hooks/use-focus-manager.js +17 -0
  75. package/build/hooks/use-focus-manager.js.map +1 -0
  76. package/build/hooks/use-focus.d.ts +29 -0
  77. package/build/hooks/use-focus.js +42 -0
  78. package/build/hooks/use-focus.js.map +1 -0
  79. package/build/hooks/use-input.d.ts +93 -0
  80. package/build/hooks/use-input.js +92 -0
  81. package/build/hooks/use-input.js.map +1 -0
  82. package/build/hooks/use-is-screen-reader-enabled.d.ts +5 -0
  83. package/build/hooks/use-is-screen-reader-enabled.js +11 -0
  84. package/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  85. package/build/hooks/use-stderr.d.ts +5 -0
  86. package/build/hooks/use-stderr.js +8 -0
  87. package/build/hooks/use-stderr.js.map +1 -0
  88. package/build/hooks/use-stdin.d.ts +5 -0
  89. package/build/hooks/use-stdin.js +8 -0
  90. package/build/hooks/use-stdin.js.map +1 -0
  91. package/build/hooks/use-stdout.d.ts +5 -0
  92. package/build/hooks/use-stdout.js +8 -0
  93. package/build/hooks/use-stdout.js.map +1 -0
  94. package/build/index.d.ts +38 -0
  95. package/build/index.js +27 -0
  96. package/build/index.js.map +1 -0
  97. package/build/ink.d.ts +110 -0
  98. package/build/ink.js +576 -0
  99. package/build/ink.js.map +1 -0
  100. package/build/instances.d.ts +3 -0
  101. package/build/instances.js +8 -0
  102. package/build/instances.js.map +1 -0
  103. package/build/layout.d.ts +18 -0
  104. package/build/layout.js +54 -0
  105. package/build/layout.js.map +1 -0
  106. package/build/log-update.d.ts +28 -0
  107. package/build/log-update.js +529 -0
  108. package/build/log-update.js.map +1 -0
  109. package/build/measure-element.d.ts +119 -0
  110. package/build/measure-element.js +825 -0
  111. package/build/measure-element.js.map +1 -0
  112. package/build/measure-text.d.ts +50 -0
  113. package/build/measure-text.js +237 -0
  114. package/build/measure-text.js.map +1 -0
  115. package/build/output.d.ts +242 -0
  116. package/build/output.js +607 -0
  117. package/build/output.js.map +1 -0
  118. package/build/parse-keypress.d.ts +14 -0
  119. package/build/parse-keypress.js +225 -0
  120. package/build/parse-keypress.js.map +1 -0
  121. package/build/reconciler.d.ts +4 -0
  122. package/build/reconciler.js +326 -0
  123. package/build/reconciler.js.map +1 -0
  124. package/build/render-background.d.ts +4 -0
  125. package/build/render-background.js +37 -0
  126. package/build/render-background.js.map +1 -0
  127. package/build/render-border.d.ts +4 -0
  128. package/build/render-border.js +81 -0
  129. package/build/render-border.js.map +1 -0
  130. package/build/render-cached.d.ts +18 -0
  131. package/build/render-cached.js +66 -0
  132. package/build/render-cached.js.map +1 -0
  133. package/build/render-container.d.ts +27 -0
  134. package/build/render-container.js +169 -0
  135. package/build/render-container.js.map +1 -0
  136. package/build/render-node-to-output.d.ts +32 -0
  137. package/build/render-node-to-output.js +177 -0
  138. package/build/render-node-to-output.js.map +1 -0
  139. package/build/render-screen-reader.d.ts +5 -0
  140. package/build/render-screen-reader.js +54 -0
  141. package/build/render-screen-reader.js.map +1 -0
  142. package/build/render-scrollbar.d.ts +23 -0
  143. package/build/render-scrollbar.js +70 -0
  144. package/build/render-scrollbar.js.map +1 -0
  145. package/build/render-sticky.d.ts +53 -0
  146. package/build/render-sticky.js +317 -0
  147. package/build/render-sticky.js.map +1 -0
  148. package/build/render-text-node.d.ts +20 -0
  149. package/build/render-text-node.js +155 -0
  150. package/build/render-text-node.js.map +1 -0
  151. package/build/render.d.ts +165 -0
  152. package/build/render.js +60 -0
  153. package/build/render.js.map +1 -0
  154. package/build/renderer.d.ts +24 -0
  155. package/build/renderer.js +292 -0
  156. package/build/renderer.js.map +1 -0
  157. package/build/replay.d.ts +59 -0
  158. package/build/replay.js +128 -0
  159. package/build/replay.js.map +1 -0
  160. package/build/resize-observer.d.ts +24 -0
  161. package/build/resize-observer.js +102 -0
  162. package/build/resize-observer.js.map +1 -0
  163. package/build/scroll.d.ts +11 -0
  164. package/build/scroll.js +123 -0
  165. package/build/scroll.js.map +1 -0
  166. package/build/selection.d.ts +52 -0
  167. package/build/selection.js +359 -0
  168. package/build/selection.js.map +1 -0
  169. package/build/serialization.d.ts +25 -0
  170. package/build/serialization.js +224 -0
  171. package/build/serialization.js.map +1 -0
  172. package/build/squash-text-nodes.d.ts +16 -0
  173. package/build/squash-text-nodes.js +58 -0
  174. package/build/squash-text-nodes.js.map +1 -0
  175. package/build/styled-line.d.ts +58 -0
  176. package/build/styled-line.js +629 -0
  177. package/build/styled-line.js.map +1 -0
  178. package/build/styles.d.ts +286 -0
  179. package/build/styles.js +257 -0
  180. package/build/styles.js.map +1 -0
  181. package/build/terminal-buffer.d.ts +57 -0
  182. package/build/terminal-buffer.js +507 -0
  183. package/build/terminal-buffer.js.map +1 -0
  184. package/build/text-wrap.d.ts +12 -0
  185. package/build/text-wrap.js +154 -0
  186. package/build/text-wrap.js.map +1 -0
  187. package/build/tokenize.d.ts +47 -0
  188. package/build/tokenize.js +419 -0
  189. package/build/tokenize.js.map +1 -0
  190. package/build/vertical-gap.d.ts +17 -0
  191. package/build/vertical-gap.js +20 -0
  192. package/build/vertical-gap.js.map +1 -0
  193. package/build/worker/animation-controller.d.ts +72 -0
  194. package/build/worker/animation-controller.js +128 -0
  195. package/build/worker/animation-controller.js.map +1 -0
  196. package/build/worker/ansi-utils.d.ts +16 -0
  197. package/build/worker/ansi-utils.js +40 -0
  198. package/build/worker/ansi-utils.js.map +1 -0
  199. package/build/worker/canvas.d.ts +49 -0
  200. package/build/worker/canvas.js +90 -0
  201. package/build/worker/canvas.js.map +1 -0
  202. package/build/worker/compositor.d.ts +33 -0
  203. package/build/worker/compositor.js +308 -0
  204. package/build/worker/compositor.js.map +1 -0
  205. package/build/worker/platform.d.ts +15 -0
  206. package/build/worker/platform.js +19 -0
  207. package/build/worker/platform.js.map +1 -0
  208. package/build/worker/render-worker.d.ts +112 -0
  209. package/build/worker/render-worker.js +944 -0
  210. package/build/worker/render-worker.js.map +1 -0
  211. package/build/worker/scene-manager.d.ts +26 -0
  212. package/build/worker/scene-manager.js +109 -0
  213. package/build/worker/scene-manager.js.map +1 -0
  214. package/build/worker/scroll-optimizer.d.ts +32 -0
  215. package/build/worker/scroll-optimizer.js +110 -0
  216. package/build/worker/scroll-optimizer.js.map +1 -0
  217. package/build/worker/terminal-writer.d.ts +116 -0
  218. package/build/worker/terminal-writer.js +708 -0
  219. package/build/worker/terminal-writer.js.map +1 -0
  220. package/build/worker/worker-entry.d.ts +6 -0
  221. package/build/worker/worker-entry.js +130 -0
  222. package/build/worker/worker-entry.js.map +1 -0
  223. package/license +9 -0
  224. package/package.json +208 -0
  225. package/readme.md +2353 -0
@@ -0,0 +1,825 @@
1
+ import Yoga from 'yoga-layout';
2
+ import { getScrollLeft, getScrollTop } from './scroll.js';
3
+ import squashTextNodes from './squash-text-nodes.js';
4
+ import { toStyledCharacters, inkCharacterWidth, styledCharsToString, } from './measure-text.js';
5
+ import { extractSelectableText } from './output.js';
6
+ import { wrapOrTruncateStyledChars } from './text-wrap.js';
7
+ import getMaxWidth from './get-max-width.js';
8
+ import { processLayout } from './layout.js';
9
+ /**
10
+ Measure the dimensions of a particular `<Box>` element.
11
+ */
12
+ const measureElement = (node) => ({
13
+ width: node.yogaNode?.getComputedWidth() ?? 0,
14
+ height: node.yogaNode?.getComputedHeight() ?? 0,
15
+ });
16
+ /**
17
+ * Get an element's inner width.
18
+ */
19
+ export const getInnerWidth = (node) => {
20
+ const { yogaNode } = node;
21
+ if (!yogaNode) {
22
+ return 0;
23
+ }
24
+ const width = yogaNode.getComputedWidth() ?? 0;
25
+ const borderLeft = yogaNode.getComputedBorder(Yoga.EDGE_LEFT);
26
+ const borderRight = yogaNode.getComputedBorder(Yoga.EDGE_RIGHT);
27
+ return width - borderLeft - borderRight;
28
+ };
29
+ /*
30
+ * Get an element's inner height.
31
+ */
32
+ export const getInnerHeight = (node) => {
33
+ const { yogaNode } = node;
34
+ if (!yogaNode) {
35
+ return 0;
36
+ }
37
+ const height = yogaNode.getComputedHeight() ?? 0;
38
+ const borderTop = yogaNode.getComputedBorder(Yoga.EDGE_TOP);
39
+ const borderBottom = yogaNode.getComputedBorder(Yoga.EDGE_BOTTOM);
40
+ return height - borderTop - borderBottom;
41
+ };
42
+ /**
43
+ * Get an element's position and dimensions relative to the root.
44
+ */
45
+ export const getBoundingBox = (node) => {
46
+ const { yogaNode } = node;
47
+ if (!yogaNode) {
48
+ return { x: 0, y: 0, width: 0, height: 0 };
49
+ }
50
+ const width = yogaNode.getComputedWidth() ?? 0;
51
+ const height = yogaNode.getComputedHeight() ?? 0;
52
+ let x = yogaNode.getComputedLeft();
53
+ let y = yogaNode.getComputedTop();
54
+ let parent = node.parentNode;
55
+ while (parent?.yogaNode) {
56
+ x += parent.yogaNode.getComputedLeft();
57
+ y += parent.yogaNode.getComputedTop();
58
+ if (parent.nodeName === 'ink-box') {
59
+ const overflow = parent.style.overflow ?? 'visible';
60
+ const overflowX = parent.style.overflowX ?? overflow;
61
+ const overflowY = parent.style.overflowY ?? overflow;
62
+ if (overflowY === 'scroll') {
63
+ y -= getScrollTop(parent);
64
+ }
65
+ if (overflowX === 'scroll') {
66
+ x -= getScrollLeft(parent);
67
+ }
68
+ }
69
+ parent = parent.parentNode;
70
+ }
71
+ return { x, y, width, height };
72
+ };
73
+ export function calculateScrollbarThumb(options) {
74
+ const { scrollbarDimension, clientDimension, scrollDimension, scrollPosition, axis, } = options;
75
+ const scrollbarDimensionHalves = scrollbarDimension * 2;
76
+ const thumbDimensionHalves = Math.max(axis === 'vertical' ? 2 : 1, Math.round((clientDimension / scrollDimension) * scrollbarDimensionHalves));
77
+ const maxScrollPosition = scrollDimension - clientDimension;
78
+ const maxThumbPosition = scrollbarDimensionHalves - thumbDimensionHalves;
79
+ const thumbPosition = maxScrollPosition > 0
80
+ ? Math.round((scrollPosition / maxScrollPosition) * maxThumbPosition)
81
+ : 0;
82
+ const thumbStartHalf = thumbPosition;
83
+ const thumbEndHalf = thumbPosition + thumbDimensionHalves;
84
+ const startIndex = Math.floor(thumbStartHalf / 2);
85
+ const endIndex = Math.min(scrollbarDimension, Math.ceil(thumbEndHalf / 2));
86
+ return { startIndex, endIndex, thumbStartHalf, thumbEndHalf };
87
+ }
88
+ export function calculateScrollbarLayout(options) {
89
+ const { x, y, width, height, marginRight, marginBottom, clientDimension, scrollDimension, scrollPosition, hasOppositeScrollbar, axis, } = options;
90
+ if (scrollDimension <= clientDimension) {
91
+ return undefined;
92
+ }
93
+ if (axis === 'vertical') {
94
+ const { startIndex, endIndex, thumbStartHalf, thumbEndHalf } = calculateScrollbarThumb({
95
+ scrollbarDimension: height,
96
+ clientDimension,
97
+ scrollDimension,
98
+ scrollPosition,
99
+ axis,
100
+ });
101
+ const scrollbarX = x + width - 1 - marginRight;
102
+ return {
103
+ x: scrollbarX,
104
+ y,
105
+ width: 1,
106
+ height,
107
+ thumb: {
108
+ x: scrollbarX,
109
+ y: y + startIndex,
110
+ width: 1,
111
+ height: endIndex - startIndex,
112
+ start: startIndex,
113
+ end: endIndex,
114
+ startHalf: thumbStartHalf,
115
+ endHalf: thumbEndHalf,
116
+ },
117
+ };
118
+ }
119
+ const scrollbarWidth = width - (hasOppositeScrollbar ? 1 : 0);
120
+ const { startIndex, endIndex, thumbStartHalf, thumbEndHalf } = calculateScrollbarThumb({
121
+ scrollbarDimension: scrollbarWidth,
122
+ clientDimension,
123
+ scrollDimension,
124
+ scrollPosition,
125
+ axis,
126
+ });
127
+ const scrollbarY = y + height - 1 - marginBottom;
128
+ return {
129
+ x,
130
+ y: scrollbarY,
131
+ width: scrollbarWidth,
132
+ height: 1,
133
+ thumb: {
134
+ x: x + startIndex,
135
+ y: scrollbarY,
136
+ width: endIndex - startIndex,
137
+ height: 1,
138
+ start: startIndex,
139
+ end: endIndex,
140
+ startHalf: thumbStartHalf,
141
+ endHalf: thumbEndHalf,
142
+ },
143
+ };
144
+ }
145
+ /**
146
+ * Get how much scroll height was added by stableScrollback.
147
+ */
148
+ export const getAddedScrollHeight = (node) => {
149
+ const scrollHeight = node.internal_scrollState?.scrollHeight ?? 0;
150
+ const actualScrollHeight = node.internal_scrollState?.actualScrollHeight ?? 0;
151
+ return Math.max(0, scrollHeight - actualScrollHeight);
152
+ };
153
+ /**
154
+ * Get the bounding box of the vertical scrollbar.
155
+ */
156
+ export const getVerticalScrollbarBoundingBox = (node, offset) => {
157
+ const { yogaNode } = node;
158
+ if (!yogaNode) {
159
+ return undefined;
160
+ }
161
+ const overflow = node.style.overflow ?? 'visible';
162
+ const overflowY = node.style.overflowY ?? overflow;
163
+ if (overflowY !== 'scroll') {
164
+ return undefined;
165
+ }
166
+ const clientHeight = node.internal_scrollState?.clientHeight ?? 0;
167
+ const scrollHeight = node.internal_scrollState?.scrollHeight ?? 0;
168
+ if (scrollHeight <= clientHeight) {
169
+ return undefined;
170
+ }
171
+ const { x, y } = offset ?? getBoundingBox(node);
172
+ const scrollbarHeight = yogaNode.getComputedHeight() -
173
+ yogaNode.getComputedBorder(Yoga.EDGE_TOP) -
174
+ yogaNode.getComputedBorder(Yoga.EDGE_BOTTOM);
175
+ return calculateScrollbarLayout({
176
+ x: x + yogaNode.getComputedBorder(Yoga.EDGE_LEFT),
177
+ y: y + yogaNode.getComputedBorder(Yoga.EDGE_TOP),
178
+ width: yogaNode.getComputedWidth() - yogaNode.getComputedBorder(Yoga.EDGE_LEFT),
179
+ height: scrollbarHeight,
180
+ marginRight: yogaNode.getComputedBorder(Yoga.EDGE_RIGHT),
181
+ marginBottom: yogaNode.getComputedBorder(Yoga.EDGE_BOTTOM),
182
+ clientDimension: clientHeight,
183
+ scrollDimension: scrollHeight,
184
+ scrollPosition: node.internal_scrollState?.scrollTop ?? 0,
185
+ hasOppositeScrollbar: false,
186
+ axis: 'vertical',
187
+ });
188
+ };
189
+ /**
190
+ * Get the bounding box of the horizontal scrollbar.
191
+ */
192
+ export const getHorizontalScrollbarBoundingBox = (node, offset) => {
193
+ const { yogaNode } = node;
194
+ if (!yogaNode) {
195
+ return undefined;
196
+ }
197
+ const overflow = node.style.overflow ?? 'visible';
198
+ const overflowX = node.style.overflowX ?? overflow;
199
+ if (overflowX !== 'scroll') {
200
+ return undefined;
201
+ }
202
+ const clientWidth = node.internal_scrollState?.clientWidth ?? 0;
203
+ const scrollWidth = node.internal_scrollState?.scrollWidth ?? 0;
204
+ if (scrollWidth <= clientWidth) {
205
+ return undefined;
206
+ }
207
+ const { x, y } = offset ?? getBoundingBox(node);
208
+ const overflowY = node.style.overflowY ?? overflow;
209
+ const clientHeight = node.internal_scrollState?.clientHeight ?? 0;
210
+ const scrollHeight = node.internal_scrollState?.scrollHeight ?? 0;
211
+ const isVerticalScrollbarVisible = overflowY === 'scroll' && scrollHeight > clientHeight;
212
+ return calculateScrollbarLayout({
213
+ x: x + yogaNode.getComputedBorder(Yoga.EDGE_LEFT),
214
+ y: y + yogaNode.getComputedBorder(Yoga.EDGE_TOP),
215
+ width: yogaNode.getComputedWidth() -
216
+ yogaNode.getComputedBorder(Yoga.EDGE_LEFT) -
217
+ yogaNode.getComputedBorder(Yoga.EDGE_RIGHT),
218
+ height: yogaNode.getComputedHeight() - yogaNode.getComputedBorder(Yoga.EDGE_TOP),
219
+ marginRight: yogaNode.getComputedBorder(Yoga.EDGE_RIGHT),
220
+ marginBottom: yogaNode.getComputedBorder(Yoga.EDGE_BOTTOM),
221
+ clientDimension: clientWidth,
222
+ scrollDimension: scrollWidth,
223
+ scrollPosition: node.internal_scrollState?.scrollLeft ?? 0,
224
+ hasOppositeScrollbar: isVerticalScrollbarVisible,
225
+ axis: 'horizontal',
226
+ });
227
+ };
228
+ export const collectSortedFragments = (node) => {
229
+ const fragments = [];
230
+ const collect = (currentNode, coords, isSelectable) => {
231
+ const { x, y, visualX, visualY } = coords;
232
+ let v = 0;
233
+ let h = 0;
234
+ let currentSelectable = isSelectable;
235
+ const { userSelect } = currentNode.style;
236
+ if (userSelect === 'none') {
237
+ currentSelectable = false;
238
+ }
239
+ else if (userSelect === 'text' || userSelect === 'all') {
240
+ currentSelectable = true;
241
+ }
242
+ if (currentNode.nodeName === 'ink-text' ||
243
+ currentNode.nodeName === 'ink-virtual-text' ||
244
+ (currentNode.nodeName === 'ink-static-render' && currentNode.cachedRender)) {
245
+ if (currentSelectable) {
246
+ const text = getText(currentNode);
247
+ fragments.push({
248
+ node: currentNode,
249
+ text,
250
+ x,
251
+ y,
252
+ visualX,
253
+ visualY,
254
+ width: currentNode.yogaNode?.getComputedWidth() ?? 0,
255
+ height: currentNode.yogaNode?.getComputedHeight() ?? 0,
256
+ });
257
+ return { v: 0, h: 0, hasContent: true };
258
+ }
259
+ return {
260
+ v: currentNode.yogaNode?.getComputedHeight() ?? 0,
261
+ h: currentNode.yogaNode?.getComputedWidth() ?? 0,
262
+ hasContent: false,
263
+ };
264
+ }
265
+ if (currentNode.nodeName === 'ink-box' ||
266
+ currentNode.nodeName === 'ink-root') {
267
+ if (!currentNode.yogaNode ||
268
+ currentNode.yogaNode.getDisplay() === Yoga.DISPLAY_NONE) {
269
+ return { v: 0, h: 0, hasContent: false };
270
+ }
271
+ const borderTop = currentNode.yogaNode.getComputedBorder(Yoga.EDGE_TOP);
272
+ const borderBottom = currentNode.yogaNode.getComputedBorder(Yoga.EDGE_BOTTOM);
273
+ const borderLeft = currentNode.yogaNode.getComputedBorder(Yoga.EDGE_LEFT);
274
+ const borderRight = currentNode.yogaNode.getComputedBorder(Yoga.EDGE_RIGHT);
275
+ v += borderTop + borderBottom;
276
+ h += borderLeft + borderRight;
277
+ const flexDirection = currentNode.yogaNode.getFlexDirection();
278
+ const isColumn = flexDirection === Yoga.FLEX_DIRECTION_COLUMN ||
279
+ flexDirection === Yoga.FLEX_DIRECTION_COLUMN_REVERSE;
280
+ let siblingRemovedH = 0;
281
+ let siblingRemovedV = 0;
282
+ let childHasContent = false;
283
+ for (const child of currentNode.childNodes) {
284
+ if (child.yogaNode) {
285
+ const parentBorderLeft = currentNode.yogaNode.getComputedBorder(Yoga.EDGE_LEFT);
286
+ const childX = x +
287
+ child.yogaNode.getComputedLeft() -
288
+ parentBorderLeft -
289
+ siblingRemovedH;
290
+ const childY = y + child.yogaNode.getComputedTop() - borderTop - siblingRemovedV;
291
+ const childVisualX = visualX + child.yogaNode.getComputedLeft();
292
+ const childVisualY = visualY + child.yogaNode.getComputedTop();
293
+ const res = collect(child, {
294
+ x: childX,
295
+ y: childY,
296
+ visualX: childVisualX,
297
+ visualY: childVisualY,
298
+ }, currentSelectable);
299
+ if (res.hasContent) {
300
+ childHasContent = true;
301
+ }
302
+ if (isColumn) {
303
+ siblingRemovedV += res.v;
304
+ }
305
+ else {
306
+ siblingRemovedH += res.h;
307
+ }
308
+ }
309
+ }
310
+ if (isColumn) {
311
+ v += siblingRemovedV;
312
+ }
313
+ else {
314
+ h += siblingRemovedH;
315
+ }
316
+ if (!childHasContent && userSelect === 'none') {
317
+ return {
318
+ v: currentNode.yogaNode.getComputedHeight(),
319
+ h: currentNode.yogaNode.getComputedWidth(),
320
+ hasContent: false,
321
+ };
322
+ }
323
+ return { v, h, hasContent: childHasContent };
324
+ }
325
+ return { v: 0, h: 0, hasContent: false };
326
+ };
327
+ const { v, h } = collect(node, { x: 0, y: 0, visualX: 0, visualY: 0 }, true);
328
+ fragments.sort((a, b) => {
329
+ if (a.y !== b.y) {
330
+ return a.y - b.y;
331
+ }
332
+ return a.x - b.x;
333
+ });
334
+ return { fragments, removedVertical: v, removedHorizontal: h };
335
+ };
336
+ export const getText = (node) => {
337
+ if (node.nodeName === '#text') {
338
+ return node.nodeValue;
339
+ }
340
+ if (node.nodeName === 'ink-static-render' &&
341
+ node.cachedRender?.selectableSpans) {
342
+ const spans = node.cachedRender.selectableSpans;
343
+ if (spans.length === 0)
344
+ return '';
345
+ // Spans are already sorted during rendering, but let's be safe
346
+ return extractSelectableText(spans);
347
+ }
348
+ if (node.nodeName === 'ink-text' || node.nodeName === 'ink-virtual-text') {
349
+ const text = squashTextNodes(node);
350
+ const styledChars = toStyledCharacters(text);
351
+ const plainText = styledChars.getText();
352
+ const textWrap = node.style.textWrap ?? 'wrap';
353
+ if (textWrap.startsWith('truncate')) {
354
+ const maxWidth = getMaxWidth(node.yogaNode);
355
+ const lines = wrapOrTruncateStyledChars(styledChars, maxWidth, textWrap);
356
+ return styledCharsToString(lines[0]);
357
+ }
358
+ return plainText;
359
+ }
360
+ if (node.nodeName === 'ink-box' || node.nodeName === 'ink-root') {
361
+ if (!node.yogaNode) {
362
+ return '';
363
+ }
364
+ const { state, lineBottom } = processLayout(node, {
365
+ initialState: () => ({ result: '' }),
366
+ onNewline(count, state) {
367
+ state.result += '\n'.repeat(count);
368
+ },
369
+ onSpace(count, state) {
370
+ state.result += ' '.repeat(count);
371
+ },
372
+ onText(fragment, state) {
373
+ state.result += fragment.text;
374
+ },
375
+ });
376
+ // Trailing newlines
377
+ const { removedVertical } = collectSortedFragments(node);
378
+ const height = node.yogaNode?.getComputedHeight() ?? 0;
379
+ const innerHeight = height - removedVertical;
380
+ if (innerHeight > lineBottom) {
381
+ state.result += '\n'.repeat(innerHeight - lineBottom);
382
+ }
383
+ return state.result;
384
+ }
385
+ return '';
386
+ };
387
+ const handleVerticalGap = (fragment, state, y, options) => {
388
+ if (fragment.y < state.lineBottom) {
389
+ return undefined;
390
+ }
391
+ if (state.trailingCandidate !== -1) {
392
+ return state.trailingCandidate;
393
+ }
394
+ const gap = fragment.y - state.lineBottom;
395
+ const newlines = state.offset > 0 ? 1 + gap : gap;
396
+ if (y < fragment.visualY) {
397
+ if (options?.snapToGap === 'end') {
398
+ return state.offset + newlines;
399
+ }
400
+ if (options?.snapToGap === 'start') {
401
+ return state.offset;
402
+ }
403
+ if (newlines > 0) {
404
+ const distance = fragment.visualY - y;
405
+ const clampedDistance = Math.min(distance, newlines);
406
+ return state.offset + newlines - clampedDistance;
407
+ }
408
+ return state.offset;
409
+ }
410
+ if (newlines > 0) {
411
+ state.offset += newlines;
412
+ state.currentX = 0;
413
+ state.lineBottom = fragment.y;
414
+ }
415
+ return undefined;
416
+ };
417
+ const handleHorizontalGap = (fragment, state, x, y, options) => {
418
+ const gap = fragment.x - state.currentX;
419
+ if (y >= fragment.visualY &&
420
+ y < fragment.visualY + 1 &&
421
+ x < fragment.visualX) {
422
+ if (options?.snapToGap === 'end') {
423
+ return state.offset + gap;
424
+ }
425
+ if (options?.snapToGap === 'start') {
426
+ return state.offset;
427
+ }
428
+ if (gap > 0) {
429
+ const distance = fragment.visualX - x;
430
+ const clampedDistance = Math.min(distance, gap);
431
+ return state.offset + gap - clampedDistance;
432
+ }
433
+ return state.offset;
434
+ }
435
+ if (gap > 0) {
436
+ state.offset += gap;
437
+ state.currentX = fragment.x;
438
+ }
439
+ return undefined;
440
+ };
441
+ const handleContentMatch = (fragment, state, x, y, options) => {
442
+ const isVerticalMatch = y >= fragment.visualY && y < fragment.visualY + fragment.height;
443
+ const isHorizontalMatch = x >= fragment.visualX && x <= fragment.visualX + fragment.width;
444
+ if (isVerticalMatch && (isHorizontalMatch || x < fragment.visualX)) {
445
+ return (state.offset +
446
+ getTextOffset(fragment.node, x - fragment.visualX, y - fragment.visualY, options));
447
+ }
448
+ if (isVerticalMatch && x > fragment.visualX + fragment.width) {
449
+ state.trailingCandidate =
450
+ state.offset +
451
+ getTextOffset(fragment.node, x - fragment.visualX, y - fragment.visualY, options);
452
+ }
453
+ return undefined;
454
+ };
455
+ const processFragment = (fragment, state, x, y, options) => {
456
+ const verticalResult = handleVerticalGap(fragment, state, y, options);
457
+ if (verticalResult !== undefined) {
458
+ return verticalResult;
459
+ }
460
+ const horizontalResult = handleHorizontalGap(fragment, state, x, y, options);
461
+ if (horizontalResult !== undefined) {
462
+ return horizontalResult;
463
+ }
464
+ const contentResult = handleContentMatch(fragment, state, x, y, options);
465
+ if (contentResult !== undefined) {
466
+ return contentResult;
467
+ }
468
+ state.offset += fragment.text.length;
469
+ const newlines = (fragment.text.match(/\n/g) ?? []).length;
470
+ if (newlines > 0) {
471
+ const lastNewlineIndex = fragment.text.lastIndexOf('\n');
472
+ state.currentX = fragment.text.length - lastNewlineIndex - 1;
473
+ }
474
+ else {
475
+ state.currentX += fragment.text.length;
476
+ }
477
+ state.lineBottom = Math.max(state.lineBottom, fragment.y + fragment.height);
478
+ return undefined;
479
+ };
480
+ const getTextOffsetForTextNode = (node, x, y, options) => {
481
+ if (y < 0) {
482
+ return 0;
483
+ }
484
+ const text = squashTextNodes(node);
485
+ const styledChars = toStyledCharacters(text);
486
+ const maxWidth = getMaxWidth(node.yogaNode);
487
+ const textWrap = node.style.textWrap ?? 'wrap';
488
+ const lines = wrapOrTruncateStyledChars(styledChars, maxWidth, textWrap);
489
+ const fullText = styledChars.getText();
490
+ let currentY = 0;
491
+ let searchOffset = 0;
492
+ for (const line of lines) {
493
+ const lineStr = line.getText();
494
+ const searchStr = lineStr.replace('…', '');
495
+ const lineStartOffset = fullText.indexOf(searchStr, searchOffset);
496
+ const actualStartOffset = lineStartOffset === -1 ? searchOffset : lineStartOffset;
497
+ if (y === currentY) {
498
+ let currentX = 0;
499
+ let currentOffset = actualStartOffset;
500
+ for (const char of line) {
501
+ const charWidth = inkCharacterWidth(char.value);
502
+ if (x < currentX + charWidth) {
503
+ if (options?.snapToChar === 'end' && x >= currentX) {
504
+ currentOffset += char.value.length;
505
+ }
506
+ return currentOffset;
507
+ }
508
+ currentX += charWidth;
509
+ currentOffset += char.value.length;
510
+ }
511
+ return currentOffset;
512
+ }
513
+ searchOffset = actualStartOffset + searchStr.length;
514
+ currentY++;
515
+ }
516
+ return fullText.length;
517
+ };
518
+ export const getTextOffset = (node, x, y, options) => {
519
+ if (node.nodeName === '#text') {
520
+ return 0;
521
+ }
522
+ if (node.nodeName === 'ink-static-render' &&
523
+ node.cachedRender?.selectableSpans) {
524
+ const spans = node.cachedRender.selectableSpans;
525
+ if (spans.length === 0)
526
+ return 0;
527
+ const sortedSpans = [...spans].sort((a, b) => a.y === b.y ? a.startX - b.startX : a.y - b.y);
528
+ let textOffset = 0;
529
+ let currentY = sortedSpans[0].y;
530
+ let currentX = sortedSpans[0].startX;
531
+ let bestDist = Infinity;
532
+ let bestOffset = 0;
533
+ for (const span of sortedSpans) {
534
+ if (span.y > currentY) {
535
+ textOffset += span.y - currentY;
536
+ currentX = 0;
537
+ currentY = span.y;
538
+ }
539
+ if (span.startX > currentX) {
540
+ textOffset += span.startX - currentX;
541
+ currentX = span.startX;
542
+ }
543
+ // Check distance
544
+ if (y === span.y && x >= span.startX && x <= span.endX) {
545
+ // Exact hit
546
+ let charOffset = 0;
547
+ let cx = span.startX;
548
+ for (const char of span.text) {
549
+ const w = inkCharacterWidth(char);
550
+ if (x < cx + w) {
551
+ if (options?.snapToChar === 'end' && x >= cx) {
552
+ charOffset += char.length;
553
+ }
554
+ return textOffset + charOffset;
555
+ }
556
+ cx += w;
557
+ charOffset += char.length;
558
+ }
559
+ return textOffset + charOffset;
560
+ }
561
+ // Update best match
562
+ const dy = Math.abs(y - span.y);
563
+ const dx = x < span.startX ? span.startX - x : x > span.endX ? x - span.endX : 0;
564
+ const dist = dy * 1000 + dx;
565
+ if (dist < bestDist) {
566
+ bestDist = dist;
567
+ bestOffset =
568
+ y < span.y || (y === span.y && x < span.startX)
569
+ ? textOffset
570
+ : textOffset + span.text.length;
571
+ }
572
+ textOffset += span.text.length;
573
+ currentX = span.endX;
574
+ }
575
+ return bestOffset;
576
+ }
577
+ if (node.nodeName === 'ink-text' || node.nodeName === 'ink-virtual-text') {
578
+ return getTextOffsetForTextNode(node, x, y, options);
579
+ }
580
+ if (node.nodeName === 'ink-box' || node.nodeName === 'ink-root') {
581
+ const { fragments, removedVertical } = collectSortedFragments(node);
582
+ const state = {
583
+ offset: 0,
584
+ currentX: 0,
585
+ lineBottom: 0,
586
+ trailingCandidate: -1,
587
+ };
588
+ for (const fragment of fragments) {
589
+ const result = processFragment(fragment, state, x, y, options);
590
+ if (result !== undefined) {
591
+ return result;
592
+ }
593
+ }
594
+ if (state.trailingCandidate !== -1) {
595
+ return state.trailingCandidate;
596
+ }
597
+ const height = node.yogaNode?.getComputedHeight() ?? 0;
598
+ const innerHeight = height - removedVertical;
599
+ if (innerHeight > state.lineBottom) {
600
+ const gap = innerHeight - state.lineBottom;
601
+ if (y >= state.lineBottom) {
602
+ // This logic uses logical Y (lineBottom) and visual Y (y).
603
+ // This is potentially buggy if they diverge.
604
+ // But we don't have a better way to map trailing empty lines without more info.
605
+ // Assuming trailing lines match logical height.
606
+ const relativeY = y - state.lineBottom;
607
+ return state.offset + Math.min(relativeY, gap);
608
+ }
609
+ }
610
+ return state.offset;
611
+ }
612
+ return 0;
613
+ };
614
+ const findNodeInSquashed = (root, offset) => {
615
+ let currentOffset = 0;
616
+ const findNode = (node) => {
617
+ if (node.nodeName === '#text') {
618
+ const { length } = node.nodeValue;
619
+ if (offset >= currentOffset && offset <= currentOffset + length) {
620
+ return { node, offset: offset - currentOffset };
621
+ }
622
+ currentOffset += length;
623
+ }
624
+ else if (node.nodeName === 'ink-static-render' && node.cachedRender) {
625
+ const { length } = getText(node);
626
+ if (offset >= currentOffset && offset <= currentOffset + length) {
627
+ return { node, offset: offset - currentOffset };
628
+ }
629
+ currentOffset += length;
630
+ }
631
+ else if (node.nodeName === 'ink-text' ||
632
+ node.nodeName === 'ink-virtual-text') {
633
+ for (const child of node.childNodes) {
634
+ const result = findNode(child);
635
+ if (result) {
636
+ return result;
637
+ }
638
+ }
639
+ }
640
+ return undefined;
641
+ };
642
+ return findNode(root);
643
+ };
644
+ export const findNodeAtOffset = (node, targetOffset) => {
645
+ if (node.nodeName === '#text') {
646
+ return { node, offset: Math.min(targetOffset, node.nodeValue.length) };
647
+ }
648
+ if (node.nodeName === 'ink-static-render' && node.cachedRender) {
649
+ return { node, offset: Math.min(targetOffset, getText(node).length) };
650
+ }
651
+ if (node.nodeName === 'ink-text' || node.nodeName === 'ink-virtual-text') {
652
+ return findNodeInSquashed(node, targetOffset);
653
+ }
654
+ if (node.nodeName === 'ink-box' || node.nodeName === 'ink-root') {
655
+ if (!node.yogaNode) {
656
+ return undefined;
657
+ }
658
+ const { fragments } = collectSortedFragments(node);
659
+ let currentOffset = 0;
660
+ let lineBottom = 0;
661
+ let currentX = 0;
662
+ for (const fragment of fragments) {
663
+ if (fragment.y >= lineBottom) {
664
+ const gap = fragment.y - lineBottom;
665
+ const newlines = currentOffset > 0 ? 1 + gap : gap;
666
+ if (newlines > 0) {
667
+ if (targetOffset < currentOffset + newlines) {
668
+ return findNodeInSquashed(fragment.node, 0);
669
+ }
670
+ currentOffset += newlines;
671
+ currentX = 0;
672
+ lineBottom = fragment.y;
673
+ }
674
+ }
675
+ if (fragment.x > currentX) {
676
+ const spaces = fragment.x - currentX;
677
+ if (targetOffset < currentOffset + spaces) {
678
+ return findNodeInSquashed(fragment.node, 0);
679
+ }
680
+ currentOffset += spaces;
681
+ currentX = fragment.x;
682
+ }
683
+ const textLength = fragment.text.length;
684
+ if (targetOffset < currentOffset + textLength) {
685
+ return findNodeInSquashed(fragment.node, targetOffset - currentOffset);
686
+ }
687
+ currentOffset += textLength;
688
+ const newlinesInText = (fragment.text.match(/\n/g) ?? []).length;
689
+ if (newlinesInText > 0) {
690
+ const lastNewlineIndex = fragment.text.lastIndexOf('\n');
691
+ currentX = fragment.text.length - lastNewlineIndex - 1;
692
+ }
693
+ else {
694
+ currentX += fragment.text.length;
695
+ }
696
+ lineBottom = Math.max(lineBottom, fragment.y + fragment.height);
697
+ }
698
+ if (fragments.length > 0) {
699
+ const lastFragment = fragments.at(-1);
700
+ const { text } = lastFragment;
701
+ return findNodeInSquashed(lastFragment.node, text.length);
702
+ }
703
+ }
704
+ return undefined;
705
+ };
706
+ export const hitTest = (node, x, y) => {
707
+ const { fragments } = collectSortedFragments(node);
708
+ let bestMatch;
709
+ for (const fragment of fragments) {
710
+ let vDist = 0;
711
+ let hDist = 0;
712
+ // Calculate vertical distance
713
+ if (y < fragment.visualY) {
714
+ vDist = fragment.visualY - y;
715
+ }
716
+ else if (y >= fragment.visualY + fragment.height) {
717
+ vDist = y - (fragment.visualY + fragment.height - 1);
718
+ }
719
+ else {
720
+ vDist = 0;
721
+ }
722
+ // Calculate horizontal distance
723
+ if (x < fragment.visualX) {
724
+ hDist = fragment.visualX - x;
725
+ }
726
+ else if (x > fragment.visualX + fragment.width) {
727
+ hDist = x - (fragment.visualX + fragment.width);
728
+ }
729
+ else {
730
+ hDist = 0;
731
+ }
732
+ // Prioritize exact matches (vDist=0, hDist=0)
733
+ if (vDist === 0 && hDist === 0) {
734
+ bestMatch = { fragment, vDist, hDist };
735
+ break;
736
+ }
737
+ if (bestMatch) {
738
+ // Compare with best match
739
+ // Priority:
740
+ // 1. Vertical distance (smaller is better)
741
+ // 2. Horizontal distance (smaller is better)
742
+ if (vDist < bestMatch.vDist) {
743
+ bestMatch = { fragment, vDist, hDist };
744
+ }
745
+ else if (vDist === bestMatch.vDist && hDist < bestMatch.hDist) {
746
+ bestMatch = { fragment, vDist, hDist };
747
+ }
748
+ }
749
+ else {
750
+ bestMatch = { fragment, vDist, hDist };
751
+ }
752
+ }
753
+ if (bestMatch) {
754
+ const { fragment } = bestMatch;
755
+ const relativeX = x - fragment.visualX;
756
+ const relativeY = y - fragment.visualY;
757
+ const offsetInSquashed = getTextOffset(fragment.node, relativeX, relativeY);
758
+ let currentOffset = 0;
759
+ const findNode = (n) => {
760
+ if (n.nodeName === '#text') {
761
+ const { length } = n.nodeValue;
762
+ if (offsetInSquashed >= currentOffset &&
763
+ offsetInSquashed <= currentOffset + length) {
764
+ return { node: n, offset: offsetInSquashed - currentOffset };
765
+ }
766
+ currentOffset += length;
767
+ }
768
+ else if (n.nodeName === 'ink-static-render' && n.cachedRender) {
769
+ const { length } = getText(n);
770
+ if (offsetInSquashed >= currentOffset &&
771
+ offsetInSquashed <= currentOffset + length) {
772
+ return { node: n, offset: offsetInSquashed - currentOffset };
773
+ }
774
+ currentOffset += length;
775
+ }
776
+ else {
777
+ for (const child of n.childNodes) {
778
+ const result = findNode(child);
779
+ if (result) {
780
+ return result;
781
+ }
782
+ }
783
+ }
784
+ return undefined;
785
+ };
786
+ return findNode(fragment.node);
787
+ }
788
+ return undefined;
789
+ };
790
+ export default measureElement;
791
+ export function getRelativeTop(node, ancestor) {
792
+ if (!node.yogaNode || node === ancestor) {
793
+ return 0;
794
+ }
795
+ let top = node.yogaNode.getComputedTop();
796
+ let parent = node.parentNode;
797
+ while (parent && parent !== ancestor) {
798
+ if (parent.yogaNode) {
799
+ top += parent.yogaNode.getComputedTop();
800
+ }
801
+ parent = parent.parentNode;
802
+ }
803
+ if (ancestor !== undefined && parent !== ancestor) {
804
+ return undefined;
805
+ }
806
+ return top;
807
+ }
808
+ export function getRelativeLeft(node, ancestor) {
809
+ if (!node.yogaNode || node === ancestor) {
810
+ return 0;
811
+ }
812
+ let left = node.yogaNode.getComputedLeft();
813
+ let parent = node.parentNode;
814
+ while (parent && parent !== ancestor) {
815
+ if (parent.yogaNode) {
816
+ left += parent.yogaNode.getComputedLeft();
817
+ }
818
+ parent = parent.parentNode;
819
+ }
820
+ if (ancestor !== undefined && parent !== ancestor) {
821
+ return undefined;
822
+ }
823
+ return left;
824
+ }
825
+ //# sourceMappingURL=measure-element.js.map