@dvrd/dvr-controls 1.0.81 → 1.0.83
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/index.ts +2 -2
- package/package.json +1 -1
- package/src/js/pdf/settings/invoiceTable/pdfInvoiceTableSettings.tsx +3 -3
- package/src/js/pdf/settings/text/pdfTextSettings.tsx +2 -2
- package/src/js/select/dvrdGroupedSelect.tsx +78 -16
- package/src/js/select/style/dvrdGroupedSelect.scss +14 -0
- package/src/js/textField/dvrdNumberInput.tsx +92 -106
- package/src/js/util/inputUtil.ts +21 -0
package/index.ts
CHANGED
|
@@ -47,7 +47,7 @@ import Media from './src/js/media/media';
|
|
|
47
47
|
import DvrdButton, {DvrdButtonProps} from './src/js/button/dvrdButton';
|
|
48
48
|
import DvrdDatePicker, {DVRDDatePickerRef} from './src/js/date/dvrdDatePicker';
|
|
49
49
|
import DvrdInputController from './src/js/textField/dvrdInputController';
|
|
50
|
-
import
|
|
50
|
+
import DVRDNumberInput from './src/js/textField/dvrdNumberInput';
|
|
51
51
|
import DVRDPasswordInput from './src/js/textField/dvrdPasswordInput';
|
|
52
52
|
import Link from './src/js/link/link';
|
|
53
53
|
import DvrdOptionsList from './src/js/optionsList/dvrdOptionsList';
|
|
@@ -98,7 +98,7 @@ export {
|
|
|
98
98
|
DvrdDatePicker,
|
|
99
99
|
DVRDDatePickerRef,
|
|
100
100
|
DvrdInputController as DvrdInput,
|
|
101
|
-
|
|
101
|
+
DVRDNumberInput,
|
|
102
102
|
DVRDPasswordInput,
|
|
103
103
|
Link,
|
|
104
104
|
DvrdOptionsList,
|
package/package.json
CHANGED
|
@@ -8,8 +8,8 @@ import PdfInvoiceTable from "../../invoiceTable/pdfInvoiceTable";
|
|
|
8
8
|
import {debug} from "../../../util/miscUtil";
|
|
9
9
|
import {ElementPosition} from "../../../util/interfaces";
|
|
10
10
|
import {fontItems} from "../../../util/pdfUtil";
|
|
11
|
-
import {ColorPicker, DvrdNumberInput, DvrdSelect} from "../../../../../index";
|
|
12
11
|
import IconButton, {IconButtonType} from "../buttons/iconButton";
|
|
12
|
+
import {ColorPicker, DVRDNumberInput, DvrdSelect} from "../../../../../index";
|
|
13
13
|
|
|
14
14
|
interface Props {
|
|
15
15
|
element: PdfInvoiceTable;
|
|
@@ -116,7 +116,7 @@ export default class PdfInvoiceTableSettings extends PureComponent<Props, State>
|
|
|
116
116
|
renderWidth = (value: number, index: number) => {
|
|
117
117
|
const title = TITLES[index];
|
|
118
118
|
return (
|
|
119
|
-
<
|
|
119
|
+
<DVRDNumberInput key={index} value={value.toString()} onChange={this.onChangeWidth(index)} label={title}
|
|
120
120
|
ornaments={{
|
|
121
121
|
element: <span style={{cursor: 'text'}}>%</span>,
|
|
122
122
|
placement: ElementPosition.RIGHT,
|
|
@@ -145,7 +145,7 @@ export default class PdfInvoiceTableSettings extends PureComponent<Props, State>
|
|
|
145
145
|
<label className='settings-label'>Tekstgrootte</label>
|
|
146
146
|
<DvrdSelect items={fontItems} value={settings.font} onChange={element.changeSetting('font')}
|
|
147
147
|
label='Lettertype' optionsContainerHeight='15rem' unControlled/>
|
|
148
|
-
<
|
|
148
|
+
<DVRDNumberInput value={settings.fontSize} onChange={element.changeSetting('fontSize')}
|
|
149
149
|
asNumber
|
|
150
150
|
ornaments={{
|
|
151
151
|
element: <span style={{cursor: 'text'}}>px</span>,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
PDFTextVariables,
|
|
15
15
|
SelectItemShape
|
|
16
16
|
} from "../../../util/interfaces";
|
|
17
|
-
import {ColorPicker,
|
|
17
|
+
import {ColorPicker, DVRDNumberInput, DvrdSelect, fontItems, getPdfVariables} from "../../../../../index";
|
|
18
18
|
import classNames from 'classnames';
|
|
19
19
|
import IconButton, {IconButtonType} from "../buttons/iconButton";
|
|
20
20
|
|
|
@@ -177,7 +177,7 @@ export default class PdfTextSettings extends PureComponent<Props, State> {
|
|
|
177
177
|
<div className='font-settings'>
|
|
178
178
|
<DvrdSelect items={fontItems} value={settings.font} onChange={element.changeSetting('font')}
|
|
179
179
|
label='Lettertype' optionsContainerHeight='15rem' unControlled/>
|
|
180
|
-
<
|
|
180
|
+
<DVRDNumberInput value={settings.fontSize} onChange={element.changeSetting('fontSize')}
|
|
181
181
|
label='Tekstgrootte' className='font-size'
|
|
182
182
|
ornaments={{
|
|
183
183
|
element: <span style={{cursor: 'text'}}>px</span>,
|
|
@@ -8,6 +8,7 @@ import {ErrorType, GroupedSelectItem, SelectValueType} from "../util/interfaces"
|
|
|
8
8
|
import classNames from 'classnames';
|
|
9
9
|
import AwesomeIcon from "../icon/awesomeIcon";
|
|
10
10
|
import defer from 'lodash.defer';
|
|
11
|
+
import {stopPropagation} from "../util/controlUtil";
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
interface Props {
|
|
@@ -27,6 +28,7 @@ interface Props {
|
|
|
27
28
|
maxItemsHeight?: number | string;
|
|
28
29
|
highlightSelected?: true;
|
|
29
30
|
placeholder?: string;
|
|
31
|
+
searchable?: boolean;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export type GroupedSelectRef = { open: VoidFunction; close: VoidFunction; toggle: (forcedValue?: boolean) => void };
|
|
@@ -42,12 +44,19 @@ function findInItems(items: Array<GroupedSelectItem>, value: string | number): G
|
|
|
42
44
|
return null;
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
function itemMatchesSearch(item: GroupedSelectItem, search: string): boolean {
|
|
48
|
+
if (!search) return true;
|
|
49
|
+
return item.label.toString().toLowerCase().includes(search.toLowerCase());
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
function DVRDGroupedSelect(props: Props, ref: ForwardedRef<GroupedSelectRef>) {
|
|
46
53
|
const {
|
|
47
54
|
className, label, labelClassName, value, placeholder, valueClassName, error, itemContainerClassName,
|
|
48
|
-
onChange, items, itemClassName, disabled, errorClassName, highlightSelected
|
|
55
|
+
onChange, items, itemClassName, disabled, errorClassName, highlightSelected, searchable
|
|
49
56
|
} = props;
|
|
50
57
|
const [open, setOpen] = useState(false);
|
|
58
|
+
const [search, setSearch] = useState('');
|
|
59
|
+
const [searchActive, setSearchActive] = useState(false);
|
|
51
60
|
const maxItemsHeight: string | undefined = useMemo(() => {
|
|
52
61
|
const {maxItemsHeight} = props;
|
|
53
62
|
if (maxItemsHeight) {
|
|
@@ -71,8 +80,36 @@ function DVRDGroupedSelect(props: Props, ref: ForwardedRef<GroupedSelectRef>) {
|
|
|
71
80
|
longest = item.label.toString();
|
|
72
81
|
return longest;
|
|
73
82
|
}, [items]);
|
|
83
|
+
const availableItems = useMemo(() => {
|
|
84
|
+
if (!searchable || !search.trim()) return items;
|
|
85
|
+
const _items: Array<GroupedSelectItem> = [];
|
|
86
|
+
for (const item of items) {
|
|
87
|
+
if (itemMatchesSearch(item, search)) _items.push(item);
|
|
88
|
+
else if (item.children) {
|
|
89
|
+
if (!!item.children.find((child: GroupedSelectItem) => itemMatchesSearch(child, search)))
|
|
90
|
+
_items.push(item);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return _items;
|
|
94
|
+
}, [items, searchable, search]);
|
|
74
95
|
const itemsContainer = useRef<HTMLDivElement>(null);
|
|
75
96
|
|
|
97
|
+
function onChangeSearch(evt: React.ChangeEvent<HTMLInputElement>) {
|
|
98
|
+
const {value} = evt.target;
|
|
99
|
+
setSearch(value);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function onFocusSearch() {
|
|
103
|
+
if (searchable) {
|
|
104
|
+
setSearchActive(true);
|
|
105
|
+
setOpen(true);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function onBlurSearch() {
|
|
110
|
+
if (searchable) setSearchActive(false);
|
|
111
|
+
}
|
|
112
|
+
|
|
76
113
|
function onSelectItem(item: GroupedSelectItem) {
|
|
77
114
|
return function (evt: React.MouseEvent) {
|
|
78
115
|
evt.stopPropagation();
|
|
@@ -122,36 +159,61 @@ function DVRDGroupedSelect(props: Props, ref: ForwardedRef<GroupedSelectRef>) {
|
|
|
122
159
|
}
|
|
123
160
|
|
|
124
161
|
function renderValue() {
|
|
125
|
-
const hasValue = selectedLabel !== null;
|
|
126
|
-
const canRender = hasValue && !['string', 'number'].includes(typeof selectedLabel)
|
|
127
162
|
const chevIcon = itemsPosition === 'bottom' ? 'chevron-down' : 'chevron-up';
|
|
128
|
-
const placeholderHidden = !placeholder?.length;
|
|
129
163
|
return (
|
|
130
|
-
<div className={classNames('grouped-select-value-container', valueClassName)}>
|
|
131
|
-
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
{placeholderHidden ? 'placeholder' : placeholder}</label>
|
|
136
|
-
}
|
|
164
|
+
<div className={classNames('grouped-select-value-container', searchable && 'searchable', valueClassName)}>
|
|
165
|
+
{renderValueInput()}
|
|
166
|
+
{renderValueCustom()}
|
|
167
|
+
{renderValueLabel()}
|
|
168
|
+
{renderValuePlaceholder()}
|
|
137
169
|
<AwesomeIcon name={chevIcon} className='chev-icon'/>
|
|
138
170
|
<div style={{height: 0, visibility: 'hidden'}}>{longestValue}</div>
|
|
139
171
|
</div>
|
|
140
172
|
);
|
|
141
173
|
}
|
|
142
174
|
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
175
|
+
function renderValueInput() {
|
|
176
|
+
if (!searchable) return null;
|
|
177
|
+
const _value = searchActive ? search : selectedLabel?.toString();
|
|
178
|
+
return (
|
|
179
|
+
<input onChange={onChangeSearch} value={_value} onFocus={onFocusSearch}
|
|
180
|
+
onBlur={onBlurSearch} className='dvrd-grouped-select-search' disabled={disabled}
|
|
181
|
+
onClick={stopPropagation} placeholder={placeholder}/>
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function renderValueCustom() {
|
|
186
|
+
if (selectedLabel === null || searchable) return null; // Nothing selected, or using a search input
|
|
187
|
+
if (['string', 'number'].includes(typeof selectedLabel)) return null; // Not a custom element
|
|
188
|
+
const element = selectedLabel as React.ReactElement;
|
|
189
|
+
return React.cloneElement(element, {
|
|
190
|
+
...element.props,
|
|
191
|
+
className: classNames(element.props?.className, 'grouped-select-value')
|
|
147
192
|
});
|
|
148
193
|
}
|
|
149
194
|
|
|
195
|
+
function renderValueLabel() {
|
|
196
|
+
if (selectedLabel === null || searchable) return null; // Nothing selected, or using a search input
|
|
197
|
+
if (!['string', 'number'].includes(typeof selectedLabel)) return null; // A custom element
|
|
198
|
+
return (
|
|
199
|
+
<label className='grouped-select-value'>{selectedLabel}</label>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function renderValuePlaceholder() {
|
|
204
|
+
if (selectedLabel !== null || searchable) return null; // Something is selected, or we are using a search input
|
|
205
|
+
const placeholderHidden = !placeholder?.length;
|
|
206
|
+
return (
|
|
207
|
+
<label className={classNames('grouped-select-placeholder', placeholderHidden && 'hidden')}>
|
|
208
|
+
{placeholderHidden ? 'placeholder' : placeholder}</label>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
150
212
|
function renderItemsContainer() {
|
|
151
213
|
return (
|
|
152
214
|
<div className={classNames('grouped-select-items', itemContainerClassName)}
|
|
153
215
|
style={{maxHeight: maxItemsHeight}} ref={itemsContainer}>
|
|
154
|
-
{
|
|
216
|
+
{availableItems.map(renderItem())}
|
|
155
217
|
</div>
|
|
156
218
|
)
|
|
157
219
|
}
|
|
@@ -33,6 +33,16 @@
|
|
|
33
33
|
cursor: pointer;
|
|
34
34
|
background-color: white;
|
|
35
35
|
|
|
36
|
+
.dvrd-grouped-select-search {
|
|
37
|
+
width: 100%;
|
|
38
|
+
outline: none;
|
|
39
|
+
border: none;
|
|
40
|
+
padding: .75rem;
|
|
41
|
+
color: #2A435F;
|
|
42
|
+
border-radius: inherit;
|
|
43
|
+
font-family: avenir-light, sans-serif;
|
|
44
|
+
}
|
|
45
|
+
|
|
36
46
|
.grouped-select-placeholder, .grouped-select-value {
|
|
37
47
|
user-select: none;
|
|
38
48
|
vertical-align: middle;
|
|
@@ -55,6 +65,10 @@
|
|
|
55
65
|
transition: transform .2s ease-in-out;
|
|
56
66
|
color: $color-gray-3;
|
|
57
67
|
}
|
|
68
|
+
|
|
69
|
+
&.searchable {
|
|
70
|
+
padding: 0 .75rem 0 0;
|
|
71
|
+
}
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
.grouped-select-items {
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* Copyright (c) 2024. Dave van Rijn Development
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import React, {FocusEventHandler, InputHTMLAttributes, KeyboardEventHandler,
|
|
5
|
+
import React, {FocusEventHandler, InputHTMLAttributes, KeyboardEventHandler, useEffect, useRef, useState} from 'react';
|
|
6
|
+
import {generateComponentId} from '../util/componentUtil';
|
|
6
7
|
import {ChangeFunction, ErrorType, OrnamentShape} from '../util/interfaces';
|
|
7
8
|
import {roundTo} from '../util/miscUtil';
|
|
8
9
|
import DvrdInputController from "./dvrdInputController";
|
|
10
|
+
import {isPasting, positionIsAfterComma, textIsSelected} from "../util/inputUtil";
|
|
9
11
|
|
|
10
12
|
interface Props {
|
|
11
13
|
onChange: ChangeFunction<string | number>;
|
|
@@ -28,130 +30,114 @@ interface Props {
|
|
|
28
30
|
error?: ErrorType;
|
|
29
31
|
id?: string;
|
|
30
32
|
placeholder?: string;
|
|
31
|
-
multipleSeparators
|
|
32
|
-
wholeNumbers
|
|
33
|
+
multipleSeparators?: boolean;
|
|
34
|
+
wholeNumbers?: boolean;
|
|
33
35
|
asNumber?: boolean;
|
|
34
|
-
asCurrency
|
|
36
|
+
asCurrency?: boolean;
|
|
35
37
|
autoSelect?: boolean;
|
|
36
38
|
unControlled?: boolean;
|
|
39
|
+
disableSymbols?: boolean;
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
|
|
42
|
+
export default function DVRDNumberInput(props: Props) {
|
|
43
|
+
const {
|
|
44
|
+
onKeyDown, inputProps, asCurrency, asNumber, wholeNumbers, disableSymbols, onChange, value, unControlled
|
|
45
|
+
} = props;
|
|
46
|
+
const [innerValue, setInnerValue] = useState(value);
|
|
47
|
+
const id = useRef(generateComponentId(props.id));
|
|
40
48
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
function _onKeyDown(evt: React.KeyboardEvent<HTMLInputElement>) {
|
|
50
|
+
const {key} = evt,
|
|
51
|
+
numValue = Number(value);
|
|
52
|
+
// Allow only number and simple edit keys
|
|
53
|
+
const editKeys = /\d|Backspace|ArrowRight|ArrowLeft|ArrowUp|ArrowDown|Escape|Esc|Enter|[,.\-+]|Tab/;
|
|
54
|
+
if (!editKeys.test(key) && !isPasting(evt))
|
|
55
|
+
evt.preventDefault();
|
|
44
56
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
// Handle Arrow events
|
|
58
|
+
if (['ArrowUp', 'ArrowDown'].includes(key)) {
|
|
59
|
+
evt.preventDefault();
|
|
60
|
+
if (!isNaN(numValue))
|
|
61
|
+
return onArrowEvent(evt, numValue);
|
|
62
|
+
}
|
|
51
63
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
// Handle event if no selected text
|
|
65
|
+
onKeyDownSelected(evt);
|
|
66
|
+
|
|
67
|
+
if (onKeyDown)
|
|
68
|
+
onKeyDown(evt);
|
|
57
69
|
}
|
|
58
70
|
|
|
71
|
+
function onArrowEvent(evt: React.KeyboardEvent<HTMLInputElement>, numValue: number) {
|
|
72
|
+
const {key} = evt,
|
|
73
|
+
step = Number(inputProps?.step || asCurrency ? .01 : 1);
|
|
74
|
+
if (key === 'ArrowUp')
|
|
75
|
+
numValue += step;
|
|
76
|
+
else if (key === 'ArrowDown')
|
|
77
|
+
numValue -= step;
|
|
78
|
+
let stringValue = numValue.toString();
|
|
79
|
+
if (asCurrency) stringValue = roundTo(numValue, 2);
|
|
80
|
+
if (asNumber) change(Number(stringValue));
|
|
81
|
+
else change(stringValue);
|
|
82
|
+
}
|
|
59
83
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (!/\d|Backspace|ArrowRight|ArrowLeft|ArrowUp|ArrowDown|Escape|Esc|Enter|[,.\-+]|Tab|Delete|Del/.test(key) &&
|
|
67
|
-
!this.isPasting(evt))
|
|
84
|
+
function onKeyDownSelected(evt: React.KeyboardEvent<HTMLInputElement>) {
|
|
85
|
+
if (textIsSelected(evt)) return;
|
|
86
|
+
const {currentTarget, key} = evt,
|
|
87
|
+
position = currentTarget.selectionStart;
|
|
88
|
+
if (/[,.]/.test(key) && /[,.]/.test(value.toString()) && asCurrency)
|
|
89
|
+
// Prevent double points or commas
|
|
68
90
|
evt.preventDefault();
|
|
69
91
|
|
|
70
|
-
if (
|
|
92
|
+
if (wholeNumbers && /[.,]/.test(key))
|
|
71
93
|
evt.preventDefault();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
let stringValue = numValue.toString();
|
|
76
|
-
if (asCurrency) stringValue = roundTo(numValue, 2);
|
|
77
|
-
if (asNumber) this.change(Number(stringValue), evt);
|
|
78
|
-
else this.change(stringValue, evt);
|
|
79
|
-
}
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
if (key === 'ArrowDown') {
|
|
94
|
+
else if (asCurrency && /[.,]\d{2}/.test(value.toString()) && /\d/.test(key) &&
|
|
95
|
+
positionIsAfterComma(position, value.toString()))
|
|
96
|
+
// Prevent more than 2 decimals when working with currencies
|
|
83
97
|
evt.preventDefault();
|
|
84
|
-
if (!isNaN(numValue)) {
|
|
85
|
-
const step = Number(inputProps?.step || asCurrency ? .01 : 1);
|
|
86
|
-
numValue -= step;
|
|
87
|
-
let stringValue = numValue.toString();
|
|
88
|
-
if (asCurrency) stringValue = roundTo(numValue, 2);
|
|
89
|
-
if (asNumber) this.change(Number(stringValue), evt);
|
|
90
|
-
else this.change(stringValue, evt);
|
|
91
|
-
}
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
98
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
evt.preventDefault();
|
|
100
|
-
|
|
101
|
-
if (wholeNumbers && /[.,]/.test(key))
|
|
102
|
-
evt.preventDefault();
|
|
103
|
-
else if (asCurrency && /[.,]\d{2}/.test(value.toString()) && /\d/.test(key) &&
|
|
104
|
-
this.positionIsAfterComma(position, value.toString()))
|
|
105
|
-
// Prevent more than 2 decimals when working with currencies
|
|
106
|
-
evt.preventDefault();
|
|
107
|
-
|
|
108
|
-
if ((position && position !== 0) && /[\-+]/.test(key))
|
|
109
|
-
// Prevent - or + sign after first position
|
|
110
|
-
evt.preventDefault();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (onKeyDown !== undefined && onKeyDown !== null)
|
|
114
|
-
onKeyDown(evt);
|
|
115
|
-
};
|
|
99
|
+
if (/[\-+]/.test(key) && (disableSymbols || (position && position !== 0)))
|
|
100
|
+
// Prevent - or + sign completely or after first position
|
|
101
|
+
evt.preventDefault();
|
|
102
|
+
}
|
|
116
103
|
|
|
117
|
-
|
|
118
|
-
|
|
104
|
+
function _onChange(value: string, evt: React.ChangeEvent<HTMLInputElement>) {
|
|
105
|
+
value = value.replace(/ /g, '');
|
|
119
106
|
if (!/^-?[\d.,]*$/.test(value)) evt.preventDefault();
|
|
120
107
|
else {
|
|
121
|
-
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
108
|
+
if (value.startsWith('.') || value.startsWith(',')) value = '0' + value;
|
|
109
|
+
if (wholeNumbers)
|
|
110
|
+
// Only allow numbers and minus sign for negative numbers
|
|
111
|
+
value = value.replace(/[^0-9-]/g, '').trim();
|
|
112
|
+
if (disableSymbols)
|
|
113
|
+
value = value.replace(/[\-+]/g, '').trim();
|
|
114
|
+
if (asNumber) {
|
|
115
|
+
const numValue = Number(value.replace(',', '.'));
|
|
116
|
+
change(numValue, evt);
|
|
117
|
+
} else change(value, evt);
|
|
125
118
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
positionIsAfterComma = (position: number | null, value: string) => {
|
|
139
|
-
if (position === null) return true;
|
|
140
|
-
if (value.includes('.')) return position > value.indexOf('.');
|
|
141
|
-
if (value.includes(',')) return position > value.indexOf(',');
|
|
142
|
-
return false;
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
textIsSelected = (evt: React.KeyboardEvent) => {
|
|
146
|
-
const target = evt.target as HTMLInputElement, start = target.selectionStart, end = target.selectionEnd;
|
|
147
|
-
return start != end;
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
render = () => {
|
|
151
|
-
const value = this.props.unControlled ? this.state.value : this.props.value;
|
|
152
|
-
return (
|
|
153
|
-
<DvrdInputController {...this.props} value={value} onChange={this.onChange} onKeyDown={this.onKeyDown}
|
|
154
|
-
unControlled={false}/>
|
|
155
|
-
);
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function change(value: number | string, evt?: React.ChangeEvent<HTMLInputElement>) {
|
|
123
|
+
setInnerValue(value);
|
|
124
|
+
onChange(value, evt);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function focusInput() {
|
|
128
|
+
document.getElementById(id.current + '-input')?.focus();
|
|
156
129
|
}
|
|
130
|
+
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
if (!unControlled) setInnerValue(value);
|
|
133
|
+
}, [value]);
|
|
134
|
+
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
if (!props.disabled && props.autoFocus)
|
|
137
|
+
focusInput();
|
|
138
|
+
}, [props.disabled, props.autoFocus]);
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<DvrdInputController {...props} id={id.current} onChange={_onChange} onKeyDown={_onKeyDown} value={innerValue}/>
|
|
142
|
+
);
|
|
157
143
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2024. Dave van Rijn Development
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function isPasting(evt: React.KeyboardEvent): boolean {
|
|
6
|
+
return (evt.metaKey || evt.ctrlKey) && evt.key === 'v';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function textIsSelected(evt: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>): boolean {
|
|
10
|
+
const {currentTarget} = evt,
|
|
11
|
+
start = currentTarget.selectionStart,
|
|
12
|
+
end = currentTarget.selectionEnd;
|
|
13
|
+
return start != end;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function positionIsAfterComma(position: number | null, value: string): boolean {
|
|
17
|
+
if (position === null) return true;
|
|
18
|
+
if (value.includes('.')) return position > value.indexOf('.');
|
|
19
|
+
if (value.includes(',')) return position > value.indexOf(',');
|
|
20
|
+
return false;
|
|
21
|
+
}
|