@particle-academy/fancy-term 0.4.0 → 0.5.0

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.
package/dist/index.d.ts CHANGED
@@ -143,7 +143,13 @@ interface TerminalContextMenuItem {
143
143
  }
144
144
  /** The actions the built-in items dispatch to (wired by the component). */
145
145
  interface TerminalMenuActions {
146
- copy: () => void;
146
+ /**
147
+ * Receives the menu's {@link TerminalContextMenuContext} so it can copy the
148
+ * selection **snapshotted when the menu opened** — by click time the live
149
+ * xterm selection is often already cleared (a mouse-reporting TUI's redraw
150
+ * clears it right after the right-click), so re-reading it copies nothing.
151
+ */
152
+ copy: (ctx: TerminalContextMenuContext) => void;
147
153
  paste: () => void;
148
154
  selectAll: () => void;
149
155
  clear: () => void;
@@ -537,4 +543,24 @@ interface MenuSize {
537
543
  */
538
544
  declare function clampMenuPosition(at: MenuPoint, menu: MenuSize, viewport: MenuSize, margin?: number): MenuPoint;
539
545
 
540
- export { BUILTIN_SHELLS, type ClipboardOption, type ClipboardPayload, type ClipboardProvider, type CopyKeyEvent, type CopyPasteBehavior, type CopyPasteMode, type CursorStyle, type KeyEventLike, type Osc52Mode, type Osc52Request, type ShellProfile, ShellSwitcher, type ShellSwitcherProps, Terminal, TerminalContextMenu, type TerminalContextMenuConfig, type TerminalContextMenuContext, type TerminalContextMenuItem, type TerminalHandle, type TerminalMenuActions, type TerminalOptions, type TerminalProps, type TerminalSessionApi, type TerminalSessionTransport, type TerminalTheme, type UseTerminalSessionOptions, clampMenuPosition, copyPasteBehavior, decodeBase64, defaultMenuItems, encodeBase64, fancyDarkTheme, isImageFile, navigatorClipboard, osc52Response, parseOsc52, registerOsc52, resolveClipboard, resolveKeyAction, resolveMenuItems, resolveShell, shouldCopyEvent, useTerminal, useTerminalFit, useTerminalSession };
546
+ /** The bits of a pointer event the snapshot decision needs. */
547
+ interface SnapshotPointerEvent {
548
+ type: "mousedown" | "mouseup";
549
+ /** 0 = left, 1 = middle, 2 = right. */
550
+ button: number;
551
+ }
552
+ /**
553
+ * Compute the next selection snapshot after a pointer event on the terminal
554
+ * surface.
555
+ *
556
+ * - A fresh **left press** starts a new gesture — drop the snapshot (the live
557
+ * selection still reads the *old* text at capture time, so it must not win).
558
+ * - Otherwise a **non-empty live selection** always refreshes the snapshot
559
+ * (drag-select completes on left mouseup; a right press re-captures while
560
+ * the selection is still alive).
561
+ * - An **empty** live selection keeps the previous snapshot — that's exactly
562
+ * the TUI-cleared-it-under-us case the snapshot exists for.
563
+ */
564
+ declare function nextSelectionSnapshot(prev: string, event: SnapshotPointerEvent, liveSelection: string): string;
565
+
566
+ export { BUILTIN_SHELLS, type ClipboardOption, type ClipboardPayload, type ClipboardProvider, type CopyKeyEvent, type CopyPasteBehavior, type CopyPasteMode, type CursorStyle, type KeyEventLike, type Osc52Mode, type Osc52Request, type ShellProfile, ShellSwitcher, type ShellSwitcherProps, type SnapshotPointerEvent, Terminal, TerminalContextMenu, type TerminalContextMenuConfig, type TerminalContextMenuContext, type TerminalContextMenuItem, type TerminalHandle, type TerminalMenuActions, type TerminalOptions, type TerminalProps, type TerminalSessionApi, type TerminalSessionTransport, type TerminalTheme, type UseTerminalSessionOptions, clampMenuPosition, copyPasteBehavior, decodeBase64, defaultMenuItems, encodeBase64, fancyDarkTheme, isImageFile, navigatorClipboard, nextSelectionSnapshot, osc52Response, parseOsc52, registerOsc52, resolveClipboard, resolveKeyAction, resolveMenuItems, resolveShell, shouldCopyEvent, useTerminal, useTerminalFit, useTerminalSession };
package/dist/index.js CHANGED
@@ -767,6 +767,12 @@ function resolveMenuItems(config, ctx, actions) {
767
767
  const items = config === void 0 || config === true ? defaults : Array.isArray(config) ? config : config(ctx, defaults);
768
768
  return items.length > 0 ? items : null;
769
769
  }
770
+
771
+ // src/selection-snapshot.ts
772
+ function nextSelectionSnapshot(prev, event, liveSelection) {
773
+ if (event.type === "mousedown" && event.button === 0) return "";
774
+ return liveSelection || prev;
775
+ }
770
776
  var Terminal = forwardRef(function Terminal2({
771
777
  output,
772
778
  theme,
@@ -867,25 +873,42 @@ var Terminal = forwardRef(function Terminal2({
867
873
  written.current = output;
868
874
  }, [output, handle]);
869
875
  const [menu, setMenu] = useState(null);
876
+ const selectionSnapshot = useRef("");
877
+ const trackSelection = useCallback(
878
+ (e) => {
879
+ selectionSnapshot.current = nextSelectionSnapshot(
880
+ selectionSnapshot.current,
881
+ { type: e.type, button: e.button },
882
+ handle.getSelection()
883
+ );
884
+ },
885
+ [handle]
886
+ );
870
887
  const openMenu = useCallback(
871
888
  (e) => {
872
889
  if (contextMenu === false) return;
873
890
  e.preventDefault();
874
- const selection = handle.getSelection();
891
+ const selection = handle.getSelection() || selectionSnapshot.current;
875
892
  const ctx = {
876
893
  hasSelection: selection.length > 0,
877
894
  selection,
878
895
  readOnly: !!readOnly
879
896
  };
880
897
  const items = resolveMenuItems(contextMenu, ctx, {
881
- copy: () => void handle.copySelection(),
898
+ // Copy writes the SNAPSHOTTED selection through the active clipboard
899
+ // provider — never a click-time re-read, which copies "" over a TUI
900
+ // (falling back to the live selection only when the snapshot is empty).
901
+ copy: (c) => {
902
+ const text = c.selection || handle.getSelection();
903
+ if (text) void providerWrite(resolveClipboard(clipboard).provider, text);
904
+ },
882
905
  paste: () => void handle.paste(),
883
906
  selectAll: () => handle.selectAll(),
884
907
  clear: () => handle.clear()
885
908
  });
886
909
  if (items) setMenu({ at: { x: e.clientX, y: e.clientY }, items, ctx });
887
910
  },
888
- [contextMenu, handle, readOnly]
911
+ [contextMenu, handle, readOnly, clipboard]
889
912
  );
890
913
  const menuNode = menu ? /* @__PURE__ */ jsx(TerminalContextMenu, { at: menu.at, items: menu.items, ctx: menu.ctx, onClose: () => setMenu(null) }) : null;
891
914
  const surface = /* @__PURE__ */ jsx(
@@ -893,6 +916,8 @@ var Terminal = forwardRef(function Terminal2({
893
916
  {
894
917
  ref: containerRef,
895
918
  "data-fancy-terminal": "",
919
+ onMouseDownCapture: trackSelection,
920
+ onMouseUpCapture: trackSelection,
896
921
  onContextMenu: openMenu,
897
922
  "data-readonly": readOnly ? "" : void 0,
898
923
  "data-fancy-terminal-shell": shellId ?? void 0,
@@ -995,6 +1020,6 @@ function shouldCopyEvent(e, hasSelection) {
995
1020
  return false;
996
1021
  }
997
1022
 
998
- export { BUILTIN_SHELLS, ShellSwitcher, Terminal, TerminalContextMenu, clampMenuPosition, copyPasteBehavior, decodeBase64, defaultMenuItems, encodeBase64, fancyDarkTheme, isImageFile, navigatorClipboard, osc52Response, parseOsc52, registerOsc52, resolveClipboard, resolveKeyAction, resolveMenuItems, resolveShell, shouldCopyEvent, useTerminal, useTerminalFit, useTerminalSession };
1023
+ export { BUILTIN_SHELLS, ShellSwitcher, Terminal, TerminalContextMenu, clampMenuPosition, copyPasteBehavior, decodeBase64, defaultMenuItems, encodeBase64, fancyDarkTheme, isImageFile, navigatorClipboard, nextSelectionSnapshot, osc52Response, parseOsc52, registerOsc52, resolveClipboard, resolveKeyAction, resolveMenuItems, resolveShell, shouldCopyEvent, useTerminal, useTerminalFit, useTerminalSession };
999
1024
  //# sourceMappingURL=index.js.map
1000
1025
  //# sourceMappingURL=index.js.map