x4js 2.3.16 → 2.3.18
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 +216 -36
- package/package.json +1 -1
- package/src/components/colorinput/colorinput.module.scss +2 -2
- package/src/components/colorpicker/colorpicker.module.scss +2 -2
- package/src/components/combobox/combobox.ts +5 -1
- package/src/components/dockingview/dockingview.module.scss +21 -3
- package/src/components/dockingview/dockingview.ts +192 -55
- package/src/components/dockingview/docklayout.ts +125 -36
- package/src/components/gauge/gauge.ts +4 -0
- package/src/components/gridview/gridview.ts +350 -175
- package/src/components/input/input.ts +6 -1
- package/src/components/keyboard/keyboard.ts +181 -65
- package/src/components/notification/notification.module.scss +8 -3
- package/src/components/notification/notification.ts +3 -0
- package/src/components/progress/progress.ts +7 -3
- package/src/components/spreadsheet/spreadsheet.ts +27 -1
- package/src/components/switch/switch.ts +2 -1
- package/src/components/tag/tag.ts +1 -2
- package/src/components/textarea/textarea.module.scss +6 -0
- package/src/components/textarea/textarea.ts +48 -10
- package/src/core/component.ts +16 -9
- package/src/core/core_clipboard.ts +239 -0
- package/src/core/core_data.ts +54 -23
- package/src/core/core_dom.ts +27 -5
- package/src/core/core_dragdrop.ts +11 -5
- package/src/core/core_element.ts +1 -1
- package/src/core/core_shortcuts.ts +1 -1
- package/src/x4.d.ts +1 -0
- package/src/x4.ts +1 -0
|
@@ -19,16 +19,21 @@
|
|
|
19
19
|
*
|
|
20
20
|
* There are two kinds of panels, which never mix:
|
|
21
21
|
*
|
|
22
|
-
* tools docked in a band
|
|
23
|
-
* other, side by side, or as tabs of the same stack;
|
|
24
|
-
* can also float.
|
|
22
|
+
* tools docked in a band along a side of the contents: one above
|
|
23
|
+
* the other, side by side, or as tabs of the same stack;
|
|
24
|
+
* they can also float. Which bands a tool may be in is said
|
|
25
|
+
* by the view (DockKinds.sides): the left and the right
|
|
26
|
+
* ones, unless it says otherwise.
|
|
25
27
|
* contents what is worked on (a document, as the editors of an IDE).
|
|
26
28
|
* They share the room between the bands: side by side, one
|
|
27
29
|
* above the other, or as tabs of the same stack.
|
|
28
30
|
*
|
|
29
31
|
* A layout is plain data, so that it can be saved as it is:
|
|
30
32
|
*
|
|
31
|
-
* left, right the
|
|
33
|
+
* left, right the bands of the sides: a width, and a tree of tools.
|
|
34
|
+
* They take the whole height.
|
|
35
|
+
* top, bottom the bands above and under the contents, between those of
|
|
36
|
+
* the sides: a height, and a tree of tools.
|
|
32
37
|
* content the tree of the contents.
|
|
33
38
|
*
|
|
34
39
|
* In a tree, a split puts its children side by side (row)
|
|
@@ -73,7 +78,10 @@ export interface DockStack {
|
|
|
73
78
|
|
|
74
79
|
export type DockNode = DockSplit | DockStack;
|
|
75
80
|
|
|
76
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* The tools docked on one side: `size` is its width in pixels (its height,
|
|
83
|
+
* for the band of the top or of the bottom), `root` is null when it has none.
|
|
84
|
+
*/
|
|
77
85
|
export interface DockBand {
|
|
78
86
|
size: number;
|
|
79
87
|
root: DockNode;
|
|
@@ -91,9 +99,16 @@ export interface DockFloat extends DockRect {
|
|
|
91
99
|
panel: string;
|
|
92
100
|
}
|
|
93
101
|
|
|
102
|
+
/**
|
|
103
|
+
* `top` and `bottom` may be missing: a band that is missing is an empty
|
|
104
|
+
* one. A layout that does not use them does not have them.
|
|
105
|
+
*/
|
|
106
|
+
|
|
94
107
|
export interface DockLayout {
|
|
95
108
|
left: DockBand;
|
|
96
109
|
right: DockBand;
|
|
110
|
+
top?: DockBand;
|
|
111
|
+
bottom?: DockBand;
|
|
97
112
|
content: DockNode;
|
|
98
113
|
floats: DockFloat[];
|
|
99
114
|
hidden: string[];
|
|
@@ -103,12 +118,47 @@ export interface DockLayout {
|
|
|
103
118
|
export interface DockKinds {
|
|
104
119
|
tools: string[];
|
|
105
120
|
contents: string[];
|
|
121
|
+
|
|
122
|
+
/** the bands a tool may be docked in, by its id; the left and the right ones for a tool that is not there */
|
|
123
|
+
sides?: Record<string, DockSide[]>;
|
|
106
124
|
}
|
|
107
125
|
|
|
126
|
+
/** the bands, in the order they are looked at */
|
|
127
|
+
const SIDES: DockSide[] = ["left", "right", "top", "bottom"];
|
|
128
|
+
|
|
129
|
+
/** where a tool may be docked when nothing says it */
|
|
130
|
+
const USUAL_SIDES: DockSide[] = ["left", "right"];
|
|
131
|
+
|
|
108
132
|
function copy( layout: DockLayout ): DockLayout {
|
|
109
133
|
return structuredClone( layout );
|
|
110
134
|
}
|
|
111
135
|
|
|
136
|
+
/**
|
|
137
|
+
* true for the bands of the top and of the bottom: they lie flat. Their
|
|
138
|
+
* size is a height, and their tools are side by side where those of the
|
|
139
|
+
* left and of the right are one above the other.
|
|
140
|
+
*/
|
|
141
|
+
|
|
142
|
+
export function isFlat( side: DockSide ): boolean {
|
|
143
|
+
return side === "top" || side === "bottom";
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The bands a tool may be docked in. A list that is empty counts as none
|
|
148
|
+
* given: a tool has to be somewhere when its layout is lost.
|
|
149
|
+
*/
|
|
150
|
+
|
|
151
|
+
export function dockSides( kinds: DockKinds, panel: string ): DockSide[] {
|
|
152
|
+
const given = kinds.sides ? kinds.sides[panel] : null;
|
|
153
|
+
|
|
154
|
+
if( !given ) {
|
|
155
|
+
return USUAL_SIDES;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const sides = given.filter( side => SIDES.includes( side ) );
|
|
159
|
+
return sides.length ? sides : USUAL_SIDES;
|
|
160
|
+
}
|
|
161
|
+
|
|
112
162
|
/**
|
|
113
163
|
* the stacks of a tree, from left to right and from top to bottom
|
|
114
164
|
*/
|
|
@@ -122,7 +172,7 @@ export function stacksOf( node: DockNode ): DockStack[] {
|
|
|
122
172
|
}
|
|
123
173
|
|
|
124
174
|
function allStacks( layout: DockLayout ): DockStack[] {
|
|
125
|
-
return [...
|
|
175
|
+
return [...SIDES.flatMap( side => stacksOf( layout[side]?.root ) ), ...stacksOf( layout.content )];
|
|
126
176
|
}
|
|
127
177
|
|
|
128
178
|
function parentOf( root: DockNode, node: DockNode ): DockSplit {
|
|
@@ -179,8 +229,13 @@ function normalize( node: DockNode ): DockNode {
|
|
|
179
229
|
|
|
180
230
|
function tidy( layout: DockLayout ): void {
|
|
181
231
|
layout.content = normalize( layout.content );
|
|
182
|
-
|
|
183
|
-
|
|
232
|
+
|
|
233
|
+
for( const side of SIDES ) {
|
|
234
|
+
const band = layout[side];
|
|
235
|
+
if( band ) {
|
|
236
|
+
band.root = normalize( band.root );
|
|
237
|
+
}
|
|
238
|
+
}
|
|
184
239
|
}
|
|
185
240
|
|
|
186
241
|
/**
|
|
@@ -189,10 +244,13 @@ function tidy( layout: DockLayout ): void {
|
|
|
189
244
|
|
|
190
245
|
function detach( layout: DockLayout, panel: string ): void {
|
|
191
246
|
// A tool that was beside others, alone in its stack, takes its width
|
|
192
|
-
// with it: its band gets narrower by its share of the row.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
247
|
+
// with it: its band gets narrower by its share of the row. In a band
|
|
248
|
+
// that lies flat it is its height, and its share of the column.
|
|
249
|
+
for( const side of SIDES ) {
|
|
250
|
+
const band = layout[side];
|
|
251
|
+
const root = band?.root;
|
|
252
|
+
const across = isFlat( side ) ? "column" : "row";
|
|
253
|
+
const at = root?.type === "split" && root.dir === across ? root.children.findIndex( child => child.type === "stack" && child.panels.length === 1 && child.panels[0] === panel ) : -1;
|
|
196
254
|
|
|
197
255
|
if( root?.type === "split" && at >= 0 ) {
|
|
198
256
|
const shares = sharesOf( root.children );
|
|
@@ -227,14 +285,8 @@ export function stackOf( layout: DockLayout, panel: string ): DockStack {
|
|
|
227
285
|
* the band a panel is docked in, null when it is a content, floats or is unknown
|
|
228
286
|
*/
|
|
229
287
|
|
|
230
|
-
export function bandOf( layout: DockLayout, panel: string ):
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if( holds( layout.left ) ) {
|
|
234
|
-
return "left";
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return holds( layout.right ) ? "right" : null;
|
|
288
|
+
export function bandOf( layout: DockLayout, panel: string ): DockSide {
|
|
289
|
+
return SIDES.find( side => stacksOf( layout[side]?.root ).some( stack => stack.panels.includes( panel ) ) ) ?? null;
|
|
238
290
|
}
|
|
239
291
|
|
|
240
292
|
export function floatOf( layout: DockLayout, panel: string ): DockFloat {
|
|
@@ -286,6 +338,10 @@ export function sharesOf( nodes: DockNode[] ): number[] {
|
|
|
286
338
|
* takes half of its room. But a tool put on the left or on the right of
|
|
287
339
|
* another one does not make it narrower: with `width`, the band gets
|
|
288
340
|
* wider by that much, which is what the tool gets.
|
|
341
|
+
*
|
|
342
|
+
* In a band that lies flat (top, bottom) it is the other way round: a
|
|
343
|
+
* tool put above or under another one makes the band higher, by `width`,
|
|
344
|
+
* which is then a height.
|
|
289
345
|
*/
|
|
290
346
|
|
|
291
347
|
export function dockMove( layout: DockLayout, panel: string, target: string, where: DockWhere, width = 0 ): DockLayout {
|
|
@@ -319,7 +375,9 @@ export function dockMove( layout: DockLayout, panel: string, target: string, whe
|
|
|
319
375
|
|
|
320
376
|
const dir = where === "left" || where === "right" ? "row" : "column";
|
|
321
377
|
const before = where === "left" || where === "top";
|
|
322
|
-
|
|
378
|
+
|
|
379
|
+
// across the band: the direction in which it grows
|
|
380
|
+
const wider = !!band && dir === ( isFlat( side ) ? "column" : "row" ) && width > 0;
|
|
323
381
|
|
|
324
382
|
const half = dest.size / 2;
|
|
325
383
|
const fresh: DockStack = { type: "stack", size: wider ? width : half, panels: [panel], active: panel };
|
|
@@ -357,17 +415,22 @@ export function dockMove( layout: DockLayout, panel: string, target: string, whe
|
|
|
357
415
|
}
|
|
358
416
|
|
|
359
417
|
/**
|
|
360
|
-
* Dock a tool in a band,
|
|
361
|
-
*
|
|
418
|
+
* Dock a tool in a band, after the tools that are already there: under
|
|
419
|
+
* them on the left and on the right, on their right in a band that lies
|
|
420
|
+
* flat (top, bottom). `size` is the width the band gets when it was empty;
|
|
421
|
+
* its height, when it lies flat.
|
|
362
422
|
*/
|
|
363
423
|
|
|
364
|
-
export function dockBand( layout: DockLayout, panel: string, side:
|
|
424
|
+
export function dockBand( layout: DockLayout, panel: string, side: DockSide, size = 0 ): DockLayout {
|
|
365
425
|
const result = copy( layout );
|
|
366
|
-
const band = result[side];
|
|
426
|
+
const band = result[side] ??= { size: 0, root: null };
|
|
367
427
|
|
|
368
428
|
detach( result, panel );
|
|
369
429
|
tidy( result );
|
|
370
430
|
|
|
431
|
+
// along the band: the direction its tools follow each other in
|
|
432
|
+
const along = isFlat( side ) ? "row" : "column";
|
|
433
|
+
|
|
371
434
|
const fresh: DockStack = { type: "stack", size: 0, panels: [panel], active: panel };
|
|
372
435
|
const root = band.root;
|
|
373
436
|
|
|
@@ -378,15 +441,15 @@ export function dockBand( layout: DockLayout, panel: string, side: "left" | "rig
|
|
|
378
441
|
band.size = size;
|
|
379
442
|
}
|
|
380
443
|
}
|
|
381
|
-
else if( root.type === "split" && root.dir ===
|
|
382
|
-
// as
|
|
444
|
+
else if( root.type === "split" && root.dir === along ) {
|
|
445
|
+
// as big as the others, on average
|
|
383
446
|
fresh.size = sharesOf( root.children ).reduce( ( a, b ) => a + b ) / root.children.length;
|
|
384
447
|
root.children.push( fresh );
|
|
385
448
|
}
|
|
386
449
|
else {
|
|
387
|
-
// what was there and the tool share the
|
|
450
|
+
// what was there and the tool share the band
|
|
388
451
|
root.size = 0;
|
|
389
|
-
band.root = { type: "split", dir:
|
|
452
|
+
band.root = { type: "split", dir: along, size: 0, children: [root, fresh] };
|
|
390
453
|
}
|
|
391
454
|
|
|
392
455
|
return result;
|
|
@@ -475,7 +538,7 @@ function sizeOf( from: Record<string, unknown>, name: string ): number {
|
|
|
475
538
|
* A layout rebuilt from something that was saved, or null when it is not a
|
|
476
539
|
* layout or holds no panel. Only what is understood is kept, and only the
|
|
477
540
|
* panels that are known, each once and where its kind may be: a tool in a
|
|
478
|
-
* band or floating
|
|
541
|
+
* band it may be docked in, or floating; a content among the contents.
|
|
479
542
|
*
|
|
480
543
|
* (a field is read only once it is known to be there: what was saved may be
|
|
481
544
|
* a state of x4, which reports the reading of a missing property)
|
|
@@ -527,17 +590,21 @@ function sanitize( saved: unknown, kinds: DockKinds ): DockLayout {
|
|
|
527
590
|
return null;
|
|
528
591
|
};
|
|
529
592
|
|
|
530
|
-
|
|
593
|
+
// a band only keeps the tools that may be docked in it
|
|
594
|
+
const band = ( name: DockSide ): DockBand => {
|
|
531
595
|
const from = name in saved ? saved[name] : null;
|
|
532
596
|
if( !isObject( from ) ) {
|
|
533
597
|
return { size: 0, root: null };
|
|
534
598
|
}
|
|
535
599
|
|
|
536
|
-
|
|
600
|
+
const allowed = kinds.tools.filter( id => dockSides( kinds, id ).includes( name ) );
|
|
601
|
+
return { size: sizeOf( from, "size" ), root: normalize( "root" in from ? node( from.root, allowed ) : null ) };
|
|
537
602
|
};
|
|
538
603
|
|
|
539
604
|
const left = band( "left" );
|
|
540
605
|
const right = band( "right" );
|
|
606
|
+
const top = band( "top" );
|
|
607
|
+
const bottom = band( "bottom" );
|
|
541
608
|
const content = normalize( "content" in saved ? node( saved.content, kinds.contents ) : null );
|
|
542
609
|
|
|
543
610
|
const floats: DockFloat[] = [];
|
|
@@ -561,7 +628,23 @@ function sanitize( saved: unknown, kinds: DockKinds ): DockLayout {
|
|
|
561
628
|
}
|
|
562
629
|
|
|
563
630
|
// nothing in it that is known: not worth more than no layout at all
|
|
564
|
-
|
|
631
|
+
if( seen.size === 0 ) {
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// the bands of the top and of the bottom are only there when they hold
|
|
636
|
+
// something: a layout that does not use them is as it always was
|
|
637
|
+
const layout: DockLayout = { left, right, content, floats, hidden };
|
|
638
|
+
|
|
639
|
+
if( top.root ) {
|
|
640
|
+
layout.top = top;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if( bottom.root ) {
|
|
644
|
+
layout.bottom = bottom;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return layout;
|
|
565
648
|
}
|
|
566
649
|
|
|
567
650
|
/**
|
|
@@ -570,7 +653,9 @@ function sanitize( saved: unknown, kinds: DockKinds ): DockLayout {
|
|
|
570
653
|
*
|
|
571
654
|
* `kinds` are the panels that exist today. A saved layout may be older
|
|
572
655
|
* than the application: the panels it does not know are added where the
|
|
573
|
-
* default layout has them, those that are gone are dropped.
|
|
656
|
+
* default layout has them, those that are gone are dropped. So is a tool
|
|
657
|
+
* that is in a band it may not be docked in anymore: it goes where the
|
|
658
|
+
* default layout has it.
|
|
574
659
|
*/
|
|
575
660
|
|
|
576
661
|
export function dockRestore( saved: unknown, kinds: DockKinds, defaults: DockLayout ): DockLayout {
|
|
@@ -582,8 +667,10 @@ export function dockRestore( saved: unknown, kinds: DockKinds, defaults: DockLay
|
|
|
582
667
|
continue;
|
|
583
668
|
}
|
|
584
669
|
|
|
670
|
+
// where the default layout has it; otherwise on the right if it may, or in the first band it may be in
|
|
671
|
+
const allowed = dockSides( kinds, panel );
|
|
585
672
|
const float = floatOf( usual, panel );
|
|
586
|
-
const side = bandOf( usual, panel ) ?? "right";
|
|
673
|
+
const side = bandOf( usual, panel ) ?? ( allowed.includes( "right" ) ? "right" : allowed[0] );
|
|
587
674
|
|
|
588
675
|
if( kinds.contents.includes( panel ) ) {
|
|
589
676
|
layout = addContent( layout, panel );
|
|
@@ -592,7 +679,9 @@ export function dockRestore( saved: unknown, kinds: DockKinds, defaults: DockLay
|
|
|
592
679
|
layout = dockFloat( layout, panel, float );
|
|
593
680
|
}
|
|
594
681
|
else {
|
|
595
|
-
|
|
682
|
+
// as wide as the default layout has that band, when it has it
|
|
683
|
+
const band = usual[side];
|
|
684
|
+
layout = dockBand( layout, panel, side, band ? band.size : 0 );
|
|
596
685
|
}
|
|
597
686
|
|
|
598
687
|
if( usual.hidden.includes( panel ) ) {
|
|
@@ -59,6 +59,10 @@ export class Gauge extends Component<GaugeProps> {
|
|
|
59
59
|
constructor( props: GaugeProps ) {
|
|
60
60
|
super( props );
|
|
61
61
|
|
|
62
|
+
// no range given: 0 to 100
|
|
63
|
+
this.props.min = props.min ?? 0;
|
|
64
|
+
this.props.max = props.max ?? 100;
|
|
65
|
+
|
|
62
66
|
this.pos = 70;
|
|
63
67
|
this.stops = [];
|
|
64
68
|
|