summaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2025-05-16 22:24:17 +0200
committerYuval Adam <_@yuv.al>2025-05-16 22:30:01 +0200
commit57b5d66a0731264d003651594e283cfb793b988d (patch)
tree916c0409fbc2fd9d89e2207bcfefd1767ecbe317 /src/components
parent45002a3b223f672de2d8e7e102e3f3db586e347e (diff)
Fix wheel event handling by attaching active listeners to refs
Diffstat (limited to 'src/components')
-rw-r--r--src/components/Cidr.tsx186
1 files changed, 131 insertions, 55 deletions
diff --git a/src/components/Cidr.tsx b/src/components/Cidr.tsx
index 2ea6bba..f3f359c 100644
--- a/src/components/Cidr.tsx
+++ b/src/components/Cidr.tsx
@@ -1,5 +1,4 @@
-
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useState, useRef } from 'react';
import { Netmask } from 'netmask';
export default function Cidr() {
@@ -9,6 +8,10 @@ export default function Cidr() {
const [isCopied, setIsCopied] = useState(false);
const [isShared, setIsShared] = useState(false);
+ // Refs for the input elements
+ const ipInputRefs = useRef<(HTMLInputElement | null)[]>([]);
+ const cidrInputRef = useRef<HTMLInputElement | null>(null);
+
const bits = ip.map(octet => Array.from({ length: 8 }, (_, i) => (octet >> (7 - i)) & 1));
const parseOctet = (val: string, max: number) => {
@@ -24,11 +27,15 @@ export default function Cidr() {
setIp(newIp);
}
- const handleWheel = (event: React.WheelEvent<HTMLInputElement>, i: number, max: number) => {
- event.preventDefault();
+ // This function will now be called by our manual event listener
+ // Note: The event type is now native WheelEvent, not React.WheelEvent
+ const processWheelEvent = (event: WheelEvent, i: number, max: number) => {
+ event.preventDefault(); // This will now be respected
const min = 0;
- const target = event.currentTarget as HTMLInputElement;
- let value = parseInt(target.value);
+ const target = event.currentTarget as HTMLInputElement; // currentTarget is the element the listener is attached to
+ let value = parseInt(target.value, 10); // Always provide radix for parseInt
+
+ if (isNaN(value)) value = 0; // Handle cases where input might be non-numeric temporarily
if (event.deltaY > 0 && value > min) {
value -= 1;
@@ -37,18 +44,20 @@ export default function Cidr() {
value += 1;
}
- if (i == 4) {
+ if (i === 4) { // Differentiate based on index passed, 4 for CIDR
setCidr(value);
- }
- else {
+ } else {
setIpOctet(i, value);
}
}
+
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>, i: number, max: number) => {
const min = 0;
const target = event.currentTarget as HTMLInputElement;
- let value = parseInt(target.value);
+ let value = parseInt(target.value, 10);
+
+ if (isNaN(value)) value = 0;
if (event.key === "ArrowDown" && value > min) {
value -= 1;
@@ -58,51 +67,59 @@ export default function Cidr() {
}
else if (event.key === '.') {
event.preventDefault();
- const parent = (event.target as Node).parentNode;
- const next = parent?.nextSibling?.firstChild;
- if (next instanceof HTMLInputElement) {
- next.select();
- next.focus();
+ // Find the next IP octet input
+ if (i < 3 && ipInputRefs.current[i + 1]) {
+ ipInputRefs.current[i + 1]?.select();
+ ipInputRefs.current[i + 1]?.focus();
+ } else if (i === 3 && cidrInputRef.current) { // From last octet to CIDR with '.'
+ cidrInputRef.current.select();
+ cidrInputRef.current.focus();
}
+ return; // Important: return early so value isn't set incorrectly
}
else if (event.key === "/") {
event.preventDefault();
- const parent = (event.target as Node).parentNode;
- const mask = parent?.nextSibling;
- if (mask instanceof HTMLInputElement) {
- mask.select();
- mask.focus();
+ // Find the CIDR input
+ if (cidrInputRef.current) {
+ cidrInputRef.current.select();
+ cidrInputRef.current.focus();
+ }
+ return; // Important: return early
+ }
+ // Allow only numbers for manual input, backspace, delete, arrows
+ else if (!/^[0-9]$/.test(event.key) && !["Backspace", "Delete", "ArrowLeft", "ArrowRight", "Tab"].includes(event.key)) {
+ if (!(event.ctrlKey || event.metaKey)) { // Allow Ctrl+A, Ctrl+C, etc.
+ event.preventDefault();
+ return;
}
}
- if (i == 4) {
+
+ if (i === 4) {
setCidr(value);
- }
- else {
+ } else {
setIpOctet(i, value);
}
}
const updateCidrString = (val: string) => {
const parts = val.split("/");
- const ip = parts[0].split(".").map(Number);
- const cidr = Number(parts[1]);
+ if (parts.length !== 2) return;
- if (ip.length != 4) {
- return
- }
+ const ipParts = parts[0].split(".").map(Number);
+ const cidrVal = Number(parts[1]);
- ip.forEach(octet => {
- if (Number.isNaN(octet) || octet < 0 || octet > 255) {
- return
- }
- })
- setIp(ip);
+ if (ipParts.length !== 4 || ipParts.some(octet => isNaN(octet) || octet < 0 || octet > 255)) {
+ // Optionally provide feedback for invalid IP
+ return;
+ }
+ setIp(ipParts);
- if (Number.isNaN(cidr) || cidr < 0 || cidr > 32) {
- return
+ if (isNaN(cidrVal) || cidrVal < 0 || cidrVal > 32) {
+ // Optionally provide feedback for invalid CIDR
+ return;
}
- setCidr(cidr);
+ setCidr(cidrVal);
}
const handlePaste = (event: React.ClipboardEvent<HTMLInputElement>) => {
@@ -133,46 +150,107 @@ export default function Cidr() {
}
}, []);
+
+ // Effect to add and remove non-passive wheel event listeners
+ useEffect(() => {
+ const currentIpInputs = ipInputRefs.current;
+ const currentCidrInput = cidrInputRef.current;
+
+ // Add listeners for IP octets
+ currentIpInputs.forEach((input, index) => {
+ if (input) {
+ const wheelHandler = (e: WheelEvent) => processWheelEvent(e, index, 255);
+ input.addEventListener('wheel', wheelHandler, { passive: false });
+ // Store handler on the element for removal, or manage in a separate array
+ (input as any).__customWheelHandler = wheelHandler;
+ }
+ });
+
+ // Add listener for CIDR input
+ if (currentCidrInput) {
+ const wheelHandler = (e: WheelEvent) => processWheelEvent(e, 4, 32); // Use 4 as index for CIDR
+ currentCidrInput.addEventListener('wheel', wheelHandler, { passive: false });
+ (currentCidrInput as any).__customWheelHandler = wheelHandler;
+ }
+
+ // Cleanup function
+ return () => {
+ currentIpInputs.forEach((input) => {
+ if (input && (input as any).__customWheelHandler) {
+ input.removeEventListener('wheel', (input as any).__customWheelHandler);
+ delete (input as any).__customWheelHandler; // Clean up the property
+ }
+ });
+ if (currentCidrInput && (currentCidrInput as any).__customWheelHandler) {
+ currentCidrInput.removeEventListener('wheel', (currentCidrInput as any).__customWheelHandler);
+ delete (currentCidrInput as any).__customWheelHandler;
+ }
+ };
+ // Rerun effect if these state setters change, or if IP length changes (though it shouldn't)
+ // Key dependencies are the functions that might be recreated if not memoized,
+ // and `ip` because its length determines the number of ipInputRefs,
+ // and `setIp`, `setCidr` because they are used inside processWheelEvent.
+ }, [ip, setIp, setCidr]); // Add ip, setIp, setCidr as dependencies
+
const pretty = ip.join('.') + '/' + cidr;
- const netmask = new Netmask(pretty);
+ let netmaskInstance;
+ let details;
- const details = {
- "Netmask": netmask.mask,
- "CIDR Base IP": netmask.base,
- "Broadcast IP": netmask.broadcast || "None",
- "Count": netmask.size.toLocaleString(),
- "First Usable IP": netmask.first,
- "Last Usable IP": netmask.last
+ try {
+ netmaskInstance = new Netmask(pretty);
+ details = {
+ "Netmask": netmaskInstance.mask,
+ "CIDR Base IP": netmaskInstance.base,
+ "Broadcast IP": netmaskInstance.broadcast || "None",
+ "Count": netmaskInstance.size.toLocaleString(),
+ "First Usable IP": netmaskInstance.first || "None", // Handle cases like /31, /32
+ "Last Usable IP": netmaskInstance.last || "None" // Handle cases like /31, /32
+ };
+ } catch (e) {
+ // Handle invalid CIDR string gracefully, e.g., during initial render or bad input
+ details = {
+ "Netmask": "Invalid",
+ "CIDR Base IP": "Invalid",
+ "Broadcast IP": "Invalid",
+ "Count": "Invalid",
+ "First Usable IP": "Invalid",
+ "Last Usable IP": "Invalid"
+ };
+ console.error("Netmask error:", e);
}
- return (
+ return (
<div className="my-6 border-0 sm:border border-gray-300 rounded-lg bg-white/70 shadow-md">
<div className="flex flex-wrap justify-center gap-4 my-10">
{ip.map((octet, i) => (
<div key={`octet-${i}`}>
<input
+ ref={el => { ipInputRefs.current[i] = el; }} // Assign ref
key={`inp-${i}`}
- type="text"
+ type="text" // Changed to text to allow better control, validation on keydown
+ inputMode="numeric" // Helps mobile keyboards
value={octet}
onChange={(e) => setIpOctet(i, parseOctet(e.target.value, 255))}
- onWheel={(e) => handleWheel(e, i, 255)}
+ // onWheel prop is removed, handled by useEffect
onKeyDown={(e) => handleKeyDown(e, i, 255)}
onPaste={handlePaste}
className={`w-20 h-20 text-3xl text-center rounded-md ${cols[i]}`}
maxLength={3}
aria-label={`Octet ${i + 1}`}
/>
- <span className="text-5xl pl-4" key={`sep-${i}`}>{i == 3 ? "/" : "."}</span>
+ <span className="text-5xl pl-4" key={`sep-${i}`}>{i === 3 ? "/" : "."}</span>
</div>
))}
<input
- type="text"
+ ref={cidrInputRef} // Assign ref
+ type="text" // Changed to text
+ inputMode="numeric"
key={`inp-cidr`}
value={cidr}
onChange={(e) => setCidr(parseOctet(e.target.value, 32))}
- onWheel={(e) => handleWheel(e, 4, 32)}
- onKeyDown={(e) => handleKeyDown(e, 4, 32)}
+ // onWheel prop is removed
+ onKeyDown={(e) => handleKeyDown(e, 4, 32)} // Use 4 as index for CIDR
onPaste={handlePaste}
className={`w-20 h-20 text-3xl text-center rounded-md ${cols[4]}`}
maxLength={2}
@@ -182,7 +260,7 @@ export default function Cidr() {
<div className="flex flex-wrap justify-center gap-4 mt-10">
{bits.map((octet, i) => <span key={`octet-${i}`} className="px-1">
- {octet.map((bit, j) => <span key={`octet-${i}-bit-${j}`} className={`font-mono border-y ${j == 0 && "border-l"} border-r border-gray-700 px-2 py-1 ${i * 8 + j < cidr ? cols[i] : cols[4]}`}>{bit}</span>)}
+ {octet.map((bit, j) => <span key={`octet-${i}-bit-${j}`} className={`font-mono border-y ${j === 0 && "border-l"} border-r border-gray-700 px-2 py-1 ${i * 8 + j < cidr ? cols[i] : cols[4]}`}>{bit}</span>)}
</span>)}
</div>
@@ -213,8 +291,6 @@ export default function Cidr() {
</button>
</div>
</div>
-
</div>
-
);
} \ No newline at end of file