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 EzCheckboxConfig extends EzBaseComponentConfig {
8
12
  size?: 'sm' | 'lg';
9
13
  disabled?: boolean;
@@ -21,62 +25,62 @@ export class EzCheckbox 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
- 'checkbox',
30
- cfg.size,
31
- cfg.disabled && 'disabled'
32
- );
33
-
34
- const input = document.createElement('input');
35
- input.type = 'checkbox';
36
- input.className = cls('input');
37
- input.checked = !!(cfg.checked ?? cfg.value);
38
-
39
- if (cfg.disabled) input.disabled = true;
40
- if (cfg.name) input.name = cfg.name;
41
-
42
31
  if (cfg.name && cfg.formData) {
43
32
  this.config.bind = `${cfg.formData}.${cfg.name}`;
44
33
  }
45
34
 
46
- const box = document.createElement('span');
47
- box.className = cls('box');
48
-
49
- const check = document.createElement('i');
50
- check.className = cls('check', 'fa-solid fa-check');
51
- box.appendChild(check);
52
-
53
- this.applyCommonBindings(input);
54
-
55
- const onChange = this._createOnChangeHandler();
56
-
57
- input.addEventListener('change', e => {
58
- if (onChange) {
59
- onChange((e.target as HTMLInputElement).checked);
35
+ const items: unknown[] = [
36
+ {
37
+ eztype: 'input',
38
+ type: 'checkbox',
39
+ cls: cls('input'),
40
+ checked: !!(cfg.checked ?? cfg.value),
41
+ disabled: cfg.disabled,
42
+ name: cfg.name
43
+ },
44
+ {
45
+ eztype: 'span',
46
+ cls: cls('box'),
47
+ items: [{ eztype: 'i', cls: [cls('check'), 'fa-solid fa-check'].join(' ') }]
60
48
  }
61
- });
62
-
63
- wrapper.appendChild(input);
64
- wrapper.appendChild(box);
49
+ ];
65
50
 
66
51
  if (cfg.label) {
67
- const label = document.createElement('span');
68
- label.className = cls('label');
69
- label.textContent = cfg.label;
70
- wrapper.appendChild(label);
52
+ items.push({
53
+ eztype: 'span',
54
+ cls: cls('label'),
55
+ text: cfg.label
56
+ });
71
57
  }
72
58
 
73
- const error = document.createElement('div');
74
- error.className = cls('fieldError');
75
- 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('checkbox', cfg.size, cfg.disabled && 'disabled'),
67
+ items
68
+ }) as HTMLLabelElement;
76
69
 
77
70
  this._wrapper = wrapper;
78
- this._input = input;
79
- this._error = error;
71
+ this._input = wrapper.querySelector('input');
72
+ this._error = wrapper.querySelector(`.${cls('fieldError')}`);
73
+
74
+ if (this._input) {
75
+ this.applyCommonBindings(this._input);
76
+
77
+ const onChange = this._createOnChangeHandler();
78
+ this._input.addEventListener('change', e => {
79
+ if (onChange) {
80
+ onChange((e.target as HTMLInputElement).checked);
81
+ }
82
+ });
83
+ }
80
84
 
81
85
  return wrapper;
82
86
  }
@@ -3,6 +3,7 @@ import { createEzStore } from '../store/EzStore.js';
3
3
  import buttonStyles from '../button/EzButton.module.scss';
4
4
 
