x4js 2.3.9 → 2.3.11

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.
@@ -38,6 +38,7 @@ interface DropdownEvents extends PopupEvents {
38
38
 
39
39
  interface DropdownProps extends Omit<PopupProps,"content"> {
40
40
  items: ListItem[];
41
+ renderer?: ( item: ListItem ) => Component;
41
42
  }
42
43
 
43
44
 
@@ -49,7 +50,7 @@ export class DropdownList extends Popup<DropdownProps,DropdownEvents> {
49
50
  constructor( props: DropdownProps ) {
50
51
  super( props );
51
52
 
52
- this._list = new Listbox( { items: props.items } );
53
+ this._list = new Listbox( { items: props.items, renderer: props.renderer } );
53
54
  this.setContent( this._list );
54
55
 
55
56
  this.addDOMEvent( "mousedown", ( ev: Event ) => {
@@ -81,7 +82,7 @@ interface ComboboxEvents extends ComponentEvents {
81
82
  interface ComboboxProps extends Omit<ComponentProps,"content"> {
82
83
  label?: string;
83
84
  name?: string;
84
- value?: string;
85
+ value?: ListboxID;
85
86
  labelWidth?: number | string;
86
87
  readonly?: boolean;
87
88
  required?: boolean;
@@ -145,10 +146,9 @@ export class Combobox extends Component<ComboboxProps,ComboboxEvents> {
145
146
  const itm = list.getItem(sel);
146
147
  if( itm ) {
147
148
  list.select( sel, false );
149
+ this._input.setValue( itm.text as string );
148
150
  }
149
151
 
150
- this._input.setValue( itm.text as string );
151
-
152
152
  if( !this._prevent_close ) {
153
153
  this._popup.show( false );
154
154
  }
@@ -162,7 +162,7 @@ export class Combobox extends Component<ComboboxProps,ComboboxEvents> {
162
162
  }
163
163
  });
164
164
 
165
- if( props.value ) {
165
+ if( props.value!==undefined && props.value!==null ) {
166
166
  _select( props.value );
167
167
  }
168
168
 
@@ -1,3 +1,4 @@
1
+ export * from "./autocomplete/autocomplete"
1
2
  export * from "./boxes/boxes"
2
3
  export * from "./breadcrumb/breadcrumb"
3
4
  export * from "./btngroup/btngroup"
@@ -5,6 +6,7 @@ export * from "./button/button"
5
6
  export * from "./calendar/calendar"
6
7
  export * from "./canvas/canvas"
7
8
  export * from "./canvas/canvas_ex"
9
+ export * from "./chart/chart"
8
10
  export * from "./checkbox/checkbox"
9
11
  export * from "./colorinput/colorinput"
10
12
  export * from "./colorpicker/colorpicker"
@@ -19,10 +19,15 @@ interface HeaderProps extends Omit<ComponentProps,"content"> {
19
19
  }
20
20
 
21
21
  const CELL_MW = 8;
22
+ const MAX_COLS = 10; // columns with a default name (ref-c1...ref-c10) handled by the listbox css
22
23
 
23
24
  /**
24
25
  * by default when a header item is resized, the 'target' style '--{name}-width' is updated
25
- *
26
+ *
27
+ * a Listbox handles 10 columns at most: the width of its cells is given by the
28
+ * css classes ref-c1 to ref-c10 (text of the item, then sub_cols).
29
+ * above, give a name to the header item and handle '--{name}-width' in your own css.
30
+ *
26
31
  * @cssvar
27
32
  * ```
28
33
  * --header-background-hover
@@ -40,6 +45,10 @@ export class Header extends HBox<HeaderProps> {
40
45
  constructor( props: HeaderProps ) {
41
46
  super( props );
42
47
 
48
+ if( props.items?.some( ( x, index ) => !x.name && index>=MAX_COLS ) ) {
49
+ console.warn( `Header: ${props.items.length} columns, only the first ${MAX_COLS} get a width in a Listbox (ref-c1 to ref-c${MAX_COLS}).` );
50
+ }
51
+
43
52
  this._els = props.items?.map( (x,index) => {
44
53
 
45
54
  if( !x.name ) {
@@ -65,16 +74,42 @@ export class Header extends HBox<HeaderProps> {
65
74
  this._calc_sizes( );
66
75
  })
67
76
 
77
+ let resized = false;
78
+
68
79
  sizer.on( "start", ( ) => {
69
- //todo: remove flex on all cols
80
+ // freeze the flex columns: if they keep taking the remaining space, the left side
81
+ // of the resized cell moves, and the sizer (which measures from it) runs away
82
+ resized = false;
83
+
84
+ this._els.forEach( c => {
85
+ const flex = c.getInternalData( "flex" );
86
+ if( flex ) {
87
+ c.setInternalData( "saved-flex", flex );
88
+ c.setInternalData( "flex", 0 );
89
+ c.setInternalData( "width", Math.round( c.getBoundingRect( ).width ) );
90
+ }
91
+ });
70
92
  } );
71
93
 
72
94
  sizer.on( "stop", ( ) => {
73
- //todo: restore flex
95
+ this._els.forEach( c => {
96
+ const flex = c.getInternalData( "saved-flex" );
97
+ if( flex ) {
98
+ c.setInternalData( "saved-flex", 0 );
99
+
100
+ // the resized column keeps its new width
101
+ if( c!==cell || !resized ) {
102
+ c.setInternalData( "flex", flex );
103
+ }
104
+ }
105
+ });
106
+
107
+ this._calc_sizes( );
74
108
  } );
75
109
 
76
110
  sizer.on( "resize", ( ev ) => {
77
111
  //cell.setStyleValue( "flexGrow", "0" );
112
+ resized = true;
78
113
  const sze = ev.size<CELL_MW ? CELL_MW : ev.size;
79
114
  cell.setInternalData("flex",0);
80
115
  cell.setInternalData("width",sze);
@@ -131,11 +131,10 @@
131
131
  overflow: hidden;
132
132
  }
133
133
 
134
- .ref-c1 { width: var( --ref-c1-width); }
135
- .ref-c2 { width: var( --ref-c2-width); }
136
- .ref-c3 { width: var( --ref-c3-width); }
137
- .ref-c4 { width: var( --ref-c4-width); }
138
- .ref-c5 { width: var( --ref-c5-width); }
134
+ // 10 columns max (see Header)
135
+ @for $i from 1 through 10 {
136
+ .ref-c#{$i} { width: var( --ref-c#{$i}-width ); }
137
+ }
139
138
 
140
139
  //&:active{
141
140
  //background-color: var( --color-80 );
@@ -35,7 +35,7 @@ export type ListboxID = number | string;
35
35
  export interface ListItem {
36
36
  id: ListboxID;
37
37
  text: string | UnsafeHtml;
38
- sub_cols?: (string | UnsafeHtml | Component)[]; // extra columns
38
+ sub_cols?: (string | UnsafeHtml | Component)[]; // extra columns (9 max: 10 columns with text), a null value creates no cell
39
39
 
40
40
  iconId?: string;
41
41
  data?: any;
@@ -1,4 +1,10 @@
1
1
 
2
+ /**
3
+ * @cssvar
4
+ * --select-border
5
+ * --select-border-focus
6
+ * --select-background
7
+ */
2
8
 
3
9
  .x4select,
4
10
  ::picker(select) {
@@ -8,8 +14,8 @@
8
14
  outline: none;
9
15
 
10
16
  border-radius: 0;
11
- border: 1px solid var( --border );
12
- background-color: white;
17
+ border: 1px solid var( --select-border, var( --border ) );
18
+ background-color: var( --select-background, white );
13
19
  }
14
20
 
15
21
  .x4select {
@@ -21,23 +27,26 @@
21
27
  background-position: right 6px center;
22
28
  background-size: 12px 8px;
23
29
  padding-right: 23px;
24
- }
25
30
 
26
- //-- global saddly
27
- option {
28
- padding: 5px;
29
- }
31
+ &:focus-visible {
32
+ border-color: var( --select-border-focus, var( --accent-background ) );
33
+ }
30
34
 
31
- option:checked {
32
- background-color: var( --accent-background );
33
- color: var( --accent-color );
34
- }
35
+ .select-option {
36
+ padding: 5px;
35
37
 
36
- option:hover {
37
- background-color: var( --accent-background-focus );
38
- color: var( --accent-color );
39
- }
38
+ &:checked {
39
+ background-color: var( --accent-background );
40
+ color: var( --accent-color );
41
+ }
42
+
43
+ &:hover {
44
+ background-color: var( --accent-background-focus );
45
+ color: var( --accent-color );
46
+ }
40
47
 
41
- option::checkmark {
42
- display: none;
48
+ &::checkmark {
49
+ display: none;
50
+ }
51
+ }
43
52
  }
@@ -16,8 +16,8 @@
16
16
 
17
17
 
18
18
  import { EventCallback } from '../../core/core_events';
19
- import { Component, ComponentEvent, ComponentProps, EvChange, EvFocus } from '../../core/component';
20
- import { class_ns } from '../../core/core_tools';
19
+ import { Component, ComponentEvents, ComponentProps, EvChange, EvFocus } from '../../core/component';
20
+ import { class_ns, IComponentInterface, IFormElement, isArray } from '../../core/core_tools';
21
21
 
22
22
  import { ListboxID, ListItem } from '../components';
23
23
 
@@ -29,14 +29,15 @@ import "./select.module.scss"
29
29
 
30
30
  export interface SelectProps extends ComponentProps {
31
31
  name?: string;
32
- value: ListboxID;
32
+ value?: ListboxID;
33
33
  items: ListItem[];
34
34
  multiple?: boolean;
35
+ required?: boolean;
35
36
  change?: EventCallback<EvChange>;
36
37
  focus?: EventCallback<EvFocus>;
37
38
  }
38
39
 
39
- interface SelectEvents extends ComponentEvent {
40
+ interface SelectEvents extends ComponentEvents {
40
41
  focus: EvFocus;
41
42
  change: EvChange;
42
43
  }
@@ -60,17 +61,21 @@ export class Select extends Component<SelectProps,SelectEvents> {
60
61
  this.setAttribute( "name", props.name );
61
62
  }
62
63
 
64
+ if( props.multiple ) {
65
+ this.setAttribute( "multiple", true );
66
+ }
67
+
68
+ if( props.required ) {
69
+ this.setAttribute( "required", true );
70
+ }
71
+
63
72
  this.setItems( props.items );
64
-
73
+
65
74
  this.addDOMEvent( "blur", ( e ) => { this.on_focus(e,true);} );
66
75
  this.addDOMEvent( "focus", ( e ) => { this.on_focus(e,false);} );
67
76
  this.addDOMEvent( "input", ( e ) => { this.on_change(e as InputEvent); });
68
77
 
69
- if( props.multiple ) {
70
- this.setAttribute( "multiple", true );
71
- }
72
-
73
- if( props.value ) {
78
+ if( props.value!==undefined ) {
74
79
  this.setValue( props.value );
75
80
  }
76
81
  }
@@ -107,14 +112,21 @@ export class Select extends Component<SelectProps,SelectEvents> {
107
112
  */
108
113
 
109
114
  setItems( items: ListItem[] ) {
115
+ const sel = this._items ? this.getSelValues( ) : [];
116
+
110
117
  this._items = [...items];
111
118
  this.setContent( items.map( x => {
112
119
  return new Component( {
113
120
  tag: "option",
121
+ cls: x.cls ? "select-option "+x.cls : "select-option",
114
122
  attrs: { value: x.id },
115
123
  content: x.text,
116
124
  });
117
125
  } ));
126
+
127
+ if( sel.length ) {
128
+ this.setSelValues( sel );
129
+ }
118
130
  }
119
131
 
120
132
  /**
@@ -136,6 +148,44 @@ export class Select extends Component<SelectProps,SelectEvents> {
136
148
  const el = (this.dom as HTMLSelectElement);
137
149
  el.selectedIndex = this._items.findIndex( x => x.id===value );
138
150
  }
151
+
152
+ /**
153
+ * @returns all selected ids (multiple)
154
+ */
155
+
156
+ public getSelValues( ) : ListboxID[] {
157
+ const el = (this.dom as HTMLSelectElement);
158
+ return Array.from( el.selectedOptions ).map( o => this._items[o.index].id );
159
+ }
160
+
161
+ /**
162
+ * @param values ids to select (multiple)
163
+ */
164
+
165
+ public setSelValues( values: ListboxID[] ) {
166
+ const el = (this.dom as HTMLSelectElement);
167
+ this._items.forEach( ( x, i ) => {
168
+ el.options[i].selected = values.includes( x.id );
169
+ });
170
+ }
171
+
172
+ /**
173
+ *
174
+ */
175
+
176
+ override queryInterface<T extends IComponentInterface>( name: string ): T {
177
+ if( name=="form-element" ) {
178
+ const i: IFormElement = {
179
+ getRawValue: ( ): any => { return this.props.multiple ? this.getSelValues() : this.getValue(); },
180
+ setRawValue: ( v: any ) => { isArray(v) ? this.setSelValues(v) : this.setValue(v); },
181
+ isValid: ( ) => { return (this.dom as HTMLSelectElement).checkValidity(); }
182
+ };
183
+
184
+ return i as unknown as T;
185
+ }
186
+
187
+ return super.queryInterface( name );
188
+ }
139
189
  }
140
190
 
141
191
 
@@ -97,6 +97,37 @@
97
97
  &.align-right {
98
98
  justify-content: end;
99
99
  }
100
+
101
+ &.editing {
102
+ position: relative;
103
+ }
104
+
105
+ // inline editor: cover the cell
106
+ .cell-editor {
107
+ position: absolute;
108
+ inset: 0;
109
+ width: 100%;
110
+ height: 100%;
111
+ box-sizing: border-box;
112
+ margin: 0;
113
+ padding: 0 4px;
114
+
115
+ font: inherit;
116
+ outline: none;
117
+ border: 2px solid var( --spreadsheet-row-background-sel );
118
+ border-radius: 0;
119
+
120
+ color: var( --spreadsheet-cell-color );
121
+ background-color: var( --spreadsheet-background );
122
+ }
123
+
124
+ &.align-center .cell-editor {
125
+ text-align: center;
126
+ }
127
+
128
+ &.align-right .cell-editor {
129
+ text-align: right;
130
+ }
100
131
  }
101
132
 
102
133
  .x4viewport {