x4js 2.3.27 → 2.3.29

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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/aicontext.md +412 -24
  4. package/package.json +8 -3
  5. package/src/components/autocomplete/autocomplete.ts +5 -1
  6. package/src/components/bargraph/bargraph.module.scss +44 -0
  7. package/src/components/bargraph/bargraph.ts +362 -0
  8. package/src/components/base.scss +71 -1
  9. package/src/components/boxes/boxes.ts +2 -2
  10. package/src/components/button/button.module.scss +4 -3
  11. package/src/components/calendar/calendar.module.scss +6 -5
  12. package/src/components/calendar/calendar.ts +14 -8
  13. package/src/components/canvas/canvas.ts +5 -4
  14. package/src/components/carousel/carousel.module.scss +165 -0
  15. package/src/components/carousel/carousel.ts +423 -0
  16. package/src/components/chart/chart.module.scss +8 -0
  17. package/src/components/chart/chart.ts +170 -12
  18. package/src/components/checkbox/checkbox.module.scss +67 -1
  19. package/src/components/checkbox/checkbox.ts +22 -6
  20. package/src/components/colorinput/colorinput.module.scss +1 -0
  21. package/src/components/combobox/combobox.module.scss +6 -2
  22. package/src/components/components.ts +3 -0
  23. package/src/components/gantt/gantt.module.scss +462 -0
  24. package/src/components/gantt/gantt.ts +1254 -0
  25. package/src/components/gridview/gridview.module.scss +6 -0
  26. package/src/components/input/input.module.scss +1 -1
  27. package/src/components/knob/knob.module.scss +113 -0
  28. package/src/components/knob/knob.ts +355 -0
  29. package/src/components/mapview/mapview.module.scss +177 -0
  30. package/src/components/mapview/mapview.ts +764 -0
  31. package/src/components/messages/messages.module.scss +9 -4
  32. package/src/components/planner/planner.module.scss +688 -0
  33. package/src/components/planner/planner.ts +1792 -0
  34. package/src/components/propgrid/progrid.module.scss +13 -5
  35. package/src/components/radio/radio.module.scss +58 -32
  36. package/src/components/radio/radio.ts +12 -1
  37. package/src/components/sizers/sizer.ts +17 -5
  38. package/src/components/slider/slider.module.scss +22 -20
  39. package/src/components/spreadsheet/spreadsheet.module.scss +6 -0
  40. package/src/components/switch/switch.module.scss +68 -44
  41. package/src/components/switch/switch.ts +6 -3
  42. package/src/components/tabs/tabs.module.scss +2 -0
  43. package/src/components/tabs/tabs.ts +1 -0
  44. package/src/components/tag/tag.module.scss +4 -4
  45. package/src/components/textarea/textarea.module.scss +9 -0
  46. package/src/components/textarea/textarea.ts +1 -0
  47. package/src/components/textedit/textedit.module.scss +5 -2
  48. package/src/components/themes.scss +20 -6
  49. package/src/components/treeview/treeview.module.scss +6 -1
  50. package/src/components/treeview/treeview.ts +1 -0
  51. package/src/components/video/video.module.scss +5 -2
  52. package/src/components/video/video.ts +14 -1
  53. package/src/core/core_i18n.ts +65 -0
  54. package/src/core/core_pdf.ts +17 -0
  55. package/src/core/core_react.ts +12 -18
  56. package/src/core/core_svg.ts +9 -6
  57. package/src/core/core_tools.ts +44 -0
  58. package/src/x4doc.ts +3 -0
@@ -21,7 +21,7 @@ import { clamp, formatNumber, IRect, isNumber, sanitizeHtml } from '../../core/c
21
21
 
22
22
  import "./chart.module.scss"
23
23
 
24
- export type ChartType = "line" | "area" | "bar" | "pie" | "donut";
24
+ export type ChartType = "line" | "area" | "bar" | "pie" | "donut" | "radar";
25
25
 
