x4js 2.3.18 → 2.3.20
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 +51 -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 +1200 -46
- package/src/core/core_router.ts +42 -44
- package/src/core/core_state.ts +22 -5
|
@@ -181,7 +181,8 @@ private _view: Viewport;
|
|
|
181
181
|
|
|
182
182
|
navigate( sens: kbNav ) {
|
|
183
183
|
|
|
184
|
-
|
|
184
|
+
// an item of id 0 is one
|
|
185
|
+
if( this._lastsel===undefined ) {
|
|
185
186
|
if( sens==kbNav.next ) sens = kbNav.first;
|
|
186
187
|
else sens = kbNav.last;
|
|
187
188
|
}
|
|
@@ -395,6 +396,7 @@ private _view: Viewport;
|
|
|
395
396
|
if( itm ) {
|
|
396
397
|
this._multisel.add( id );
|
|
397
398
|
itm.addClass( "selected" );
|
|
399
|
+
this._lastsel = id; // the arrows go on from it
|
|
398
400
|
}
|
|
399
401
|
});
|
|
400
402
|
|
|
@@ -434,6 +436,9 @@ private _view: Viewport;
|
|
|
434
436
|
}
|
|
435
437
|
|
|
436
438
|
clearSelection( fireEvent = true ) {
|
|
439
|
+
// nothing selected: the arrows start from the first item
|
|
440
|
+
this._lastsel = undefined;
|
|
441
|
+
|
|
437
442
|
if( this._multisel.size ) {
|
|
438
443
|
this._clearSelection( );
|
|
439
444
|
if( fireEvent ) {
|
|
@@ -106,10 +106,13 @@ export class MessageBox extends Dialog<DialogProps>
|
|
|
106
106
|
box.on( "btnclick", ( ev ) => {
|
|
107
107
|
asap( ( ) => {
|
|
108
108
|
resolve( ev.button );
|
|
109
|
-
box.close()
|
|
109
|
+
box.close()
|
|
110
110
|
});
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
+
// closed without a button (Escape, the close box): nothing was chosen
|
|
114
|
+
box.on( "closed", ( ) => resolve( null ) );
|
|
115
|
+
|
|
113
116
|
box.show();
|
|
114
117
|
} );
|
|
115
118
|
|
|
@@ -175,10 +178,13 @@ export class InputBox extends Dialog<DialogProps>
|
|
|
175
178
|
box.on( "btnclick", ( ev ) => {
|
|
176
179
|
asap( ( ) => {
|
|
177
180
|
resolve( ev.button=="ok" ? box.getValue( ) : null );
|
|
178
|
-
box.close()
|
|
181
|
+
box.close()
|
|
179
182
|
});
|
|
180
183
|
});
|
|
181
184
|
|
|
185
|
+
// closed without a button (Escape, the close box): nothing was chosen
|
|
186
|
+
box.on( "closed", ( ) => resolve( null ) );
|
|
187
|
+
|
|
182
188
|
box.show();
|
|
183
189
|
} );
|
|
184
190
|
|
|
@@ -233,10 +239,13 @@ export class PromptBox extends Dialog<DialogProps>
|
|
|
233
239
|
box.on( "btnclick", ( ev ) => {
|
|
234
240
|
asap( ( ) => {
|
|
235
241
|
resolve( ev.button );
|
|
236
|
-
box.close()
|
|
242
|
+
box.close()
|
|
237
243
|
});
|
|
238
244
|
});
|
|
239
245
|
|
|
246
|
+
// closed without a button (Escape, the close box): nothing was chosen
|
|
247
|
+
box.on( "closed", ( ) => resolve( null ) );
|
|
248
|
+
|
|
240
249
|
box.show();
|
|
241
250
|
} );
|
|
242
251
|
}
|
|
@@ -46,6 +46,22 @@ function getRoot( ) {
|
|
|
46
46
|
return document.body;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* on the document while the event goes down (capture): a press outside
|
|
51
|
+
* every popup closes the group opened last, the others at the next presses.
|
|
52
|
+
* The event is left as it is, what is pressed handles it as usual.
|
|
53
|
+
* One function for all the popups: it is removed by the one that empties the list.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
function dismissLast( e: UIEvent ) {
|
|
57
|
+
if( autoclose_list.some( x => x.dom.contains( e.target as Node ) ) ) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// the newest popup: dismiss( ) closes its whole group (a menu and its sub-menus)
|
|
62
|
+
autoclose_list[autoclose_list.length-1].dismiss( );
|
|
63
|
+
}
|
|
64
|
+
|
|
49
65
|
/**
|
|
50
66
|
* @cssvar
|
|
51
67
|
* ```
|
|
@@ -233,7 +249,7 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
|
|
|
233
249
|
if( idx>=0 ) {
|
|
234
250
|
autoclose_list.splice( idx, 1 );
|
|
235
251
|
if( autoclose_list.length==0 ) {
|
|
236
|
-
document.removeEventListener( "pointerdown",
|
|
252
|
+
document.removeEventListener( "pointerdown", dismissLast, true );
|
|
237
253
|
}
|
|
238
254
|
}
|
|
239
255
|
}
|
|
@@ -263,7 +279,7 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
|
|
|
263
279
|
|
|
264
280
|
if( this.props.autoClose ) {
|
|
265
281
|
if( autoclose_list.length==0 ) {
|
|
266
|
-
document.addEventListener( "pointerdown",
|
|
282
|
+
document.addEventListener( "pointerdown", dismissLast, true );
|
|
267
283
|
}
|
|
268
284
|
|
|
269
285
|
autoclose_list.push( this );
|
|
@@ -320,23 +336,6 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
|
|
|
320
336
|
this._do_hide( );
|
|
321
337
|
}
|
|
322
338
|
|
|
323
|
-
/**
|
|
324
|
-
* binded
|
|
325
|
-
*
|
|
326
|
-
* listened on the document while the event goes down (capture): a press
|
|
327
|
-
* closes the popups even when what is pressed stops its propagation.
|
|
328
|
-
* the event is left as it is, what is pressed handles it as usual.
|
|
329
|
-
*/
|
|
330
|
-
|
|
331
|
-
private _dismiss = ( e: UIEvent ) => {
|
|
332
|
-
const onac = autoclose_list.some( x=> x.dom.contains(e.target as Node) )
|
|
333
|
-
if( onac ) {
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
this.dismiss( );
|
|
338
|
-
}
|
|
339
|
-
|
|
340
339
|
/**
|
|
341
340
|
* dismiss all popup belonging to the same group as 'this'
|
|
342
341
|
*/
|
|
@@ -369,7 +368,7 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
|
|
|
369
368
|
const list = inc_group.reverse( );
|
|
370
369
|
autoclose_list = excl_group;
|
|
371
370
|
if( autoclose_list.length==0 ) {
|
|
372
|
-
document.removeEventListener( "pointerdown",
|
|
371
|
+
document.removeEventListener( "pointerdown", dismissLast, true );
|
|
373
372
|
}
|
|
374
373
|
|
|
375
374
|
list.forEach( x => x.close() );
|
|
@@ -19,7 +19,7 @@ import { class_ns } from '../../core/core_tools';
|
|
|
19
19
|
import { EventCallback } from '../../core/core_events';
|
|
20
20
|
|
|
21
21
|
import { Label } from '../label/label';
|
|
22
|
-
import { Input } from '../input/input'
|
|
22
|
+
import { Input, getRadioOwner } from '../input/input'
|
|
23
23
|
import { HBox } from '../boxes/boxes';
|
|
24
24
|
|
|
25
25
|
import icons from "../assets/icons"
|
|
@@ -152,10 +152,14 @@ export class Radio extends Component<RadioProps,RadioEvents> {
|
|
|
152
152
|
*/
|
|
153
153
|
|
|
154
154
|
checkValue( vname: string ) {
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
// the radios of the group: in the form that holds this one, else in
|
|
156
|
+
// its dialog, else in the document (as a Form reads them)
|
|
157
|
+
const owner = getRadioOwner( this.dom );
|
|
158
|
+
const selector = `input[name="${this.props.name}"][value="${vname}"]`;
|
|
159
|
+
|
|
160
|
+
const el = owner.querySelector( selector ) as HTMLInputElement;
|
|
157
161
|
if( el ) {
|
|
158
|
-
el.
|
|
162
|
+
el.checked = true;
|
|
159
163
|
}
|
|
160
164
|
}
|
|
161
165
|
}
|
|
@@ -14,12 +14,11 @@
|
|
|
14
14
|
* that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
|
|
15
15
|
**/
|
|
16
16
|
|
|
17
|
-
import { Component, ComponentEvents,
|
|
17
|
+
import { Component, ComponentEvents, ComponentProps, EvChange } from '../../core/component';
|
|
18
18
|
import { EventCallback } from '../../core/core_events';
|
|
19
19
|
import { HBox } from '../boxes/boxes';
|
|
20
|
-
import { Input } from '../input/input';
|
|
21
20
|
import { Icon } from '../icon/icon';
|
|
22
|
-
import { class_ns } from '../../core/core_tools';
|
|
21
|
+
import { class_ns, IComponentInterface, IFormElement } from '../../core/core_tools';
|
|
23
22
|
|
|
24
23
|
import icons from "../assets/icons"
|
|
25
24
|
|
|
@@ -54,50 +53,33 @@ export interface RatingProps extends ComponentProps {
|
|
|
54
53
|
export class Rating extends HBox<RatingProps,RatingEventMap> {
|
|
55
54
|
|
|
56
55
|
private m_els: Component[];
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
|
|
59
57
|
constructor( props: RatingProps ) {
|
|
60
58
|
super( props );
|
|
61
59
|
|
|
62
|
-
props
|
|
60
|
+
// this.props: a copy of props (Box adds its tag)
|
|
61
|
+
this.props.steps = this.props.steps ?? 5;
|
|
62
|
+
|
|
63
|
+
// the name is on the rating: a Form finds it, and reads its value as a number (queryInterface)
|
|
64
|
+
if( props.name ) {
|
|
65
|
+
this.setAttribute( "name", props.name );
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.addDOMEvent( 'click', (e) => this._on_click(e) );
|
|
63
69
|
this._update( );
|
|
64
70
|
}
|
|
65
71
|
|
|
72
|
+
// value: the count of stars that are lit, 0 for none
|
|
66
73
|
private _update( ) {
|
|
67
|
-
|
|
68
74
|
const props = this.props;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
let value = props.value ?? 0;
|
|
72
|
-
|
|
73
|
-
this.m_input = new Input( {
|
|
74
|
-
type: "text",
|
|
75
|
-
hidden: true,
|
|
76
|
-
name: props.name,
|
|
77
|
-
value: ''+value
|
|
78
|
-
} );
|
|
79
|
-
|
|
80
|
-
this.addDOMEvent( 'click', (e) => this._on_click(e) );
|
|
75
|
+
const shape = props.icon ?? icons.star.solid;
|
|
76
|
+
const value = props.value ?? 0;
|
|
81
77
|
|
|
82
78
|
this.m_els = [];
|
|
83
79
|
for( let i=0; i<props.steps; i++ ) {
|
|
84
|
-
|
|
85
|
-
let cls = 'item';
|
|
86
|
-
if( i+1 <= value ) {
|
|
87
|
-
cls += ' checked';
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
let c = new Icon( {
|
|
91
|
-
cls,
|
|
92
|
-
iconId: shape,
|
|
93
|
-
} );
|
|
94
|
-
|
|
95
|
-
c.setInternalData( "value", i );
|
|
96
|
-
|
|
97
|
-
this.m_els.push( c );
|
|
80
|
+
this.m_els.push( new Icon( { cls: i < value ? 'item checked' : 'item', iconId: shape } ) );
|
|
98
81
|
}
|
|
99
82
|
|
|
100
|
-
this.m_els.push( this.m_input );
|
|
101
83
|
this.setContent( this.m_els );
|
|
102
84
|
}
|
|
103
85
|
|
|
@@ -107,12 +89,7 @@ export class Rating extends HBox<RatingProps,RatingEventMap> {
|
|
|
107
89
|
|
|
108
90
|
setValue( v: number ) {
|
|
109
91
|
this.props.value = v;
|
|
110
|
-
|
|
111
|
-
for( let c=0; c<this.props.steps; c++ ) {
|
|
112
|
-
this.m_els[c].setClass( 'checked', this.m_els[c].getInternalData('value')<=v );
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
this.m_input.setValue( ''+this.props.value );
|
|
92
|
+
this.m_els.forEach( ( c, i ) => c.setClass( 'checked', i < v ) );
|
|
116
93
|
}
|
|
117
94
|
|
|
118
95
|
setSteps( n: number ) {
|
|
@@ -121,18 +98,31 @@ export class Rating extends HBox<RatingProps,RatingEventMap> {
|
|
|
121
98
|
}
|
|
122
99
|
|
|
123
100
|
setShape( icon: string ) {
|
|
124
|
-
this.removeClass( this.props.icon );
|
|
125
101
|
this.props.icon = icon;
|
|
102
|
+
this._update( );
|
|
126
103
|
}
|
|
127
104
|
|
|
105
|
+
// the star clicked: its svg, or the Icon itself
|
|
128
106
|
private _on_click( ev: MouseEvent ) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
this.
|
|
107
|
+
const n = this.m_els.findIndex( c => c.dom.contains( ev.target as Node ) );
|
|
108
|
+
if( n>=0 ) {
|
|
109
|
+
// the star of the value, clicked again: none is lit
|
|
110
|
+
this.setValue( n+1===this.props.value ? 0 : n+1 );
|
|
111
|
+
this.fire( 'change', { value: this.props.value } );
|
|
134
112
|
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
override queryInterface<T extends IComponentInterface>( name: string ): T {
|
|
116
|
+
if( name=="form-element" ) {
|
|
117
|
+
const i: IFormElement = {
|
|
118
|
+
getRawValue: ( ) => this.getValue( ),
|
|
119
|
+
setRawValue: ( v: any ) => { this.setValue( +v ); },
|
|
120
|
+
isValid: ( ) => true,
|
|
121
|
+
};
|
|
135
122
|
|
|
136
|
-
|
|
137
|
-
|
|
123
|
+
return i as unknown as T;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return super.queryInterface( name );
|
|
127
|
+
}
|
|
138
128
|
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import { EventCallback } from '../../core/core_events';
|
|
18
18
|
import { Component, ComponentEvents, ComponentProps, EvChange } from '../../core/component';
|
|
19
|
-
import { class_ns, Rect } from '../../core/core_tools';
|
|
19
|
+
import { class_ns, IComponentInterface, IFormElement, Rect } from '../../core/core_tools';
|
|
20
20
|
|
|
21
21
|
import { HBox } from '../boxes/boxes';
|
|
22
22
|
import { Input } from '../input/input';
|
|
@@ -33,6 +33,8 @@ interface SliderProps extends ComponentProps {
|
|
|
33
33
|
max: number;
|
|
34
34
|
step?: number;
|
|
35
35
|
vertical?: boolean;
|
|
36
|
+
/** for a Form: it reads the value as a number */
|
|
37
|
+
name?: string;
|
|
36
38
|
change?: EventCallback<EvChange>;
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -83,6 +85,12 @@ export class Slider extends Component<SliderProps,SliderEvents> {
|
|
|
83
85
|
|
|
84
86
|
this.setAttribute( "tabindex", 0 );
|
|
85
87
|
|
|
88
|
+
// the name is on the slider: a Form finds it (queryInterface). The hidden range keeps
|
|
89
|
+
// the value: the browser brings it between min and max, on the step
|
|
90
|
+
if( props.name ) {
|
|
91
|
+
this.setAttribute( "name", props.name );
|
|
92
|
+
}
|
|
93
|
+
|
|
86
94
|
this.setDOMEvents( {
|
|
87
95
|
pointerdown: ( ev ) => this._on_mousedown( ev ),
|
|
88
96
|
pointermove: ( ev ) => this._on_mousemove( ev ),
|
|
@@ -106,8 +114,8 @@ export class Slider extends Component<SliderProps,SliderEvents> {
|
|
|
106
114
|
}
|
|
107
115
|
|
|
108
116
|
private _getMinMax( ) {
|
|
109
|
-
const min =
|
|
110
|
-
const max =
|
|
117
|
+
const min = parseFloat( this._range.getAttribute( 'min' ) );
|
|
118
|
+
const max = parseFloat( this._range.getAttribute( 'max' ) );
|
|
111
119
|
return [min,max];
|
|
112
120
|
}
|
|
113
121
|
|
|
@@ -134,15 +142,17 @@ export class Slider extends Component<SliderProps,SliderEvents> {
|
|
|
134
142
|
const [min,max] = this._getMinMax( );
|
|
135
143
|
|
|
136
144
|
this._range.setNumValue( min + (perc * (max-min) ) );
|
|
137
|
-
this._update( );
|
|
145
|
+
this._update( true );
|
|
138
146
|
}
|
|
139
147
|
}
|
|
140
148
|
|
|
141
|
-
|
|
149
|
+
// notify: a change made by the user (drag, arrows); not by the code, nor at the construction
|
|
150
|
+
private _update( notify = false ) {
|
|
142
151
|
const value = this._range.getNumValue( );
|
|
143
152
|
const [min,max] = this._getMinMax( );
|
|
144
153
|
|
|
145
|
-
|
|
154
|
+
// from min: min is at 0%, max at 100%
|
|
155
|
+
let perc = max>min ? (value-min) / (max-min) * 100 : 0;
|
|
146
156
|
|
|
147
157
|
if( !this.props.vertical ) {
|
|
148
158
|
this._thumb.setStyleValue( "left", perc+"%" );
|
|
@@ -155,7 +165,9 @@ export class Slider extends Component<SliderProps,SliderEvents> {
|
|
|
155
165
|
this._bar.setStyleValue( "height", perc+"%" );
|
|
156
166
|
}
|
|
157
167
|
|
|
158
|
-
|
|
168
|
+
if( notify ) {
|
|
169
|
+
this.fire( "change", { value } );
|
|
170
|
+
}
|
|
159
171
|
}
|
|
160
172
|
|
|
161
173
|
private _on_mouseup( ev: PointerEvent ) {
|
|
@@ -184,7 +196,7 @@ export class Slider extends Component<SliderProps,SliderEvents> {
|
|
|
184
196
|
}
|
|
185
197
|
|
|
186
198
|
this._range.setNumValue( this._range.getNumValue()+inc );
|
|
187
|
-
this._update( );
|
|
199
|
+
this._update( true );
|
|
188
200
|
}
|
|
189
201
|
}
|
|
190
202
|
|
|
@@ -202,4 +214,22 @@ export class Slider extends Component<SliderProps,SliderEvents> {
|
|
|
202
214
|
this._range.setNumValue( v );
|
|
203
215
|
this._update( );
|
|
204
216
|
}
|
|
217
|
+
|
|
218
|
+
getValue( ) {
|
|
219
|
+
return this._range.getNumValue( );
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
override queryInterface<T extends IComponentInterface>( name: string ): T {
|
|
223
|
+
if( name=="form-element" ) {
|
|
224
|
+
const i: IFormElement = {
|
|
225
|
+
getRawValue: ( ) => this.getValue( ),
|
|
226
|
+
setRawValue: ( v: any ) => { this.setValue( +v ); },
|
|
227
|
+
isValid: ( ) => true,
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
return i as unknown as T;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return super.queryInterface( name );
|
|
234
|
+
}
|
|
205
235
|
}
|