x4js 2.0.35 → 2.1.0-manual
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 +21 -21
- package/package.json +39 -26
- package/src/components/base.scss +25 -89
- package/src/components/boxes/boxes.module.scss +54 -54
- package/src/components/boxes/boxes.ts +513 -513
- package/src/components/breadcrumb/breadcrumb.scss +56 -56
- package/src/components/breadcrumb/breadcrumb.ts +93 -93
- package/src/components/btngroup/btngroup.module.scss +40 -40
- package/src/components/btngroup/btngroup.ts +152 -152
- package/src/components/button/button.module.scss +172 -172
- package/src/components/button/button.ts +232 -232
- package/src/components/calendar/calendar.module.scss +162 -162
- package/src/components/calendar/calendar.ts +326 -326
- package/src/components/canvas/canvas.module.scss +24 -24
- package/src/components/canvas/canvas.ts +195 -195
- package/src/components/canvas/canvas_ex.ts +275 -275
- package/src/components/checkbox/check.svg +3 -3
- package/src/components/checkbox/checkbox.module.scss +141 -141
- package/src/components/checkbox/checkbox.ts +139 -139
- package/src/components/colorinput/colorinput.module.scss +64 -64
- package/src/components/colorinput/colorinput.ts +90 -90
- package/src/components/colorpicker/colorpicker.module.scss +132 -132
- package/src/components/colorpicker/colorpicker.ts +481 -481
- package/src/components/combobox/combobox.module.scss +145 -145
- package/src/components/combobox/combobox.ts +282 -282
- package/src/components/combobox/updown.svg +3 -3
- package/src/components/components.ts +45 -44
- package/src/components/dialog/dialog.module.scss +103 -105
- package/src/components/dialog/dialog.ts +233 -233
- package/src/components/filedrop/filedrop.module.scss +69 -69
- package/src/components/filedrop/filedrop.ts +130 -130
- package/src/components/form/form.module.scss +38 -38
- package/src/components/form/form.ts +172 -172
- package/src/components/gridview/gridview.module.scss +323 -337
- package/src/components/gridview/gridview.ts +1276 -1316
- package/src/components/header/header.module.scss +40 -40
- package/src/components/header/header.ts +141 -141
- package/src/components/icon/icon.module.scss +32 -32
- package/src/components/icon/icon.ts +165 -165
- package/src/components/image/image.module.scss +27 -27
- package/src/components/image/image.ts +168 -168
- package/src/components/input/input.module.scss +74 -74
- package/src/components/input/input.ts +537 -537
- package/src/components/keyboard/keyboard.module.scss +136 -136
- package/src/components/keyboard/keyboard.ts +549 -549
- package/src/components/label/label.module.scss +90 -91
- package/src/components/label/label.ts +101 -101
- package/src/components/link/link.module.scss +44 -44
- package/src/components/link/link.ts +87 -87
- package/src/components/listbox/listbox.module.scss +179 -179
- package/src/components/listbox/listbox.ts +596 -596
- package/src/components/menu/menu.module.scss +128 -128
- package/src/components/menu/menu.ts +174 -174
- package/src/components/messages/messages.module.scss +92 -146
- package/src/components/messages/messages.ts +237 -303
- package/src/components/normalize.scss +391 -391
- package/src/components/notification/notification.module.scss +83 -83
- package/src/components/notification/notification.ts +107 -107
- package/src/components/panel/panel.module.scss +66 -71
- package/src/components/panel/panel.ts +57 -57
- package/src/components/popup/popup.module.scss +51 -51
- package/src/components/popup/popup.ts +457 -457
- package/src/components/progress/progress.module.scss +56 -56
- package/src/components/progress/progress.ts +43 -43
- package/src/components/propgrid/progrid.module.scss +111 -111
- package/src/components/propgrid/propgrid.ts +300 -300
- package/src/components/propgrid/updown.svg +3 -3
- package/src/components/radio/radio.module.scss +163 -163
- package/src/components/radio/radio.svg +3 -3
- package/src/components/radio/radio.ts +141 -141
- package/src/components/rating/rating.module.scss +22 -22
- package/src/components/rating/rating.ts +131 -131
- package/src/components/select/select.module.scss +8 -8
- package/src/components/select/select.ts +134 -134
- package/src/components/shared.scss +141 -71
- package/src/components/sizers/sizer.module.scss +90 -112
- package/src/components/sizers/sizer.ts +131 -155
- package/src/components/slider/slider.module.scss +117 -117
- package/src/components/slider/slider.ts +197 -197
- package/src/components/spreadsheet/spreadsheet.module.scss +307 -307
- package/src/components/spreadsheet/spreadsheet.ts +1223 -1223
- package/src/components/switch/switch.module.scss +126 -126
- package/src/components/switch/switch.ts +61 -61
- package/src/components/tabs/tabs.module.scss +46 -67
- package/src/components/tabs/tabs.ts +229 -234
- package/src/components/textarea/textarea.module.scss +63 -63
- package/src/components/textarea/textarea.ts +131 -131
- package/src/components/textedit/textedit.module.scss +115 -115
- package/src/components/textedit/textedit.ts +122 -122
- package/src/components/themes.scss +90 -90
- package/src/components/tickline/tickline.module.scss +25 -25
- package/src/components/tickline/tickline.ts +81 -81
- package/src/components/tooltips/tooltips.scss +71 -71
- package/src/components/tooltips/tooltips.ts +120 -120
- package/src/components/treeview/treeview.module.scss +192 -192
- package/src/components/treeview/treeview.ts +484 -484
- package/src/components/viewport/viewport.module.scss +31 -31
- package/src/components/viewport/viewport.ts +41 -41
- package/src/core/component.ts +1299 -1299
- package/src/core/core_application.ts +361 -361
- package/src/core/core_colors.ts +512 -512
- package/src/core/core_data.ts +1297 -1310
- package/src/core/core_dom.ts +481 -481
- package/src/core/core_dragdrop.ts +225 -225
- package/src/core/core_element.ts +221 -221
- package/src/core/core_events.ts +214 -214
- package/src/core/core_i18n.ts +395 -395
- package/src/core/core_pdf.ts +454 -454
- package/src/core/core_react.ts +78 -78
- package/src/core/core_router.ts +296 -296
- package/src/core/core_state.ts +62 -62
- package/src/core/core_styles.ts +213 -213
- package/src/core/core_svg.ts +1042 -1042
- package/src/core/core_tools.ts +996 -996
- package/src/types/scss.d.ts +4 -4
- package/src/types/x4react.d.ts +8 -8
- package/src/x4.scss +19 -19
- package/src/x4.ts +36 -36
- package/src/x4tsx.d.ts +26 -26
- package/.vscode/launch.json +0 -14
- package/.vscode/settings.json +0 -2
- package/demo/assets/house-light.svg +0 -1
- package/demo/assets/radio.svg +0 -4
- package/demo/index.html +0 -12
- package/demo/main.scss +0 -23
- package/demo/main.ts +0 -324
- package/demo/package.json +0 -26
- package/demo/scss.d.ts +0 -4
- package/demo/svg.d.ts +0 -1
- package/demo/tsconfig.json +0 -14
- package/src/components/gridview/folder-open.svg +0 -1
- package/src/components/messages/spinner.svg +0 -1
- package/src/x4.d.ts +0 -10
- package/tsconfig.json +0 -11
|
@@ -1,537 +1,537 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ___ ___ __
|
|
3
|
-
* \ \/ / / _
|
|
4
|
-
* \ / /_| |_
|
|
5
|
-
* / \____ _|
|
|
6
|
-
* /__/\__\ |_|
|
|
7
|
-
*
|
|
8
|
-
* @file input.ts
|
|
9
|
-
* @author Etienne Cochard
|
|
10
|
-
*
|
|
11
|
-
* @copyright (c) 2024 R-libre ingenierie
|
|
12
|
-
*
|
|
13
|
-
* Use of this source code is governed by an MIT-style license
|
|
14
|
-
* that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
|
|
15
|
-
**/
|
|
16
|
-
|
|
17
|
-
import { EventCallback } from '../../core/core_events';
|
|
18
|
-
import { Component, ComponentEvent, componentFromDOM, ComponentProps, EvChange, EvFocus } from '../../core/component';
|
|
19
|
-
import { class_ns, formatIntlDate, IComponentInterface, IFormElement, isString } from '../../core/core_tools';
|
|
20
|
-
|
|
21
|
-
import "./input.module.scss"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
function getRadioOwner( el: Element ) {
|
|
25
|
-
|
|
26
|
-
while( el!=document.body ) {
|
|
27
|
-
const comp = componentFromDOM(el);
|
|
28
|
-
const ifx = comp.queryInterface( "tab-handler");
|
|
29
|
-
if( ifx ) {
|
|
30
|
-
return el;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
el = el.parentElement;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return document;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Base properties for all input types.
|
|
42
|
-
*/
|
|
43
|
-
|
|
44
|
-
export interface BaseProps extends ComponentProps {
|
|
45
|
-
/**
|
|
46
|
-
* Input field name.
|
|
47
|
-
* required if you want to use form getValues/setValues
|
|
48
|
-
*/
|
|
49
|
-
|
|
50
|
-
name?: string;
|
|
51
|
-
/** Automatically focus the input on page load. */
|
|
52
|
-
autofocus?: boolean;
|
|
53
|
-
/** Marks the input as required. */
|
|
54
|
-
required?: boolean;
|
|
55
|
-
/** Makes the input read-only. */
|
|
56
|
-
readonly?: boolean;
|
|
57
|
-
/** Placeholder text displayed when empty. */
|
|
58
|
-
placeholder?: string;
|
|
59
|
-
/** Fired when the input receives/loses focus. */
|
|
60
|
-
focus?: EventCallback<EvFocus>;
|
|
61
|
-
/** Fired when the input value changes. */
|
|
62
|
-
change?: EventCallback<EvChange>;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Checkbox-specific input properties.
|
|
67
|
-
*/
|
|
68
|
-
|
|
69
|
-
interface CheckboxProps extends BaseProps {
|
|
70
|
-
type: "checkbox";
|
|
71
|
-
/** Checkbox value (submitted when checked). */
|
|
72
|
-
value?: boolean | number | string;
|
|
73
|
-
/** Initial checked state. */
|
|
74
|
-
checked?: boolean;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Radio button-specific input properties.
|
|
79
|
-
*/
|
|
80
|
-
|
|
81
|
-
interface RadioProps extends BaseProps {
|
|
82
|
-
type: "radio";
|
|
83
|
-
/** Radio value (submitted when selected). */
|
|
84
|
-
value?: boolean | number | string;
|
|
85
|
-
/** Initial checked state. */
|
|
86
|
-
checked?: boolean;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Range slider input properties.
|
|
91
|
-
*/
|
|
92
|
-
|
|
93
|
-
export interface RangeProps extends BaseProps {
|
|
94
|
-
type: "range";
|
|
95
|
-
/** Current slider value. */
|
|
96
|
-
value?: number;
|
|
97
|
-
/** Minimum allowed value. */
|
|
98
|
-
min: number;
|
|
99
|
-
/** Maximum allowed value. */
|
|
100
|
-
max: number;
|
|
101
|
-
/** Step increment. */
|
|
102
|
-
step?: number;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* File upload input properties.
|
|
107
|
-
*/
|
|
108
|
-
export interface FileProps extends BaseProps {
|
|
109
|
-
type: "file";
|
|
110
|
-
/** Allowed file types (e.g., `"image/*"` or `[".pdf", ".doc"]`). */
|
|
111
|
-
accept: string | string[];
|
|
112
|
-
value?: never;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Date picker input properties.
|
|
117
|
-
*/
|
|
118
|
-
export interface DateProps extends BaseProps {
|
|
119
|
-
type: "date";
|
|
120
|
-
/** Current date value (Date object or ISO string). */
|
|
121
|
-
value?: Date | string;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Time picker input properties.
|
|
126
|
-
*/
|
|
127
|
-
|
|
128
|
-
export interface TimeProps extends BaseProps {
|
|
129
|
-
type: "time";
|
|
130
|
-
readonly?: boolean;
|
|
131
|
-
required?: boolean;
|
|
132
|
-
/** Current time value (e.g., `"12:30"`). */
|
|
133
|
-
value?: string;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Numeric input properties.
|
|
138
|
-
*/
|
|
139
|
-
|
|
140
|
-
export interface NumberProps extends BaseProps {
|
|
141
|
-
type: "number";
|
|
142
|
-
readonly?: boolean;
|
|
143
|
-
required?: boolean;
|
|
144
|
-
/** Current numeric value. */
|
|
145
|
-
value?: number | string;
|
|
146
|
-
/** Minimum allowed value. */
|
|
147
|
-
min?: number;
|
|
148
|
-
/** Maximum allowed value. */
|
|
149
|
-
max?: number;
|
|
150
|
-
/** Step increment. */
|
|
151
|
-
step?: number;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Text/email/password input properties.
|
|
156
|
-
*/
|
|
157
|
-
|
|
158
|
-
export interface TextInputProps extends BaseProps {
|
|
159
|
-
type?: "text" | "email" | "password";
|
|
160
|
-
readonly?: boolean;
|
|
161
|
-
required?: boolean;
|
|
162
|
-
/** Regex pattern for validation. */
|
|
163
|
-
pattern?: string;
|
|
164
|
-
/** Input value. */
|
|
165
|
-
value?: string | number;
|
|
166
|
-
/** Enables/disables spellcheck. */
|
|
167
|
-
spellcheck?: boolean;
|
|
168
|
-
/** Minimum input length. */
|
|
169
|
-
minlength?: number;
|
|
170
|
-
/** Maximum input length. */
|
|
171
|
-
maxlength?: number;
|
|
172
|
-
trim?: boolean;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
export type InputProps = TextInputProps | CheckboxProps | RadioProps | RangeProps | DateProps | NumberProps | FileProps | TimeProps;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
interface InputEvents extends ComponentEvent {
|
|
180
|
-
focus: EvFocus;
|
|
181
|
-
change: EvChange;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Customizable input component supporting multiple types (text, number, date, etc.).
|
|
187
|
-
* Auto-generates CSS class: `x4input`.
|
|
188
|
-
*
|
|
189
|
-
* @example
|
|
190
|
-
* ```ts
|
|
191
|
-
* // Text input
|
|
192
|
-
* const nameInput = new Input({ type: "text", placeholder: "Enter name" });
|
|
193
|
-
*
|
|
194
|
-
* // Checkbox
|
|
195
|
-
* const agreeCheckbox = new Input({
|
|
196
|
-
* type: "checkbox",
|
|
197
|
-
* checked: true,
|
|
198
|
-
* change: (e) => console.log("Checked:", e.value)
|
|
199
|
-
* });
|
|
200
|
-
* ```
|
|
201
|
-
*/
|
|
202
|
-
|
|
203
|
-
@class_ns( "x4" )
|
|
204
|
-
export class Input extends Component<InputProps,InputEvents> {
|
|
205
|
-
constructor( props: InputProps ) {
|
|
206
|
-
super( { tag: "input", ...props } );
|
|
207
|
-
|
|
208
|
-
this.mapPropEvents( props, "focus", "change" );
|
|
209
|
-
|
|
210
|
-
this.setAttribute( "type", props.type ?? "text" );
|
|
211
|
-
this.setAttribute( "name", props.name );
|
|
212
|
-
|
|
213
|
-
if( props.autofocus===true ) {
|
|
214
|
-
this.setAttribute( "autofocus", true );
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
switch( props.type ) {
|
|
218
|
-
case "checkbox":
|
|
219
|
-
case "radio": {
|
|
220
|
-
const ck = this.dom as HTMLInputElement;
|
|
221
|
-
ck.checked = props.checked;
|
|
222
|
-
ck.value = props.value === undefined ? "" : props.value+"";
|
|
223
|
-
//this.setAttribute( "checked", props.checked );
|
|
224
|
-
//this.setAttribute( "value", props.value );
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
case "range": {
|
|
229
|
-
this.setAttribute( "min", props.min );
|
|
230
|
-
this.setAttribute( "max", props.max );
|
|
231
|
-
this.setAttribute( "step", props.step );
|
|
232
|
-
this.setAttribute( "value", props.value );
|
|
233
|
-
break;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
case "number": {
|
|
237
|
-
const p = this.props as NumberProps;
|
|
238
|
-
|
|
239
|
-
this.setAttribute( "required", p.required );
|
|
240
|
-
this.setAttribute( "readonly", p.readonly );
|
|
241
|
-
this.setAttribute( "min", p.min );
|
|
242
|
-
this.setAttribute( "max", p.max );
|
|
243
|
-
this.setAttribute( "step", p.step );
|
|
244
|
-
//this.setAttribute( "value", p.value+'' );
|
|
245
|
-
this.setNumValue( isString(p.value) ? parseFloat(p.value) : p.value, -2 );
|
|
246
|
-
|
|
247
|
-
this.addDOMEvent( "wheel", ( e: WheelEvent ) => {
|
|
248
|
-
if( this.hasFocus() ) { // only if has focus
|
|
249
|
-
e.preventDefault( );
|
|
250
|
-
let v = this.getNumValue();
|
|
251
|
-
const delta = e.deltaY < 0 ? 1 : -1;
|
|
252
|
-
v += (p.step ? p.step : 1) * delta;
|
|
253
|
-
this.setNumValue( v, -2 );
|
|
254
|
-
|
|
255
|
-
this.dom.dispatchEvent(new Event('input'));
|
|
256
|
-
}
|
|
257
|
-
}, );
|
|
258
|
-
break;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
case "date": {
|
|
262
|
-
this.setAttribute( "required", props.required );
|
|
263
|
-
|
|
264
|
-
let v = props.value;
|
|
265
|
-
if( v instanceof Date ) {
|
|
266
|
-
this.setAttribute( "value", formatIntlDate( v, "Y-M-D" ) );
|
|
267
|
-
}
|
|
268
|
-
else if( props.value!==null && props.value!==undefined ) {
|
|
269
|
-
this.setAttribute( "value", v );
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
case "file": {
|
|
276
|
-
let v: string;
|
|
277
|
-
if( Array.isArray(props.accept) ) {
|
|
278
|
-
v = props.accept.join("," );
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
v = props.accept;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
this.setAttribute( "accept", v );
|
|
285
|
-
break;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
case "time": {
|
|
289
|
-
this.setAttribute( "required", props.required );
|
|
290
|
-
if( props.value!==null && props.value!==undefined ) {
|
|
291
|
-
this.setAttribute( "value", props.value );
|
|
292
|
-
}
|
|
293
|
-
break;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
default: {
|
|
297
|
-
this.setAttribute( "required", props.required );
|
|
298
|
-
this.setAttribute( "readonly", props.readonly );
|
|
299
|
-
|
|
300
|
-
if( props.value!==null && props.value!==undefined ) {
|
|
301
|
-
this.setAttribute( "value", props.value );
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if( props.pattern!==null && props.pattern!==undefined ) {
|
|
305
|
-
this.setAttribute( "pattern", props.pattern );
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
if( props.placeholder!==null && props.placeholder!==undefined ) {
|
|
309
|
-
this.setAttribute( "placeholder", props.placeholder );
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if( props.spellcheck===false ) {
|
|
313
|
-
this.setAttribute( "spellcheck", false );
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if( props.minlength!==undefined ) {
|
|
317
|
-
this.setAttribute( "minlength", props.minlength );
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if( props.maxlength!==undefined ) {
|
|
321
|
-
this.setAttribute( "maxlength", props.maxlength );
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
this.addDOMEvent( "blur", ( e ) => { this.on_focus(e,true);} );
|
|
329
|
-
this.addDOMEvent( "focus", ( e ) => { this.on_focus(e,false);} );
|
|
330
|
-
this.addDOMEvent( "input", ( e ) => { this.on_change(e as InputEvent); });
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
*
|
|
335
|
-
*/
|
|
336
|
-
|
|
337
|
-
private on_focus( ev: FocusEvent, focus_out: boolean ) {
|
|
338
|
-
const event: EvFocus = { focus_out }
|
|
339
|
-
this.fire( "focus", event );
|
|
340
|
-
|
|
341
|
-
if( event.defaultPrevented ) {
|
|
342
|
-
ev.preventDefault( );
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
*
|
|
348
|
-
*/
|
|
349
|
-
|
|
350
|
-
private on_change( ev: InputEvent ) {
|
|
351
|
-
|
|
352
|
-
const event: EvChange = { value: this.getValue() };
|
|
353
|
-
this.fire( "change", event );
|
|
354
|
-
|
|
355
|
-
if( event.defaultPrevented ) {
|
|
356
|
-
ev.preventDefault( );
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/** Gets the current input value as a string. */
|
|
361
|
-
|
|
362
|
-
public getValue( ) {
|
|
363
|
-
let v = (this.dom as HTMLInputElement).value;
|
|
364
|
-
if( (this.props as any).trim!==false ) {
|
|
365
|
-
v = v.trim( );
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return v;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Sets the input value.
|
|
373
|
-
* @param value - New value (converted to string).
|
|
374
|
-
*/
|
|
375
|
-
|
|
376
|
-
public setValue( value: string ) {
|
|
377
|
-
(this.dom as HTMLInputElement).value = value+"";
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Gets the numeric value (for `type="number"` or `type="range"`).
|
|
382
|
-
* @param defNan - Default value if parsing fails (default: `NaN`).
|
|
383
|
-
* @returns Parsed number or `defNan`.
|
|
384
|
-
*/
|
|
385
|
-
|
|
386
|
-
public getNumValue( defNan?: number ) {
|
|
387
|
-
const v = parseFloat( this.getValue() );
|
|
388
|
-
if( isNaN(v) && defNan!==undefined ) {
|
|
389
|
-
return defNan;
|
|
390
|
-
}
|
|
391
|
-
return v;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Sets a numeric value with optional decimal precision.
|
|
396
|
-
* @param value - Numeric value to set.
|
|
397
|
-
* @param ndec - Decimal places:
|
|
398
|
-
* `-1` = auto,
|
|
399
|
-
* `-2` = use `step` prop,
|
|
400
|
-
* `≥0` = fixed decimals.
|
|
401
|
-
*/
|
|
402
|
-
|
|
403
|
-
public setNumValue( value: number, ndec = -1 ) {
|
|
404
|
-
|
|
405
|
-
if( ndec==-2 && this.props.type=='number' ) {
|
|
406
|
-
const p = this.props as NumberProps;
|
|
407
|
-
|
|
408
|
-
if( p.step<1 ) {
|
|
409
|
-
let ndec = -Math.floor(Math.log10(p.step ?? 1) );
|
|
410
|
-
return this.setValue( value.toFixed(ndec) );
|
|
411
|
-
}
|
|
412
|
-
else if( p.step>1 ) {
|
|
413
|
-
return this.setValue( value.toFixed() );
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
else if( ndec>=0 ) {
|
|
417
|
-
return this.setValue( value.toFixed(ndec) );
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
this.setValue( value+"" );
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/** Gets the checked state (for checkboxes/radio buttons). */
|
|
424
|
-
public getCheck() {
|
|
425
|
-
const d = this.dom as HTMLInputElement;
|
|
426
|
-
return d.checked;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/** Sets the checked state (for checkboxes/radio buttons). */
|
|
430
|
-
|
|
431
|
-
public setCheck(ck: boolean) {
|
|
432
|
-
const d = this.dom as HTMLInputElement;
|
|
433
|
-
d.checked = ck;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/** Toggles read-only mode. */
|
|
437
|
-
|
|
438
|
-
public setReadOnly( ro: boolean ) {
|
|
439
|
-
const d = this.dom as HTMLInputElement;
|
|
440
|
-
d.readOnly = ro;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/** Selects all text in the input. */
|
|
444
|
-
|
|
445
|
-
public selectAll( ) {
|
|
446
|
-
const d = this.dom as HTMLInputElement;
|
|
447
|
-
d.select();
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* Selects a text range.
|
|
452
|
-
* @param start - Start position
|
|
453
|
-
* @param length - Length of selection
|
|
454
|
-
*/
|
|
455
|
-
|
|
456
|
-
public select( start: number, length: number = 9999 ) : void {
|
|
457
|
-
const d = this.dom as HTMLInputElement;
|
|
458
|
-
d.setSelectionRange( start, start+length );
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* Gets the current text selection.
|
|
463
|
-
* @returns Object with `start` and `length` properties.
|
|
464
|
-
*/
|
|
465
|
-
public getSelection( ) {
|
|
466
|
-
const d = this.dom as HTMLInputElement;
|
|
467
|
-
|
|
468
|
-
return {
|
|
469
|
-
start: d.selectionStart,
|
|
470
|
-
length: d.selectionEnd - d.selectionStart,
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
/** Validates the input (checks `required` constraint). */
|
|
475
|
-
|
|
476
|
-
public isValid( ) {
|
|
477
|
-
|
|
478
|
-
if( (this.props as any).required ) {
|
|
479
|
-
const v = this.getValue( );
|
|
480
|
-
if( v==="" ) {
|
|
481
|
-
return false;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
return true;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
*
|
|
490
|
-
*/
|
|
491
|
-
|
|
492
|
-
override queryInterface<T extends IComponentInterface>( name: string ): T {
|
|
493
|
-
if( name=="form-element" ) {
|
|
494
|
-
const i: IFormElement = {
|
|
495
|
-
getRawValue: ( ): any => {
|
|
496
|
-
if( this.props.type=='checkbox' ) {
|
|
497
|
-
return this.getCheck( );
|
|
498
|
-
}
|
|
499
|
-
else if( this.props.type=='radio' ) {
|
|
500
|
-
const owner = getRadioOwner( this.dom );
|
|
501
|
-
const checked = owner.querySelector( `input[name="${this.props.name}"]:checked` )
|
|
502
|
-
return checked ? (checked as HTMLInputElement).value : undefined;
|
|
503
|
-
}
|
|
504
|
-
else if( this.props.type=='number' ) {
|
|
505
|
-
return this.getNumValue( 0 );
|
|
506
|
-
}
|
|
507
|
-
else {
|
|
508
|
-
return this.getValue();
|
|
509
|
-
}
|
|
510
|
-
},
|
|
511
|
-
setRawValue: ( v: any ) => {
|
|
512
|
-
if( this.props.type=='checkbox' ) {
|
|
513
|
-
this.setCheck( !!v );
|
|
514
|
-
}
|
|
515
|
-
else if( this.props.type=='radio' ) {
|
|
516
|
-
if( this.props.value==v ) {
|
|
517
|
-
this.setCheck( true ) ;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
else if( this.props.type=='number' ) {
|
|
521
|
-
return this.setNumValue( v, -2 );
|
|
522
|
-
}
|
|
523
|
-
else {
|
|
524
|
-
this.setValue(v);
|
|
525
|
-
}
|
|
526
|
-
},
|
|
527
|
-
isValid: ( ) => { return this.isValid(); }
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
//@ts-ignore
|
|
531
|
-
return i as T;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
return super.queryInterface( name );
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
1
|
+
/**
|
|
2
|
+
* ___ ___ __
|
|
3
|
+
* \ \/ / / _
|
|
4
|
+
* \ / /_| |_
|
|
5
|
+
* / \____ _|
|
|
6
|
+
* /__/\__\ |_|
|
|
7
|
+
*
|
|
8
|
+
* @file input.ts
|
|
9
|
+
* @author Etienne Cochard
|
|
10
|
+
*
|
|
11
|
+
* @copyright (c) 2024 R-libre ingenierie
|
|
12
|
+
*
|
|
13
|
+
* Use of this source code is governed by an MIT-style license
|
|
14
|
+
* that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
|
|
15
|
+
**/
|
|
16
|
+
|
|
17
|
+
import { EventCallback } from '../../core/core_events';
|
|
18
|
+
import { Component, ComponentEvent, componentFromDOM, ComponentProps, EvChange, EvFocus } from '../../core/component';
|
|
19
|
+
import { class_ns, formatIntlDate, IComponentInterface, IFormElement, isString } from '../../core/core_tools';
|
|
20
|
+
|
|
21
|
+
import "./input.module.scss"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
function getRadioOwner( el: Element ) {
|
|
25
|
+
|
|
26
|
+
while( el!=document.body ) {
|
|
27
|
+
const comp = componentFromDOM(el);
|
|
28
|
+
const ifx = comp.queryInterface( "tab-handler");
|
|
29
|
+
if( ifx ) {
|
|
30
|
+
return el;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
el = el.parentElement;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return document;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Base properties for all input types.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
export interface BaseProps extends ComponentProps {
|
|
45
|
+
/**
|
|
46
|
+
* Input field name.
|
|
47
|
+
* required if you want to use form getValues/setValues
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
name?: string;
|
|
51
|
+
/** Automatically focus the input on page load. */
|
|
52
|
+
autofocus?: boolean;
|
|
53
|
+
/** Marks the input as required. */
|
|
54
|
+
required?: boolean;
|
|
55
|
+
/** Makes the input read-only. */
|
|
56
|
+
readonly?: boolean;
|
|
57
|
+
/** Placeholder text displayed when empty. */
|
|
58
|
+
placeholder?: string;
|
|
59
|
+
/** Fired when the input receives/loses focus. */
|
|
60
|
+
focus?: EventCallback<EvFocus>;
|
|
61
|
+
/** Fired when the input value changes. */
|
|
62
|
+
change?: EventCallback<EvChange>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Checkbox-specific input properties.
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
interface CheckboxProps extends BaseProps {
|
|
70
|
+
type: "checkbox";
|
|
71
|
+
/** Checkbox value (submitted when checked). */
|
|
72
|
+
value?: boolean | number | string;
|
|
73
|
+
/** Initial checked state. */
|
|
74
|
+
checked?: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Radio button-specific input properties.
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
interface RadioProps extends BaseProps {
|
|
82
|
+
type: "radio";
|
|
83
|
+
/** Radio value (submitted when selected). */
|
|
84
|
+
value?: boolean | number | string;
|
|
85
|
+
/** Initial checked state. */
|
|
86
|
+
checked?: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Range slider input properties.
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
export interface RangeProps extends BaseProps {
|
|
94
|
+
type: "range";
|
|
95
|
+
/** Current slider value. */
|
|
96
|
+
value?: number;
|
|
97
|
+
/** Minimum allowed value. */
|
|
98
|
+
min: number;
|
|
99
|
+
/** Maximum allowed value. */
|
|
100
|
+
max: number;
|
|
101
|
+
/** Step increment. */
|
|
102
|
+
step?: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* File upload input properties.
|
|
107
|
+
*/
|
|
108
|
+
export interface FileProps extends BaseProps {
|
|
109
|
+
type: "file";
|
|
110
|
+
/** Allowed file types (e.g., `"image/*"` or `[".pdf", ".doc"]`). */
|
|
111
|
+
accept: string | string[];
|
|
112
|
+
value?: never;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Date picker input properties.
|
|
117
|
+
*/
|
|
118
|
+
export interface DateProps extends BaseProps {
|
|
119
|
+
type: "date";
|
|
120
|
+
/** Current date value (Date object or ISO string). */
|
|
121
|
+
value?: Date | string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Time picker input properties.
|
|
126
|
+
*/
|
|
127
|
+
|
|
128
|
+
export interface TimeProps extends BaseProps {
|
|
129
|
+
type: "time";
|
|
130
|
+
readonly?: boolean;
|
|
131
|
+
required?: boolean;
|
|
132
|
+
/** Current time value (e.g., `"12:30"`). */
|
|
133
|
+
value?: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Numeric input properties.
|
|
138
|
+
*/
|
|
139
|
+
|
|
140
|
+
export interface NumberProps extends BaseProps {
|
|
141
|
+
type: "number";
|
|
142
|
+
readonly?: boolean;
|
|
143
|
+
required?: boolean;
|
|
144
|
+
/** Current numeric value. */
|
|
145
|
+
value?: number | string;
|
|
146
|
+
/** Minimum allowed value. */
|
|
147
|
+
min?: number;
|
|
148
|
+
/** Maximum allowed value. */
|
|
149
|
+
max?: number;
|
|
150
|
+
/** Step increment. */
|
|
151
|
+
step?: number;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Text/email/password input properties.
|
|
156
|
+
*/
|
|
157
|
+
|
|
158
|
+
export interface TextInputProps extends BaseProps {
|
|
159
|
+
type?: "text" | "email" | "password";
|
|
160
|
+
readonly?: boolean;
|
|
161
|
+
required?: boolean;
|
|
162
|
+
/** Regex pattern for validation. */
|
|
163
|
+
pattern?: string;
|
|
164
|
+
/** Input value. */
|
|
165
|
+
value?: string | number;
|
|
166
|
+
/** Enables/disables spellcheck. */
|
|
167
|
+
spellcheck?: boolean;
|
|
168
|
+
/** Minimum input length. */
|
|
169
|
+
minlength?: number;
|
|
170
|
+
/** Maximum input length. */
|
|
171
|
+
maxlength?: number;
|
|
172
|
+
trim?: boolean;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
export type InputProps = TextInputProps | CheckboxProps | RadioProps | RangeProps | DateProps | NumberProps | FileProps | TimeProps;
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
interface InputEvents extends ComponentEvent {
|
|
180
|
+
focus: EvFocus;
|
|
181
|
+
change: EvChange;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Customizable input component supporting multiple types (text, number, date, etc.).
|
|
187
|
+
* Auto-generates CSS class: `x4input`.
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```ts
|
|
191
|
+
* // Text input
|
|
192
|
+
* const nameInput = new Input({ type: "text", placeholder: "Enter name" });
|
|
193
|
+
*
|
|
194
|
+
* // Checkbox
|
|
195
|
+
* const agreeCheckbox = new Input({
|
|
196
|
+
* type: "checkbox",
|
|
197
|
+
* checked: true,
|
|
198
|
+
* change: (e) => console.log("Checked:", e.value)
|
|
199
|
+
* });
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
|
|
203
|
+
@class_ns( "x4" )
|
|
204
|
+
export class Input extends Component<InputProps,InputEvents> {
|
|
205
|
+
constructor( props: InputProps ) {
|
|
206
|
+
super( { tag: "input", ...props } );
|
|
207
|
+
|
|
208
|
+
this.mapPropEvents( props, "focus", "change" );
|
|
209
|
+
|
|
210
|
+
this.setAttribute( "type", props.type ?? "text" );
|
|
211
|
+
this.setAttribute( "name", props.name );
|
|
212
|
+
|
|
213
|
+
if( props.autofocus===true ) {
|
|
214
|
+
this.setAttribute( "autofocus", true );
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
switch( props.type ) {
|
|
218
|
+
case "checkbox":
|
|
219
|
+
case "radio": {
|
|
220
|
+
const ck = this.dom as HTMLInputElement;
|
|
221
|
+
ck.checked = props.checked;
|
|
222
|
+
ck.value = props.value === undefined ? "" : props.value+"";
|
|
223
|
+
//this.setAttribute( "checked", props.checked );
|
|
224
|
+
//this.setAttribute( "value", props.value );
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
case "range": {
|
|
229
|
+
this.setAttribute( "min", props.min );
|
|
230
|
+
this.setAttribute( "max", props.max );
|
|
231
|
+
this.setAttribute( "step", props.step );
|
|
232
|
+
this.setAttribute( "value", props.value );
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
case "number": {
|
|
237
|
+
const p = this.props as NumberProps;
|
|
238
|
+
|
|
239
|
+
this.setAttribute( "required", p.required );
|
|
240
|
+
this.setAttribute( "readonly", p.readonly );
|
|
241
|
+
this.setAttribute( "min", p.min );
|
|
242
|
+
this.setAttribute( "max", p.max );
|
|
243
|
+
this.setAttribute( "step", p.step );
|
|
244
|
+
//this.setAttribute( "value", p.value+'' );
|
|
245
|
+
this.setNumValue( isString(p.value) ? parseFloat(p.value) : p.value, -2 );
|
|
246
|
+
|
|
247
|
+
this.addDOMEvent( "wheel", ( e: WheelEvent ) => {
|
|
248
|
+
if( this.hasFocus() ) { // only if has focus
|
|
249
|
+
e.preventDefault( );
|
|
250
|
+
let v = this.getNumValue();
|
|
251
|
+
const delta = e.deltaY < 0 ? 1 : -1;
|
|
252
|
+
v += (p.step ? p.step : 1) * delta;
|
|
253
|
+
this.setNumValue( v, -2 );
|
|
254
|
+
|
|
255
|
+
this.dom.dispatchEvent(new Event('input'));
|
|
256
|
+
}
|
|
257
|
+
}, );
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
case "date": {
|
|
262
|
+
this.setAttribute( "required", props.required );
|
|
263
|
+
|
|
264
|
+
let v = props.value;
|
|
265
|
+
if( v instanceof Date ) {
|
|
266
|
+
this.setAttribute( "value", formatIntlDate( v, "Y-M-D" ) );
|
|
267
|
+
}
|
|
268
|
+
else if( props.value!==null && props.value!==undefined ) {
|
|
269
|
+
this.setAttribute( "value", v );
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
case "file": {
|
|
276
|
+
let v: string;
|
|
277
|
+
if( Array.isArray(props.accept) ) {
|
|
278
|
+
v = props.accept.join("," );
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
v = props.accept;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
this.setAttribute( "accept", v );
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
case "time": {
|
|
289
|
+
this.setAttribute( "required", props.required );
|
|
290
|
+
if( props.value!==null && props.value!==undefined ) {
|
|
291
|
+
this.setAttribute( "value", props.value );
|
|
292
|
+
}
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
default: {
|
|
297
|
+
this.setAttribute( "required", props.required );
|
|
298
|
+
this.setAttribute( "readonly", props.readonly );
|
|
299
|
+
|
|
300
|
+
if( props.value!==null && props.value!==undefined ) {
|
|
301
|
+
this.setAttribute( "value", props.value );
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if( props.pattern!==null && props.pattern!==undefined ) {
|
|
305
|
+
this.setAttribute( "pattern", props.pattern );
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if( props.placeholder!==null && props.placeholder!==undefined ) {
|
|
309
|
+
this.setAttribute( "placeholder", props.placeholder );
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if( props.spellcheck===false ) {
|
|
313
|
+
this.setAttribute( "spellcheck", false );
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if( props.minlength!==undefined ) {
|
|
317
|
+
this.setAttribute( "minlength", props.minlength );
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if( props.maxlength!==undefined ) {
|
|
321
|
+
this.setAttribute( "maxlength", props.maxlength );
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
this.addDOMEvent( "blur", ( e ) => { this.on_focus(e,true);} );
|
|
329
|
+
this.addDOMEvent( "focus", ( e ) => { this.on_focus(e,false);} );
|
|
330
|
+
this.addDOMEvent( "input", ( e ) => { this.on_change(e as InputEvent); });
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
*
|
|
335
|
+
*/
|
|
336
|
+
|
|
337
|
+
private on_focus( ev: FocusEvent, focus_out: boolean ) {
|
|
338
|
+
const event: EvFocus = { focus_out }
|
|
339
|
+
this.fire( "focus", event );
|
|
340
|
+
|
|
341
|
+
if( event.defaultPrevented ) {
|
|
342
|
+
ev.preventDefault( );
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
*
|
|
348
|
+
*/
|
|
349
|
+
|
|
350
|
+
private on_change( ev: InputEvent ) {
|
|
351
|
+
|
|
352
|
+
const event: EvChange = { value: this.getValue() };
|
|
353
|
+
this.fire( "change", event );
|
|
354
|
+
|
|
355
|
+
if( event.defaultPrevented ) {
|
|
356
|
+
ev.preventDefault( );
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Gets the current input value as a string. */
|
|
361
|
+
|
|
362
|
+
public getValue( ) {
|
|
363
|
+
let v = (this.dom as HTMLInputElement).value;
|
|
364
|
+
if( (this.props as any).trim!==false ) {
|
|
365
|
+
v = v.trim( );
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return v;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Sets the input value.
|
|
373
|
+
* @param value - New value (converted to string).
|
|
374
|
+
*/
|
|
375
|
+
|
|
376
|
+
public setValue( value: string ) {
|
|
377
|
+
(this.dom as HTMLInputElement).value = value+"";
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Gets the numeric value (for `type="number"` or `type="range"`).
|
|
382
|
+
* @param defNan - Default value if parsing fails (default: `NaN`).
|
|
383
|
+
* @returns Parsed number or `defNan`.
|
|
384
|
+
*/
|
|
385
|
+
|
|
386
|
+
public getNumValue( defNan?: number ) {
|
|
387
|
+
const v = parseFloat( this.getValue() );
|
|
388
|
+
if( isNaN(v) && defNan!==undefined ) {
|
|
389
|
+
return defNan;
|
|
390
|
+
}
|
|
391
|
+
return v;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Sets a numeric value with optional decimal precision.
|
|
396
|
+
* @param value - Numeric value to set.
|
|
397
|
+
* @param ndec - Decimal places:
|
|
398
|
+
* `-1` = auto,
|
|
399
|
+
* `-2` = use `step` prop,
|
|
400
|
+
* `≥0` = fixed decimals.
|
|
401
|
+
*/
|
|
402
|
+
|
|
403
|
+
public setNumValue( value: number, ndec = -1 ) {
|
|
404
|
+
|
|
405
|
+
if( ndec==-2 && this.props.type=='number' ) {
|
|
406
|
+
const p = this.props as NumberProps;
|
|
407
|
+
|
|
408
|
+
if( p.step<1 ) {
|
|
409
|
+
let ndec = -Math.floor(Math.log10(p.step ?? 1) );
|
|
410
|
+
return this.setValue( value.toFixed(ndec) );
|
|
411
|
+
}
|
|
412
|
+
else if( p.step>1 ) {
|
|
413
|
+
return this.setValue( value.toFixed() );
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
else if( ndec>=0 ) {
|
|
417
|
+
return this.setValue( value.toFixed(ndec) );
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
this.setValue( value+"" );
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** Gets the checked state (for checkboxes/radio buttons). */
|
|
424
|
+
public getCheck() {
|
|
425
|
+
const d = this.dom as HTMLInputElement;
|
|
426
|
+
return d.checked;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** Sets the checked state (for checkboxes/radio buttons). */
|
|
430
|
+
|
|
431
|
+
public setCheck(ck: boolean) {
|
|
432
|
+
const d = this.dom as HTMLInputElement;
|
|
433
|
+
d.checked = ck;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** Toggles read-only mode. */
|
|
437
|
+
|
|
438
|
+
public setReadOnly( ro: boolean ) {
|
|
439
|
+
const d = this.dom as HTMLInputElement;
|
|
440
|
+
d.readOnly = ro;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** Selects all text in the input. */
|
|
444
|
+
|
|
445
|
+
public selectAll( ) {
|
|
446
|
+
const d = this.dom as HTMLInputElement;
|
|
447
|
+
d.select();
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Selects a text range.
|
|
452
|
+
* @param start - Start position
|
|
453
|
+
* @param length - Length of selection
|
|
454
|
+
*/
|
|
455
|
+
|
|
456
|
+
public select( start: number, length: number = 9999 ) : void {
|
|
457
|
+
const d = this.dom as HTMLInputElement;
|
|
458
|
+
d.setSelectionRange( start, start+length );
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Gets the current text selection.
|
|
463
|
+
* @returns Object with `start` and `length` properties.
|
|
464
|
+
*/
|
|
465
|
+
public getSelection( ) {
|
|
466
|
+
const d = this.dom as HTMLInputElement;
|
|
467
|
+
|
|
468
|
+
return {
|
|
469
|
+
start: d.selectionStart,
|
|
470
|
+
length: d.selectionEnd - d.selectionStart,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** Validates the input (checks `required` constraint). */
|
|
475
|
+
|
|
476
|
+
public isValid( ) {
|
|
477
|
+
|
|
478
|
+
if( (this.props as any).required ) {
|
|
479
|
+
const v = this.getValue( );
|
|
480
|
+
if( v==="" ) {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
*
|
|
490
|
+
*/
|
|
491
|
+
|
|
492
|
+
override queryInterface<T extends IComponentInterface>( name: string ): T {
|
|
493
|
+
if( name=="form-element" ) {
|
|
494
|
+
const i: IFormElement = {
|
|
495
|
+
getRawValue: ( ): any => {
|
|
496
|
+
if( this.props.type=='checkbox' ) {
|
|
497
|
+
return this.getCheck( );
|
|
498
|
+
}
|
|
499
|
+
else if( this.props.type=='radio' ) {
|
|
500
|
+
const owner = getRadioOwner( this.dom );
|
|
501
|
+
const checked = owner.querySelector( `input[name="${this.props.name}"]:checked` )
|
|
502
|
+
return checked ? (checked as HTMLInputElement).value : undefined;
|
|
503
|
+
}
|
|
504
|
+
else if( this.props.type=='number' ) {
|
|
505
|
+
return this.getNumValue( 0 );
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
return this.getValue();
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
setRawValue: ( v: any ) => {
|
|
512
|
+
if( this.props.type=='checkbox' ) {
|
|
513
|
+
this.setCheck( !!v );
|
|
514
|
+
}
|
|
515
|
+
else if( this.props.type=='radio' ) {
|
|
516
|
+
if( this.props.value==v ) {
|
|
517
|
+
this.setCheck( true ) ;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
else if( this.props.type=='number' ) {
|
|
521
|
+
return this.setNumValue( v, -2 );
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
this.setValue(v);
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
isValid: ( ) => { return this.isValid(); }
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
//@ts-ignore
|
|
531
|
+
return i as T;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return super.queryInterface( name );
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|