@electerm/electerm-react 3.15.175 → 3.15.178

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.
@@ -161,6 +161,9 @@ class Term extends Component {
161
161
 
162
162
  componentWillUnmount () {
163
163
  refs.remove(this.id)
164
+ clearTimeout(this.longPressTimer)
165
+ this.longPressTimer = null
166
+ this.touchStartPos = null
164
167
  if (window.store.activeTerminalId === this.props.tab.id) {
165
168
  window.store.activeTerminalId = ''
166
169
  }
@@ -625,6 +628,131 @@ class Term extends Component {
625
628
  }
626
629
  }
627
630
 
631
+ // ---- Mobile touch support ----
632
+ // On touch devices, long-press should (1) select the word under the finger
633
+ // and (2) open the context menu — mirroring desktop right-click behaviour.
634
+ // xterm.js only uses touch events for scrolling, so we add explicit
635
+ // long-press detection here.
636
+ longPressTimer = null
637
+ touchStartPos = null
638
+ longPressThreshold = 500 // ms
639
+ longPressMoveTolerance = 10 // px
640
+
641
+ onTouchStart = (e) => {
642
+ if (e.touches.length !== 1) {
643
+ return
644
+ }
645
+ const touch = e.touches[0]
646
+ this.touchStartPos = {
647
+ clientX: touch.clientX,
648
+ clientY: touch.clientY,
649
+ target: e.currentTarget
650
+ }
651
+ clearTimeout(this.longPressTimer)
652
+ this.longPressTimer = setTimeout(() => {
653
+ this.handleLongPress()
654
+ }, this.longPressThreshold)
655
+ }
656
+
657
+ onTouchMove = (e) => {
658
+ if (!this.touchStartPos) {
659
+ return
660
+ }
661
+ const touch = e.touches[0]
662
+ const dx = touch.clientX - this.touchStartPos.clientX
663
+ const dy = touch.clientY - this.touchStartPos.clientY
664
+ if (Math.sqrt(dx * dx + dy * dy) > this.longPressMoveTolerance) {
665
+ clearTimeout(this.longPressTimer)
666
+ this.longPressTimer = null
667
+ this.touchStartPos = null
668
+ }
669
+ }
670
+
671
+ onTouchEnd = () => {
672
+ clearTimeout(this.longPressTimer)
673
+ this.longPressTimer = null
674
+ this.touchStartPos = null
675
+ }
676
+
677
+ handleLongPress = () => {
678
+ if (!this.touchStartPos || this.state.loading) {
679
+ return
680
+ }
681
+ const { clientX, clientY, target } = this.touchStartPos
682
+
683
+ // Select the word at the touch position (same as desktop right-click word
684
+ // select) so the user can immediately copy or act on it.
685
+ this.selectWordAt(clientX, clientY)
686
+
687
+ // Respect pasteWhenContextMenu: when enabled, long-press pastes instead
688
+ // of showing the menu (same as desktop right-click).
689
+ if (this.props.config.pasteWhenContextMenu) {
690
+ this.onPaste()
691
+ return
692
+ }
693
+
694
+ // Dispatch a synthetic contextmenu event so antd Dropdown's contextMenu
695
+ // trigger opens the menu at the exact finger position.
696
+ const event = new MouseEvent('contextmenu', {
697
+ bubbles: true,
698
+ cancelable: true,
699
+ clientX,
700
+ clientY
701
+ })
702
+ target.dispatchEvent(event)
703
+ }
704
+
705
+ selectWordAt = (clientX, clientY) => {
706
+ if (!this.term) {
707
+ return
708
+ }
709
+ const termElement = this.term.element
710
+ if (!termElement) {
711
+ return
712
+ }
713
+ const rect = termElement.getBoundingClientRect()
714
+ const x = clientX - rect.left
715
+ const y = clientY - rect.top
716
+ if (x < 0 || y < 0 || x > rect.width || y > rect.height) {
717
+ return
718
+ }
719
+
720
+ const cellWidth = rect.width / this.term.cols
721
+ const cellHeight = rect.height / this.term.rows
722
+ const col = Math.floor(x / cellWidth)
723
+ const row = Math.floor(y / cellHeight)
724
+
725
+ const buffer = this.term.buffer.active
726
+ const line = buffer.getLine(row)
727
+ if (!line) {
728
+ return
729
+ }
730
+
731
+ const text = line.translateToString(true)
732
+ const wordSeparator = this.props.config.terminalWordSeparator ||
733
+ ' ./\\()"\'-:,.;<>~!@#$%^&*|+=[]{}`~ ?'
734
+
735
+ // If the touched cell is empty or a separator, nothing to select
736
+ if (col >= text.length || wordSeparator.includes(text[col])) {
737
+ return
738
+ }
739
+
740
+ // Find word start
741
+ let start = col
742
+ while (start > 0 && !wordSeparator.includes(text[start - 1])) {
743
+ start--
744
+ }
745
+ // Find word end
746
+ let end = col
747
+ while (end < text.length && !wordSeparator.includes(text[end])) {
748
+ end++
749
+ }
750
+
751
+ if (end > start) {
752
+ this.term.select(start, row, end - start)
753
+ }
754
+ }
755
+
628
756
  onContextMenuInner = e => {
629
757
  e.preventDefault()
630
758
  if (this.state.loading) {
@@ -1926,7 +2054,10 @@ class Term extends Component {
1926
2054
  zIndex: 10
1927
2055
  },
1928
2056
  onDrop: this.onDrop,
1929
- onContextMenu: this.onContextMenuInner
2057
+ onContextMenu: this.onContextMenuInner,
2058
+ onTouchStart: this.onTouchStart,
2059
+ onTouchMove: this.onTouchMove,
2060
+ onTouchEnd: this.onTouchEnd
1930
2061
  }
1931
2062
  // const fileProps = {
1932
2063
  // type: 'file',
@@ -28,8 +28,6 @@
28
28
  .horizontal
29
29
  .term-wrap.not-first-term
30
30
  border-left 1px solid var(--main-lighter)
31
- .term-sftp-tabs
32
- margin-left 5px
33
31
 
34
32
  #container
35
33
  .xterm
@@ -1,7 +1,3 @@
1
- // Session pane tab label: show full name by default, short label on mobile
2
- .type-tab-short
3
- display none
4
-
5
1
  // ---------------------------------------------------------------------------
6
2
  // Mobile UI driven by store.isMobile (is-mobile class on root)
7
3
  // Breakpoint: 600px (see mobileBreakpoint in constants.js)
@@ -147,11 +143,19 @@
147
143
  .upgrade-panel
148
144
  max-width calc(100vw - 20px)
149
145
 
150
- // ---- 9. Session pane tab label: hide full name, show short label
151
- .type-tab-full
152
- display none
153
- .type-tab-short
154
- display inline
146
+ // ---- 9. Terminal touch support: prevent the browser's native long-press
147
+ // callout (iOS "Copy/Look Up" menu) and text-selection gesture from
148
+ // interfering with xterm's own touch handling and our custom
149
+ // long-press context-menu detection. xterm.js already sets
150
+ // user-select:none on .xterm; we additionally suppress the iOS
151
+ // callout and set touch-action so the browser does not compete with
152
+ // xterm for touch gesture interpretation.
153
+ .term-wrap
154
+ -webkit-touch-callout none
155
+ -webkit-tap-highlight-color transparent
156
+ .xterm
157
+ -webkit-touch-callout none
158
+ touch-action none
155
159
 
156
160
  // ---------------------------------------------------------------------------
157
161
  // Touch-primary devices only (feature-detected via pointer media query, not
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "3.15.175",
3
+ "version": "3.15.178",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",