x4js 2.0.26 → 2.0.30

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 (73) hide show
  1. package/.vscode/launch.json +14 -0
  2. package/.vscode/settings.json +2 -0
  3. package/ai-comments.txt +97 -0
  4. package/demo/assets/house-light.svg +1 -0
  5. package/demo/assets/radio.svg +4 -0
  6. package/demo/index.html +12 -0
  7. package/demo/main.scss +23 -0
  8. package/demo/main.ts +324 -0
  9. package/demo/package.json +26 -0
  10. package/demo/scss.d.ts +4 -0
  11. package/demo/svg.d.ts +1 -0
  12. package/demo/tsconfig.json +14 -0
  13. package/lib/types/x4js.d.ts +0 -2374
  14. package/package.json +23 -47
  15. package/prepack.mjs +3 -0
  16. package/scripts/prepack.mjs +342 -0
  17. package/src/colors.scss +246 -0
  18. package/src/components/boxes/boxes.module.scss +1 -1
  19. package/src/components/boxes/boxes.ts +139 -28
  20. package/src/components/button/button.ts +80 -33
  21. package/src/components/combobox/combobox.ts +1 -1
  22. package/src/components/dialog/dialog.ts +4 -0
  23. package/src/components/gauge/gauge.module.scss +3 -0
  24. package/src/components/gauge/gauge.ts +1 -1
  25. package/src/components/gridview/gridview.ts +106 -8
  26. package/src/components/icon/icon.ts +42 -14
  27. package/src/components/input/input.ts +155 -76
  28. package/src/components/keyboard/keyboard.module.scss +1 -1
  29. package/src/components/keyboard/keyboard.ts +31 -9
  30. package/src/components/label/label.module.scss +9 -0
  31. package/src/components/label/label.ts +10 -6
  32. package/src/components/link/link.module.scss +44 -0
  33. package/src/components/link/link.ts +7 -1
  34. package/src/components/listbox/listbox.module.scss +18 -4
  35. package/src/components/listbox/listbox.ts +34 -15
  36. package/src/components/menu/menu.module.scss +14 -2
  37. package/src/components/menu/menu.ts +1 -1
  38. package/src/components/messages/messages.ts +13 -5
  39. package/src/components/panel/panel.module.scss +7 -0
  40. package/src/components/popup/popup.ts +14 -10
  41. package/src/components/propgrid/propgrid.ts +13 -3
  42. package/src/components/shared.scss +4 -0
  43. package/src/components/spreadsheet/spreadsheet.module.scss +308 -0
  44. package/src/components/spreadsheet/spreadsheet.ts +1223 -0
  45. package/src/components/tabs/tabs.module.scss +1 -0
  46. package/src/components/textarea/textarea.ts +8 -2
  47. package/src/components/textedit/textedit.ts +7 -0
  48. package/src/components/themes.scss +2 -0
  49. package/src/components/tooltips/tooltips.ts +15 -3
  50. package/src/core/component.ts +358 -162
  51. package/src/core/core_application.ts +129 -32
  52. package/src/core/core_colors.ts +382 -119
  53. package/src/core/core_data.ts +73 -86
  54. package/src/core/core_dom.ts +10 -0
  55. package/src/core/core_dragdrop.ts +32 -7
  56. package/src/core/core_element.ts +111 -4
  57. package/src/core/core_events.ts +48 -11
  58. package/src/core/core_i18n.ts +2 -0
  59. package/src/core/core_pdf.ts +454 -0
  60. package/src/core/core_router.ts +64 -5
  61. package/src/core/core_state.ts +1 -0
  62. package/src/core/core_styles.ts +11 -12
  63. package/src/core/core_svg.ts +348 -51
  64. package/src/core/core_tools.ts +105 -17
  65. package/src/x4.ts +1 -0
  66. package/src/x4tsx.d.ts +2 -1
  67. package/tsconfig.json +11 -0
  68. package/lib/README.txt +0 -20
  69. package/lib/cjs/x4.css +0 -1
  70. package/lib/cjs/x4.js +0 -2
  71. package/lib/esm/x4.css +0 -1
  72. package/lib/esm/x4.mjs +0 -2
  73. package/lib/styles/x4.css +0 -1
