x4js 1.5.5 → 1.5.6

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/lib/changelog.txt CHANGED
@@ -1,3 +1,11 @@
1
+ 2022/12/08 : 1.5.6 --------------------------------------------------------
2
+ combobox.ts - allow editable
3
+ items can be a function
4
+ populate removed
5
+
6
+ listview.ts - focus bug on popuplistview
7
+ label.ts - bug: display dangerous html elements when multiline = true
8
+
1
9
  2022/12/05 : 1.5.1 --------------------------------------------------------
2
10
  icon.ts - now accepts direct <svg>..</svg> in argument (var or other)
3
11
 
@@ -47,9 +47,36 @@ const tools_1 = require("./tools");
47
47
  class ComboBox extends layout_1.HLayout {
48
48
  constructor(props) {
49
49
  super(props);
50
- this.setDomEvent('keypress', () => this.showPopup());
51
- this.setDomEvent('click', () => this.showPopup());
50
+ if (!props.editable) {
51
+ this.setDomEvent('keypress', () => this.showPopup());
52
+ }
53
+ this.setDomEvent('click', () => {
54
+ if (this.m_props.editable) {
55
+ this.m_ui_input.focus();
56
+ }
57
+ this.showPopup();
58
+ });
59
+ this.setDomEvent("keydown", e => this._onKey(e));
52
60
  this.mapPropEvents(props, 'selectionChange');
61
+ this.m_popvis = false;
62
+ this.m_lockpop = false;
63
+ this.m_lockchg = false;
64
+ }
65
+ _onKey(e) {
66
+ if (this.m_popvis) {
67
+ if (e.key == "ArrowUp" || e.key == "ArrowDown") {
68
+ this.m_lockpop = true;
69
+ this.m_popup.handleKey(e);
70
+ this.m_lockpop = false;
71
+ e.preventDefault();
72
+ e.stopPropagation();
73
+ }
74
+ else if (e.key == "Escape") {
75
+ this._hidePopup();
76
+ e.preventDefault();
77
+ e.stopPropagation();
78
+ }
79
+ }
53
80
  }
54
81
  set items(items) {
55
82
  this.m_props.items = items;
@@ -61,16 +88,35 @@ class ComboBox extends layout_1.HLayout {
61
88
  render(props) {
62
89
  var _a;
63
90
  if (!props.renderer) {
64
- this.m_ui_input = new input_1.Input({
91
+ const input = new input_1.Input({
65
92
  flex: 1,
66
- readOnly: true,
93
+ readOnly: this.m_props.editable ? false : true,
67
94
  tabIndex: 0,
68
95
  name: props.name,
69
96
  value_hook: {
70
97
  get: () => { return this.value; },
71
98
  set: (v) => { this.value = v; }
99
+ },
100
+ dom_events: {
101
+ focus: () => {
102
+ if (this.m_props.editable && input.value.length == 0) {
103
+ this.showPopup();
104
+ }
105
+ },
106
+ input: () => {
107
+ if (this.m_lockchg) {
108
+ return;
109
+ }
110
+ const text = input.value;
111
+ this.m_selection = { id: undefined, text };
112
+ let items = this.showPopup();
113
+ if (items && items.length && items[0].text == text) {
114
+ this.m_selection = { id: items[0].id, text };
115
+ }
116
+ }
72
117
  }
73
118
  });
119
+ this.m_ui_input = input;
74
120
  }
75
121
  else {
76
122
  this.m_ui_input = new component_1.Component({
@@ -104,7 +150,9 @@ class ComboBox extends layout_1.HLayout {
104
150
  cls: 'gadget',
105
151
  icon: 'var( --x4-icon-angle-down )',
106
152
  tabIndex: false,
107
- click: () => this.showPopup(),
153
+ click: () => {
154
+ this.showPopup(false);
155
+ },
108
156
  dom_events: {
109
157
  focus: () => { this.dom.focus(); },
110
158
  }
@@ -125,10 +173,18 @@ class ComboBox extends layout_1.HLayout {
125
173
  /**
126
174
  * display the popup
127
175
  */
128
- showPopup() {
176
+ showPopup(filter_items = true) {
129
177
  let props = this.m_props;
130
178
  if (props.readOnly || this.hasClass("@disable")) {
131
- return;
179
+ return null;
180
+ }
181
+ let items = props.items;
182
+ if ((0, tools_1.isFunction)(items)) {
183
+ const filter = filter_items ? this.m_ui_input.value : null;
184
+ items = items(filter);
185
+ }
186
+ if (items.length == 0) {
187
+ return null;
132
188
  }
133
189
  // need creation ?
134
190
  if (!this.m_popup) {
@@ -138,10 +194,15 @@ class ComboBox extends layout_1.HLayout {
138
194
  // prepare the combo listview
139
195
  this.m_popup = new listview_1.PopupListView({
140
196
  cls: '@combo-popup',
141
- items: props.items,
142
197
  populate: props.populate,
143
198
  renderItem: this.m_props.renderer,
144
- selectionChange: (e) => this._selectItem(e),
199
+ selectionChange: (e) => {
200
+ this._selectItem(e);
201
+ if (!this.m_lockpop) {
202
+ this._hidePopup();
203
+ this.focus();
204
+ }
205
+ },
145
206
  cancel: (e) => this.signal('cancel', e),
146
207
  style: {
147
208
  fontFamily,
@@ -149,14 +210,18 @@ class ComboBox extends layout_1.HLayout {
149
210
  }
150
211
  });
151
212
  }
213
+ this.m_popup.items = items;
152
214
  let r1 = this.m_ui_button.getBoundingRect(), r2 = this.m_ui_input.getBoundingRect();
153
215
  this.m_popup.setStyle({
154
216
  minWidth: r1.right - r2.left,
155
217
  });
156
218
  this.m_popup.displayAt(r2.left, r2.bottom);
219
+ this.m_popvis = true;
220
+ this.startTimer("focus-check", 100, true, () => this._checkFocus());
157
221
  if (this.value !== undefined) {
158
222
  this.m_popup.selection = this.value;
159
223
  }
224
+ return items;
160
225
  }
161
226
  /** @ignore
162
227
  */
@@ -165,15 +230,17 @@ class ComboBox extends layout_1.HLayout {
165
230
  if (!item) {
166
231
  return;
167
232
  }
233
+ this.m_lockchg = true;
168
234
  this._setInput(item, true);
235
+ this.m_lockchg = false;
169
236
  this.m_selection = {
170
237
  id: item.id,
171
238
  text: item.text
172
239
  };
173
240
  this.emit('selectionChange', (0, x4events_1.EvSelectionChange)(item));
174
241
  this.emit('change', (0, x4events_1.EvChange)(item.id));
175
- this.m_ui_input.focus();
176
- this.m_popup.hide();
242
+ //this.m_ui_input.focus( );
243
+ //this.m_popup.hide( );
177
244
  }
178
245
  /**
179
246
  *
@@ -209,7 +276,13 @@ class ComboBox extends layout_1.HLayout {
209
276
  return this.m_selection ? this.m_selection.id : undefined;
210
277
  }
211
278
  get valueText() {
212
- return this.m_selection ? this.m_selection.text : undefined;
279
+ if (this.m_selection) {
280
+ return this.m_selection.text;
281
+ }
282
+ if (this.m_props.editable) {
283
+ return this.m_ui_input.value;
284
+ }
285
+ return '';
213
286
  }
214
287
  /**
215
288
  *
@@ -217,7 +290,7 @@ class ComboBox extends layout_1.HLayout {
217
290
  set value(id) {
218
291
  let items = this.m_props.items;
219
292
  if ((0, tools_1.isFunction)(items)) {
220
- items = items();
293
+ items = items(null);
221
294
  }
222
295
  const found = items.some((v) => {
223
296
  if (v.id === id) {
@@ -234,6 +307,23 @@ class ComboBox extends layout_1.HLayout {
234
307
  get input() {
235
308
  return this.m_ui_input instanceof input_1.Input ? this.m_ui_input : null;
236
309
  }
310
+ _checkFocus() {
311
+ const focus = document.activeElement;
312
+ if (this.dom && this.dom.contains(focus) || focus == document.body) {
313
+ return;
314
+ }
315
+ if (this.m_popup && this.m_popup.dom && this.m_popup.dom.contains(focus)) {
316
+ return;
317
+ }
318
+ this._hidePopup();
319
+ }
320
+ _hidePopup() {
321
+ if (this.m_popvis) {
322
+ this.m_popup.close();
323
+ this.m_popvis = false;
324
+ this.stopTimer("focus-check");
325
+ }
326
+ }
237
327
  static storeProxy(props) {
238
328
  let view = props.store instanceof datastore_1.DataStore ? props.store.createView() : props.store;
239
329
  return () => {
@@ -247,13 +337,13 @@ class ComboBox extends layout_1.HLayout {
247
337
  return result;
248
338
  };
249
339
  }
340
+ focus() {
341
+ if (this.m_props.editable) {
342
+ this.m_ui_input.focus();
343
+ }
344
+ else {
345
+ super.focus();
346
+ }
347
+ }
250
348
  }
251
349
  exports.ComboBox = ComboBox;
252
- /*
253
- export type CBComboBoxRenderer = ( rec: Record ) => string;
254
- export interface ComboBoxStore {
255
- store: DataStore;
256
- display: string | CBComboBoxRenderer; // if string, the field name to display
257
- }
258
-
259
- */
package/lib/cjs/label.js CHANGED
@@ -49,7 +49,7 @@ class Label extends component_1.Component {
49
49
  var _a;
50
50
  let text = this.m_props.text;
51
51
  if (this.m_props.multiline && !(text instanceof tools_1.HtmlString)) {
52
- text = new tools_1.HtmlString(text.replace(/\n/g, '<br/>'));
52
+ text = new tools_1.HtmlString((0, tools_1.escapeHtml)(text, true));
53
53
  }
54
54
  if (!props.icon) {
55
55
  this.setContent(text);
@@ -74,7 +74,7 @@ class Label extends component_1.Component {
74
74
  props.text = txt;
75
75
  let text = this.m_props.text;
76
76
  if (this.m_props.multiline && !(text instanceof tools_1.HtmlString)) {
77
- text = new tools_1.HtmlString(text.replace('/\n/g', '<br/>'));
77
+ text = new tools_1.HtmlString((0, tools_1.escapeHtml)(text, true));
78
78
  }
79
79
  if (this.dom) {
80
80
  let comp = this;
@@ -64,7 +64,7 @@ class ListView extends layout_1.VLayout {
64
64
  this._buildItems();
65
65
  }
66
66
  else if (this.m_props.populate) {
67
- this.items = this.m_props.populate();
67
+ this.items = this.m_props.populate(null);
68
68
  }
69
69
  }
70
70
  render(props) {
@@ -316,6 +316,8 @@ class ListView extends layout_1.VLayout {
316
316
  }
317
317
  /** @ignore */
318
318
  _handleClick(e) {
319
+ e.stopImmediatePropagation();
320
+ e.preventDefault();
319
321
  let dom = e.target, self = this.dom, list_items = this.m_props.items; // already created by build
320
322
  // go up until we find something interesting
321
323
  while (dom && dom != self) {
@@ -27,6 +27,15 @@
27
27
  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
28
28
  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29
29
  **/
30
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
31
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
32
+ return new (P || (P = Promise))(function (resolve, reject) {
33
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
34
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
35
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
36
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
37
+ });
38
+ };
30
39
  Object.defineProperty(exports, "__esModule", { value: true });
31
40
  exports.PromptDialogBox = exports.MessageBox = void 0;
32
41
  const dialog_1 = require("./dialog");
@@ -79,6 +88,24 @@ class MessageBox extends dialog_1.Dialog {
79
88
  msg.show();
80
89
  return msg;
81
90
  }
91
+ static showAsync(props) {
92
+ return __awaiter(this, void 0, void 0, function* () {
93
+ return new Promise((resolve, reject) => {
94
+ let _props;
95
+ const cb = (btn) => {
96
+ resolve(btn);
97
+ };
98
+ if ((0, tools_1.isString)(props) || (0, tools_1.isHtmlString)(props)) {
99
+ _props = { message: props, click: cb };
100
+ }
101
+ else {
102
+ _props = Object.assign(Object.assign({}, props), { click: cb });
103
+ }
104
+ const msg = new MessageBox(_props);
105
+ msg.show();
106
+ });
107
+ });
108
+ }
82
109
  /**
83
110
  * display an alert message
84
111
  */
package/lib/cjs/tools.js CHANGED
@@ -273,7 +273,7 @@ exports.sprintf = sprintf;
273
273
  */
274
274
  function escapeHtml(unsafe, nl_br = false) {
275
275
  if (!unsafe || unsafe.length == 0) {
276
- return unsafe;
276
+ return "";
277
277
  }
278
278
  let result = unsafe.replace(/[<>\&\"\']/g, function (c) {
279
279
  return '&#' + c.charCodeAt(0) + ';';
@@ -29,4 +29,4 @@
29
29
  **/
30
30
  Object.defineProperty(exports, "__esModule", { value: true });
31
31
  exports.x4js_version = void 0;
32
- exports.x4js_version = "1.5.5";
32
+ exports.x4js_version = "1.5.6";
@@ -45,13 +45,42 @@ export class ComboBox extends HLayout {
45
45
  m_ui_input;
46
46
  m_ui_button;
47
47
  m_popup;
48
+ m_lockpop;
49
+ m_lockchg;
50
+ m_popvis;
48
51
  m_selection;
49
- m_defer_sel;
50
52
  constructor(props) {
51
53
  super(props);
52
- this.setDomEvent('keypress', () => this.showPopup());
53
- this.setDomEvent('click', () => this.showPopup());
54
+ if (!props.editable) {
55
+ this.setDomEvent('keypress', () => this.showPopup());
56
+ }
57
+ this.setDomEvent('click', () => {
58
+ if (this.m_props.editable) {
59
+ this.m_ui_input.focus();
60
+ }
61
+ this.showPopup();
62
+ });
63
+ this.setDomEvent("keydown", e => this._onKey(e));
54
64
  this.mapPropEvents(props, 'selectionChange');
65
+ this.m_popvis = false;
66
+ this.m_lockpop = false;
67
+ this.m_lockchg = false;
68
+ }
69
+ _onKey(e) {
70
+ if (this.m_popvis) {
71
+ if (e.key == "ArrowUp" || e.key == "ArrowDown") {
72
+ this.m_lockpop = true;
73
+ this.m_popup.handleKey(e);
74
+ this.m_lockpop = false;
75
+ e.preventDefault();
76
+ e.stopPropagation();
77
+ }
78
+ else if (e.key == "Escape") {
79
+ this._hidePopup();
80
+ e.preventDefault();
81
+ e.stopPropagation();
82
+ }
83
+ }
55
84
  }
56
85
  set items(items) {
57
86
  this.m_props.items = items;
@@ -62,16 +91,35 @@ export class ComboBox extends HLayout {
62
91
  /** @ignore */
63
92
  render(props) {
64
93
  if (!props.renderer) {
65
- this.m_ui_input = new Input({
94
+ const input = new Input({
66
95
  flex: 1,
67
- readOnly: true,
96
+ readOnly: this.m_props.editable ? false : true,
68
97
  tabIndex: 0,
69
98
  name: props.name,
70
99
  value_hook: {
71
100
  get: () => { return this.value; },
72
101
  set: (v) => { this.value = v; }
102
+ },
103
+ dom_events: {
104
+ focus: () => {
105
+ if (this.m_props.editable && input.value.length == 0) {
106
+ this.showPopup();
107
+ }
108
+ },
109
+ input: () => {
110
+ if (this.m_lockchg) {
111
+ return;
112
+ }
113
+ const text = input.value;
114
+ this.m_selection = { id: undefined, text };
115
+ let items = this.showPopup();
116
+ if (items && items.length && items[0].text == text) {
117
+ this.m_selection = { id: items[0].id, text };
118
+ }
119
+ }
73
120
  }
74
121
  });
122
+ this.m_ui_input = input;
75
123
  }
76
124
  else {
77
125
  this.m_ui_input = new Component({
@@ -105,7 +153,9 @@ export class ComboBox extends HLayout {
105
153
  cls: 'gadget',
106
154
  icon: 'var( --x4-icon-angle-down )',
107
155
  tabIndex: false,
108
- click: () => this.showPopup(),
156
+ click: () => {
157
+ this.showPopup(false);
158
+ },
109
159
  dom_events: {
110
160
  focus: () => { this.dom.focus(); },
111
161
  }
@@ -126,10 +176,18 @@ export class ComboBox extends HLayout {
126
176
  /**
127
177
  * display the popup
128
178
  */
129
- showPopup() {
179
+ showPopup(filter_items = true) {
130
180
  let props = this.m_props;
131
181
  if (props.readOnly || this.hasClass("@disable")) {
132
- return;
182
+ return null;
183
+ }
184
+ let items = props.items;
185
+ if (isFunction(items)) {
186
+ const filter = filter_items ? this.m_ui_input.value : null;
187
+ items = items(filter);
188
+ }
189
+ if (items.length == 0) {
190
+ return null;
133
191
  }
134
192
  // need creation ?
135
193
  if (!this.m_popup) {
@@ -139,10 +197,15 @@ export class ComboBox extends HLayout {
139
197
  // prepare the combo listview
140
198
  this.m_popup = new PopupListView({
141
199
  cls: '@combo-popup',
142
- items: props.items,
143
200
  populate: props.populate,
144
201
  renderItem: this.m_props.renderer,
145
- selectionChange: (e) => this._selectItem(e),
202
+ selectionChange: (e) => {
203
+ this._selectItem(e);
204
+ if (!this.m_lockpop) {
205
+ this._hidePopup();
206
+ this.focus();
207
+ }
208
+ },
146
209
  cancel: (e) => this.signal('cancel', e),
147
210
  style: {
148
211
  fontFamily,
@@ -150,14 +213,18 @@ export class ComboBox extends HLayout {
150
213
  }
151
214
  });
152
215
  }
216
+ this.m_popup.items = items;
153
217
  let r1 = this.m_ui_button.getBoundingRect(), r2 = this.m_ui_input.getBoundingRect();
154
218
  this.m_popup.setStyle({
155
219
  minWidth: r1.right - r2.left,
156
220
  });
157
221
  this.m_popup.displayAt(r2.left, r2.bottom);
222
+ this.m_popvis = true;
223
+ this.startTimer("focus-check", 100, true, () => this._checkFocus());
158
224
  if (this.value !== undefined) {
159
225
  this.m_popup.selection = this.value;
160
226
  }
227
+ return items;
161
228
  }
162
229
  /** @ignore
163
230
  */
@@ -166,15 +233,17 @@ export class ComboBox extends HLayout {
166
233
  if (!item) {
167
234
  return;
168
235
  }
236
+ this.m_lockchg = true;
169
237
  this._setInput(item, true);
238
+ this.m_lockchg = false;
170
239
  this.m_selection = {
171
240
  id: item.id,
172
241
  text: item.text
173
242
  };
174
243
  this.emit('selectionChange', EvSelectionChange(item));
175
244
  this.emit('change', EvChange(item.id));
176
- this.m_ui_input.focus();
177
- this.m_popup.hide();
245
+ //this.m_ui_input.focus( );
246
+ //this.m_popup.hide( );
178
247
  }
179
248
  /**
180
249
  *
@@ -210,7 +279,13 @@ export class ComboBox extends HLayout {
210
279
  return this.m_selection ? this.m_selection.id : undefined;
211
280
  }
212
281
  get valueText() {
213
- return this.m_selection ? this.m_selection.text : undefined;
282
+ if (this.m_selection) {
283
+ return this.m_selection.text;
284
+ }
285
+ if (this.m_props.editable) {
286
+ return this.m_ui_input.value;
287
+ }
288
+ return '';
214
289
  }
215
290
  /**
216
291
  *
@@ -218,7 +293,7 @@ export class ComboBox extends HLayout {
218
293
  set value(id) {
219
294
  let items = this.m_props.items;
220
295
  if (isFunction(items)) {
221
- items = items();
296
+ items = items(null);
222
297
  }
223
298
  const found = items.some((v) => {
224
299
  if (v.id === id) {
@@ -235,6 +310,23 @@ export class ComboBox extends HLayout {
235
310
  get input() {
236
311
  return this.m_ui_input instanceof Input ? this.m_ui_input : null;
237
312
  }
313
+ _checkFocus() {
314
+ const focus = document.activeElement;
315
+ if (this.dom && this.dom.contains(focus) || focus == document.body) {
316
+ return;
317
+ }
318
+ if (this.m_popup && this.m_popup.dom && this.m_popup.dom.contains(focus)) {
319
+ return;
320
+ }
321
+ this._hidePopup();
322
+ }
323
+ _hidePopup() {
324
+ if (this.m_popvis) {
325
+ this.m_popup.close();
326
+ this.m_popvis = false;
327
+ this.stopTimer("focus-check");
328
+ }
329
+ }
238
330
  static storeProxy(props) {
239
331
  let view = props.store instanceof DataStore ? props.store.createView() : props.store;
240
332
  return () => {
@@ -248,12 +340,12 @@ export class ComboBox extends HLayout {
248
340
  return result;
249
341
  };
250
342
  }
343
+ focus() {
344
+ if (this.m_props.editable) {
345
+ this.m_ui_input.focus();
346
+ }
347
+ else {
348
+ super.focus();
349
+ }
350
+ }
251
351
  }
252
- /*
253
- export type CBComboBoxRenderer = ( rec: Record ) => string;
254
- export interface ComboBoxStore {
255
- store: DataStore;
256
- display: string | CBComboBoxRenderer; // if string, the field name to display
257
- }
258
-
259
- */
package/lib/esm/label.js CHANGED
@@ -27,7 +27,7 @@
27
27
  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
28
  **/
29
29
  import { Component } from './component';
30
- import { HtmlString } from './tools';
30
+ import { escapeHtml, HtmlString } from './tools';
31
31
  import { Icon } from './icon';
32
32
  /**
33
33
  * Standard label
@@ -45,7 +45,7 @@ export class Label extends Component {
45
45
  render(props) {
46
46
  let text = this.m_props.text;
47
47
  if (this.m_props.multiline && !(text instanceof HtmlString)) {
48
- text = new HtmlString(text.replace(/\n/g, '<br/>'));
48
+ text = new HtmlString(escapeHtml(text, true));
49
49
  }
50
50
  if (!props.icon) {
51
51
  this.setContent(text);
@@ -70,7 +70,7 @@ export class Label extends Component {
70
70
  props.text = txt;
71
71
  let text = this.m_props.text;
72
72
  if (this.m_props.multiline && !(text instanceof HtmlString)) {
73
- text = new HtmlString(text.replace('/\n/g', '<br/>'));
73
+ text = new HtmlString(escapeHtml(text, true));
74
74
  }
75
75
  if (this.dom) {
76
76
  let comp = this;
@@ -59,7 +59,7 @@ export class ListView extends VLayout {
59
59
  this._buildItems();
60
60
  }
61
61
  else if (this.m_props.populate) {
62
- this.items = this.m_props.populate();
62
+ this.items = this.m_props.populate(null);
63
63
  }
64
64
  }
65
65
  render(props) {
@@ -307,6 +307,8 @@ export class ListView extends VLayout {
307
307
  }
308
308
  /** @ignore */
309
309
  _handleClick(e) {
310
+ e.stopImmediatePropagation();
311
+ e.preventDefault();
310
312
  let dom = e.target, self = this.dom, list_items = this.m_props.items; // already created by build
311
313
  // go up until we find something interesting
312
314
  while (dom && dom != self) {
@@ -76,6 +76,22 @@ export class MessageBox extends Dialog {
76
76
  msg.show();
77
77
  return msg;
78
78
  }
79
+ static async showAsync(props) {
80
+ return new Promise((resolve, reject) => {
81
+ let _props;
82
+ const cb = (btn) => {
83
+ resolve(btn);
84
+ };
85
+ if (isString(props) || isHtmlString(props)) {
86
+ _props = { message: props, click: cb };
87
+ }
88
+ else {
89
+ _props = { ...props, click: cb };
90
+ }
91
+ const msg = new MessageBox(_props);
92
+ msg.show();
93
+ });
94
+ }
79
95
  /**
80
96
  * display an alert message
81
97
  */
package/lib/esm/tools.js CHANGED
@@ -269,7 +269,7 @@ export function sprintf(format, ...args) {
269
269
  */
270
270
  export function escapeHtml(unsafe, nl_br = false) {
271
271
  if (!unsafe || unsafe.length == 0) {
272
- return unsafe;
272
+ return "";
273
273
  }
274
274
  let result = unsafe.replace(/[<>\&\"\']/g, function (c) {
275
275
  return '&#' + c.charCodeAt(0) + ';';
@@ -26,4 +26,4 @@
26
26
  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
27
  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
28
  **/
29
- export const x4js_version = "1.5.5";
29
+ export const x4js_version = "1.5.6";
@@ -38,7 +38,7 @@ import { Input } from './input'
38
38
  import { Label } from './label'
39
39
  import { Button } from './button'
40
40
  import { HLayout } from './layout'
41
- import { PopupListView, ListViewItem, PopulateItems, EvCancel } from './listview';
41
+ import { PopupListView, ListViewItem, EvCancel, PopulateItems } from './listview';
42
42
  import { DataStore, DataView, Record } from './datastore'
43
43
  import { isFunction, HtmlString } from './tools'
44
44
 
@@ -73,12 +73,12 @@ export interface ComboBoxProps extends CProps<ComboBoxEventMap> {
73
73
 
74
74
  labelAlign?: 'left' | 'right';
75
75
 
76
- items?: ListViewItem[];
77
- populate?: PopulateItems; // if not specified, fire 'populate' event
76
+ items?: ListViewItem[] | PopulateItems;
78
77
  value?: any; // shown value at init
79
78
 
80
79
  renderer?: ComboItemRender;
81
80
  selectionChange?: EventCallback<EvSelectionChange>;// shortcut to events: { selectionChange: ... }
81
+ editable?: boolean;
82
82
  }
83
83
 
84
84
  /**
@@ -87,43 +87,97 @@ export interface ComboBoxProps extends CProps<ComboBoxEventMap> {
87
87
 
88
88
  export class ComboBox extends HLayout<ComboBoxProps,ComboBoxEventMap> {
89
89
 
90
- private m_ui_input: Component;
90
+ private m_ui_input: Input | Component;
91
91
  private m_ui_button: Button;
92
92
  private m_popup: PopupListView;
93
-
93
+ private m_lockpop: boolean;
94
+ private m_lockchg: boolean;
95
+ private m_popvis: boolean;
94
96
  private m_selection: ListViewItem;
95
- private m_defer_sel: any;
96
-
97
+
97
98
  constructor(props: ComboBoxProps) {
98
99
  super(props);
99
100
 
100
- this.setDomEvent( 'keypress', () => this.showPopup() );
101
- this.setDomEvent( 'click', () => this.showPopup() );
101
+ if( !props.editable ) {
102
+ this.setDomEvent( 'keypress', () => this.showPopup() );
103
+ }
104
+
105
+ this.setDomEvent( 'click', () => {
106
+ if( this.m_props.editable ) {
107
+ this.m_ui_input.focus( );
108
+ }
109
+
110
+ this.showPopup();
111
+ } );
102
112
 
113
+ this.setDomEvent("keydown", e => this._onKey(e));
103
114
  this.mapPropEvents(props, 'selectionChange' );
115
+
116
+ this.m_popvis = false;
117
+ this.m_lockpop = false;
118
+ this.m_lockchg = false;
104
119
  }
105
120
 
121
+ _onKey(e) {
122
+ if (this.m_popvis) {
123
+ if (e.key == "ArrowUp" || e.key == "ArrowDown") {
124
+ this.m_lockpop = true;
125
+ this.m_popup.handleKey(e);
126
+ this.m_lockpop = false;
127
+ e.preventDefault();
128
+ e.stopPropagation();
129
+ }
130
+ else if (e.key == "Escape") {
131
+ this._hidePopup();
132
+ e.preventDefault();
133
+ e.stopPropagation();
134
+ }
135
+ }
136
+ }
137
+
106
138
  set items( items: ListViewItem[] ) {
107
139
  this.m_props.items = items;
108
140
  if( this.m_popup ) {
109
141
  this.m_popup.items = items;
110
142
  }
111
143
  }
112
-
144
+
113
145
  /** @ignore */
114
146
  render( props: ComboBoxProps ) {
115
147
 
116
148
  if( !props.renderer ) {
117
- this.m_ui_input = new Input( {
149
+
150
+ const input = new Input( {
118
151
  flex : 1,
119
- readOnly : true,
152
+ readOnly : this.m_props.editable ? false : true,
120
153
  tabIndex : 0,
121
154
  name: props.name,
122
155
  value_hook: {
123
156
  get: (): string => { return this.value; },
124
157
  set: (v: string) => { this.value = v; }
158
+ },
159
+ dom_events: {
160
+ focus: () => {
161
+ if( this.m_props.editable && input.value.length==0 ) {
162
+ this.showPopup( );
163
+ }
164
+ },
165
+ input: ( ) => {
166
+ if( this.m_lockchg ) {
167
+ return;
168
+ }
169
+
170
+ const text = input.value;
171
+ this.m_selection = { id: undefined, text };
172
+ let items = this.showPopup( );
173
+ if( items && items.length && items[0].text==text ) {
174
+ this.m_selection = { id: items[0].id, text };
175
+ }
176
+ }
125
177
  }
126
178
  });
179
+
180
+ this.m_ui_input = input;
127
181
  }
128
182
  else {
129
183
  this.m_ui_input = new Component( {
@@ -162,7 +216,9 @@ export class ComboBox extends HLayout<ComboBoxProps,ComboBoxEventMap> {
162
216
  cls: 'gadget',
163
217
  icon: 'var( --x4-icon-angle-down )',
164
218
  tabIndex: false,
165
- click: () => this.showPopup(),
219
+ click: () => {
220
+ this.showPopup( false )
221
+ },
166
222
  dom_events: {
167
223
  focus: () => { this.dom.focus(); },
168
224
  }
@@ -188,11 +244,21 @@ export class ComboBox extends HLayout<ComboBoxProps,ComboBoxEventMap> {
188
244
  * display the popup
189
245
  */
190
246
 
191
- showPopup() {
247
+ showPopup( filter_items: boolean = true ) {
192
248
 
193
249
  let props = this.m_props;
194
250
  if (props.readOnly || this.hasClass("@disable") ) {
195
- return;
251
+ return null;
252
+ }
253
+
254
+ let items = props.items;
255
+ if( isFunction( items ) ) {
256
+ const filter = filter_items ? (this.m_ui_input as Input).value : null;
257
+ items = items( filter );
258
+ }
259
+
260
+ if( items.length==0 ) {
261
+ return null;
196
262
  }
197
263
 
198
264
  // need creation ?
@@ -205,10 +271,17 @@ export class ComboBox extends HLayout<ComboBoxProps,ComboBoxEventMap> {
205
271
  // prepare the combo listview
206
272
  this.m_popup = new PopupListView({
207
273
  cls: '@combo-popup',
208
- items: props.items,
209
274
  populate: props.populate,
210
275
  renderItem: this.m_props.renderer,
211
- selectionChange: (e) => this._selectItem(e),
276
+ selectionChange: (e) => {
277
+
278
+ this._selectItem(e);
279
+
280
+ if (!this.m_lockpop) {
281
+ this._hidePopup();
282
+ this.focus();
283
+ }
284
+ },
212
285
  cancel: ( e ) => this.signal( 'cancel', e ),
213
286
  style: {
214
287
  fontFamily,
@@ -216,6 +289,8 @@ export class ComboBox extends HLayout<ComboBoxProps,ComboBoxEventMap> {
216
289
  }
217
290
  });
218
291
  }
292
+
293
+ this.m_popup.items = items;
219
294
 
220
295
  let r1 = this.m_ui_button.getBoundingRect(),
221
296
  r2 = this.m_ui_input.getBoundingRect();
@@ -225,10 +300,14 @@ export class ComboBox extends HLayout<ComboBoxProps,ComboBoxEventMap> {
225
300
  });
226
301
 
227
302
  this.m_popup.displayAt(r2.left, r2.bottom);
303
+ this.m_popvis = true;
304
+ this.startTimer("focus-check", 100, true, () => this._checkFocus());
228
305
 
229
306
  if( this.value!==undefined ) {
230
307
  this.m_popup.selection = this.value;
231
308
  }
309
+
310
+ return items;
232
311
  }
233
312
 
234
313
  /** @ignore
@@ -241,8 +320,10 @@ export class ComboBox extends HLayout<ComboBoxProps,ComboBoxEventMap> {
241
320
  return;
242
321
  }
243
322
 
323
+ this.m_lockchg = true;
244
324
  this._setInput( item, true );
245
-
325
+ this.m_lockchg = false;
326
+
246
327
  this.m_selection = {
247
328
  id: item.id,
248
329
  text: item.text
@@ -250,9 +331,8 @@ export class ComboBox extends HLayout<ComboBoxProps,ComboBoxEventMap> {
250
331
 
251
332
  this.emit( 'selectionChange', EvSelectionChange( item ) );
252
333
  this.emit( 'change', EvChange(item.id) );
253
- this.m_ui_input.focus( );
254
-
255
- this.m_popup.hide( );
334
+ //this.m_ui_input.focus( );
335
+ //this.m_popup.hide( );
256
336
  }
257
337
 
258
338
  /**
@@ -294,7 +374,15 @@ export class ComboBox extends HLayout<ComboBoxProps,ComboBoxEventMap> {
294
374
  }
295
375
 
296
376
  public get valueText( ) {
297
- return this.m_selection ? this.m_selection.text : undefined;
377
+ if( this.m_selection ) {
378
+ return this.m_selection.text;
379
+ }
380
+
381
+ if( this.m_props.editable ) {
382
+ return (this.m_ui_input as Input).value;
383
+ }
384
+
385
+ return '';
298
386
  }
299
387
 
300
388
  /**
@@ -305,9 +393,9 @@ export class ComboBox extends HLayout<ComboBoxProps,ComboBoxEventMap> {
305
393
  let items = this.m_props.items;
306
394
 
307
395
  if( isFunction(items) ) {
308
- items = items( );
396
+ items = items( null );
309
397
  }
310
-
398
+
311
399
  const found = items.some( (v) => {
312
400
  if (v.id === id) {
313
401
  this._setInput( v );
@@ -326,6 +414,27 @@ export class ComboBox extends HLayout<ComboBoxProps,ComboBoxEventMap> {
326
414
  return this.m_ui_input instanceof Input ? this.m_ui_input : null;
327
415
  }
328
416
 
417
+ _checkFocus() {
418
+ const focus = document.activeElement;
419
+ if (this.dom && this.dom.contains(focus) || focus==document.body ) {
420
+ return;
421
+ }
422
+
423
+ if (this.m_popup && this.m_popup.dom && this.m_popup.dom.contains(focus)) {
424
+ return;
425
+ }
426
+
427
+ this._hidePopup();
428
+ }
429
+
430
+ _hidePopup() {
431
+ if (this.m_popvis) {
432
+ this.m_popup.close();
433
+ this.m_popvis = false;
434
+ this.stopTimer("focus-check");
435
+ }
436
+ }
437
+
329
438
  static storeProxy( props: ComboStoreProxyProps ): PopulateItems {
330
439
 
331
440
  let view: DataView = props.store instanceof DataStore ? props.store.createView() : props.store;
@@ -343,20 +452,14 @@ export class ComboBox extends HLayout<ComboBoxProps,ComboBoxEventMap> {
343
452
  return result;
344
453
  };
345
454
  }
346
- }
347
455
 
348
-
349
-
350
- /*
351
- export type CBComboBoxRenderer = ( rec: Record ) => string;
352
- export interface ComboBoxStore {
353
- store: DataStore;
354
- display: string | CBComboBoxRenderer; // if string, the field name to display
456
+ focus( ) {
457
+ if( this.m_props.editable ) {
458
+ this.m_ui_input.focus( );
459
+ }
460
+ else {
461
+ super.focus( );
462
+ }
463
+ }
355
464
  }
356
465
 
357
- */
358
-
359
-
360
-
361
-
362
-
package/lib/src/label.ts CHANGED
@@ -28,7 +28,7 @@
28
28
  **/
29
29
 
30
30
  import { Component, CProps } from './component'
31
- import { HtmlString } from './tools'
31
+ import { escapeHtml, HtmlString } from './tools'
32
32
  import { Icon, IconID } from './icon'
33
33
 
34
34
  // ============================================================================
@@ -69,7 +69,7 @@ export class Label extends Component<LabelProps>
69
69
 
70
70
  let text: any = this.m_props.text;
71
71
  if( this.m_props.multiline && !(text instanceof HtmlString) ) {
72
- text = new HtmlString( (text as string).replace( /\n/g, '<br/>' ) );
72
+ text = new HtmlString( escapeHtml(text,true) );
73
73
  }
74
74
 
75
75
  if( !props.icon ) {
@@ -102,7 +102,7 @@ export class Label extends Component<LabelProps>
102
102
 
103
103
  let text: any = this.m_props.text;
104
104
  if( this.m_props.multiline && !(text instanceof HtmlString) ) {
105
- text = new HtmlString( (text as string).replace( '/\n/g', '<br/>' ) );
105
+ text = new HtmlString(escapeHtml(text,true) );
106
106
  }
107
107
 
108
108
  if( this.dom ) {
@@ -62,7 +62,7 @@ export interface RenderListItem {
62
62
  * callback to fill the list
63
63
  */
64
64
  export interface PopulateItems {
65
- (): ListViewItem[];
65
+ ( filter: string ): ListViewItem[];
66
66
  }
67
67
 
68
68
  /**
@@ -137,7 +137,7 @@ export class ListView extends VLayout<ListViewProps,ListViewEventMap> {
137
137
  this._buildItems();
138
138
  }
139
139
  else if( this.m_props.populate ) {
140
- this.items = this.m_props.populate( );
140
+ this.items = this.m_props.populate( null );
141
141
  }
142
142
  }
143
143
 
@@ -449,6 +449,9 @@ export class ListView extends VLayout<ListViewProps,ListViewEventMap> {
449
449
  /** @ignore */
450
450
  private _handleClick(e: MouseEvent) {
451
451
 
452
+ e.stopImmediatePropagation();
453
+ e.preventDefault( );
454
+
452
455
  let dom = e.target as HTMLElement,
453
456
  self = this.dom,
454
457
  list_items = this.m_props.items as ListViewItem[]; // already created by build
@@ -105,6 +105,27 @@ export class MessageBox extends Dialog<MessageBoxProps>
105
105
  return msg;
106
106
  }
107
107
 
108
+ static async showAsync( props: string | HtmlString | MessageBoxProps): Promise<string> {
109
+ return new Promise( (resolve, reject ) => {
110
+
111
+ let _props: MessageBoxProps;
112
+
113
+ const cb = ( btn: string ) => {
114
+ resolve( btn );
115
+ }
116
+
117
+ if (isString(props) || isHtmlString(props)) {
118
+ _props = { message: props, click: cb };
119
+ }
120
+ else {
121
+ _props = { ...props, click: cb };
122
+ }
123
+
124
+ const msg = new MessageBox(_props);
125
+ msg.show();
126
+ });
127
+ }
128
+
108
129
  /**
109
130
  * display an alert message
110
131
  */
package/lib/src/tools.ts CHANGED
@@ -346,7 +346,7 @@ export function sprintf(format: string, ...args) {
346
346
  */
347
347
  export function escapeHtml(unsafe: string, nl_br = false): string {
348
348
  if (!unsafe || unsafe.length == 0) {
349
- return unsafe;
349
+ return "";
350
350
  }
351
351
 
352
352
  let result = unsafe.replace(/[<>\&\"\']/g, function (c) {
@@ -27,4 +27,4 @@
27
27
  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
28
  **/
29
29
 
30
- export const x4js_version = "1.5.5";
30
+ export const x4js_version = "1.5.6";
@@ -33,7 +33,7 @@ import { Component, CProps, ContainerEventMap } from './component';
33
33
  import { EvChange, EvSelectionChange, EventCallback } from './x4events';
34
34
  import { Input } from './input';
35
35
  import { HLayout } from './layout';
36
- import { ListViewItem, PopulateItems, EvCancel } from './listview';
36
+ import { ListViewItem, EvCancel, PopulateItems } from './listview';
37
37
  import { DataStore, DataView, Record } from './datastore';
38
38
  import { HtmlString } from './tools';
39
39
  export interface ComboStoreProxyProps {
@@ -55,11 +55,11 @@ export interface ComboBoxProps extends CProps<ComboBoxEventMap> {
55
55
  label?: string;
56
56
  labelWidth?: number;
57
57
  labelAlign?: 'left' | 'right';
58
- items?: ListViewItem[];
59
- populate?: PopulateItems;
58
+ items?: ListViewItem[] | PopulateItems;
60
59
  value?: any;
61
60
  renderer?: ComboItemRender;
62
61
  selectionChange?: EventCallback<EvSelectionChange>;
62
+ editable?: boolean;
63
63
  }
64
64
  /**
65
65
  * @review use textedit
@@ -68,9 +68,12 @@ export declare class ComboBox extends HLayout<ComboBoxProps, ComboBoxEventMap> {
68
68
  private m_ui_input;
69
69
  private m_ui_button;
70
70
  private m_popup;
71
+ private m_lockpop;
72
+ private m_lockchg;
73
+ private m_popvis;
71
74
  private m_selection;
72
- private m_defer_sel;
73
75
  constructor(props: ComboBoxProps);
76
+ _onKey(e: any): void;
74
77
  set items(items: ListViewItem[]);
75
78
  /** @ignore */
76
79
  render(props: ComboBoxProps): void;
@@ -78,7 +81,7 @@ export declare class ComboBox extends HLayout<ComboBoxProps, ComboBoxEventMap> {
78
81
  /**
79
82
  * display the popup
80
83
  */
81
- showPopup(): void;
84
+ showPopup(filter_items?: boolean): ListViewItem[];
82
85
  /** @ignore
83
86
  */
84
87
  private _selectItem;
@@ -96,6 +99,9 @@ export declare class ComboBox extends HLayout<ComboBoxProps, ComboBoxEventMap> {
96
99
  */
97
100
  set value(id: any);
98
101
  get input(): Input;
102
+ _checkFocus(): void;
103
+ _hidePopup(): void;
99
104
  static storeProxy(props: ComboStoreProxyProps): PopulateItems;
105
+ focus(): void;
100
106
  }
101
107
  export {};
@@ -52,7 +52,7 @@ export interface RenderListItem {
52
52
  * callback to fill the list
53
53
  */
54
54
  export interface PopulateItems {
55
- (): ListViewItem[];
55
+ (filter: string): ListViewItem[];
56
56
  }
57
57
  /**
58
58
  * listview can generate these events
@@ -46,6 +46,7 @@ export declare class MessageBox extends Dialog<MessageBoxProps> {
46
46
  * display a messagebox
47
47
  */
48
48
  static show(props: string | HtmlString | MessageBoxProps): MessageBox;
49
+ static showAsync(props: string | HtmlString | MessageBoxProps): Promise<string>;
49
50
  /**
50
51
  * display an alert message
51
52
  */
@@ -26,4 +26,4 @@
26
26
  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
27
  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
28
  **/
29
- export declare const x4js_version = "1.5.5";
29
+ export declare const x4js_version = "1.5.6";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x4js",
3
- "version": "1.5.5",
3
+ "version": "1.5.6",
4
4
  "description": "X4js core files",
5
5
  "main": "lib/cjs/index.js",
6
6
  "types": "lib/types/index.d.ts",