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,13 +19,13 @@ import { Component, ComponentEvent, ComponentEvents, ComponentProps } from '../.
|
|
|
19
19
|
import { EventCallback } from '../../core/core_events';
|
|
20
20
|
import { CSizer } from '../sizers/sizer';
|
|
21
21
|
|
|
22
|
-
import { bandOf, dockActivate, dockBand, dockFloat, dockMove, dockRestore, dockShow, dropZone, floatOf, isShown, keepInside, sharesOf, shownPanel, sideRect, stackOf, stacksOf, visiblePanels } from './docklayout';
|
|
23
|
-
import { DockFloat, DockKinds, DockLayout, DockNode, DockRect, DockStack } from './docklayout';
|
|
22
|
+
import { bandOf, dockActivate, dockBand, dockFloat, dockMove, dockRestore, dockShow, dockSides, dropZone, floatOf, isFlat, isShown, keepInside, sharesOf, shownPanel, sideRect, stackOf, stacksOf, visiblePanels } from './docklayout';
|
|
23
|
+
import { DockFloat, DockKinds, DockLayout, DockNode, DockRect, DockSide, DockStack } from './docklayout';
|
|
24
24
|
|
|
25
25
|
import "./dockingview.module.scss";
|
|
26
26
|
|
|
27
27
|
// the layout is given and read by the host: its types go with the component
|
|
28
|
-
export type { DockLayout, DockBand, DockNode, DockSplit, DockStack, DockFloat } from './docklayout';
|
|
28
|
+
export type { DockLayout, DockBand, DockNode, DockSplit, DockStack, DockFloat, DockSide } from './docklayout';
|
|
29
29
|
|
|
30
30
|
/** under this distance, in pixels, a press on a title is a click, not a drag */
|
|
31
31
|
const DRAG_SLOP = 4;
|
|
@@ -39,8 +39,12 @@ const MIN_SIZE = 48;
|
|
|
39
39
|
/** what a sizer always leaves to the contents, between the two bands */
|
|
40
40
|
const MIN_AREA = 80;
|
|
41
41
|
|
|
42
|
-
/** width of a band whose layout gives none */
|
|
42
|
+
/** width of a band whose layout gives none; height, for the band of the top or of the bottom */
|
|
43
43
|
const BAND_SIZE = 200;
|
|
44
|
+
const BAND_HEIGHT = 160;
|
|
45
|
+
|
|
46
|
+
/** the bands, in the order they are looked at */
|
|
47
|
+
const SIDES: DockSide[] = ["left", "right", "top", "bottom"];
|
|
44
48
|
|
|
45
49
|
/** smallest size of a panel that starts to float */
|
|
46
50
|
const FLOAT_MIN_W = 160;
|
|
@@ -68,6 +72,9 @@ export interface DockPanel {
|
|
|
68
72
|
|
|
69
73
|
/** a tool: instead of what the box says. A content never floats. */
|
|
70
74
|
floating?: boolean;
|
|
75
|
+
|
|
76
|
+
/** a tool: the bands it may be docked in, instead of what the box says */
|
|
77
|
+
sides?: DockSide[];
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
export interface DockingViewProps extends ComponentProps {
|
|
@@ -82,6 +89,14 @@ export interface DockingViewProps extends ComponentProps {
|
|
|
82
89
|
/** tools have a button that hides them (see showPanel) */
|
|
83
90
|
closable?: boolean;
|
|
84
91
|
|
|
92
|
+
/**
|
|
93
|
+
* The bands tools may be docked in: "left", "right", "top", "bottom".
|
|
94
|
+
* The left and the right ones when missing. A tool is never offered a
|
|
95
|
+
* place in a band it may not be in, neither beside one of its tools nor
|
|
96
|
+
* as one of their tabs.
|
|
97
|
+
*/
|
|
98
|
+
sides?: DockSide[];
|
|
99
|
+
|
|
85
100
|
/**
|
|
86
101
|
* A name: the layout is then remembered under it, in the storage of
|
|
87
102
|
* the application, and found again the next time.
|
|
@@ -142,6 +157,11 @@ interface Drag {
|
|
|
142
157
|
* dropped anywhere else floats above the rest. With `closable`, its title
|
|
143
158
|
* has a button that hides it; showPanel() brings it back.
|
|
144
159
|
*
|
|
160
|
+
* With `sides`, a tool may also be docked above or under the contents, in
|
|
161
|
+
* a band that lies between those of the left and of the right: for what is
|
|
162
|
+
* wide and low (a log, a list of results, a timeline). `sides` says which
|
|
163
|
+
* bands a tool may be in, for all the tools or for one of them.
|
|
164
|
+
*
|
|
145
165
|
* A content is what is worked on (a document, as the editors of an IDE).
|
|
146
166
|
* Contents share the room between the bands: a title dragged to a side of
|
|
147
167
|
* another content is put beside it, to its middle it becomes one of its
|
|
@@ -175,7 +195,7 @@ export class DockingView extends Component<DockingViewProps, DockingViewEvents>
|
|
|
175
195
|
|
|
176
196
|
// the boxes of the layout as it is shown now
|
|
177
197
|
private nodes = new Map<DockNode, Component>( );
|
|
178
|
-
private bands = new Map<
|
|
198
|
+
private bands = new Map<DockSide, Component>( );
|
|
179
199
|
private floats = new Map<string, Component>( );
|
|
180
200
|
private area: Component; // the room of the contents, between the bands
|
|
181
201
|
|
|
@@ -277,7 +297,23 @@ export class DockingView extends Component<DockingViewProps, DockingViewEvents>
|
|
|
277
297
|
|
|
278
298
|
private kinds( ): DockKinds {
|
|
279
299
|
const ids = this.props.panels.map( panel => panel.id );
|
|
280
|
-
|
|
300
|
+
const tools = ids.filter( id => this.isTool( id ) );
|
|
301
|
+
|
|
302
|
+
// what a tool says for itself, otherwise what the box says for all
|
|
303
|
+
const sides: Record<string, DockSide[]> = {};
|
|
304
|
+
for( const id of tools ) {
|
|
305
|
+
sides[id] = this.panel( id ).sides ?? this.props.sides;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return { tools, contents: ids.filter( id => !this.isTool( id ) ), sides };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* true when a tool may be docked in a band
|
|
313
|
+
*/
|
|
314
|
+
|
|
315
|
+
private mayDock( id: string, side: DockSide ): boolean {
|
|
316
|
+
return dockSides( this.kinds( ), id ).includes( side );
|
|
281
317
|
}
|
|
282
318
|
|
|
283
319
|
private mayFloat( id: string ): boolean {
|
|
@@ -316,8 +352,10 @@ export class DockingView extends Component<DockingViewProps, DockingViewEvents>
|
|
|
316
352
|
// :: BUILDING ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
317
353
|
|
|
318
354
|
/**
|
|
319
|
-
* Make the boxes of the layout again: the band of the left,
|
|
320
|
-
*
|
|
355
|
+
* Make the boxes of the layout again: the band of the left, then,
|
|
356
|
+
* one above the other, the band of the top, the room of the contents
|
|
357
|
+
* and the band of the bottom, then the band of the right; and the
|
|
358
|
+
* floating tools.
|
|
321
359
|
*
|
|
322
360
|
* What the panels show is not built again: their pages are moved. They
|
|
323
361
|
* go straight from a place in the document to another one, the park
|
|
@@ -346,9 +384,19 @@ export class DockingView extends Component<DockingViewProps, DockingViewEvents>
|
|
|
346
384
|
content: content && isShown( content, hidden ) ? this.makeNode( content, titled ) : null,
|
|
347
385
|
} );
|
|
348
386
|
|
|
387
|
+
// between the bands of the sides, which take the whole height
|
|
388
|
+
const middle = new Component( {
|
|
389
|
+
cls: "dock-middle",
|
|
390
|
+
content: [
|
|
391
|
+
...this.makeBand( "top" ),
|
|
392
|
+
this.area,
|
|
393
|
+
...this.makeBand( "bottom" ),
|
|
394
|
+
],
|
|
395
|
+
} );
|
|
396
|
+
|
|
349
397
|
this.frame.setContent( [
|
|
350
398
|
...this.makeBand( "left" ),
|
|
351
|
-
|
|
399
|
+
middle,
|
|
352
400
|
...this.makeBand( "right" ),
|
|
353
401
|
...floats.filter( float => !hidden.includes( float.panel ) ).map( float => this.makeFloat( float ) ),
|
|
354
402
|
] );
|
|
@@ -362,39 +410,58 @@ export class DockingView extends Component<DockingViewProps, DockingViewEvents>
|
|
|
362
410
|
}
|
|
363
411
|
|
|
364
412
|
/**
|
|
365
|
-
* A band and the sizer that changes its width,
|
|
366
|
-
*
|
|
413
|
+
* A band and the sizer that changes its width (its height, for the
|
|
414
|
+
* band of the top or of the bottom), in the order they are shown;
|
|
415
|
+
* nothing when none of its tools is shown.
|
|
367
416
|
*/
|
|
368
417
|
|
|
369
|
-
private makeBand( side:
|
|
418
|
+
private makeBand( side: DockSide ): Component[] {
|
|
370
419
|
const band = this.layout[side];
|
|
371
420
|
|
|
372
|
-
if( !band
|
|
421
|
+
if( !band?.root || !isShown( band.root, this.layout.hidden ) ) {
|
|
373
422
|
return [];
|
|
374
423
|
}
|
|
375
424
|
|
|
425
|
+
const flat = isFlat( side );
|
|
426
|
+
|
|
376
427
|
const box = new Component( {
|
|
377
428
|
cls: `dock-band ${side}`,
|
|
378
|
-
width: band.size || BAND_SIZE,
|
|
379
429
|
content: this.makeNode( band.root, true ),
|
|
380
430
|
} );
|
|
381
431
|
|
|
432
|
+
if( flat ) {
|
|
433
|
+
box.setHeight( band.size || BAND_HEIGHT );
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
box.setWidth( band.size || BAND_SIZE );
|
|
437
|
+
}
|
|
438
|
+
|
|
382
439
|
this.bands.set( side, box );
|
|
383
440
|
|
|
384
|
-
// what the band gets is taken from the contents
|
|
385
|
-
|
|
441
|
+
// what the band gets is taken from the contents: the sizer is on
|
|
442
|
+
// the side of the band that faces them
|
|
443
|
+
const first = side === "left" || side === "top";
|
|
444
|
+
const sign = first ? 1 : -1;
|
|
386
445
|
let size = 0;
|
|
387
446
|
let max = 0;
|
|
388
447
|
|
|
389
|
-
const sizer = this.makeSizer(
|
|
448
|
+
const sizer = this.makeSizer( !flat, ( ) => {
|
|
449
|
+
const room = this.area.getBoundingRect( );
|
|
450
|
+
|
|
390
451
|
size = band.size;
|
|
391
|
-
max = size + Math.max( 0,
|
|
452
|
+
max = size + Math.max( 0, ( flat ? room.height : room.width ) - MIN_AREA );
|
|
392
453
|
}, delta => {
|
|
393
454
|
band.size = Math.round( Math.max( MIN_SIZE, Math.min( size + delta * sign, max ) ) );
|
|
394
|
-
|
|
455
|
+
|
|
456
|
+
if( flat ) {
|
|
457
|
+
box.setHeight( band.size );
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
box.setWidth( band.size );
|
|
461
|
+
}
|
|
395
462
|
} );
|
|
396
463
|
|
|
397
|
-
return
|
|
464
|
+
return first ? [box, sizer] : [sizer, box];
|
|
398
465
|
}
|
|
399
466
|
|
|
400
467
|
private makeNode( node: DockNode, titled: boolean ): Component {
|
|
@@ -588,12 +655,23 @@ export class DockingView extends Component<DockingViewProps, DockingViewEvents>
|
|
|
588
655
|
visit( this.layout.content, null );
|
|
589
656
|
}
|
|
590
657
|
|
|
591
|
-
for( const side of
|
|
658
|
+
for( const side of SIDES ) {
|
|
592
659
|
const band = this.layout[side];
|
|
593
|
-
|
|
660
|
+
if( !band ) {
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// A band that is not shown has no box: its size stays what it
|
|
665
|
+
// was. Otherwise its width, or its height when it lies flat.
|
|
666
|
+
const box = this.bands.get( side );
|
|
667
|
+
|
|
668
|
+
if( box ) {
|
|
669
|
+
const rc = box.getBoundingRect( );
|
|
670
|
+
const size = Math.round( isFlat( side ) ? rc.height : rc.width );
|
|
594
671
|
|
|
595
|
-
|
|
596
|
-
|
|
672
|
+
if( size > 0 ) {
|
|
673
|
+
band.size = size;
|
|
674
|
+
}
|
|
597
675
|
}
|
|
598
676
|
|
|
599
677
|
if( band.root ) {
|
|
@@ -761,8 +839,11 @@ export class DockingView extends Component<DockingViewProps, DockingViewEvents>
|
|
|
761
839
|
*
|
|
762
840
|
* a side of a tool beside it, above or under it
|
|
763
841
|
* the middle of another tool one of its tabs
|
|
764
|
-
*
|
|
842
|
+
* a side of the contents the band of that side, after its tools
|
|
765
843
|
* anywhere else it floats, if it may
|
|
844
|
+
*
|
|
845
|
+
* The first three only for a band the tool may be docked in (`sides`):
|
|
846
|
+
* the left and the right ones, unless the host says otherwise.
|
|
766
847
|
*/
|
|
767
848
|
|
|
768
849
|
private dropTool( x: number, y: number ): Drop {
|
|
@@ -777,8 +858,10 @@ export class DockingView extends Component<DockingViewProps, DockingViewEvents>
|
|
|
777
858
|
|
|
778
859
|
const inside = ( rect: DockRect ) => dropZone( rect, x, y, 1 ) !== null;
|
|
779
860
|
|
|
780
|
-
// the width the tool brings to a band: the one it has now, a third of the box at most
|
|
861
|
+
// the width the tool brings to a band: the one it has now, a third of the box at most;
|
|
862
|
+
// and its height, for a band that lies flat
|
|
781
863
|
const width = Math.round( Math.max( MIN_SIZE, Math.min( ( float?.w ?? page.w ) || BAND_SIZE, within.w / 3 ) ) );
|
|
864
|
+
const height = Math.round( Math.max( MIN_SIZE, Math.min( ( float?.h ?? page.h ) || BAND_HEIGHT, within.h / 3 ) ) );
|
|
782
865
|
|
|
783
866
|
const floating = ( ): Drop => {
|
|
784
867
|
if( !this.mayFloat( panel ) ) {
|
|
@@ -800,44 +883,52 @@ export class DockingView extends Component<DockingViewProps, DockingViewEvents>
|
|
|
800
883
|
return floating( );
|
|
801
884
|
}
|
|
802
885
|
|
|
803
|
-
// a tool of a band
|
|
804
|
-
for( const
|
|
805
|
-
|
|
806
|
-
const rect = box ? this.rectOf( box ) : null;
|
|
807
|
-
const zone = rect ? dropZone( rect, x, y, SIDE_BAND ) : null;
|
|
808
|
-
|
|
809
|
-
if( !zone ) {
|
|
886
|
+
// a tool of a band, when the one that is dragged may be in that band
|
|
887
|
+
for( const side of SIDES ) {
|
|
888
|
+
if( !this.mayDock( panel, side ) ) {
|
|
810
889
|
continue;
|
|
811
890
|
}
|
|
812
891
|
|
|
813
|
-
const
|
|
892
|
+
for( const stack of stacksOf( layout[side]?.root ) ) {
|
|
893
|
+
const box = this.nodes.get( stack );
|
|
894
|
+
const rect = box ? this.rectOf( box ) : null;
|
|
895
|
+
const zone = rect ? dropZone( rect, x, y, SIDE_BAND ) : null;
|
|
814
896
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
}
|
|
897
|
+
if( !zone ) {
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
819
900
|
|
|
820
|
-
|
|
821
|
-
if( stack === own && panels.length === 1 ) {
|
|
822
|
-
return null;
|
|
823
|
-
}
|
|
901
|
+
const panels = visiblePanels( stack, hidden );
|
|
824
902
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
903
|
+
if( zone === "center" ) {
|
|
904
|
+
// the middle of its own stack: it leaves it
|
|
905
|
+
return stack === own ? floating( ) : { rect, apply: ( ) => dockMove( layout, panel, panels[0], "tab" ) };
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// beside the stack it is alone in: it is already there
|
|
909
|
+
if( stack === own && panels.length === 1 ) {
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// Half of the stack is shown. On the left or on the right, the
|
|
914
|
+
// band gets wider instead: the two tools keep their width. In
|
|
915
|
+
// a band that lies flat, it is above and under, and its height.
|
|
916
|
+
const half = ( zone === "left" || zone === "right" ? rect.w : rect.h ) / 2;
|
|
917
|
+
return { rect: sideRect( rect, zone, half ), apply: ( ) => dockMove( layout, panel, panels[0], zone, isFlat( side ) ? height : width ) };
|
|
918
|
+
}
|
|
829
919
|
}
|
|
830
920
|
|
|
831
|
-
//
|
|
921
|
+
// a side of the contents: the band of that side
|
|
832
922
|
const area = this.rectOf( this.area );
|
|
833
|
-
const side =
|
|
923
|
+
const side = this.edgeOf( panel, area, x, y );
|
|
834
924
|
|
|
835
925
|
if( !side ) {
|
|
836
926
|
return floating( );
|
|
837
927
|
}
|
|
838
928
|
|
|
929
|
+
const flat = isFlat( side );
|
|
839
930
|
const band = this.bands.get( side );
|
|
840
|
-
const root = layout[side]
|
|
931
|
+
const root = layout[side]?.root;
|
|
841
932
|
const shown = stacksOf( root ).filter( stack => isShown( stack, hidden ) );
|
|
842
933
|
|
|
843
934
|
// the only tool of that band: it is already there
|
|
@@ -845,14 +936,60 @@ export class DockingView extends Component<DockingViewProps, DockingViewEvents>
|
|
|
845
936
|
return null;
|
|
846
937
|
}
|
|
847
938
|
|
|
848
|
-
// A band that shows nothing is made beside the contents, as
|
|
849
|
-
// the tool is now. Otherwise the tool goes
|
|
850
|
-
// holds: one more row of it, or its lower half
|
|
851
|
-
|
|
939
|
+
// A band that shows nothing is made beside the contents, as big as
|
|
940
|
+
// the tool is now. Otherwise the tool goes after what the band
|
|
941
|
+
// holds: one more row of it, or its lower half; one more column, or
|
|
942
|
+
// its right half, for a band that lies flat.
|
|
943
|
+
const size = flat ? height : width;
|
|
944
|
+
const count = root?.type === "split" && root.dir === ( flat ? "row" : "column" ) ? root.children.filter( child => isShown( child, hidden ) ).length : 1;
|
|
852
945
|
const bandRect = band ? this.rectOf( band ) : null;
|
|
853
|
-
const rect = bandRect ? sideRect( bandRect, "bottom", bandRect.h / ( rows + 1 ) ) : sideRect( area, side, width );
|
|
854
946
|
|
|
855
|
-
|
|
947
|
+
let rect: DockRect;
|
|
948
|
+
|
|
949
|
+
if( !bandRect ) {
|
|
950
|
+
rect = sideRect( area, side, size );
|
|
951
|
+
}
|
|
952
|
+
else if( flat ) {
|
|
953
|
+
rect = sideRect( bandRect, "right", bandRect.w / ( count + 1 ) );
|
|
954
|
+
}
|
|
955
|
+
else {
|
|
956
|
+
rect = sideRect( bandRect, "bottom", bandRect.h / ( count + 1 ) );
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
return { rect, apply: ( ) => dockBand( layout, panel, side, band ? 0 : size ) };
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* The side of the contents a point is near, among those the band of
|
|
964
|
+
* which a tool may be docked in; the nearest one, null for none.
|
|
965
|
+
*
|
|
966
|
+
* A point beyond the side counts too: it is between the contents and
|
|
967
|
+
* the band. But it has to be along the side: over a band of another
|
|
968
|
+
* side, it is not near this one.
|
|
969
|
+
*/
|
|
970
|
+
|
|
971
|
+
private edgeOf( panel: string, area: DockRect, x: number, y: number ): DockSide {
|
|
972
|
+
const along = ( side: DockSide ) => isFlat( side ) ? x >= area.x && x < area.x + area.w : y >= area.y && y < area.y + area.h;
|
|
973
|
+
|
|
974
|
+
// how deep the point is from each side: 0 on it, 1 where its zone ends
|
|
975
|
+
const depth = ( side: DockSide ): number => {
|
|
976
|
+
switch( side ) {
|
|
977
|
+
case "left": return ( x - area.x ) / SIDE_BAND;
|
|
978
|
+
case "right": return ( area.x + area.w - x ) / SIDE_BAND;
|
|
979
|
+
case "top": return ( y - area.y ) / SIDE_BAND;
|
|
980
|
+
case "bottom": return ( area.y + area.h - y ) / SIDE_BAND;
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
|
|
984
|
+
let nearest: DockSide = null;
|
|
985
|
+
|
|
986
|
+
for( const side of SIDES ) {
|
|
987
|
+
if( this.mayDock( panel, side ) && along( side ) && depth( side ) < 1 && ( nearest === null || depth( side ) < depth( nearest ) ) ) {
|
|
988
|
+
nearest = side;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
return nearest;
|
|
856
993
|
}
|
|
857
994
|
|
|
858
995
|
/**
|