x4js 2.3.18 → 2.3.19
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/aicli.md +3 -3
- package/aicontext.md +49 -23
- package/cli/config.mjs +14 -7
- package/cli/copy-plugin.mjs +8 -0
- package/cli/create.mjs +8 -2
- package/cli/dev.mjs +4 -1
- package/cli/less-plugin.mjs +5 -1
- package/cli/main.mjs +15 -5
- package/cli/rawfile-plugin.mjs +23 -3
- package/cli/templates.mjs +5 -1
- package/package.json +1 -1
- package/src/components/calendar/calendar.module.scss +16 -1
- package/src/components/calendar/calendar.ts +52 -9
- package/src/components/checkbox/checkbox.ts +1 -0
- package/src/components/combobox/combobox.ts +51 -23
- package/src/components/dialog/dialog.ts +4 -1
- package/src/components/form/form.ts +9 -5
- package/src/components/gridview/gridview.ts +2 -1
- package/src/components/input/input.ts +63 -38
- package/src/components/keyboard/keyboard.module.scss +35 -4
- package/src/components/keyboard/keyboard.ts +120 -21
- package/src/components/listbox/listbox.ts +6 -1
- package/src/components/messages/messages.ts +12 -3
- package/src/components/popup/popup.ts +19 -20
- package/src/components/radio/radio.ts +8 -4
- package/src/components/rating/rating.ts +38 -48
- package/src/components/slider/slider.ts +38 -8
- package/src/components/spreadsheet/spreadsheet.ts +140 -131
- package/src/components/switch/switch.ts +2 -1
- package/src/components/textarea/textarea.module.scss +2 -4
- package/src/components/textarea/textarea.ts +2 -1
- package/src/components/textedit/textedit.ts +12 -2
- package/src/core/core_application.ts +8 -4
- package/src/core/core_data.ts +64 -26
- package/src/core/core_dom.ts +9 -1
- package/src/core/core_pdf.ts +696 -46
- package/src/core/core_router.ts +42 -44
- package/src/core/core_state.ts +22 -5
|
@@ -144,30 +144,15 @@ export class Combobox extends Component<ComboboxProps,ComboboxEvents> {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
this._popup = new DropdownList( { items: props.items, renderer: props.renderer } );
|
|
147
|
-
const list = this._popup.getList( );
|
|
148
|
-
|
|
149
|
-
const _select = ( sel: ListboxID ) => {
|
|
150
|
-
const itm = list.getItem(sel);
|
|
151
|
-
if( itm ) {
|
|
152
|
-
list.select( sel, false );
|
|
153
|
-
this._input.setValue( itm.text as string );
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if( !this._prevent_close ) {
|
|
157
|
-
this._popup.show( false );
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
147
|
this._popup.on( "click", ( ev ) => {
|
|
162
148
|
const sel = ev.context as ListboxID;
|
|
163
149
|
if( sel!==undefined ) { // no empty sel
|
|
164
|
-
|
|
165
|
-
this.fire( "selectionChange", { selection: [sel], empty: false } );
|
|
150
|
+
this._choose( sel, true );
|
|
166
151
|
}
|
|
167
152
|
});
|
|
168
153
|
|
|
169
154
|
if( props.value!==undefined && props.value!==null ) {
|
|
170
|
-
|
|
155
|
+
this._choose( props.value, false );
|
|
171
156
|
}
|
|
172
157
|
|
|
173
158
|
this._button.addDOMEvent( "click", ( ) => this._on_click( ) );
|
|
@@ -180,6 +165,27 @@ export class Combobox extends Component<ComboboxProps,ComboboxEvents> {
|
|
|
180
165
|
})
|
|
181
166
|
}
|
|
182
167
|
|
|
168
|
+
/**
|
|
169
|
+
* the item chosen: by a click, by the arrows (the popup stays open), or by the props
|
|
170
|
+
*/
|
|
171
|
+
|
|
172
|
+
private _choose( sel: ListboxID, notify: boolean ) {
|
|
173
|
+
const list = this._getList( );
|
|
174
|
+
const itm = list.getItem( sel );
|
|
175
|
+
if( itm ) {
|
|
176
|
+
list.select( sel, false );
|
|
177
|
+
this._input.setValue( itm.text as string );
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if( !this._prevent_close ) {
|
|
181
|
+
this._popup.show( false );
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if( notify ) {
|
|
185
|
+
this.fire( "selectionChange", { selection: [sel], empty: false } );
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
183
189
|
private _on_key( ev: KeyboardEvent ) {
|
|
184
190
|
switch( ev.key ) {
|
|
185
191
|
case "Enter":
|
|
@@ -197,7 +203,13 @@ export class Combobox extends Component<ComboboxProps,ComboboxEvents> {
|
|
|
197
203
|
this.showDropDown( );
|
|
198
204
|
}
|
|
199
205
|
else {
|
|
200
|
-
|
|
206
|
+
// the item reached is chosen at once
|
|
207
|
+
const list = this._getList( );
|
|
208
|
+
list.navigate( kbNav.prev );
|
|
209
|
+
const [sel] = list.getSelection( );
|
|
210
|
+
if( sel!==undefined ) {
|
|
211
|
+
this._choose( sel, true );
|
|
212
|
+
}
|
|
201
213
|
}
|
|
202
214
|
|
|
203
215
|
this._prevent_close = false;
|
|
@@ -209,7 +221,13 @@ export class Combobox extends Component<ComboboxProps,ComboboxEvents> {
|
|
|
209
221
|
this.showDropDown( );
|
|
210
222
|
}
|
|
211
223
|
else {
|
|
212
|
-
|
|
224
|
+
// the item reached is chosen at once
|
|
225
|
+
const list = this._getList( );
|
|
226
|
+
list.navigate( kbNav.next );
|
|
227
|
+
const [sel] = list.getSelection( );
|
|
228
|
+
if( sel!==undefined ) {
|
|
229
|
+
this._choose( sel, true );
|
|
230
|
+
}
|
|
213
231
|
}
|
|
214
232
|
|
|
215
233
|
this._prevent_close = false;
|
|
@@ -229,7 +247,18 @@ export class Combobox extends Component<ComboboxProps,ComboboxEvents> {
|
|
|
229
247
|
this.showDropDown( );
|
|
230
248
|
}
|
|
231
249
|
|
|
232
|
-
|
|
250
|
+
// the text typed is no longer the one of the item chosen: none is chosen
|
|
251
|
+
const list = this._getList( );
|
|
252
|
+
const sel = this.getSelection( );
|
|
253
|
+
if( sel!==undefined ) {
|
|
254
|
+
const itm = list.getItem( sel );
|
|
255
|
+
if( !itm || itm.text!==this._input.getValue( ) ) {
|
|
256
|
+
list.clearSelection( false );
|
|
257
|
+
this.fire( "selectionChange", { selection: [], empty: true } );
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
list.filter( this._input.getValue( ) );
|
|
233
262
|
}
|
|
234
263
|
|
|
235
264
|
private _on_focusout( ) {
|
|
@@ -268,10 +297,9 @@ export class Combobox extends Component<ComboboxProps,ComboboxEvents> {
|
|
|
268
297
|
selectItem( index: ListboxID ) {
|
|
269
298
|
const list = this._getList( );
|
|
270
299
|
list.select( index );
|
|
300
|
+
// no item (null...): the text is emptied too
|
|
271
301
|
const el = list.getItem( index );
|
|
272
|
-
|
|
273
|
-
this.setValue( el.text as string );
|
|
274
|
-
}
|
|
302
|
+
this.setValue( el ? el.text as string : "" );
|
|
275
303
|
}
|
|
276
304
|
|
|
277
305
|
getSelection( ) {
|
|
@@ -257,11 +257,14 @@ export class Dialog<P extends DialogProps = DialogProps, E extends DialogEvents
|
|
|
257
257
|
|
|
258
258
|
this.on( "btnclick", ( ev ) => {
|
|
259
259
|
asap( ( ) => {
|
|
260
|
-
this.close( );
|
|
261
260
|
resolve( ev.button );
|
|
261
|
+
this.close( );
|
|
262
262
|
});
|
|
263
263
|
});
|
|
264
264
|
|
|
265
|
+
// closed without a button (Escape, the close box): nothing was chosen
|
|
266
|
+
this.on( "closed", ( ) => resolve( null ) );
|
|
267
|
+
|
|
265
268
|
this.show();
|
|
266
269
|
} );
|
|
267
270
|
}
|
|
@@ -117,13 +117,14 @@ export class Form<P extends FormProps = FormProps> extends Box<P> {
|
|
|
117
117
|
|
|
118
118
|
setValidator( validator: ValidationFn ) {
|
|
119
119
|
|
|
120
|
+
// focusout: once, the first time
|
|
120
121
|
if( !this.validator ) {
|
|
121
|
-
this.validator = validator;
|
|
122
|
-
|
|
123
122
|
// validation en live des valeurs
|
|
124
123
|
// disable du bouton ok si pas bon
|
|
125
124
|
this.addDOMEvent( "focusout", ( ) => this.validate() );
|
|
126
125
|
}
|
|
126
|
+
|
|
127
|
+
this.validator = validator;
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
/**
|
|
@@ -155,11 +156,14 @@ export class Form<P extends FormProps = FormProps> extends Box<P> {
|
|
|
155
156
|
return null;
|
|
156
157
|
}
|
|
157
158
|
|
|
158
|
-
|
|
159
|
+
// the elements already found: a select, a textarea, a rating... every radio of a group
|
|
160
|
+
for( const x of items ) {
|
|
161
|
+
const name = x.getAttribute( "name" );
|
|
159
162
|
if( new_values[name] != result[name] ) {
|
|
160
|
-
const x = this.query( `input[name="${name}"]` );
|
|
161
163
|
const ifx = x.queryInterface( "form-element" ) as IFormElement;
|
|
162
|
-
|
|
164
|
+
if( ifx ) {
|
|
165
|
+
ifx.setRawValue( new_values[name] );
|
|
166
|
+
}
|
|
163
167
|
}
|
|
164
168
|
}
|
|
165
169
|
|
|
@@ -699,7 +699,8 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
699
699
|
if (lock) {
|
|
700
700
|
this._lock++;
|
|
701
701
|
}
|
|
702
|
-
|
|
702
|
+
// an unlock too many leaves it at 0: the grid goes on drawing
|
|
703
|
+
else if (this._lock > 0) {
|
|
703
704
|
if (--this._lock == 0 && this._dirty) {
|
|
704
705
|
this._update( true );
|
|
705
706
|
}
|
|
@@ -15,22 +15,24 @@
|
|
|
15
15
|
**/
|
|
16
16
|
|
|
17
17
|
import { EventCallback } from '../../core/core_events';
|
|
18
|
-
import { Component, ComponentEvent,
|
|
18
|
+
import { Component, ComponentEvent, ComponentProps, EvChange, EvFocus } from '../../core/component';
|
|
19
19
|
import { class_ns, formatIntlDate, IComponentInterface, IFormElement, isString } from '../../core/core_tools';
|
|
20
20
|
|
|
21
21
|
import "./input.module.scss"
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* @internal where to look for the radios of a group: the form that holds them,
|
|
26
|
+
* else the document
|
|
27
|
+
*/
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
const comp = componentFromDOM(el);
|
|
28
|
-
const ifx = comp.queryInterface( "tab-handler");
|
|
29
|
-
if( ifx ) {
|
|
30
|
-
return el;
|
|
31
|
-
}
|
|
29
|
+
export function getRadioOwner( el: Element ): ParentNode {
|
|
32
30
|
|
|
33
|
-
|
|
31
|
+
// two forms of a page can each have a group of the same name;
|
|
32
|
+
// a dialog has its form, its radios are in it
|
|
33
|
+
const form = el.closest( "form" );
|
|
34
|
+
if( form ) {
|
|
35
|
+
return form;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
return document;
|
|
@@ -222,7 +224,19 @@ export class Input extends Component<InputProps,InputEvents> {
|
|
|
222
224
|
if( props.autofocus===true ) {
|
|
223
225
|
this.setAttribute( "autofocus", true );
|
|
224
226
|
}
|
|
225
|
-
|
|
227
|
+
|
|
228
|
+
// every type but checkbox and radio (range and file ignore readonly)
|
|
229
|
+
if( props.type!="checkbox" && props.type!="radio" ) {
|
|
230
|
+
this.setAttribute( "required", props.required );
|
|
231
|
+
this.setAttribute( "readonly", props.readonly );
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// placeholder: shown by the browser on text, email, password and number only,
|
|
235
|
+
// not on date nor time (they show their own pattern)
|
|
236
|
+
if( props.type===undefined || props.type=="text" || props.type=="email" || props.type=="password" || props.type=="number" ) {
|
|
237
|
+
this.setAttribute( "placeholder", props.placeholder );
|
|
238
|
+
}
|
|
239
|
+
|
|
226
240
|
switch( props.type ) {
|
|
227
241
|
case "checkbox":
|
|
228
242
|
case "radio": {
|
|
@@ -244,9 +258,7 @@ export class Input extends Component<InputProps,InputEvents> {
|
|
|
244
258
|
|
|
245
259
|
case "number": {
|
|
246
260
|
const p = this.props as NumberProps;
|
|
247
|
-
|
|
248
|
-
this.setAttribute( "required", p.required );
|
|
249
|
-
this.setAttribute( "readonly", p.readonly );
|
|
261
|
+
|
|
250
262
|
this.setAttribute( "min", p.min );
|
|
251
263
|
this.setAttribute( "max", p.max );
|
|
252
264
|
this.setAttribute( "step", p.step );
|
|
@@ -256,20 +268,26 @@ export class Input extends Component<InputProps,InputEvents> {
|
|
|
256
268
|
this.addDOMEvent( "wheel", ( e: WheelEvent ) => {
|
|
257
269
|
if( this.hasFocus() ) { // only if has focus
|
|
258
270
|
e.preventDefault( );
|
|
259
|
-
let v = this.getNumValue();
|
|
260
|
-
const delta = e.deltaY < 0 ? 1 : -1;
|
|
261
|
-
v += (p.step ? p.step : 1) * delta;
|
|
262
|
-
this.setNumValue( v, -2 );
|
|
263
271
|
|
|
264
|
-
|
|
272
|
+
// the browser keeps the value between min and max, on the step,
|
|
273
|
+
// and starts from a valid one when the field is empty
|
|
274
|
+
const input = this.dom as HTMLInputElement;
|
|
275
|
+
if( e.deltaY < 0 ) {
|
|
276
|
+
input.stepUp( );
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
input.stepDown( );
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// the decimals of the step: 10.1 shown 10.10 for a step of 0.01
|
|
283
|
+
this.setNumValue( this.getNumValue( ), -2 );
|
|
284
|
+
this.dom.dispatchEvent(new Event('input'));
|
|
265
285
|
}
|
|
266
286
|
}, );
|
|
267
287
|
break;
|
|
268
288
|
}
|
|
269
289
|
|
|
270
290
|
case "date": {
|
|
271
|
-
this.setAttribute( "required", props.required );
|
|
272
|
-
|
|
273
291
|
let v = props.value;
|
|
274
292
|
if( v instanceof Date ) {
|
|
275
293
|
this.setAttribute( "value", formatIntlDate( v, "Y-M-D" ) );
|
|
@@ -295,7 +313,6 @@ export class Input extends Component<InputProps,InputEvents> {
|
|
|
295
313
|
}
|
|
296
314
|
|
|
297
315
|
case "time": {
|
|
298
|
-
this.setAttribute( "required", props.required );
|
|
299
316
|
if( props.value!==null && props.value!==undefined ) {
|
|
300
317
|
this.setAttribute( "value", props.value );
|
|
301
318
|
}
|
|
@@ -303,9 +320,6 @@ export class Input extends Component<InputProps,InputEvents> {
|
|
|
303
320
|
}
|
|
304
321
|
|
|
305
322
|
default: {
|
|
306
|
-
this.setAttribute( "required", props.required );
|
|
307
|
-
this.setAttribute( "readonly", props.readonly );
|
|
308
|
-
|
|
309
323
|
if( props.value!==null && props.value!==undefined ) {
|
|
310
324
|
this.setAttribute( "value", props.value );
|
|
311
325
|
}
|
|
@@ -314,10 +328,6 @@ export class Input extends Component<InputProps,InputEvents> {
|
|
|
314
328
|
this.setAttribute( "pattern", props.pattern );
|
|
315
329
|
}
|
|
316
330
|
|
|
317
|
-
if( props.placeholder!==null && props.placeholder!==undefined ) {
|
|
318
|
-
this.setAttribute( "placeholder", props.placeholder );
|
|
319
|
-
}
|
|
320
|
-
|
|
321
331
|
if( props.spellcheck===false ) {
|
|
322
332
|
this.setAttribute( "spellcheck", false );
|
|
323
333
|
}
|
|
@@ -417,14 +427,19 @@ export class Input extends Component<InputProps,InputEvents> {
|
|
|
417
427
|
}
|
|
418
428
|
|
|
419
429
|
if( ndec==-2 && this.props.type=='number' ) {
|
|
420
|
-
const
|
|
430
|
+
const step = (this.props as NumberProps).step;
|
|
431
|
+
|
|
432
|
+
// as many decimals as the step, 4 at most: the step in 1/10000 is an integer,
|
|
433
|
+
// each 0 on its right is a decimal less (0.25 → 2500 → 2, 2.5 → 25000 → 1, 2 → 0)
|
|
434
|
+
if( step!==undefined && step!=1 ) {
|
|
435
|
+
let n = Math.round( step * 10000 );
|
|
436
|
+
let decimals = 4;
|
|
437
|
+
while( n && n%10==0 && decimals ) {
|
|
438
|
+
n /= 10;
|
|
439
|
+
decimals--;
|
|
440
|
+
}
|
|
421
441
|
|
|
422
|
-
|
|
423
|
-
let ndec = -Math.floor(Math.log10(p.step ?? 1) );
|
|
424
|
-
return this.setValue( value.toFixed(ndec) );
|
|
425
|
-
}
|
|
426
|
-
else if( p.step>1 ) {
|
|
427
|
-
return this.setValue( value.toFixed() );
|
|
442
|
+
return this.setValue( value.toFixed( decimals ) );
|
|
428
443
|
}
|
|
429
444
|
}
|
|
430
445
|
else if( ndec>=0 ) {
|
|
@@ -506,9 +521,16 @@ export class Input extends Component<InputProps,InputEvents> {
|
|
|
506
521
|
override queryInterface<T extends IComponentInterface>( name: string ): T {
|
|
507
522
|
if( name=="form-element" ) {
|
|
508
523
|
const i: IFormElement = {
|
|
509
|
-
getRawValue: ( ): any => {
|
|
524
|
+
getRawValue: ( ): any => {
|
|
525
|
+
// checkbox without value, or a boolean one: a boolean; with one: the value when checked,
|
|
526
|
+
// null when not (as a <form>)
|
|
510
527
|
if( this.props.type=='checkbox' ) {
|
|
511
|
-
|
|
528
|
+
const value = this.props.value;
|
|
529
|
+
if( value===undefined || typeof value==="boolean" ) {
|
|
530
|
+
return this.getCheck( );
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return this.getCheck( ) ? value : null;
|
|
512
534
|
}
|
|
513
535
|
else if( this.props.type=='radio' ) {
|
|
514
536
|
const owner = getRadioOwner( this.dom );
|
|
@@ -523,8 +545,11 @@ export class Input extends Component<InputProps,InputEvents> {
|
|
|
523
545
|
}
|
|
524
546
|
},
|
|
525
547
|
setRawValue: ( v: any ) => {
|
|
548
|
+
// checkbox with a value (not a boolean): checked by that value (or true)
|
|
526
549
|
if( this.props.type=='checkbox' ) {
|
|
527
|
-
this.
|
|
550
|
+
const value = this.props.value;
|
|
551
|
+
const plain = value===undefined || typeof value==="boolean";
|
|
552
|
+
this.setCheck( plain ? !!v : ( v===value || v===true ) );
|
|
528
553
|
}
|
|
529
554
|
else if( this.props.type=='radio' ) {
|
|
530
555
|
if( this.props.value==v ) {
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
:root {
|
|
18
18
|
--keyboard-key-background: #33302d;
|
|
19
19
|
--keyboard-hilite-color: #5ba921;
|
|
20
|
+
--keyboard-key-color: #ffffff;
|
|
21
|
+
--keyboard-accents-color: #7cc7ff;
|
|
22
|
+
--keyboard-symbols-color: #f2c94c;
|
|
20
23
|
--keyboard-zoom: 100%;
|
|
21
24
|
}
|
|
22
25
|
|
|
@@ -62,7 +65,8 @@
|
|
|
62
65
|
width: 100%;
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
color
|
|
68
|
+
// the color of the page, for the keys of characters
|
|
69
|
+
color: var( --kb-page-color, var( --keyboard-key-color ) );
|
|
66
70
|
text-align: center;
|
|
67
71
|
|
|
68
72
|
background-color: var( --keyboard-key-background);
|
|
@@ -83,6 +87,29 @@
|
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
|
|
90
|
+
.accents {
|
|
91
|
+
--kb-page-color: var( --keyboard-accents-color );
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.symbols {
|
|
95
|
+
--kb-page-color: var( --keyboard-symbols-color );
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// the keys of commands keep their color, the keys that call a page have its one
|
|
99
|
+
.cnum,
|
|
100
|
+
.calpha,
|
|
101
|
+
.cspace {
|
|
102
|
+
color: var( --keyboard-key-color );
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.caccents {
|
|
106
|
+
color: var( --keyboard-accents-color );
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.csymbols {
|
|
110
|
+
color: var( --keyboard-symbols-color );
|
|
111
|
+
}
|
|
112
|
+
|
|
86
113
|
.cplace {
|
|
87
114
|
visibility: hidden;
|
|
88
115
|
}
|
|
@@ -109,8 +136,9 @@
|
|
|
109
136
|
|
|
110
137
|
// color: rgba(255,255,255,0.3);
|
|
111
138
|
|
|
112
|
-
.
|
|
113
|
-
.
|
|
139
|
+
.alpha,
|
|
140
|
+
.accents,
|
|
141
|
+
.symbols {
|
|
114
142
|
|
|
115
143
|
.cspace {
|
|
116
144
|
flex-basis: 689px;
|
|
@@ -128,7 +156,10 @@
|
|
|
128
156
|
flex-basis: 88px;
|
|
129
157
|
}
|
|
130
158
|
|
|
131
|
-
.cnum
|
|
159
|
+
.cnum,
|
|
160
|
+
.caccents,
|
|
161
|
+
.csymbols,
|
|
162
|
+
.calpha {
|
|
132
163
|
flex-basis: 88px;
|
|
133
164
|
}
|
|
134
165
|
}
|
|
@@ -30,8 +30,9 @@ import icon_hide from "./eye-slash.svg";
|
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
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
|
|
34
|
-
* upper case is computed, the digits and the signs
|
|
33
|
+
* a language adds to them (its extra), shown on a page of their own,
|
|
34
|
+
* called by a key. The upper case is computed, the digits and the signs
|
|
35
|
+
* are the same for all.
|
|
35
36
|
*
|
|
36
37
|
* base: the countries where it is not QWERTY are those that
|
|
37
38
|
* https://en.wikipedia.org/wiki/QWERTZ and /AZERTY list.
|
|
@@ -40,6 +41,7 @@ import icon_hide from "./eye-slash.svg";
|
|
|
40
41
|
*/
|
|
41
42
|
|
|
42
43
|
type KbBase = "azerty" | "qwerty" | "qwertz";
|
|
44
|
+
type KbMode = "alpha" | "accents" | "symbols" | "number" | "date";
|
|
43
45
|
|
|
44
46
|
interface KbLayout {
|
|
45
47
|
base: KbBase;
|
|
@@ -90,6 +92,13 @@ const kb_countries: Record<string,Partial<KbLayout>> = {
|
|
|
90
92
|
const kb_digits = "1 2 3 4 5 6 7 8 9 0";
|
|
91
93
|
const kb_signs = "! @ # $ % ^ & * ( ) _ +";
|
|
92
94
|
|
|
95
|
+
// the page of the signs, that the letters do not have
|
|
96
|
+
const kb_symbols = [
|
|
97
|
+
"- / : ; ( ) € & @ \"",
|
|
98
|
+
"\\ | ~ _ ` § £ $ ^ %",
|
|
99
|
+
"[ ] { } < > = + * #",
|
|
100
|
+
];
|
|
101
|
+
|
|
93
102
|
const kb_pads = {
|
|
94
103
|
number: [
|
|
95
104
|
"1 2 3 {2}",
|
|
@@ -160,13 +169,11 @@ function extraLines( extra: string ): string[] {
|
|
|
160
169
|
|
|
161
170
|
function letterLines( layout: KbLayout, upper: boolean ): string[] {
|
|
162
171
|
let [l1, l2, l3] = kb_bases[layout.base];
|
|
163
|
-
let extra = extraLines( layout.extra );
|
|
164
172
|
|
|
165
173
|
if( upper ) {
|
|
166
174
|
l1 = upperKeys( l1 );
|
|
167
175
|
l2 = upperKeys( l2 );
|
|
168
176
|
l3 = upperKeys( l3 );
|
|
169
|
-
extra = extra.map( upperKeys );
|
|
170
177
|
}
|
|
171
178
|
|
|
172
179
|
return [
|
|
@@ -174,11 +181,64 @@ function letterLines( layout: KbLayout, upper: boolean ): string[] {
|
|
|
174
181
|
l1 + " {2}",
|
|
175
182
|
l2 + " {3}",
|
|
176
183
|
"{4} " + l3 + ( upper ? " ? : {4}" : " , . {4}" ),
|
|
184
|
+
bottomLine( layout, "alpha" ),
|
|
185
|
+
];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* the page called by {10}: the extra letters
|
|
190
|
+
*/
|
|
191
|
+
|
|
192
|
+
function accentLines( layout: KbLayout, upper: boolean ): string[] {
|
|
193
|
+
let extra = extraLines( layout.extra );
|
|
194
|
+
if( upper ) {
|
|
195
|
+
extra = extra.map( upperKeys );
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return [
|
|
177
199
|
...extra,
|
|
178
|
-
"{
|
|
200
|
+
"{4} {2} {3}",
|
|
201
|
+
bottomLine( layout, "accents" ),
|
|
179
202
|
];
|
|
180
203
|
}
|
|
181
204
|
|
|
205
|
+
/**
|
|
206
|
+
* the page called by {11}: the signs
|
|
207
|
+
*/
|
|
208
|
+
|
|
209
|
+
function symbolLines( layout: KbLayout ): string[] {
|
|
210
|
+
const [s1, s2, s3] = kb_symbols;
|
|
211
|
+
|
|
212
|
+
return [
|
|
213
|
+
s1 + " {2}",
|
|
214
|
+
s2 + " {3}",
|
|
215
|
+
s3,
|
|
216
|
+
bottomLine( layout, "symbols" ),
|
|
217
|
+
];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* the last row of the pages of letters: the key of the page that is shown
|
|
222
|
+
* is replaced by {8} at its place, to come back to the letters.
|
|
223
|
+
* no key of the extra letters when the language has none
|
|
224
|
+
*/
|
|
225
|
+
|
|
226
|
+
function bottomLine( layout: KbLayout, page: "alpha" | "accents" | "symbols" ): string {
|
|
227
|
+
const accents = !layout.extra ? "" : page=="accents" ? "{8} " : "{10} ";
|
|
228
|
+
const symbols = page=="symbols" ? "{8} " : "{11} ";
|
|
229
|
+
|
|
230
|
+
return "{5} " + accents + symbols + "{6} ' {7}";
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* the text of the key that calls the extra letters: the first two of them
|
|
235
|
+
*/
|
|
236
|
+
|
|
237
|
+
function accentLabel( layout: KbLayout, upper: boolean ): string {
|
|
238
|
+
const label = layout.extra.split( " " ).slice( 0, 2 ).join( " " );
|
|
239
|
+
return ( upper ? upperKeys( label ) : label ).replace( " ", "" );
|
|
240
|
+
}
|
|
241
|
+
|
|
182
242
|
|
|
183
243
|
const RE_sel = /text|password|search|tel|url/i;
|
|
184
244
|
|
|
@@ -195,13 +255,17 @@ interface KeyboardProps extends BoxProps {
|
|
|
195
255
|
* ```
|
|
196
256
|
* --keyboard-key-background
|
|
197
257
|
* --keyboard-hilite-color
|
|
258
|
+
* --keyboard-key-color
|
|
259
|
+
* --keyboard-accents-color
|
|
260
|
+
* --keyboard-symbols-color
|
|
198
261
|
* --keyboard-zoom
|
|
199
262
|
* ```
|
|
200
263
|
*/
|
|
201
264
|
@class_ns( "x4" )
|
|
202
265
|
export class Keyboard extends HBox<KeyboardProps>
|
|
203
266
|
{
|
|
204
|
-
mode:
|
|
267
|
+
mode: KbMode;
|
|
268
|
+
upper: boolean;
|
|
205
269
|
locale: string;
|
|
206
270
|
keyboard: Box;
|
|
207
271
|
visible: boolean;
|
|
@@ -210,7 +274,8 @@ export class Keyboard extends HBox<KeyboardProps>
|
|
|
210
274
|
constructor( props: KeyboardProps ) {
|
|
211
275
|
super( { ...props, id: 'v-keyboard' } );
|
|
212
276
|
|
|
213
|
-
this.mode = '
|
|
277
|
+
this.mode = 'alpha';
|
|
278
|
+
this.upper = false;
|
|
214
279
|
this.locale = props.locale ?? 'fr-FR';
|
|
215
280
|
this.visible = false;
|
|
216
281
|
|
|
@@ -255,6 +320,11 @@ export class Keyboard extends HBox<KeyboardProps>
|
|
|
255
320
|
setLocale( locale: string ) {
|
|
256
321
|
this.locale = locale;
|
|
257
322
|
|
|
323
|
+
// a language without extra letters has no page for them
|
|
324
|
+
if( this.mode=="accents" && !findLayout( locale ).extra ) {
|
|
325
|
+
this.mode = "alpha";
|
|
326
|
+
}
|
|
327
|
+
|
|
258
328
|
// drawn at least once
|
|
259
329
|
if( this.keyboard ) {
|
|
260
330
|
this._redraw( );
|
|
@@ -301,15 +371,9 @@ export class Keyboard extends HBox<KeyboardProps>
|
|
|
301
371
|
|
|
302
372
|
// shift
|
|
303
373
|
case 4: {
|
|
304
|
-
|
|
305
|
-
this.mode = 'upper';
|
|
306
|
-
}
|
|
307
|
-
else {
|
|
308
|
-
this.mode = 'lower';
|
|
309
|
-
}
|
|
310
|
-
|
|
374
|
+
this.upper = !this.upper;
|
|
311
375
|
this._redraw( );
|
|
312
|
-
break;
|
|
376
|
+
break;
|
|
313
377
|
}
|
|
314
378
|
|
|
315
379
|
// num + sym
|
|
@@ -331,7 +395,19 @@ export class Keyboard extends HBox<KeyboardProps>
|
|
|
331
395
|
}
|
|
332
396
|
|
|
333
397
|
case 8: {
|
|
334
|
-
this._switchMode( "
|
|
398
|
+
this._switchMode( "alpha" );
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// extra letters
|
|
403
|
+
case 10: {
|
|
404
|
+
this._switchMode( "accents" );
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// signs
|
|
409
|
+
case 11: {
|
|
410
|
+
this._switchMode( "symbols" );
|
|
335
411
|
break;
|
|
336
412
|
}
|
|
337
413
|
|
|
@@ -354,7 +430,7 @@ export class Keyboard extends HBox<KeyboardProps>
|
|
|
354
430
|
*
|
|
355
431
|
*/
|
|
356
432
|
|
|
357
|
-
private _switchMode( m:
|
|
433
|
+
private _switchMode( m: KbMode ) {
|
|
358
434
|
this.mode = m;
|
|
359
435
|
this._redraw( );
|
|
360
436
|
}
|
|
@@ -421,7 +497,8 @@ export class Keyboard extends HBox<KeyboardProps>
|
|
|
421
497
|
this._switchMode( "date");
|
|
422
498
|
}
|
|
423
499
|
else {
|
|
424
|
-
this.
|
|
500
|
+
this.upper = false;
|
|
501
|
+
this._switchMode( "alpha" );
|
|
425
502
|
}
|
|
426
503
|
}
|
|
427
504
|
}
|
|
@@ -546,11 +623,19 @@ export class Keyboard extends HBox<KeyboardProps>
|
|
|
546
623
|
|
|
547
624
|
private _createContent( ) {
|
|
548
625
|
let lines: string[];
|
|
626
|
+
const layout = findLayout( this.locale );
|
|
627
|
+
|
|
549
628
|
if( this.mode=="number" || this.mode=="date" ) {
|
|
550
629
|
lines = kb_pads[this.mode];
|
|
551
630
|
}
|
|
631
|
+
else if( this.mode=="accents" ) {
|
|
632
|
+
lines = accentLines( layout, this.upper );
|
|
633
|
+
}
|
|
634
|
+
else if( this.mode=="symbols" ) {
|
|
635
|
+
lines = symbolLines( layout );
|
|
636
|
+
}
|
|
552
637
|
else {
|
|
553
|
-
lines = letterLines(
|
|
638
|
+
lines = letterLines( layout, this.upper );
|
|
554
639
|
}
|
|
555
640
|
|
|
556
641
|
let result: Box[] = [];
|
|
@@ -643,8 +728,22 @@ export class Keyboard extends HBox<KeyboardProps>
|
|
|
643
728
|
|
|
644
729
|
case 9:
|
|
645
730
|
{
|
|
646
|
-
content = '';
|
|
647
|
-
cls += ' cplace';
|
|
731
|
+
content = '';
|
|
732
|
+
cls += ' cplace';
|
|
733
|
+
break;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
case 10:
|
|
737
|
+
{
|
|
738
|
+
content = accentLabel( layout, this.upper );
|
|
739
|
+
cls += ' caccents';
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
case 11:
|
|
744
|
+
{
|
|
745
|
+
content = '#+=';
|
|
746
|
+
cls += ' csymbols';
|
|
648
747
|
break;
|
|
649
748
|
}
|
|
650
749
|
}
|