@dooboostore/simple-web-component 1.0.48 → 1.0.49
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/README.md +117 -19
- package/dist/cjs/config/config.js +1 -1
- package/dist/cjs/config/config.js.map +2 -2
- package/dist/cjs/decorators/addEventListener.js +387 -29
- package/dist/cjs/decorators/addEventListener.js.map +3 -3
- package/dist/cjs/decorators/applyAttribute.js +25 -0
- package/dist/cjs/decorators/applyAttribute.js.map +2 -2
- package/dist/cjs/decorators/applyClass.js +49 -18
- package/dist/cjs/decorators/applyClass.js.map +3 -3
- package/dist/cjs/decorators/applyNode.js +67 -1
- package/dist/cjs/decorators/applyNode.js.map +3 -3
- package/dist/cjs/decorators/applyProperty.js +101 -3
- package/dist/cjs/decorators/applyProperty.js.map +2 -2
- package/dist/cjs/decorators/applyStyle.js +36 -5
- package/dist/cjs/decorators/applyStyle.js.map +2 -2
- package/dist/cjs/decorators/changedAttribute.js +39 -0
- package/dist/cjs/decorators/changedAttribute.js.map +2 -2
- package/dist/cjs/decorators/elementDefine.js +237 -437
- package/dist/cjs/decorators/elementDefine.js.map +3 -3
- package/dist/cjs/decorators/emitCustomEvent.js +55 -0
- package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
- package/dist/cjs/decorators/index.js +3 -1
- package/dist/cjs/decorators/index.js.map +2 -2
- package/dist/cjs/decorators/intersectionObserver.js +209 -0
- package/dist/cjs/decorators/intersectionObserver.js.map +7 -0
- package/dist/cjs/decorators/mutationObserver.js +140 -0
- package/dist/cjs/decorators/mutationObserver.js.map +3 -3
- package/dist/cjs/decorators/query.js +117 -102
- package/dist/cjs/decorators/query.js.map +2 -2
- package/dist/cjs/decorators/resizeObserver.js +87 -0
- package/dist/cjs/decorators/resizeObserver.js.map +2 -2
- package/dist/cjs/decorators/setInterval.js +82 -0
- package/dist/cjs/decorators/setInterval.js.map +7 -0
- package/dist/cjs/decorators/setTimeout.js +82 -0
- package/dist/cjs/decorators/setTimeout.js.map +7 -0
- package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js +9 -0
- package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js.map +2 -2
- package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js +9 -0
- package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +2 -2
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/esm/config/config.js +1 -1
- package/dist/esm/config/config.js.map +2 -2
- package/dist/esm/decorators/addEventListener.js +388 -29
- package/dist/esm/decorators/addEventListener.js.map +3 -3
- package/dist/esm/decorators/applyAttribute.js +25 -0
- package/dist/esm/decorators/applyAttribute.js.map +2 -2
- package/dist/esm/decorators/applyClass.js +49 -18
- package/dist/esm/decorators/applyClass.js.map +3 -3
- package/dist/esm/decorators/applyNode.js +67 -1
- package/dist/esm/decorators/applyNode.js.map +3 -3
- package/dist/esm/decorators/applyProperty.js +101 -3
- package/dist/esm/decorators/applyProperty.js.map +2 -2
- package/dist/esm/decorators/applyStyle.js +36 -5
- package/dist/esm/decorators/applyStyle.js.map +2 -2
- package/dist/esm/decorators/changedAttribute.js +40 -0
- package/dist/esm/decorators/changedAttribute.js.map +2 -2
- package/dist/esm/decorators/elementDefine.js +244 -444
- package/dist/esm/decorators/elementDefine.js.map +3 -3
- package/dist/esm/decorators/emitCustomEvent.js +55 -0
- package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
- package/dist/esm/decorators/index.js +3 -1
- package/dist/esm/decorators/index.js.map +2 -2
- package/dist/esm/decorators/intersectionObserver.js +191 -0
- package/dist/esm/decorators/intersectionObserver.js.map +7 -0
- package/dist/esm/decorators/mutationObserver.js +141 -0
- package/dist/esm/decorators/mutationObserver.js.map +3 -3
- package/dist/esm/decorators/query.js +117 -102
- package/dist/esm/decorators/query.js.map +2 -2
- package/dist/esm/decorators/resizeObserver.js +88 -0
- package/dist/esm/decorators/resizeObserver.js.map +2 -2
- package/dist/esm/decorators/setInterval.js +64 -0
- package/dist/esm/decorators/setInterval.js.map +7 -0
- package/dist/esm/decorators/setTimeout.js +64 -0
- package/dist/esm/decorators/setTimeout.js.map +7 -0
- package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js +9 -0
- package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js.map +2 -2
- package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js +9 -0
- package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +2 -2
- package/dist/esm/types/index.js.map +1 -1
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +2565 -1474
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
- package/dist/types/config/config.d.ts.map +1 -1
- package/dist/types/decorators/addEventListener.d.ts +84 -33
- package/dist/types/decorators/addEventListener.d.ts.map +1 -1
- package/dist/types/decorators/applyAttribute.d.ts +22 -34
- package/dist/types/decorators/applyAttribute.d.ts.map +1 -1
- package/dist/types/decorators/applyClass.d.ts +34 -34
- package/dist/types/decorators/applyClass.d.ts.map +1 -1
- package/dist/types/decorators/applyNode.d.ts +30 -20
- package/dist/types/decorators/applyNode.d.ts.map +1 -1
- package/dist/types/decorators/applyProperty.d.ts +57 -34
- package/dist/types/decorators/applyProperty.d.ts.map +1 -1
- package/dist/types/decorators/applyStyle.d.ts +25 -12
- package/dist/types/decorators/applyStyle.d.ts.map +1 -1
- package/dist/types/decorators/changedAttribute.d.ts +11 -0
- package/dist/types/decorators/changedAttribute.d.ts.map +1 -1
- package/dist/types/decorators/elementDefine.d.ts +0 -14
- package/dist/types/decorators/elementDefine.d.ts.map +1 -1
- package/dist/types/decorators/emitCustomEvent.d.ts +28 -4
- package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
- package/dist/types/decorators/index.d.ts +3 -1
- package/dist/types/decorators/index.d.ts.map +1 -1
- package/dist/types/decorators/intersectionObserver.d.ts +49 -0
- package/dist/types/decorators/intersectionObserver.d.ts.map +1 -0
- package/dist/types/decorators/mutationObserver.d.ts +35 -25
- package/dist/types/decorators/mutationObserver.d.ts.map +1 -1
- package/dist/types/decorators/query.d.ts +57 -14
- package/dist/types/decorators/query.d.ts.map +1 -1
- package/dist/types/decorators/resizeObserver.d.ts +34 -23
- package/dist/types/decorators/resizeObserver.d.ts.map +1 -1
- package/dist/types/decorators/setInterval.d.ts +29 -0
- package/dist/types/decorators/setInterval.d.ts.map +1 -0
- package/dist/types/decorators/setTimeout.d.ts +29 -0
- package/dist/types/decorators/setTimeout.d.ts.map +1 -0
- package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts +7 -1
- package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts.map +1 -1
- package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts +6 -0
- package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts.map +1 -1
- package/dist/types/types/index.d.ts +104 -0
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +2565 -1474
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
- package/package.json +7 -5
- package/src/config/config.ts +1 -1
- package/src/decorators/addEventListener.ts +394 -34
- package/src/decorators/applyAttribute.ts +50 -36
- package/src/decorators/applyClass.ts +76 -26
- package/src/decorators/applyNode.ts +114 -36
- package/src/decorators/applyProperty.ts +152 -40
- package/src/decorators/applyStyle.ts +64 -17
- package/src/decorators/changedAttribute.ts +46 -0
- package/src/decorators/elementDefine.ts +281 -580
- package/src/decorators/emitCustomEvent.ts +73 -5
- package/src/decorators/index.ts +3 -1
- package/src/decorators/intersectionObserver.ts +259 -0
- package/src/decorators/mutationObserver.ts +215 -39
- package/src/decorators/query.ts +168 -126
- package/src/decorators/resizeObserver.ts +152 -46
- package/src/decorators/setInterval.ts +80 -0
- package/src/decorators/setTimeout.ts +80 -0
- package/src/decorators/subscribeSwcAppMessageWhileConnected.ts +17 -0
- package/src/decorators/subscribeSwcAppRouteChangeWhileConnected.ts +19 -0
- package/src/types/index.ts +141 -0
- package/dist/cjs/decorators/queryAll.js +0 -181
- package/dist/cjs/decorators/queryAll.js.map +0 -7
- package/dist/esm/decorators/queryAll.js +0 -162
- package/dist/esm/decorators/queryAll.js.map +0 -7
- package/dist/types/decorators/queryAll.d.ts +0 -22
- package/dist/types/decorators/queryAll.d.ts.map +0 -1
- package/src/decorators/queryAll.ts +0 -186
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dooboostore/simple-web-component",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.49",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -67,10 +67,7 @@
|
|
|
67
67
|
},
|
|
68
68
|
"author": "dooboostore <dooboostore@gmail.com> (https://github.com/dooboostore)",
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"reflect-metadata": "^0.2.2"
|
|
71
|
-
"@dooboostore/core": "1.0.32",
|
|
72
|
-
"@dooboostore/core-web": "1.0.13",
|
|
73
|
-
"@dooboostore/simple-boot": "1.0.57"
|
|
70
|
+
"reflect-metadata": "^0.2.2"
|
|
74
71
|
},
|
|
75
72
|
"devDependencies": {
|
|
76
73
|
"@babel/core": "^7.24.0",
|
|
@@ -96,6 +93,11 @@
|
|
|
96
93
|
"webpack-cli": "^5.1.4",
|
|
97
94
|
"webpack-dev-server": "^5.2.0"
|
|
98
95
|
},
|
|
96
|
+
"dependencies": {
|
|
97
|
+
"@dooboostore/core-web": "1.0.13",
|
|
98
|
+
"@dooboostore/core": "1.0.32",
|
|
99
|
+
"@dooboostore/simple-boot": "1.0.57"
|
|
100
|
+
},
|
|
99
101
|
"scripts": {
|
|
100
102
|
"clean": "rm -rf dist",
|
|
101
103
|
"build:esm": "node build.mjs esm",
|
package/src/config/config.ts
CHANGED
|
@@ -58,6 +58,6 @@ export const HTML_TAG_ENTRIES: [string, string][] = [
|
|
|
58
58
|
['HTMLBodyElement', 'body']
|
|
59
59
|
];
|
|
60
60
|
|
|
61
|
-
export const DOM_EVENT_NAMES = ['click', 'dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'contextmenu', 'keydown', 'keypress', 'keyup', 'blur', 'change', 'focus', 'focusin', 'focusout', 'input', 'invalid', 'reset', 'search', 'select', 'submit', 'drag', 'dragend', 'dragenter', 'dragleave', 'dragover', 'dragstart', 'drop', 'copy', 'cut', 'paste', 'abort', 'canplay', 'canplaythrough', 'durationchange', 'emptied', 'ended', 'error', 'loadeddata', 'loadedmetadata', 'loadstart', 'pause', 'play', 'playing', 'progress', 'ratechange', 'seeked', 'seeking', 'stalled', 'suspend', 'timeupdate', 'volumechange', 'waiting', 'scroll', 'resize', 'load', '
|
|
61
|
+
export const DOM_EVENT_NAMES = ['click', 'dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'contextmenu', 'keydown', 'keypress', 'keyup', 'blur', 'change', 'focus', 'focusin', 'focusout', 'input', 'invalid', 'reset', 'search', 'select', 'submit', 'drag', 'dragend', 'dragenter', 'dragleave', 'dragover', 'dragstart', 'drop', 'copy', 'cut', 'paste', 'abort', 'canplay', 'canplaythrough', 'durationchange', 'emptied', 'ended', 'error', 'loadeddata', 'loadedmetadata', 'loadstart', 'pause', 'play', 'playing', 'progress', 'ratechange', 'seeked', 'seeking', 'stalled', 'suspend', 'timeupdate', 'volumechange', 'waiting', 'scroll', 'resize', 'load', 'hashchange', 'connected'];
|
|
62
62
|
|
|
63
63
|
export const BROWSER_GLOBALS = ['window', 'document', 'Node', 'Element', 'HTMLElement', 'DocumentFragment', 'CustomEvent', 'customElements', 'NodeFilter', ...HTML_TAG_ENTRIES.map(([className]) => className)];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReflectUtils } from '@dooboostore/core';
|
|
2
|
-
import { SpecialSelector, SwcQueryOptions, HelperHostSet } from '../types';
|
|
2
|
+
import { SpecialSelector, SwcQueryOptions, HelperHostSet, SwcFnSelector, SwcSelector } from '../types';
|
|
3
3
|
|
|
4
4
|
export interface AddEventListenerBaseOptions<TEvent extends Event = Event> extends EventListenerOptions {
|
|
5
5
|
capture?: boolean;
|
|
@@ -9,51 +9,65 @@ export interface AddEventListenerBaseOptions<TEvent extends Event = Event> exten
|
|
|
9
9
|
stopImmediatePropagation?: boolean;
|
|
10
10
|
preventDefault?: boolean;
|
|
11
11
|
// removeOnDisconnected?: boolean;
|
|
12
|
-
delegate
|
|
12
|
+
// delegate는 문자열 셀렉터 전용 → AddEventListenerQueryOptions에만 존재
|
|
13
13
|
filter?: (target: Event | CustomEvent, meta:{currentThis: any, helper: HelperHostSet}) => boolean;
|
|
14
14
|
// 리스너 제거(disconnected 또는 unmount) 시 호출되는 콜백. 첫 번째 인자는 바인딩된 타겟 element, 두 번째는 이 옵션이 속한 전체 옵션 객체(Base + SwcQuery + delegate).
|
|
15
|
-
removeListener?: (target: Element, optionValue:
|
|
15
|
+
removeListener?: (target: Element, optionValue: AddEventListenerQueryOptions<TEvent>) => void;
|
|
16
16
|
// RxJS operator options
|
|
17
17
|
debounceTime?: number;
|
|
18
18
|
throttleTime?: number;
|
|
19
19
|
distinctUntilChanged?: boolean | ((prev: TEvent, curr: TEvent) => boolean);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export type
|
|
22
|
+
export type EventListenerFnSelector = SwcFnSelector;
|
|
23
|
+
export type EventListenerSelector = SwcSelector;
|
|
23
24
|
|
|
24
25
|
export interface AddEventListenerMetadata<TEvent extends Event = Event> {
|
|
25
26
|
propertyKey: string | symbol;
|
|
26
27
|
selector: EventListenerSelector;
|
|
27
28
|
type: string;
|
|
28
|
-
options:
|
|
29
|
+
options: AddEventListenerQueryOptions<TEvent>;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export const ADD_EVENT_LISTENER_METADATA_KEY = Symbol.for('simple-web-component:add-event-listener');
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
export
|
|
34
|
+
// root + delegate 허용 — 문자열 셀렉터는 컴포넌트 DOM 트리 안에서 탐색/델리게이션하므로 의미 있음
|
|
35
|
+
export type AddEventListenerQueryOptions<TEvent extends Event = Event> = AddEventListenerBaseOptions<TEvent> & SwcQueryOptions & { delegate?: boolean | 'this' | 'mutation' };
|
|
36
|
+
// root·delegate 비허용 — 함수 셀렉터는 이미 요소를 직접 반환하므로 root·delegate가 무의미함
|
|
37
|
+
export type AddEventListenerNonQueryOptions<TEvent extends Event = Event> = AddEventListenerBaseOptions<TEvent>;
|
|
38
|
+
|
|
39
|
+
// 셀렉터 종류에 따라 옵션 타입을 분기
|
|
40
|
+
export type AddEventListenerOptionsOf<S extends EventListenerSelector, TEvent extends Event = Event> =
|
|
41
|
+
S extends string ? AddEventListenerQueryOptions<TEvent> : AddEventListenerNonQueryOptions<TEvent>;
|
|
42
|
+
|
|
43
|
+
export function addEventListener<TEvent extends Event = Event>(target: SpecialSelector, type: string, options?: AddEventListenerQueryOptions<TEvent>): MethodDecorator;
|
|
44
|
+
export function addEventListener<TEvent extends Event = Event>(selector: string, type: string, options?: AddEventListenerQueryOptions<TEvent>): MethodDecorator;
|
|
45
|
+
export function addEventListener<TEvent extends Event = Event>(selector: string, type: string[], options?: AddEventListenerQueryOptions<TEvent>): MethodDecorator;
|
|
46
|
+
export function addEventListener<TEvent extends Event = Event>(selector: EventListenerFnSelector, type: string, options?: AddEventListenerNonQueryOptions<TEvent>): MethodDecorator;
|
|
35
47
|
/**
|
|
36
48
|
* @addEventListener(type, options?) — 셀렉터 생략 시 $this(컴포넌트 자신)로 바인딩
|
|
37
49
|
*/
|
|
38
|
-
export function addEventListener<TEvent extends Event = Event>(type: string, options?:
|
|
50
|
+
export function addEventListener<TEvent extends Event = Event>(type: string, options?: AddEventListenerQueryOptions<TEvent>): MethodDecorator;
|
|
51
|
+
export function addEventListener<TEvent extends Event = Event>(type: string[], options?: AddEventListenerQueryOptions<TEvent>): MethodDecorator;
|
|
39
52
|
/**
|
|
40
53
|
* @addEventListener decorator to bind events to elements.
|
|
41
54
|
*/
|
|
42
|
-
export function addEventListener<TEvent extends Event = Event>(selectorOrType: EventListenerSelector | string, typeOrOptions?: string |
|
|
55
|
+
export function addEventListener<TEvent extends Event = Event>(selectorOrType: EventListenerSelector | string | string[], typeOrOptions?: string | string[] | AddEventListenerQueryOptions<TEvent>, maybeOptions?: AddEventListenerQueryOptions<TEvent>): MethodDecorator {
|
|
43
56
|
return (targetObj: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
|
44
57
|
let selector: EventListenerSelector;
|
|
45
|
-
let
|
|
58
|
+
let types: string[];
|
|
46
59
|
let opts: any = {};
|
|
47
60
|
|
|
48
|
-
if (typeof typeOrOptions === 'string') {
|
|
49
|
-
// (selector, type[, options]) form — 기존 호환
|
|
61
|
+
if (typeof typeOrOptions === 'string' || Array.isArray(typeOrOptions)) {
|
|
62
|
+
// (selector, type|[types][, options]) form — 기존 호환
|
|
50
63
|
selector = selectorOrType as EventListenerSelector;
|
|
51
|
-
|
|
64
|
+
types = Array.isArray(typeOrOptions) ? typeOrOptions : [typeOrOptions];
|
|
52
65
|
opts = maybeOptions ?? {};
|
|
53
66
|
} else {
|
|
54
|
-
// (type[, options]) form — selector 기본값 $this
|
|
67
|
+
// (type|[types][, options]) form — selector 기본값 $this
|
|
55
68
|
selector = '$this';
|
|
56
|
-
|
|
69
|
+
if (Array.isArray(selectorOrType)) types = selectorOrType;
|
|
70
|
+
else types = [selectorOrType as string];
|
|
57
71
|
opts = typeOrOptions ?? {};
|
|
58
72
|
}
|
|
59
73
|
|
|
@@ -65,69 +79,89 @@ export function addEventListener<TEvent extends Event = Event>(selectorOrType: E
|
|
|
65
79
|
ReflectUtils.defineMetadata(ADD_EVENT_LISTENER_METADATA_KEY, listeners, constructor);
|
|
66
80
|
}
|
|
67
81
|
|
|
68
|
-
listeners.push({ propertyKey, selector, type, options: opts });
|
|
82
|
+
for (const type of types) listeners.push({ propertyKey, selector, type, options: opts });
|
|
69
83
|
};
|
|
70
84
|
}
|
|
71
85
|
|
|
72
86
|
|
|
73
|
-
export function
|
|
87
|
+
export function addEventListenerDelegateLight<TEvent extends Event = Event>(selector: string, type: string, options?: Omit<AddEventListenerQueryOptions<TEvent>, 'delegate'>): MethodDecorator {
|
|
74
88
|
return addEventListener<TEvent>(selector, type, {...options??{}, root:'light', delegate: true});
|
|
75
89
|
}
|
|
76
90
|
|
|
77
|
-
export function
|
|
91
|
+
export function addEventListenerDelegateShadow<TEvent extends Event = Event>(selector: string, type: string, options?: Omit<AddEventListenerQueryOptions<TEvent>, 'delegate'>): MethodDecorator {
|
|
78
92
|
return addEventListener<TEvent>(selector, type, {...options??{}, root:'shadow', delegate: true});
|
|
79
93
|
}
|
|
80
|
-
export function
|
|
94
|
+
export function addEventListenerDelegateAll<TEvent extends Event = Event>(selector: string, type: string, options?: Omit<AddEventListenerQueryOptions<TEvent>, 'delegate'>): MethodDecorator {
|
|
81
95
|
return addEventListener<TEvent>(selector, type, {...options??{}, root:'all', delegate: true});
|
|
82
96
|
}
|
|
83
|
-
export function addEventListenerDelegate<TEvent extends Event = Event>(selector:
|
|
97
|
+
export function addEventListenerDelegate<TEvent extends Event = Event>(selector: string, type: string, options?: Omit<AddEventListenerQueryOptions<TEvent>, 'delegate'>): MethodDecorator {
|
|
84
98
|
return addEventListener<TEvent>(selector, type, {...options??{}, root:'auto', delegate: true});
|
|
85
99
|
}
|
|
86
100
|
|
|
87
|
-
|
|
101
|
+
export function addEventListenerMutationLight<TEvent extends Event = Event>(selector: string, type: string, options?: Omit<AddEventListenerQueryOptions<TEvent>, 'delegate'>): MethodDecorator {
|
|
102
|
+
return addEventListener<TEvent>(selector, type, {...options??{}, root:'light', delegate: 'mutation'});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function addEventListenerMutationShadow<TEvent extends Event = Event>(selector: string, type: string, options?: Omit<AddEventListenerQueryOptions<TEvent>, 'delegate'>): MethodDecorator {
|
|
106
|
+
return addEventListener<TEvent>(selector, type, {...options??{}, root:'shadow', delegate: 'mutation'});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function addEventListenerMutationAll<TEvent extends Event = Event>(selector: string, type: string, options?: Omit<AddEventListenerQueryOptions<TEvent>, 'delegate'>): MethodDecorator {
|
|
110
|
+
return addEventListener<TEvent>(selector, type, {...options??{}, root:'all', delegate: 'mutation'});
|
|
111
|
+
}
|
|
88
112
|
|
|
89
|
-
export function
|
|
113
|
+
export function addEventListenerMutation<TEvent extends Event = Event>(selector: string, type: string, options?: Omit<AddEventListenerQueryOptions<TEvent>, 'delegate'>): MethodDecorator {
|
|
114
|
+
return addEventListener<TEvent>(selector, type, {...options??{}, root:'auto', delegate: 'mutation'});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─── root별 일반 헬퍼 (delegate 없음, 문자열 셀렉터 전용) ───
|
|
118
|
+
|
|
119
|
+
export function addEventListenerLight<TEvent extends Event = Event>(selector: string, type: string, options?: AddEventListenerQueryOptions<TEvent>): MethodDecorator {
|
|
90
120
|
return addEventListener<TEvent>(selector, type, {...options??{}, root:'light'});
|
|
91
121
|
}
|
|
92
122
|
|
|
93
|
-
export function
|
|
123
|
+
export function addEventListenerShadow<TEvent extends Event = Event>(selector: string, type: string, options?: AddEventListenerQueryOptions<TEvent>): MethodDecorator {
|
|
94
124
|
return addEventListener<TEvent>(selector, type, {...options??{}, root:'shadow'});
|
|
95
125
|
}
|
|
96
126
|
|
|
97
|
-
export function
|
|
127
|
+
export function addEventListenerAll<TEvent extends Event = Event>(selector: string, type: string, options?: AddEventListenerQueryOptions<TEvent>): MethodDecorator {
|
|
98
128
|
return addEventListener<TEvent>(selector, type, {...options??{}, root:'all'});
|
|
99
129
|
}
|
|
100
130
|
|
|
101
131
|
/**
|
|
102
132
|
* @addEventListenerThis decorator - simplified version of @addEventListener for $this selector
|
|
103
133
|
*/
|
|
104
|
-
export function addEventListenerThis<TEvent extends Event = Event>(type: string, options?:
|
|
134
|
+
export function addEventListenerThis<TEvent extends Event = Event>(type: string, options?: AddEventListenerQueryOptions<TEvent>): MethodDecorator {
|
|
105
135
|
return addEventListener<TEvent>('$this', type, options);
|
|
106
136
|
}
|
|
107
137
|
|
|
108
138
|
/**
|
|
109
139
|
* @addEventListenerAppHost decorator - simplified version of @addEventListener for $appHost selector
|
|
110
140
|
*/
|
|
111
|
-
export function addEventListenerAppHost<TEvent extends Event = Event>(type: string, options?:
|
|
141
|
+
export function addEventListenerAppHost<TEvent extends Event = Event>(type: string, options?: AddEventListenerQueryOptions<TEvent>): MethodDecorator {
|
|
112
142
|
return addEventListener<TEvent>('$appHost', type, options);
|
|
113
143
|
}
|
|
114
144
|
|
|
115
|
-
export function addEventListenerWindow<TEvent extends Event = Event>(type: string, options?:
|
|
145
|
+
export function addEventListenerWindow<TEvent extends Event = Event>(type: string, options?: AddEventListenerQueryOptions<TEvent>): MethodDecorator {
|
|
116
146
|
return addEventListener<TEvent>('$window', type, options);
|
|
117
147
|
}
|
|
118
|
-
export function addEventListenerDocument<TEvent extends Event = Event>(type: string, options?:
|
|
148
|
+
export function addEventListenerDocument<TEvent extends Event = Event>(type: string, options?: AddEventListenerQueryOptions<TEvent>): MethodDecorator {
|
|
119
149
|
return addEventListener<TEvent>('$document', type, options);
|
|
120
150
|
}
|
|
121
151
|
|
|
122
152
|
// --- Aliases: event... ---
|
|
123
153
|
export const event = addEventListener;
|
|
124
|
-
export const
|
|
125
|
-
export const
|
|
126
|
-
export const
|
|
154
|
+
export const eventDelegateLight = addEventListenerDelegateLight;
|
|
155
|
+
export const eventDelegateShadow = addEventListenerDelegateShadow;
|
|
156
|
+
export const eventDelegateAll = addEventListenerDelegateAll;
|
|
127
157
|
export const eventDelegate = addEventListenerDelegate;
|
|
128
|
-
export const
|
|
129
|
-
export const
|
|
130
|
-
export const
|
|
158
|
+
export const eventMutationLight = addEventListenerMutationLight;
|
|
159
|
+
export const eventMutationShadow = addEventListenerMutationShadow;
|
|
160
|
+
export const eventMutationAll = addEventListenerMutationAll;
|
|
161
|
+
export const eventMutation = addEventListenerMutation;
|
|
162
|
+
export const eventLight = addEventListenerLight;
|
|
163
|
+
export const eventShadow = addEventListenerShadow;
|
|
164
|
+
export const eventAll = addEventListenerAll;
|
|
131
165
|
export const eventAppHost = addEventListenerAppHost;
|
|
132
166
|
export const eventWindow = addEventListenerWindow;
|
|
133
167
|
export const eventDocument = addEventListenerDocument;
|
|
@@ -137,3 +171,329 @@ export const getAddEventListenerMetadata = (target: any): AddEventListenerMetada
|
|
|
137
171
|
const constructor = target instanceof Function ? target : target.constructor;
|
|
138
172
|
return ReflectUtils.getMetadata(ADD_EVENT_LISTENER_METADATA_KEY, constructor);
|
|
139
173
|
};
|
|
174
|
+
|
|
175
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
176
|
+
// EventListenerLifeCycler
|
|
177
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
178
|
+
import { ElementDefineLifeCycler, MutationObserverSetEntry, OnConnectedResult } from '../types';
|
|
179
|
+
import { Subject } from '@dooboostore/core';
|
|
180
|
+
import { debounceTime, distinctUntilChanged, throttleTime } from '@dooboostore/core/message/operators';
|
|
181
|
+
import { SwcUtils } from '../utils/Utils';
|
|
182
|
+
|
|
183
|
+
export interface BoundListenerEntry {
|
|
184
|
+
target: EventTarget;
|
|
185
|
+
type: string;
|
|
186
|
+
handler: EventListener;
|
|
187
|
+
options: AddEventListenerOptions;
|
|
188
|
+
onRemoves?: Array<{ fn: (target: EventTarget, optionValue: unknown) => void; opts: unknown }>;
|
|
189
|
+
subscription?: { unsubscribe: () => void };
|
|
190
|
+
meta?: AddEventListenerMetadata<Event>;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export class EventListenerLifeCycler implements ElementDefineLifeCycler {
|
|
194
|
+
/** 인스턴스별 boundListeners 상태 (시클러는 elementDefine 시 1회 생성되어 공유되므로) */
|
|
195
|
+
private readonly boundListenersMap = new WeakMap<Element, BoundListenerEntry[]>();
|
|
196
|
+
/** 인스턴스별 delegate:'mutation' 바인딩 추적 WeakSet — MutationObserverLifeCycler 에서 참조 */
|
|
197
|
+
private readonly eventBoundSetsMap = new WeakMap<Element, Map<AddEventListenerMetadata, WeakSet<Element>>>();
|
|
198
|
+
|
|
199
|
+
// ── 외부(MutationObserverLifeCycler)에서 직접 사용하는 공개 헬퍼 ──
|
|
200
|
+
|
|
201
|
+
/** 인스턴스별 boundListeners 를 반환한다 (없으면 생성). */
|
|
202
|
+
getBoundListeners(inst: Element): BoundListenerEntry[] {
|
|
203
|
+
let list = this.boundListenersMap.get(inst);
|
|
204
|
+
if (!list) { list = []; this.boundListenersMap.set(inst, list); }
|
|
205
|
+
return list;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** 인스턴스별 eventBoundSets 를 반환한다 (없으면 생성). */
|
|
209
|
+
getEventBoundSets(inst: Element): Map<AddEventListenerMetadata, WeakSet<Element>> {
|
|
210
|
+
let map = this.eventBoundSetsMap.get(inst);
|
|
211
|
+
if (!map) { map = new Map(); this.eventBoundSetsMap.set(inst, map); }
|
|
212
|
+
return map;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* target 에 meta 이벤트를 직접 바인딩한다.
|
|
217
|
+
* delegate:'mutation' 초기/동적 바인딩에서 호출된다.
|
|
218
|
+
*/
|
|
219
|
+
bindDirect(helperHostSet: HelperHostSet, target: EventTarget, meta: AddEventListenerMetadata): void {
|
|
220
|
+
const inst = helperHostSet.$this;
|
|
221
|
+
const currentWin = helperHostSet.$w;
|
|
222
|
+
const { type, options } = meta;
|
|
223
|
+
const opts = { capture: options.capture, once: options.once, passive: options.passive };
|
|
224
|
+
|
|
225
|
+
const handler = async (event: Event) => {
|
|
226
|
+
if (options.filter) {
|
|
227
|
+
const helper = SwcUtils.getHelperAndHostSet(currentWin, target as HTMLElement);
|
|
228
|
+
if (!options.filter(event, { currentThis: inst, helper })) return;
|
|
229
|
+
}
|
|
230
|
+
if (options.stopPropagation) event.stopPropagation();
|
|
231
|
+
if (options.stopImmediatePropagation) event.stopImmediatePropagation();
|
|
232
|
+
if (options.preventDefault) event.preventDefault();
|
|
233
|
+
const currentHostSet = SwcUtils.getHostSet(inst);
|
|
234
|
+
await inst[meta.propertyKey](event, { currentHostSet, $matchedElement: event.currentTarget }, { event, ...currentHostSet, $el: target, $root: target });
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const eventSubject = new Subject<Event>();
|
|
238
|
+
let eventStream: any = eventSubject;
|
|
239
|
+
if (options.debounceTime && options.debounceTime > 0)
|
|
240
|
+
eventStream = eventStream.pipe(debounceTime(options.debounceTime));
|
|
241
|
+
if (options.throttleTime && options.throttleTime > 0)
|
|
242
|
+
eventStream = eventStream.pipe(throttleTime(options.throttleTime));
|
|
243
|
+
if (options.distinctUntilChanged !== undefined && options.distinctUntilChanged !== false) {
|
|
244
|
+
eventStream = typeof options.distinctUntilChanged === 'function'
|
|
245
|
+
? eventStream.pipe(distinctUntilChanged(options.distinctUntilChanged))
|
|
246
|
+
: eventStream.pipe(distinctUntilChanged());
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const subscription = eventStream.subscribe({
|
|
250
|
+
next: (e: Event) => handler(e).catch((err: any) => console.error('Event handler error:', err)),
|
|
251
|
+
error: (err: any) => console.error('Event stream error:', err),
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const wrappedHandler = (event: Event) => eventSubject.next(event);
|
|
255
|
+
target.addEventListener(type, wrappedHandler, opts);
|
|
256
|
+
|
|
257
|
+
const onRemoves: Array<{ fn: any; opts: any }> =
|
|
258
|
+
options.removeListener ? [{ fn: options.removeListener, opts: options }] : [];
|
|
259
|
+
this.getBoundListeners(inst).push({ target, type, handler: wrappedHandler, options: opts, subscription, onRemoves, meta });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** target + meta 조합 리스너를 제거한다. MutationObserver 추적 요소 제거 시 호출. */
|
|
263
|
+
unbindDirect(helperHostSet: HelperHostSet, target: EventTarget, meta: AddEventListenerMetadata): void {
|
|
264
|
+
const inst = helperHostSet.$this;
|
|
265
|
+
const list = this.getBoundListeners(inst);
|
|
266
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
267
|
+
const l = list[i];
|
|
268
|
+
if (l.target === target && l.type === meta.type && l.meta === meta) {
|
|
269
|
+
try { l.target.removeEventListener(l.type, l.handler, l.options); l.subscription?.unsubscribe?.(); }
|
|
270
|
+
catch (e) { console.error('[SWC] unbindDirect error:', e); }
|
|
271
|
+
for (const r of l.onRemoves ?? []) {
|
|
272
|
+
try { r.fn(l.target, r.opts); } catch (e) { console.error('[SWC] unbindDirect removeListener error:', e); }
|
|
273
|
+
}
|
|
274
|
+
list.splice(i, 1);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* 메타데이터 목록을 delegate / mutationDelegate / nonDelegate 로 분류한다.
|
|
281
|
+
* MutationObserverLifeCycler 에서 mutationDelegate 목록을 꺼낼 때 사용한다.
|
|
282
|
+
*/
|
|
283
|
+
classifyListeners(metaList: AddEventListenerMetadata[]): {
|
|
284
|
+
delegateListeners: AddEventListenerMetadata[];
|
|
285
|
+
mutationDelegateListeners: AddEventListenerMetadata[];
|
|
286
|
+
nonDelegateListeners: AddEventListenerMetadata[];
|
|
287
|
+
} {
|
|
288
|
+
const isSpecial = (sel: any) =>
|
|
289
|
+
['$window', '$document', '$host', '$appHost', '$firstHost', '$lastHost',
|
|
290
|
+
'$firstAppHost', '$lastAppHost', '$hosts', '$appHosts', '$this', ''].includes(sel);
|
|
291
|
+
|
|
292
|
+
const delegateListeners: AddEventListenerMetadata[] = [];
|
|
293
|
+
const mutationDelegateListeners: AddEventListenerMetadata[] = [];
|
|
294
|
+
const nonDelegateListeners: AddEventListenerMetadata[] = [];
|
|
295
|
+
|
|
296
|
+
for (const meta of metaList) {
|
|
297
|
+
const isStr = typeof meta.selector === 'string';
|
|
298
|
+
const mode = meta.options.delegate;
|
|
299
|
+
if (mode && isStr && !isSpecial(meta.selector)) {
|
|
300
|
+
mode === 'mutation' ? mutationDelegateListeners.push(meta) : delegateListeners.push(meta);
|
|
301
|
+
} else {
|
|
302
|
+
nonDelegateListeners.push(meta);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return { delegateListeners, mutationDelegateListeners, nonDelegateListeners };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ── ElementDefineLifeCycler 구현 ──
|
|
309
|
+
|
|
310
|
+
onConnected(helperHostSet: HelperHostSet): OnConnectedResult | void {
|
|
311
|
+
const inst = helperHostSet.$this;
|
|
312
|
+
const currentWin = helperHostSet.$w;
|
|
313
|
+
|
|
314
|
+
this.boundListenersMap.delete(inst);
|
|
315
|
+
this.eventBoundSetsMap.delete(inst);
|
|
316
|
+
|
|
317
|
+
const allMeta = getAddEventListenerMetadata(inst) ?? [];
|
|
318
|
+
const { delegateListeners, nonDelegateListeners, mutationDelegateListeners } = this.classifyListeners(allMeta);
|
|
319
|
+
|
|
320
|
+
// ── delegate 리스너: type+root 키로 그룹핑 → unified handler ──
|
|
321
|
+
const groups = new Map<string, AddEventListenerMetadata[]>();
|
|
322
|
+
for (const meta of delegateListeners) {
|
|
323
|
+
const key = `${meta.type}:${meta.options.root || 'auto'}`;
|
|
324
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
325
|
+
groups.get(key)!.push(meta);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
groups.forEach((metaList, key) => {
|
|
329
|
+
const ci = key.indexOf(':');
|
|
330
|
+
const type = key.slice(0, ci);
|
|
331
|
+
const r = key.slice(ci + 1);
|
|
332
|
+
const opts = { capture: metaList[0].options.capture, once: metaList[0].options.once, passive: metaList[0].options.passive };
|
|
333
|
+
|
|
334
|
+
const roots: (HTMLElement | ShadowRoot)[] = [];
|
|
335
|
+
if (r === 'auto') roots.push(inst.shadowRoot || inst);
|
|
336
|
+
else if (r === 'light') roots.push(inst);
|
|
337
|
+
else if (r === 'shadow' && inst.shadowRoot) roots.push(inst.shadowRoot);
|
|
338
|
+
else if (r === 'all') { roots.push(inst); if (inst.shadowRoot) roots.push(inst.shadowRoot); }
|
|
339
|
+
|
|
340
|
+
for (const br of roots) {
|
|
341
|
+
const sorted = [...metaList].sort((a, b) => (b.options.stopPropagation ? 1 : 0) - (a.options.stopPropagation ? 1 : 0));
|
|
342
|
+
const handler = async (event: Event) => {
|
|
343
|
+
for (const m of sorted) {
|
|
344
|
+
const matchedEl = (event.target as HTMLElement)?.closest(m.selector as string);
|
|
345
|
+
console.log('[DBG][delegate] type=', event.type, '| evtTarget=', (event.target as any)?.tagName, '| sel=', m.selector, '| matchedEl=', matchedEl?.className || matchedEl?.tagName, '| brContains=', (br as any).contains?.(matchedEl), '| brTag=', (br as any).tagName || (br as any).constructor?.name);
|
|
346
|
+
if (matchedEl && (br as any).contains(matchedEl)) {
|
|
347
|
+
if (m.options.filter) {
|
|
348
|
+
const helper = SwcUtils.getHelperAndHostSet(currentWin, matchedEl as HTMLElement);
|
|
349
|
+
if (!m.options.filter(event, { currentThis: inst, helper })) continue;
|
|
350
|
+
}
|
|
351
|
+
if (m.options.stopPropagation) event.stopPropagation();
|
|
352
|
+
if (m.options.stopImmediatePropagation) event.stopImmediatePropagation();
|
|
353
|
+
if (m.options.preventDefault) event.preventDefault();
|
|
354
|
+
const hs = SwcUtils.getHostSet(inst);
|
|
355
|
+
await inst[m.propertyKey](event, { ...hs, $matchedElement: matchedEl }, { event, ...hs, $el: matchedEl, $root: br });
|
|
356
|
+
if ((event as any).cancelBubble) break;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
br.addEventListener(type, handler, opts);
|
|
361
|
+
if (type === 'click') {
|
|
362
|
+
console.log('[DBG][eventCycler] bound click on br=', (br as any).tagName || (br as any).constructor?.name, '| inst=', (inst as any).tagName || (inst as any).constructor?.name, '| isSame=', br === inst);
|
|
363
|
+
}
|
|
364
|
+
const onRemoves: Array<{ fn: any; opts: any }> = sorted.filter(m => m.options.removeListener).map(m => ({ fn: m.options.removeListener, opts: m.options }));
|
|
365
|
+
this.getBoundListeners(inst).push({ target: br, type, handler, options: opts, onRemoves });
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// ── non-delegate 리스너: 대상 요소 직접 탐색 → bindDirect ──
|
|
370
|
+
for (const meta of nonDelegateListeners) {
|
|
371
|
+
const { selector, options } = meta;
|
|
372
|
+
const r = options.root || 'auto';
|
|
373
|
+
const bindTargets: EventTarget[] = [];
|
|
374
|
+
|
|
375
|
+
let resolvedSel: string | null = null;
|
|
376
|
+
if (typeof selector === 'function') {
|
|
377
|
+
const result = selector(inst, helperHostSet);
|
|
378
|
+
if (typeof result === 'string') resolvedSel = result;
|
|
379
|
+
else if (result instanceof currentWin.Element) bindTargets.push(result as EventTarget);
|
|
380
|
+
else if (result instanceof currentWin.NodeList) bindTargets.push(...(Array.from(result) as EventTarget[]));
|
|
381
|
+
else if (Array.isArray(result)) bindTargets.push(...(result as EventTarget[]));
|
|
382
|
+
} else {
|
|
383
|
+
resolvedSel = selector;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const applyRoot = (t: any) => {
|
|
387
|
+
if (!t) return;
|
|
388
|
+
if (r === 'auto') bindTargets.push(t.shadowRoot || t);
|
|
389
|
+
else { if (r === 'light' || r === 'all') bindTargets.push(t); if ((r === 'shadow' || r === 'all') && t.shadowRoot) bindTargets.push(t.shadowRoot); }
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
if (resolvedSel) {
|
|
393
|
+
if (resolvedSel === '$window') bindTargets.push(currentWin);
|
|
394
|
+
else if (resolvedSel === '$document') bindTargets.push(currentWin.document);
|
|
395
|
+
else if (resolvedSel === '$host') applyRoot(helperHostSet.$host);
|
|
396
|
+
else if (resolvedSel === '$parentHost') applyRoot(helperHostSet.$parentHost);
|
|
397
|
+
else if (resolvedSel === '$appHost') applyRoot(helperHostSet.$appHost);
|
|
398
|
+
else if (resolvedSel === '$firstHost') applyRoot(helperHostSet.$firstHost);
|
|
399
|
+
else if (resolvedSel === '$lastHost') applyRoot(helperHostSet.$lastHost);
|
|
400
|
+
else if (resolvedSel === '$firstAppHost') applyRoot(helperHostSet.$firstAppHost);
|
|
401
|
+
else if (resolvedSel === '$lastAppHost') applyRoot(helperHostSet.$lastAppHost);
|
|
402
|
+
else if (resolvedSel === '$hosts') helperHostSet.$hosts.forEach(applyRoot);
|
|
403
|
+
else if (resolvedSel === '$appHosts') helperHostSet.$appHosts.forEach(applyRoot);
|
|
404
|
+
else if (resolvedSel === '$this' || !resolvedSel) applyRoot(inst);
|
|
405
|
+
else {
|
|
406
|
+
const searchRoots: (HTMLElement | ShadowRoot)[] = [];
|
|
407
|
+
if (r === 'auto') searchRoots.push(inst.shadowRoot || inst);
|
|
408
|
+
else if (r === 'light') searchRoots.push(inst);
|
|
409
|
+
else if (r === 'shadow' && inst.shadowRoot) searchRoots.push(inst.shadowRoot);
|
|
410
|
+
else if (r === 'all') { searchRoots.push(inst); if (inst.shadowRoot) searchRoots.push(inst.shadowRoot); }
|
|
411
|
+
for (const sr of searchRoots) sr.querySelectorAll(resolvedSel).forEach(el => bindTargets.push(el));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
for (const t of bindTargets) this.bindDirect(helperHostSet, t, meta);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ── delegate:'mutation' 리스너: MutationObserver 로 추가/제거된 요소를 추적해 직접 바인딩/해제 ──
|
|
419
|
+
// 이벤트 리스너의 책임이므로 EventListenerLifeCycler 가 직접 처리하고,
|
|
420
|
+
// mutationObserverSet 을 반환해 elementDefine 이 하나의 MutationObserver 로 생성하게 한다.
|
|
421
|
+
if (mutationDelegateListeners.length > 0) {
|
|
422
|
+
const shadowEventDelegates = mutationDelegateListeners.filter(m => {
|
|
423
|
+
const r = m.options.root || 'auto';
|
|
424
|
+
return inst.shadowRoot && (r === 'shadow' || r === 'all' || r === 'auto');
|
|
425
|
+
});
|
|
426
|
+
const lightEventDelegates = mutationDelegateListeners.filter(m => {
|
|
427
|
+
const r = m.options.root || 'auto';
|
|
428
|
+
return r === 'light' || r === 'all' || (!inst.shadowRoot && r === 'auto');
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const buildMutationEntry = (
|
|
432
|
+
root: HTMLElement | ShadowRoot,
|
|
433
|
+
eventDelegates: AddEventListenerMetadata[],
|
|
434
|
+
): MutationObserverSetEntry | null => {
|
|
435
|
+
if (eventDelegates.length === 0) return null;
|
|
436
|
+
const boundSets = this.getEventBoundSets(inst);
|
|
437
|
+
|
|
438
|
+
const callback = (mutations: MutationRecord[], obs: MutationObserver) => {
|
|
439
|
+
for (const m of eventDelegates) {
|
|
440
|
+
let boundSet = boundSets.get(m);
|
|
441
|
+
if (!boundSet) { boundSet = new WeakSet<Element>(); boundSets.set(m, boundSet); }
|
|
442
|
+
|
|
443
|
+
const isThis = m.selector === '$this' || m.selector === '';
|
|
444
|
+
const matchesSel = (n: any) => {
|
|
445
|
+
if (!n || n.nodeType !== 1) return false;
|
|
446
|
+
if (isThis) return true;
|
|
447
|
+
return typeof m.selector === 'string' && (n as HTMLElement).matches?.(m.selector);
|
|
448
|
+
};
|
|
449
|
+
const bindEl = (el: Element) => { if (!boundSet!.has(el)) { this.bindDirect(helperHostSet, el, m); boundSet!.add(el); } };
|
|
450
|
+
const unbindEl = (el: Element) => { if (boundSet!.has(el)) { this.unbindDirect(helperHostSet, el, m); boundSet!.delete(el); } };
|
|
451
|
+
|
|
452
|
+
for (const mut of mutations) {
|
|
453
|
+
Array.from(mut.addedNodes || []).forEach(n => { if (n.nodeType !== 1) return; if (matchesSel(n)) bindEl(n as Element); else if (!isThis && typeof m.selector === 'string') (n as Element).querySelectorAll?.(m.selector).forEach(bindEl); });
|
|
454
|
+
Array.from(mut.removedNodes || []).forEach(n => { if (n.nodeType !== 1) return; const el = n as Element; if (matchesSel(el)) unbindEl(el); else if (!isThis && typeof m.selector === 'string') el.querySelectorAll?.(m.selector).forEach(unbindEl); });
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
// delegate:'mutation' 초기 바인딩 (connected 시 이미 존재하는 요소)
|
|
460
|
+
for (const m of eventDelegates) {
|
|
461
|
+
let boundSet = boundSets.get(m);
|
|
462
|
+
if (!boundSet) { boundSet = new WeakSet<Element>(); boundSets.set(m, boundSet); }
|
|
463
|
+
if (typeof m.selector !== 'string') continue;
|
|
464
|
+
if (m.selector === '$this' || m.selector === '') {
|
|
465
|
+
this.bindDirect(helperHostSet, root as EventTarget, m);
|
|
466
|
+
boundSet.add(root as Element);
|
|
467
|
+
} else {
|
|
468
|
+
root.querySelectorAll(m.selector).forEach(el => {
|
|
469
|
+
if (!boundSet!.has(el)) { this.bindDirect(helperHostSet, el, m); boundSet!.add(el); }
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return { target: root, options: { childList: true, subtree: true }, callback };
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const shadowEntry = inst.shadowRoot ? buildMutationEntry(inst.shadowRoot, shadowEventDelegates) : null;
|
|
478
|
+
const lightEntry = buildMutationEntry(inst as any, lightEventDelegates);
|
|
479
|
+
const mutationObserverSet: MutationObserverSetEntry[] = [shadowEntry, lightEntry].filter(Boolean) as MutationObserverSetEntry[];
|
|
480
|
+
if (mutationObserverSet.length > 0) return { mutationObserverSet };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return undefined;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
onDisconnected(helperHostSet: HelperHostSet): void {
|
|
487
|
+
const inst = helperHostSet.$this;
|
|
488
|
+
const list = this.boundListenersMap.get(inst) ?? [];
|
|
489
|
+
for (const l of list) {
|
|
490
|
+
try { l.target.removeEventListener(l.type, l.handler, l.options); l.subscription?.unsubscribe?.(); }
|
|
491
|
+
catch (e) { console.error('[SWC] EventListenerLifeCycler cleanup error:', e); }
|
|
492
|
+
for (const r of l.onRemoves ?? []) {
|
|
493
|
+
try { r.fn(l.target, r.opts); } catch (e) { console.error('[SWC] removeListener error:', e); }
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
this.boundListenersMap.delete(inst);
|
|
497
|
+
this.eventBoundSetsMap.delete(inst);
|
|
498
|
+
}
|
|
499
|
+
}
|