@web-atoms/web-controls 2.1.75 → 2.1.79

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.
@@ -0,0 +1,402 @@
1
+ import { AtomBinder } from "@web-atoms/core/dist/core/AtomBinder";
2
+ import Bind from "@web-atoms/core/dist/core/Bind";
3
+ import { BindableProperty } from "@web-atoms/core/dist/core/BindableProperty";
4
+ import { IDisposable } from "@web-atoms/core/dist/core/types";
5
+ import XNode from "@web-atoms/core/dist/core/XNode";
6
+ import StyleRule from "@web-atoms/core/dist/style/StyleRule";
7
+ import { AtomControl } from "@web-atoms/core/dist/web/controls/AtomControl";
8
+ import { PopupWindow } from "@web-atoms/core/dist/web/services/PopupService";
9
+ import CSS from "@web-atoms/core/dist/web/styles/CSS";
10
+
11
+ const popupCSS = CSS(StyleRule()
12
+ .height(500)
13
+ .width(300)
14
+ .verticalFlexLayout({ alignItems: "stretch" })
15
+ .child(StyleRule(".items")
16
+ .flexStretch()
17
+ .overflow("auto")
18
+ .child(StyleRule(".presenter")
19
+ .child(StyleRule("*")
20
+ .padding(5)
21
+ )
22
+ )
23
+ )
24
+ );
25
+
26
+ export type Match<T> = (text: string) => (item: T) => boolean;
27
+
28
+ export type IAskSuggestion<T> = (items: T[], itemRenderer: (item: T) => XNode, match: Match<T>) => any;
29
+
30
+ export const MatchTrue = (... a: any[]) => true;
31
+
32
+ export const MatchFalse = (... a: any[]) => false;
33
+
34
+ export const ArrowToString = (item) => item.label?.toString() ?? item.toString();
35
+
36
+ export const MatchCaseInsensitive = (textField?: (item) => string) => {
37
+ textField ??= ArrowToString;
38
+ return (s: string) => {
39
+ s = s.toLowerCase();
40
+ return (item) => textField(item)?.toLowerCase()?.includes(s);
41
+ };
42
+ };
43
+
44
+ export const SameObjectValue = (item) => item;
45
+
46
+ /**
47
+ * Asks user for selecting item from given suggestions
48
+ * @param items items to display
49
+ * @param itemRenderer render function
50
+ * @param match search match
51
+ * @returns selected item
52
+ */
53
+ export function askSuggestion<T>(
54
+ items: T[],
55
+ itemRenderer: (item: T) => XNode,
56
+ match: Match<T>): Promise<T> {
57
+ class Suggestions extends PopupWindow {
58
+
59
+ @BindableProperty
60
+ public search: string;
61
+
62
+ protected create(): void {
63
+ this.render(<div class={popupCSS}>
64
+ <input
65
+ type="search"
66
+ value={Bind.twoWaysImmediate(() => this.search)}
67
+ autofocus={true}/>
68
+ <div class="items">
69
+ <AtomRepeater
70
+ class="presenter"
71
+ itemRenderer={itemRenderer}
72
+ visibilityFilter={Bind.oneWay(() => match(this.search))}
73
+ eventItemClick={(e) => {
74
+ this.viewModel.close(e.detail);
75
+ }}
76
+ items={items}/>
77
+ </div>
78
+ </div>);
79
+ }
80
+ }
81
+
82
+ return Suggestions.showModal();
83
+ }
84
+
85
+ export interface ISelectorCheckBox {
86
+ text?: string;
87
+ iconSelected?: string;
88
+ icon?: string;
89
+ [key: string]: any;
90
+ }
91
+
92
+ CSS(StyleRule()
93
+ .nested(StyleRule("i[data-click-event]")
94
+ .padding(5)
95
+ )
96
+ , "*[data-selected-item]");
97
+
98
+ CSS(StyleRule()
99
+ .nested(StyleRule("i[data-click-event=item-select]")
100
+ .padding(5)
101
+ )
102
+ .displayNone(" i[data-click-event=item-select]")
103
+ , "*[data-selected-item=true]");
104
+
105
+ CSS(StyleRule()
106
+ .displayNone(" i[data-click-event=item-deselect]")
107
+ , "*[data-selected-item=false]");
108
+
109
+ export function SelectorCheckBox(
110
+ {
111
+ text,
112
+ icon = "far fa-square",
113
+ iconSelected = "fas fa-check-square",
114
+ ... a
115
+ }: ISelectorCheckBox,
116
+ ... nodes: XNode[]) {
117
+ if (text) {
118
+ return <label>
119
+ <i class={icon} data-click-event="item-select"/>
120
+ <i class={iconSelected} data-click-event="item-deselect"/>
121
+ <span text={text}/>
122
+ { ... nodes }
123
+ </label>;
124
+ }
125
+ return <label>
126
+ <i class={icon} data-click-event="item-select"/>
127
+ <i class={iconSelected} data-click-event="item-deselect"/>
128
+ { ... nodes }
129
+ </label>;
130
+ }
131
+
132
+ export default class AtomRepeater extends AtomControl {
133
+
134
+ public "event-item-click"?: (e: CustomEvent) => void;
135
+ public "event-item-select"?: (e: CustomEvent) => void;
136
+ public "event-item-deselect"?: (e: CustomEvent) => void;
137
+
138
+ @BindableProperty
139
+ public allowMultipleSelection: boolean;
140
+
141
+ @BindableProperty
142
+ public selectedItems: any[];
143
+
144
+ @BindableProperty
145
+ public itemsPresenter: any;
146
+
147
+ @BindableProperty
148
+ public items: any[];
149
+
150
+ @BindableProperty
151
+ public visibilityFilter: (item: any) => boolean;
152
+
153
+ @BindableProperty
154
+ public itemRenderer: (item) => XNode;
155
+
156
+ @BindableProperty
157
+ public valuePath: (a) => any;
158
+
159
+ public get value() {
160
+ if (this.initialValue !== undefined) {
161
+ return this.initialValue;
162
+ }
163
+ const vp = this.valuePath ?? SameObjectValue;
164
+ return vp(this.selectedItem);
165
+ }
166
+
167
+ public set value(v) {
168
+ this.initialValue = v;
169
+ if (!this.items) {
170
+ return;
171
+ }
172
+ const vp = this.valuePath ?? SameObjectValue;
173
+ const selectedItem = this.items.find((item) => vp(item) === v);
174
+ this.selectedItem = selectedItem;
175
+ delete this.initialValue;
176
+ }
177
+
178
+ public get selectedItem() {
179
+ return this.selectedItems?.[0];
180
+ }
181
+
182
+ public set selectedItem(value) {
183
+ const si = this.selectedItems ??= [];
184
+ const first = si[0];
185
+ if (value === first) {
186
+ return;
187
+ }
188
+ si.length = 0;
189
+ si[0] = value;
190
+ this.updateClasses();
191
+ AtomBinder.refreshValue(this, "selectedItem");
192
+ AtomBinder.refreshValue(this, "value");
193
+ }
194
+
195
+ private initialValue: any;
196
+
197
+ private itemsDisposable: IDisposable;
198
+
199
+ private selectedItemsDisposable: IDisposable;
200
+
201
+ public onPropertyChanged(name: string): void {
202
+ switch (name) {
203
+ case "items":
204
+ this.itemsDisposable?.dispose();
205
+ const items = this.items;
206
+ const d = items?.watch(() => {
207
+ this.updateItems();
208
+ AtomBinder.refreshValue(this, "selectedItem");
209
+ AtomBinder.refreshValue(this, "value");
210
+ });
211
+ if (d) {
212
+ this.itemsDisposable = this.registerDisposable(d);
213
+ }
214
+ const iv = this.initialValue;
215
+ if (iv) {
216
+ this.value = iv;
217
+ }
218
+ this.updateItems();
219
+ break;
220
+ case "selectedItems":
221
+ this.selectedItemsDisposable?.dispose();
222
+ const selectedItems = this.selectedItems;
223
+ const sd = selectedItems?.watch(() => {
224
+ this.updateClasses();
225
+ AtomBinder.refreshValue(this, "selectedItem");
226
+ AtomBinder.refreshValue(this, "value");
227
+ });
228
+ if (sd) {
229
+ this.selectedItemsDisposable = this.registerDisposable(sd);
230
+ }
231
+ this.updateClasses();
232
+ break;
233
+ case "itemRenderer":
234
+ this.updateItems();
235
+ break;
236
+ case "visibilityFilter":
237
+ this.updateVisibility();
238
+ break;
239
+ }
240
+ }
241
+
242
+ public forEach<T>(action: (item: T, element: HTMLElement) => void, container?: HTMLElement) {
243
+ container ??= this.itemsPresenter ?? this.element;
244
+ const items = this.items;
245
+ let start = container.firstElementChild as HTMLElement;
246
+ while (start) {
247
+ // tslint:disable-next-line: no-bitwise
248
+ const index = ~~start.dataset.itemIndex;
249
+ const item = items[index];
250
+ action(item, start);
251
+ start = start.nextElementSibling as HTMLElement;
252
+ }
253
+ }
254
+
255
+ public *any(fx?: (item) => boolean, itemSelector?: string, container?: HTMLElement) {
256
+ container ??= this.itemsPresenter ?? this.element;
257
+ const items = this.items;
258
+ let node = container.firstElementChild as HTMLElement;
259
+ while (node) {
260
+ // tslint:disable-next-line: no-bitwise
261
+ const index = ~~node.dataset.itemIndex;
262
+ const item = items[index];
263
+ let element = node;
264
+ if (itemSelector) {
265
+ element = element.querySelector(itemSelector);
266
+ }
267
+ const ie = { item, element };
268
+ if (fx) {
269
+ if (fx(item)) {
270
+ yield ie;
271
+ }
272
+ continue;
273
+ }
274
+ yield ie;
275
+ node = node.nextElementSibling as HTMLElement;
276
+ }
277
+ }
278
+
279
+ public *all(container?: HTMLElement) {
280
+ container ??= this.itemsPresenter ?? this.element;
281
+ const items = this.items;
282
+ let element = container.firstElementChild as HTMLElement;
283
+ while (element) {
284
+ // tslint:disable-next-line: no-bitwise
285
+ const index = ~~element.dataset.itemIndex;
286
+ const item = items[index];
287
+ yield { item, element };
288
+ element = element.nextElementSibling as HTMLElement;
289
+ }
290
+ }
291
+
292
+ public updateItems(container?: HTMLElement) {
293
+ container ??= this.itemsPresenter ?? this.element;
294
+ let start = container.firstElementChild;
295
+ while (start) {
296
+ const e = start as any;
297
+ start = start.nextElementSibling;
298
+ this.unbindEvent(e);
299
+ e.atomControl?.dispose();
300
+ e.remove();
301
+ }
302
+ const ir = this.itemRenderer;
303
+ if (!ir) {
304
+ return;
305
+ }
306
+ const items = this.items;
307
+ if (!items) {
308
+ return;
309
+ }
310
+
311
+ const vp = this.valuePath ?? ((it) => it);
312
+ const si = (this.selectedItems ?? []).map(vp);
313
+ let i = 0;
314
+ for (const iterator of items) {
315
+ const e = ir(iterator);
316
+ const ea = e.attributes ??= {};
317
+ const v = vp(iterator);
318
+ ea["data-item-index"] = (i++).toString();
319
+ ea["data-selected-item"] = si.indexOf(v) !== -1
320
+ ? "true"
321
+ : "false";
322
+ this.render(<div>
323
+ { e }
324
+ </div>, container, this);
325
+ }
326
+
327
+ }
328
+
329
+ protected updateClasses() {
330
+ const container = this.itemsPresenter ?? this.element;
331
+ const items = this.items;
332
+ let element = container.firstElementChild as HTMLElement;
333
+ const vp = this.valuePath ?? ((i) => i);
334
+ const si = (this.selectedItems ?? []).map(vp);
335
+ while (element) {
336
+ // tslint:disable-next-line: no-bitwise
337
+ const index = ~~element.dataset.itemIndex;
338
+ const item = items[index];
339
+ const v = vp(item);
340
+ element.dataset.selectedItem = si.indexOf(v) !== -1
341
+ ? "true"
342
+ : "false";
343
+ element = element.nextElementSibling as HTMLElement;
344
+ }
345
+
346
+ }
347
+
348
+ protected preCreate(): void {
349
+ this.bindEvent(this.element, "click", (e: MouseEvent) => this.onElementClick(e));
350
+ }
351
+
352
+ protected onElementClick(e: MouseEvent) {
353
+ const items = this.items;
354
+ let target = e.target as HTMLElement;
355
+ let eventName = "itemClick";
356
+ while (target) {
357
+ const itemIndex = target.dataset.itemIndex;
358
+ const itemClickEvent = target.dataset.clickEvent;
359
+ if (itemClickEvent) {
360
+ eventName = itemClickEvent.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
361
+ }
362
+ if (typeof itemIndex !== "undefined") {
363
+ // tslint:disable-next-line: no-bitwise
364
+ const item = items[~~itemIndex];
365
+ if (eventName === "itemSelect" || eventName === "itemDeselect") {
366
+ const si = this.selectedItems;
367
+ if (si) {
368
+ const index = si.indexOf(item);
369
+ if (index === -1) {
370
+ si.add(item);
371
+ } else {
372
+ si.removeAt(index);
373
+ }
374
+ }
375
+ }
376
+ if (item) {
377
+ this.element.dispatchEvent(new CustomEvent(eventName, {
378
+ detail: item,
379
+ bubbles: false
380
+ }));
381
+ }
382
+ return;
383
+ }
384
+ target = target.parentElement as HTMLElement;
385
+ }
386
+ }
387
+
388
+ protected updateVisibility() {
389
+ const container = this.itemsPresenter ?? this.element;
390
+ const items = this.items;
391
+ let element = container.firstElementChild as HTMLElement;
392
+ const vf = this.visibilityFilter ?? MatchTrue;
393
+ while (element) {
394
+ // tslint:disable-next-line: no-bitwise
395
+ const index = ~~element.dataset.itemIndex;
396
+ const item = items[index];
397
+ element.style.display = vf(item) ? "" : "none";
398
+ element = element.nextElementSibling as HTMLElement;
399
+ }
400
+ }
401
+
402
+ }
@@ -0,0 +1,56 @@
1
+ import { BindableProperty } from "@web-atoms/core/dist/core/BindableProperty";
2
+ import Colors from "@web-atoms/core/dist/core/Colors";
3
+ import XNode from "@web-atoms/core/dist/core/XNode";
4
+ import StyleRule from "@web-atoms/core/dist/style/StyleRule";
5
+ import CSS from "@web-atoms/core/dist/web/styles/CSS";
6
+ import AtomRepeater from "./AtomRepeater";
7
+
8
+ CSS(StyleRule()
9
+ .flexLayout({ inline: true, justifyContent: "flex-start"})
10
+ .flexFlow("wrap"),
11
+ "div[data-checkbox-list=checkbox-list]");
12
+
13
+ CSS(StyleRule()
14
+ .flexLayout({ justifyContent: "flex-start" })
15
+ .marginRight(5)
16
+ .child(StyleRule("span")
17
+ .cursor("pointer")
18
+ )
19
+ .and(StyleRule("[data-selected-item=true]")
20
+ .color(Colors.blue)
21
+ )
22
+ .displayNone("[data-selected-item=true] > i.far")
23
+ .displayNone("[data-selected-item=false] > i.fas")
24
+ , "div[data-item-type=checkbox]");
25
+
26
+ export default class CheckBoxList extends AtomRepeater {
27
+
28
+ @BindableProperty
29
+ public labelPath;
30
+
31
+ protected preCreate(): void {
32
+ super.preCreate();
33
+ this.element.dataset.checkboxList = "checkbox-list";
34
+ this.bindEvent(this.element, "itemClick", (e: CustomEvent) => {
35
+ const s = this.selectedItems;
36
+ if (!s) {
37
+ return;
38
+ }
39
+ const item = e.detail;
40
+ if (s.indexOf(item) === -1) {
41
+ s.add(item);
42
+ this.element.dispatchEvent(new CustomEvent("itemSelect", { detail: item, bubbles: false }));
43
+ } else {
44
+ this.element.dispatchEvent(new CustomEvent("itemDeselect", { detail: item, bubbles: false }));
45
+ s.remove(item);
46
+ }
47
+ });
48
+
49
+ this.itemRenderer = (item) => <div data-item-type="checkbox">
50
+ <i class="far fa-square"/>
51
+ <i class="fas fa-check-square"/>
52
+ <span text={item.label}/>
53
+ </div>;
54
+ }
55
+
56
+ }
@@ -0,0 +1,56 @@
1
+ import { BindableProperty } from "@web-atoms/core/dist/core/BindableProperty";
2
+ import XNode from "@web-atoms/core/dist/core/XNode";
3
+ import AtomRepeater from "./AtomRepeater";
4
+
5
+ export default class ComboBox extends AtomRepeater {
6
+
7
+ @BindableProperty
8
+ public labelPath: any;
9
+
10
+ private isChanging = false;
11
+
12
+ constructor(app, e) {
13
+ super(app, e ?? document.createElement("select"));
14
+ }
15
+
16
+ public updateItems(container?: HTMLElement): void {
17
+ super.updateItems(container);
18
+ if (this.isChanging) {
19
+ return;
20
+ }
21
+ const selectedItems = this.selectedItems;
22
+ if (!selectedItems) {
23
+ return;
24
+ }
25
+ if (selectedItems.length === 0) {
26
+ return;
27
+ }
28
+ this.isChanging = true;
29
+ const first = selectedItems[0];
30
+ (this.element as HTMLSelectElement).selectedIndex = this.items.indexOf(first);
31
+ this.isChanging = false;
32
+ }
33
+
34
+ protected preCreate(): void {
35
+ super.preCreate();
36
+ this.labelPath = (item) => item?.label ?? item.toString();
37
+ this.valuePath = (item) => item?.value ?? item.toString();
38
+ this.itemRenderer = (item) => <option>{this.labelPath(item)}</option>;
39
+
40
+ this.bindEvent(this.element, "change", () => this.changeSelection());
41
+ }
42
+
43
+ protected changeSelection(): void {
44
+ if (this.isChanging) {
45
+ return;
46
+ }
47
+ this.isChanging = true;
48
+ this.selectedItems?.clear();
49
+ const index = (this.element as HTMLSelectElement).selectedIndex;
50
+ if (index !== -1) {
51
+ this.selectedItems?.add(this.items[index]);
52
+ }
53
+ this.isChanging = false;
54
+ }
55
+
56
+ }
@@ -39,6 +39,7 @@ const css = CSS(StyleRule()
39
39
  )
