PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.5.5
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.5.5
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / vendor / team-updraft / lib-onboarding-wizard / Wizard / Onboarding / src / components / Fields / NumberInputWithControls.tsx

NumberInputWithControls.tsx in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 4.5.5, at vendor/team-updraft/lib-onboarding-wizard/Wizard/Onboarding/src/components/Fields/NumberInputWithControls.tsx

158 lines 5.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import {
2 memo, useState, useEffect,
3 type ChangeEvent,
4 type KeyboardEvent,
5 type ClipboardEvent
6 } from 'react';
7 import FieldWrapper from './FieldWrapper';
8 import TextInput from './TextInput';
9 import Icon from '../../utils/Icon';
10 import { __ } from '@wordpress/i18n';
11
12 interface NumberInputWithControlsProps {
13 field: {
14 id: string;
15 label?: string;
16 default?: number;
17 min?: number;
18 max?: number;
19 is_lock?: boolean;
20 tooltip?: any;
21 [key: string]: any;
22 };
23 value: number;
24 onChange: (value: number) => void;
25 }
26
27 const NumberInputWithControls = ({ field, value, onChange }: NumberInputWithControlsProps) => {
28 const min = field.min ?? 0;
29 const max = field.max ?? Infinity;
30 const disabled = field.is_lock === true;
31
32 const [internalValue, setInternalValue] = useState<number>(value);
33
34 useEffect(() => {
35 setInternalValue(value);
36 }, [value]);
37
38 const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
39 const sanitizedValue = e.target.value.replace(/[^0-9]/g, '');
40 const newValue = sanitizedValue === '' ? min : parseInt(sanitizedValue, 10);
41
42 const clampedValue = Math.max(min, Math.min(max, newValue));
43 setInternalValue(clampedValue);
44 onChange(clampedValue);
45 };
46
47 const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
48 if (
49 ['Backspace', 'Delete', 'Tab', 'Escape', 'Enter', 'ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key) ||
50 ((e.ctrlKey || e.metaKey) && ['a', 'c', 'v', 'x'].includes(e.key.toLowerCase()))
51 ) {
52 return;
53 }
54 if (!/^\d$/.test(e.key)) {
55 e.preventDefault();
56 }
57 };
58
59 const handlePaste = (e: ClipboardEvent<HTMLInputElement>) => {
60 e.preventDefault();
61 if (disabled) return;
62
63 const pasteData = e.clipboardData.getData('text');
64 const sanitizedPasteData = pasteData.replace(/[^0-9]/g, '');
65
66 const currentTarget = e.target as HTMLInputElement;
67 const start = currentTarget.selectionStart ?? 0;
68 const end = currentTarget.selectionEnd ?? 0;
69 const currentValue = currentTarget.value;
70
71 const newValueString = currentValue.substring(0, start) + sanitizedPasteData + currentValue.substring(end);
72 const finalSanitizedValue = newValueString.replace(/[^0-9]/g, '');
73
74 const newValue = finalSanitizedValue === '' ? min : parseInt(finalSanitizedValue, 10);
75 const clampedValue = Math.max(min, Math.min(max, newValue));
76
77 setInternalValue(clampedValue);
78 onChange(clampedValue);
79
80 setTimeout(() => {
81 const newCursorPosition = start + sanitizedPasteData.length;
82 currentTarget.value = String(clampedValue);
83 currentTarget.setSelectionRange(newCursorPosition, newCursorPosition);
84 }, 0);
85 };
86
87 const handleIncrement = () => {
88 if (!disabled && internalValue < max) {
89 const newValue = internalValue + 1;
90 setInternalValue(newValue);
91 onChange(newValue);
92 }
93 };
94
95 const handleDecrement = () => {
96 if (!disabled && internalValue > min) {
97 const newValue = internalValue - 1;
98 setInternalValue(newValue);
99 onChange(newValue);
100 }
101 };
102
103 return (
104 <FieldWrapper
105 inputId={field.id}
106 label={
107 <div className="flex items-center gap-2">
108 <span>{field.label}</span>
109 {field.tooltip && (
110 <Icon
111 name={field.tooltip.icon ?? 'info'}
112 color="gray500"
113 fill="gray500"
114 size={16}
115 tooltip={field.tooltip}
116 className="ml-[-4px]"
117 />
118 )}
119 </div>
120 }
121 >
122 <div className="flex w-full items-center border border-gray-400 rounded-md overflow-hidden">
123 <TextInput
124 id={field.id}
125 type="number"
126 value={internalValue}
127 onChange={handleInputChange}
128 onKeyDown={handleKeyDown}
129 onPaste={handlePaste}
130 min={min}
131 max={max}
132 disabled={disabled}
133 className="text-center flex-1 border-none focus:ring-0 focus:border-none rounded-none bg-white"
134 />
135 <div className="flex h-full divide-x divide-gray-400">
136 <button
137 type="button"
138 onClick={handleDecrement}
139 disabled={disabled || internalValue <= min}
140 className="p-2 bg-gray-100 hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed"
141 >
142 <Icon name="minus" size={16} color="gray-700" />
143 </button>
144 <button
145 type="button"
146 onClick={handleIncrement}
147 disabled={disabled || internalValue >= max}
148 className="p-2 bg-gray-100 hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed"
149 >
150 <Icon name="plus" size={16} color="gray-700" />
151 </button>
152 </div>
153 </div>
154 </FieldWrapper>
155 );
156 };
157
158 export default memo(NumberInputWithControls);