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
@@ -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 SelectOption {
8
12
  value: string | number;
9
13
  label: string;
@@ -40,71 +44,71 @@ export class EzSelect extends EzBaseComponent {
40
44
  this.onSelect = config.onSelect || null;
41
45
  }
42
46
 
43
- render(): HTMLDivElement {
44
- this.el = document.createElement('div');
45
- this.el.className = cls('select');
46
-
47
+ async render(): Promise<HTMLDivElement> {
48
+ const classes = [cls('select')];
47
49
  if (this.config.cls) {
48
50
  const clsValue = this.config.cls;
49
51
  if (Array.isArray(clsValue)) {
50
- clsValue.forEach(c => c && this.el.classList.add(c));
52
+ classes.push(...clsValue.filter(Boolean) as string[]);
51
53
  } else if (typeof clsValue === 'string') {
52
- this.el.classList.add(clsValue);
54
+ classes.push(clsValue);
53
55
  }
54
56
  }
55
57
 
56
- this._trigger = document.createElement('button');
57
- this._trigger.type = 'button';
58
- this._trigger.className = cls('selectTrigger');
59
-
60
- this._triggerText = document.createElement('span');
61
- this._triggerText.className = cls('selectTriggerText');
62
- this._trigger.appendChild(this._triggerText);
63
-
64
- const arrow = document.createElement('span');
65
- arrow.className = cls('selectArrow');
66
- arrow.innerHTML = '&#9662;';
67
- this._trigger.appendChild(arrow);
68
-
69
- this.el.appendChild(this._trigger);
70
-
71
- this._dropdown = document.createElement('div');
72
- this._dropdown.className = cls('selectDropdown');
58
+ this.el = await ez._createElement({
59
+ eztype: 'div',
60
+ cls: classes.join(' '),
61
+ items: [
62
+ {
63
+ eztype: 'button',
64
+ type: 'button',
65
+ cls: cls('selectTrigger'),
66
+ items: [
67
+ { eztype: 'span', cls: cls('selectTriggerText') },
68
+ { eztype: 'span', cls: cls('selectArrow'), html: '&#9662;' }
69
+ ]
70
+ },
71
+ {
72
+ eztype: 'div',
73
+ cls: cls('selectDropdown')
74
+ }
75
+ ]
76
+ }) as HTMLDivElement;
77
+
78
+ this._trigger = this.el.querySelector(`.${cls('selectTrigger')}`);
79
+ this._triggerText = this.el.querySelector(`.${cls('selectTriggerText')}`);
80
+ this._dropdown = this.el.querySelector(`.${cls('selectDropdown')}`);
73
81
 
74
82
  this._buildOptions();
75
- this.el.appendChild(this._dropdown);
76
-
77
83
  this._updateDisplay();
78
84
  this._bindEvents();
79
85
 
80
86
  return this.el;
81
87
  }
82
88
 
83
- private _buildOptions(): void {
89
+ private async _buildOptions(): Promise<void> {
84
90
  if (!this._dropdown) return;
85
91
  this._dropdown.innerHTML = '';
86
92
 
87
- this.options.forEach(opt => {
88
- const item = document.createElement('div');
89
- item.className = cls('selectOption');
90
-
93
+ for (const opt of this.options) {
91
94
  const value = typeof opt === 'object' ? opt.value : opt;
92
95
  const label = typeof opt === 'object' ? opt.label : String(opt);
93
96
 
94
- item.dataset.value = String(value);
95
- item.textContent = label;
96
-
97
- if (value === this.value) {
98
- item.classList.add(cls('isSelected'));
99
- }
100
-
101
- item.addEventListener('click', (e) => {
102
- e.stopPropagation();
103
- this._selectOption(value);
97
+ const item = await ez._createElement({
98
+ eztype: 'div',
99
+ cls: value === this.value
100
+ ? [cls('selectOption'), cls('isSelected')].join(' ')
101
+ : cls('selectOption'),
102
+ 'data-value': String(value),
103
+ text: label,
104
+ onClick: (e: MouseEvent) => {
105
+ e.stopPropagation();
106
+ this._selectOption(value);
107
+ }
104
108
  });
105
109
 
106
- this._dropdown!.appendChild(item);
107
- });
110
+ this._dropdown.appendChild(item);
111
+ }
108
112
  }
109
113
 
110
114
  private _updateDisplay(): void {
@@ -150,7 +154,7 @@ export class EzSelect extends EzBaseComponent {
150
154
  }
151
155
  };
152
156
 
153
- document.addEventListener('click', this._outsideClickHandler);
157
+ document.addEventListener('click', this._outsideClickHandler, true);
154
158
 
155
159
  this._trigger.addEventListener('keydown', (e) => {
156
160
  if (e.key === 'Enter' || e.key === ' ') {
@@ -232,7 +236,7 @@ export class EzSelect extends EzBaseComponent {
232
236
 
233
237
  destroy(): void {
234
238
  if (this._outsideClickHandler) {
235
- document.removeEventListener('click', this._outsideClickHandler);
239
+ document.removeEventListener('click', this._outsideClickHandler, true);
236
240
  }
237
241
  }
238
242
  }
@@ -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 EzSkeletonConfig extends EzBaseComponentConfig {
8
12
  variant?: 'text' | 'circle' | 'rectangular';
9
13
  animation?: 'pulse' | 'wave' | 'none';
@@ -18,53 +22,54 @@ export interface EzSkeletonConfig extends EzBaseComponentConfig {
18
22
  export class EzSkeleton extends EzBaseComponent {
19
23
  declare config: EzSkeletonConfig;
20
24
 
21
- render(): HTMLElement {
25
+ async render(): Promise<HTMLElement> {
22
26
  const cfg = this.config;
23
27
 
24
28
  if (cfg.count && cfg.count > 1) {
25
- const wrapper = document.createElement('div');
26
- wrapper.className = cls('skeletonGroup');
27
-
28
- if (cfg.gap) {
29
- wrapper.style.gap = typeof cfg.gap === 'number' ? `${cfg.gap}px` : cfg.gap;
30
- }
31
-
29
+ const skeletonItems: unknown[] = [];
32
30
  for (let i = 0; i < cfg.count; i++) {
33
- const skeleton = this._createSkeleton(cfg);
34
- wrapper.appendChild(skeleton);
31
+ skeletonItems.push(this._buildSkeletonConfig(cfg));
35
32
  }
36
33
 
34
+ const wrapper = await ez._createElement({
35
+ eztype: 'div',
36
+ cls: cls('skeletonGroup'),
37
+ style: cfg.gap ? { gap: typeof cfg.gap === 'number' ? `${cfg.gap}px` : cfg.gap } : undefined,
38
+ items: skeletonItems
39
+ }) as HTMLElement;
40
+
37
41
  return wrapper;
38
42
  }
39
43
 
40
- return this._createSkeleton(cfg);
44
+ return await ez._createElement(this._buildSkeletonConfig(cfg)) as HTMLElement;
41
45
  }
42
46
 
43
- private _createSkeleton(cfg: EzSkeletonConfig): HTMLElement {
44
- const skeleton = document.createElement('div');
45
- skeleton.className = cls(
46
- 'skeleton',
47
- cfg.variant || 'text',
48
- cfg.animation || 'pulse',
49
- cfg.rounded && 'rounded'
50
- );
47
+ private _buildSkeletonConfig(cfg: EzSkeletonConfig): unknown {
48
+ const style: Record<string, string> = {};
51
49
 
52
50
  if (cfg.width) {
53
- skeleton.style.width = typeof cfg.width === 'number' ? `${cfg.width}px` : cfg.width;
51
+ style.width = typeof cfg.width === 'number' ? `${cfg.width}px` : cfg.width;
54
52
  }
55
53
 
56
54
  if (cfg.height) {
57
- skeleton.style.height = typeof cfg.height === 'number' ? `${cfg.height}px` : cfg.height;
55
+ style.height = typeof cfg.height === 'number' ? `${cfg.height}px` : cfg.height;
58
56
  }
59
57
 
60
58
  if (cfg.variant === 'circle') {
61
59
  const size = cfg.size || 40;
62
- skeleton.style.width = `${size}px`;
63
- skeleton.style.height = `${size}px`;
60
+ style.width = `${size}px`;
61
+ style.height = `${size}px`;
64
62
  }
65
63
 
66
- this.applyStyles(skeleton);
67
-
68
- return skeleton;
64
+ return {
65
+ eztype: 'div',
66
+ cls: cls(
67
+ 'skeleton',
68
+ cfg.variant || 'text',
69
+ cfg.animation || 'pulse',
70
+ cfg.rounded && 'rounded'
71
+ ),
72
+ style: Object.keys(style).length > 0 ? style : undefined
73
+ };
69
74
  }
70
75
  }
@@ -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 EzSwitchConfig extends EzBaseComponentConfig {
8
12
  size?: 'sm' | 'lg';
9
13
  disabled?: boolean;
@@ -21,63 +25,67 @@ export class EzSwitch extends EzBaseComponent {
21
25
  private _input: HTMLInputElement | null = null;
22
26
  private _error: HTMLDivElement | null = null;
23
27
 
24
- render(): HTMLLabelElement {
28
+ async render(): Promise<HTMLLabelElement> {
25
29
  const cfg = this.config;
26
30
 
27
- const wrapper = document.createElement('label');
28
- wrapper.className = cls(
29
- 'switch',
30
- cfg.size,
31
- cfg.disabled && 'disabled',
32
- cfg.labelPosition === 'left' && 'labelLeft'
33
- );
34
-
35
- const input = document.createElement('input');
36
- input.type = 'checkbox';
37
- input.className = cls('input');
38
- input.checked = !!cfg.value;
39
-
40
- if (cfg.disabled) input.disabled = true;
41
- if (cfg.name) input.name = cfg.name;
42
-
43
31
  if (cfg.name && cfg.formData) {
44
32
  this.config.bind = `${cfg.formData}.${cfg.name}`;
45
33
  }
46
34
 
47
- const track = document.createElement('span');
48
- track.className = cls('track');
49
-
50
- const thumb = document.createElement('span');
51
- thumb.className = cls('thumb');
52
- track.appendChild(thumb);
53
-
54
- this.applyCommonBindings(input);
55
-
56
- const onChange = this._createOnChangeHandler();
57
-
58
- input.addEventListener('change', e => {
59
- if (onChange) {
60
- onChange((e.target as HTMLInputElement).checked);
35
+ const items: unknown[] = [
36
+ {
37
+ eztype: 'input',
38
+ type: 'checkbox',
39
+ cls: cls('input'),
40
+ checked: !!cfg.value,
41
+ disabled: cfg.disabled,
42
+ name: cfg.name
43
+ },
44
+ {
45
+ eztype: 'span',
46
+ cls: cls('track'),
47
+ items: [{ eztype: 'span', cls: cls('thumb') }]
61
48
  }
62
- });
63
-
64
- wrapper.appendChild(input);
65
- wrapper.appendChild(track);
49
+ ];
66
50
 
67
51
  if (cfg.label) {
68
- const label = document.createElement('span');
69
- label.className = cls('label');
70
- label.textContent = cfg.label;
71
- wrapper.appendChild(label);
52
+ items.push({
53
+ eztype: 'span',
54
+ cls: cls('label'),
55
+ text: cfg.label
56
+ });
72
57
  }
73
58
 
74
- const error = document.createElement('div');
75
- error.className = cls('fieldError');
76
- wrapper.appendChild(error);
59
+ items.push({
60
+ eztype: 'div',
61
+ cls: cls('fieldError')
62
+ });
63
+
64
+ const wrapper = await ez._createElement({
65
+ eztype: 'label',
66
+ cls: cls(
67
+ 'switch',
68
+ cfg.size,
69
+ cfg.disabled && 'disabled',
70
+ cfg.labelPosition === 'left' && 'labelLeft'
71
+ ),
72
+ items
73
+ }) as HTMLLabelElement;
77
74
 
78
75
  this._wrapper = wrapper;
79
- this._input = input;
80
- this._error = error;
76
+ this._input = wrapper.querySelector('input');
77
+ this._error = wrapper.querySelector(`.${cls('fieldError')}`);
78
+
79
+ if (this._input) {
80
+ this.applyCommonBindings(this._input);
81
+
82
+ const onChange = this._createOnChangeHandler();
83
+ this._input.addEventListener('change', e => {
84
+ if (onChange) {
85
+ onChange((e.target as HTMLInputElement).checked);
86
+ }
87
+ });
88
+ }
81
89
 
82
90
  return wrapper;
83
91
  }
@@ -4,6 +4,7 @@ import { EzBaseComponent, EzBaseComponentConfig } from '../EzBaseComponent.js';
4
4
 
5
5
  declare const ez: {
6
6
  _createElement(config: unknown, controller?: string | null, parent?: unknown): Promise<HTMLElement>;
7
+ _createChildElements(items: unknown[], controller: string | null, parent: unknown): Promise<Node[]>;
7
8
  };
8
9
 
9
10
  const cls = cx(styles);
@@ -134,19 +135,19 @@ export class EzTabPanel extends EzBaseComponent {
134
135
 
135
136
  async render(): Promise<HTMLElement> {
136
137
  const cfg = this.config;
137
- const el = document.createElement('div');
138
138
 
139
- el.classList.add(cls('tabPanel'));
139
+ const el = await ez._createElement({
140
+ eztype: 'div',
141
+ cls: cls('tabPanel', cfg.variant),
142
+ style: cfg.style,
143
+ items: [
144
+ { eztype: 'div', cls: cls('tabHeader') },
145
+ { eztype: 'div', cls: cls('tabContent') }
146
+ ]
147
+ }, cfg.controller || null, this) as HTMLElement;
140
148
 
141
- if (cfg.variant) {
142
- el.classList.add(cls(cfg.variant));
143
- }
144
-
145
- this._headerEl = document.createElement('div');
146
- this._headerEl.classList.add(cls('tabHeader'));
147
-
148
- this._contentEl = document.createElement('div');
149
- this._contentEl.classList.add(cls('tabContent'));
149
+ this._headerEl = el.querySelector(`.${cls('tabHeader')}`);
150
+ this._contentEl = el.querySelector(`.${cls('tabContent')}`);
150
151
 
151
152
  const tabs = cfg.tabs || [];
152
153
 
@@ -160,10 +161,6 @@ export class EzTabPanel extends EzBaseComponent {
160
161
  await this._createTab(tab, i);
161
162
  }
162
163
 
163
- el.appendChild(this._headerEl);
164
- el.appendChild(this._contentEl);
165
-
166
- this.applyStyles(el);
167
164
  this.el = el;
168
165
 
169
166
  if (activeTabId != null) {
@@ -178,53 +175,60 @@ export class EzTabPanel extends EzBaseComponent {
178
175
  return el;
179
176
  }
180
177
 
181
- private async _createTab(tab: TabConfig, index: number): Promise<string | number> {
182
- const tabId = tab.id ?? index;
183
-
184
- this._tabConfigs.set(tabId, tab);
185
-
186
- const tabBtn = document.createElement('button');
187
- tabBtn.classList.add(cls('tab'));
188
- tabBtn.setAttribute('data-tab-id', String(tabId));
178
+ private _buildTabButtonConfig(tab: TabConfig, tabId: string | number): unknown {
179
+ const buttonItems: unknown[] = [];
189
180
 
190
181
  if (tab.icon) {
191
- const icon = document.createElement('i');
192
- icon.className = tab.icon;
193
- tabBtn.appendChild(icon);
182
+ buttonItems.push({ eztype: 'i', cls: tab.icon });
194
183
  }
195
184
 
196
185
  if (tab.title) {
197
- const span = document.createElement('span');
198
- span.textContent = tab.title;
199
- tabBtn.appendChild(span);
186
+ buttonItems.push({ eztype: 'span', text: tab.title });
200
187
  }
201
188
 
202
189
  if (tab.closable) {
203
- const closeBtn = document.createElement('span');
204
- closeBtn.classList.add(cls('tabClose'));
205
- closeBtn.innerHTML = '<i class="fa-solid fa-xmark"></i>';
206
- closeBtn.addEventListener('click', (e) => {
207
- e.stopPropagation();
208
- this.removeTab(tabId);
190
+ buttonItems.push({
191
+ eztype: 'span',
192
+ cls: cls('tabClose'),
193
+ items: [{ eztype: 'i', cls: 'fa-solid fa-xmark' }],
194
+ onClick: (e: MouseEvent) => {
195
+ e.stopPropagation();
196
+ this.removeTab(tabId);
197
+ }
209
198
  });
210
- tabBtn.appendChild(closeBtn);
211
199
  }
212
200
 
213
- tabBtn.addEventListener('click', () => this.setActiveTab(tabId));
214
- this._headerEl!.appendChild(tabBtn);
215
- this._tabElements.set(tabId, tabBtn);
201
+ return {
202
+ eztype: 'button',
203
+ type: 'button',
204
+ cls: cls('tab'),
205
+ 'data-tab-id': String(tabId),
206
+ items: buttonItems,
207
+ onClick: () => this.setActiveTab(tabId)
208
+ };
209
+ }
216
210
 
217
- const pane = document.createElement('div');
218
- pane.classList.add(cls('tabPane'));
219
- pane.setAttribute('data-tab-id', String(tabId));
211
+ private async _buildTabPaneConfig(tab: TabConfig, tabId: string | number): Promise<unknown> {
212
+ return {
213
+ eztype: 'div',
214
+ cls: cls('tabPane'),
215
+ 'data-tab-id': String(tabId),
216
+ items: tab.items || []
217
+ };
218
+ }
220
219
 
221
- if (Array.isArray(tab.items)) {
222
- for (const item of tab.items) {
223
- const child = await ez._createElement(item, this.config.controller || null, this);
224
- pane.appendChild(child);
225
- }
226
- }
220
+ private async _createTab(tab: TabConfig, index: number): Promise<string | number> {
221
+ const tabId = tab.id ?? index;
222
+
223
+ this._tabConfigs.set(tabId, tab);
224
+
225
+ const tabBtnConfig = this._buildTabButtonConfig(tab, tabId);
226
+ const tabBtn = await ez._createElement(tabBtnConfig, null, this) as HTMLElement;
227
+ this._headerEl!.appendChild(tabBtn);
228
+ this._tabElements.set(tabId, tabBtn);
227
229
 
230
+ const paneConfig = await this._buildTabPaneConfig(tab, tabId);
231
+ const pane = await ez._createElement(paneConfig, this.config.controller || null, this) as HTMLElement;
228
232
  this._contentEl!.appendChild(pane);
229
233
  this._contentElements.set(tabId, pane);
230
234
 
@@ -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 EzTextareaConfig extends EzBaseComponentConfig {
8
12
  size?: 'sm' | 'lg';
9
13
  disabled?: boolean;
@@ -29,77 +33,88 @@ export class EzTextarea extends EzBaseComponent {
29
33
  private _error: HTMLDivElement | null = null;
30
34
  private _counter: HTMLDivElement | null = null;
31
35
 
32
- render(): HTMLDivElement {
36
+ async render(): Promise<HTMLDivElement> {
33
37
  const cfg = this.config;
34
38
 
35
- const wrapper = document.createElement('div');
36
- wrapper.className = cls(
37
- 'textareaGroup',
38
- cfg.size,
39
- cfg.disabled && 'disabled',
40
- cfg.resize === false && 'noResize',
41
- cfg.rows && 'hasRows'
42
- );
43
-
44
- const textarea = document.createElement('textarea');
45
- textarea.className = cls('textarea');
46
- textarea.placeholder = cfg.placeholder || '';
47
- if (cfg.value !== undefined) textarea.value = cfg.value;
48
-
49
- if (cfg.rows) textarea.rows = cfg.rows;
50
- const paddingY = cfg.size === 'sm' ? 16 : cfg.size === 'lg' ? 24 : 20;
51
- if (cfg.minRows) textarea.style.minHeight = `calc(${cfg.minRows * 1.5}em + ${paddingY}px)`;
52
- if (cfg.maxRows) textarea.style.maxHeight = `calc(${cfg.maxRows * 1.5}em + ${paddingY}px)`;
53
- if (cfg.maxLength) textarea.maxLength = cfg.maxLength;
54
- if (cfg.disabled) textarea.disabled = true;
55
- if (cfg.readonly) textarea.readOnly = true;
56
-
57
39
  if (cfg.name && cfg.formData) {
58
40
  this.config.bind = `${cfg.formData}.${cfg.name}`;
59
41
  }
60
42
 
61
- if (cfg.name) {
62
- textarea.setAttribute('data-ez-field', cfg.name);
43
+ const paddingY = cfg.size === 'sm' ? 16 : cfg.size === 'lg' ? 24 : 20;
44
+ const textareaStyle: Record<string, string> = {};
45
+ if (cfg.minRows) textareaStyle.minHeight = `calc(${cfg.minRows * 1.5}em + ${paddingY}px)`;
46
+ if (cfg.maxRows) textareaStyle.maxHeight = `calc(${cfg.maxRows * 1.5}em + ${paddingY}px)`;
47
+
48
+ const items: unknown[] = [
49
+ {
50
+ eztype: 'textarea',
51
+ cls: cls('textarea'),
52
+ placeholder: cfg.placeholder || '',
53
+ value: cfg.value,
54
+ rows: cfg.rows,
55
+ maxLength: cfg.maxLength,
56
+ disabled: cfg.disabled,
57
+ readOnly: cfg.readonly,
58
+ 'data-ez-field': cfg.name,
59
+ style: Object.keys(textareaStyle).length > 0 ? textareaStyle : undefined
60
+ }
61
+ ];
62
+
63
+ if (cfg.showCount) {
64
+ items.push({
65
+ eztype: 'div',
66
+ cls: cls('counter')
67
+ });
63
68
  }
64
69
 
65
- this.applyCommonBindings(textarea);
70
+ items.push({
71
+ eztype: 'div',
72
+ cls: cls('fieldError')
73
+ });
66
74
 
67
- const onChange = this._createOnChangeHandler();
75
+ const wrapper = await ez._createElement({
76
+ eztype: 'div',
77
+ cls: cls(
78
+ 'textareaGroup',
79
+ cfg.size,
80
+ cfg.disabled && 'disabled',
81
+ cfg.resize === false && 'noResize',
82
+ cfg.rows && 'hasRows'
83
+ ),
84
+ items
85
+ }) as HTMLDivElement;
68
86
 
69
- textarea.addEventListener('input', e => {
70
- if (onChange) {
71
- onChange((e.target as HTMLTextAreaElement).value);
72
- }
87
+ this._wrapper = wrapper;
88
+ this._textarea = wrapper.querySelector('textarea');
89
+ this._error = wrapper.querySelector(`.${cls('fieldError')}`);
90
+ this._counter = wrapper.querySelector(`.${cls('counter')}`);
73
91
 
74
- if (cfg.autoResize) {
75
- this._autoResize(textarea);
76
- }
92
+ if (this._textarea) {
93
+ this.applyCommonBindings(this._textarea);
77
94
 
78
- if (cfg.showCount && this._counter) {
79
- this._updateCounter((e.target as HTMLTextAreaElement).value.length);
80
- }
81
- });
95
+ const onChange = this._createOnChangeHandler();
82
96
 
83
- wrapper.appendChild(textarea);
97
+ this._textarea.addEventListener('input', e => {
98
+ if (onChange) {
99
+ onChange((e.target as HTMLTextAreaElement).value);
100
+ }
84
101
 
85
- if (cfg.showCount) {
86
- const counter = document.createElement('div');
87
- counter.className = cls('counter');
88
- this._counter = counter;
89
- this._updateCounter(0);
90
- wrapper.appendChild(counter);
91
- }
102
+ if (cfg.autoResize) {
103
+ this._autoResize(this._textarea!);
104
+ }
92
105
 
93
- const error = document.createElement('div');
94
- error.className = cls('fieldError');
95
- wrapper.appendChild(error);
106
+ if (cfg.showCount && this._counter) {
107
+ this._updateCounter((e.target as HTMLTextAreaElement).value.length);
108
+ }
109
+ });
96
110
 
97
- this._wrapper = wrapper;
98
- this._textarea = textarea;
99
- this._error = error;
111
+ if (cfg.showCount) {
112
+ this._updateCounter(0);
113
+ }
100
114
 
101
- if (cfg.autoResize) {
102
- requestAnimationFrame(() => this._autoResize(textarea));
115
+ if (cfg.autoResize) {
116
+ requestAnimationFrame(() => this._autoResize(this._textarea!));
117
+ }
103
118
  }
104
119
 
105
120
  return wrapper;