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
@@ -40,26 +40,36 @@ export class EzDropdown extends EzBaseComponent {
40
40
  async render(): Promise<HTMLDivElement> {
41
41
  const cfg = this.config;
42
42
 
43
- const wrapper = document.createElement('div');
44
- wrapper.className = cls('dropdown');
43
+ const wrapperItems: unknown[] = [];
45
44
 
46
45
  if (cfg.trigger) {
47
- const triggerEl = await ez._createElement(cfg.trigger);
48
- triggerEl.classList.add(cls('trigger'));
49
- triggerEl.addEventListener('click', (e) => {
50
- e.stopPropagation();
51
- this._toggle();
46
+ wrapperItems.push({
47
+ ...cfg.trigger as object,
48
+ cls: cls('trigger'),
49
+ onClick: (e: MouseEvent) => {
50
+ e.stopPropagation();
51
+ this._toggle();
52
+ }
52
53
  });
53
- wrapper.appendChild(triggerEl);
54
- this._triggerEl = triggerEl;
55
54
  }
56
55
 
57
- const menu = document.createElement('div');
58
- menu.className = cls('menu');
59
- menu.style.opacity = '0';
60
- menu.style.visibility = 'hidden';
56
+ const wrapper = await ez._createElement({
57
+ eztype: 'div',
58
+ cls: cls('dropdown'),
59
+ items: wrapperItems
60
+ }) as HTMLDivElement;
61
+
62
+ this._triggerEl = wrapper.querySelector(`.${cls('trigger')}`);
63
+
64
+ // Build menu items
65
+ const menuItems = await this._buildMenuItems(cfg.items || []);
61
66
 
62
- this._buildItems(menu, cfg.items || []);
67
+ const menu = await ez._createElement({
68
+ eztype: 'div',
69
+ cls: cls('menu'),
70
+ style: { opacity: '0', visibility: 'hidden' },
71
+ items: menuItems
72
+ }) as HTMLDivElement;
63
73
 
64
74
  document.body.appendChild(menu);
65
75
 
@@ -73,7 +83,7 @@ export class EzDropdown extends EzBaseComponent {
73
83
  this._close();
74
84
  }
75
85
  };
76
- document.addEventListener('click', this._onOutsideClick);
86
+ document.addEventListener('click', this._onOutsideClick, true);
77
87
 
78
88
  this._onEscape = (e: KeyboardEvent) => {
79
89
  if (e.key === 'Escape' && this._isOpen) {
@@ -85,65 +95,69 @@ export class EzDropdown extends EzBaseComponent {
85
95
  return wrapper;
86
96
  }
87
97
 
88
- private _buildItems(menu: HTMLDivElement, items: DropdownItem[]): void {
89
- items.forEach(item => {
98
+ private _buildMenuItems(items: DropdownItem[]): unknown[] {
99
+ const menuItems: unknown[] = [];
100
+
101
+ for (const item of items) {
90
102
  if (item.divider) {
91
- const divider = document.createElement('div');
92
- divider.className = cls('divider');
93
- menu.appendChild(divider);
94
- return;
103
+ menuItems.push({
104
+ eztype: 'div',
105
+ cls: cls('divider')
106
+ });
107
+ continue;
95
108
  }
96
109
 
97
110
  if (item.header) {
98
- const header = document.createElement('div');
99
- header.className = cls('header');
100
- header.textContent = item.header;
101
- menu.appendChild(header);
102
- return;
111
+ menuItems.push({
112
+ eztype: 'div',
113
+ cls: cls('header'),
114
+ text: item.header
115
+ });
116
+ continue;
103
117
  }
104
118
 
105
- const menuItem = document.createElement('button');
106
- menuItem.type = 'button';
107
- menuItem.className = cls(
108
- 'item',
109
- item.danger && 'danger',
110
- item.disabled && 'disabled'
111
- );
112
-
113
- if (item.disabled) {
114
- menuItem.disabled = true;
115
- }
119
+ const buttonItems: unknown[] = [];
116
120
 
117
121
  if (item.icon) {
118
- const icon = document.createElement('i');
119
- icon.className = cls('itemIcon', item.icon);
120
- menuItem.appendChild(icon);
122
+ buttonItems.push({
123
+ eztype: 'i',
124
+ cls: [cls('itemIcon'), item.icon].join(' ')
125
+ });
121
126
  }
122
127
 
123
- const text = document.createElement('span');
124
- text.className = cls('itemText');
125
- text.textContent = item.text || '';
126
- menuItem.appendChild(text);
128
+ buttonItems.push({
129
+ eztype: 'span',
130
+ cls: cls('itemText'),
131
+ text: item.text || ''
132
+ });
127
133
 
128
134
  if (item.shortcut) {
129
- const shortcut = document.createElement('span');
130
- shortcut.className = cls('shortcut');
131
- shortcut.textContent = item.shortcut;
132
- menuItem.appendChild(shortcut);
135
+ buttonItems.push({
136
+ eztype: 'span',
137
+ cls: cls('shortcut'),
138
+ text: item.shortcut
139
+ });
133
140
  }
134
141
 
135
- menuItem.addEventListener('click', (e) => {
136
- if (item.disabled) return;
137
- if (item.onClick) {
138
- item.onClick(e);
139
- }
140
- if (item.closeOnClick !== false) {
141
- this._close();
142
+ menuItems.push({
143
+ eztype: 'button',
144
+ type: 'button',
145
+ cls: cls('item', item.danger && 'danger', item.disabled && 'disabled'),
146
+ disabled: item.disabled,
147
+ items: buttonItems,
148
+ onClick: (e: MouseEvent) => {
149
+ if (item.disabled) return;
150
+ if (item.onClick) {
151
+ item.onClick(e);
152
+ }
153
+ if (item.closeOnClick !== false) {
154
+ this._close();
155
+ }
142
156
  }
143
157
  });
158
+ }
144
159
 
145
- menu.appendChild(menuItem);
146
- });
160
+ return menuItems;
147
161
  }
148
162
 
149
163
  private _toggle(): void {
@@ -222,7 +236,7 @@ export class EzDropdown extends EzBaseComponent {
222
236
 
223
237
  destroy(): void {
224
238
  if (this._onOutsideClick) {
225
- document.removeEventListener('click', this._onOutsideClick);
239
+ document.removeEventListener('click', this._onOutsideClick, true);
226
240
  }
227
241
  if (this._onEscape) {
228
242
  document.removeEventListener('keydown', this._onEscape);
@@ -67,34 +67,6 @@ export class EzForm extends EzBaseComponent {
67
67
  }
68
68
 
69
69
  async render(): Promise<HTMLFormElement> {
70
- const el = document.createElement('form');
71
- el.className = (this.config.cls as string) || '';
72
- el.style.display = 'flex';
73
- el.style.flexDirection = 'column';
74
- el.style.gap = this.config.gap ?? 'var(--ez-spacing-md, 12px)';
75
-
76
- this.applyStyles(el);
77
-
78
- el.addEventListener('submit', (e: Event) => {
79
- e.preventDefault();
80
- this._submitted = true;
81
-
82
- if (!this.validate()) {
83
- this._applyErrors();
84
- return;
85
- }
86
-
87
- this._clearErrors();
88
- this._handleSubmit();
89
- });
90
-
91
- el.addEventListener('input', () => {
92
- if (!this._submitted) return;
93
-
94
- this.validate();
95
- this._applyErrors();
96
- });
97
-
98
70
  const controllerName = this.config.controller;
99
71
 
100
72
  if (!this._initialFormDataCaptured) {
@@ -102,6 +74,8 @@ export class EzForm extends EzBaseComponent {
102
74
  this._initialFormDataCaptured = true;
103
75
  }
104
76
 
77
+ const formItems: unknown[] = [];
78
+
105
79
  if (Array.isArray(this.config.items)) {
106
80
  const items = this.config.items;
107
81
  const layout = this.config.layout;
@@ -117,9 +91,7 @@ export class EzForm extends EzBaseComponent {
117
91
 
118
92
  let itemIndex = 0;
119
93
  for (const rowCount of layout) {
120
- const row = document.createElement('div');
121
- row.style.display = 'flex';
122
- row.style.gap = this.config.gap ?? 'var(--ez-spacing-md, 12px)';
94
+ const rowItems: unknown[] = [];
123
95
 
124
96
  for (let i = 0; i < rowCount; i++) {
125
97
  const itemCfg = items[itemIndex++];
@@ -129,21 +101,57 @@ export class EzForm extends EzBaseComponent {
129
101
  finalCfg.flex = 1;
130
102
  }
131
103
 
132
- const child = await ez._createElement(finalCfg, controllerName, this);
133
- row.appendChild(child);
104
+ rowItems.push(finalCfg);
134
105
  }
135
106
 
136
- el.appendChild(row);
107
+ formItems.push({
108
+ eztype: 'div',
109
+ style: {
110
+ display: 'flex',
111
+ gap: this.config.gap ?? 'var(--ez-spacing-md, 12px)'
112
+ },
113
+ items: rowItems
114
+ });
137
115
  }
138
116
  } else {
139
117
  for (const itemCfg of items) {
140
- const finalCfg = this._prepareItemConfig(itemCfg);
141
- const child = await ez._createElement(finalCfg, controllerName, this);
142
- el.appendChild(child);
118
+ formItems.push(this._prepareItemConfig(itemCfg));
143
119
  }
144
120
  }
145
121
  }
146
122
 
123
+ const el = await ez._createElement({
124
+ eztype: 'form',
125
+ cls: this.config.cls,
126
+ style: {
127
+ display: 'flex',
128
+ flexDirection: 'column',
129
+ gap: this.config.gap ?? 'var(--ez-spacing-md, 12px)',
130
+ ...this.config.style as object
131
+ },
132
+ items: formItems
133
+ }, controllerName, this) as HTMLFormElement;
134
+
135
+ el.addEventListener('submit', (e: Event) => {
136
+ e.preventDefault();
137
+ this._submitted = true;
138
+
139
+ if (!this.validate()) {
140
+ this._applyErrors();
141
+ return;
142
+ }
143
+
144
+ this._clearErrors();
145
+ this._handleSubmit();
146
+ });
147
+
148
+ el.addEventListener('input', () => {
149
+ if (!this._submitted) return;
150
+
151
+ this.validate();
152
+ this._applyErrors();
153
+ });
154
+
147
155
  this.el = el;
148
156
  return el;
149
157
  }
@@ -0,0 +1,221 @@
1
+ .kanban {
2
+ --ez-kanban-bg: var(--ez-surface-primary, #f1f5f9);
3
+ --ez-kanban-column-bg: var(--ez-surface-secondary, #ffffff);
4
+ --ez-kanban-card-bg: var(--ez-surface-secondary, #ffffff);
5
+ --ez-kanban-border: var(--ez-border, #e2e8f0);
6
+ --ez-kanban-text: var(--ez-text-primary, #1e293b);
7
+ --ez-kanban-text-secondary: var(--ez-text-secondary, #475569);
8
+ --ez-kanban-text-muted: var(--ez-text-tertiary, #94a3b8);
9
+ --ez-kanban-primary: var(--ez-primary, #005871);
10
+ --ez-kanban-radius: 8px;
11
+ --ez-kanban-column-gap: 16px;
12
+ --ez-kanban-card-gap: 8px;
13
+
14
+ --ez-kanban-priority-critical: #dc2626;
15
+ --ez-kanban-priority-high: #ea580c;
16
+ --ez-kanban-priority-medium: #ca8a04;
17
+ --ez-kanban-priority-low: #16a34a;
18
+
19
+ display: flex;
20
+ flex-direction: column;
21
+ flex: 1;
22
+ min-height: 0;
23
+ background: var(--ez-kanban-bg);
24
+ border-radius: var(--ez-kanban-radius);
25
+ overflow: hidden;
26
+ }
27
+
28
+ .elevated {
29
+ border: 1px solid var(--ez-kanban-border);
30
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 4px 12px rgba(0, 0, 0, 0.03);
31
+ }
32
+
33
+ .flat {
34
+ border: none;
35
+ background: transparent;
36
+ }
37
+
38
+ .outlined {
39
+ border: 1px solid var(--ez-kanban-border);
40
+ }
41
+
42
+ :global {
43
+ .ez-kanban-toolbar {
44
+ display: flex;
45
+ align-items: center;
46
+ gap: 12px;
47
+ padding: 12px 16px;
48
+ border-bottom: 1px solid var(--ez-kanban-border);
49
+ background: var(--ez-kanban-column-bg);
50
+ }
51
+
52
+ .ez-kanban-board {
53
+ display: flex;
54
+ flex: 1;
55
+ min-height: 0;
56
+ padding: 16px;
57
+ gap: var(--ez-kanban-column-gap);
58
+ overflow-x: auto;
59
+ overflow-y: hidden;
60
+ }
61
+
62
+ .ez-kanban-column {
63
+ display: flex;
64
+ flex-direction: column;
65
+ flex-shrink: 0;
66
+ background: var(--ez-kanban-column-bg);
67
+ border-radius: var(--ez-kanban-radius);
68
+ border: 1px solid var(--ez-kanban-border);
69
+ min-height: 0;
70
+ max-height: 100%;
71
+ transition: box-shadow 0.15s, opacity 0.15s;
72
+
73
+ &.is-dragging {
74
+ opacity: 0.5;
75
+ }
76
+
77
+ &.drop-before {
78
+ box-shadow: -3px 0 0 0 var(--ez-kanban-primary);
79
+ }
80
+
81
+ &.drop-after {
82
+ box-shadow: 3px 0 0 0 var(--ez-kanban-primary);
83
+ }
84
+ }
85
+
86
+ .ez-kanban-column-header {
87
+ display: flex;
88
+ align-items: center;
89
+ padding: 12px 16px;
90
+ border-bottom: 1px solid var(--ez-kanban-border);
91
+ gap: 8px;
92
+ cursor: grab;
93
+
94
+ &:active {
95
+ cursor: grabbing;
96
+ }
97
+
98
+ .column-title {
99
+ font-weight: 600;
100
+ font-size: 14px;
101
+ color: var(--ez-kanban-text);
102
+ flex: 1;
103
+ min-width: 0;
104
+ overflow: hidden;
105
+ text-overflow: ellipsis;
106
+ white-space: nowrap;
107
+ }
108
+
109
+ .column-count {
110
+ background: var(--ez-surface-tertiary, #f1f5f9);
111
+ border-radius: 10px;
112
+ padding: 2px 8px;
113
+ font-size: 12px;
114
+ font-weight: 500;
115
+ color: var(--ez-kanban-text-secondary);
116
+
117
+ &.at-limit {
118
+ background: var(--ez-kanban-priority-high);
119
+ color: white;
120
+ }
121
+ }
122
+
123
+ .column-actions {
124
+ display: flex;
125
+ gap: 4px;
126
+ opacity: 0;
127
+ transition: opacity 0.15s;
128
+ }
129
+
130
+ &:hover .column-actions {
131
+ opacity: 1;
132
+ }
133
+
134
+ .column-action {
135
+ display: flex;
136
+ align-items: center;
137
+ justify-content: center;
138
+ width: 28px;
139
+ height: 28px;
140
+ border: none;
141
+ background: transparent;
142
+ border-radius: 6px;
143
+ color: var(--ez-kanban-text-muted);
144
+ cursor: pointer;
145
+ transition: background 0.15s, color 0.15s;
146
+
147
+ &:hover {
148
+ background: var(--ez-surface-tertiary, #f1f5f9);
149
+ color: var(--ez-kanban-text);
150
+ }
151
+
152
+ &.column-action--danger:hover {
153
+ background: #fef2f2;
154
+ color: #dc2626;
155
+ }
156
+ }
157
+ }
158
+
159
+ .ez-kanban-column-cards {
160
+ flex: 1;
161
+ overflow-y: auto;
162
+ padding: 8px;
163
+ display: flex;
164
+ flex-direction: column;
165
+ gap: var(--ez-kanban-card-gap);
166
+ min-height: 60px;
167
+
168
+ &:empty::after {
169
+ content: 'No cards';
170
+ display: flex;
171
+ align-items: center;
172
+ justify-content: center;
173
+ padding: 24px;
174
+ color: var(--ez-kanban-text-muted);
175
+ font-size: 13px;
176
+ }
177
+
178
+ &.drop-empty {
179
+ background: rgba(0, 88, 113, 0.05);
180
+ border: 2px dashed var(--ez-kanban-primary);
181
+ border-radius: 6px;
182
+
183
+ &::after {
184
+ content: 'Drop here';
185
+ color: var(--ez-kanban-primary);
186
+ }
187
+ }
188
+ }
189
+
190
+ .ez-kanban-add-card,
191
+ .ez-kanban-add-column {
192
+ display: flex;
193
+ align-items: center;
194
+ justify-content: center;
195
+ gap: 6px;
196
+ padding: 10px;
197
+ margin: 8px;
198
+ border: 1px dashed var(--ez-kanban-border);
199
+ border-radius: 6px;
200
+ background: transparent;
201
+ color: var(--ez-kanban-text-muted);
202
+ font-size: 13px;
203
+ cursor: pointer;
204
+ transition: all 0.15s;
205
+
206
+ &:hover {
207
+ background: var(--ez-surface-primary, #f8fafc);
208
+ color: var(--ez-kanban-primary);
209
+ border-color: var(--ez-kanban-primary);
210
+ }
211
+ }
212
+
213
+ .ez-kanban-add-column {
214
+ flex-shrink: 0;
215
+ width: 280px;
216
+ min-width: 250px;
217
+ height: 60px;
218
+ margin: 0;
219
+ align-self: flex-start;
220
+ }
221
+ }