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.
Files changed (43) hide show
  1. package/components/EzBaseComponent.ts +100 -5
  2. package/components/EzComponent.ts +3 -3
  3. package/components/EzLabel.ts +12 -3
  4. package/components/avatar/EzAvatar.ts +84 -54
  5. package/components/badge/EzBadge.ts +43 -24
  6. package/components/button/EzButton.ts +5 -3
  7. package/components/button/EzButtonGroup.ts +7 -10
  8. package/components/card/EzCard.ts +2 -1
  9. package/components/chart/EzChart.ts +20 -15
  10. package/components/checkbox/EzCheckbox.ts +47 -43
  11. package/components/dataview/EzDataView.ts +14 -29
  12. package/components/dataview/modes/EzDataViewCards.ts +51 -41
  13. package/components/dataview/modes/EzDataViewGrid.ts +5 -2
  14. package/components/datepicker/EzDatePicker.ts +2 -2
  15. package/components/dialog/EzDialog.ts +84 -67
  16. package/components/dropdown/EzDropdown.ts +72 -58
  17. package/components/form/EzForm.ts +45 -37
  18. package/components/kanban/EzKanban.module.scss +221 -0
  19. package/components/kanban/EzKanban.ts +222 -0
  20. package/components/kanban/EzKanbanTypes.ts +166 -0
  21. package/components/kanban/board/EzKanbanBoard.ts +117 -0
  22. package/components/kanban/card/EzKanbanCard.module.scss +173 -0
  23. package/components/kanban/card/EzKanbanCard.ts +275 -0
  24. package/components/kanban/card/EzKanbanCardEditor.ts +209 -0
  25. package/components/kanban/column/EzKanbanColumn.ts +253 -0
  26. package/components/kanban/state/EzKanbanController.ts +373 -0
  27. package/components/kanban/state/EzKanbanDragDrop.ts +226 -0
  28. package/components/panel/EzPanel.ts +59 -68
  29. package/components/picker/EzPicker.module.scss +14 -0
  30. package/components/picker/EzPicker.ts +118 -0
  31. package/components/radio/EzRadio.ts +55 -47
  32. package/components/select/EzSelect.ts +48 -44
  33. package/components/skeleton/EzSkeleton.ts +31 -26
  34. package/components/switch/EzSwitch.ts +52 -44
  35. package/components/tabs/EzTabPanel.ts +52 -48
  36. package/components/textarea/EzTextarea.ts +69 -54
  37. package/components/timepicker/EzTimePicker.ts +2 -2
  38. package/components/tooltip/EzTooltip.ts +20 -33
  39. package/core/ez.ts +7 -0
  40. package/core/loader.ts +2 -0
  41. package/core/renderer.ts +80 -4
  42. package/core/styleShortcuts.ts +418 -0
  43. 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
- _createChildElements(items: unknown[], controller: string | null, state: unknown, css?: string | null): Promise<Node[]>;
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 panel = document.createElement('div');
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 header = document.createElement('div');
47
- header.className = cls('header');
40
+ const headerItems: unknown[] = [];
48
41
 
49
42
  if (cfg.icon) {
50
- const icon = document.createElement('i');
51
- icon.className = cls('icon', cfg.icon);
52
- header.appendChild(icon);
43
+ headerItems.push({
44
+ eztype: 'i',
45
+ cls: [cls('icon'), cfg.icon].join(' ')
46
+ });
53
47
  }
54
48
 
55
49
  if (cfg.title) {
56
- const title = document.createElement('span');
57
- title.className = cls('title');
58
- title.textContent = cfg.title;
59
- header.appendChild(title);
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
- const badge = document.createElement('span');
64
- badge.className = cls('badge');
65
- badge.textContent = String(cfg.badge);
66
- header.appendChild(badge);
58
+ headerItems.push({
59
+ eztype: 'span',
60
+ cls: cls('badge'),
61
+ text: String(cfg.badge)
62
+ });
67
63
  }
68
64
 
69
- const spacer = document.createElement('span');
70
- spacer.className = cls('spacer');
71
- header.appendChild(spacer);
65
+ headerItems.push({
66
+ eztype: 'span',
67
+ cls: cls('spacer')
68
+ });
72
69
 
73
70
  if (cfg.tools && cfg.tools.length > 0) {
74
- const tools = document.createElement('div');
75
- tools.className = cls('tools');
76
-
77
- const toolEls = await ez._createChildElements(
78
- cfg.tools,
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
- const toggle = document.createElement('button');
93
- toggle.type = 'button';
94
- toggle.className = cls('toggle');
95
- toggle.innerHTML = '<i class="fa-solid fa-chevron-down"></i>';
96
- toggle.addEventListener('click', () => this.toggleCollapse());
97
- header.appendChild(toggle);
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
- panel.appendChild(header);
88
+ panelItems.push({
89
+ eztype: 'div',
90
+ cls: cls('header'),
91
+ items: headerItems
92
+ });
101
93
  }
102
94
 
103
- const body = document.createElement('div');
104
- body.className = cls('body');
105
-
106
- if (cfg.items && cfg.items.length > 0) {
107
- const children = await ez._createChildElements(
108
- cfg.items,
109
- cfg.controller || null,
110
- null,
111
- (cfg.css as string) || null
112
- );
113
- for (const child of children) {
114
- if (child instanceof Node) {
115
- body.appendChild(child);
116
- }
117
- }
118
- }
119
-
120
- panel.appendChild(body);
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,14 @@
1
+ .picker {
2
+ display: flex;
3
+ flex-wrap: wrap;
4
+ gap: 8px;
5
+
6
+ &.vertical {
7
+ flex-direction: column;
8
+ }
9
+ }
10
+
11
+ .option {
12
+ cursor: pointer;
13
+ transition: all 0.15s ease;
14
+ }
@@ -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
- this._inputs = [];
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
- const label = document.createElement('label');
57
- label.className = cls(
58
- 'radio',
59
- (cfg.disabled || optDisabled) && 'disabled'
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
- const input = document.createElement('input');
63
- input.type = 'radio';
64
- input.className = cls('input');
65
- input.name = groupName;
66
- input.value = String(optValue);
67
- input.checked = cfg.value === optValue;
77
+ radioItems.push({
78
+ eztype: 'div',
79
+ cls: cls('fieldError'),
80
+ items: []
81
+ });
68
82
 
69
- if (cfg.disabled || optDisabled) input.disabled = true;
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
- const circle = document.createElement('span');
72
- circle.className = cls('circle');
94
+ this._wrapper = wrapper;
95
+ this._inputs = Array.from(wrapper.querySelectorAll('input[type="radio"]'));
96
+ this._error = wrapper.querySelector(`.${cls('fieldError')}`);
73
97
 
74
- const dot = document.createElement('span');
75
- dot.className = cls('dot');
76
- circle.appendChild(dot);
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