@dvrd/dvr-controls 1.0.82 → 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 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 DvrdNumberInput from './src/js/textField/dvrdNumberInput';
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
- DvrdNumberInput,
101
+ DVRDNumberInput,
102
102
  DVRDPasswordInput,
103
103
  Link,
104
104
  DvrdOptionsList,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dvrd/dvr-controls",
3
- "version": "1.0.82",
3
+ "version": "1.0.83",
4
4
  "description": "Custom web controls",
5
5
  "main": "index.ts",
6
6
  "files": [
@@ -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
- <DvrdNumberInput key={index} value={value.toString()} onChange={this.onChangeWidth(index)} label={title}
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
- <DvrdNumberInput value={settings.fontSize} onChange={element.changeSetting('fontSize')}
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, DvrdNumberInput, DvrdSelect, fontItems, getPdfVariables} from "../../../../../index";
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
- <DvrdNumberInput value={settings.fontSize} onChange={element.changeSetting('fontSize')}
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>,
@@ -2,10 +2,12 @@
2
2
  * Copyright (c) 2024. Dave van Rijn Development
3
3
  */
4
4
 
5
- import React, {FocusEventHandler, InputHTMLAttributes, KeyboardEventHandler, PureComponent} from 'react';
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: boolean;
32
- wholeNumbers: boolean;
33
+ multipleSeparators?: boolean;
34
+ wholeNumbers?: boolean;
33
35
  asNumber?: boolean;
34
- asCurrency: boolean;
36
+ asCurrency?: boolean;
35
37
  autoSelect?: boolean;
36
38
  unControlled?: boolean;
39
+ disableSymbols?: boolean;
37
40
  }
38
41
 
39
- // TODO Add option to disable symbols (+,-)
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
- interface State {
42
- value: number | string;
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
- export default class DvrdNumberInput extends PureComponent<Props, State> {
46
- static defaultProps = {
47
- asCurrency: false,
48
- wholeNumbers: false,
49
- multipleSeparators: false,
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
- constructor(props: Props) {
53
- super(props);
54
- this.state = {
55
- value: props.value,
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
- onKeyDown = (evt: React.KeyboardEvent) => {
61
- const {onKeyDown, asCurrency, wholeNumbers, inputProps, asNumber, multipleSeparators} = this.props;
62
- const {value} = this.state;
63
- const {key, target} = evt;
64
- let numValue = Number(value);
65
- // Allow only number and simple edit keys
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 (key === 'ArrowUp') {
92
+ if (wholeNumbers && /[.,]/.test(key))
71
93
  evt.preventDefault();
72
- if (!isNaN(numValue)) {
73
- const step = Number(inputProps?.step || asCurrency ? .01 : 1);
74
- numValue += step;
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
- const textIsSelected = this.textIsSelected(evt), position = (target as HTMLInputElement).selectionStart;
96
- if (!textIsSelected) {
97
- if (/[,.]/.test(key) && /[,.]/.test(value.toString()) && !multipleSeparators)
98
- // Prevent double points or commas
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
- onChange = (value: string, evt: React.ChangeEvent) => {
118
- const {asNumber} = this.props;
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
- let changeValue: string | number = value;
122
- if (value.startsWith('.') || value.startsWith(',')) value = 0 + value;
123
- if (asNumber) changeValue = Number(value.replace(',', '.'));
124
- this.change(changeValue, evt);
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
- change = (value: string | number, evt: React.ChangeEvent | React.KeyboardEvent) => {
129
- this.setState({value}, () => {
130
- this.props.onChange(value, evt as React.ChangeEvent);
131
- })
132
- };
133
-
134
- isPasting = (evt: React.KeyboardEvent) => {
135
- return (evt.metaKey || evt.ctrlKey) && evt.key === 'v';
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
+ }