5
5
  declare const ez: {
6
+ _createElement(config: unknown, controller?: string | null, parent?: unknown): Promise<HTMLElement>;
6
7
  _createChildElements(items: unknown[], controller: string | null, state: unknown): Promise<Node[]>;
7
8
  getControllerSync(name: string): EzController | null;
8
9
  define(name: string, component: unknown): void;
@@ -311,36 +312,20 @@ export class EzDataView extends EzBaseComponent {
311
312
  }
312
313
 
313
314
  async render(): Promise<HTMLElement> {
314
- const el = document.createElement('div');
315
-
316
- el.style.display = 'flex';
317
- el.style.flexDirection = 'column';
318
- el.style.minHeight = '0';
319
-
320
- if (this.config.flex) {
321
- el.style.flex = String(this.config.flex);
322
- }
323
-
324
- if (this.config.style) {
325
- Object.assign(el.style, this.config.style);
326
- }
327
-
328
- this.applyCls(el);
329
-
330
315
  const items = (this.config as { items?: unknown[] }).items;
331
- if (items) {
332
- const children = await ez._createChildElements(
333
- items,
334
- this.config.controller || null,
335
- null
336
- );
337
-
338
- for (const child of children) {
339
- if (child instanceof Node) {
340
- el.appendChild(child);
341
- }
342
- }
343
- }
316
+
317
+ const el = await ez._createElement({
318
+ eztype: 'div',
319
+ cls: this.config.cls,
320
+ style: {
321
+ display: 'flex',
322
+ flexDirection: 'column',
323
+ minHeight: '0',
324
+ flex: this.config.flex ? String(this.config.flex) : undefined,
325
+ ...this.config.style as object
326
+ },
327
+ items: items || []
328
+ }, this.config.controller || null, this) as HTMLElement;
344
329
 
345
330
  this.el = el;
346
331
 
@@ -1,6 +1,7 @@
1
1
  import { EzBaseComponent, EzBaseComponentConfig } from '../../EzBaseComponent.js';
2
2
 
3
3
  declare const ez: {
4
+ _createElement(config: unknown, controller?: string | null, parent?: unknown): Promise<HTMLElement>;
4
5
  _createChildElements(items: unknown[], controller: string | null, state: unknown): Promise<Node[]>;
5
6
  define(name: string, component: unknown): void;
6
7
  };
@@ -63,28 +64,56 @@ export class EzDataViewCards extends EzBaseComponent {
63
64
  }
64
65
 
65
66
  async render(): Promise<HTMLElement> {
66
- const el = document.createElement('div');
67
- el.className = 'ez-dataview-cards';
68
- el.style.display = 'flex';
69
- el.style.flexDirection = 'column';
70
- el.style.flex = '1';
71
- el.style.minHeight = '0';
72
- el.style.overflow = 'hidden';
73
-
74
- this._cardsContainer = document.createElement('div');
75
- this._cardsContainer.className = 'ez-dataview-cards-grid';
76
- this._applyGridStyles();
77
-
78
- this._footerEl = document.createElement('div');
79
- this._footerEl.className = 'ez-dataview-cards-footer';
80
- this._footerEl.style.padding = '12px 16px';
81
- this._footerEl.style.borderTop = '1px solid var(--ez-border)';
82
- this._footerEl.style.display = 'flex';
83
- this._footerEl.style.alignItems = 'center';
84
- this._footerEl.style.gap = '16px';
85
-
86
- el.appendChild(this._cardsContainer);
87
- el.appendChild(this._footerEl);
67
+ const { columns = 'auto', minWidth = 300, gap = 16 } = this._cardsConfig;
68
+
69
+ const gridTemplate = columns === 'auto'
70
+ ? `repeat(auto-fill, minmax(${minWidth}px, 1fr))`
71
+ : `repeat(${columns}, 1fr)`;
72
+
73
+ const el = await ez._createElement({
74
+ eztype: 'div',
75
+ cls: 'ez-dataview-cards',
76
+ style: {
77
+ display: 'flex',
78
+ flexDirection: 'column',
79
+ flex: '1',
80
+ minHeight: '0',
81
+ overflow: 'hidden'
82
+ },
83
+ items: [
84
+ {
85
+ eztype: 'div',
86
+ cls: 'ez-dataview-cards-grid',
87
+ style: {
88
+ display: 'grid',
89
+ gridTemplateColumns: gridTemplate,
90
+ gap: `${gap}px`,
91
+ padding: '16px',
92
+ flex: '1',
93
+ overflowY: 'auto',
94
+ minHeight: '0',
95
+ alignContent: 'start'
96
+ },
97
+ _ezAfterRender: (container: HTMLElement) => {
98
+ this._cardsContainer = container;
99
+ }
100
+ },
101
+ {
102
+ eztype: 'div',
103
+ cls: 'ez-dataview-cards-footer',
104
+ style: {
105
+ padding: '12px 16px',
106
+ borderTop: '1px solid var(--ez-border)',
107
+ display: 'flex',
108
+ alignItems: 'center',
109
+ gap: '16px'
110
+ },
111
+ _ezAfterRender: (footer: HTMLElement) => {
112
+ this._footerEl = footer;
113
+ }
114
+ }
115
+ ]
116
+ }, this.config.controller || null, this) as HTMLElement;
88
117
 
89
118
  if (this._store) {
90
119
  this._unsubscribers.push(
@@ -106,25 +135,6 @@ export class EzDataViewCards extends EzBaseComponent {
106
135
  return el;
107
136
  }
108
137
 
109
- private _applyGridStyles(): void {
110
- if (!this._cardsContainer) return;
111
-
112
- const { columns = 'auto', minWidth = 300, gap = 16 } = this._cardsConfig;
113
-
114
- const gridTemplate = columns === 'auto'
115
- ? `repeat(auto-fill, minmax(${minWidth}px, 1fr))`
116
- : `repeat(${columns}, 1fr)`;
117
-
118
- this._cardsContainer.style.display = 'grid';
119
- this._cardsContainer.style.gridTemplateColumns = gridTemplate;
120
- this._cardsContainer.style.gap = `${gap}px`;
121
- this._cardsContainer.style.padding = '16px';
122
- this._cardsContainer.style.flex = '1';
123
- this._cardsContainer.style.overflowY = 'auto';
124
- this._cardsContainer.style.minHeight = '0';
125
- this._cardsContainer.style.alignContent = 'start';
126
- }
127
-
128
138
  private _onLoadingChange(loading: boolean): void {
129
139
  if (loading) {
130
140
  this._showLoading();
@@ -1,6 +1,7 @@
1
1
  import { EzBaseComponent, EzBaseComponentConfig } from '../../EzBaseComponent.js';
2
2
 
3
3
  declare const ez: {
4
+ _createElement(config: unknown): Promise<HTMLElement>;
4
5
  _createChildElements(items: unknown[], controller: string | null, state: unknown): Promise<Node[]>;
5
6
  define(name: string, component: unknown): void;
6
7
  };
@@ -62,8 +63,10 @@ export class EzDataViewGrid extends EzBaseComponent {
62
63
  return this.el;
63
64
  }
64
65
 
65
- const el = document.createElement('div');
66
- el.textContent = 'Failed to render grid';
66
+ const el = await ez._createElement({
67
+ eztype: 'div',
68
+ text: 'Failed to render grid'
69
+ }) as HTMLElement;
67
70
  this.el = el;
68
71
  return el;
69
72
  }
@@ -397,7 +397,7 @@ export class EzDatePicker extends EzBaseComponent {
397
397
  this._close();
398
398
  }
399
399
  };
400
- document.addEventListener('click', this._outsideClickHandler);
400
+ document.addEventListener('click', this._outsideClickHandler, true);
401
401
 
402
402
  this._input.addEventListener('keydown', (e) => {
403
403
  if (e.key === 'Escape') {
@@ -509,7 +509,7 @@ export class EzDatePicker extends EzBaseComponent {
509
509
 
510
510
  destroy(): void {
511
511
  if (this._outsideClickHandler) {
512
- document.removeEventListener('click', this._outsideClickHandler);
512
+ document.removeEventListener('click', this._outsideClickHandler, true);
513
513
  }
514
514
  if (this._dropdown && this._dropdown.parentNode) {
515
515
  this._dropdown.parentNode.removeChild(this._dropdown);
@@ -58,114 +58,131 @@ export class EzDialog extends EzBaseComponent {
58
58
  _dialogInstance
59
59
  } = props as EzDialogConfig['props'] & { [key: string]: unknown };
60
60
 
61
- // Create overlay
62
- const overlay = document.createElement('div');
63
- overlay.className = css('overlay');
64
-
65
- // Create dialog
66
- const dialog = document.createElement('div');
67
- dialog.className = css('dialog', size);
61
+ const dialogItems: unknown[] = [];
68
62
 
69
63
  // Header
70
64
  if (title || icon || closable || maximizable) {
71
- const header = document.createElement('div');
72
- header.className = css('header');
65
+ const headerItems: unknown[] = [];
73
66
 
74
67
  if (icon) {
75
- const iconEl = document.createElement('div');
76
- iconEl.className = css('headerIcon', iconVariant);
77
- iconEl.innerHTML = `<i class="${icon}"></i>`;
78
- header.appendChild(iconEl);
68
+ headerItems.push({
69
+ eztype: 'div',
70
+ cls: css('headerIcon', iconVariant),
71
+ items: [{ eztype: 'i', cls: icon }]
72
+ });
79
73
  }
80
74
 
81
75
  if (title) {
82
- const titleEl = document.createElement('h3');
83
- titleEl.className = css('title');
84
- titleEl.textContent = title;
85
- header.appendChild(titleEl);
76
+ headerItems.push({
77
+ eztype: 'h3',
78
+ cls: css('title'),
79
+ text: title
80
+ });
86
81
  }
87
82
 
88
83
  // Spacer
89
- const spacer = document.createElement('div');
90
- spacer.style.flex = '1';
91
- header.appendChild(spacer);
84
+ headerItems.push({
85
+ eztype: 'div',
86
+ style: { flex: '1' }
87
+ });
92
88
 
93
89
  if (maximizable) {
94
- const maxBtn = document.createElement('button');
95
- maxBtn.className = css('headerBtn');
96
- maxBtn.innerHTML = '<i class="fa-solid fa-expand"></i>';
97
- maxBtn.onclick = () => _dialogInstance?.toggleMaximize();
98
- header.appendChild(maxBtn);
90
+ headerItems.push({
91
+ eztype: 'button',
92
+ cls: css('headerBtn'),
93
+ items: [{ eztype: 'i', cls: 'fa-solid fa-expand' }],
94
+ onClick: () => _dialogInstance?.toggleMaximize()
95
+ });
99
96
  }
100
97
 
101
98
  if (closable) {
102
- const closeBtn = document.createElement('button');
103
- closeBtn.className = css('headerBtn');
104
- closeBtn.innerHTML = '<i class="fa-solid fa-xmark"></i>';
105
- closeBtn.onclick = () => _dialogInstance?.close();
106
- header.appendChild(closeBtn);
99
+ headerItems.push({
100
+ eztype: 'button',
101
+ cls: css('headerBtn'),
102
+ items: [{ eztype: 'i', cls: 'fa-solid fa-xmark' }],
103
+ onClick: () => _dialogInstance?.close()
104
+ });
107
105
  }
108
106
 
109
- dialog.appendChild(header);
107
+ dialogItems.push({
108
+ eztype: 'div',
109
+ cls: css('header'),
110
+ items: headerItems
111
+ });
110
112
  }
111
113
 
112
114
  // Body
113
- const body = document.createElement('div');
114
- body.className = css('body');
115
+ const bodyItems: unknown[] = [];
115
116
 
116
117
  if (message) {
117
- const messageEl = document.createElement('p');
118
- messageEl.className = css('message');
119
- messageEl.textContent = message;
120
- body.appendChild(messageEl);
118
+ bodyItems.push({
119
+ eztype: 'p',
120
+ cls: css('message'),
121
+ text: message
122
+ });
121
123
  }
122
124
 
123
125
  if (items && items.length > 0) {
124
- const children = await ez._createChildElements(items, null, null);
125
- for (const child of children) {
126
- if (child instanceof Node) {
127
- body.appendChild(child);
126
+ const itemCss = (props as Record<string, unknown>).css;
127
+ if (itemCss) {
128
+ for (const item of items) {
129
+ const itemWithCss = { ...(item as Record<string, unknown>) };
130
+ if (!itemWithCss.css) {
131
+ itemWithCss.css = itemCss;
132
+ }
133
+ bodyItems.push(itemWithCss);
128
134
  }
135
+ } else {
136
+ bodyItems.push(...items);
129
137
  }
130
138
  }
131
139
 
132
- dialog.appendChild(body);
140
+ dialogItems.push({
141
+ eztype: 'div',
142
+ cls: css('body'),
143
+ items: bodyItems
144
+ });
133
145
 
134
146
  // Footer with buttons
135
147
  let buttonList = buttons;
136
148
  if (typeof buttons === 'function') {
137
- buttonList = buttons(_dialogInstance);
149
+ buttonList = (buttons as (instance: DialogInstance | undefined) => DialogButton[])(_dialogInstance);
138
150
  }
139
151
 
140
152
  if (Array.isArray(buttonList) && buttonList.length > 0) {
141
- const footer = document.createElement('div');
142
- footer.className = css('footer');
143
-
144
- for (const btn of buttonList) {
145
- const buttonConfig = {
146
- eztype: 'EzButton',
147
- text: btn.text,
148
- variant: btn.variant || 'secondary',
149
- onClick: () => {
150
- if (btn.onClick) {
151
- btn.onClick(_dialogInstance!);
152
- }
153
- if (btn.value !== undefined) {
154
- _dialogInstance?.close(btn.value);
155
- } else if (!btn.onClick) {
156
- _dialogInstance?.close();
157
- }
153
+ const footerItems = buttonList.map(btn => ({
154
+ eztype: 'EzButton',
155
+ text: btn.text,
156
+ variant: btn.variant || 'secondary',
157
+ onClick: () => {
158
+ if (btn.onClick) {
159
+ btn.onClick(_dialogInstance!);
158
160
  }
159
- };
160
-
161
- const buttonEl = await ez._createElement(buttonConfig);
162
- footer.appendChild(buttonEl);
163
- }
161
+ if (btn.value !== undefined) {
162
+ _dialogInstance?.close(btn.value);
163
+ } else if (!btn.onClick) {
164
+ _dialogInstance?.close();
165
+ }
166
+ }
167
+ }));
164
168
 
165
- dialog.appendChild(footer);
169
+ dialogItems.push({
170
+ eztype: 'div',
171
+ cls: css('footer'),
172
+ items: footerItems
173
+ });
166
174
  }
167
175
 
168
- overlay.appendChild(dialog);
176
+ const overlay = await ez._createElement({
177
+ eztype: 'div',
178
+ cls: css('overlay'),
179
+ items: [{
180
+ eztype: 'div',
181
+ cls: css('dialog', size),
182
+ items: dialogItems
183
+ }]
184
+ }) as HTMLDivElement;
185
+
169
186
  return overlay;
170
187
  }
171
188
  }