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.
Files changed (134) hide show
  1. package/README.md +21 -21
  2. package/package.json +39 -26
  3. package/src/components/base.scss +25 -89
  4. package/src/components/boxes/boxes.module.scss +54 -54
  5. package/src/components/boxes/boxes.ts +513 -513
  6. package/src/components/breadcrumb/breadcrumb.scss +56 -56
  7. package/src/components/breadcrumb/breadcrumb.ts +93 -93
  8. package/src/components/btngroup/btngroup.module.scss +40 -40
  9. package/src/components/btngroup/btngroup.ts +152 -152
  10. package/src/components/button/button.module.scss +172 -172
  11. package/src/components/button/button.ts +232 -232
  12. package/src/components/calendar/calendar.module.scss +162 -162
  13. package/src/components/calendar/calendar.ts +326 -326
  14. package/src/components/canvas/canvas.module.scss +24 -24
  15. package/src/components/canvas/canvas.ts +195 -195
  16. package/src/components/canvas/canvas_ex.ts +275 -275
  17. package/src/components/checkbox/check.svg +3 -3
  18. package/src/components/checkbox/checkbox.module.scss +141 -141
  19. package/src/components/checkbox/checkbox.ts +139 -139
  20. package/src/components/colorinput/colorinput.module.scss +64 -64
  21. package/src/components/colorinput/colorinput.ts +90 -90
  22. package/src/components/colorpicker/colorpicker.module.scss +132 -132
  23. package/src/components/colorpicker/colorpicker.ts +481 -481
  24. package/src/components/combobox/combobox.module.scss +145 -145
  25. package/src/components/combobox/combobox.ts +282 -282
  26. package/src/components/combobox/updown.svg +3 -3
  27. package/src/components/components.ts +45 -44
  28. package/src/components/dialog/dialog.module.scss +103 -105
  29. package/src/components/dialog/dialog.ts +233 -233
  30. package/src/components/filedrop/filedrop.module.scss +69 -69
  31. package/src/components/filedrop/filedrop.ts +130 -130
  32. package/src/components/form/form.module.scss +38 -38
  33. package/src/components/form/form.ts +172 -172
  34. package/src/components/gridview/gridview.module.scss +323 -337
  35. package/src/components/gridview/gridview.ts +1276 -1316
  36. package/src/components/header/header.module.scss +40 -40
  37. package/src/components/header/header.ts +141 -141
  38. package/src/components/icon/icon.module.scss +32 -32
  39. package/src/components/icon/icon.ts +165 -165
  40. package/src/components/image/image.module.scss +27 -27
  41. package/src/components/image/image.ts +168 -168
  42. package/src/components/input/input.module.scss +74 -74
  43. package/src/components/input/input.ts +537 -537
  44. package/src/components/keyboard/keyboard.module.scss +136 -136
  45. package/src/components/keyboard/keyboard.ts +549 -549
  46. package/src/components/label/label.module.scss +90 -91
  47. package/src/components/label/label.ts +101 -101
  48. package/src/components/link/link.module.scss +44 -44
  49. package/src/components/link/link.ts +87 -87
  50. package/src/components/listbox/listbox.module.scss +179 -179
  51. package/src/components/listbox/listbox.ts +596 -596
  52. package/src/components/menu/menu.module.scss +128 -128
  53. package/src/components/menu/menu.ts +174 -174
  54. package/src/components/messages/messages.module.scss +92 -146
  55. package/src/components/messages/messages.ts +237 -303
  56. package/src/components/normalize.scss +391 -391
  57. package/src/components/notification/notification.module.scss +83 -83
  58. package/src/components/notification/notification.ts +107 -107
  59. package/src/components/panel/panel.module.scss +66 -71
  60. package/src/components/panel/panel.ts +57 -57
  61. package/src/components/popup/popup.module.scss +51 -51
  62. package/src/components/popup/popup.ts +457 -457
  63. package/src/components/progress/progress.module.scss +56 -56
  64. package/src/components/progress/progress.ts +43 -43
  65. package/src/components/propgrid/progrid.module.scss +111 -111
  66. package/src/components/propgrid/propgrid.ts +300 -300
  67. package/src/components/propgrid/updown.svg +3 -3
  68. package/src/components/radio/radio.module.scss +163 -163
  69. package/src/components/radio/radio.svg +3 -3
  70. package/src/components/radio/radio.ts +141 -141
  71. package/src/components/rating/rating.module.scss +22 -22
  72. package/src/components/rating/rating.ts +131 -131
  73. package/src/components/select/select.module.scss +8 -8
  74. package/src/components/select/select.ts +134 -134
  75. package/src/components/shared.scss +141 -71
  76. package/src/components/sizers/sizer.module.scss +90 -112
  77. package/src/components/sizers/sizer.ts +131 -155
  78. package/src/components/slider/slider.module.scss +117 -117
  79. package/src/components/slider/slider.ts +197 -197
  80. package/src/components/spreadsheet/spreadsheet.module.scss +307 -307
  81. package/src/components/spreadsheet/spreadsheet.ts +1223 -1223
  82. package/src/components/switch/switch.module.scss +126 -126
  83. package/src/components/switch/switch.ts +61 -61
  84. package/src/components/tabs/tabs.module.scss +46 -67
  85. package/src/components/tabs/tabs.ts +229 -234
  86. package/src/components/textarea/textarea.module.scss +63 -63
  87. package/src/components/textarea/textarea.ts +131 -131
  88. package/src/components/textedit/textedit.module.scss +115 -115
  89. package/src/components/textedit/textedit.ts +122 -122
  90. package/src/components/themes.scss +90 -90
  91. package/src/components/tickline/tickline.module.scss +25 -25
  92. package/src/components/tickline/tickline.ts +81 -81
  93. package/src/components/tooltips/tooltips.scss +71 -71
  94. package/src/components/tooltips/tooltips.ts +120 -120
  95. package/src/components/treeview/treeview.module.scss +192 -192
  96. package/src/components/treeview/treeview.ts +484 -484
  97. package/src/components/viewport/viewport.module.scss +31 -31
  98. package/src/components/viewport/viewport.ts +41 -41
  99. package/src/core/component.ts +1299 -1299
  100. package/src/core/core_application.ts +361 -361
  101. package/src/core/core_colors.ts +512 -512
  102. package/src/core/core_data.ts +1297 -1310
  103. package/src/core/core_dom.ts +481 -481
  104. package/src/core/core_dragdrop.ts +225 -225
  105. package/src/core/core_element.ts +221 -221
  106. package/src/core/core_events.ts +214 -214
  107. package/src/core/core_i18n.ts +395 -395
  108. package/src/core/core_pdf.ts +454 -454
  109. package/src/core/core_react.ts +78 -78
  110. package/src/core/core_router.ts +296 -296
  111. package/src/core/core_state.ts +62 -62
  112. package/src/core/core_styles.ts +213 -213
  113. package/src/core/core_svg.ts +1042 -1042
  114. package/src/core/core_tools.ts +996 -996
  115. package/src/types/scss.d.ts +4 -4
  116. package/src/types/x4react.d.ts +8 -8
  117. package/src/x4.scss +19 -19
  118. package/src/x4.ts +36 -36
  119. package/src/x4tsx.d.ts +26 -26
  120. package/.vscode/launch.json +0 -14
  121. package/.vscode/settings.json +0 -2
  122. package/demo/assets/house-light.svg +0 -1
  123. package/demo/assets/radio.svg +0 -4
  124. package/demo/index.html +0 -12
  125. package/demo/main.scss +0 -23
  126. package/demo/main.ts +0 -324
  127. package/demo/package.json +0 -26
  128. package/demo/scss.d.ts +0 -4
  129. package/demo/svg.d.ts +0 -1
  130. package/demo/tsconfig.json +0 -14
  131. package/src/components/gridview/folder-open.svg +0 -1
  132. package/src/components/messages/spinner.svg +0 -1
  133. package/src/x4.d.ts +0 -10
  134. 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
+