40
40
  )
41
41
  .child(StyleRule(".label")
42
+ .display("flex")
42
43
  .child(StyleRule(".true")
43
44
  .visibility("visible")
44
45
  .color(Colors.red)
@@ -48,24 +49,18 @@ const css = CSS(StyleRule()
48
49
  )
49
50
  .child(StyleRule("i")
50
51
  .cursor("pointer")
51
- .marginLeft(5)
52
+ .marginLeft("auto")
53
+ .color(Colors.lightGreen)
52
54
  )
53
55
  )
54
56
  , "div[data-wa-form-field=wa-form-field]");
55
57
 
56
- const helpCSS = CSS(StyleRule()
57
- .padding(10)
58
- .child(StyleRule(".fad")
59
- .absolutePosition({ top: 5, right: 5 })
60
- )
61
- );
62
-
63
58
  export default function FormField(
64
59
  {
65
60
  label,
66
61
  required,
67
62
  error,
68
- helpIcon = "fad fa-question-circle",
63
+ helpIcon = "fas fa-question-circle",
69
64
  help,
70
65
  helpEventClick,
71
66
  helpTitle,
@@ -82,20 +77,14 @@ export default function FormField(
82
77
  return;
83
78
  }
84
79
 
85
- const cancelToken = new CancelToken();
86
-
87
- class HelpPopup extends AtomControl {
80
+ class HelpPopup extends PopupWindow {
88
81
  protected create(): void {
89
- this.render(<div class={helpCSS}>
90
- <i
91
- class="fad fa-times-circle"
92
- eventClick={() => cancelToken.cancel()}
93
- />
82
+ this.render(<div>
94
83
  { help as any}
95
84
  </div>);
96
85
  }
97
86
  }
98
- PopupService.showWindow(s.element as any, HelpPopup as any, { cancelToken });
87
+ HelpPopup.showWindow({ title : helpTitle ?? "Help" });
99
88
  });
100
89
  }
101
90
 
@@ -0,0 +1,56 @@
1
+ import Bind from "@web-atoms/core/dist/core/Bind";
2
+ import { BindableProperty } from "@web-atoms/core/dist/core/BindableProperty";
3
+ import Colors from "@web-atoms/core/dist/core/Colors";
4
+ import XNode from "@web-atoms/core/dist/core/XNode";
5
+ import StyleRule from "@web-atoms/core/dist/style/StyleRule";
6
+ import { AtomControl } from "@web-atoms/core/dist/web/controls/AtomControl";
7
+ import CSS from "@web-atoms/core/dist/web/styles/CSS";
8
+ import AtomRepeater from "./AtomRepeater";
9
+
10
+ CSS(StyleRule()
11
+ .flexLayout({ inline: true, justifyContent: "flex-start"})
12
+ .flexFlow("wrap"),
13
+ "*[data-radio-button-list=radio-button-list]");
14
+
15
+ CSS(StyleRule()
16
+ .flexLayout({ justifyContent: "flex-start" })
17
+ .marginRight(5)
18
+ .child(StyleRule("span")
19
+ .cursor("pointer")
20
+ )
21
+ .and(StyleRule("[data-selected-item=true]")
22
+ .color(Colors.blue)
23
+ )
24
+ .displayNone("[data-selected-item=true] > i.fa-circle")
25
+ .displayNone("[data-selected-item=false] > i.fa-dot-circle")
26
+ , "div[data-item-type=radio]");
27
+
28
+ export default class RadioButtonList extends AtomRepeater {
29
+
30
+ protected preCreate(): void {
31
+ super.preCreate();
32
+ this.valuePath = (item) => item?.value ?? item;
33
+ this.bindEvent(this.element, "itemClick", (e: CustomEvent) => {
34
+ const s = this.selectedItems;
35
+ if (!s) {
36
+ return;
37
+ }
38
+ const item = e.detail;
39
+ const old = this.selectedItem;
40
+ if (old) {
41
+ this.element.dispatchEvent(new CustomEvent("itemDeselect", { detail: old, bubbles: false }));
42
+ }
43
+ this.selectedItem = item;
44
+ this.element.dispatchEvent(new CustomEvent("itemSelect", { detail: item, bubbles: false }));
45
+ });
46
+ this.element.dataset.radioButtonList = "radio-button-list";
47
+ this.itemRenderer = (item) => <div
48
+ data-item-type="radio">
49
+ <i class="far fa-dot-circle"/>
50
+ <i class="far fa-circle"/>
51
+ <span text={item.label}/>
52
+ </div>;
53
+
54
+ }
55
+
56
+ }