26
26
  export interface ChartTick {
27
27
  value: number; // on x axis: index of the point
@@ -38,9 +38,9 @@ export interface ChartAxis {
38
38
 
39
39
  export interface ChartSerie {
40
40
  name?: string;
41
- values: number[]; // undefined: no value (hole in the line)
41
+ values: number[]; // undefined: no value (hole in the line; at the center of a radar)
42
42
  color?: string; // default: palette --chart-color-1..8
43
- fill?: string; // area fill color
43
+ fill?: string; // area fill color ("none" on a radar: the line only)
44
44
  dots?: boolean; // always show the points
45
45
  }
46
46
 
@@ -57,7 +57,7 @@ interface ChartEvents extends ComponentEvents {
57
57
  export interface ChartProps extends Omit<SvgProps,"svg"|"viewbox"|"content"> {
58
58
  type?: ChartType; // default "line"
59
59
  series: ChartSerie[]; // pie & donut use the first serie
60
- labels?: string[]; // default: 1, 2, 3...
60
+ labels?: string[]; // default: 1, 2, 3...; radar: the name of each axis
61
61
  xaxis?: ChartAxis;
62
62
  yaxis?: ChartAxis;
63
63
  legend?: boolean; // default: shown when there is more than one named serie
@@ -211,7 +211,10 @@ function drawBar( grp: SvgGroup, x: number, w: number, y0: number, y1: number, r
211
211
 
212
212
 
213
213
  /**
214
- * Minimal chart (line, area, bar, pie, donut) drawn in svg.
214
+ * Minimal chart (line, area, bar, pie, donut, radar) drawn in svg.
215
+ *
216
+ * radar: an axis for each point, from the center (yaxis.min, 0 by default) to the outer ring
217
+ * (yaxis.max); each serie is a closed line, tinted inside. At least 3 points.
215
218
  *
216
219
  * @example
217
220
  * new Chart( {
@@ -335,6 +338,11 @@ export class Chart extends SvgComponent<ChartProps,ChartEvents> {
335
338
  * make an element clickable with a tooltip
336
339
  */
337
340
 
341
+ private _tip( serie: number, index: number, value: number ) {
342
+ const name = this._series[serie].name ? ` · ${this._series[serie].name}` : '';
343
+ return `${this._label(index)}${name}: ${this._format(value)}`;
344
+ }
345
+
338
346
  private _mark<T extends SvgShape | SvgPath>( item: T, serie: number, index: number, tip: string ): T {
339
347
  item.setAttr( "data-serie", serie+"" )
340
348
  .setAttr( "data-index", index+"" )
@@ -381,6 +389,9 @@ export class Chart extends SvgComponent<ChartProps,ChartEvents> {
381
389
  if( pie ) {
382
390
  this._drawPie( bld, area );
383
391
  }
392
+ else if( this._type=="radar" ) {
393
+ this._drawRadar( bld, area );
394
+ }
384
395
  else {
385
396
  this._drawXY( bld, area );
386
397
  }
@@ -648,11 +659,6 @@ export class Chart extends SvgComponent<ChartProps,ChartEvents> {
648
659
  grp.clip( id );
649
660
  }
650
661
 
651
- const tip = ( s: number, i: number, v: number ) => {
652
- const name = series[s].name ? ` · ${series[s].name}` : '';
653
- return `${this._label(i)}${name}: ${this._format(v)}`;
654
- }
655
-
656
662
  if( band ) {
657
663
  const n = stacked ? 1 : Math.max( 1, series.length );
658
664
  const w = Math.min( xstep*0.8/n, MAX_BAR );
@@ -684,7 +690,7 @@ export class Chart extends SvgComponent<ChartProps,ChartEvents> {
684
690
  }
685
691
 
686
692
  this._paint( bar.addClass( "bar" ), s, serie.color );
687
- this._mark( bar, s, i, tip( s, i, v ) );
693
+ this._mark( bar, s, i, this._tip( s, i, v ) );
688
694
  }
689
695
  });
690
696
  }
@@ -757,11 +763,163 @@ export class Chart extends SvgComponent<ChartProps,ChartEvents> {
757
763
  for( const p of pts ) {
758
764
  const dot = dots.circle( p.x, p.y, 4 ).addClass( visible ? "dot" : "dot ghost" );
759
765
  this._paint( dot, s, serie.color );
760
- this._mark( dot, s, p.i, tip( s, p.i, value( s, p.i ) ?? 0 ) );
766
+ this._mark( dot, s, p.i, this._tip( s, p.i, value( s, p.i ) ?? 0 ) );
761
767
  }
762
768
  }
763
769
  }
764
770
  });
765
771
  }
766
772
  }
