x4js 2.3.13 → 2.3.15
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 +50 -13
- package/package.json +1 -1
- package/src/components/colorinput/colorinput.module.scss +36 -2
- package/src/components/colorinput/colorinput.ts +205 -32
- package/src/components/colorpicker/colorpicker.module.scss +23 -0
- package/src/components/colorpicker/colorpicker.ts +149 -35
- package/src/components/popup/popup.ts +7 -6
- package/src/components/propgrid/progrid.module.scss +49 -31
- package/src/components/propgrid/propgrid.ts +36 -3
- package/src/core/component.ts +52 -6
- package/src/core/core_application.ts +33 -0
- package/src/core/core_shortcuts.ts +181 -0
- package/src/core/core_state.ts +7 -3
- package/src/core/core_tools.ts +1 -1
- package/src/x4.d.ts +1 -0
- package/src/x4.ts +1 -0
|
@@ -17,13 +17,19 @@
|
|
|
17
17
|
import { Color, Hsv } from '../../core/core_colors';
|
|
18
18
|
import { Rect, clamp, class_ns, isFeatureAvailable } from '../../core/core_tools';
|
|
19
19
|
|
|
20
|
-
import { Component, ComponentEvent, ComponentEvents, ComponentProps } from '../../core/component';
|
|
20
|
+
import { Component, ComponentEvent, ComponentEvents, ComponentProps, EvChange } from '../../core/component';
|
|
21
21
|
import { Box, BoxProps, HBox, VBox } from '../boxes/boxes';
|
|
22
22
|
|
|
23
23
|
import "./colorpicker.module.scss"
|
|
24
24
|
|
|
25
|
-
interface ColorPickerProps extends ComponentProps {
|
|
25
|
+
export interface ColorPickerProps extends ComponentProps {
|
|
26
26
|
color: string | Color;
|
|
27
|
+
|
|
28
|
+
/** Colors offered under the sliders, to be picked with a click. */
|
|
29
|
+
swatches?: ( string | Color )[];
|
|
30
|
+
|
|
31
|
+
/** Adds a "no color" swatch: picking it fires `change` with a null color. */
|
|
32
|
+
nullable?: boolean;
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
interface HueChangeEvent extends ComponentEvent {
|
|
@@ -127,6 +133,18 @@ export class Saturation extends Box<BoxProps,CommonEvents> {
|
|
|
127
133
|
this.color.setStyleValue( "backgroundColor", base.toRgbString(false) );
|
|
128
134
|
}
|
|
129
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Shows another color: base color and thumb. Does not fire `sat_change`.
|
|
138
|
+
*/
|
|
139
|
+
|
|
140
|
+
setColor( hsv: Hsv ) {
|
|
141
|
+
this.hsv.saturation = hsv.saturation;
|
|
142
|
+
this.hsv.value = hsv.value;
|
|
143
|
+
|
|
144
|
+
this.updateBaseColor( hsv );
|
|
145
|
+
this.updateThumbMarker( );
|
|
146
|
+
}
|
|
147
|
+
|
|
130
148
|
move( sens: string, delta: number ) {
|
|
131
149
|
switch( sens ) {
|
|
132
150
|
case 'saturation': {
|
|
@@ -335,31 +353,41 @@ class AlphaSlider extends Box<BoxProps,CommonEvents> {
|
|
|
335
353
|
*
|
|
336
354
|
*/
|
|
337
355
|
|
|
338
|
-
|
|
356
|
+
/**
|
|
357
|
+
* Fired by ColorPicker and ColorInput when the user changes the color.
|
|
358
|
+
*
|
|
359
|
+
* `value` is the color as text: "#rrggbb", or "#rrggbbaa" when it is not
|
|
360
|
+
* opaque. Both are null when the user picked "no color" (`nullable`).
|
|
361
|
+
*/
|
|
362
|
+
|
|
363
|
+
export interface EvColorChange extends EvChange {
|
|
339
364
|
color: Color;
|
|
365
|
+
readonly value: string;
|
|
340
366
|
}
|
|
341
367
|
|
|
342
368
|
interface ColorPickerChangeEvents extends ComponentEvents {
|
|
343
|
-
change:
|
|
369
|
+
change: EvColorChange
|
|
344
370
|
}
|
|
345
371
|
|
|
346
372
|
/**
|
|
347
|
-
*
|
|
373
|
+
*
|
|
348
374
|
*/
|
|
349
375
|
|
|
350
376
|
@class_ns( "x4" )
|
|
351
377
|
export class ColorPicker extends VBox<ColorPickerProps,ColorPickerChangeEvents> {
|
|
352
378
|
|
|
353
379
|
private _base: Color;
|
|
380
|
+
private _hsv: Hsv;
|
|
354
381
|
private _sat: Saturation;
|
|
355
382
|
private _swatch: Component;
|
|
356
383
|
private _hue: HueSlider;
|
|
357
384
|
private _alpha: AlphaSlider;
|
|
385
|
+
private _swatches: HBox;
|
|
358
386
|
|
|
359
387
|
|
|
360
388
|
constructor( props: ColorPickerProps ) {
|
|
361
389
|
super( props );
|
|
362
|
-
|
|
390
|
+
|
|
363
391
|
if( props.color instanceof Color ) {
|
|
364
392
|
this._base = props.color;
|
|
365
393
|
}
|
|
@@ -367,7 +395,7 @@ export class ColorPicker extends VBox<ColorPickerProps,ColorPickerChangeEvents>
|
|
|
367
395
|
this._base = new Color( props.color );
|
|
368
396
|
}
|
|
369
397
|
|
|
370
|
-
|
|
398
|
+
const hsv = this._hsv = this._base.toHsv( );
|
|
371
399
|
|
|
372
400
|
this.setAttribute( "tabindex", 0 );
|
|
373
401
|
|
|
@@ -385,48 +413,34 @@ export class ColorPicker extends VBox<ColorPickerProps,ColorPickerChangeEvents>
|
|
|
385
413
|
this._swatch = new Component( { cls: "overlay" } ),
|
|
386
414
|
] } )
|
|
387
415
|
]
|
|
388
|
-
})
|
|
416
|
+
}),
|
|
417
|
+
this._swatches = new HBox( { cls: "swatches" } ),
|
|
389
418
|
]);
|
|
390
419
|
|
|
391
420
|
this._sat.on( "sat_change", ( ev ) => {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
this._alpha.updateBaseColor(
|
|
421
|
+
this._hsv.saturation = ev.saturation;
|
|
422
|
+
this._hsv.value = ev.value;
|
|
423
|
+
this._changed( );
|
|
424
|
+
this._alpha.updateBaseColor( this._hsv );
|
|
396
425
|
} );
|
|
397
426
|
|
|
398
427
|
this._hue.on( 'hue_change', ( ev ) => {
|
|
399
|
-
|
|
400
|
-
this._sat.updateBaseColor(
|
|
401
|
-
this._alpha.updateBaseColor(
|
|
402
|
-
|
|
428
|
+
this._hsv.hue = ev.hue;
|
|
429
|
+
this._sat.updateBaseColor( this._hsv );
|
|
430
|
+
this._alpha.updateBaseColor( this._hsv );
|
|
431
|
+
this._changed( );
|
|
403
432
|
} );
|
|
404
433
|
|
|
405
434
|
this._alpha.on( 'alpha_change', ( ev ) => {
|
|
406
|
-
|
|
407
|
-
|
|
435
|
+
this._hsv.alpha = ev.alpha;
|
|
436
|
+
this._changed( );
|
|
408
437
|
} );
|
|
409
438
|
|
|
410
|
-
const updateColor = ( ) => {
|
|
411
|
-
this._base.setHsv( hsv.hue, hsv.saturation, hsv.value, hsv.alpha );
|
|
412
|
-
this._swatch.setStyleValue( "backgroundColor", this._base.toRgbString() );
|
|
413
|
-
this._swatch.setAttribute( "tooltip", this._base.toRgbString() );
|
|
414
|
-
|
|
415
|
-
this.fire( "change", { color: this._base } );
|
|
416
|
-
}
|
|
417
|
-
|
|
418
439
|
if( isFeatureAvailable("eyedropper") ) {
|
|
419
440
|
this._swatch.addDOMEvent( "click", ( e ) => {
|
|
420
441
|
const eyeDropper = new (window as any).EyeDropper();
|
|
421
442
|
eyeDropper.open( ).then( ( result: any ) => {
|
|
422
|
-
|
|
423
|
-
hsv = color.toHsv( );
|
|
424
|
-
|
|
425
|
-
this._alpha.setColor( hsv );
|
|
426
|
-
|
|
427
|
-
this._sat.updateBaseColor( hsv );
|
|
428
|
-
this._hue.updateHue( hsv );
|
|
429
|
-
updateColor( );
|
|
443
|
+
this._pick( new Color( result.sRGBHex ) );
|
|
430
444
|
}).catch( (_: any )=> {
|
|
431
445
|
/* silence */
|
|
432
446
|
} );
|
|
@@ -435,7 +449,107 @@ export class ColorPicker extends VBox<ColorPickerProps,ColorPickerChangeEvents>
|
|
|
435
449
|
|
|
436
450
|
this.addDOMEvent( "keydown", ( ev ) => this._onkey( ev ) );
|
|
437
451
|
|
|
438
|
-
|
|
452
|
+
this.setSwatches( props.swatches );
|
|
453
|
+
this._changed( );
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* the sliders moved: compute the color, show it, tell it
|
|
458
|
+
*/
|
|
459
|
+
|
|
460
|
+
private _changed( ) {
|
|
461
|
+
const hsv = this._hsv;
|
|
462
|
+
|
|
463
|
+
this._base.setHsv( hsv.hue, hsv.saturation, hsv.value, hsv.alpha );
|
|
464
|
+
this._refresh( );
|
|
465
|
+
this._notify( );
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
private _refresh( ) {
|
|
469
|
+
this._swatch.setStyleValue( "backgroundColor", this._base.toRgbString() );
|
|
470
|
+
this._swatch.setAttribute( "tooltip", this._base.toRgbString() );
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
private _notify( ) {
|
|
474
|
+
this.fire( "change", { color: this._base, value: this._base.toHexString() } );
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* the user took a color as a whole (eye dropper, swatch)
|
|
479
|
+
*/
|
|
480
|
+
|
|
481
|
+
private _pick( color: Color ) {
|
|
482
|
+
this.setColor( color );
|
|
483
|
+
this._notify( );
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* The current color. It is the object the `change` event gives, and it
|
|
488
|
+
* changes with the picker: copy it to keep a value.
|
|
489
|
+
*/
|
|
490
|
+
|
|
491
|
+
getColor( ): Color {
|
|
492
|
+
return this._base;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Shows another color. Does not fire `change`.
|
|
497
|
+
*/
|
|
498
|
+
|
|
499
|
+
setColor( color: Color | string ) {
|
|
500
|
+
const rgb = ( color instanceof Color ? color : new Color( color ) ).toRgb( );
|
|
501
|
+
|
|
502
|
+
// the color itself is kept as given: going through hsv could shift it
|
|
503
|
+
this._base.setRgb( rgb.red, rgb.green, rgb.blue, rgb.alpha );
|
|
504
|
+
|
|
505
|
+
const hsv = this._base.toHsv( );
|
|
506
|
+
this._hsv.hue = hsv.hue;
|
|
507
|
+
this._hsv.saturation = hsv.saturation;
|
|
508
|
+
this._hsv.value = hsv.value;
|
|
509
|
+
this._hsv.alpha = hsv.alpha;
|
|
510
|
+
|
|
511
|
+
this._sat.setColor( this._hsv );
|
|
512
|
+
this._hue.updateHue( this._hsv );
|
|
513
|
+
this._alpha.setColor( this._hsv );
|
|
514
|
+
|
|
515
|
+
this._refresh( );
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Colors offered under the sliders. A click on one of them takes it
|
|
520
|
+
* and fires `change`. Nothing is shown without swatches.
|
|
521
|
+
*/
|
|
522
|
+
|
|
523
|
+
setSwatches( swatches: ( string | Color )[] ) {
|
|
524
|
+
const items: Component[] = [];
|
|
525
|
+
|
|
526
|
+
if( this.props.nullable ) {
|
|
527
|
+
items.push( new Component( {
|
|
528
|
+
cls: "item none",
|
|
529
|
+
dom_events: {
|
|
530
|
+
click: ( ) => this.fire( "change", { color: null, value: null } ),
|
|
531
|
+
},
|
|
532
|
+
} ) );
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
for( const swatch of swatches ?? [] ) {
|
|
536
|
+
const color = swatch instanceof Color ? swatch : new Color( swatch );
|
|
537
|
+
if( color.isInvalid( ) ) {
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
items.push( new Component( {
|
|
542
|
+
cls: "item",
|
|
543
|
+
tooltip: color.toHexString( ),
|
|
544
|
+
style: { backgroundColor: color.toRgbString( ) },
|
|
545
|
+
dom_events: {
|
|
546
|
+
click: ( ) => this._pick( color ),
|
|
547
|
+
},
|
|
548
|
+
} ) );
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
this._swatches.setContent( items );
|
|
552
|
+
this._swatches.show( items.length>0 );
|
|
439
553
|
}
|
|
440
554
|
|
|
441
555
|
private _onkey( ev: KeyboardEvent ) {
|
|
@@ -233,7 +233,7 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
|
|
|
233
233
|
if( idx>=0 ) {
|
|
234
234
|
autoclose_list.splice( idx, 1 );
|
|
235
235
|
if( autoclose_list.length==0 ) {
|
|
236
|
-
document.removeEventListener( "pointerdown", this._dismiss );
|
|
236
|
+
document.removeEventListener( "pointerdown", this._dismiss, true );
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
239
|
}
|
|
@@ -263,7 +263,7 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
|
|
|
263
263
|
|
|
264
264
|
if( this.props.autoClose ) {
|
|
265
265
|
if( autoclose_list.length==0 ) {
|
|
266
|
-
document.addEventListener( "pointerdown", this._dismiss );
|
|
266
|
+
document.addEventListener( "pointerdown", this._dismiss, true );
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
autoclose_list.push( this );
|
|
@@ -322,6 +322,10 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
|
|
|
322
322
|
|
|
323
323
|
/**
|
|
324
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.
|
|
325
329
|
*/
|
|
326
330
|
|
|
327
331
|
private _dismiss = ( e: UIEvent ) => {
|
|
@@ -330,9 +334,6 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
|
|
|
330
334
|
return;
|
|
331
335
|
}
|
|
332
336
|
|
|
333
|
-
e.preventDefault( );
|
|
334
|
-
e.stopPropagation( );
|
|
335
|
-
|
|
336
337
|
this.dismiss( );
|
|
337
338
|
}
|
|
338
339
|
|
|
@@ -368,7 +369,7 @@ export class Popup<P extends PopupProps = PopupProps, E extends PopupEvents = Po
|
|
|
368
369
|
const list = inc_group.reverse( );
|
|
369
370
|
autoclose_list = excl_group;
|
|
370
371
|
if( autoclose_list.length==0 ) {
|
|
371
|
-
document.removeEventListener( "pointerdown", this._dismiss );
|
|
372
|
+
document.removeEventListener( "pointerdown", this._dismiss, true );
|
|
372
373
|
}
|
|
373
374
|
|
|
374
375
|
list.forEach( x => x.close() );
|
|
@@ -12,8 +12,12 @@
|
|
|
12
12
|
--propertygrid-odd-background: var(--color-primary-a5);
|
|
13
13
|
--propertygrid-even-background: transparent;
|
|
14
14
|
--propertygrid-disabled-background: rgb(170 170 170 / 14%);
|
|
15
|
+
--propertygrid-hover-background: var(--color-primary-a10);
|
|
16
|
+
--propertygrid-focus-background: var(--color-primary-a20);
|
|
17
|
+
--propertygrid-input-background: rgba(255,255,255,0.6);
|
|
15
18
|
|
|
16
19
|
--propertygrid-group-background: var( --color-primary-a5 );
|
|
20
|
+
--propertygrid-group-color: var( --color-primary-a50 );
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
.x4propertygrid {
|
|
@@ -36,19 +40,23 @@
|
|
|
36
40
|
margin: 0;
|
|
37
41
|
}
|
|
38
42
|
|
|
43
|
+
// Two columns shared by all the rows: the titles, as wide as the longest
|
|
44
|
+
// of them but never more than half of the grid, and the editors, which
|
|
45
|
+
// take the rest.
|
|
39
46
|
.root {
|
|
40
47
|
display: grid;
|
|
41
|
-
grid-template-columns: 1fr;
|
|
48
|
+
grid-template-columns: fit-content( 50% ) 1fr;
|
|
42
49
|
grid-template-rows: auto;
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
.group {
|
|
53
|
+
grid-column: 1 / -1;
|
|
46
54
|
padding: 6px 4px 4px 4px;
|
|
47
55
|
min-height: 2em;
|
|
48
56
|
background-color: var( --propertygrid-group-background );
|
|
49
57
|
margin-bottom: 8px;
|
|
50
58
|
box-shadow: 4px 3px 8px rgba(0, 0, 0, 0.4);
|
|
51
|
-
color: var(--
|
|
59
|
+
color: var( --propertygrid-group-color );
|
|
52
60
|
display: flex;
|
|
53
61
|
align-items: center;
|
|
54
62
|
padding: 8px;
|
|
@@ -75,14 +83,14 @@
|
|
|
75
83
|
|
|
76
84
|
&>.x4button {
|
|
77
85
|
background-color: transparent;
|
|
78
|
-
color: var(--
|
|
86
|
+
color: var( --propertygrid-group-color );
|
|
79
87
|
}
|
|
80
|
-
|
|
88
|
+
|
|
81
89
|
&>.x4label {
|
|
82
90
|
.x4icon {
|
|
83
91
|
.fa-secondary,
|
|
84
92
|
.fa-primary {
|
|
85
|
-
fill: var( --
|
|
93
|
+
fill: var( --propertygrid-group-color );
|
|
86
94
|
}
|
|
87
95
|
|
|
88
96
|
width: 1em;
|
|
@@ -103,52 +111,62 @@
|
|
|
103
111
|
}
|
|
104
112
|
|
|
105
113
|
&:not( :last-child ) {
|
|
106
|
-
border-bottom: 1px solid var(--
|
|
114
|
+
border-bottom: 1px solid var( --propertygrid-group-color );
|
|
107
115
|
//box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.2);
|
|
108
116
|
}
|
|
109
117
|
}
|
|
110
118
|
|
|
119
|
+
// A row has no box of its own: its two cells go straight on the columns
|
|
120
|
+
// of the grid, which is how all the titles get the same width. (A row is
|
|
121
|
+
// a fieldset: it cannot be a subgrid.) What a row looked like, background
|
|
122
|
+
// and bottom line, is carried by its cells.
|
|
111
123
|
.row {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
124
|
+
display: contents;
|
|
125
|
+
|
|
126
|
+
> .cell {
|
|
127
|
+
min-width: 0;
|
|
128
|
+
padding: 4px;
|
|
129
|
+
border-bottom: 1px solid var( --border-light );
|
|
130
|
+
|
|
131
|
+
input {
|
|
132
|
+
background: var( --propertygrid-input-background );
|
|
133
|
+
}
|
|
134
|
+
}
|
|
116
135
|
|
|
117
|
-
&.even:not(:disabled) {
|
|
136
|
+
&.even:not(:disabled) > .cell {
|
|
118
137
|
background-color: var(--propertygrid-even-background)
|
|
119
138
|
}
|
|
120
|
-
|
|
121
|
-
&.odd:not(:disabled) {
|
|
139
|
+
|
|
140
|
+
&.odd:not(:disabled) > .cell {
|
|
122
141
|
background-color: var(--propertygrid-odd-background)
|
|
123
142
|
}
|
|
124
|
-
|
|
125
|
-
&.odd:disabled {
|
|
143
|
+
|
|
144
|
+
&.odd:disabled > .cell {
|
|
126
145
|
background-color: var(--propertygrid-disabled-background)
|
|
127
146
|
}
|
|
128
|
-
|
|
129
|
-
&:has(:focus) {
|
|
130
|
-
background-color: var(--color-primary-a20);
|
|
131
|
-
}
|
|
132
147
|
|
|
133
|
-
&:
|
|
134
|
-
background-color: var(--
|
|
148
|
+
&:has(:focus) > .cell {
|
|
149
|
+
background-color: var( --propertygrid-focus-background );
|
|
135
150
|
}
|
|
136
|
-
|
|
137
|
-
.cell {
|
|
138
|
-
flex-grow: 1;
|
|
139
|
-
flex-basis: 10px;
|
|
140
|
-
padding: 4px;
|
|
141
151
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
152
|
+
&:hover > .cell {
|
|
153
|
+
background-color: var( --propertygrid-hover-background );
|
|
145
154
|
}
|
|
146
155
|
|
|
147
|
-
.cell:first-child {
|
|
156
|
+
> .cell:first-child {
|
|
148
157
|
border-right: 1px solid var( --border );
|
|
149
158
|
}
|
|
150
159
|
|
|
151
|
-
|
|
160
|
+
// a row without title (a button): its editor takes the two columns
|
|
161
|
+
> .cell:only-child {
|
|
162
|
+
grid-column: 1 / -1;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// The title, centered in the height of the row. Wider than half of
|
|
166
|
+
// the grid, it is cut by the column and goes on several lines.
|
|
167
|
+
> .hdr {
|
|
168
|
+
display: flex;
|
|
169
|
+
align-items: center;
|
|
152
170
|
padding: 8px;
|
|
153
171
|
padding-left: 34px;
|
|
154
172
|
}
|
|
@@ -23,6 +23,8 @@ import { ListboxID, ListItem } from "../listbox/listbox"
|
|
|
23
23
|
import { Label, SimpleText } from "../label/label"
|
|
24
24
|
import { asap, class_ns, isFunction, isNumber } from '../../core/core_tools';
|
|
25
25
|
import { Icon, ScrollView } from '../components'
|
|
26
|
+
import { ColorInput } from '../colorinput/colorinput'
|
|
27
|
+
import { EvColorChange } from '../colorpicker/colorpicker'
|
|
26
28
|
|
|
27
29
|
import icons from "../assets/icons"
|
|
28
30
|
|
|
@@ -32,12 +34,14 @@ type IValue = boolean | number | string;
|
|
|
32
34
|
type IValueCB = ( name: string) => IValue;
|
|
33
35
|
|
|
34
36
|
export interface PropertyValue {
|
|
35
|
-
type: 'boolean' | 'number' | 'string' | 'password' | 'options' | 'label' | 'button';
|
|
37
|
+
type: 'boolean' | 'number' | 'string' | 'password' | 'options' | 'color' | 'label' | 'button';
|
|
36
38
|
title?: string;
|
|
37
39
|
desc?: string;
|
|
38
40
|
name?: string;
|
|
39
41
|
value: IValue | IValueCB;
|
|
40
42
|
options?: ListItem[];
|
|
43
|
+
swatches?: string[] | ( ( ) => string[] ); // for colors: offered in the picker; a function is called when it opens
|
|
44
|
+
nullable?: boolean; // for colors: "no color" can be chosen, the value is then null
|
|
41
45
|
callback?: ( name: string, value: any ) => void;
|
|
42
46
|
live?: boolean; // for live update
|
|
43
47
|
cls?: string;
|
|
@@ -67,6 +71,16 @@ export interface PropertyProps extends ComponentProps {
|
|
|
67
71
|
* @cssvar
|
|
68
72
|
* ```
|
|
69
73
|
* --propertygrid-background
|
|
74
|
+
* --propertygrid-odd-background
|
|
75
|
+
* --propertygrid-even-background
|
|
76
|
+
* --propertygrid-disabled-background
|
|
77
|
+
* --propertygrid-hover-background
|
|
78
|
+
* --propertygrid-focus-background
|
|
79
|
+
* --propertygrid-input-background
|
|
80
|
+
* --propertygrid-group-background
|
|
81
|
+
* --propertygrid-group-color
|
|
82
|
+
* --propertygrid-gadget-background
|
|
83
|
+
* --propertygrid-gadget-color
|
|
70
84
|
* ```
|
|
71
85
|
*/
|
|
72
86
|
|
|
@@ -260,10 +274,23 @@ export class PropertyGrid extends VBox {
|
|
|
260
274
|
}
|
|
261
275
|
});
|
|
262
276
|
}
|
|
277
|
+
else if (item.type === 'color') {
|
|
278
|
+
editor = new ColorInput({
|
|
279
|
+
id: item.name,
|
|
280
|
+
color: value as string,
|
|
281
|
+
format: "hex",
|
|
282
|
+
picker: true,
|
|
283
|
+
swatches: item.swatches,
|
|
284
|
+
nullable: item.nullable,
|
|
285
|
+
change: ( e: EvColorChange ) => {
|
|
286
|
+
item.callback?.( item.name, e.value );
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}
|
|
263
290
|
else if (item.type === 'label') {
|
|
264
|
-
editor = new Label({
|
|
291
|
+
editor = new Label({
|
|
265
292
|
id: item.name,
|
|
266
|
-
text: value as string,
|
|
293
|
+
text: value as string,
|
|
267
294
|
});
|
|
268
295
|
}
|
|
269
296
|
else if (item.type === 'button') {
|
|
@@ -347,6 +374,12 @@ export class PropertyGrid extends VBox {
|
|
|
347
374
|
editor.setNumValue( value as number, -2 );
|
|
348
375
|
}
|
|
349
376
|
}
|
|
377
|
+
else if (item.type === 'color') {
|
|
378
|
+
const editor = this.root.query<ColorInput>( '#'+item.name );
|
|
379
|
+
if( editor ) {
|
|
380
|
+
editor.setColor( value as string );
|
|
381
|
+
}
|
|
382
|
+
}
|
|
350
383
|
else if (item.type === 'label') {
|
|
351
384
|
const label = this.root.query<Label>( '#'+item.name );
|
|
352
385
|
if( label ) {
|
package/src/core/component.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { CoreEvent, EventMap } from './core_events';
|
|
|
21
21
|
import { addEvent, DOMEventHandler, GlobalDOMEvents, COMPONENT } from './core_dom';
|
|
22
22
|
import { Application, EvMessage } from './core_application';
|
|
23
23
|
import { makeState } from './core_state.js';
|
|
24
|
+
import { Shortcuts, ShortcutCallback, ShortcutOptions } from './core_shortcuts';
|
|
24
25
|
|
|
25
26
|
type ComponentAttributes = Record<string,string|number|boolean>;
|
|
26
27
|
type CreateComponentCallBack = ( attrs: Record<string,string> ) => ComponentContent;
|
|
@@ -70,6 +71,15 @@ export const makeUniqueComponentId = ( ) => {
|
|
|
70
71
|
return `x4-${gen_id++}`;
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
/**
|
|
75
|
+
* CSS properties. A number gets "px", except for the unitless properties
|
|
76
|
+
* (opacity, lineHeight, flexGrow...).
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
export type StyleProps = {
|
|
80
|
+
[K in keyof CSSStyleDeclaration]?: CSSStyleDeclaration[K] | number;
|
|
81
|
+
};
|
|
82
|
+
|
|
73
83
|
/**
|
|
74
84
|
* Base properties for all components.
|
|
75
85
|
*/
|
|
@@ -80,7 +90,7 @@ export interface ComponentProps {
|
|
|
80
90
|
/** Namespace for SVG/MathML elements. */
|
|
81
91
|
ns?: string;
|
|
82
92
|
/** Inline CSS styles. */
|
|
83
|
-
style?:
|
|
93
|
+
style?: StyleProps;
|
|
84
94
|
/** HTML attributes. */
|
|
85
95
|
attrs?: Record<string,string|number|boolean>;
|
|
86
96
|
/** Child content (components, strings, or HTML). */
|
|
@@ -160,6 +170,7 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
|
|
|
160
170
|
|
|
161
171
|
#store: Map<string|symbol,any>;
|
|
162
172
|
#pstate: any;
|
|
173
|
+
#shortcuts: Shortcuts;
|
|
163
174
|
|
|
164
175
|
constructor(props: P) {
|
|
165
176
|
super( );
|
|
@@ -685,17 +696,17 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
|
|
|
685
696
|
* @returns The component instance for chaining.
|
|
686
697
|
*/
|
|
687
698
|
|
|
688
|
-
setStyle( style:
|
|
699
|
+
setStyle( style: StyleProps ): this {
|
|
689
700
|
const _style = (this.dom as HTMLElement).style;
|
|
690
701
|
|
|
691
702
|
for( const name in style ) {
|
|
692
|
-
|
|
693
|
-
let value = style[name];
|
|
703
|
+
|
|
704
|
+
let value: any = style[name];
|
|
694
705
|
if( !unitless[name] && (isNumber(value) || RE_NUMBER.test(value)) ) {
|
|
695
706
|
value += "px";
|
|
696
707
|
}
|
|
697
708
|
|
|
698
|
-
_style[name] = value;
|
|
709
|
+
(_style as any)[name] = value;
|
|
699
710
|
}
|
|
700
711
|
|
|
701
712
|
return this;
|
|
@@ -1160,6 +1171,40 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
|
|
|
1160
1171
|
return null;
|
|
1161
1172
|
}
|
|
1162
1173
|
|
|
1174
|
+
// :: SHORTCUTS ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* Adds a keyboard shortcut to the component. It works while the focus
|
|
1178
|
+
* is in the component, and while the focus is nowhere if this component
|
|
1179
|
+
* is the last one with shortcuts that had it: of two views that are shown
|
|
1180
|
+
* together, the one the user works in answers.
|
|
1181
|
+
*
|
|
1182
|
+
* A key that a child has used (it prevented its default, or stopped its
|
|
1183
|
+
* propagation) is not a shortcut. Nor is a key typed in a field, unless
|
|
1184
|
+
* `editable` is set.
|
|
1185
|
+
*
|
|
1186
|
+
* @param keys - "Mod+C", "Shift+Mod+Z", "Delete"... or several of them. Mod is Ctrl, or Cmd on a Mac.
|
|
1187
|
+
* @see Application.addShortcut for the shortcuts of the whole application
|
|
1188
|
+
*
|
|
1189
|
+
* @example
|
|
1190
|
+
* this.addShortcut( "Mod+C", ( ) => this.copy( ) );
|
|
1191
|
+
* this.addShortcut( ["Mod+Y", "Shift+Mod+Z"], ( ) => this.redo( ) );
|
|
1192
|
+
*/
|
|
1193
|
+
|
|
1194
|
+
addShortcut( keys: string | string[], callback: ShortcutCallback, options?: ShortcutOptions ): this {
|
|
1195
|
+
if( !this.#shortcuts ) {
|
|
1196
|
+
const shortcuts = this.#shortcuts = new Shortcuts( );
|
|
1197
|
+
|
|
1198
|
+
// straight on the element, not with addDOMEvent: the handlers of the
|
|
1199
|
+
// children are called exactly as before
|
|
1200
|
+
this.dom.addEventListener( "keydown", ( ev: KeyboardEvent ) => shortcuts.handle( ev ) );
|
|
1201
|
+
this.dom.addEventListener( "focusin", ( ) => Application.instance( ).setActiveShortcuts( this, shortcuts ) );
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
this.#shortcuts.add( keys, callback, options );
|
|
1205
|
+
return this;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1163
1208
|
// :: PERSISTENCE ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
1164
1209
|
|
|
1165
1210
|
protected loadPState( name: string, defaults: Record<string, any> ): any {
|
|
@@ -1185,9 +1230,10 @@ export class Component<P extends ComponentProps = ComponentProps, E extends Comp
|
|
|
1185
1230
|
|
|
1186
1231
|
const state = makeState( raw );
|
|
1187
1232
|
|
|
1233
|
+
// the state is what is saved, not raw: makeState works on a copy of it
|
|
1188
1234
|
state.on( "change", ( ) => {
|
|
1189
1235
|
this.setTimeout( key, 500, ( ) => {
|
|
1190
|
-
Application.instance().setStorage( key, JSON.stringify(
|
|
1236
|
+
Application.instance().setStorage( key, JSON.stringify( state ) );
|
|
1191
1237
|
} );
|
|
1192
1238
|
});
|
|
1193
1239
|
|