@@ -16,7 +16,7 @@
16
16
 
17
17
 
18
18
  import { Component, ComponentContent, ComponentEvents, ComponentProps, EvClick, EvContextMenu, EvDblClick, EvSelectionChange, componentFromDOM } from '../../core/component';
19
- import { class_ns, isNumber, isString, setWaitCursor } from '../../core/core_tools';
19
+ import { class_ns, isNumber, isString, setWaitCursor, UnsafeHtml } from '../../core/core_tools';
20
20
  import { DataModel, DataStore, DataView, DataRecord, EvViewChange } from '../../core/core_data';
21
21
  import { EventCallback } from '../../core/core_events';
22
22
  import { kbNav } from '../../core/core_tools';
@@ -34,7 +34,7 @@ import "./gridview.module.scss"
34
34
  export type CellRenderer = (rec: DataRecord) => Component;
35
35
  export type CellClassifier = (data: any, rec: DataRecord, col: string ) => string; // return the cell computed class
36
36
 
37
- type ColType = "number" | "money" | "checkbox" | "date" | "string" | "image" | "percent" | "icon";
37
+ type ColType = "number" | "money" | "checkbox" | "date" | "string" | "image" | "percent" | "icon" | "date-time";
38
38
 
39
39
  const SCROLL_LIMIT = 200;
40
40
 
@@ -45,7 +45,7 @@ const SCROLL_LIMIT = 200;
45
45
 
46
46
  export interface GridColumn {
47
47
  id: any;
48
- title: string;
48
+ title: string | UnsafeHtml;
49
49
  width: number;
50
50
  fixed?: boolean;
51
51
  flex?: number;
@@ -121,9 +121,12 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
121
121
  private _end: number;
122
122
 
123
123
  private _selection: Set<number>;
124
- private _num_fmt = new Intl.NumberFormat('fr-FR');
125
- private _mny_fmt = new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' });
126
- private _dte_fmt = new Intl.DateTimeFormat('fr-FR', {});
124
+
125
+ // TODO: that
126
+ private _num_fmt = new Intl.NumberFormat('fr-FR');
127
+ private _mny_fmt = new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' });
128
+ private _dte_fmt = new Intl.DateTimeFormat('fr-FR', {});
129
+ private _dtetme_fmt = new Intl.DateTimeFormat('fr-FR', {day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:"2-digit"});
127
130
 
128
131
  private _has_fixed: boolean;
129
132
  private _has_footer: boolean;
@@ -345,6 +348,68 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
345
348
  }
346
349
  }
347
350
 
351
+ setColumns( columns: GridColumn[] ) {
352
+ this._columns = columns.map(x => x);
353
+ if( this.dom ) {
354
+
355
+ this._updateFlexs( );
356
+
357
+ // Rebuild headers
358
+ if (this._fheader) {
359
+ /*
360
+ const newFixedHeader = this._buildColHeader(true);
361
+ this._fheader.setContent(newFixedHeader.getChildren());
362
+ // On doit remplacer _fheader dans le DOM ou mettre à jour son contenu
363
+ // La méthode _buildColHeader retourne une Box.
364
+ // Ici, je vais simplifier en vidant et remplissant si possible,
365
+ // mais Box n'a pas forcément de méthode simple pour remplacer tout le DOM interne sans casser les events.
366
+ // Le plus simple est de remplacer les composants header dans le DOM global du gridview.
367
+
368
+ // Approche : on recrée les headers et on remplace les anciens
369
+ this._fheader.destroy();
370
+ this._hheader.destroy();
371
+
372
+ this._fheader = this._buildColHeader(true);
373
+ this._hheader = this._buildColHeader(false);
374
+
375
+ // Il faut réinsérer ces headers au bon endroit dans le DOM du Gridview.
376
+ // _init fait:
377
+ // this.setContent([
378
+ // this._fheader,
379
+ // this._hheader,
380
+ // this._vheader,
381
+ // this._viewport,
382
+ // this._ffooter,
383
+ // this._footer,
384
+ // ]);
385
+
386
+ // Donc on peut reconstruire le content complet
387
+ const content = [
388
+ this._fheader,
389
+ this._hheader,
390
+ this._vheader,
391
+ this._viewport
392
+ ];
393
+
394
+ if (this._has_footer) {
395
+ this._ffooter.destroy();
396
+ this._footer.destroy();
397
+ this._ffooter = this._buildColFooter(true);
398
+ this._footer = this._buildColFooter(false);
399
+ content.push(this._ffooter);
400
+ content.push(this._footer);
401
+ }
402
+
403
+ this.setContent(content);
404
+ */
405
+ console.assert( false, "TODO" );
406
+ }
407
+
408
+ this._computeFullSize( );
409
+ this._update( true );
410
+ }
411
+ }
412
+
348
413
  getView( ): DataView {
349
414
  return this._dataview;
350
415
  }