773
+
774
+ /**
775
+ * radar: an axis for each point, a ring for each tick of the y scale
776
+ */
777
+
778
+ private _drawRadar( bld: SvgBuilder, area: IRect ) {
779
+
780
+ const series = this._series;
781
+ const xaxis = this._xaxis;
782
+ const yaxis = this._yaxis;
783
+
784
+ // number of axes
785
+ let count = xaxis.max ?? 0;
786
+ if( xaxis.max===undefined ) {
787
+ series.forEach( s => { count = Math.max( count, s.values?.length ?? 0 ); } );
788
+ }
789
+
790
+ // less than 3 axes make no surface
791
+ if( count<3 ) {
792
+ return;
793
+ }
794
+
795
+ const value = ( s: number, i: number ) => {
796
+ const v = series[s].values?.[i];
797
+ return isNumber(v) ? v : undefined;
798
+ }
799
+
800
+ // room for the labels around
801
+ let maxw = 0;
802
+ for( let i=0; i<count; i++ ) {
803
+ maxw = Math.max( maxw, textWidth( this._label(i) ) );
804
+ }
805
+
806
+ const cx = area.left + area.width/2;
807
+ const cy = area.top + area.height/2;
808
+ const radius = Math.min( area.width/2 - maxw - 8, area.height/2 - 16 );
809
+
810
+ if( radius<=10 ) {
811
+ return;
812
+ }
813
+
814
+ // scale: the center at 0, lower for negative values
815
+ let dmin = 0;
816
+ let dmax = -Infinity;
817
+
818
+ series.forEach( ( _, s ) => {
819
+ for( let i=0; i<count; i++ ) {
820
+ const v = value( s, i );
821
+ if( v!==undefined ) {
822
+ dmin = Math.min( dmin, v );
823
+ dmax = Math.max( dmax, v );
824
+ }
825
+ }
826
+ });
827
+
828
+ if( dmax==-Infinity ) {
829
+ dmax = 1;
830
+ }
831
+
832
+ const nice = niceScale( yaxis.min ?? dmin, yaxis.max ?? dmax, clamp( Math.floor( radius/30 ), 2, 6 ) );
833
+ const min = yaxis.min ?? nice.min;
834
+ let max = yaxis.max ?? nice.max;
835
+ if( max<=min ) {
836
+ max = min+1;
837
+ }
838
+
839
+ const step = yaxis.step>0 ? yaxis.step : nice.step;
840
+ const rings = ( yaxis.ticks ?? makeTicks( min, max, step, v => this._format(v) ) ).filter( t => t.value>min && t.value<=max );
841
+
842
+ // the axis i: 0 at the top, clockwise; a value out of the bounds stays on them
843
+ const angle = ( i: number ) => ( i/count*360 - 90 ) * Math.PI/180;
844
+ const point = ( i: number, v: number ) => {
845
+ const r = ( clamp( v, min, max ) - min ) / ( max-min ) * radius;
846
+ return { x: cx + r*Math.cos( angle(i) ), y: cy + r*Math.sin( angle(i) ) };
847
+ }
848
+
849
+ const polygon = ( grp: SvgGroup, v: ( i: number ) => number ) => {
850
+ const pth = grp.path( );
851
+ for( let i=0; i<count; i++ ) {
852
+ const p = point( i, v(i) );
853
+ if( i==0 ) {
854
+ pth.moveTo( p.x, p.y );
855
+ }
856
+ else {
857
+ pth.lineTo( p.x, p.y );
858
+ }
859
+ }
860
+
861
+ return pth.closePath( );
862
+ }
863
+
864
+ // grid & axes
865
+ const grid = bld.group( ).addClass( "axis radar" );
866
+
867
+ for( const t of rings ) {
868
+ polygon( grid, ( ) => t.value ).addClass( "grid" );
869
+ }
870
+
871
+ for( let i=0; i<count; i++ ) {
872
+ const p = point( i, max );
873
+ grid.path( ).moveTo( cx, cy ).lineTo( p.x, p.y ).addClass( "base" );
874
+
875
+ // the label beyond the end of its axis, on the side of the axis
876
+ const cos = Math.cos( angle(i) );
877
+ const sin = Math.sin( angle(i) );
878
+ const halign = Math.abs( cos )<0.3 ? 'center' : ( cos>0 ? 'left' : 'right' );
879
+ const valign = Math.abs( sin )<0.3 ? 'center' : ( sin>0 ? 'top' : 'bottom' );
880
+ drawText( grid, cx + ( radius+8 )*cos, cy + ( radius+8 )*sin, this._label(i), halign, valign );
881
+ }
882
+
883
+ // the values of the rings, along the first axis
884
+ for( const t of rings ) {
885
+ drawText( grid, cx+4, point( 0, t.value ).y, t.text, 'left', 'center' );
886
+ }
887
+
888
+ // series
889
+ const grp = bld.group( ).addClass( "plot" );
890
+ const areas = grp.group( );
891
+ const lines = grp.group( );
892
+ const dots = grp.group( );
893
+
894
+ const ghosts = series.length*count<=MAX_GHOSTS;
895
+
896
+ series.forEach( ( serie, s ) => {
897
+ // a value missing is at the center
898
+ const at = ( i: number ) => value( s, i ) ?? min;
899
+
900
+ const pth = polygon( areas, at ).addClass( "area" );
901
+ this._paint( pth, s, serie.color );
902
+
903
+ if( serie.fill ) {
904
+ pth.setStyle( "fill", serie.fill ).setStyle( "fillOpacity", "1" );
905
+ }
906
+
907
+ this._paint( polygon( lines, at ).addClass( "line" ), s, serie.color );
908
+
909
+ // points: always visible, or only on hover (ghost)
910
+ if( serie.dots || ghosts ) {
911
+ for( let i=0; i<count; i++ ) {
912
+ const v = value( s, i );
913
+ if( v===undefined ) {
914
+ continue;
915
+ }
916
+
917
+ const p = point( i, v );
918
+ const dot = dots.circle( p.x, p.y, 4 ).addClass( serie.dots ? "dot" : "dot ghost" );
919
+ this._paint( dot, s, serie.color );
920
+ this._mark( dot, s, i, this._tip( s, i, v ) );
921
+ }
922
+ }
923
+ });
924
+ }
767
925
  }
