ezfw-core 1.0.21 → 1.0.23
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/components/EzBaseComponent.ts +100 -5
- package/components/EzComponent.ts +3 -3
- package/components/EzLabel.ts +12 -3
- package/components/avatar/EzAvatar.ts +84 -54
- package/components/badge/EzBadge.ts +43 -24
- package/components/button/EzButton.ts +5 -3
- package/components/button/EzButtonGroup.ts +7 -10
- package/components/card/EzCard.ts +2 -1
- package/components/chart/EzChart.ts +20 -15
- package/components/checkbox/EzCheckbox.ts +47 -43
- package/components/dataview/EzDataView.ts +14 -29
- package/components/dataview/modes/EzDataViewCards.ts +51 -41
- package/components/dataview/modes/EzDataViewGrid.ts +5 -2
- package/components/datepicker/EzDatePicker.ts +2 -2
- package/components/dialog/EzDialog.ts +84 -67
- package/components/dropdown/EzDropdown.ts +72 -58
- package/components/form/EzForm.ts +45 -37
- package/components/kanban/EzKanban.module.scss +221 -0
- package/components/kanban/EzKanban.ts +222 -0
- package/components/kanban/EzKanbanTypes.ts +166 -0
- package/components/kanban/board/EzKanbanBoard.ts +117 -0
- package/components/kanban/card/EzKanbanCard.module.scss +173 -0
- package/components/kanban/card/EzKanbanCard.ts +275 -0
- package/components/kanban/card/EzKanbanCardEditor.ts +209 -0
- package/components/kanban/column/EzKanbanColumn.ts +253 -0
- package/components/kanban/state/EzKanbanController.ts +373 -0
- package/components/kanban/state/EzKanbanDragDrop.ts +226 -0
- package/components/panel/EzPanel.ts +59 -68
- package/components/picker/EzPicker.module.scss +14 -0
- package/components/picker/EzPicker.ts +118 -0
- package/components/radio/EzRadio.ts +55 -47
- package/components/select/EzSelect.ts +48 -44
- package/components/skeleton/EzSkeleton.ts +31 -26
- package/components/switch/EzSwitch.ts +52 -44
- package/components/tabs/EzTabPanel.ts +52 -48
- package/components/textarea/EzTextarea.ts +69 -54
- package/components/timepicker/EzTimePicker.ts +2 -2
- package/components/tooltip/EzTooltip.ts +20 -33
- package/core/ez.ts +7 -0
- package/core/loader.ts +2 -0
- package/core/renderer.ts +80 -4
- package/core/styleShortcuts.ts +418 -0
- package/package.json +1 -1
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { KanbanCard, NormalizedColumn, DragContext } from '../EzKanbanTypes.js';
|
|
2
|
+
import type { EzKanbanController } from './EzKanbanController.js';
|
|
3
|
+
|
|
4
|
+
export class EzKanbanDragDrop {
|
|
5
|
+
private _controller: EzKanbanController;
|
|
6
|
+
private _boardEl: HTMLElement | null = null;
|
|
7
|
+
|
|
8
|
+
constructor(controller: EzKanbanController) {
|
|
9
|
+
this._controller = controller;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
setBoardElement(el: HTMLElement): void {
|
|
13
|
+
this._boardEl = el;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
handleCardDragStart(e: DragEvent, card: KanbanCard, columnId: string): void {
|
|
17
|
+
const context: DragContext = {
|
|
18
|
+
type: 'card',
|
|
19
|
+
item: card,
|
|
20
|
+
sourceColumnId: columnId,
|
|
21
|
+
sourceIndex: this._controller.findCardIndex(card.id, columnId),
|
|
22
|
+
isDragging: true
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
this._controller.setDragContext(context);
|
|
26
|
+
|
|
27
|
+
e.dataTransfer!.effectAllowed = 'move';
|
|
28
|
+
e.dataTransfer!.setData('application/json', JSON.stringify({
|
|
29
|
+
type: 'card',
|
|
30
|
+
cardId: card.id,
|
|
31
|
+
columnId
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
const cardEl = (e.target as HTMLElement).closest('.ez-kanban-card');
|
|
35
|
+
if (cardEl) {
|
|
36
|
+
cardEl.classList.add('is-dragging');
|
|
37
|
+
e.dataTransfer!.setDragImage(cardEl, 20, 20);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
handleCardDragOver(e: DragEvent, targetColumnId: string): void {
|
|
42
|
+
const context = this._controller.getDragContext();
|
|
43
|
+
if (!context || context.type !== 'card') return;
|
|
44
|
+
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
e.dataTransfer!.dropEffect = 'move';
|
|
47
|
+
|
|
48
|
+
const cardEl = (e.target as HTMLElement).closest('.ez-kanban-card');
|
|
49
|
+
const columnEl = (e.target as HTMLElement).closest('.ez-kanban-column');
|
|
50
|
+
|
|
51
|
+
this._clearDropIndicators();
|
|
52
|
+
|
|
53
|
+
if (cardEl && !cardEl.classList.contains('is-dragging')) {
|
|
54
|
+
const rect = cardEl.getBoundingClientRect();
|
|
55
|
+
const midY = rect.top + rect.height / 2;
|
|
56
|
+
|
|
57
|
+
if (e.clientY < midY) {
|
|
58
|
+
cardEl.classList.add('drop-above');
|
|
59
|
+
} else {
|
|
60
|
+
cardEl.classList.add('drop-below');
|
|
61
|
+
}
|
|
62
|
+
} else if (columnEl) {
|
|
63
|
+
const cardsContainer = columnEl.querySelector('.ez-kanban-column-cards');
|
|
64
|
+
if (cardsContainer && cardsContainer.children.length === 0) {
|
|
65
|
+
cardsContainer.classList.add('drop-empty');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
handleCardDragLeave(e: DragEvent): void {
|
|
71
|
+
const cardEl = (e.target as HTMLElement).closest('.ez-kanban-card');
|
|
72
|
+
if (cardEl) {
|
|
73
|
+
cardEl.classList.remove('drop-above', 'drop-below');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const cardsContainer = (e.target as HTMLElement).closest('.ez-kanban-column-cards');
|
|
77
|
+
if (cardsContainer) {
|
|
78
|
+
cardsContainer.classList.remove('drop-empty');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
handleCardDrop(e: DragEvent, targetColumnId: string, cardsContainer: HTMLElement): void {
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
|
|
85
|
+
const context = this._controller.getDragContext();
|
|
86
|
+
if (!context || context.type !== 'card') return;
|
|
87
|
+
|
|
88
|
+
const card = context.item as KanbanCard;
|
|
89
|
+
const targetIndex = this._getDropIndex(e, cardsContainer);
|
|
90
|
+
|
|
91
|
+
this._controller.moveCard(card.id, targetColumnId, targetIndex);
|
|
92
|
+
this._cleanup();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
handleCardDragEnd(): void {
|
|
96
|
+
this._cleanup();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
handleColumnDragStart(e: DragEvent, column: NormalizedColumn): void {
|
|
100
|
+
const columns = this._controller.state.columns;
|
|
101
|
+
const sourceIndex = columns.findIndex(c => c._id === column._id);
|
|
102
|
+
|
|
103
|
+
const context: DragContext = {
|
|
104
|
+
type: 'column',
|
|
105
|
+
item: column,
|
|
106
|
+
sourceColumnId: null,
|
|
107
|
+
sourceIndex,
|
|
108
|
+
isDragging: true
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
this._controller.setDragContext(context);
|
|
112
|
+
|
|
113
|
+
e.dataTransfer!.effectAllowed = 'move';
|
|
114
|
+
e.dataTransfer!.setData('application/json', JSON.stringify({
|
|
115
|
+
type: 'column',
|
|
116
|
+
columnId: column._id
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
const columnEl = (e.target as HTMLElement).closest('.ez-kanban-column');
|
|
120
|
+
if (columnEl) {
|
|
121
|
+
columnEl.classList.add('is-dragging');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
handleColumnDragOver(e: DragEvent, targetColumn: NormalizedColumn): void {
|
|
126
|
+
const context = this._controller.getDragContext();
|
|
127
|
+
if (!context || context.type !== 'column') return;
|
|
128
|
+
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
e.dataTransfer!.dropEffect = 'move';
|
|
131
|
+
|
|
132
|
+
const columnEl = (e.target as HTMLElement).closest('.ez-kanban-column');
|
|
133
|
+
if (!columnEl || columnEl.classList.contains('is-dragging')) return;
|
|
134
|
+
|
|
135
|
+
this._clearColumnDropIndicators();
|
|
136
|
+
|
|
137
|
+
const rect = columnEl.getBoundingClientRect();
|
|
138
|
+
const midX = rect.left + rect.width / 2;
|
|
139
|
+
|
|
140
|
+
if (e.clientX < midX) {
|
|
141
|
+
columnEl.classList.add('drop-before');
|
|
142
|
+
} else {
|
|
143
|
+
columnEl.classList.add('drop-after');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
handleColumnDrop(e: DragEvent, targetColumn: NormalizedColumn): void {
|
|
148
|
+
e.preventDefault();
|
|
149
|
+
|
|
150
|
+
const context = this._controller.getDragContext();
|
|
151
|
+
if (!context || context.type !== 'column') return;
|
|
152
|
+
|
|
153
|
+
const sourceColumn = context.item as NormalizedColumn;
|
|
154
|
+
const columns = this._controller.state.columns;
|
|
155
|
+
|
|
156
|
+
const columnEl = (e.target as HTMLElement).closest('.ez-kanban-column');
|
|
157
|
+
const rect = columnEl?.getBoundingClientRect();
|
|
158
|
+
const isDropBefore = rect && e.clientX < rect.left + rect.width / 2;
|
|
159
|
+
|
|
160
|
+
let targetIndex = columns.findIndex(c => c._id === targetColumn._id);
|
|
161
|
+
if (!isDropBefore) targetIndex++;
|
|
162
|
+
|
|
163
|
+
const sourceIndex = columns.findIndex(c => c._id === sourceColumn._id);
|
|
164
|
+
if (sourceIndex < targetIndex) targetIndex--;
|
|
165
|
+
|
|
166
|
+
this._controller.moveColumn(sourceColumn._id, targetIndex);
|
|
167
|
+
this._cleanup();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
handleColumnDragEnd(): void {
|
|
171
|
+
this._cleanup();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private _getDropIndex(e: DragEvent, cardsContainer: HTMLElement): number {
|
|
175
|
+
const cards = cardsContainer.querySelectorAll('.ez-kanban-card:not(.is-dragging)');
|
|
176
|
+
let index = cards.length;
|
|
177
|
+
|
|
178
|
+
for (let i = 0; i < cards.length; i++) {
|
|
179
|
+
const rect = cards[i].getBoundingClientRect();
|
|
180
|
+
if (e.clientY < rect.top + rect.height / 2) {
|
|
181
|
+
index = i;
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return index;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private _clearDropIndicators(): void {
|
|
190
|
+
if (!this._boardEl) return;
|
|
191
|
+
|
|
192
|
+
this._boardEl.querySelectorAll('.ez-kanban-card').forEach(el => {
|
|
193
|
+
el.classList.remove('drop-above', 'drop-below');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
this._boardEl.querySelectorAll('.ez-kanban-column-cards').forEach(el => {
|
|
197
|
+
el.classList.remove('drop-empty');
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private _clearColumnDropIndicators(): void {
|
|
202
|
+
if (!this._boardEl) return;
|
|
203
|
+
|
|
204
|
+
this._boardEl.querySelectorAll('.ez-kanban-column').forEach(el => {
|
|
205
|
+
el.classList.remove('drop-before', 'drop-after');
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private _cleanup(): void {
|
|
210
|
+
if (this._boardEl) {
|
|
211
|
+
this._boardEl.querySelectorAll('.ez-kanban-card').forEach(el => {
|
|
212
|
+
el.classList.remove('is-dragging', 'drop-above', 'drop-below');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
this._boardEl.querySelectorAll('.ez-kanban-column').forEach(el => {
|
|
216
|
+
el.classList.remove('is-dragging', 'drop-before', 'drop-after');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
this._boardEl.querySelectorAll('.ez-kanban-column-cards').forEach(el => {
|
|
220
|
+
el.classList.remove('drop-empty');
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
this._controller.setDragContext(null);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -3,7 +3,7 @@ import { cx } from '../../utils/cssModules.js';
|
|
|
3
3
|
import { EzBaseComponent, EzBaseComponentConfig } from '../EzBaseComponent.js';
|
|
4
4
|
|
|
5
5
|
declare const ez: {
|
|
6
|
-
|
|
6
|
+
_createElement(config: unknown): Promise<HTMLElement>;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
const cls = cx(styles);
|
|
@@ -33,96 +33,87 @@ export class EzPanel extends EzBaseComponent {
|
|
|
33
33
|
async render(): Promise<HTMLElement> {
|
|
34
34
|
const cfg = this.config;
|
|
35
35
|
|
|
36
|
-
const
|
|
37
|
-
panel.className = cls(
|
|
38
|
-
'panel',
|
|
39
|
-
cfg.variant,
|
|
40
|
-
cfg.noPadding && 'noPadding',
|
|
41
|
-
cfg.collapsible && 'collapsible',
|
|
42
|
-
cfg.collapsed && 'collapsed'
|
|
43
|
-
);
|
|
36
|
+
const panelItems: unknown[] = [];
|
|
44
37
|
|
|
38
|
+
// Header
|
|
45
39
|
if (cfg.title || cfg.icon || cfg.tools || cfg.badge !== undefined) {
|
|
46
|
-
const
|
|
47
|
-
header.className = cls('header');
|
|
40
|
+
const headerItems: unknown[] = [];
|
|
48
41
|
|
|
49
42
|
if (cfg.icon) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
headerItems.push({
|
|
44
|
+
eztype: 'i',
|
|
45
|
+
cls: [cls('icon'), cfg.icon].join(' ')
|
|
46
|
+
});
|
|
53
47
|
}
|
|
54
48
|
|
|
55
49
|
if (cfg.title) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
50
|
+
headerItems.push({
|
|
51
|
+
eztype: 'span',
|
|
52
|
+
cls: cls('title'),
|
|
53
|
+
text: cfg.title
|
|
54
|
+
});
|
|
60
55
|
}
|
|
61
56
|
|
|
62
57
|
if (cfg.badge !== undefined) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
58
|
+
headerItems.push({
|
|
59
|
+
eztype: 'span',
|
|
60
|
+
cls: cls('badge'),
|
|
61
|
+
text: String(cfg.badge)
|
|
62
|
+
});
|
|
67
63
|
}
|
|
68
64
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
headerItems.push({
|
|
66
|
+
eztype: 'span',
|
|
67
|
+
cls: cls('spacer')
|
|
68
|
+
});
|
|
72
69
|
|
|
73
70
|
if (cfg.tools && cfg.tools.length > 0) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
cfg.controller || null,
|
|
80
|
-
null,
|
|
81
|
-
(cfg.css as string) || null
|
|
82
|
-
);
|
|
83
|
-
for (const toolEl of toolEls) {
|
|
84
|
-
if (toolEl instanceof Node) {
|
|
85
|
-
tools.appendChild(toolEl);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
header.appendChild(tools);
|
|
71
|
+
headerItems.push({
|
|
72
|
+
eztype: 'div',
|
|
73
|
+
cls: cls('tools'),
|
|
74
|
+
items: cfg.tools
|
|
75
|
+
});
|
|
89
76
|
}
|
|
90
77
|
|
|
91
78
|
if (cfg.collapsible) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
79
|
+
headerItems.push({
|
|
80
|
+
eztype: 'button',
|
|
81
|
+
type: 'button',
|
|
82
|
+
cls: cls('toggle'),
|
|
83
|
+
items: [{ eztype: 'i', cls: 'fa-solid fa-chevron-down' }],
|
|
84
|
+
onClick: () => this.toggleCollapse()
|
|
85
|
+
});
|
|
98
86
|
}
|
|
99
87
|
|
|
100
|
-
|
|
88
|
+
panelItems.push({
|
|
89
|
+
eztype: 'div',
|
|
90
|
+
cls: cls('header'),
|
|
91
|
+
items: headerItems
|
|
92
|
+
});
|
|
101
93
|
}
|
|
102
94
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
95
|
+
// Body
|
|
96
|
+
panelItems.push({
|
|
97
|
+
eztype: 'div',
|
|
98
|
+
cls: cls('body'),
|
|
99
|
+
items: cfg.items || []
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const panel = await ez._createElement({
|
|
103
|
+
eztype: 'div',
|
|
104
|
+
cls: cls(
|
|
105
|
+
'panel',
|
|
106
|
+
cfg.variant,
|
|
107
|
+
cfg.noPadding && 'noPadding',
|
|
108
|
+
cfg.collapsible && 'collapsible',
|
|
109
|
+
cfg.collapsed && 'collapsed'
|
|
110
|
+
),
|
|
111
|
+
style: cfg.style,
|
|
112
|
+
items: panelItems
|
|
113
|
+
}) as HTMLElement;
|
|
121
114
|
|
|
122
115
|
this._panel = panel;
|
|
123
|
-
this._body = body;
|
|
124
|
-
|
|
125
|
-
this.applyStyles(panel);
|
|
116
|
+
this._body = panel.querySelector(`.${cls('body')}`);
|
|
126
117
|
|
|
127
118
|
return panel;
|
|
128
119
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import styles from './EzPicker.module.scss';
|
|
2
|
+
import { cx } from '../../utils/cssModules.js';
|
|
3
|
+
import { EzBaseComponent, EzBaseComponentConfig } from '../EzBaseComponent.js';
|
|
4
|
+
|
|
5
|
+
const css = cx(styles);
|
|
6
|
+
|
|
7
|
+
declare const ez: {
|
|
8
|
+
define(name: string, component: unknown): void;
|
|
9
|
+
_createElement(config: unknown, controller?: string | null, parent?: unknown): Promise<HTMLElement>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
interface EzPickerConfig extends EzBaseComponentConfig {
|
|
13
|
+
options?: unknown[];
|
|
14
|
+
value?: unknown;
|
|
15
|
+
onChange?: (value: unknown) => void;
|
|
16
|
+
optionRender?: (option: unknown, selected: boolean) => unknown;
|
|
17
|
+
layout?: 'horizontal' | 'vertical';
|
|
18
|
+
gap?: number | string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class EzPicker extends EzBaseComponent {
|
|
22
|
+
static eztype = 'EzPicker';
|
|
23
|
+
declare config: EzPickerConfig;
|
|
24
|
+
private _selected: unknown;
|
|
25
|
+
private _container: HTMLDivElement | null = null;
|
|
26
|
+
private _options: unknown[] = [];
|
|
27
|
+
private _optionRender: ((option: unknown, selected: boolean) => unknown) | undefined;
|
|
28
|
+
private _onChange: ((value: unknown) => void) | undefined;
|
|
29
|
+
private _cssModule: string | undefined;
|
|
30
|
+
|
|
31
|
+
async render(): Promise<HTMLDivElement> {
|
|
32
|
+
const props = (this.config.props || {}) as Record<string, unknown>;
|
|
33
|
+
this._options = (props.options || this.config.options || []) as unknown[];
|
|
34
|
+
this._selected = props.value ?? this.config.value;
|
|
35
|
+
this._onChange = (props.onChange || this.config.onChange) as ((value: unknown) => void) | undefined;
|
|
36
|
+
this._optionRender = (props.optionRender || this.config.optionRender) as ((option: unknown, selected: boolean) => unknown) | undefined;
|
|
37
|
+
this._cssModule = ((props.css as string | undefined) || this.config.css) as string | undefined;
|
|
38
|
+
|
|
39
|
+
const layout = (props.layout || this.config.layout || 'horizontal') as 'horizontal' | 'vertical';
|
|
40
|
+
const gap = (props.gap ?? this.config.gap) as number | string | undefined;
|
|
41
|
+
|
|
42
|
+
const optionConfigs = await this._buildOptionConfigs();
|
|
43
|
+
|
|
44
|
+
const container = await ez._createElement({
|
|
45
|
+
eztype: 'div',
|
|
46
|
+
cls: css('picker', layout),
|
|
47
|
+
style: gap ? { gap: typeof gap === 'number' ? `${gap}px` : gap } : undefined,
|
|
48
|
+
items: optionConfigs
|
|
49
|
+
}, this.config.controller || null, this) as HTMLDivElement;
|
|
50
|
+
|
|
51
|
+
this._container = container;
|
|
52
|
+
|
|
53
|
+
return container;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private async _buildOptionConfigs(): Promise<unknown[]> {
|
|
57
|
+
const configs: unknown[] = [];
|
|
58
|
+
|
|
59
|
+
for (const option of this._options) {
|
|
60
|
+
const isSelected = option === this._selected;
|
|
61
|
+
|
|
62
|
+
if (this._optionRender) {
|
|
63
|
+
const config = this._optionRender(option, isSelected) as Record<string, unknown>;
|
|
64
|
+
if (config) {
|
|
65
|
+
if (this._cssModule && !config.css) {
|
|
66
|
+
config.css = this._cssModule;
|
|
67
|
+
}
|
|
68
|
+
configs.push({
|
|
69
|
+
eztype: 'div',
|
|
70
|
+
cls: css('option'),
|
|
71
|
+
items: [config],
|
|
72
|
+
onClick: () => this._selectOption(option)
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
configs.push({
|
|
77
|
+
eztype: 'div',
|
|
78
|
+
cls: css('option'),
|
|
79
|
+
text: String(option),
|
|
80
|
+
onClick: () => this._selectOption(option)
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return configs;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private async _renderOptions(): Promise<void> {
|
|
89
|
+
if (!this._container) return;
|
|
90
|
+
|
|
91
|
+
this._container.innerHTML = '';
|
|
92
|
+
|
|
93
|
+
const optionConfigs = await this._buildOptionConfigs();
|
|
94
|
+
for (const config of optionConfigs) {
|
|
95
|
+
const el = await ez._createElement(config);
|
|
96
|
+
this._container.appendChild(el);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private async _selectOption(option: unknown): Promise<void> {
|
|
101
|
+
if (option === this._selected) return;
|
|
102
|
+
|
|
103
|
+
this._selected = option;
|
|
104
|
+
|
|
105
|
+
// Re-render to update selected state
|
|
106
|
+
await this._renderOptions();
|
|
107
|
+
|
|
108
|
+
if (this._onChange) {
|
|
109
|
+
this._onChange(option);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getValue(): unknown {
|
|
114
|
+
return this._selected;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
ez.define('EzPicker', EzPicker);
|
|
@@ -4,6 +4,10 @@ import { EzBaseComponent, EzBaseComponentConfig } from '../EzBaseComponent.js';
|
|
|
4
4
|
|
|
5
5
|
const cls = cx(styles);
|
|
6
6
|
|
|
7
|
+
declare const ez: {
|
|
8
|
+
_createElement(config: unknown): Promise<HTMLElement>;
|
|
9
|
+
};
|
|
10
|
+
|
|
7
11
|
export interface RadioOption {
|
|
8
12
|
value: string | number;
|
|
9
13
|
label: string;
|
|
@@ -27,18 +31,10 @@ export class EzRadio extends EzBaseComponent {
|
|
|
27
31
|
private _inputs: HTMLInputElement[] = [];
|
|
28
32
|
private _error: HTMLDivElement | null = null;
|
|
29
33
|
|
|
30
|
-
render(): HTMLDivElement {
|
|
34
|
+
async render(): Promise<HTMLDivElement> {
|
|
31
35
|
const cfg = this.config;
|
|
32
36
|
const options = cfg.options || [];
|
|
33
37
|
|
|
34
|
-
const wrapper = document.createElement('div');
|
|
35
|
-
wrapper.className = cls(
|
|
36
|
-
'radioGroup',
|
|
37
|
-
cfg.size,
|
|
38
|
-
cfg.disabled && 'disabled',
|
|
39
|
-
cfg.layout === 'horizontal' && 'horizontal'
|
|
40
|
-
);
|
|
41
|
-
|
|
42
38
|
if (cfg.name && cfg.formData) {
|
|
43
39
|
this.config.bind = `${cfg.formData}.${cfg.name}`;
|
|
44
40
|
}
|
|
@@ -46,53 +42,72 @@ export class EzRadio extends EzBaseComponent {
|
|
|
46
42
|
const onChange = this._createOnChangeHandler();
|
|
47
43
|
const groupName = cfg.name || `radio-${Date.now()}`;
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
options.forEach((option) => {
|
|
45
|
+
const radioItems = options.map((option) => {
|
|
52
46
|
const optValue = typeof option === 'object' ? option.value : option;
|
|
53
47
|
const optLabel = typeof option === 'object' ? option.label : String(option);
|
|
54
48
|
const optDisabled = typeof option === 'object' ? option.disabled : false;
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
'radio',
|
|
59
|
-
|
|
60
|
-
|
|
50
|
+
return {
|
|
51
|
+
eztype: 'label',
|
|
52
|
+
cls: cls('radio', (cfg.disabled || optDisabled) && 'disabled'),
|
|
53
|
+
items: [
|
|
54
|
+
{
|
|
55
|
+
eztype: 'input',
|
|
56
|
+
type: 'radio',
|
|
57
|
+
cls: cls('input'),
|
|
58
|
+
name: groupName,
|
|
59
|
+
value: String(optValue),
|
|
60
|
+
checked: cfg.value === optValue,
|
|
61
|
+
disabled: cfg.disabled || optDisabled
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
eztype: 'span',
|
|
65
|
+
cls: cls('circle'),
|
|
66
|
+
items: [{ eztype: 'span', cls: cls('dot') }]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
eztype: 'span',
|
|
70
|
+
cls: cls('label'),
|
|
71
|
+
text: optLabel
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
};
|
|
75
|
+
});
|
|
61
76
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
input.checked = cfg.value === optValue;
|
|
77
|
+
radioItems.push({
|
|
78
|
+
eztype: 'div',
|
|
79
|
+
cls: cls('fieldError'),
|
|
80
|
+
items: []
|
|
81
|
+
});
|
|
68
82
|
|
|
69
|
-
|
|
83
|
+
const wrapper = await ez._createElement({
|
|
84
|
+
eztype: 'div',
|
|
85
|
+
cls: cls(
|
|
86
|
+
'radioGroup',
|
|
87
|
+
cfg.size,
|
|
88
|
+
cfg.disabled && 'disabled',
|
|
89
|
+
cfg.layout === 'horizontal' && 'horizontal'
|
|
90
|
+
),
|
|
91
|
+
items: radioItems
|
|
92
|
+
}) as HTMLDivElement;
|
|
70
93
|
|
|
71
|
-
|
|
72
|
-
|
|
94
|
+
this._wrapper = wrapper;
|
|
95
|
+
this._inputs = Array.from(wrapper.querySelectorAll('input[type="radio"]'));
|
|
96
|
+
this._error = wrapper.querySelector(`.${cls('fieldError')}`);
|
|
73
97
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
98
|
+
// Setup change handlers
|
|
99
|
+
this._inputs.forEach((input, index) => {
|
|
100
|
+
const option = options[index];
|
|
101
|
+
const optValue = typeof option === 'object' ? option.value : option;
|
|
77
102
|
|
|
78
103
|
input.addEventListener('change', e => {
|
|
79
104
|
if ((e.target as HTMLInputElement).checked && onChange) {
|
|
80
105
|
onChange(optValue);
|
|
81
106
|
}
|
|
82
107
|
});
|
|
83
|
-
|
|
84
|
-
label.appendChild(input);
|
|
85
|
-
label.appendChild(circle);
|
|
86
|
-
|
|
87
|
-
const text = document.createElement('span');
|
|
88
|
-
text.className = cls('label');
|
|
89
|
-
text.textContent = optLabel;
|
|
90
|
-
label.appendChild(text);
|
|
91
|
-
|
|
92
|
-
wrapper.appendChild(label);
|
|
93
|
-
this._inputs.push(input);
|
|
94
108
|
});
|
|
95
109
|
|
|
110
|
+
// Apply initial binding value
|
|
96
111
|
if (this.config.bind) {
|
|
97
112
|
const binding = this._resolveBinding();
|
|
98
113
|
if (binding) {
|
|
@@ -108,13 +123,6 @@ export class EzRadio extends EzBaseComponent {
|
|
|
108
123
|
}
|
|
109
124
|
}
|
|
110
125
|
|
|
111
|
-
const error = document.createElement('div');
|
|
112
|
-
error.className = cls('fieldError');
|
|
113
|
-
wrapper.appendChild(error);
|
|
114
|
-
|
|
115
|
-
this._wrapper = wrapper;
|
|
116
|
-
this._error = error;
|
|
117
|
-
|
|
118
126
|
return wrapper;
|
|
119
127
|
}
|
|
120
128
|
|