ezfw-core 1.0.28 → 1.0.29
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 +441 -137
- package/components/EzComponent.ts +6 -5
- package/components/EzIcon.ts +95 -0
- package/components/EzLabel.ts +45 -0
- package/components/avatar/EzAvatar.ts +62 -0
- package/components/badge/EzBadge.module.scss +1 -1
- package/components/button/EzButton.module.scss +76 -0
- package/components/button/EzButton.ts +11 -4
- package/components/checkbox/EzCheckbox.ts +11 -1
- package/components/dataview/EzDataView.module.scss +99 -3
- package/components/dataview/EzDataView.ts +120 -26
- package/components/dataview/modes/EzDataViewCards.ts +522 -157
- package/components/datepicker/EzDatePicker.module.scss +26 -8
- package/components/datepicker/EzDatePicker.ts +66 -41
- package/components/dialog/EzDialog.module.scss +2 -0
- package/components/dialog/EzDialog.ts +25 -2
- package/components/form/EzForm.ts +115 -32
- package/components/grid/EzGrid.ts +355 -59
- package/components/grid/body/EzGridCell.ts +6 -1
- package/components/grid/footer/EzGridFooter.scss +154 -107
- package/components/grid/footer/EzGridFooter.ts +89 -26
- package/components/grid/state/EzGridRemote.ts +2 -2
- package/components/grid/types.ts +17 -0
- package/components/input/EzInput.module.scss +42 -13
- package/components/input/EzInput.ts +61 -31
- package/components/kanban/EzKanban.ts +1 -1
- package/components/layout/EzLayout.module.scss +22 -0
- package/components/layout/EzLayout.ts +364 -0
- package/components/layout/README.md +230 -0
- package/components/mask/EzMask.module.scss +49 -0
- package/components/mask/EzMask.ts +53 -0
- package/components/orgchart/EzOrgChart.module.scss +197 -0
- package/components/orgchart/EzOrgChart.ts +1203 -0
- package/components/paper/EzPaper.module.scss +42 -0
- package/components/paper/EzPaper.ts +47 -0
- package/components/searchfilter/EzSearchFilter.module.scss +175 -0
- package/components/searchfilter/EzSearchFilter.ts +502 -0
- package/components/select/EzSelect.module.scss +90 -30
- package/components/select/EzSelect.ts +528 -114
- package/components/store/EzStore.ts +160 -15
- package/components/tabs/EzTabPanel.module.scss +93 -10
- package/components/tabs/EzTabPanel.ts +107 -30
- package/components/textarea/EzTextarea.module.scss +28 -8
- package/components/textarea/EzTextarea.ts +46 -37
- package/components/timepicker/EzTimePicker.module.scss +13 -0
- package/components/timepicker/EzTimePicker.ts +20 -0
- package/components/tooltip/EzTooltip.module.scss +19 -0
- package/components/tooltip/EzTooltip.ts +42 -10
- package/components/tree/EzTree.module.scss +235 -0
- package/components/tree/EzTree.ts +629 -0
- package/core/EzComponentTypes.ts +40 -2
- package/core/EzModel.ts +25 -3
- package/core/ez.ts +92 -5
- package/core/loader.ts +31 -4
- package/core/renderer.ts +915 -180
- package/core/router.ts +29 -2
- package/core/services.ts +142 -136
- package/package.json +4 -6
- package/services/dialog.js +25 -0
- package/services/fetchApi.js +64 -14
- package/services/mask.js +43 -0
- package/template/doc/EzDocsController.js +6 -4
- package/template/doc/data/activityfeed/EzActivityFeedDoc.js +2 -2
- package/template/doc/data/avatar/EzAvatarDoc.js +2 -2
- package/template/doc/data/badge/EzBadgeDoc.js +2 -2
- package/template/doc/data/button/EzButtonDoc.js +2 -2
- package/template/doc/data/buttongroup/EzButtonGroupDoc.js +2 -2
- package/template/doc/data/card/EzCardDoc.js +2 -2
- package/template/doc/data/chart/EzChartDoc.js +2 -2
- package/template/doc/data/checkbox/EzCheckboxDoc.js +2 -2
- package/template/doc/data/component/EzComponentDoc.js +2 -2
- package/template/doc/data/dataview/EzDataViewDoc.js +146 -0
- package/template/doc/data/datepicker/EzDatePickerDoc.js +2 -2
- package/template/doc/data/dialog/EzDialogDoc.js +2 -2
- package/template/doc/data/dropdown/EzDropdownDoc.js +2 -2
- package/template/doc/data/grid/EzGridDoc.js +2 -2
- package/template/doc/data/input/EzInputDoc.js +2 -2
- package/template/doc/data/kanban/EzKanbanDoc.js +109 -0
- package/template/doc/data/label/EzLabelDoc.js +2 -2
- package/template/doc/data/panel/EzPanelDoc.js +2 -2
- package/template/doc/data/paper/EzPaperDoc.js +119 -0
- package/template/doc/data/picker/EzPickerDoc.js +111 -0
- package/template/doc/data/radio/EzRadioDoc.js +2 -2
- package/template/doc/data/select/EzSelectDoc.js +2 -2
- package/template/doc/data/skeleton/EzSkeletonDoc.js +2 -2
- package/template/doc/data/store/EzStoreDoc.js +94 -0
- package/template/doc/data/switch/EzSwitchDoc.js +2 -2
- package/template/doc/data/tabpanel/EzTabPanelDoc.js +7 -7
- package/template/doc/data/textarea/EzTextareaDoc.js +2 -2
- package/template/doc/data/timepicker/EzTimePickerDoc.js +2 -2
- package/template/doc/data/tooltip/EzTooltipDoc.js +2 -2
- package/template/doc/viewer/content/EzDocsContent.js +2 -2
- package/themes/ez-theme-slate.scss +434 -0
- package/themes/ez-theme.scss +6 -1
- package/services/crypto.js +0 -68
|
@@ -11,11 +11,15 @@ const tooltipCls = cx(tooltipStyles);
|
|
|
11
11
|
|
|
12
12
|
declare const ez: {
|
|
13
13
|
_controllers: Record<string, EzController | undefined>;
|
|
14
|
-
getController(name: string): EzController | null
|
|
14
|
+
getController(name: string): Promise<EzController | null>;
|
|
15
15
|
getControllerSync(name: string): EzController | null;
|
|
16
16
|
getDeepValue(obj: unknown, path: string[]): unknown;
|
|
17
17
|
setDeepValue(obj: unknown, path: string[], value: unknown): void;
|
|
18
|
-
_createElement(
|
|
18
|
+
_createElement(
|
|
19
|
+
config: EzComponentConfig,
|
|
20
|
+
controllerName?: string | null,
|
|
21
|
+
inheritedState?: unknown
|
|
22
|
+
): Promise<HTMLElement>;
|
|
19
23
|
hasStyles(name: string): boolean;
|
|
20
24
|
};
|
|
21
25
|
|
|
@@ -33,7 +37,8 @@ type TooltipPosition = 'top' | 'bottom' | 'left' | 'right';
|
|
|
33
37
|
type TooltipVariant = 'dark' | 'light';
|
|
34
38
|
|
|
35
39
|
interface TooltipConfig {
|
|
36
|
-
text
|
|
40
|
+
text?: string;
|
|
41
|
+
component?: EzComponentConfig;
|
|
37
42
|
position?: TooltipPosition;
|
|
38
43
|
variant?: TooltipVariant;
|
|
39
44
|
delay?: number;
|
|
@@ -45,8 +50,16 @@ interface BindConfig {
|
|
|
45
50
|
visible?: string;
|
|
46
51
|
cls?: string | (() => string);
|
|
47
52
|
html?: string | (() => string);
|
|
53
|
+
sanitizeHtml?: boolean;
|
|
48
54
|
style?: Record<string, string> | (() => Record<string, string>);
|
|
49
55
|
text?: string | (() => string);
|
|
56
|
+
[key: string]: unknown;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function sanitizeHtml(html: string): string {
|
|
60
|
+
const div = document.createElement('div');
|
|
61
|
+
div.textContent = html;
|
|
62
|
+
return div.innerHTML;
|
|
50
63
|
}
|
|
51
64
|
|
|
52
65
|
type StyleValue = string | number | undefined;
|
|
@@ -130,6 +143,9 @@ export interface EzComponentConfig {
|
|
|
130
143
|
// Wrapper for grouping shortcuts
|
|
131
144
|
ezStyle?: EzStyleShortcuts;
|
|
132
145
|
|
|
146
|
+
// Behavior shortcuts
|
|
147
|
+
showOnHover?: boolean;
|
|
148
|
+
|
|
133
149
|
[key: string]: unknown;
|
|
134
150
|
}
|
|
135
151
|
|
|
@@ -158,18 +174,45 @@ export class EzBaseComponent {
|
|
|
158
174
|
config: EzComponentConfig;
|
|
159
175
|
props: Record<string, unknown>;
|
|
160
176
|
el: HTMLElement | null = null;
|
|
177
|
+
controller: EzController | null = null;
|
|
161
178
|
|
|
162
179
|
protected _effects?: EffectCleanup[];
|
|
163
180
|
protected _domListeners?: DomListenerCleanup[];
|
|
164
181
|
protected _children?: EzBaseComponent[];
|
|
165
182
|
protected _tooltip?: HTMLDivElement;
|
|
166
183
|
protected _tooltipCleanup?: () => void;
|
|
184
|
+
protected _activateHandler?: (e: Event) => void;
|
|
185
|
+
protected _showOnHoverCleanup?: () => void;
|
|
167
186
|
|
|
168
187
|
constructor(config: EzComponentConfig = {}) {
|
|
169
188
|
this.config = config;
|
|
170
189
|
this.props = config.props || {};
|
|
171
190
|
}
|
|
172
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Lifecycle method called when component becomes active/visible.
|
|
194
|
+
* Override in subclasses to handle activation (e.g., reload data).
|
|
195
|
+
* Triggered by parent containers like EzTabPanel via 'ez-activate' event.
|
|
196
|
+
*/
|
|
197
|
+
onActivate(): void {
|
|
198
|
+
// Override in subclasses
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Setup activation listener on element.
|
|
203
|
+
* Call this in render() after element is created.
|
|
204
|
+
*/
|
|
205
|
+
protected _setupActivateListener(el: HTMLElement): void {
|
|
206
|
+
this._activateHandler = () => this.onActivate();
|
|
207
|
+
el.addEventListener('ez-activate', this._activateHandler);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
protected async _resolveController(): Promise<void> {
|
|
211
|
+
if (this.config.controller) {
|
|
212
|
+
this.controller = await ez.getController(this.config.controller);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
173
216
|
/**
|
|
174
217
|
* Resolve binding configuration to get controller and path.
|
|
175
218
|
* Supports:
|
|
@@ -195,7 +238,7 @@ export class EzBaseComponent {
|
|
|
195
238
|
|
|
196
239
|
if (!controllerName) return null;
|
|
197
240
|
|
|
198
|
-
const controller = ez.
|
|
241
|
+
const controller = ez.getControllerSync(controllerName);
|
|
199
242
|
if (!controller?.state) return null;
|
|
200
243
|
|
|
201
244
|
return {
|
|
@@ -214,7 +257,7 @@ export class EzBaseComponent {
|
|
|
214
257
|
}
|
|
215
258
|
|
|
216
259
|
if (typeof this.config.onChange === 'string') {
|
|
217
|
-
const controller = ez.
|
|
260
|
+
const controller = ez.getControllerSync(this.config.controller ?? '');
|
|
218
261
|
const method = controller?.[this.config.onChange];
|
|
219
262
|
if (typeof method === 'function') {
|
|
220
263
|
return (method as (value: unknown) => void).bind(controller);
|
|
@@ -249,6 +292,7 @@ export class EzBaseComponent {
|
|
|
249
292
|
el.tagName === 'INPUT' ||
|
|
250
293
|
el.tagName === 'TEXTAREA' ||
|
|
251
294
|
el.tagName === 'SELECT';
|
|
295
|
+
const isCheckbox = el.tagName === 'INPUT' && (el as HTMLInputElement).type === 'checkbox';
|
|
252
296
|
|
|
253
297
|
// 2️⃣ String bind: 'AppController:prop'
|
|
254
298
|
if (typeof bind === 'string') {
|
|
@@ -273,12 +317,18 @@ export class EzBaseComponent {
|
|
|
273
317
|
? ez._controllers[ctrl!]?.state[props[0]]
|
|
274
318
|
: ez.getDeepValue(ez._controllers[ctrl!]?.state, props!);
|
|
275
319
|
|
|
276
|
-
|
|
277
|
-
|
|
320
|
+
if (isCheckbox) {
|
|
321
|
+
const nextBool = !!next;
|
|
322
|
+
if ((el as HTMLInputElement).checked !== nextBool) {
|
|
323
|
+
(el as HTMLInputElement).checked = nextBool;
|
|
324
|
+
}
|
|
325
|
+
} else if (isInput) {
|
|
326
|
+
const nextStr = (next as string) ?? '';
|
|
278
327
|
if ((el as HTMLInputElement).value !== nextStr) {
|
|
279
328
|
(el as HTMLInputElement).value = nextStr;
|
|
280
329
|
}
|
|
281
330
|
} else {
|
|
331
|
+
const nextStr = (next as string) ?? '';
|
|
282
332
|
if (el.textContent !== nextStr) {
|
|
283
333
|
el.textContent = nextStr;
|
|
284
334
|
}
|
|
@@ -289,13 +339,13 @@ export class EzBaseComponent {
|
|
|
289
339
|
|
|
290
340
|
// 3️⃣ Object bind
|
|
291
341
|
if (typeof bind === 'object') {
|
|
292
|
-
this._applyValueBind(el, bind, ctrl, isInput);
|
|
342
|
+
this._applyValueBind(el, bind, ctrl, isInput, isCheckbox);
|
|
293
343
|
this._applyDataBind(el, bind, ctrl);
|
|
294
|
-
this._applyVisibleBind(el, bind);
|
|
295
|
-
this._applyClsBind(el, bind);
|
|
296
|
-
this._applyStyleBind(el, bind);
|
|
297
|
-
this._applyTextBind(el, bind);
|
|
298
|
-
this._applyHtmlBind(el, bind);
|
|
344
|
+
this._applyVisibleBind(el, bind, ctrl);
|
|
345
|
+
this._applyClsBind(el, bind, ctrl);
|
|
346
|
+
this._applyStyleBind(el, bind, ctrl);
|
|
347
|
+
this._applyTextBind(el, bind, ctrl);
|
|
348
|
+
this._applyHtmlBind(el, bind, ctrl);
|
|
299
349
|
}
|
|
300
350
|
}
|
|
301
351
|
|
|
@@ -303,7 +353,8 @@ export class EzBaseComponent {
|
|
|
303
353
|
el: HTMLElement,
|
|
304
354
|
bind: BindConfig,
|
|
305
355
|
ctrl: string | undefined,
|
|
306
|
-
isInput: boolean
|
|
356
|
+
isInput: boolean,
|
|
357
|
+
isCheckbox: boolean
|
|
307
358
|
): void {
|
|
308
359
|
if (!bind.value) return;
|
|
309
360
|
|
|
@@ -330,12 +381,18 @@ export class EzBaseComponent {
|
|
|
330
381
|
? ez._controllers[activeCtrl!]?.state[props[0]]
|
|
331
382
|
: ez.getDeepValue(ez._controllers[activeCtrl!]?.state, props!);
|
|
332
383
|
|
|
333
|
-
|
|
334
|
-
|
|
384
|
+
if (isCheckbox) {
|
|
385
|
+
const nextBool = !!next;
|
|
386
|
+
if ((el as HTMLInputElement).checked !== nextBool) {
|
|
387
|
+
(el as HTMLInputElement).checked = nextBool;
|
|
388
|
+
}
|
|
389
|
+
} else if (isInput) {
|
|
390
|
+
const nextStr = (next as string) ?? '';
|
|
335
391
|
if ((el as HTMLInputElement).value !== nextStr) {
|
|
336
392
|
(el as HTMLInputElement).value = nextStr;
|
|
337
393
|
}
|
|
338
394
|
} else {
|
|
395
|
+
const nextStr = (next as string) ?? '';
|
|
339
396
|
if (el.textContent !== nextStr) {
|
|
340
397
|
el.textContent = nextStr;
|
|
341
398
|
}
|
|
@@ -391,6 +448,21 @@ export class EzBaseComponent {
|
|
|
391
448
|
// Check if this render is still current before modifying DOM
|
|
392
449
|
if (renderVersion !== currentRenderVersion) return;
|
|
393
450
|
|
|
451
|
+
// Destroy old children before clearing (they have tooltips, etc.)
|
|
452
|
+
if (this._children) {
|
|
453
|
+
const childrenToRemove: EzBaseComponent[] = [];
|
|
454
|
+
for (const child of this._children) {
|
|
455
|
+
if (child.el && el.contains(child.el)) {
|
|
456
|
+
childrenToRemove.push(child);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
for (const child of childrenToRemove) {
|
|
460
|
+
child.destroy();
|
|
461
|
+
const idx = this._children.indexOf(child);
|
|
462
|
+
if (idx !== -1) this._children.splice(idx, 1);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
394
466
|
el.innerHTML = '';
|
|
395
467
|
|
|
396
468
|
if (Array.isArray(data)) {
|
|
@@ -427,7 +499,8 @@ export class EzBaseComponent {
|
|
|
427
499
|
childCfg.css = this.config.css;
|
|
428
500
|
childCfg._styleModule = this.config._styleModule;
|
|
429
501
|
}
|
|
430
|
-
|
|
502
|
+
// Pass 'this' as inheritedState so children are added to _children
|
|
503
|
+
const childEl = await ez._createElement(childCfg, null, this);
|
|
431
504
|
|
|
432
505
|
// Final check before appending
|
|
433
506
|
if (renderVersion !== currentRenderVersion) return;
|
|
@@ -440,7 +513,7 @@ export class EzBaseComponent {
|
|
|
440
513
|
this._effects!.push(stop);
|
|
441
514
|
}
|
|
442
515
|
|
|
443
|
-
private _applyVisibleBind(el: HTMLElement, bind: BindConfig): void {
|
|
516
|
+
private _applyVisibleBind(el: HTMLElement, bind: BindConfig, ctrl: string | undefined): void {
|
|
444
517
|
if (!bind.visible) return;
|
|
445
518
|
|
|
446
519
|
const expr = bind.visible;
|
|
@@ -453,20 +526,30 @@ export class EzBaseComponent {
|
|
|
453
526
|
rightExpr: string;
|
|
454
527
|
}
|
|
455
528
|
|
|
456
|
-
function parseVisibilityExpression(expr: string): ParsedVisibility | null {
|
|
529
|
+
function parseVisibilityExpression(expr: string, defaultCtrl: string | undefined): ParsedVisibility | null {
|
|
457
530
|
const operatorMatch = expr.match(/(>=|<=|==|!=|>|<)/);
|
|
458
531
|
if (!operatorMatch) return null;
|
|
459
532
|
|
|
460
533
|
const operator = operatorMatch[0];
|
|
461
534
|
const [leftExpr, rightExpr] = expr.split(operator).map(s => s.trim());
|
|
462
535
|
|
|
463
|
-
|
|
464
|
-
|
|
536
|
+
let controllerName: string;
|
|
537
|
+
let propertyPath: string;
|
|
538
|
+
|
|
539
|
+
if (leftExpr.includes(':')) {
|
|
540
|
+
[controllerName, propertyPath] = leftExpr.split(':');
|
|
541
|
+
} else {
|
|
542
|
+
controllerName = defaultCtrl || '';
|
|
543
|
+
propertyPath = leftExpr;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (!controllerName) return null;
|
|
465
547
|
|
|
548
|
+
const props = propertyPath.split('.');
|
|
466
549
|
return { controllerName, props, operator, rightExpr };
|
|
467
550
|
}
|
|
468
551
|
|
|
469
|
-
const parsed = parseVisibilityExpression(expr);
|
|
552
|
+
const parsed = parseVisibilityExpression(expr, ctrl);
|
|
470
553
|
|
|
471
554
|
if (parsed) {
|
|
472
555
|
const { controllerName, props, operator, rightExpr } = parsed;
|
|
@@ -488,48 +571,97 @@ export class EzBaseComponent {
|
|
|
488
571
|
}
|
|
489
572
|
|
|
490
573
|
if (result) {
|
|
491
|
-
el.
|
|
574
|
+
el.removeAttribute('data-ez-hidden');
|
|
492
575
|
} else {
|
|
493
|
-
el.
|
|
576
|
+
el.setAttribute('data-ez-hidden', '');
|
|
494
577
|
}
|
|
495
578
|
});
|
|
496
579
|
this._effects!.push(stop);
|
|
497
|
-
} else
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
580
|
+
} else {
|
|
581
|
+
// Simple boolean binding: 'Controller:path' or 'path'
|
|
582
|
+
let activeCtrl = ctrl;
|
|
583
|
+
let props: string[];
|
|
584
|
+
|
|
585
|
+
if (expr.includes(':')) {
|
|
586
|
+
const [controllerName, path] = expr.split(':');
|
|
587
|
+
if (!controllerName || !path) {
|
|
588
|
+
console.warn(`[ez] Invalid visible expression format: ${expr}`);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
activeCtrl = controllerName;
|
|
592
|
+
props = path.split('.');
|
|
593
|
+
} else {
|
|
594
|
+
if (!activeCtrl) {
|
|
595
|
+
console.warn(`[ez] No controller specified for visible binding: ${expr}`);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
props = expr.split('.');
|
|
504
599
|
}
|
|
505
600
|
|
|
506
601
|
const stop = effect(() => {
|
|
507
|
-
const result = !!ez.getDeepValue(ez._controllers[
|
|
508
|
-
|
|
602
|
+
const result = !!ez.getDeepValue(ez._controllers[activeCtrl!]?.state, props);
|
|
603
|
+
if (result) {
|
|
604
|
+
el.removeAttribute('data-ez-hidden');
|
|
605
|
+
} else {
|
|
606
|
+
el.setAttribute('data-ez-hidden', '');
|
|
607
|
+
}
|
|
509
608
|
});
|
|
510
609
|
this._effects!.push(stop);
|
|
511
|
-
|
|
512
|
-
} else {
|
|
513
|
-
console.warn(`[ez] Unsupported visibility expression: ${expr}`);
|
|
514
610
|
}
|
|
515
611
|
}
|
|
516
612
|
|
|
517
|
-
private _applyClsBind(el: HTMLElement, bind: BindConfig): void {
|
|
613
|
+
private _applyClsBind(el: HTMLElement, bind: BindConfig, ctrl: string | undefined): void {
|
|
518
614
|
if (!bind.cls) return;
|
|
519
615
|
|
|
520
616
|
let prevClasses: string[] = [];
|
|
521
617
|
const styleModule = this.config._styleModule;
|
|
522
618
|
|
|
523
|
-
|
|
524
|
-
|
|
619
|
+
// If cls is a function, call it reactively
|
|
620
|
+
if (typeof bind.cls === 'function') {
|
|
621
|
+
const stop = effect(() => {
|
|
622
|
+
const next = (bind.cls as () => string)() || '';
|
|
623
|
+
const classes = next.split(' ').filter(Boolean).map(c => {
|
|
624
|
+
if (styleModule && styleModule[c]) {
|
|
625
|
+
return styleModule[c];
|
|
626
|
+
}
|
|
627
|
+
return c;
|
|
628
|
+
});
|
|
525
629
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
630
|
+
if (prevClasses.length) {
|
|
631
|
+
el.classList.remove(...prevClasses);
|
|
632
|
+
}
|
|
633
|
+
if (classes.length) {
|
|
634
|
+
el.classList.add(...classes);
|
|
635
|
+
}
|
|
636
|
+
prevClasses = classes;
|
|
637
|
+
});
|
|
638
|
+
this._effects!.push(stop);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// If cls is a string, treat it as a state path
|
|
643
|
+
const clsPath = bind.cls;
|
|
644
|
+
let props: string[];
|
|
645
|
+
let activeCtrl = ctrl;
|
|
529
646
|
|
|
530
|
-
|
|
531
|
-
|
|
647
|
+
if (clsPath.includes(':')) {
|
|
648
|
+
const [controller, path] = clsPath.split(':');
|
|
649
|
+
if (!controller) {
|
|
650
|
+
console.warn(`[ez] Invalid bind.cls format: ${clsPath}`);
|
|
651
|
+
return;
|
|
532
652
|
}
|
|
653
|
+
activeCtrl = controller;
|
|
654
|
+
props = path.split('.');
|
|
655
|
+
} else {
|
|
656
|
+
props = clsPath.split('.');
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const stop = effect(() => {
|
|
660
|
+
const next = (
|
|
661
|
+
props.length === 1
|
|
662
|
+
? ez._controllers[activeCtrl!]?.state[props[0]]
|
|
663
|
+
: ez.getDeepValue(ez._controllers[activeCtrl!]?.state, props)
|
|
664
|
+
) as string || '';
|
|
533
665
|
|
|
534
666
|
const classes = next.split(' ').filter(Boolean).map(c => {
|
|
535
667
|
if (styleModule && styleModule[c]) {
|
|
@@ -541,88 +673,164 @@ export class EzBaseComponent {
|
|
|
541
673
|
if (prevClasses.length) {
|
|
542
674
|
el.classList.remove(...prevClasses);
|
|
543
675
|
}
|
|
544
|
-
|
|
545
676
|
if (classes.length) {
|
|
546
677
|
el.classList.add(...classes);
|
|
547
678
|
}
|
|
548
|
-
|
|
549
679
|
prevClasses = classes;
|
|
550
680
|
});
|
|
551
681
|
|
|
552
682
|
this._effects!.push(stop);
|
|
553
683
|
}
|
|
554
684
|
|
|
555
|
-
private _applyHtmlBind(el: HTMLElement, bind: BindConfig): void {
|
|
685
|
+
private _applyHtmlBind(el: HTMLElement, bind: BindConfig, ctrl: string | undefined): void {
|
|
556
686
|
if (!bind.html) return;
|
|
557
687
|
|
|
558
|
-
const
|
|
559
|
-
let next = bind.html;
|
|
688
|
+
const shouldSanitize = bind.sanitizeHtml === true;
|
|
560
689
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
690
|
+
// If html is a function, call it reactively
|
|
691
|
+
if (typeof bind.html === 'function') {
|
|
692
|
+
const stop = effect(() => {
|
|
693
|
+
const next = (bind.html as () => string)();
|
|
694
|
+
const content = String(next ?? '');
|
|
695
|
+
el.innerHTML = shouldSanitize ? sanitizeHtml(content) : content;
|
|
696
|
+
});
|
|
697
|
+
this._effects!.push(stop);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// If html is a string, treat it as a state path
|
|
702
|
+
const htmlPath = bind.html;
|
|
703
|
+
let props: string[];
|
|
704
|
+
let activeCtrl = ctrl;
|
|
564
705
|
|
|
565
|
-
|
|
566
|
-
|
|
706
|
+
if (htmlPath.includes(':')) {
|
|
707
|
+
const [controller, path] = htmlPath.split(':');
|
|
708
|
+
if (!controller) {
|
|
709
|
+
console.warn(`[ez] Invalid bind.html format: ${htmlPath}`);
|
|
710
|
+
return;
|
|
567
711
|
}
|
|
712
|
+
activeCtrl = controller;
|
|
713
|
+
props = path.split('.');
|
|
714
|
+
} else {
|
|
715
|
+
props = htmlPath.split('.');
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const stop = effect(() => {
|
|
719
|
+
const next = (
|
|
720
|
+
props.length === 1
|
|
721
|
+
? ez._controllers[activeCtrl!]?.state[props[0]]
|
|
722
|
+
: ez.getDeepValue(ez._controllers[activeCtrl!]?.state, props)
|
|
723
|
+
) as string;
|
|
568
724
|
|
|
569
|
-
|
|
725
|
+
const content = String(next ?? '');
|
|
726
|
+
el.innerHTML = shouldSanitize ? sanitizeHtml(content) : content;
|
|
570
727
|
});
|
|
571
728
|
|
|
572
729
|
this._effects!.push(stop);
|
|
573
730
|
}
|
|
574
731
|
|
|
575
|
-
private _applyStyleBind(el: HTMLElement, bind: BindConfig): void {
|
|
732
|
+
private _applyStyleBind(el: HTMLElement, bind: BindConfig, ctrl: string | undefined): void {
|
|
576
733
|
if (!bind.style) return;
|
|
577
734
|
|
|
578
735
|
let prevStyles: Record<string, string> = {};
|
|
579
736
|
|
|
580
|
-
|
|
581
|
-
|
|
737
|
+
// If style is a function, call it reactively
|
|
738
|
+
if (typeof bind.style === 'function') {
|
|
739
|
+
const stop = effect(() => {
|
|
740
|
+
const next = (bind.style as () => Record<string, string>)() || {};
|
|
582
741
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
742
|
+
for (const key of Object.keys(prevStyles)) {
|
|
743
|
+
if (!(key in next)) {
|
|
744
|
+
(el.style as unknown as Record<string, string>)[key] = '';
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
for (const [key, value] of Object.entries(next)) {
|
|
748
|
+
(el.style as unknown as Record<string, string>)[key] = value;
|
|
749
|
+
}
|
|
750
|
+
prevStyles = { ...next };
|
|
751
|
+
});
|
|
752
|
+
this._effects!.push(stop);
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// If style is a string, treat it as a state path to a style object
|
|
757
|
+
const stylePath = bind.style as unknown as string;
|
|
758
|
+
if (typeof stylePath !== 'string') return;
|
|
586
759
|
|
|
587
|
-
|
|
588
|
-
|
|
760
|
+
let props: string[];
|
|
761
|
+
let activeCtrl = ctrl;
|
|
762
|
+
|
|
763
|
+
if (stylePath.includes(':')) {
|
|
764
|
+
const [controller, path] = stylePath.split(':');
|
|
765
|
+
if (!controller) {
|
|
766
|
+
console.warn(`[ez] Invalid bind.style format: ${stylePath}`);
|
|
767
|
+
return;
|
|
589
768
|
}
|
|
769
|
+
activeCtrl = controller;
|
|
770
|
+
props = path.split('.');
|
|
771
|
+
} else {
|
|
772
|
+
props = stylePath.split('.');
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const stop = effect(() => {
|
|
776
|
+
const next = (
|
|
777
|
+
props.length === 1
|
|
778
|
+
? ez._controllers[activeCtrl!]?.state[props[0]]
|
|
779
|
+
: ez.getDeepValue(ez._controllers[activeCtrl!]?.state, props)
|
|
780
|
+
) as Record<string, string> || {};
|
|
590
781
|
|
|
591
|
-
// Remove previous styles that are not in the new object
|
|
592
782
|
for (const key of Object.keys(prevStyles)) {
|
|
593
783
|
if (!(key in next)) {
|
|
594
784
|
(el.style as unknown as Record<string, string>)[key] = '';
|
|
595
785
|
}
|
|
596
786
|
}
|
|
597
|
-
|
|
598
|
-
// Apply new styles (supports camelCase like 'backgroundColor')
|
|
599
787
|
for (const [key, value] of Object.entries(next)) {
|
|
600
788
|
(el.style as unknown as Record<string, string>)[key] = value;
|
|
601
789
|
}
|
|
602
|
-
|
|
603
790
|
prevStyles = { ...next };
|
|
604
791
|
});
|
|
605
792
|
|
|
606
793
|
this._effects!.push(stop);
|
|
607
794
|
}
|
|
608
795
|
|
|
609
|
-
private _applyTextBind(el: HTMLElement, bind: BindConfig): void {
|
|
796
|
+
private _applyTextBind(el: HTMLElement, bind: BindConfig, ctrl: string | undefined): void {
|
|
610
797
|
if (!bind.text) return;
|
|
611
798
|
|
|
612
|
-
|
|
613
|
-
|
|
799
|
+
// If text is a function, call it reactively
|
|
800
|
+
if (typeof bind.text === 'function') {
|
|
801
|
+
const stop = effect(() => {
|
|
802
|
+
const next = (bind.text as () => string)();
|
|
803
|
+
el.textContent = String(next ?? '');
|
|
804
|
+
});
|
|
805
|
+
this._effects!.push(stop);
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
614
808
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
809
|
+
// If text is a string, treat it as a state path (like 'prop' or 'Controller:prop')
|
|
810
|
+
const textPath = bind.text;
|
|
811
|
+
let props: string[] | undefined;
|
|
812
|
+
let activeCtrl = ctrl;
|
|
618
813
|
|
|
619
|
-
|
|
620
|
-
|
|
814
|
+
if (textPath.includes(':')) {
|
|
815
|
+
const [controller, path] = textPath.split(':');
|
|
816
|
+
props = path?.split('.');
|
|
817
|
+
if (!controller) {
|
|
818
|
+
console.warn(`[ez] Invalid bind.text format: ${textPath}`);
|
|
819
|
+
return;
|
|
621
820
|
}
|
|
821
|
+
activeCtrl = controller;
|
|
822
|
+
} else {
|
|
823
|
+
props = textPath.split('.');
|
|
824
|
+
}
|
|
622
825
|
|
|
623
|
-
|
|
624
|
-
|
|
826
|
+
const stop = effect(() => {
|
|
827
|
+
const next =
|
|
828
|
+
props?.length === 1
|
|
829
|
+
? ez._controllers[activeCtrl!]?.state[props[0]]
|
|
830
|
+
: ez.getDeepValue(ez._controllers[activeCtrl!]?.state, props!);
|
|
625
831
|
|
|
832
|
+
el.textContent = String(next ?? '');
|
|
833
|
+
});
|
|
626
834
|
this._effects!.push(stop);
|
|
627
835
|
}
|
|
628
836
|
|
|
@@ -674,89 +882,132 @@ export class EzBaseComponent {
|
|
|
674
882
|
}
|
|
675
883
|
|
|
676
884
|
/**
|
|
677
|
-
* Apply tooltip if configured
|
|
885
|
+
* Apply tooltip if configured.
|
|
886
|
+
* Tooltip is created lazily on first hover to avoid DOM pollution.
|
|
887
|
+
* Supports text tooltips: tooltip: "Hello" or tooltip: { text: "Hello" }
|
|
888
|
+
* Supports component tooltips: tooltip: { component: { eztype: 'MyCard', props: {...} } }
|
|
678
889
|
*/
|
|
679
890
|
applyTooltip(el: HTMLElement): void {
|
|
680
891
|
const tipConfig = this.config.tooltip;
|
|
681
892
|
if (!tipConfig) return;
|
|
682
893
|
|
|
683
|
-
const
|
|
894
|
+
const isString = typeof tipConfig === 'string';
|
|
895
|
+
const text = isString ? tipConfig : tipConfig.text;
|
|
896
|
+
const componentConfig = isString ? null : tipConfig.component;
|
|
684
897
|
const position: TooltipPosition = (typeof tipConfig === 'object' ? tipConfig.position : null) || 'top';
|
|
685
898
|
const variant: TooltipVariant = (typeof tipConfig === 'object' ? tipConfig.variant : null) || 'dark';
|
|
686
899
|
const delay = (typeof tipConfig === 'object' ? tipConfig.delay : null) ?? 200;
|
|
687
900
|
|
|
688
|
-
if (!text) return;
|
|
901
|
+
if (!text && !componentConfig) return;
|
|
689
902
|
|
|
690
|
-
const
|
|
691
|
-
tooltip
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
903
|
+
const isComponentTooltip = !!componentConfig;
|
|
904
|
+
let tooltip: HTMLDivElement | null = null;
|
|
905
|
+
let showTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
906
|
+
let hideTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
907
|
+
let componentRendered = false;
|
|
908
|
+
|
|
909
|
+
const createTooltip = (): HTMLDivElement => {
|
|
910
|
+
const tip = document.createElement('div');
|
|
911
|
+
tip.className = tooltipCls('tooltip', isComponentTooltip ? 'component' : variant);
|
|
912
|
+
tip.style.opacity = '0';
|
|
913
|
+
tip.style.visibility = 'hidden';
|
|
914
|
+
|
|
915
|
+
if (text && !componentConfig) {
|
|
916
|
+
tip.textContent = text;
|
|
917
|
+
const arrow = document.createElement('div');
|
|
918
|
+
arrow.className = tooltipCls('arrow');
|
|
919
|
+
tip.appendChild(arrow);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
document.body.appendChild(tip);
|
|
923
|
+
return tip;
|
|
924
|
+
};
|
|
695
925
|
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
926
|
+
const positionTooltip = (): void => {
|
|
927
|
+
if (!tooltip) return;
|
|
928
|
+
const rect = el.getBoundingClientRect();
|
|
929
|
+
const tooltipRect = tooltip.getBoundingClientRect();
|
|
930
|
+
|
|
931
|
+
let top: number;
|
|
932
|
+
let left: number;
|
|
933
|
+
|
|
934
|
+
switch (position) {
|
|
935
|
+
case 'top':
|
|
936
|
+
top = rect.top - tooltipRect.height - 8;
|
|
937
|
+
left = rect.left + (rect.width - tooltipRect.width) / 2;
|
|
938
|
+
break;
|
|
939
|
+
case 'bottom':
|
|
940
|
+
top = rect.bottom + 8;
|
|
941
|
+
left = rect.left + (rect.width - tooltipRect.width) / 2;
|
|
942
|
+
break;
|
|
943
|
+
case 'left':
|
|
944
|
+
top = rect.top + (rect.height - tooltipRect.height) / 2;
|
|
945
|
+
left = rect.left - tooltipRect.width - 8;
|
|
946
|
+
break;
|
|
947
|
+
case 'right':
|
|
948
|
+
top = rect.top + (rect.height - tooltipRect.height) / 2;
|
|
949
|
+
left = rect.right + 8;
|
|
950
|
+
break;
|
|
951
|
+
default:
|
|
952
|
+
top = rect.top - tooltipRect.height - 8;
|
|
953
|
+
left = rect.left + (rect.width - tooltipRect.width) / 2;
|
|
954
|
+
}
|
|
699
955
|
|
|
700
|
-
|
|
956
|
+
const padding = 8;
|
|
957
|
+
if (left < padding) left = padding;
|
|
958
|
+
if (left + tooltipRect.width > window.innerWidth - padding) {
|
|
959
|
+
left = window.innerWidth - tooltipRect.width - padding;
|
|
960
|
+
}
|
|
961
|
+
if (top < padding) top = padding;
|
|
962
|
+
if (top + tooltipRect.height > window.innerHeight - padding) {
|
|
963
|
+
top = window.innerHeight - tooltipRect.height - padding;
|
|
964
|
+
}
|
|
701
965
|
|
|
702
|
-
|
|
703
|
-
|
|
966
|
+
tooltip.style.top = `${top + window.scrollY}px`;
|
|
967
|
+
tooltip.style.left = `${left + window.scrollX}px`;
|
|
968
|
+
tooltip.className = tooltipCls('tooltip', isComponentTooltip ? 'component' : variant, position);
|
|
969
|
+
};
|
|
704
970
|
|
|
705
971
|
const showTooltip = (): void => {
|
|
706
972
|
if (hideTimeout) clearTimeout(hideTimeout);
|
|
707
|
-
showTimeout = setTimeout(() => {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
let left: number;
|
|
713
|
-
|
|
714
|
-
switch (position) {
|
|
715
|
-
case 'top':
|
|
716
|
-
top = rect.top - tooltipRect.height - 8;
|
|
717
|
-
left = rect.left + (rect.width - tooltipRect.width) / 2;
|
|
718
|
-
break;
|
|
719
|
-
case 'bottom':
|
|
720
|
-
top = rect.bottom + 8;
|
|
721
|
-
left = rect.left + (rect.width - tooltipRect.width) / 2;
|
|
722
|
-
break;
|
|
723
|
-
case 'left':
|
|
724
|
-
top = rect.top + (rect.height - tooltipRect.height) / 2;
|
|
725
|
-
left = rect.left - tooltipRect.width - 8;
|
|
726
|
-
break;
|
|
727
|
-
case 'right':
|
|
728
|
-
top = rect.top + (rect.height - tooltipRect.height) / 2;
|
|
729
|
-
left = rect.right + 8;
|
|
730
|
-
break;
|
|
731
|
-
default:
|
|
732
|
-
top = rect.top - tooltipRect.height - 8;
|
|
733
|
-
left = rect.left + (rect.width - tooltipRect.width) / 2;
|
|
973
|
+
showTimeout = setTimeout(async () => {
|
|
974
|
+
// Lazy create tooltip on first hover
|
|
975
|
+
if (!tooltip) {
|
|
976
|
+
tooltip = createTooltip();
|
|
977
|
+
this._tooltip = tooltip;
|
|
734
978
|
}
|
|
735
979
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
980
|
+
if (componentConfig && !componentRendered) {
|
|
981
|
+
const componentEl = await ez._createElement(componentConfig);
|
|
982
|
+
tooltip.appendChild(componentEl);
|
|
983
|
+
componentRendered = true;
|
|
984
|
+
requestAnimationFrame(() => {
|
|
985
|
+
positionTooltip();
|
|
986
|
+
if (tooltip) {
|
|
987
|
+
tooltip.style.opacity = '1';
|
|
988
|
+
tooltip.style.visibility = 'visible';
|
|
989
|
+
}
|
|
990
|
+
});
|
|
991
|
+
} else {
|
|
992
|
+
positionTooltip();
|
|
993
|
+
if (tooltip) {
|
|
994
|
+
tooltip.style.opacity = '1';
|
|
995
|
+
tooltip.style.visibility = 'visible';
|
|
996
|
+
}
|
|
744
997
|
}
|
|
745
|
-
|
|
746
|
-
tooltip.style.top = `${top + window.scrollY}px`;
|
|
747
|
-
tooltip.style.left = `${left + window.scrollX}px`;
|
|
748
|
-
tooltip.className = tooltipCls('tooltip', variant, position);
|
|
749
|
-
tooltip.style.opacity = '1';
|
|
750
|
-
tooltip.style.visibility = 'visible';
|
|
751
998
|
}, delay);
|
|
752
999
|
};
|
|
753
1000
|
|
|
754
1001
|
const hideTooltip = (): void => {
|
|
755
1002
|
if (showTimeout) clearTimeout(showTimeout);
|
|
756
1003
|
hideTimeout = setTimeout(() => {
|
|
757
|
-
tooltip
|
|
758
|
-
|
|
759
|
-
|
|
1004
|
+
if (tooltip?.parentNode) {
|
|
1005
|
+
tooltip.parentNode.removeChild(tooltip);
|
|
1006
|
+
tooltip = null;
|
|
1007
|
+
this._tooltip = undefined;
|
|
1008
|
+
componentRendered = false;
|
|
1009
|
+
}
|
|
1010
|
+
}, 150); // Small delay for smooth transition
|
|
760
1011
|
};
|
|
761
1012
|
|
|
762
1013
|
el.addEventListener('mouseenter', showTooltip);
|
|
@@ -764,7 +1015,6 @@ export class EzBaseComponent {
|
|
|
764
1015
|
el.addEventListener('focus', showTooltip);
|
|
765
1016
|
el.addEventListener('blur', hideTooltip);
|
|
766
1017
|
|
|
767
|
-
this._tooltip = tooltip;
|
|
768
1018
|
this._tooltipCleanup = () => {
|
|
769
1019
|
if (showTimeout) clearTimeout(showTimeout);
|
|
770
1020
|
if (hideTimeout) clearTimeout(hideTimeout);
|
|
@@ -772,12 +1022,57 @@ export class EzBaseComponent {
|
|
|
772
1022
|
el.removeEventListener('mouseleave', hideTooltip);
|
|
773
1023
|
el.removeEventListener('focus', showTooltip);
|
|
774
1024
|
el.removeEventListener('blur', hideTooltip);
|
|
775
|
-
if (tooltip
|
|
1025
|
+
if (tooltip?.parentNode) {
|
|
776
1026
|
tooltip.parentNode.removeChild(tooltip);
|
|
777
1027
|
}
|
|
1028
|
+
tooltip = null;
|
|
778
1029
|
};
|
|
779
1030
|
}
|
|
780
1031
|
|
|
1032
|
+
/**
|
|
1033
|
+
* Apply showOnHover behavior.
|
|
1034
|
+
* Element is hidden by default and revealed when parent is hovered.
|
|
1035
|
+
* Must be called AFTER element is added to DOM (needs parentElement).
|
|
1036
|
+
*/
|
|
1037
|
+
applyShowOnHover(el: HTMLElement): void {
|
|
1038
|
+
if (!this.config.showOnHover) return;
|
|
1039
|
+
|
|
1040
|
+
// Set initial hidden state
|
|
1041
|
+
el.style.opacity = '0';
|
|
1042
|
+
el.style.visibility = 'hidden';
|
|
1043
|
+
el.style.transition = 'opacity 0.15s ease, visibility 0.15s ease';
|
|
1044
|
+
|
|
1045
|
+
// Defer to next frame to ensure element is in DOM
|
|
1046
|
+
requestAnimationFrame(() => {
|
|
1047
|
+
const parent = el.parentElement;
|
|
1048
|
+
if (!parent) return;
|
|
1049
|
+
|
|
1050
|
+
// Ensure parent has position for absolute children if needed
|
|
1051
|
+
const parentPosition = getComputedStyle(parent).position;
|
|
1052
|
+
if (parentPosition === 'static') {
|
|
1053
|
+
parent.style.position = 'relative';
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
const show = () => {
|
|
1057
|
+
el.style.opacity = '1';
|
|
1058
|
+
el.style.visibility = 'visible';
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
const hide = () => {
|
|
1062
|
+
el.style.opacity = '0';
|
|
1063
|
+
el.style.visibility = 'hidden';
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
parent.addEventListener('mouseenter', show);
|
|
1067
|
+
parent.addEventListener('mouseleave', hide);
|
|
1068
|
+
|
|
1069
|
+
this._showOnHoverCleanup = () => {
|
|
1070
|
+
parent.removeEventListener('mouseenter', show);
|
|
1071
|
+
parent.removeEventListener('mouseleave', hide);
|
|
1072
|
+
};
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
|
|
781
1076
|
destroy(): void {
|
|
782
1077
|
// Destroy children first
|
|
783
1078
|
if (this._children) {
|
|
@@ -803,6 +1098,15 @@ export class EzBaseComponent {
|
|
|
803
1098
|
this._tooltipCleanup();
|
|
804
1099
|
}
|
|
805
1100
|
|
|
1101
|
+
if (this._showOnHoverCleanup) {
|
|
1102
|
+
this._showOnHoverCleanup();
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
if (this._activateHandler && this.el) {
|
|
1106
|
+
this.el.removeEventListener('ez-activate', this._activateHandler);
|
|
1107
|
+
this._activateHandler = undefined;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
806
1110
|
if (this.el) {
|
|
807
1111
|
(this.el as HTMLElement & { __ezInstance: unknown }).__ezInstance = null;
|
|
808
1112
|
this.el = null;
|