@@ -25,6 +25,16 @@
25
25
  --checkbox-check-border: var( --border );
26
26
  --checkbox-check-border-hover: var( --border-hover );
27
27
  --checkbox-check-color: var( --accent-color );
28
+ --checkbox-focus: var( --accent-background );
29
+
30
+ --checkbox-button-border: var( --border );
31
+ --checkbox-button-color: var( --text-ternary );
32
+ --checkbox-button-color-checked: var( --accent-background-active );
33
+ --checkbox-button-background-checked: var( --background-secondary );
34
+ --checkbox-button-border-checked: var( --accent-background );
35
+
36
+ --checkbox-danger-color: var( --alert-background );
37
+ --checkbox-danger-background: var( --alert-background-light );
28
38
  }
29
39
 
30
40
  .x4checkbox {
@@ -136,8 +146,64 @@
136
146
  order: 2;
137
147
  }
138
148
 
139
- &.left #label {
149
+ // its label has no id: the element, before the box
150
+ &.left label {
140
151
  order: 0;
141
152
  text-align: left;
142
153
  }
154
+ }
155
+
156
+ // button: a button, its background and its border marked when checked (no bold: its width
157
+ // would change with its state). The box hidden, its input still in the page (not display: none),
158
+ // so that Tab and the space bar reach it; the focus of the keyboard shown around it
159
+ .x4checkbox.button {
160
+ // the colour of its icon and its text
161
+ --checkbox-ink: var( --checkbox-button-color );
162
+
163
+ gap: 0;
164
+ margin: 0;
165
+ padding: 8px;
166
+ border: 1px solid var( --checkbox-button-border );
167
+
168
+ &.outline {
169
+ border-radius: 22px;
170
+ }
171
+
172
+ .inner {
173
+ width: 0;
174
+ height: 0;
175
+ overflow: hidden;
176
+ opacity: 0;
177
+ }
178
+
179
+ &:has(input:focus-visible) {
180
+ outline: 1px dotted var( --checkbox-focus );
181
+ outline-offset: 2px;
182
+ }
183
+
184
+ label {
185
+ --label-color: var( --checkbox-ink );
186
+
187
+ #icon {
188
+ color: var( --checkbox-ink );
189
+ fill: var( --checkbox-ink );
190
+ width: 24px;
191
+ height: 24px;
192
+ }
193
+ }
194
+
195
+ &:has(input:checked) {
196
+ --checkbox-ink: var( --checkbox-button-color-checked );
197
+ background-color: var( --checkbox-button-background-checked );
198
+ border-color: var( --checkbox-button-border-checked );
199
+ }
200
+ }
201
+
202
+ // danger: checked in the red of the alerts, its box or its button
203
+ .x4checkbox.danger {
204
+ --checkbox-background-check: var( --checkbox-danger-color );
205
+ --checkbox-background-check-hover: var( --checkbox-danger-color );
206
+ --checkbox-button-color-checked: var( --checkbox-danger-color );
207
+ --checkbox-button-background-checked: var( --checkbox-danger-background );
208
+ --checkbox-button-border-checked: var( --checkbox-danger-color );
143
209
  }
