react-retro-display-tty-ansi-ascii 0.1.39 → 0.1.46
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/README.md +60 -0
- package/dist/index.cjs +71 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -1
- package/dist/styles.css +16 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,66 @@ That is the whole entry point.
|
|
|
45
45
|
You hand the component a mode, a value or controller when needed, and let it handle the grid,
|
|
46
46
|
wrapping, cursor rendering, and terminal feel.
|
|
47
47
|
|
|
48
|
+
## Touch Input
|
|
49
|
+
|
|
50
|
+
Use `touchInput` when the display itself should behave like a touch surface and report
|
|
51
|
+
grid-aligned cell hits back to the host application.
|
|
52
|
+
|
|
53
|
+
The touch contract is intentionally simple:
|
|
54
|
+
|
|
55
|
+
- the component emits 1-based `row` and `col` coordinates
|
|
56
|
+
- it also reports the measured `rows` and `cols` that were active for that touch
|
|
57
|
+
- input is delivered as a single `down` event for each press
|
|
58
|
+
- long presses do not stream repeated events
|
|
59
|
+
- move events are ignored by default in touch mode
|
|
60
|
+
- the next touch is not accepted until the current press is released
|
|
61
|
+
|
|
62
|
+
That makes it a good fit for grid-driven interfaces such as soft terminals, retro games,
|
|
63
|
+
touch menus, and keypad-like overlays where the host wants to interpret one deliberate press
|
|
64
|
+
at a time.
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { useState } from "react";
|
|
68
|
+
import { RetroScreen } from "react-retro-display-tty-ansi-ascii";
|
|
69
|
+
import "react-retro-display-tty-ansi-ascii/styles.css";
|
|
70
|
+
|
|
71
|
+
export function TouchDemo() {
|
|
72
|
+
const [lastTouch, setLastTouch] = useState("Tap the screen");
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<RetroScreen
|
|
76
|
+
mode="terminal"
|
|
77
|
+
gridMode="static"
|
|
78
|
+
rows={12}
|
|
79
|
+
cols={32}
|
|
80
|
+
value={[
|
|
81
|
+
"┌──────────────────────────────┐",
|
|
82
|
+
"│ │",
|
|
83
|
+
"│ TOUCH ME │",
|
|
84
|
+
"│ │",
|
|
85
|
+
"│ ↑ up right → │",
|
|
86
|
+
"│ │",
|
|
87
|
+
"│ ← left down ↓ │",
|
|
88
|
+
"│ │",
|
|
89
|
+
"│ │",
|
|
90
|
+
"│ │",
|
|
91
|
+
"│ │",
|
|
92
|
+
"└──────────────────────────────┘"
|
|
93
|
+
].join("\\n")}
|
|
94
|
+
touchInput={{
|
|
95
|
+
enabled: true,
|
|
96
|
+
onTouchCell: ({ row, col, rows, cols, pointerType }) => {
|
|
97
|
+
setLastTouch(`${pointerType} @ ${row},${col} inside ${rows}x${cols}`);
|
|
98
|
+
}
|
|
99
|
+
}}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The host remains responsible for deciding what a touch means. RetroScreen only handles the
|
|
106
|
+
overlay, pointer capture, hit testing, and the single-press-until-release behavior.
|
|
107
|
+
|
|
48
108
|
## Display Padding
|
|
49
109
|
|
|
50
110
|
Use `displayPadding` when the screen content should sit tighter to the glass or breathe a little
|
package/dist/index.cjs
CHANGED
|
@@ -3915,6 +3915,10 @@ var joinClassNames2 = (...classNames) => classNames.filter(Boolean).join(" ");
|
|
|
3915
3915
|
var useIsomorphicLayoutEffect2 = typeof window === "undefined" ? import_react9.useEffect : import_react9.useLayoutEffect;
|
|
3916
3916
|
var clampSelection2 = (value, text) => Math.max(0, Math.min(text.length, Number.isFinite(value) ? Math.floor(value) : text.length));
|
|
3917
3917
|
var isMouseTrackingActive = (snapshot) => snapshot.modes.mouseTrackingMode !== "none" && snapshot.modes.mouseProtocol === "sgr";
|
|
3918
|
+
var shouldHandleRetroTouchPointer = ({
|
|
3919
|
+
enabled,
|
|
3920
|
+
phase
|
|
3921
|
+
}) => Boolean(enabled) && phase === "down";
|
|
3918
3922
|
var isEditorWordNavigationModifier = (event) => event.altKey || event.ctrlKey || event.metaKey;
|
|
3919
3923
|
var isEditorSelectAllShortcut = (event) => (event.ctrlKey || event.metaKey) && !event.altKey && event.key.toLowerCase() === "a";
|
|
3920
3924
|
var toTerminalMouseButton = (button) => {
|
|
@@ -3958,6 +3962,7 @@ function RetroScreen(props) {
|
|
|
3958
3962
|
const editorProps = props.mode === "editor" ? props : null;
|
|
3959
3963
|
const terminalProps = props.mode === "terminal" ? props : null;
|
|
3960
3964
|
const promptProps = props.mode === "prompt" ? props : null;
|
|
3965
|
+
const touchInput = props.touchInput;
|
|
3961
3966
|
const screenRef = (0, import_react9.useRef)(null);
|
|
3962
3967
|
const viewportRef = (0, import_react9.useRef)(null);
|
|
3963
3968
|
const probeRef = (0, import_react9.useRef)(null);
|
|
@@ -3967,6 +3972,7 @@ function RetroScreen(props) {
|
|
|
3967
3972
|
const editorSelectionAnchorRef = (0, import_react9.useRef)(null);
|
|
3968
3973
|
const editorSelectionDraggingRef = (0, import_react9.useRef)(false);
|
|
3969
3974
|
const previousEditableValueRef = (0, import_react9.useRef)(valueProps?.value ?? "");
|
|
3975
|
+
const activeTouchPointerIdRef = (0, import_react9.useRef)(null);
|
|
3970
3976
|
const internalTerminalController = useRetroScreenController({
|
|
3971
3977
|
rows: staticGridActive ? props.rows : void 0,
|
|
3972
3978
|
cols: staticGridActive ? props.cols : void 0,
|
|
@@ -4590,6 +4596,51 @@ function RetroScreen(props) {
|
|
|
4590
4596
|
shiftKey: event.shiftKey
|
|
4591
4597
|
};
|
|
4592
4598
|
};
|
|
4599
|
+
const handleTouchOverlayPointer = async (event, phase) => {
|
|
4600
|
+
if (phase === "up") {
|
|
4601
|
+
if (activeTouchPointerIdRef.current === event.pointerId) {
|
|
4602
|
+
activeTouchPointerIdRef.current = null;
|
|
4603
|
+
}
|
|
4604
|
+
try {
|
|
4605
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
4606
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
4607
|
+
}
|
|
4608
|
+
} catch {
|
|
4609
|
+
}
|
|
4610
|
+
return;
|
|
4611
|
+
}
|
|
4612
|
+
if (!touchInput?.onTouchCell || !shouldHandleRetroTouchPointer({ enabled: touchInput.enabled, phase })) {
|
|
4613
|
+
return;
|
|
4614
|
+
}
|
|
4615
|
+
if (activeTouchPointerIdRef.current !== null && activeTouchPointerIdRef.current !== event.pointerId) {
|
|
4616
|
+
return;
|
|
4617
|
+
}
|
|
4618
|
+
const screenNode = screenRef.current;
|
|
4619
|
+
if (!screenNode) {
|
|
4620
|
+
return;
|
|
4621
|
+
}
|
|
4622
|
+
const { row, col } = getRetroScreenPointerGridPosition({
|
|
4623
|
+
clientX: event.clientX,
|
|
4624
|
+
clientY: event.clientY,
|
|
4625
|
+
rect: screenNode.getBoundingClientRect(),
|
|
4626
|
+
geometry
|
|
4627
|
+
});
|
|
4628
|
+
activeTouchPointerIdRef.current = event.pointerId;
|
|
4629
|
+
try {
|
|
4630
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
4631
|
+
} catch {
|
|
4632
|
+
}
|
|
4633
|
+
event.preventDefault();
|
|
4634
|
+
await touchInput.onTouchCell({
|
|
4635
|
+
row,
|
|
4636
|
+
col,
|
|
4637
|
+
rows: geometry.rows,
|
|
4638
|
+
cols: geometry.cols,
|
|
4639
|
+
phase,
|
|
4640
|
+
pointerType: event.pointerType,
|
|
4641
|
+
buttons: event.buttons
|
|
4642
|
+
});
|
|
4643
|
+
};
|
|
4593
4644
|
const maybeEmitFocusReport = (focusedState) => {
|
|
4594
4645
|
if (props.mode !== "terminal" || !captureTerminalFocusReport || !terminalSnapshot.modes.focusReportingMode) {
|
|
4595
4646
|
return;
|
|
@@ -4822,6 +4873,26 @@ function RetroScreen(props) {
|
|
|
4822
4873
|
"data-fit-width-content-aspect-ratio": fitWidthLayoutMetrics ? String(fitWidthLayoutMetrics.contentAspectRatio) : void 0,
|
|
4823
4874
|
children: [
|
|
4824
4875
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "retro-screen__bezel", "aria-hidden": "true" }),
|
|
4876
|
+
touchInput?.enabled ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
4877
|
+
"div",
|
|
4878
|
+
{
|
|
4879
|
+
className: "retro-screen__touch-overlay",
|
|
4880
|
+
"data-testid": touchInput.overlayTestId,
|
|
4881
|
+
"aria-hidden": "true",
|
|
4882
|
+
onPointerCancel: (event) => {
|
|
4883
|
+
void handleTouchOverlayPointer(event, "up");
|
|
4884
|
+
},
|
|
4885
|
+
onPointerDown: (event) => {
|
|
4886
|
+
void handleTouchOverlayPointer(event, "down");
|
|
4887
|
+
},
|
|
4888
|
+
onPointerMove: (event) => {
|
|
4889
|
+
void handleTouchOverlayPointer(event, "move");
|
|
4890
|
+
},
|
|
4891
|
+
onPointerUp: (event) => {
|
|
4892
|
+
void handleTouchOverlayPointer(event, "up");
|
|
4893
|
+
}
|
|
4894
|
+
}
|
|
4895
|
+
) : null,
|
|
4825
4896
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
4826
4897
|
RetroScreenDisplay,
|
|
4827
4898
|
{
|