x4js 2.3.17 → 2.3.18
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/aicontext.md +163 -25
- package/package.json +1 -1
- package/src/components/gridview/gridview.ts +350 -175
- package/src/components/keyboard/keyboard.ts +181 -65
- package/src/components/spreadsheet/spreadsheet.ts +27 -1
- package/src/components/tag/tag.ts +1 -2
- package/src/components/textarea/textarea.module.scss +6 -0
- package/src/components/textarea/textarea.ts +48 -10
- package/src/core/component.ts +16 -9
- package/src/core/core_clipboard.ts +239 -0
- package/src/core/core_data.ts +54 -23
- package/src/core/core_dom.ts +27 -5
- package/src/core/core_dragdrop.ts +11 -5
- package/src/core/core_element.ts +1 -1
- package/src/core/core_shortcuts.ts +1 -1
- package/src/x4.d.ts +1 -0
- package/src/x4.ts +1 -0
|
@@ -28,75 +28,166 @@ import icon_hide from "./eye-slash.svg";
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
31
|
+
/**
|
|
32
|
+
* A layout is the place of the 26 letters (its base) and the letters that
|
|
33
|
+
* a language adds to them (its extra), shown on rows of their own. The
|
|
34
|
+
* upper case is computed, the digits and the signs are the same for all.
|
|
35
|
+
*
|
|
36
|
+
* base: the countries where it is not QWERTY are those that
|
|
37
|
+
* https://en.wikipedia.org/wiki/QWERTZ and /AZERTY list.
|
|
38
|
+
* extra: the main letters of the language in the CLDR of Unicode
|
|
39
|
+
* (cldr-json, cldr-misc-full/main/<lang>/characters.json), without a..z.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
type KbBase = "azerty" | "qwerty" | "qwertz";
|
|
43
|
+
|
|
44
|
+
interface KbLayout {
|
|
45
|
+
base: KbBase;
|
|
46
|
+
extra: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const kb_bases: Record<KbBase,string[]> = {
|
|
50
|
+
azerty: [ "a z e r t y u i o p", "q s d f g h j k l m", "w x c v b n" ],
|
|
51
|
+
qwerty: [ "q w e r t y u i o p", "a s d f g h j k l", "z x c v b n m" ],
|
|
52
|
+
qwertz: [ "q w e r t z u i o p", "a s d f g h j k l", "y x c v b n m" ],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// by language
|
|
56
|
+
const kb_langs: Record<string,KbLayout> = {
|
|
57
|
+
cs: { base: "qwertz", extra: "á č ď é ě í ň ó ř š ť ú ů ý ž" },
|
|
58
|
+
da: { base: "qwerty", extra: "æ ø å" },
|
|
59
|
+
de: { base: "qwertz", extra: "ä ö ü ß" },
|
|
60
|
+
en: { base: "qwerty", extra: "" },
|
|
61
|
+
es: { base: "qwerty", extra: "á é í ñ ó ú ü" },
|
|
62
|
+
et: { base: "qwerty", extra: "õ ä ö ü š ž" },
|
|
63
|
+
fi: { base: "qwerty", extra: "å ä ö š ž" },
|
|
64
|
+
fr: { base: "azerty", extra: "é è ê ë à â æ ç î ï ô œ ù û ü ÿ" },
|
|
65
|
+
ga: { base: "qwerty", extra: "á é í ó ú" },
|
|
66
|
+
hr: { base: "qwertz", extra: "č ć đ š ž" },
|
|
67
|
+
hu: { base: "qwertz", extra: "á é í ó ö ő ú ü ű" },
|
|
68
|
+
it: { base: "qwerty", extra: "à è é ì ò ù" },
|
|
69
|
+
lt: { base: "qwerty", extra: "ą č ę ė į š ų ū ž" },
|
|
70
|
+
lv: { base: "qwerty", extra: "ā č ē ģ ī ķ ļ ņ š ū ž" },
|
|
71
|
+
mt: { base: "qwerty", extra: "à ċ è ġ ħ ì ò ù ż" },
|
|
72
|
+
nl: { base: "qwerty", extra: "á ä é ë í ï ó ö ú ü" },
|
|
73
|
+
pl: { base: "qwerty", extra: "ą ć ę ł ń ó ś ź ż" },
|
|
74
|
+
pt: { base: "qwerty", extra: "á à â ã ç é ê í ó ò ô õ ú" },
|
|
75
|
+
ro: { base: "qwerty", extra: "ă â î ș ț" },
|
|
76
|
+
sk: { base: "qwertz", extra: "á ä č ď é í ĺ ľ ň ó ô ŕ š ť ú ý ž" },
|
|
77
|
+
sl: { base: "qwertz", extra: "č š ž" },
|
|
78
|
+
sv: { base: "qwerty", extra: "å ä ö é à" },
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// the countries that do not have the keyboard of their language
|
|
82
|
+
const kb_countries: Record<string,Partial<KbLayout>> = {
|
|
83
|
+
"de-CH": { extra: "ä ö ü" }, // no ß in Switzerland
|
|
84
|
+
"fr-CH": { base: "qwertz" },
|
|
85
|
+
"it-CH": { base: "qwertz" },
|
|
86
|
+
"fr-LU": { base: "qwertz" }, // the Swiss French keyboard
|
|
87
|
+
"nl-BE": { base: "azerty" },
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const kb_digits = "1 2 3 4 5 6 7 8 9 0";
|
|
91
|
+
const kb_signs = "! @ # $ % ^ & * ( ) _ +";
|
|
92
|
+
|
|
93
|
+
const kb_pads = {
|
|
94
|
+
number: [
|
|
95
|
+
"1 2 3 {2}",
|
|
96
|
+
"4 5 6 {8}",
|
|
97
|
+
"7 8 9 {9}",
|
|
98
|
+
"0 . {3} {9}"
|
|
99
|
+
],
|
|
100
|
+
date: [
|
|
101
|
+
"1 2 3 {2}",
|
|
102
|
+
"4 5 6 {8}",
|
|
103
|
+
"7 8 9 {9}",
|
|
104
|
+
"0 / {3} {9}"
|
|
105
|
+
]
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const KB_MAX_EXTRA = 12; // keys on a row of extra letters, as many as there are signs
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* "de-CH", "de_ch", "de": the layout of the country, or else the one of
|
|
112
|
+
* its language. A language that is not known gets a QWERTY.
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
function findLayout( locale: string ): KbLayout {
|
|
116
|
+
const [lang, country] = locale.replace( "_", "-" ).split( "-" );
|
|
117
|
+
|
|
118
|
+
let layout = kb_langs[lang.toLowerCase( )];
|
|
119
|
+
if( !layout ) {
|
|
120
|
+
layout = kb_langs.en;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if( country ) {
|
|
124
|
+
const own = kb_countries[lang.toLowerCase( ) + "-" + country.toUpperCase( )];
|
|
125
|
+
if( own ) {
|
|
126
|
+
layout = { ...layout, ...own };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return layout;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function upperKeys( line: string ): string {
|
|
134
|
+
return line.split( " " ).map( key => {
|
|
135
|
+
const up = key.toUpperCase( );
|
|
136
|
+
return up.length===1 ? up : key; // ß has no upper case of one letter
|
|
137
|
+
} ).join( " " );
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* the extra letters, on rows of the same length
|
|
142
|
+
*/
|
|
143
|
+
|
|
144
|
+
function extraLines( extra: string ): string[] {
|
|
145
|
+
if( !extra ) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const keys = extra.split( " " );
|
|
150
|
+
const count = Math.ceil( keys.length / KB_MAX_EXTRA );
|
|
151
|
+
const size = Math.ceil( keys.length / count );
|
|
152
|
+
|
|
153
|
+
const lines: string[] = [];
|
|
154
|
+
for( let i=0; i<keys.length; i+=size ) {
|
|
155
|
+
lines.push( keys.slice( i, i+size ).join( " " ) );
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return lines;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function letterLines( layout: KbLayout, upper: boolean ): string[] {
|
|
162
|
+
let [l1, l2, l3] = kb_bases[layout.base];
|
|
163
|
+
let extra = extraLines( layout.extra );
|
|
164
|
+
|
|
165
|
+
if( upper ) {
|
|
166
|
+
l1 = upperKeys( l1 );
|
|
167
|
+
l2 = upperKeys( l2 );
|
|
168
|
+
l3 = upperKeys( l3 );
|
|
169
|
+
extra = extra.map( upperKeys );
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return [
|
|
173
|
+
upper ? kb_signs : kb_digits,
|
|
174
|
+
l1 + " {2}",
|
|
175
|
+
l2 + " {3}",
|
|
176
|
+
"{4} " + l3 + ( upper ? " ? : {4}" : " , . {4}" ),
|
|
177
|
+
...extra,
|
|
178
|
+
"{5} {6} ' {7}",
|
|
179
|
+
];
|
|
93
180
|
}
|
|
94
181
|
|
|
95
182
|
|
|
96
183
|
const RE_sel = /text|password|search|tel|url/i;
|
|
97
184
|
|
|
98
185
|
interface KeyboardProps extends BoxProps {
|
|
99
|
-
|
|
186
|
+
/**
|
|
187
|
+
* the language of the keys, with or without its country: "fr-FR",
|
|
188
|
+
* "de-CH", "pl"... "fr-FR" when it is not given
|
|
189
|
+
*/
|
|
190
|
+
locale?: string;
|
|
100
191
|
}
|
|
101
192
|
|
|
102
193
|
/**
|
|
@@ -120,7 +211,7 @@ export class Keyboard extends HBox<KeyboardProps>
|
|
|
120
211
|
super( { ...props, id: 'v-keyboard' } );
|
|
121
212
|
|
|
122
213
|
this.mode = 'lower';
|
|
123
|
-
this.locale = 'fr-FR';
|
|
214
|
+
this.locale = props.locale ?? 'fr-FR';
|
|
124
215
|
this.visible = false;
|
|
125
216
|
|
|
126
217
|
document.addEventListener( 'focusin', (e) => this.handleFocus(e.target as Element,true), false );
|
|
@@ -152,6 +243,24 @@ export class Keyboard extends HBox<KeyboardProps>
|
|
|
152
243
|
this.setStyleVariable( '--keyboard-zoom', perc+'%' );
|
|
153
244
|
}
|
|
154
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Change the language of the keys.
|
|
248
|
+
* @param locale - "fr-FR", "de-CH", "pl"... A country that has no keyboard
|
|
249
|
+
* of its own gets the one of its language, a language that is not known a QWERTY.
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* keyboard.setLocale( "de-CH" );
|
|
253
|
+
*/
|
|
254
|
+
|
|
255
|
+
setLocale( locale: string ) {
|
|
256
|
+
this.locale = locale;
|
|
257
|
+
|
|
258
|
+
// drawn at least once
|
|
259
|
+
if( this.keyboard ) {
|
|
260
|
+
this._redraw( );
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
155
264
|
/**
|
|
156
265
|
*
|
|
157
266
|
*/
|
|
@@ -436,7 +545,14 @@ export class Keyboard extends HBox<KeyboardProps>
|
|
|
436
545
|
*/
|
|
437
546
|
|
|
438
547
|
private _createContent( ) {
|
|
439
|
-
let lines
|
|
548
|
+
let lines: string[];
|
|
549
|
+
if( this.mode=="number" || this.mode=="date" ) {
|
|
550
|
+
lines = kb_pads[this.mode];
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
lines = letterLines( findLayout( this.locale ), this.mode=="upper" );
|
|
554
|
+
}
|
|
555
|
+
|
|
440
556
|
let result: Box[] = [];
|
|
441
557
|
|
|
442
558
|
for( let j=0; j<lines.length; j++ ) {
|
|
@@ -676,7 +676,9 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
|
|
|
676
676
|
const sizer = new CSizer("right");
|
|
677
677
|
|
|
678
678
|
sizer.on("stop", () => {
|
|
679
|
+
// the flex columns took what was left: the body gets the width of its columns
|
|
679
680
|
this._updateFlexs();
|
|
681
|
+
this._computeFullSize();
|
|
680
682
|
})
|
|
681
683
|
|
|
682
684
|
sizer.on("resize", (ev) => {
|
|
@@ -698,6 +700,24 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
|
|
|
698
700
|
}
|
|
699
701
|
})
|
|
700
702
|
|
|
703
|
+
// a double click on the sizer: the column takes its share of the place
|
|
704
|
+
// that is left (flex 1). To stop taking it, the sizer is dragged
|
|
705
|
+
sizer.addDOMEvent("dblclick", (ev) => {
|
|
706
|
+
// both: the grid has a double click of its own
|
|
707
|
+
ev.stopPropagation();
|
|
708
|
+
ev.preventDefault();
|
|
709
|
+
|
|
710
|
+
// a fixed column is not in the place that is shared
|
|
711
|
+
if (fixed || cdata.flex) {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
cdata.flex = 1;
|
|
716
|
+
|
|
717
|
+
this._updateFlexs();
|
|
718
|
+
this._computeFullSize();
|
|
719
|
+
});
|
|
720
|
+
|
|
701
721
|
const cell = new Component({
|
|
702
722
|
cls: `cell`,
|
|
703
723
|
attrs: { "data-col": col },
|
|
@@ -956,6 +976,7 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
|
|
|
956
976
|
|
|
957
977
|
const el = new Component({
|
|
958
978
|
cls: "cell",
|
|
979
|
+
attrs: { "data-col": col }, // the sizer of the column finds its cells by it
|
|
959
980
|
style: { width: cdata?.width ? cdata.width + "px" : undefined, justifyContent: align },
|
|
960
981
|
content
|
|
961
982
|
});
|
|
@@ -993,7 +1014,12 @@ export class Spreadsheet<P extends SpreadsheetProps = SpreadsheetProps, E extend
|
|
|
993
1014
|
for (let x = 0; x < ccount; x++) {
|
|
994
1015
|
const cdata = this._getCol(x);
|
|
995
1016
|
|
|
996
|
-
|
|
1017
|
+
// the fixed columns are not in the viewport: their width is not taken from its own
|
|
1018
|
+
if (cdata.fixed) {
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
if (cdata.flex) {
|
|
997
1023
|
flexc += cdata.flex;
|
|
998
1024
|
}
|
|
999
1025
|
else {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
**/
|
|
18
18
|
|
|
19
19
|
import { BaseProps } from '../input/input';
|
|
20
|
-
import { Component, ComponentProps } from '../../core/component';
|
|
20
|
+
import { Component, ComponentEvents, ComponentProps, EvChange, EvFocus } from '../../core/component';
|
|
21
21
|
|
|
22
22
|
import { Label } from '../label/label';
|
|
23
23
|
import { VBox } from '../boxes/boxes';
|
|
@@ -29,25 +29,35 @@ import { class_ns, IFormElement } from '../../core/core_tools';
|
|
|
29
29
|
*
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
-
interface
|
|
33
|
-
label?: string;
|
|
32
|
+
export interface SimpleTextAreaProps extends BaseProps {
|
|
34
33
|
value?: string;
|
|
35
34
|
resize?: boolean | "none" | "vertical" | "horizontal" | "both";
|
|
36
35
|
readonly?: boolean;
|
|
37
36
|
trim?: boolean;
|
|
38
37
|
}
|
|
39
38
|
|
|
39
|
+
export interface TextAreaProps extends SimpleTextAreaProps {
|
|
40
|
+
label?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface TextAreaEvents extends ComponentEvents {
|
|
44
|
+
focus: EvFocus;
|
|
45
|
+
change: EvChange;
|
|
46
|
+
}
|
|
47
|
+
|
|
40
48
|
|
|
41
49
|
/**
|
|
42
|
-
*
|
|
50
|
+
*
|
|
43
51
|
*/
|
|
44
52
|
|
|
45
53
|
@class_ns( "x4" )
|
|
46
|
-
export class SimpleTextArea extends Component<
|
|
54
|
+
export class SimpleTextArea extends Component<SimpleTextAreaProps,TextAreaEvents> {
|
|
47
55
|
|
|
48
|
-
constructor( props:
|
|
56
|
+
constructor( props: SimpleTextAreaProps ) {
|
|
49
57
|
super( { ...props, tag: "textarea" } );
|
|
50
58
|
|
|
59
|
+
this.mapPropEvents( props, "focus", "change" );
|
|
60
|
+
|
|
51
61
|
this.setAttribute( "name", props.name );
|
|
52
62
|
if( props.value ) {
|
|
53
63
|
this.setText( props.value+'' );
|
|
@@ -60,6 +70,22 @@ export class SimpleTextArea extends Component<TextAreaProps> {
|
|
|
60
70
|
if( props.readonly ) {
|
|
61
71
|
this.setAttribute( "readonly", true );
|
|
62
72
|
}
|
|
73
|
+
|
|
74
|
+
if( props.placeholder ) {
|
|
75
|
+
this.setAttribute( "placeholder", props.placeholder );
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if( props.required ) {
|
|
79
|
+
this.setAttribute( "required", true );
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if( props.autofocus ) {
|
|
83
|
+
this.setAttribute( "autofocus", true );
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.addDOMEvent( "blur", ( ) => this.fire( "focus", { focus_out: true } ) );
|
|
87
|
+
this.addDOMEvent( "focus", ( ) => this.fire( "focus", { focus_out: false } ) );
|
|
88
|
+
this.addDOMEvent( "input", ( ) => this.fire( "change", { value: this.getText( ) } ) );
|
|
63
89
|
}
|
|
64
90
|
|
|
65
91
|
setText( text: string ) {
|
|
@@ -79,12 +105,18 @@ export class SimpleTextArea extends Component<TextAreaProps> {
|
|
|
79
105
|
this.dom.scrollTop = this.dom.scrollHeight;
|
|
80
106
|
}
|
|
81
107
|
|
|
108
|
+
/** false when it is required and empty */
|
|
109
|
+
|
|
110
|
+
isValid( ) {
|
|
111
|
+
return !this.props.required || this.getText( )!=="";
|
|
112
|
+
}
|
|
113
|
+
|
|
82
114
|
queryInterface<T>(name: string): T {
|
|
83
115
|
if( name=="form-element" ) {
|
|
84
116
|
const i: IFormElement = {
|
|
85
117
|
getRawValue: ( ): any => { return this.getText(); },
|
|
86
118
|
setRawValue: ( v: any ) => { this.setText( v ); },
|
|
87
|
-
isValid: ( ) => { return
|
|
119
|
+
isValid: ( ) => { return this.isValid( ); }
|
|
88
120
|
};
|
|
89
121
|
|
|
90
122
|
return i as unknown as T;
|
|
@@ -115,11 +147,11 @@ export class TextArea extends VBox {
|
|
|
115
147
|
super( props );
|
|
116
148
|
|
|
117
149
|
|
|
118
|
-
const {
|
|
150
|
+
const { value, resize, readonly, trim, name, placeholder, required, autofocus, focus, change } = props;
|
|
119
151
|
|
|
120
152
|
this.setContent( [
|
|
121
153
|
new Label( { text: props.label }),
|
|
122
|
-
this._input = new SimpleTextArea( {
|
|
154
|
+
this._input = new SimpleTextArea( { value,resize,readonly,trim,name,placeholder,required,autofocus,focus,change } )
|
|
123
155
|
])
|
|
124
156
|
}
|
|
125
157
|
|
|
@@ -135,12 +167,18 @@ export class TextArea extends VBox {
|
|
|
135
167
|
this._input.scrollToBottom( );
|
|
136
168
|
}
|
|
137
169
|
|
|
170
|
+
/** false when it is required and empty */
|
|
171
|
+
|
|
172
|
+
isValid( ) {
|
|
173
|
+
return this._input.isValid( );
|
|
174
|
+
}
|
|
175
|
+
|
|
138
176
|
queryInterface<T>(name: string): T {
|
|
139
177
|
if( name=="form-element" ) {
|
|
140
178
|
const i: IFormElement = {
|
|
141
179
|
getRawValue: ( ): any => { return this.getText(); },
|
|
142
180
|
setRawValue: ( v: any ) => { this.setText( v ); },
|
|
143
|
-
isValid: ( ) => { return
|
|
181
|
+
isValid: ( ) => { return this.isValid( ); }
|
|
144
182
|
};
|
|
145
183
|
|
|
146
184
|
return i as unknown as T;
|
package/src/core/component.ts
CHANGED
|
@@ -234,7 +234,7 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
|
|
|
234
234
|
this.setStyle( props.style );
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
if( props.content ) {
|
|
237
|
+
if( props.content || props.content===0 ) {
|
|
238
238
|
this.setContent( props.content );
|
|
239
239
|
}
|
|
240
240
|
|
|
@@ -321,7 +321,7 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
|
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
if( cls.indexOf(' ')>=0 ) {
|
|
324
|
-
const ccs = cls.split( " " );
|
|
324
|
+
const ccs = cls.split( " " ).filter( x => x );
|
|
325
325
|
this.dom.classList.remove(...ccs);
|
|
326
326
|
}
|
|
327
327
|
else {
|
|
@@ -358,7 +358,7 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
|
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
if( cls.indexOf(' ')>=0 ) {
|
|
361
|
-
const ccs = cls.split( " " );
|
|
361
|
+
const ccs = cls.split( " " ).filter( x => x );
|
|
362
362
|
ccs.forEach( toggle );
|
|
363
363
|
}
|
|
364
364
|
else {
|
|
@@ -897,7 +897,8 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
|
|
|
897
897
|
|
|
898
898
|
/**
|
|
899
899
|
* Enables or disables the component.
|
|
900
|
-
* This sets the `disabled`
|
|
900
|
+
* This sets or removes the `disabled` and `inert` attributes of the component itself. Its children
|
|
901
|
+
* are not changed: the browser makes all that is inside an `inert` element inactive.
|
|
901
902
|
* @param ena - If `true`, the component is enabled; if `false`, it's disabled. Defaults to `true`.
|
|
902
903
|
* @returns The component instance for chaining.
|
|
903
904
|
*/
|
|
@@ -1195,8 +1196,8 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
|
|
|
1195
1196
|
if( !this.#shortcuts ) {
|
|
1196
1197
|
const shortcuts = this.#shortcuts = new Shortcuts( );
|
|
1197
1198
|
|
|
1198
|
-
// straight on the element, not with addDOMEvent:
|
|
1199
|
-
// children are called
|
|
1199
|
+
// straight on the element, not with addDOMEvent: it does not change
|
|
1200
|
+
// how the handlers of the children are called
|
|
1200
1201
|
this.dom.addEventListener( "keydown", ( ev ) => shortcuts.handle( ev as KeyboardEvent ) );
|
|
1201
1202
|
this.dom.addEventListener( "focusin", ( ) => Application.instance( ).setActiveShortcuts( this, shortcuts ) );
|
|
1202
1203
|
}
|
|
@@ -1230,11 +1231,17 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
|
|
|
1230
1231
|
|
|
1231
1232
|
const state = makeState( raw );
|
|
1232
1233
|
|
|
1233
|
-
// the state is what is saved, not raw: makeState works on a copy of it
|
|
1234
|
+
// the state is what is saved, not raw: makeState works on a copy of it.
|
|
1235
|
+
// The write waits for the changes to stop. Its timer is the one of the
|
|
1236
|
+
// browser, not this.setTimeout: the timers of a component are cleared
|
|
1237
|
+
// when it leaves the DOM, and what changed just before must still be written
|
|
1238
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
1239
|
+
|
|
1234
1240
|
state.on( "change", ( ) => {
|
|
1235
|
-
|
|
1241
|
+
clearTimeout( timer );
|
|
1242
|
+
timer = setTimeout( ( ) => {
|
|
1236
1243
|
Application.instance().setStorage( key, JSON.stringify( state ) );
|
|
1237
|
-
|
|
1244
|
+
}, 500 );
|
|
1238
1245
|
});
|
|
1239
1246
|
|
|
1240
1247
|
this.#pstate[name] = state;
|