@@ -417,7 +482,7 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
417
482
  attrs: { "data-col": col },
418
483
  style: { width: cdata.width ? cdata.width + "px" : undefined },
419
484
  content: [
420
- new SimpleText({ text: cdata.title, align: cdata.header_align ?? "left" }),
485
+ new SimpleText({ cls: 'title', text: cdata.title, align: cdata.header_align ?? "left" }),
421
486
  new Component({ cls: "sorter" }),
422
487
  sizer
423
488
  ]
@@ -642,10 +707,23 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
642
707
  }
643
708
 
644
709
  case "date": {
710
+ if( isString(data) ) {
711
+ data = new Date( data );
712
+ }
713
+
645
714
  data = this._dte_fmt.format(data as Date);
646
715
  break;
647
716
  }
648
717
 
718
+ case "date-time": {
719
+ if( isString(data) ) {
720
+ data = new Date( data );
721
+ }
722
+
723
+ data = this._dtetme_fmt.format(data as Date);
724
+ break;
725
+ }
726
+
649
727
  default: {
650
728
  data = data + "";
651
729
  break;
@@ -1111,6 +1189,10 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
1111
1189
  *
1112
1190
  */
1113
1191
 
1192
+ clearSelection( ) {
1193
+ this._clearSelection( );
1194
+ }
1195
+
1114
1196
  private _clearSelection() {
1115
1197
  for (const ref of this._selection.keys()) {
1116
1198
  const els = this.queryAll(`.row[data-row="${ref}"]`)
@@ -1168,10 +1250,26 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
1168
1250
  *
1169
1251
  */
1170
1252
 
1171
- selectItem( id: any ) {
1253
+ selectItem( id: any, ensureVisible = true ) {
1172
1254
  const index = this._dataview.indexOfId( id );
1173
1255
  if( index>=0 ) {
1174
1256
  this._addSelection( index );
1257
+ if( ensureVisible ) {
1258
+ this._scrollToIndex( index );
1259
+ }
1260
+ }
1261
+ }
1262
+
1263
+ /**
1264
+ *
1265
+ */
1266
+
1267
+ setColTitle( col_name: any, title: string ) {
1268
+ const col = this._columns.findIndex( x => x.id==col_name );
1269
+ if( col>=0 ) {
1270
+ this._columns[col].title = title;
1271
+ const el = this._hheader.query<SimpleText>(`[data-col="${col}"] .title` )
1272
+ el.setText( title );
1175
1273
  }
1176
1274
  }
1177
1275
  }
@@ -14,8 +14,8 @@
14
14
  * that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
15
15
  **/
16
16
 
17
- import { class_ns } from '../../core/core_tools';
18
- import { Component, ComponentProps } from '../../core/component';
17
+ import { class_ns } from '../../core/core_tools.ts';
18
+ import { Component, ComponentProps } from '../../core/component.ts';
19
19
 
20
20
  import "./icon.module.scss"
21
21
 
@@ -68,20 +68,44 @@ class SvgLoader {
68
68
  export const svgLoader = new SvgLoader( );
69
69
 
70
70
  /**
71
- *
71
+ * Icon component properties.
72
+ *
73
+ * @property {string} [iconId] - Identifier or path of the icon to display.
72
74
  */
73
75
 
74
76
  export interface IconProps extends ComponentProps {
77
+ /**
78
+ * Identifier for the icon.
79
+ * Can be a URL, a CSS variable (prefixed with `var:`), or inline SVG data.
80
+ * @example
81
+ * // Using a CSS variable
82
+ * { iconId: "var:home" }
83
+ * @example
84
+ * // Using an imported SVG
85
+ * import myicon from "./myicon.svg";
86
+ * { iconId: myicon }
87
+ */
75
88
  iconId?: string;
76
89
  }
77
90
 
78
91
  /**
79
- *
92
+ * A component for rendering icons.
93
+ * Supports inline SVG, external SVG files, and CSS variables for icon paths.
94
+ * The CSS class for this component is automatically generated as `x4icon`.
80
95
  */
81
96
 
82
97
  @class_ns( "x4" )
83
98
  export class Icon extends Component<IconProps> {
84
99
 
100
+ /**
101
+ * Create a new Icon.
102
+ *
103
+ * @param {IconProps} props - Optional initial icon identifier.
104
+ *
105
+ * @example
106
+ * const icon = new Icon({ iconId: "check" });
107
+ */
108
+
85
109
  constructor( props: IconProps ) {
86
110
  super( props );
87
111
 
@@ -89,16 +113,20 @@ export class Icon extends Component<IconProps> {
89
113
  }
90
114
 
91
115
  /**
92
- * change the icon content
93
- * @param iconId if name is starting with var: then we use css variable name a path
94
- * @example
95
- *
96
- * setIcon( "var:home" )
97
- *
98
- * import myicon from "./myicon.svg"
99
- * setIcon( myicon );
100
- *
101
- */
116
+ * Sets or updates the icon content.
117
+ * @param iconId - Identifier for the icon.
118
+ * If it starts with `var:`, the value is treated as a CSS variable name.
119
+ * If it is a data URL (e.g., `data:image/svg+xml,<svg...`), the SVG is rendered directly.
120
+ * If it ends with `.svg`, the file is loaded asynchronously.
121
+ * Otherwise, it is treated as an image URL.
122
+ * @example
123
+ * // Using a CSS variable
124
+ * setIcon("var:home");
125
+ * @example
126
+ * // Using an imported SVG
127
+ * import myicon from "./myicon.svg";
128
+ * setIcon(myicon);
129
+ */
102
130
 
103
131
  setIcon( iconId: string ) {
104
132
  this.clearContent( );
@@ -20,75 +20,156 @@ import { class_ns, formatIntlDate, IComponentInterface, IFormElement, isString }
20
20
 
21
21
  import "./input.module.scss"
22
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
+
23
44
  export interface BaseProps extends ComponentProps {
45
+ /**
46
+ * Input field name.
47
+ * required if you want to use form getValues/setValues
48
+ */
49
+
24
50
  name?: string;
51
+ /** Automatically focus the input on page load. */
25
52
  autofocus?: boolean;
53
+ /** Marks the input as required. */
26
54
  required?: boolean;
55
+ /** Makes the input read-only. */
27
56
  readonly?: boolean;
28
- placeholder?: string;
29
-
30
- focus?: EventCallback<EvFocus>;
31
- change?: EventCallback<EvChange>;
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>;
32
63
  }
33
64
 
65
+ /**
66
+ * Checkbox-specific input properties.
67
+ */
68
+
34
69
  interface CheckboxProps extends BaseProps {
35
70
  type: "checkbox";
71
+ /** Checkbox value (submitted when checked). */
36
72
  value?: boolean | number | string;
73
+ /** Initial checked state. */
37
74
  checked?: boolean;
38
75
  }
39
76
 
77
+ /**
78
+ * Radio button-specific input properties.
79
+ */
80
+
40
81
  interface RadioProps extends BaseProps {
41
82
  type: "radio";
83
+ /** Radio value (submitted when selected). */
42
84
  value?: boolean | number | string;
85
+ /** Initial checked state. */
43
86
  checked?: boolean;
44
87
  }
45
88
 
89
+ /**
90
+ * Range slider input properties.
91
+ */
92
+
46
93
  export interface RangeProps extends BaseProps {
47
94
  type: "range";
95
+ /** Current slider value. */
48
96
  value?: number;
97
+ /** Minimum allowed value. */
49
98
  min: number;
99
+ /** Maximum allowed value. */
50
100
  max: number;
101
+ /** Step increment. */
51
102
  step?: number;
52
103
  }
53
104
 
105
+ /**
106
+ * File upload input properties.
107
+ */
54
108
  export interface FileProps extends BaseProps {
55
109
  type: "file";
110
+ /** Allowed file types (e.g., `"image/*"` or `[".pdf", ".doc"]`). */
56
111
  accept: string | string[];
57
112
  value?: never;
58
113
  }
59
114
 
60
-
115
+ /**
116
+ * Date picker input properties.
117
+ */
61
118
  export interface DateProps extends BaseProps {
62
119
  type: "date";
63
- value?: Date | string;
120
+ /** Current date value (Date object or ISO string). */
121
+ value?: Date | string;
64
122
  }
65
123
 
124
+ /**
125
+ * Time picker input properties.
126
+ */
127
+
66
128
  export interface TimeProps extends BaseProps {
67
129
  type: "time";
68
130
  readonly?: boolean;
69
131
  required?: boolean;
70
- value?: string;
132
+ /** Current time value (e.g., `"12:30"`). */
133
+ value?: string;
71
134
  }
72
135
 
136
+ /**
137
+ * Numeric input properties.
138
+ */
139
+
73
140
  export interface NumberProps extends BaseProps {
74
141
  type: "number";
75
142
  readonly?: boolean;
76
143
  required?: boolean;
77
- value?: number | string;
78
- min?: number;
79
- max?: number;
80
- step?: number;
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;
81
152
  }
82
153
 
154
+ /**
155
+ * Text/email/password input properties.
156
+ */
157
+
83
158
  export interface TextInputProps extends BaseProps {
84
159
  type?: "text" | "email" | "password";
85
160
  readonly?: boolean;
86
161
  required?: boolean;
162
+ /** Regex pattern for validation. */
87
163
  pattern?: string;
164
+ /** Input value. */
88
165
  value?: string | number;
166
+ /** Enables/disables spellcheck. */
89
167
  spellcheck?: boolean;
168
+ /** Minimum input length. */
90
169
  minlength?: number;
170
+ /** Maximum input length. */
91
171
  maxlength?: number;
172
+ trim?: boolean;
92
173
  }
93
174
 
94
175
 
@@ -102,7 +183,21 @@ interface InputEvents extends ComponentEvent {
102
183
 
103
184
 
104
185
  /**
105
- *
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
+ * ```
106
201
  */
107
202
 
108
203
  @class_ns( "x4" )
@@ -262,27 +357,31 @@ export class Input extends Component<InputProps,InputEvents> {
262
357
  }
263
358
  }
264
359
 
265
- /**
266
- * @returns
267
- */
268
-
360
+ /** Gets the current input value as a string. */
361
+
269
362
  public getValue( ) {
270
- return (this.dom as HTMLInputElement).value;
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;
271
369
  }
272
370
 
273
371
  /**
274
- *
275
- * @param value
276
- */
372
+ * Sets the input value.
373
+ * @param value - New value (converted to string).
374
+ */
277
375
 
278
376
  public setValue( value: string ) {
279
377
  (this.dom as HTMLInputElement).value = value+"";
280
378
  }
281
379
 
282
380
  /**
283
- *
284
- * @returns
285
- */
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
+ */
286
385
 
287
386
  public getNumValue( defNan?: number ) {
288
387
  const v = parseFloat( this.getValue() );
@@ -293,11 +392,13 @@ export class Input extends Component<InputProps,InputEvents> {
293
392
  }
294
393
 
295
394
  /**
296
- *
297
- * @param value
298
- * @param ndec number of decimals or -1 for auto, -2 as prop.step
299
- *
300
- */
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
+ */
301
402
 
302
403
  public setNumValue( value: number, ndec = -1 ) {
303
404
 
@@ -319,48 +420,38 @@ export class Input extends Component<InputProps,InputEvents> {
319
420
  this.setValue( value+"" );
320
421
  }
321
422
 
322
- /**
323
- * @return the checked value
324
- */
325
-
326
- public getCheck() {
423
+ /** Gets the checked state (for checkboxes/radio buttons). */
424
+ public getCheck() {
327
425
  const d = this.dom as HTMLInputElement;
328
426
  return d.checked;
329
427
  }
330
428
 
331
- /**
332
- * change the checked value
333
- * @param {boolean} ck new checked value
334
- */
335
-
429
+ /** Sets the checked state (for checkboxes/radio buttons). */
430
+
336
431
  public setCheck(ck: boolean) {
337
432
  const d = this.dom as HTMLInputElement;
338
433
  d.checked = ck;
339
434
  }
340
435
 
341
- /**
342
- *
343
- */
344
-
436
+ /** Toggles read-only mode. */
437
+
345
438
  public setReadOnly( ro: boolean ) {
346
439
  const d = this.dom as HTMLInputElement;
347
440
  d.readOnly = ro;
348
441
  }
349
442
 
350
- /**
351
- * select all the text
352
- */
353
-
443
+ /** Selects all text in the input. */
444
+
354
445
  public selectAll( ) {
355
446
  const d = this.dom as HTMLInputElement;
356
447
  d.select();
357
448
  }
358
449
 
359
450
  /**
360
- * select a part of the text
361
- * @param start
362
- * @param length
363
- */
451
+ * Selects a text range.
452
+ * @param start - Start position
453
+ * @param length - Length of selection
454
+ */
364
455
 
365
456
  public select( start: number, length: number = 9999 ) : void {
366
457
  const d = this.dom as HTMLInputElement;
@@ -368,9 +459,9 @@ export class Input extends Component<InputProps,InputEvents> {
368
459
  }
369
460
 
370
461
  /**
371
- * get the selection as { start, length }
372
- */
373
-
462
+ * Gets the current text selection.
463
+ * @returns Object with `start` and `length` properties.
464
+ */
374
465
  public getSelection( ) {
375
466
  const d = this.dom as HTMLInputElement;
376
467
 
@@ -380,10 +471,8 @@ export class Input extends Component<InputProps,InputEvents> {
380
471
  };
381
472
  }
382
473
 
383
- /**
384
- *
385
- */
386
-
474
+ /** Validates the input (checks `required` constraint). */
475
+
387
476
  public isValid( ) {
388
477
 
389
478
  if( (this.props as any).required ) {
@@ -412,8 +501,12 @@ export class Input extends Component<InputProps,InputEvents> {
412
501
  const checked = owner.querySelector( `input[name="${this.props.name}"]:checked` )
413
502
  return checked ? (checked as HTMLInputElement).value : undefined;
414
503
  }
415
-
416
- return this.getValue();
504
+ else if( this.props.type=='number' ) {
505
+ return this.getNumValue( 0 );
506
+ }
507
+ else {
508
+ return this.getValue();
509
+ }
417
510
  },
418
511
  setRawValue: ( v: any ) => {
419
512
  if( this.props.type=='checkbox' ) {
@@ -424,6 +517,9 @@ export class Input extends Component<InputProps,InputEvents> {
424
517
  this.setCheck( true ) ;
425
518
  }
426
519
  }
520
+ else if( this.props.type=='number' ) {
521
+ return this.setNumValue( v, -2 );
522
+ }
427
523
  else {
428
524
  this.setValue(v);
429
525
  }
@@ -439,20 +535,3 @@ export class Input extends Component<InputProps,InputEvents> {
439
535
  }
440
536
  }
441
537
 
442
-
443
- function getRadioOwner( el: Element ) {
444
-
445
- while( el!=document.body ) {
446
- const comp = componentFromDOM(el);
447
- const ifx = comp.queryInterface( "tab-handler");
448
- if( ifx ) {
449
- return el;
450
- }
451
-
452
- el = el.parentElement;
453
- }
454
-
455
- return document;
456
- }
457
-
458
-
@@ -23,7 +23,7 @@
23
23
  .x4keyboard {
24
24
  z-index: 2000;
25
25
  position: absolute !important;
26
- width: 100vw;
26
+ width: 100%;
27
27
  bottom: 0;
28
28
  justify-content: center;
29
29