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.
- package/.vscode/launch.json +14 -0
- package/.vscode/settings.json +2 -0
- package/ai-comments.txt +97 -0
- package/demo/assets/house-light.svg +1 -0
- package/demo/assets/radio.svg +4 -0
- package/demo/index.html +12 -0
- package/demo/main.scss +23 -0
- package/demo/main.ts +324 -0
- package/demo/package.json +26 -0
- package/demo/scss.d.ts +4 -0
- package/demo/svg.d.ts +1 -0
- package/demo/tsconfig.json +14 -0
- package/lib/types/x4js.d.ts +0 -2374
- package/package.json +23 -47
- package/prepack.mjs +3 -0
- package/scripts/prepack.mjs +342 -0
- package/src/colors.scss +246 -0
- package/src/components/boxes/boxes.module.scss +1 -1
- package/src/components/boxes/boxes.ts +139 -28
- package/src/components/button/button.ts +80 -33
- package/src/components/combobox/combobox.ts +1 -1
- package/src/components/dialog/dialog.ts +4 -0
- package/src/components/gauge/gauge.module.scss +3 -0
- package/src/components/gauge/gauge.ts +1 -1
- package/src/components/gridview/gridview.ts +106 -8
- package/src/components/icon/icon.ts +42 -14
- package/src/components/input/input.ts +155 -76
- package/src/components/keyboard/keyboard.module.scss +1 -1
- package/src/components/keyboard/keyboard.ts +31 -9
- package/src/components/label/label.module.scss +9 -0
- package/src/components/label/label.ts +10 -6
- package/src/components/link/link.module.scss +44 -0
- package/src/components/link/link.ts +7 -1
- package/src/components/listbox/listbox.module.scss +18 -4
- package/src/components/listbox/listbox.ts +34 -15
- package/src/components/menu/menu.module.scss +14 -2
- package/src/components/menu/menu.ts +1 -1
- package/src/components/messages/messages.ts +13 -5
- package/src/components/panel/panel.module.scss +7 -0
- package/src/components/popup/popup.ts +14 -10
- package/src/components/propgrid/propgrid.ts +13 -3
- package/src/components/shared.scss +4 -0
- package/src/components/spreadsheet/spreadsheet.module.scss +308 -0
- package/src/components/spreadsheet/spreadsheet.ts +1223 -0
- package/src/components/tabs/tabs.module.scss +1 -0
- package/src/components/textarea/textarea.ts +8 -2
- package/src/components/textedit/textedit.ts +7 -0
- package/src/components/themes.scss +2 -0
- package/src/components/tooltips/tooltips.ts +15 -3
- package/src/core/component.ts +358 -162
- package/src/core/core_application.ts +129 -32
- package/src/core/core_colors.ts +382 -119
- package/src/core/core_data.ts +73 -86
- package/src/core/core_dom.ts +10 -0
- package/src/core/core_dragdrop.ts +32 -7
- package/src/core/core_element.ts +111 -4
- package/src/core/core_events.ts +48 -11
- package/src/core/core_i18n.ts +2 -0
- package/src/core/core_pdf.ts +454 -0
- package/src/core/core_router.ts +64 -5
- package/src/core/core_state.ts +1 -0
- package/src/core/core_styles.ts +11 -12
- package/src/core/core_svg.ts +348 -51
- package/src/core/core_tools.ts +105 -17
- package/src/x4.ts +1 -0
- package/src/x4tsx.d.ts +2 -1
- package/tsconfig.json +11 -0
- package/lib/README.txt +0 -20
- package/lib/cjs/x4.css +0 -1
- package/lib/cjs/x4.js +0 -2
- package/lib/esm/x4.css +0 -1
- package/lib/esm/x4.mjs +0 -2
- 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
|
-
|
|
125
|
-
|
|
126
|
-
private
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
focus
|
|
31
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
267
|
-
*/
|
|
268
|
-
|
|
360
|
+
/** Gets the current input value as a string. */
|
|
361
|
+
|
|
269
362
|
public getValue( ) {
|
|
270
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
298
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|