11import type { TerminalCopyMouseEvent } from "./terminal-copy-selection-drag.js"
22
3+ /**
4+ * Facade for xterm's hidden textarea used by native browser copy commands.
5+ *
6+ * @pure true - the type only describes the injected DOM shell boundary.
7+ * @effect none at declaration time; implementations perform focus/select/style/value DOM effects.
8+ * @invariant Style and value writes affect the same textarea that focus() and select() address.
9+ * @precondition Implementations are xterm-compatible textarea-like objects.
10+ * @postcondition Consumers can prepare selected text for the browser context-menu Copy command.
11+ * @complexity O(1)
12+ * @throws Never
13+ */
314export type TerminalCopyTextarea = {
415 readonly focus : ( ) => void
516 readonly select : ( ) => void
@@ -13,13 +24,35 @@ export type TerminalCopyTextarea = {
1324 value : string
1425}
1526
27+ /**
28+ * Minimal host facade for locating the terminal screen relative to context-menu coordinates.
29+ *
30+ * @pure true - the type only describes optional DOM lookup capabilities.
31+ * @effect none at declaration time; implementations may perform DOM layout reads when called.
32+ * @invariant querySelector(".xterm-screen") returns an element in the host coordinate space when available.
33+ * @precondition Host methods, when present, follow DOM getBoundingClientRect/querySelector contracts.
34+ * @postcondition Consumers can resolve the screen origin without depending on concrete browser classes.
35+ * @complexity O(1)
36+ * @throws Never
37+ */
1638type TerminalCopyScreenElement = {
1739 readonly getBoundingClientRect : ( ) => {
1840 readonly left : number
1941 readonly top : number
2042 }
2143}
2244
45+ /**
46+ * Host facade used by the native copy menu preparation shell.
47+ *
48+ * @pure true - the type only describes injected DOM capabilities.
49+ * @effect none at declaration time; method implementations can read DOM layout.
50+ * @invariant At least one of host.getBoundingClientRect or host.querySelector(".xterm-screen") may define origin.
51+ * @precondition Optional methods preserve their `this` binding semantics when called through this facade.
52+ * @postcondition Consumers can choose the most precise terminal screen origin available.
53+ * @complexity O(1)
54+ * @throws Never
55+ */
2356export type TerminalNativeCopyMenuHost = {
2457 readonly getBoundingClientRect ?: TerminalCopyScreenElement [ "getBoundingClientRect" ]
2558 readonly querySelector ?: ( selector : string ) => TerminalCopyScreenElement | null
@@ -36,8 +69,54 @@ const terminalContextMenuTextareaOffsetPx = 10
3669const terminalContextMenuTextareaSizePx = 20
3770const xtermScreenSelector = ".xterm-screen"
3871
72+ /**
73+ * Normalizes optional event coordinates to the DOM origin default.
74+ *
75+ * @param value - Optional mouse coordinate from a browser-like event facade.
76+ * @returns The coordinate value or zero when the event omits it.
77+ * @pure true
78+ * @effect none
79+ * @invariant result = value when value is defined; otherwise result = 0.
80+ * @precondition value is either undefined or a finite event coordinate.
81+ * @postcondition The caller can use the result in textarea positioning arithmetic.
82+ * @complexity O(1)
83+ * @throws Never
84+ */
85+ // CHANGE: normalize optional mouse coordinates for native copy textarea positioning
86+ // WHY: synthetic test events and browser events can omit client coordinates
87+ // QUOTE(ТЗ): n/a
88+ // REF: PR-407-CodeRabbit-native-copy-menu-docs
89+ // SOURCE: n/a
90+ // FORMAT THEOREM: optionalNumber(x) = x if x is defined, otherwise 0
91+ // PURITY: CORE
92+ // EFFECT: none
93+ // INVARIANT: result is always a number
94+ // COMPLEXITY: O(1)/O(1)
3995const optionalNumber = ( value : number | undefined ) : number => value ?? 0
4096
97+ /**
98+ * Adapts a host-level bounding box method into a screen element facade.
99+ *
100+ * @param host - Native copy menu host with an optional getBoundingClientRect method.
101+ * @returns A screen element facade when the host can provide its own rectangle; otherwise null.
102+ * @pure true - does not read layout until the returned facade is invoked.
103+ * @effect none during resolution; returned facade calls host.getBoundingClientRect().
104+ * @invariant Non-null result preserves host as the `this` value for getBoundingClientRect.
105+ * @precondition host is the DOM-like object that owns the optional getBoundingClientRect method.
106+ * @postcondition The caller can use the result as a fallback screen-origin provider.
107+ * @complexity O(1)
108+ * @throws Never
109+ */
110+ // CHANGE: preserve host-bound layout reads when no .xterm-screen child is available
111+ // WHY: the fallback must call getBoundingClientRect with the original DOM receiver
112+ // QUOTE(ТЗ): n/a
113+ // REF: PR-407-CodeRabbit-native-copy-menu-docs
114+ // SOURCE: n/a
115+ // FORMAT THEOREM: hasRect(host) => result.getBoundingClientRect() = host.getBoundingClientRect.call(host)
116+ // PURITY: SHELL
117+ // EFFECT: deferred host.getBoundingClientRect()
118+ // INVARIANT: null is returned iff the host has no bounding-rect method
119+ // COMPLEXITY: O(1)/O(1)
41120const resolveContextMenuHostScreenElement = (
42121 host : TerminalNativeCopyMenuHost
43122) : TerminalCopyScreenElement | null => {
@@ -50,11 +129,57 @@ const resolveContextMenuHostScreenElement = (
50129 }
51130}
52131
132+ /**
133+ * Resolves the preferred terminal screen origin for native copy menu positioning.
134+ *
135+ * @param host - Native copy menu host that may expose xterm screen lookup or host bounds.
136+ * @returns The xterm screen element when available, otherwise host bounds, otherwise null.
137+ * @pure true - delegates to injected DOM facades without mutating them.
138+ * @effect optional host.querySelector(".xterm-screen") and deferred getBoundingClientRect reads.
139+ * @invariant querySelector(".xterm-screen") takes precedence over host-level bounds.
140+ * @precondition host methods follow DOM-compatible contracts.
141+ * @postcondition Non-null result can provide coordinates for the helper textarea.
142+ * @complexity O(1)
143+ * @throws Never
144+ */
145+ // CHANGE: prefer the xterm screen coordinate space for context-menu helper positioning
146+ // WHY: textarea coordinates need to be relative to the terminal screen, not an arbitrary ancestor
147+ // QUOTE(ТЗ): n/a
148+ // REF: PR-407-CodeRabbit-native-copy-menu-docs
149+ // SOURCE: n/a
150+ // FORMAT THEOREM: screen(host) = querySelector(.xterm-screen) ?? hostBounds(host)
151+ // PURITY: SHELL
152+ // EFFECT: optional host.querySelector lookup
153+ // INVARIANT: screen child lookup wins over host fallback
154+ // COMPLEXITY: O(1)/O(1)
53155const resolveContextMenuScreenElement = (
54156 host : TerminalNativeCopyMenuHost
55157) : TerminalCopyScreenElement | null =>
56158 host . querySelector ?.( xtermScreenSelector ) ?? resolveContextMenuHostScreenElement ( host )
57159
160+ /**
161+ * Prepares xterm's hidden textarea so the native browser context-menu Copy item copies terminal text.
162+ *
163+ * @param args - Event coordinates, host lookup facade, cached selection text, and xterm textarea facade.
164+ * @returns True iff a non-empty selection was written into a resolved textarea and selected for copying.
165+ * @pure false - mutates the injected textarea and reads DOM layout through injected facades.
166+ * @effect textarea.style/value writes, textarea.focus(), textarea.select(), screen.getBoundingClientRect().
167+ * @invariant result => selection.length > 0 and textarea.value = selection.
168+ * @precondition event coordinates are in the same viewport space as getBoundingClientRect values.
169+ * @postcondition Success positions a small helper textarea near the context-menu event and selects its value.
170+ * @complexity O(n) where n = selection.length.
171+ * @throws Never under the injected facade contracts.
172+ */
173+ // CHANGE: prepare xterm's hidden textarea before the browser context menu opens
174+ // WHY: Chrome only shows and executes native Copy when a focused selected textarea value exists
175+ // QUOTE(ТЗ): "А куда пропала кнопка copy?"
176+ // REF: user-message-2026-06-15-native-copy-menu
177+ // SOURCE: n/a
178+ // FORMAT THEOREM: selection != "" and textarea and screen => prepared(textarea, selection)
179+ // PURITY: SHELL
180+ // EFFECT: DOM textarea style/value/focus/select and layout reads
181+ // INVARIANT: false result leaves textarea unmodified by this function
182+ // COMPLEXITY: O(n)/O(1)
58183export const prepareNativeBrowserCopyMenu = (
59184 { event, host, selection, textarea } : PrepareNativeBrowserCopyMenuArgs
60185) : boolean => {
@@ -74,6 +199,28 @@ export const prepareNativeBrowserCopyMenu = (
74199 return true
75200}
76201
202+ /**
203+ * Clears the helper textarea after the copy context is consumed or invalidated.
204+ *
205+ * @param textarea - Optional xterm helper textarea facade.
206+ * @pure false - mutates the injected textarea when present.
207+ * @effect textarea.value = "".
208+ * @invariant textarea === undefined => no effect; textarea !== undefined => textarea.value = "" after return.
209+ * @precondition textarea, when present, accepts value writes.
210+ * @postcondition No stale cached selection remains in the helper textarea.
211+ * @complexity O(1)
212+ * @throws Never under the injected facade contract.
213+ */
214+ // CHANGE: clear native copy helper text when selection context is no longer active
215+ // WHY: stale helper textarea contents must not be copied after selection invalidation
216+ // QUOTE(ТЗ): n/a
217+ // REF: PR-407-CodeRabbit-native-copy-menu-docs
218+ // SOURCE: n/a
219+ // FORMAT THEOREM: clear(textarea) => textarea.value = "" when textarea exists
220+ // PURITY: SHELL
221+ // EFFECT: textarea.value mutation
222+ // INVARIANT: undefined textarea is a no-op
223+ // COMPLEXITY: O(1)/O(1)
77224export const clearNativeBrowserCopyMenu = (
78225 textarea : TerminalCopyTextarea | undefined
79226) : void => {
0 commit comments