x4js 2.3.17 → 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 +211 -47
- 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 +352 -176
- package/src/components/input/input.ts +63 -38
- package/src/components/keyboard/keyboard.module.scss +35 -4
- package/src/components/keyboard/keyboard.ts +295 -80
- 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 +167 -132
- package/src/components/switch/switch.ts +2 -1
- package/src/components/tag/tag.ts +1 -2
- package/src/components/textarea/textarea.module.scss +6 -2
- package/src/components/textarea/textarea.ts +50 -11
- package/src/components/textedit/textedit.ts +12 -2
- package/src/core/component.ts +16 -9
- package/src/core/core_application.ts +8 -4
- package/src/core/core_clipboard.ts +239 -0
- package/src/core/core_data.ts +118 -49
- package/src/core/core_dom.ts +36 -6
- package/src/core/core_dragdrop.ts +11 -5
- package/src/core/core_element.ts +1 -1
- package/src/core/core_pdf.ts +696 -46
- package/src/core/core_router.ts +42 -44
- package/src/core/core_shortcuts.ts +1 -1
- package/src/core/core_state.ts +22 -5
- package/src/x4.d.ts +1 -0
- package/src/x4.ts +1 -0
package/cli/dev.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { parseArgs } from "node:util";
|
|
|
5
5
|
import esbuild from "esbuild";
|
|
6
6
|
import { DEFAULT_CONFIG_FILE, expandEnv, loadConfig, loadEnvFile, resolveConfigFile, resolveTls } from "./config.mjs";
|
|
7
7
|
import { createBuildOptions } from "./build-options.mjs";
|
|
8
|
-
import { info, success, failure } from "./log.mjs";
|
|
8
|
+
import { info, success, failure, warning } from "./log.mjs";
|
|
9
9
|
|
|
10
10
|
function openBrowser(url) {
|
|
11
11
|
let command;
|
|
@@ -28,6 +28,9 @@ function openBrowser(url) {
|
|
|
28
28
|
detached: true,
|
|
29
29
|
stdio: "ignore",
|
|
30
30
|
});
|
|
31
|
+
|
|
32
|
+
// no browser could be started (xdg-open missing...): the server goes on
|
|
33
|
+
child.on("error", (error) => warning(`cannot open the browser: ${error.message}`));
|
|
31
34
|
child.unref();
|
|
32
35
|
}
|
|
33
36
|
|
package/cli/less-plugin.mjs
CHANGED
|
@@ -28,7 +28,11 @@ export function lessPlugin(projectRoot) {
|
|
|
28
28
|
setup(build) {
|
|
29
29
|
build.onLoad({ filter: /\.less$/ }, async (args) => {
|
|
30
30
|
const source = await fs.readFile(args.path, "utf8");
|
|
31
|
-
|
|
31
|
+
// not kept when it fails: less can be installed while dev runs
|
|
32
|
+
lessPromise ??= loadLess(projectRoot).catch((error) => {
|
|
33
|
+
lessPromise = undefined;
|
|
34
|
+
throw error;
|
|
35
|
+
});
|
|
32
36
|
const less = await lessPromise;
|
|
33
37
|
|
|
34
38
|
const result = await less.render(source, {
|
package/cli/main.mjs
CHANGED
|
@@ -25,17 +25,27 @@ Usage:
|
|
|
25
25
|
x4js build [--config <file>] [--env <file>] [--debug]
|
|
26
26
|
|
|
27
27
|
Options:
|
|
28
|
-
--config <file>
|
|
29
|
-
--env <file>
|
|
30
|
-
-
|
|
31
|
-
-
|
|
28
|
+
--config <file> Config file (default: x4.config.json)
|
|
29
|
+
--env <file> File of NAME=value lines, for $NAME in the config
|
|
30
|
+
-t, --template <name> create: template to use (default: app)
|
|
31
|
+
--no-install create: do not run npm install
|
|
32
|
+
--host <host> dev: address to listen on (default: 127.0.0.1)
|
|
33
|
+
--port <port> dev: port to listen on (default: the first free port from 8000)
|
|
34
|
+
--http, --https dev: force the protocol, whatever the config says
|
|
35
|
+
--open dev: open the browser once the server is ready
|
|
36
|
+
--debug build: not minified, with source maps
|
|
37
|
+
-h, --help Show help
|
|
38
|
+
-v, --version Show version`);
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
const version = getVersion();
|
|
35
42
|
const [command, ...argv] = process.argv.slice(2);
|
|
36
43
|
|
|
44
|
+
// x4js <command> --help: the help, that gives the options of every command
|
|
45
|
+
const help = argv.includes("--help") || argv.includes("-h");
|
|
46
|
+
|
|
37
47
|
try {
|
|
38
|
-
switch (command) {
|
|
48
|
+
switch (help ? "help" : command) {
|
|
39
49
|
case "create": {
|
|
40
50
|
const { create } = await import("./create.mjs");
|
|
41
51
|
await create(argv, { version });
|
package/cli/rawfile-plugin.mjs
CHANGED
|
@@ -8,15 +8,35 @@ export function rawFilePlugin({ filter, mime }) {
|
|
|
8
8
|
name: "x4-raw-file",
|
|
9
9
|
|
|
10
10
|
setup(build) {
|
|
11
|
-
build.onResolve({ filter }, (args) => {
|
|
11
|
+
build.onResolve({ filter }, async (args) => {
|
|
12
12
|
// Seulement les imports JS/TS.
|
|
13
|
+
// pluginData: the call of build.resolve below, which esbuild resolves itself
|
|
13
14
|
if (
|
|
14
|
-
args.kind !== "import-statement" &&
|
|
15
|
-
args.kind !== "dynamic-import"
|
|
15
|
+
(args.kind !== "import-statement" &&
|
|
16
|
+
args.kind !== "dynamic-import") ||
|
|
17
|
+
args.pluginData === namespace
|
|
16
18
|
) {
|
|
17
19
|
return;
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
// a package: esbuild finds it in node_modules. Its file is read here
|
|
23
|
+
// too, in base64: Icon inserts the svg of the url as it is, a "%23"
|
|
24
|
+
// of a url in text would stay in it
|
|
25
|
+
if (!args.path.startsWith(".") && !path.isAbsolute(args.path)) {
|
|
26
|
+
const found = await build.resolve(args.path, {
|
|
27
|
+
kind: args.kind,
|
|
28
|
+
resolveDir: args.resolveDir,
|
|
29
|
+
importer: args.importer,
|
|
30
|
+
pluginData: namespace,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (found.errors.length) {
|
|
34
|
+
return { errors: found.errors };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { path: found.path, namespace };
|
|
38
|
+
}
|
|
39
|
+
|
|
20
40
|
return {
|
|
21
41
|
path: path.resolve(args.resolveDir, args.path),
|
|
22
42
|
namespace,
|
package/cli/templates.mjs
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
1
2
|
import { fetchTemplates } from "./templates-source.mjs";
|
|
2
3
|
import { color } from "./log.mjs";
|
|
3
4
|
|
|
4
|
-
export async function templates(
|
|
5
|
+
export async function templates(argv = [], { version = "unknown" } = {}) {
|
|
6
|
+
// takes no argument: one given is an error
|
|
7
|
+
parseArgs({ args: argv, options: {}, strict: true });
|
|
8
|
+
|
|
5
9
|
const manifest = await fetchTemplates(version);
|
|
6
10
|
const names = Object.keys(manifest).sort();
|
|
7
11
|
const width = names.reduce((max, name) => Math.max(max, name.length), 0) + 3;
|
package/package.json
CHANGED
|
@@ -41,7 +41,10 @@
|
|
|
41
41
|
.x4calendar {
|
|
42
42
|
border: 1px solid var( --border );
|
|
43
43
|
padding: 4px;
|
|
44
|
-
|
|
44
|
+
|
|
45
|
+
// too short for its weeks (6 in some months): what does not fit is cut, not drawn over what follows
|
|
46
|
+
overflow: hidden;
|
|
47
|
+
|
|
45
48
|
.month-sel {
|
|
46
49
|
height: 2em;
|
|
47
50
|
align-items: center;
|
|
@@ -147,6 +150,18 @@
|
|
|
147
150
|
color: var( --calendard-day-color-hover );
|
|
148
151
|
}
|
|
149
152
|
}
|
|
153
|
+
|
|
154
|
+
// out of minDate..maxDate: cannot be chosen
|
|
155
|
+
.disabled {
|
|
156
|
+
color: var( --calendar-out-color );
|
|
157
|
+
text-decoration: line-through;
|
|
158
|
+
cursor: default;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.disabled:hover .text {
|
|
162
|
+
background-color: transparent;
|
|
163
|
+
color: var( --calendar-out-color );
|
|
164
|
+
}
|
|
150
165
|
}
|
|
151
166
|
|
|
152
167
|
.header {
|
|
@@ -78,7 +78,7 @@ export class Calendar extends VBox<CalendarProps, CalendarEventMap>
|
|
|
78
78
|
super(props);
|
|
79
79
|
|
|
80
80
|
this.mapPropEvents( props, 'change' );
|
|
81
|
-
this.m_date = props.date ? date_clone( props.date ) : new Date();
|
|
81
|
+
this.m_date = this._clamp( props.date ? date_clone( props.date ) : new Date() );
|
|
82
82
|
|
|
83
83
|
this._update( );
|
|
84
84
|
}
|
|
@@ -130,7 +130,7 @@ export class Calendar extends VBox<CalendarProps, CalendarEventMap>
|
|
|
130
130
|
}),
|
|
131
131
|
new Flex( ),
|
|
132
132
|
new Button({ icon: icon_prev, click: () => this._next(false) } ),
|
|
133
|
-
new Button({ icon: icon_today, click: () => this.setDate(new Date()), tooltip: _tr.global.today } ),
|
|
133
|
+
new Button({ icon: icon_today, click: () => this.setDate( this._clamp( new Date() ) ), tooltip: _tr.global.today } ),
|
|
134
134
|
new Button({ icon: icon_next, click: () => this._next(true) } )
|
|
135
135
|
]
|
|
136
136
|
});
|
|
@@ -159,6 +159,9 @@ export class Calendar extends VBox<CalendarProps, CalendarEventMap>
|
|
|
159
159
|
}));
|
|
160
160
|
|
|
161
161
|
let cmonth = this.m_date.getMonth();
|
|
162
|
+
const { minDate, maxDate } = this.props;
|
|
163
|
+
const min_hash = minDate ? date_hash( minDate ) : null;
|
|
164
|
+
const max_hash = maxDate ? date_hash( maxDate ) : null;
|
|
162
165
|
|
|
163
166
|
// weeks
|
|
164
167
|
let first = true;
|
|
@@ -184,6 +187,13 @@ export class Calendar extends VBox<CalendarProps, CalendarEventMap>
|
|
|
184
187
|
cls += ' out';
|
|
185
188
|
}
|
|
186
189
|
|
|
190
|
+
// out of minDate..maxDate: shown, not chosen
|
|
191
|
+
const hash = date_hash( dte );
|
|
192
|
+
const off = (min_hash!==null && hash < min_hash) || (max_hash!==null && hash > max_hash);
|
|
193
|
+
if( off ) {
|
|
194
|
+
cls += ' disabled';
|
|
195
|
+
}
|
|
196
|
+
|
|
187
197
|
const mkItem = ( dte: Date ) => {
|
|
188
198
|
return new HBox({
|
|
189
199
|
cls,
|
|
@@ -192,7 +202,7 @@ export class Calendar extends VBox<CalendarProps, CalendarEventMap>
|
|
|
192
202
|
cls: "text",
|
|
193
203
|
content: unsafeHtml( `<span>${formatIntlDate(dte, 'd')}</span>` ),
|
|
194
204
|
}),
|
|
195
|
-
dom_events: {
|
|
205
|
+
dom_events: off ? undefined : {
|
|
196
206
|
click: () => this.select(dte)
|
|
197
207
|
}
|
|
198
208
|
})
|
|
@@ -230,8 +240,40 @@ export class Calendar extends VBox<CalendarProps, CalendarEventMap>
|
|
|
230
240
|
*/
|
|
231
241
|
|
|
232
242
|
private _next(n: boolean) {
|
|
233
|
-
this.m_date.
|
|
234
|
-
|
|
243
|
+
this._goto( this.m_date.getFullYear( ), this.m_date.getMonth() + (n ? 1 : -1) );
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* shows another month: the day is kept, not past the end of the month
|
|
248
|
+
* (31 January → 28 February), and the date stays between minDate and maxDate
|
|
249
|
+
*/
|
|
250
|
+
|
|
251
|
+
private _goto( year: number, month: number ) {
|
|
252
|
+
const day = this.m_date.getDate( );
|
|
253
|
+
const d = date_clone( this.m_date );
|
|
254
|
+
d.setDate( 1 );
|
|
255
|
+
d.setFullYear( year, month ); // month 12: January of the next year
|
|
256
|
+
d.setDate( Math.min( day, new Date( d.getFullYear( ), d.getMonth( )+1, 0 ).getDate( ) ) );
|
|
257
|
+
|
|
258
|
+
this.m_date = this._clamp( d );
|
|
259
|
+
this._update( );
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* the date brought between minDate and maxDate
|
|
264
|
+
*/
|
|
265
|
+
|
|
266
|
+
private _clamp( d: Date ) {
|
|
267
|
+
const { minDate, maxDate } = this.props;
|
|
268
|
+
if( minDate && d < minDate ) {
|
|
269
|
+
return date_clone( minDate );
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if( maxDate && d > maxDate ) {
|
|
273
|
+
return date_clone( maxDate );
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return d;
|
|
235
277
|
}
|
|
236
278
|
|
|
237
279
|
/**
|
|
@@ -246,7 +288,7 @@ export class Calendar extends VBox<CalendarProps, CalendarEventMap>
|
|
|
246
288
|
for (let m = 0; m < 12; m++) {
|
|
247
289
|
items.push(({
|
|
248
290
|
text: _tr.global.month_long[m],
|
|
249
|
-
click: () => { this.
|
|
291
|
+
click: () => { this._goto( this.m_date.getFullYear( ), m ); }
|
|
250
292
|
}));
|
|
251
293
|
}
|
|
252
294
|
}
|
|
@@ -258,7 +300,7 @@ export class Calendar extends VBox<CalendarProps, CalendarEventMap>
|
|
|
258
300
|
for (let m = max; m >= min; m--) {
|
|
259
301
|
items.push({
|
|
260
302
|
text: '' + m,
|
|
261
|
-
click: () => { this.
|
|
303
|
+
click: () => { this._goto( m, this.m_date.getMonth( ) ); }
|
|
262
304
|
});
|
|
263
305
|
}
|
|
264
306
|
}
|
|
@@ -271,12 +313,13 @@ export class Calendar extends VBox<CalendarProps, CalendarEventMap>
|
|
|
271
313
|
menu.displayAt(rc.left, rc.top);
|
|
272
314
|
}
|
|
273
315
|
|
|
316
|
+
// a copy: the calendar and its caller do not share the object
|
|
274
317
|
getDate() {
|
|
275
|
-
return this.m_date;
|
|
318
|
+
return date_clone( this.m_date );
|
|
276
319
|
}
|
|
277
320
|
|
|
278
321
|
setDate(date: Date) {
|
|
279
|
-
this.m_date = date;
|
|
322
|
+
this.m_date = date_clone( date );
|
|
280
323
|
this._update();
|
|
281
324
|
}
|
|
282
325
|
}
|
|
@@ -71,6 +71,7 @@ export class Checkbox extends Component<CheckboxProps,CheckBoxEvents> {
|
|
|
71
71
|
type:"checkbox",
|
|
72
72
|
id: inputId,
|
|
73
73
|
name: props.name,
|
|
74
|
+
value: props.value, // sent by a <form> of the browser when checked; a Form of x4 reads a boolean
|
|
74
75
|
checked: props.checked,
|
|
75
76
|
dom_events: {
|
|
76
77
|
change: ( ) => this._on_change( ),
|
|
@@ -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
|
|