@@ -21,7 +21,8 @@ interface CheckBoxEvents extends ComponentEvents {
21
21
  */
22
22
 
23
23
  interface CheckboxProps extends ComponentProps {
24
- label: string; // The text label for the checkbox.
24
+ label?: string; // The text label for the checkbox; none: its icon alone (with a tooltip)
25
+ icon?: string; // an icon before its label
25
26
  checked?: boolean; // Optional boolean indicating if the checkbox is checked by default.
26
27
  value?: boolean | number | string; // Optional value associated with the checkbox.
27
28
  name?: string;
@@ -30,7 +31,13 @@ interface CheckboxProps extends ComponentProps {
30
31
 
31
32
  /**
32
33
  * Checkbox component that can be checked or unchecked.
33
- *
34
+ * Its look, by its class (cls): "left" (its label on the left), "button" (a button, "button
35
+ * outline" rounded), "danger" (checked in red, its box or its button). Without the box, its
36
+ * input is still reached by Tab.
37
+ *
38
+ * A toggle button is a Checkbox with the class "button":
39
+ * new Checkbox( { cls: "button", icon: icon_bold, tooltip: "Bold", change: ( ev ) => setBold( ev.value ) } )
40
+ *
34
41
  * @cssvar
35
42
  * ```
36
43
  * --checkbox-background-check
@@ -41,6 +48,14 @@ interface CheckboxProps extends ComponentProps {
41
48
  * --checkbox-check-border
42
49
  * --checkbox-check-border-hover
43
50
  * --checkbox-check-color
51
+ * --checkbox-focus
52
+ * --checkbox-button-border
53
+ * --checkbox-button-color
54
+ * --checkbox-button-color-checked
55
+ * --checkbox-button-background-checked
56
+ * --checkbox-button-border-checked
57
+ * --checkbox-danger-color
58
+ * --checkbox-danger-background
44
59
  * ```
45
60
  */
46
61
 
@@ -80,11 +95,12 @@ export class Checkbox extends Component<CheckboxProps,CheckBoxEvents> {
80
95
  })
81
96
  ]
82
97
  }),
83
- new Label( {
98
+ new Label( {
84
99
  tag: 'label',
85
- text: props.label,
86
- labelFor: inputId,
87
- id: undefined
100
+ text: props.label,
101
+ icon: props.icon,
102
+ labelFor: inputId,
103
+ id: undefined
88
104
  } ),
89
105
  ])
90
106
 
@@ -71,6 +71,7 @@
71
71
  margin: 0;
72
72
  height: 100%;
73
73
  flex-grow: 1;
74
+ min-width: 0; // narrower than its default size in a narrow box: it does not overflow
74
75
  }
75
76
 
76
77
  // the eye dropper: as high as the field, and wide enough to be aimed at
@@ -23,7 +23,7 @@
23
23
  --combobox-border: var( --border );
24
24
  --combobox-border-focus: var( --border-focus );
25
25
 
26
- --combobox-btn-background: inherit;
26
+ --combobox-btn-background: var( --background-input ); // the one of its field: inherit was the page (dark theme)
27
27
  --combobox-btn-color: var( --text-secondary );
28
28
  --combobox-btn-color-hover: var( --text-primary );
29
29
 
@@ -72,7 +72,10 @@
72
72
  &> #label {
73
73
 
74
74
  &> .x4label {
75
- padding: 0 6px 0 0;
75
+ // its text on the right of its width (labelWidth), its colon against the gap of the box:
76
+ // in a form, under the one of a TextEdit
77
+ justify-content: end;
78
+ padding: 0;
76
79
  border-bottom: 1px solid transparent;
77
80
 
78
81
  height: 100%;
@@ -107,6 +110,7 @@
107
110
  margin: 0;
108
111
  padding: 0;
109
112
  outline: none;
113
+ align-self: stretch; // the height of the field: no strip of the page above and under it
110
114
  background-color: var( --combobox-btn-background );
111
115
  color: var( --combobox-btn-color );
112
116
 
@@ -1,4 +1,5 @@
1
1
  export * from "./autocomplete/autocomplete"
2
+ export * from "./bargraph/bargraph"
2
3
  export * from "./boxes/boxes"
3
4
  export * from "./breadcrumb/breadcrumb"
4
5
  export * from "./btngroup/btngroup"
@@ -6,6 +7,7 @@ export * from "./button/button"
6
7
  export * from "./calendar/calendar"
7
8
  export * from "./canvas/canvas"
8
9
  export * from "./canvas/canvas_ex"
10
+ export * from "./carousel/carousel"
9
11
  export * from "./chart/chart"
10
12
  export * from "./checkbox/checkbox"
11
13
  export * from "./colorinput/colorinput"
@@ -22,6 +24,7 @@ export * from "./icon/icon"
22
24
  export * from "./image/image"
23
25
  export * from "./input/input"
24
26
  export * from "./keyboard/keyboard"
27
+ export * from "./knob/knob"
25
28
  export * from "./label/label"
26
29
  export * from "./link/link"
27
30
  export * from "./listbox/listbox"