@web-atoms/web-controls 2.1.76 → 2.1.80
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/dist/basic/AtomRepeater.d.ts +59 -0
- package/dist/basic/AtomRepeater.d.ts.map +1 -0
- package/dist/basic/AtomRepeater.js +379 -0
- package/dist/basic/AtomRepeater.js.map +1 -0
- package/dist/basic/CheckBoxList.d.ts +6 -0
- package/dist/basic/CheckBoxList.d.ts.map +1 -0
- package/dist/basic/CheckBoxList.js +70 -0
- package/dist/basic/CheckBoxList.js.map +1 -0
- package/dist/basic/ComboBox.d.ts +10 -0
- package/dist/basic/ComboBox.d.ts.map +1 -0
- package/dist/basic/ComboBox.js +73 -0
- package/dist/basic/ComboBox.js.map +1 -0
- package/dist/basic/DropDown.d.ts +14 -0
- package/dist/basic/DropDown.d.ts.map +1 -0
- package/dist/basic/DropDown.js +103 -0
- package/dist/basic/DropDown.js.map +1 -0
- package/dist/basic/FormField.d.ts.map +1 -1
- package/dist/basic/FormField.js +4 -2
- package/dist/basic/FormField.js.map +1 -1
- package/dist/basic/RadioButtonList.d.ts +5 -0
- package/dist/basic/RadioButtonList.d.ts.map +1 -0
- package/dist/basic/RadioButtonList.js +55 -0
- package/dist/basic/RadioButtonList.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/basic/AtomRepeater.tsx +402 -0
- package/src/basic/CheckBoxList.tsx +56 -0
- package/src/basic/ComboBox.tsx +56 -0
- package/src/basic/DropDown.tsx +81 -0
- package/src/basic/FormField.tsx +4 -2
- package/src/basic/RadioButtonList.tsx +56 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import Bind from "@web-atoms/core/dist/core/Bind";
|
|
2
|
+
import { BindableProperty } from "@web-atoms/core/dist/core/BindableProperty";
|
|
3
|
+
import XNode from "@web-atoms/core/dist/core/XNode";
|
|
4
|
+
import StyleRule from "@web-atoms/core/dist/style/StyleRule";
|
|
5
|
+
import { AtomControl } from "@web-atoms/core/dist/web/controls/AtomControl";
|
|
6
|
+
import { PopupWindow } from "@web-atoms/core/dist/web/services/PopupService";
|
|
7
|
+
import CSS from "@web-atoms/core/dist/web/styles/CSS";
|
|
8
|
+
import AtomRepeater, { askSuggestion, Match, MatchCaseInsensitive } from "./AtomRepeater";
|
|
9
|
+
|
|
10
|
+
CSS(StyleRule()
|
|
11
|
+
.flexLayout({ inline: true, justifyContent: "stretch" as any})
|
|
12
|
+
, "div[data-drop-down=drop-down]");
|
|
13
|
+
|
|
14
|
+
export default class DropDown extends AtomRepeater {
|
|
15
|
+
|
|
16
|
+
@BindableProperty
|
|
17
|
+
public prompt: string;
|
|
18
|
+
|
|
19
|
+
@BindableProperty
|
|
20
|
+
public labelPath: (item) => string;
|
|
21
|
+
|
|
22
|
+
@BindableProperty
|
|
23
|
+
public match: Match<any>;
|
|
24
|
+
|
|
25
|
+
@BindableProperty
|
|
26
|
+
public suggestionRenderer: (item) => XNode;
|
|
27
|
+
|
|
28
|
+
public updateItems(container?: HTMLElement): void {
|
|
29
|
+
// don't do anything...
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public onPropertyChanged(name: string): void {
|
|
33
|
+
super.onPropertyChanged(name);
|
|
34
|
+
switch (name) {
|
|
35
|
+
case "labelPath":
|
|
36
|
+
this.itemRenderer = (item) => <div text={this.labelPath(item)}/>;
|
|
37
|
+
break;
|
|
38
|
+
case "prompt":
|
|
39
|
+
this.updateClasses();
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
protected preCreate(): void {
|
|
45
|
+
// super.preCreate();
|
|
46
|
+
this.prompt = "Select";
|
|
47
|
+
this.bindEvent(this.element, "click", () => this.openPopup());
|
|
48
|
+
this.valuePath = (item) => item?.value ?? item;
|
|
49
|
+
this.labelPath = (item) => item?.label ?? item;
|
|
50
|
+
this.itemRenderer = (item) => <div text={this.labelPath(item)}/>;
|
|
51
|
+
this.element.dataset.dropDown = "drop-down";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
protected async openPopup() {
|
|
55
|
+
const selected = await askSuggestion(
|
|
56
|
+
this.items,
|
|
57
|
+
this.suggestionRenderer ?? this.itemRenderer,
|
|
58
|
+
this.match ?? MatchCaseInsensitive(this.labelPath));
|
|
59
|
+
this.selectedItem = selected;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
protected updateClasses(): void {
|
|
63
|
+
this.removeAllChildren(this.element);
|
|
64
|
+
const ir = this.itemRenderer;
|
|
65
|
+
if (!ir) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!this.selectedItem) {
|
|
69
|
+
this.render(<div>
|
|
70
|
+
<div text={this.prompt}/>
|
|
71
|
+
<i class="fad fa-caret-circle-down"/>
|
|
72
|
+
</div>);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.render(<div>
|
|
76
|
+
{ ir(this.selectedItem) }
|
|
77
|
+
<i class="fad fa-caret-circle-down"/>
|
|
78
|
+
</div>);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
}
|
package/src/basic/FormField.tsx
CHANGED
|
@@ -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,7 +49,8 @@ const css = CSS(StyleRule()
|
|
|
48
49
|
)
|
|
49
50
|
.child(StyleRule("i")
|
|
50
51
|
.cursor("pointer")
|
|
51
|
-
.marginLeft(
|
|
52
|
+
.marginLeft("auto")
|
|
53
|
+
.color(Colors.lightGreen)
|
|
52
54
|
)
|
|
53
55
|
)
|
|
54
56
|
, "div[data-wa-form-field=wa-form-field]");
|
|
@@ -58,7 +60,7 @@ export default function FormField(
|
|
|
58
60
|
label,
|
|
59
61
|
required,
|
|
60
62
|
error,
|
|
61
|
-
helpIcon = "
|
|
63
|
+
helpIcon = "fas fa-question-circle",
|
|
62
64
|
help,
|
|
63
65
|
helpEventClick,
|
|
64
66
|
helpTitle,
|
|
@@ -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
|
+
}
|