x4js 2.3.12 → 2.3.14
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 +61 -5
- package/aicontext.md +22 -4
- package/cli/build-options.mjs +2 -2
- package/cli/build.mjs +8 -2
- package/cli/config.mjs +46 -3
- package/cli/dev.mjs +22 -11
- package/cli/main.mjs +3 -2
- package/package.json +1 -1
- package/src/components/colorinput/colorinput.module.scss +36 -2
- package/src/components/colorinput/colorinput.ts +202 -32
- package/src/components/colorpicker/colorpicker.module.scss +23 -0
- package/src/components/colorpicker/colorpicker.ts +149 -35
- package/src/components/propgrid/progrid.module.scss +49 -31
- package/src/components/propgrid/propgrid.ts +36 -3
- package/src/core/component.ts +14 -5
- package/src/core/core_tools.ts +1 -1
|
@@ -1,40 +1,70 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
2
|
* ___ ___ __
|
|
3
3
|
* \ \/ / / _
|
|
4
4
|
* \ / /_| |_
|
|
5
|
-
* / \____ _|
|
|
5
|
+
* / \____ _|
|
|
6
6
|
* /__/\__\ |_|
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* @file colorinput.ts
|
|
9
|
-
* @author Etienne Cochard
|
|
10
|
-
*
|
|
9
|
+
* @author Etienne Cochard
|
|
10
|
+
*
|
|
11
11
|
* @copyright (c) 2026 R-libre ingenierie
|
|
12
12
|
*
|
|
13
|
-
* Use of this source code is governed by an MIT-style license
|
|
13
|
+
* Use of this source code is governed by an MIT-style license
|
|
14
14
|
* that can be found in the LICENSE file or at https://opensource.org/licenses/MIT.
|
|
15
15
|
**/
|
|
16
16
|
|
|
17
|
-
import { Component } from '../../core/component';
|
|
18
|
-
import {
|
|
17
|
+
import { Component, ComponentEvents } from '../../core/component';
|
|
18
|
+
import { EventCallback } from '../../core/core_events';
|
|
19
|
+
import { isFeatureAvailable, class_ns, isFunction, IComponentInterface, IFormElement } from '../../core/core_tools';
|
|
19
20
|
import { Color } from '../../core/core_colors';
|
|
20
21
|
|
|
21
22
|
import { BoxProps, HBox } from '../boxes/boxes';
|
|
22
23
|
import { Input } from '../input/input';
|
|
23
24
|
import { Button } from '../button/button';
|
|
25
|
+
import { Popup } from '../popup/popup';
|
|
26
|
+
import { ColorPicker, EvColorChange } from '../colorpicker/colorpicker';
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
import "./colorinput.module.scss"
|
|
27
30
|
import icon from "./crosshairs-simple-sharp-light.svg"
|
|
28
31
|
|
|
29
|
-
//TODO: add swatches
|
|
30
32
|
//TODO: better keyboard handling (selection after cursor)
|
|
31
33
|
|
|
34
|
+
type Swatches = ( Color | string )[];
|
|
35
|
+
|
|
32
36
|
/**
|
|
33
|
-
*
|
|
37
|
+
*
|
|
34
38
|
*/
|
|
35
39
|
|
|
36
|
-
interface ColorInputProps extends BoxProps {
|
|
40
|
+
export interface ColorInputProps extends BoxProps {
|
|
41
|
+
/** The color at start. null: no color is shown. */
|
|
37
42
|
color: Color | string;
|
|
43
|
+
|
|
44
|
+
/** Name of the field: with it, a Form reads and writes the color (as "#rrggbb"). */
|
|
45
|
+
name?: string;
|
|
46
|
+
|
|
47
|
+
/** How the field writes the color. "rgb" by default: rgb(r,g,b). "hex": #rrggbb. */
|
|
48
|
+
format?: "rgb" | "hex";
|
|
49
|
+
|
|
50
|
+
/** A click on the swatch opens a color picker. */
|
|
51
|
+
picker?: boolean;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Colors offered in the picker. A function is called each time the
|
|
55
|
+
* picker opens: the list may change with time.
|
|
56
|
+
*/
|
|
57
|
+
swatches?: Swatches | ( ( ) => Swatches );
|
|
58
|
+
|
|
59
|
+
/** The field may have no color: emptied, or "no color" picked. */
|
|
60
|
+
nullable?: boolean;
|
|
61
|
+
|
|
62
|
+
/** The user changed the color (not fired by setColor). */
|
|
63
|
+
change?: EventCallback<EvColorChange>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface ColorInputEvents extends ComponentEvents {
|
|
67
|
+
change: EvColorChange;
|
|
38
68
|
}
|
|
39
69
|
|
|
40
70
|
/**
|
|
@@ -43,56 +73,196 @@ interface ColorInputProps extends BoxProps {
|
|
|
43
73
|
* --colorinput-btn-background
|
|
44
74
|
* --colorinput-btn-color
|
|
45
75
|
* --colorinput-btn-color-hover
|
|
76
|
+
* --colorinput-popup-background
|
|
46
77
|
* ```
|
|
47
78
|
*/
|
|
48
79
|
|
|
49
80
|
@class_ns( "x4" )
|
|
50
|
-
export class ColorInput extends HBox<ColorInputProps> {
|
|
81
|
+
export class ColorInput extends HBox<ColorInputProps,ColorInputEvents> {
|
|
51
82
|
static "$cls-ns" = "x4";
|
|
52
83
|
|
|
84
|
+
private _color: Color; // null: no color
|
|
85
|
+
private _swatch: Component;
|
|
86
|
+
private _edit: Input;
|
|
87
|
+
private _swatches: Swatches | ( ( ) => Swatches );
|
|
88
|
+
private _popup: Popup; // the color picker, once it was opened
|
|
89
|
+
|
|
53
90
|
constructor( props: ColorInputProps ) {
|
|
54
91
|
super( props );
|
|
55
92
|
|
|
56
|
-
|
|
57
|
-
|
|
93
|
+
this.mapPropEvents( props, "change" );
|
|
94
|
+
this._swatches = props.swatches;
|
|
95
|
+
|
|
96
|
+
if( props.name ) {
|
|
97
|
+
this.setAttribute( "name", props.name );
|
|
98
|
+
}
|
|
58
99
|
|
|
59
100
|
this.setContent( [
|
|
60
|
-
|
|
61
|
-
|
|
101
|
+
this._swatch = new Component( { cls: "swatch" } ),
|
|
102
|
+
this._edit = new Input( { type: "text", value: "", spellcheck: false } ),
|
|
62
103
|
|
|
63
104
|
isFeatureAvailable("eyedropper") ? new Button( { icon: icon, click: ( ) => {
|
|
64
105
|
const eyeDropper = new (window as any).EyeDropper();
|
|
65
106
|
eyeDropper.open( ).then( ( result: any ) => {
|
|
66
|
-
|
|
67
|
-
updateColor( color );
|
|
107
|
+
this._changed( new Color( result.sRGBHex ) );
|
|
68
108
|
}).catch( (_: any ) => {
|
|
69
109
|
/* silence */
|
|
70
110
|
});
|
|
71
111
|
} } ) : null
|
|
72
112
|
])
|
|
73
113
|
|
|
74
|
-
|
|
75
|
-
const txt =
|
|
114
|
+
this._edit.addDOMEvent( "input", ( ) => {
|
|
115
|
+
const txt = this._edit.getValue( );
|
|
116
|
+
|
|
117
|
+
if( this.props.nullable && txt.trim( )=="" ) {
|
|
118
|
+
this._changed( null, false );
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
76
122
|
const clr = new Color( txt );
|
|
77
123
|
if( !clr.isInvalid() ) {
|
|
78
|
-
|
|
79
|
-
updateColor( color );
|
|
124
|
+
this._changed( clr );
|
|
80
125
|
}
|
|
81
126
|
});
|
|
82
127
|
|
|
83
|
-
|
|
84
|
-
swatch
|
|
85
|
-
|
|
128
|
+
if( props.picker ) {
|
|
129
|
+
// A click on the swatch opens the picker, a second one closes it.
|
|
130
|
+
// The picker closes by itself as soon as the button goes down
|
|
131
|
+
// outside of it: whether it was open is to be known before that.
|
|
132
|
+
let wasOpen = false;
|
|
133
|
+
|
|
134
|
+
this._swatch.addClass( "picker" );
|
|
135
|
+
this._swatch.addDOMEvent( "pointerdown", ( ) => { wasOpen = !!this._popup?.isOpen( ); } );
|
|
136
|
+
this._swatch.addDOMEvent( "click", ( ) => {
|
|
137
|
+
if( !wasOpen ) {
|
|
138
|
+
this._openPicker( );
|
|
139
|
+
}
|
|
140
|
+
} );
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
this._show( this._parse( props.color ) );
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private _parse( color: Color | string ): Color {
|
|
147
|
+
if( color instanceof Color ) {
|
|
148
|
+
return color;
|
|
86
149
|
}
|
|
87
150
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
151
|
+
// Nothing to show (a color that is not known, or several ones). Whether
|
|
152
|
+
// the user may choose "no color" is another matter: props.nullable.
|
|
153
|
+
if( color===null || color===undefined || color==="" ) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return new Color( color );
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private _format( color: Color ): string {
|
|
161
|
+
return this.props.format=="hex" ? color.toHexString( ) : color.toRgbString( false );
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* show a color: swatch and text
|
|
166
|
+
* @param text false while the user empties the field: the text is left alone
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
private _show( color: Color, text = true ) {
|
|
170
|
+
this._color = color;
|
|
171
|
+
|
|
172
|
+
this._swatch.setClass( "none", !color );
|
|
173
|
+
this._swatch.setStyleValue( "backgroundColor", color ? color.toRgbString(false) : null );
|
|
174
|
+
|
|
175
|
+
if( text ) {
|
|
176
|
+
this._edit.setValue( color ? this._format( color ) : "" );
|
|
91
177
|
}
|
|
92
|
-
|
|
93
|
-
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* the user changed the color
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
private _changed( color: Color, text = true ) {
|
|
185
|
+
this._show( color, text );
|
|
186
|
+
this.fire( "change", { color, value: color ? color.toHexString( ) : null } );
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The color picker, with the swatches, under the field. It closes on
|
|
191
|
+
* a click outside of it.
|
|
192
|
+
*/
|
|
193
|
+
|
|
194
|
+
private _openPicker( ) {
|
|
195
|
+
const swatches = isFunction( this._swatches ) ? this._swatches( ) : this._swatches;
|
|
196
|
+
|
|
197
|
+
// the picker works on its own color: it changes it in place
|
|
198
|
+
const picker = new ColorPicker( {
|
|
199
|
+
color: this._color ? new Color( this._color.toHexString( ) ) : new Color( 255, 255, 255 ),
|
|
200
|
+
swatches,
|
|
201
|
+
nullable: this.props.nullable,
|
|
202
|
+
} );
|
|
203
|
+
|
|
204
|
+
const popup = this._popup = new Popup( { cls: "x4colorinput-popup", autoClose: true, content: picker } );
|
|
205
|
+
|
|
206
|
+
// after the construction: the picker fires a first change when it is built
|
|
207
|
+
picker.on( "change", ev => {
|
|
208
|
+
this._changed( ev.color ? new Color( ev.value ) : null );
|
|
209
|
+
|
|
210
|
+
if( !ev.color ) {
|
|
211
|
+
popup.close( );
|
|
212
|
+
}
|
|
213
|
+
} );
|
|
214
|
+
|
|
215
|
+
popup.displayNear( this.getBoundingRect( ), "top left", "bottom left", { x: 0, y: 6 } );
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* The current color, null when there is none.
|
|
220
|
+
*/
|
|
221
|
+
|
|
222
|
+
getColor( ): Color {
|
|
223
|
+
return this._color;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* The current color as "#rrggbb" ("#rrggbbaa" when it is not opaque),
|
|
228
|
+
* null when there is none.
|
|
229
|
+
*/
|
|
230
|
+
|
|
231
|
+
getValue( ): string {
|
|
232
|
+
return this._color ? this._color.toHexString( ) : null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Shows another color, or none (null). Does not fire `change`.
|
|
237
|
+
*/
|
|
238
|
+
|
|
239
|
+
setColor( color: Color | string ) {
|
|
240
|
+
this._show( this._parse( color ) );
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Changes the colors offered in the picker (see props.swatches).
|
|
245
|
+
*/
|
|
246
|
+
|
|
247
|
+
setSwatches( swatches: Swatches | ( ( ) => Swatches ) ) {
|
|
248
|
+
this._swatches = swatches;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
*
|
|
253
|
+
*/
|
|
254
|
+
|
|
255
|
+
override queryInterface<T extends IComponentInterface>( name: string ): T {
|
|
256
|
+
if( name=="form-element" ) {
|
|
257
|
+
const i: IFormElement = {
|
|
258
|
+
getRawValue: ( ): any => { return this.getValue( ); },
|
|
259
|
+
setRawValue: ( v: any ) => { this.setColor( v ); },
|
|
260
|
+
isValid: ( ) => { return true; }
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
return i as unknown as T;
|
|
94
264
|
}
|
|
95
265
|
|
|
96
|
-
|
|
266
|
+
return super.queryInterface( name );
|
|
97
267
|
}
|
|
98
|
-
}
|
|
268
|
+
}
|
|
@@ -130,4 +130,27 @@
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
+
|
|
134
|
+
// the colors offered under the sliders (props.swatches)
|
|
135
|
+
.swatches {
|
|
136
|
+
flex-wrap: wrap;
|
|
137
|
+
gap: 4px;
|
|
138
|
+
|
|
139
|
+
.item {
|
|
140
|
+
width: 18px;
|
|
141
|
+
height: 18px;
|
|
142
|
+
border: 1px solid var( --border );
|
|
143
|
+
border-radius: 3px;
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
|
|
146
|
+
&:hover {
|
|
147
|
+
border-color: var( --border-focus );
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// no color: crossed out
|
|
151
|
+
&.none {
|
|
152
|
+
background: linear-gradient( to top right, transparent 45%, #d04040 45%, #d04040 55%, transparent 55% );
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
133
156
|
}
|
|
@@ -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 ) {
|