@dooboostore/simple-web-component 1.0.48 → 1.0.50
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 +189 -19
- package/dist/cjs/config/config.js +1 -1
- package/dist/cjs/config/config.js.map +2 -2
- package/dist/cjs/decorators/addEventListener.js +2373 -30
- package/dist/cjs/decorators/addEventListener.js.map +3 -3
- package/dist/cjs/decorators/applyAttribute.js +31 -0
- package/dist/cjs/decorators/applyAttribute.js.map +2 -2
- package/dist/cjs/decorators/applyClass.js +55 -18
- package/dist/cjs/decorators/applyClass.js.map +3 -3
- package/dist/cjs/decorators/applyNode.js +73 -1
- package/dist/cjs/decorators/applyNode.js.map +3 -3
- package/dist/cjs/decorators/applyProperty.js +107 -3
- package/dist/cjs/decorators/applyProperty.js.map +2 -2
- package/dist/cjs/decorators/applySlot.js +6 -5
- package/dist/cjs/decorators/applySlot.js.map +2 -2
- package/dist/cjs/decorators/applyStyle.js +42 -5
- package/dist/cjs/decorators/applyStyle.js.map +2 -2
- package/dist/cjs/decorators/changedAttribute.js +61 -13
- package/dist/cjs/decorators/changedAttribute.js.map +2 -2
- package/dist/cjs/decorators/elementDefine.js +254 -439
- 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/eventMedia.js +84 -0
- package/dist/cjs/decorators/eventMedia.js.map +7 -0
- package/dist/cjs/decorators/index.js +6 -1
- package/dist/cjs/decorators/index.js.map +2 -2
- package/dist/cjs/decorators/intersectionObserver.js +200 -0
- package/dist/cjs/decorators/intersectionObserver.js.map +7 -0
- package/dist/cjs/decorators/mutationObserver.js +186 -55
- package/dist/cjs/decorators/mutationObserver.js.map +3 -3
- package/dist/cjs/decorators/parameter.js +73 -0
- package/dist/cjs/decorators/parameter.js.map +7 -0
- package/dist/cjs/decorators/query.js +117 -102
- package/dist/cjs/decorators/query.js.map +2 -2
- package/dist/cjs/decorators/requestAnimationFrame.js +124 -0
- package/dist/cjs/decorators/requestAnimationFrame.js.map +7 -0
- package/dist/cjs/decorators/resizeObserver.js +133 -55
- package/dist/cjs/decorators/resizeObserver.js.map +2 -2
- package/dist/cjs/decorators/setInterval.js +114 -0
- package/dist/cjs/decorators/setInterval.js.map +7 -0
- package/dist/cjs/decorators/setTimeout.js +117 -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/elements/SwcAppMixin.js +19 -3
- package/dist/cjs/elements/SwcAppMixin.js.map +2 -2
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/window.d.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 +2374 -30
- package/dist/esm/decorators/addEventListener.js.map +3 -3
- package/dist/esm/decorators/applyAttribute.js +31 -0
- package/dist/esm/decorators/applyAttribute.js.map +2 -2
- package/dist/esm/decorators/applyClass.js +55 -18
- package/dist/esm/decorators/applyClass.js.map +3 -3
- package/dist/esm/decorators/applyNode.js +73 -1
- package/dist/esm/decorators/applyNode.js.map +3 -3
- package/dist/esm/decorators/applyProperty.js +107 -3
- package/dist/esm/decorators/applyProperty.js.map +2 -2
- package/dist/esm/decorators/applySlot.js +6 -5
- package/dist/esm/decorators/applySlot.js.map +2 -2
- package/dist/esm/decorators/applyStyle.js +42 -5
- package/dist/esm/decorators/applyStyle.js.map +2 -2
- package/dist/esm/decorators/changedAttribute.js +62 -13
- package/dist/esm/decorators/changedAttribute.js.map +2 -2
- package/dist/esm/decorators/elementDefine.js +261 -446
- 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/eventMedia.js +66 -0
- package/dist/esm/decorators/eventMedia.js.map +7 -0
- package/dist/esm/decorators/index.js +6 -1
- package/dist/esm/decorators/index.js.map +2 -2
- package/dist/esm/decorators/intersectionObserver.js +182 -0
- package/dist/esm/decorators/intersectionObserver.js.map +7 -0
- package/dist/esm/decorators/mutationObserver.js +187 -55
- package/dist/esm/decorators/mutationObserver.js.map +3 -3
- package/dist/esm/decorators/parameter.js +54 -0
- package/dist/esm/decorators/parameter.js.map +7 -0
- package/dist/esm/decorators/query.js +117 -102
- package/dist/esm/decorators/query.js.map +2 -2
- package/dist/esm/decorators/requestAnimationFrame.js +105 -0
- package/dist/esm/decorators/requestAnimationFrame.js.map +7 -0
- package/dist/esm/decorators/resizeObserver.js +134 -55
- package/dist/esm/decorators/resizeObserver.js.map +2 -2
- package/dist/esm/decorators/setInterval.js +95 -0
- package/dist/esm/decorators/setInterval.js.map +7 -0
- package/dist/esm/decorators/setTimeout.js +98 -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/elements/SwcAppMixin.js +20 -4
- package/dist/esm/elements/SwcAppMixin.js.map +2 -2
- package/dist/esm/types/index.js.map +1 -1
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +4961 -1628
- 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 +1046 -33
- package/dist/types/decorators/addEventListener.d.ts.map +1 -1
- package/dist/types/decorators/applyAttribute.d.ts +24 -34
- package/dist/types/decorators/applyAttribute.d.ts.map +1 -1
- package/dist/types/decorators/applyClass.d.ts +36 -34
- package/dist/types/decorators/applyClass.d.ts.map +1 -1
- package/dist/types/decorators/applyNode.d.ts +32 -20
- package/dist/types/decorators/applyNode.d.ts.map +1 -1
- package/dist/types/decorators/applyProperty.d.ts +61 -34
- package/dist/types/decorators/applyProperty.d.ts.map +1 -1
- package/dist/types/decorators/applySlot.d.ts.map +1 -1
- package/dist/types/decorators/applyStyle.d.ts +27 -12
- package/dist/types/decorators/applyStyle.d.ts.map +1 -1
- package/dist/types/decorators/changedAttribute.d.ts +12 -3
- 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/eventMedia.d.ts +25 -0
- package/dist/types/decorators/eventMedia.d.ts.map +1 -0
- package/dist/types/decorators/index.d.ts +6 -1
- package/dist/types/decorators/index.d.ts.map +1 -1
- package/dist/types/decorators/intersectionObserver.d.ts +58 -0
- package/dist/types/decorators/intersectionObserver.d.ts.map +1 -0
- package/dist/types/decorators/mutationObserver.d.ts +44 -25
- package/dist/types/decorators/mutationObserver.d.ts.map +1 -1
- package/dist/types/decorators/parameter.d.ts +35 -0
- package/dist/types/decorators/parameter.d.ts.map +1 -0
- package/dist/types/decorators/query.d.ts +57 -14
- package/dist/types/decorators/query.d.ts.map +1 -1
- package/dist/types/decorators/requestAnimationFrame.d.ts +47 -0
- package/dist/types/decorators/requestAnimationFrame.d.ts.map +1 -0
- package/dist/types/decorators/resizeObserver.d.ts +43 -23
- package/dist/types/decorators/resizeObserver.d.ts.map +1 -1
- package/dist/types/decorators/setInterval.d.ts +41 -0
- package/dist/types/decorators/setInterval.d.ts.map +1 -0
- package/dist/types/decorators/setTimeout.d.ts +41 -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/elements/SwcAppMixin.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 +4961 -1628
- 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 +1446 -34
- package/src/decorators/applyAttribute.ts +60 -36
- package/src/decorators/applyClass.ts +86 -26
- package/src/decorators/applyNode.ts +124 -36
- package/src/decorators/applyProperty.ts +172 -40
- package/src/decorators/applySlot.ts +6 -5
- package/src/decorators/applyStyle.ts +74 -17
- package/src/decorators/changedAttribute.ts +76 -15
- package/src/decorators/elementDefine.ts +310 -582
- package/src/decorators/emitCustomEvent.ts +73 -5
- package/src/decorators/eventMedia.ts +78 -0
- package/src/decorators/index.ts +6 -1
- package/src/decorators/intersectionObserver.ts +275 -0
- package/src/decorators/mutationObserver.ts +282 -89
- package/src/decorators/parameter.ts +81 -0
- package/src/decorators/query.ts +168 -126
- package/src/decorators/requestAnimationFrame.ts +158 -0
- package/src/decorators/resizeObserver.ts +211 -89
- package/src/decorators/setInterval.ts +136 -0
- package/src/decorators/setTimeout.ts +139 -0
- package/src/decorators/subscribeSwcAppMessageWhileConnected.ts +17 -0
- package/src/decorators/subscribeSwcAppRouteChangeWhileConnected.ts +19 -0
- package/src/elements/SwcAppMixin.ts +20 -5
- package/src/types/index.ts +141 -0
- package/src/types/window.d.ts +2 -18
- package/dist/cjs/decorators/queryAll.js +0 -181
- package/dist/cjs/decorators/queryAll.js.map +0 -7
- package/dist/cjs/examples/RxJSOperatorsExample.js +0 -285
- package/dist/cjs/examples/RxJSOperatorsExample.js.map +0 -7
- package/dist/esm/decorators/queryAll.js +0 -162
- package/dist/esm/decorators/queryAll.js.map +0 -7
- package/dist/esm/examples/RxJSOperatorsExample.js +0 -266
- package/dist/esm/examples/RxJSOperatorsExample.js.map +0 -7
- package/dist/types/decorators/queryAll.d.ts +0 -22
- package/dist/types/decorators/queryAll.d.ts.map +0 -1
- package/dist/types/examples/RxJSOperatorsExample.d.ts +0 -94
- package/dist/types/examples/RxJSOperatorsExample.d.ts.map +0 -1
- package/src/decorators/queryAll.ts +0 -186
- package/src/examples/RxJSOperatorsExample.ts +0 -279
|
@@ -1,129 +1,251 @@
|
|
|
1
1
|
import { ReflectUtils } from '@dooboostore/core';
|
|
2
|
-
import { SpecialSelector, SwcQueryOptions, HelperHostSet } from '../types';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export interface
|
|
6
|
-
// 관찰 대상 셀렉터. 기본값 $this (컴포넌트 자신)
|
|
7
|
-
// delegate: true 인 경우 루트에서 매칭 요소들의 크기 변화를 감지 (동적 요소 포함)
|
|
8
|
-
delegate?: boolean;
|
|
9
|
-
// ResizeObserver box 옵션
|
|
2
|
+
import { SpecialSelector, SwcQueryOptions, SwcFnSelector, SwcSelector, HelperHostSet } from '../types';
|
|
3
|
+
|
|
4
|
+
// 공통 옵션 — root·delegate 없음
|
|
5
|
+
export interface ResizeObserverBaseOptions {
|
|
10
6
|
box?: 'content-box' | 'border-box' | 'device-pixel-content-box';
|
|
11
7
|
filter?: (matchedEls: HTMLElement[], meta: { currentThis: any, helper: HelperHostSet }) => boolean;
|
|
12
|
-
// observer 해제(disconnected) 시 호출되는 콜백. 첫 번째 인자는 observe된 target element, 두 번째는 사용자 옵션 객체.
|
|
13
8
|
removeObserver?: (target: Element, optionValue: ResizeObserverOptions) => void;
|
|
14
9
|
}
|
|
15
10
|
|
|
11
|
+
// 문자열 셀렉터 전용 — root·delegate 허용
|
|
12
|
+
export type ResizeObserverQueryOptions = ResizeObserverBaseOptions & SwcQueryOptions & { delegate?: boolean };
|
|
13
|
+
// 함수 셀렉터 전용 — root·delegate 금지
|
|
14
|
+
export type ResizeObserverNonQueryOptions = ResizeObserverBaseOptions;
|
|
15
|
+
// 내부 저장/해석용
|
|
16
|
+
export type ResizeObserverOptions = ResizeObserverQueryOptions;
|
|
17
|
+
|
|
18
|
+
// 셀렉터 종류에 따라 옵션 타입 분기
|
|
19
|
+
export type ResizeObserverOptionsOf<S extends SwcSelector> = S extends string ? ResizeObserverQueryOptions : ResizeObserverNonQueryOptions;
|
|
20
|
+
|
|
16
21
|
export interface ResizeObserverMetadata {
|
|
17
22
|
propertyKey: string | symbol;
|
|
18
|
-
selector:
|
|
23
|
+
selector: SwcSelector;
|
|
19
24
|
options: ResizeObserverOptions;
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
export const RESIZE_OBSERVER_METADATA_KEY = Symbol.for('simple-web-component:resize-observer');
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
const applyResizeObserver = (selector: SwcSelector, options: ResizeObserverOptions, targetObj: Object, propertyKey: string | symbol): void => {
|
|
30
|
+
const constructor = targetObj.constructor;
|
|
31
|
+
let observers = ReflectUtils.getMetadata<ResizeObserverMetadata[]>(RESIZE_OBSERVER_METADATA_KEY, constructor);
|
|
32
|
+
if (!observers) {
|
|
33
|
+
observers = [];
|
|
34
|
+
ReflectUtils.defineMetadata(RESIZE_OBSERVER_METADATA_KEY, observers, constructor);
|
|
35
|
+
}
|
|
36
|
+
observers.push({ propertyKey, selector, options });
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const resolveResizeObserverArgs = (
|
|
40
|
+
selectorOrOptions: SwcSelector | ResizeObserverOptions | undefined,
|
|
41
|
+
maybeOptions: ResizeObserverOptions | undefined,
|
|
42
|
+
extra: Partial<ResizeObserverOptions>
|
|
43
|
+
): { selector: SwcSelector; options: ResizeObserverOptions } => {
|
|
44
|
+
if (typeof selectorOrOptions === 'string' || typeof selectorOrOptions === 'function') {
|
|
45
|
+
return { selector: selectorOrOptions, options: { ...(maybeOptions ?? {}), ...extra } };
|
|
46
|
+
}
|
|
47
|
+
return { selector: '$this', options: { ...(selectorOrOptions ?? {}), ...extra } };
|
|
48
|
+
};
|
|
49
|
+
|
|
27
50
|
/**
|
|
28
|
-
* @
|
|
51
|
+
* bare(@decorator)와 factory(@decorator()/@decorator(...)) 호출을 모두 처리하는 공통 디스패처.
|
|
52
|
+
* extra는 Light/Shadow/All/Delegate 변형이 강제로 덧씌우는 옵션(root, delegate 등)이다.
|
|
29
53
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
observers.push({ propertyKey, selector, options });
|
|
54
|
+
const dispatchResizeObserver = (
|
|
55
|
+
selectorOrOptionsOrTarget: SwcSelector | ResizeObserverOptions | Object | undefined,
|
|
56
|
+
maybeOptionsOrPropertyKey: ResizeObserverOptions | string | symbol | undefined,
|
|
57
|
+
descriptor: PropertyDescriptor | undefined,
|
|
58
|
+
extra: Partial<ResizeObserverOptions>
|
|
59
|
+
): MethodDecorator | void => {
|
|
60
|
+
if ((typeof maybeOptionsOrPropertyKey === 'string' || typeof maybeOptionsOrPropertyKey === 'symbol') && descriptor !== undefined) {
|
|
61
|
+
// 옵션 없이: @decorator
|
|
62
|
+
applyResizeObserver('$this', { ...extra }, selectorOrOptionsOrTarget as Object, maybeOptionsOrPropertyKey);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// 옵션과 함께: @decorator() / @decorator(selector, options) / @decorator(options)
|
|
66
|
+
const { selector, options } = resolveResizeObserverArgs(
|
|
67
|
+
selectorOrOptionsOrTarget as SwcSelector | ResizeObserverOptions | undefined,
|
|
68
|
+
maybeOptionsOrPropertyKey as ResizeObserverOptions | undefined,
|
|
69
|
+
extra
|
|
70
|
+
);
|
|
71
|
+
return (targetObj: Object, propertyKey: string | symbol) => {
|
|
72
|
+
applyResizeObserver(selector, options, targetObj, propertyKey);
|
|
52
73
|
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export function resizeObserver(target: SpecialSelector, options?: ResizeObserverQueryOptions): MethodDecorator;
|
|
77
|
+
export function resizeObserver(selector: string, options?: ResizeObserverQueryOptions): MethodDecorator;
|
|
78
|
+
export function resizeObserver(selector: SwcFnSelector, options?: ResizeObserverNonQueryOptions): MethodDecorator;
|
|
79
|
+
export function resizeObserver(options?: ResizeObserverQueryOptions): MethodDecorator;
|
|
80
|
+
export function resizeObserver(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
81
|
+
/**
|
|
82
|
+
* @resizeObserver decorator to observe element size changes.
|
|
83
|
+
* 옵션 없이 `@resizeObserver` 그대로 붙여도 되고, `@resizeObserver(...)`처럼 셀렉터/옵션을 줄 수도 있다.
|
|
84
|
+
*/
|
|
85
|
+
export function resizeObserver(selectorOrOptionsOrTarget?: SwcSelector | ResizeObserverOptions | Object, maybeOptionsOrPropertyKey?: ResizeObserverOptions | string | symbol, descriptor?: PropertyDescriptor): MethodDecorator | void {
|
|
86
|
+
return dispatchResizeObserver(selectorOrOptionsOrTarget, maybeOptionsOrPropertyKey, descriptor, {});
|
|
53
87
|
}
|
|
54
88
|
|
|
55
|
-
export function resizeObserverThis(options?:
|
|
56
|
-
|
|
89
|
+
export function resizeObserverThis(options?: ResizeObserverQueryOptions): MethodDecorator;
|
|
90
|
+
export function resizeObserverThis(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
91
|
+
export function resizeObserverThis(optionsOrTarget?: ResizeObserverQueryOptions | Object, propertyKey?: string | symbol, descriptor?: PropertyDescriptor): MethodDecorator | void {
|
|
92
|
+
return dispatchResizeObserver(optionsOrTarget, propertyKey, descriptor, {});
|
|
57
93
|
}
|
|
58
94
|
|
|
59
|
-
// ─── root별 delegate 헬퍼 ───
|
|
95
|
+
// ─── root별 delegate 헬퍼 (root 주입 → 문자열 셀렉터 전용) ───
|
|
60
96
|
|
|
61
|
-
export function resizeObserverDelegateLight(selector:
|
|
62
|
-
export function resizeObserverDelegateLight(options?: Omit<
|
|
63
|
-
export function resizeObserverDelegateLight(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
return resizeObserver({...selectorOrOptions ?? {}, root: 'light', delegate: true});
|
|
97
|
+
export function resizeObserverDelegateLight(selector: string, options?: Omit<ResizeObserverQueryOptions, 'delegate'>): MethodDecorator;
|
|
98
|
+
export function resizeObserverDelegateLight(options?: Omit<ResizeObserverQueryOptions, 'delegate'>): MethodDecorator;
|
|
99
|
+
export function resizeObserverDelegateLight(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
100
|
+
export function resizeObserverDelegateLight(selectorOrOptionsOrTarget?: string | Omit<ResizeObserverQueryOptions, 'delegate'> | Object, maybeOptionsOrPropertyKey?: Omit<ResizeObserverQueryOptions, 'delegate'> | string | symbol, descriptor?: PropertyDescriptor): MethodDecorator | void {
|
|
101
|
+
return dispatchResizeObserver(selectorOrOptionsOrTarget as any, maybeOptionsOrPropertyKey as any, descriptor, { root: 'light', delegate: true });
|
|
68
102
|
}
|
|
69
103
|
|
|
70
|
-
export function resizeObserverDelegateShadow(selector:
|
|
71
|
-
export function resizeObserverDelegateShadow(options?: Omit<
|
|
72
|
-
export function resizeObserverDelegateShadow(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
return resizeObserver({...selectorOrOptions ?? {}, root: 'shadow', delegate: true});
|
|
104
|
+
export function resizeObserverDelegateShadow(selector: string, options?: Omit<ResizeObserverQueryOptions, 'delegate'>): MethodDecorator;
|
|
105
|
+
export function resizeObserverDelegateShadow(options?: Omit<ResizeObserverQueryOptions, 'delegate'>): MethodDecorator;
|
|
106
|
+
export function resizeObserverDelegateShadow(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
107
|
+
export function resizeObserverDelegateShadow(selectorOrOptionsOrTarget?: string | Omit<ResizeObserverQueryOptions, 'delegate'> | Object, maybeOptionsOrPropertyKey?: Omit<ResizeObserverQueryOptions, 'delegate'> | string | symbol, descriptor?: PropertyDescriptor): MethodDecorator | void {
|
|
108
|
+
return dispatchResizeObserver(selectorOrOptionsOrTarget as any, maybeOptionsOrPropertyKey as any, descriptor, { root: 'shadow', delegate: true });
|
|
77
109
|
}
|
|
78
110
|
|
|
79
|
-
export function resizeObserverDelegateAll(selector:
|
|
80
|
-
export function resizeObserverDelegateAll(options?: Omit<
|
|
81
|
-
export function resizeObserverDelegateAll(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
return resizeObserver({...selectorOrOptions ?? {}, root: 'all', delegate: true});
|
|
111
|
+
export function resizeObserverDelegateAll(selector: string, options?: Omit<ResizeObserverQueryOptions, 'delegate'>): MethodDecorator;
|
|
112
|
+
export function resizeObserverDelegateAll(options?: Omit<ResizeObserverQueryOptions, 'delegate'>): MethodDecorator;
|
|
113
|
+
export function resizeObserverDelegateAll(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
114
|
+
export function resizeObserverDelegateAll(selectorOrOptionsOrTarget?: string | Omit<ResizeObserverQueryOptions, 'delegate'> | Object, maybeOptionsOrPropertyKey?: Omit<ResizeObserverQueryOptions, 'delegate'> | string | symbol, descriptor?: PropertyDescriptor): MethodDecorator | void {
|
|
115
|
+
return dispatchResizeObserver(selectorOrOptionsOrTarget as any, maybeOptionsOrPropertyKey as any, descriptor, { root: 'all', delegate: true });
|
|
86
116
|
}
|
|
87
117
|
|
|
88
|
-
export function resizeObserverDelegate(selector:
|
|
89
|
-
export function resizeObserverDelegate(options?: Omit<
|
|
90
|
-
export function resizeObserverDelegate(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
return resizeObserver({...selectorOrOptions ?? {}, root: 'auto', delegate: true});
|
|
118
|
+
export function resizeObserverDelegate(selector: string, options?: Omit<ResizeObserverQueryOptions, 'delegate'>): MethodDecorator;
|
|
119
|
+
export function resizeObserverDelegate(options?: Omit<ResizeObserverQueryOptions, 'delegate'>): MethodDecorator;
|
|
120
|
+
export function resizeObserverDelegate(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
121
|
+
export function resizeObserverDelegate(selectorOrOptionsOrTarget?: string | Omit<ResizeObserverQueryOptions, 'delegate'> | Object, maybeOptionsOrPropertyKey?: Omit<ResizeObserverQueryOptions, 'delegate'> | string | symbol, descriptor?: PropertyDescriptor): MethodDecorator | void {
|
|
122
|
+
return dispatchResizeObserver(selectorOrOptionsOrTarget as any, maybeOptionsOrPropertyKey as any, descriptor, { root: 'auto', delegate: true });
|
|
95
123
|
}
|
|
96
124
|
|
|
97
125
|
// ─── root별 일반 헬퍼 (delegate 없이, 셀렉터 생략 시 $this) ───
|
|
98
126
|
|
|
99
|
-
export function resizeObserverLight(selector:
|
|
100
|
-
export function resizeObserverLight(options?:
|
|
101
|
-
export function resizeObserverLight(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
return resizeObserver({...selectorOrOptions ?? {}, root: 'light'});
|
|
127
|
+
export function resizeObserverLight(selector: string, options?: ResizeObserverQueryOptions): MethodDecorator;
|
|
128
|
+
export function resizeObserverLight(options?: ResizeObserverQueryOptions): MethodDecorator;
|
|
129
|
+
export function resizeObserverLight(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
130
|
+
export function resizeObserverLight(selectorOrOptionsOrTarget?: string | ResizeObserverQueryOptions | Object, maybeOptionsOrPropertyKey?: ResizeObserverQueryOptions | string | symbol, descriptor?: PropertyDescriptor): MethodDecorator | void {
|
|
131
|
+
return dispatchResizeObserver(selectorOrOptionsOrTarget as any, maybeOptionsOrPropertyKey as any, descriptor, { root: 'light' });
|
|
106
132
|
}
|
|
107
133
|
|
|
108
|
-
export function resizeObserverShadow(selector:
|
|
109
|
-
export function resizeObserverShadow(options?:
|
|
110
|
-
export function resizeObserverShadow(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
return resizeObserver({...selectorOrOptions ?? {}, root: 'shadow'});
|
|
134
|
+
export function resizeObserverShadow(selector: string, options?: ResizeObserverQueryOptions): MethodDecorator;
|
|
135
|
+
export function resizeObserverShadow(options?: ResizeObserverQueryOptions): MethodDecorator;
|
|
136
|
+
export function resizeObserverShadow(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
137
|
+
export function resizeObserverShadow(selectorOrOptionsOrTarget?: string | ResizeObserverQueryOptions | Object, maybeOptionsOrPropertyKey?: ResizeObserverQueryOptions | string | symbol, descriptor?: PropertyDescriptor): MethodDecorator | void {
|
|
138
|
+
return dispatchResizeObserver(selectorOrOptionsOrTarget as any, maybeOptionsOrPropertyKey as any, descriptor, { root: 'shadow' });
|
|
115
139
|
}
|
|
116
140
|
|
|
117
|
-
export function resizeObserverAll(selector:
|
|
118
|
-
export function resizeObserverAll(options?:
|
|
119
|
-
export function resizeObserverAll(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
return resizeObserver({...selectorOrOptions ?? {}, root: 'all'});
|
|
141
|
+
export function resizeObserverAll(selector: string, options?: ResizeObserverQueryOptions): MethodDecorator;
|
|
142
|
+
export function resizeObserverAll(options?: ResizeObserverQueryOptions): MethodDecorator;
|
|
143
|
+
export function resizeObserverAll(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
|
|
144
|
+
export function resizeObserverAll(selectorOrOptionsOrTarget?: string | ResizeObserverQueryOptions | Object, maybeOptionsOrPropertyKey?: ResizeObserverQueryOptions | string | symbol, descriptor?: PropertyDescriptor): MethodDecorator | void {
|
|
145
|
+
return dispatchResizeObserver(selectorOrOptionsOrTarget as any, maybeOptionsOrPropertyKey as any, descriptor, { root: 'all' });
|
|
124
146
|
}
|
|
125
147
|
|
|
126
148
|
export function getResizeObserverMetadata(target: any): ResizeObserverMetadata[] | undefined {
|
|
127
149
|
const constructor = target instanceof Function ? target : target.constructor;
|
|
128
150
|
return ReflectUtils.getMetadata(RESIZE_OBSERVER_METADATA_KEY, constructor);
|
|
129
|
-
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
154
|
+
// ResizeObserverLifeCycler
|
|
155
|
+
// observer 를 직접 생성하지 않고 OnConnectedResult 를 반환한다.
|
|
156
|
+
// elementDefine 이 수집된 ResizeObserverSet 으로 observer 를 생성하고,
|
|
157
|
+
// observe/unobserve 함수를 주입해 MutationObserver delegate 추적이 사용할 수 있게 한다.
|
|
158
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
159
|
+
import { ElementDefineLifeCycler, OnConnectedResult, ResizeObserverSet, ResizeObserverSetEntry } from '../types';
|
|
160
|
+
import { SwcUtils } from '../utils/Utils';
|
|
161
|
+
|
|
162
|
+
export class ResizeObserverLifeCycler implements ElementDefineLifeCycler {
|
|
163
|
+
private readonly removeObserverCallbacksMap = new WeakMap<Element, Array<{ fn: (target: Element, opts: unknown) => void; target: any; opts: unknown }>>();
|
|
164
|
+
|
|
165
|
+
onConnected(helperHostSet: HelperHostSet): OnConnectedResult | void {
|
|
166
|
+
const inst = helperHostSet.$this;
|
|
167
|
+
const currentWin = helperHostSet.$w;
|
|
168
|
+
|
|
169
|
+
const removeObserverCallbacks: Array<{ fn: (target: Element, opts: unknown) => void; target: any; opts: unknown }> = [];
|
|
170
|
+
this.removeObserverCallbacksMap.set(inst, removeObserverCallbacks);
|
|
171
|
+
|
|
172
|
+
const allMeta = getResizeObserverMetadata(inst) ?? [];
|
|
173
|
+
if (allMeta.length === 0) return;
|
|
174
|
+
|
|
175
|
+
const root: HTMLElement | ShadowRoot = inst.shadowRoot || inst;
|
|
176
|
+
|
|
177
|
+
// removeObserver 콜백 수집
|
|
178
|
+
for (const m of allMeta) {
|
|
179
|
+
if (m.options.removeObserver) removeObserverCallbacks.push({ fn: m.options.removeObserver, target: root, opts: m.options });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── ResizeObserver 콜백 ──
|
|
183
|
+
const callback: ResizeObserverSetEntry['callback'] = (entries, obs) => {
|
|
184
|
+
for (const m of allMeta) {
|
|
185
|
+
let matchedEls: HTMLElement[];
|
|
186
|
+
if (m.options.delegate && typeof m.selector === 'string') {
|
|
187
|
+
const isThis = m.selector === '$this' || m.selector === '';
|
|
188
|
+
matchedEls = entries.map(e => e.target as HTMLElement).filter(el => {
|
|
189
|
+
if (!el || el.nodeType !== 1) return false;
|
|
190
|
+
if (isThis) return true;
|
|
191
|
+
return el.matches?.(m.selector as string) || !!el.closest?.(m.selector as string);
|
|
192
|
+
});
|
|
193
|
+
} else {
|
|
194
|
+
matchedEls = entries.map(e => e.target as HTMLElement).filter(t => t && t.nodeType === 1);
|
|
195
|
+
}
|
|
196
|
+
if (matchedEls.length === 0) continue;
|
|
197
|
+
if (m.options.filter) {
|
|
198
|
+
if (!m.options.filter(matchedEls, { currentThis: inst, helper: helperHostSet })) continue;
|
|
199
|
+
}
|
|
200
|
+
const hostSet = SwcUtils.getHostSet(inst);
|
|
201
|
+
inst[m.propertyKey](matchedEls, entries, obs, { ...hostSet, $root: root });
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// ── observe 대상 목록 구성 ──
|
|
206
|
+
// - non-delegate: 실제 요소를 초기 observe
|
|
207
|
+
// - delegate: 동적 추적용 셀렉터 문자열로 선언 (elementDefine 의 MutationObserver 콜백이 observe/unobserve)
|
|
208
|
+
// 모든 대상은 이 루트의 callback 을 공유한다.
|
|
209
|
+
const resizeObserverSet: ResizeObserverSet = [];
|
|
210
|
+
for (const m of allMeta) {
|
|
211
|
+
if (m.options.delegate && typeof m.selector === 'string') {
|
|
212
|
+
resizeObserverSet.push({ target: m.selector, delegate: true, delegateRoot: m.options.root, options: m.options.box ? { box: m.options.box } : undefined, callback });
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
let targets: HTMLElement[] = [];
|
|
216
|
+
|
|
217
|
+
if (typeof m.selector === 'function') {
|
|
218
|
+
const res = (m.selector as any)(inst, helperHostSet);
|
|
219
|
+
if (res instanceof currentWin.HTMLElement) targets = [res as HTMLElement];
|
|
220
|
+
else if (res instanceof currentWin.NodeList) targets = Array.from(res as NodeList).filter((e: any) => e instanceof currentWin.HTMLElement) as HTMLElement[];
|
|
221
|
+
else if (Array.isArray(res)) targets = res.filter((e: any) => e instanceof currentWin.HTMLElement) as HTMLElement[];
|
|
222
|
+
} else if (typeof m.selector === 'string') {
|
|
223
|
+
if (m.selector === '$this' || m.selector === '') {
|
|
224
|
+
targets = [inst as HTMLElement];
|
|
225
|
+
} else {
|
|
226
|
+
const r: any = m.options.root || 'auto';
|
|
227
|
+
const scopes: any[] = [];
|
|
228
|
+
if (r === 'auto') scopes.push(inst.shadowRoot || inst);
|
|
229
|
+
else if (r === 'light') scopes.push(inst);
|
|
230
|
+
else if (r === 'shadow' && inst.shadowRoot) scopes.push(inst.shadowRoot);
|
|
231
|
+
else if (r === 'all') { scopes.push(inst); if (inst.shadowRoot) scopes.push(inst.shadowRoot); }
|
|
232
|
+
for (const s of scopes) {
|
|
233
|
+
const found = s?.querySelectorAll?.(m.selector as string) as NodeListOf<HTMLElement> | undefined;
|
|
234
|
+
if (found?.length) targets.push(...Array.from(found));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
for (const el of targets) resizeObserverSet.push({ target: el, options: m.options.box ? { box: m.options.box } : undefined, callback });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { resizeObserverSet };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
onDisconnected(helperHostSet: HelperHostSet): void {
|
|
245
|
+
const inst = helperHostSet.$this;
|
|
246
|
+
for (const cb of this.removeObserverCallbacksMap.get(inst) ?? []) {
|
|
247
|
+
try { cb.fn(cb.target, cb.opts); } catch (e) { console.error('[SWC] removeObserver error:', e); }
|
|
248
|
+
}
|
|
249
|
+
this.removeObserverCallbacksMap.delete(inst);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { ReflectUtils } from '@dooboostore/core';
|
|
2
|
+
import { ensureInit, getElementConfig } from './elementDefine';
|
|
3
|
+
import { SwcUtils } from '../utils/Utils';
|
|
4
|
+
import { ElementDefineLifeCycler, HelperHostSet } from '../types';
|
|
5
|
+
|
|
6
|
+
export const SET_INTERVAL_METADATA_KEY = Symbol.for('simple-web-component:set-interval');
|
|
7
|
+
|
|
8
|
+
export type SetIntervalType = 'onConnected' | 'returnValue';
|
|
9
|
+
|
|
10
|
+
export interface SetIntervalOptions {
|
|
11
|
+
/**
|
|
12
|
+
* 'onConnected' (connect 시 자동으로 시작, disconnect 시 자동 정리) |
|
|
13
|
+
* 'returnValue' (기본값 - 자동 시작 없음. 개발자가 이 메서드를 직접 호출해야 시작되고,
|
|
14
|
+
* 그 호출의 리턴값이 실제로 반복 실행될 함수가 된다 - 아래 설명 참고).
|
|
15
|
+
*/
|
|
16
|
+
type?: SetIntervalType;
|
|
17
|
+
/** 매 tick마다 데코레이트된 메서드에 넘길 인자를 계산. type:'onConnected'에서만 의미가 있음 (없으면 인자 없이 호출). */
|
|
18
|
+
parameter?: (set: HelperHostSet) => any[];
|
|
19
|
+
/** 타이머 등록 직후 1회 호출 (디버깅/로깅용). id는 실제 setInterval 반환값 - cleanup은 자동으로 처리되므로 직접 clear할 필요 없음. */
|
|
20
|
+
created?: (set: HelperHostSet, id: number) => void;
|
|
21
|
+
/**
|
|
22
|
+
* type:'onConnected' - 매 tick마다 데코레이트된 메서드의 리턴값이 객체이고 이 키의 값이 함수이면 (id: number)로 호출한다.
|
|
23
|
+
* type:'returnValue' - 리턴값이 함수면 그 자체를, 객체면 이 키의 값이 함수일 때 그 값을 "실제로 반복 실행될 함수"로 사용한다.
|
|
24
|
+
* 안 주면 SET_INTERVAL_METADATA_KEY로 폴백한다 - applyAttribute.ts/applyNode.ts의 valueKey와 동일한 컨벤션
|
|
25
|
+
* (리턴 객체에서 이 데코레이터 몫만 뽑아 쓰고, 다른 데코레이터와 스택할 때만 커스텀 키로 구분하면 된다).
|
|
26
|
+
*/
|
|
27
|
+
valueKey?: symbol | string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SetIntervalMetadata {
|
|
31
|
+
propertyKey: string | symbol;
|
|
32
|
+
interval: number;
|
|
33
|
+
options: SetIntervalOptions;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const activeMap = new WeakMap<any, number[]>();
|
|
37
|
+
const getActiveEntries = (inst: any): number[] => {
|
|
38
|
+
let entries = activeMap.get(inst);
|
|
39
|
+
if (!entries) {
|
|
40
|
+
entries = [];
|
|
41
|
+
activeMap.set(inst, entries);
|
|
42
|
+
}
|
|
43
|
+
return entries;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* type:'onConnected' - connect 시 자동으로 setInterval을 걸고, disconnect 시 자동으로 clearInterval 한다.
|
|
48
|
+
* type:'returnValue'(기본값) - 메서드를 감싸지 않는 대신, 호출될 때마다(개발자가 직접 호출) 원본 로직을
|
|
49
|
+
* 그대로 실행하고 리턴값(또는 valueKey로 뽑은 값)이 함수면 그 함수를 새 setInterval의 반복 콜백으로 등록한다.
|
|
50
|
+
* 호출할 때마다 별도의 새 타이머가 생기며, disconnect 시 전부 정리된다. 원본 리턴값은 그대로 통과시키므로
|
|
51
|
+
* 다른 데코레이터와 같은 메서드에 스택해도 순서에 상관없이 안전하다.
|
|
52
|
+
*/
|
|
53
|
+
export function setInterval(interval: number, options: SetIntervalOptions = {}): MethodDecorator {
|
|
54
|
+
return (target: Object, propertyKey: string | symbol, descriptor?: PropertyDescriptor) => {
|
|
55
|
+
const constructor = target.constructor;
|
|
56
|
+
const normalizedOptions: SetIntervalOptions = { ...options, type: options.type ?? 'returnValue' };
|
|
57
|
+
|
|
58
|
+
let metaList = ReflectUtils.getOwnMetadata(SET_INTERVAL_METADATA_KEY, constructor) as SetIntervalMetadata[];
|
|
59
|
+
if (!metaList) {
|
|
60
|
+
metaList = [];
|
|
61
|
+
ReflectUtils.defineMetadata(SET_INTERVAL_METADATA_KEY, metaList, constructor);
|
|
62
|
+
}
|
|
63
|
+
metaList.push({ propertyKey, interval, options: normalizedOptions });
|
|
64
|
+
|
|
65
|
+
if (normalizedOptions.type === 'onConnected' || !descriptor) {
|
|
66
|
+
return; // LifeCycler가 connect/disconnect를 전담 - wrapping 불필요
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const original = descriptor.value;
|
|
70
|
+
descriptor.value = function (this: any, ...args: any[]) {
|
|
71
|
+
const res = original.apply(this, args);
|
|
72
|
+
// valueKey를 안 주면 이 데코레이터 전용 METADATA_KEY로 폴백 (applyAttribute.ts/applyNode.ts와 동일한 컨벤션)
|
|
73
|
+
const vk = normalizedOptions.valueKey ?? SET_INTERVAL_METADATA_KEY;
|
|
74
|
+
const tickFn: ((id: number) => void) | undefined =
|
|
75
|
+
typeof res === 'function' ? res : res && typeof res === 'object' && typeof res[vk] === 'function' ? res[vk] : undefined;
|
|
76
|
+
|
|
77
|
+
if (tickFn) {
|
|
78
|
+
ensureInit(this);
|
|
79
|
+
const conf = getElementConfig(this);
|
|
80
|
+
const win = (this as any)._resolveWindow?.(conf) || ((typeof window !== 'undefined' ? window : undefined) as Window);
|
|
81
|
+
const id = win.setInterval(() => {
|
|
82
|
+
try {
|
|
83
|
+
tickFn(id);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
console.error('[SWC] setInterval tick error:', e);
|
|
86
|
+
}
|
|
87
|
+
}, interval);
|
|
88
|
+
normalizedOptions.created?.(SwcUtils.getHelperAndHostSet(win, this), id);
|
|
89
|
+
getActiveEntries(this).push(id);
|
|
90
|
+
}
|
|
91
|
+
return res;
|
|
92
|
+
};
|
|
93
|
+
return descriptor;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const findAllSetIntervalMetadata = (target: any): SetIntervalMetadata[] => {
|
|
98
|
+
const constructor = target instanceof Function ? target : target.constructor;
|
|
99
|
+
return (ReflectUtils.findAllMetadata<SetIntervalMetadata[]>(SET_INTERVAL_METADATA_KEY, constructor) || []).flat();
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export class SetIntervalLifeCycler implements ElementDefineLifeCycler {
|
|
103
|
+
onConnected(helperHostSet: HelperHostSet): void {
|
|
104
|
+
const inst = helperHostSet.$this;
|
|
105
|
+
activeMap.delete(inst); // 재연결 대비 리셋
|
|
106
|
+
for (const meta of findAllSetIntervalMetadata(inst)) {
|
|
107
|
+
if (meta.options.type !== 'onConnected') continue; // returnValue 타입은 수동 호출을 기다림
|
|
108
|
+
const id = helperHostSet.$w.setInterval(() => {
|
|
109
|
+
const args = meta.options.parameter?.(helperHostSet) ?? [];
|
|
110
|
+
try {
|
|
111
|
+
const res = inst[meta.propertyKey](...args);
|
|
112
|
+
const vk = meta.options.valueKey ?? SET_INTERVAL_METADATA_KEY;
|
|
113
|
+
if (res && typeof res === 'object' && typeof res[vk] === 'function') {
|
|
114
|
+
res[vk](id);
|
|
115
|
+
}
|
|
116
|
+
} catch (e) {
|
|
117
|
+
console.error('[SWC] setInterval tick error:', e);
|
|
118
|
+
}
|
|
119
|
+
}, meta.interval);
|
|
120
|
+
meta.options.created?.(helperHostSet, id);
|
|
121
|
+
getActiveEntries(inst).push(id);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
onDisconnected(helperHostSet: HelperHostSet): void {
|
|
126
|
+
const inst = helperHostSet.$this;
|
|
127
|
+
for (const id of activeMap.get(inst) ?? []) {
|
|
128
|
+
try {
|
|
129
|
+
helperHostSet.$w.clearInterval(id);
|
|
130
|
+
} catch (e) {
|
|
131
|
+
console.error('[SWC] clearInterval error:', e);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
activeMap.delete(inst);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { ReflectUtils } from '@dooboostore/core';
|
|
2
|
+
import { ensureInit, getElementConfig } from './elementDefine';
|
|
3
|
+
import { SwcUtils } from '../utils/Utils';
|
|
4
|
+
import { ElementDefineLifeCycler, HelperHostSet } from '../types';
|
|
5
|
+
|
|
6
|
+
export const SET_TIMEOUT_METADATA_KEY = Symbol.for('simple-web-component:set-timeout');
|
|
7
|
+
|
|
8
|
+
export type SetTimeoutType = 'onConnected' | 'returnValue';
|
|
9
|
+
|
|
10
|
+
export interface SetTimeoutOptions {
|
|
11
|
+
/**
|
|
12
|
+
* 'onConnected' (connect 시 자동으로 예약, disconnect 시 자동 정리) |
|
|
13
|
+
* 'returnValue' (기본값 - 자동 예약 없음. 개발자가 이 메서드를 직접 호출해야 예약되고,
|
|
14
|
+
* 그 호출의 리턴값이 실제로 실행될 함수가 된다 - 아래 설명 참고).
|
|
15
|
+
*/
|
|
16
|
+
type?: SetTimeoutType;
|
|
17
|
+
/** 실행 시 데코레이트된 메서드에 넘길 인자를 계산. type:'onConnected'에서만 의미가 있음 (없으면 인자 없이 호출). */
|
|
18
|
+
parameter?: (set: HelperHostSet) => any[];
|
|
19
|
+
/** 타이머 등록 직후 1회 호출 (디버깅/로깅용). id는 실제 setTimeout 반환값 - cleanup은 자동으로 처리되므로 직접 clear할 필요 없음. */
|
|
20
|
+
created?: (set: HelperHostSet, id: number) => void;
|
|
21
|
+
/**
|
|
22
|
+
* type:'onConnected' - fire 시 데코레이트된 메서드의 리턴값이 객체이고 이 키의 값이 함수이면 (id: number)로 호출한다.
|
|
23
|
+
* type:'returnValue' - 리턴값이 함수면 그 자체를, 객체면 이 키의 값이 함수일 때 그 값을 "실제로 실행될 함수"로 사용한다.
|
|
24
|
+
* 안 주면 SET_TIMEOUT_METADATA_KEY로 폴백한다 - applyAttribute.ts/applyNode.ts의 valueKey와 동일한 컨벤션
|
|
25
|
+
* (리턴 객체에서 이 데코레이터 몫만 뽑아 쓰고, 다른 데코레이터와 스택할 때만 커스텀 키로 구분하면 된다).
|
|
26
|
+
*/
|
|
27
|
+
valueKey?: symbol | string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SetTimeoutMetadata {
|
|
31
|
+
propertyKey: string | symbol;
|
|
32
|
+
delay: number;
|
|
33
|
+
options: SetTimeoutOptions;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const activeMap = new WeakMap<any, number[]>();
|
|
37
|
+
const getActiveEntries = (inst: any): number[] => {
|
|
38
|
+
let entries = activeMap.get(inst);
|
|
39
|
+
if (!entries) {
|
|
40
|
+
entries = [];
|
|
41
|
+
activeMap.set(inst, entries);
|
|
42
|
+
}
|
|
43
|
+
return entries;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* type:'onConnected' - connect 시 자동으로 setTimeout을 걸고, disconnect 시 (아직 실행 전이라면) 자동으로 clearTimeout 한다.
|
|
48
|
+
* type:'returnValue'(기본값) - 메서드를 감싸지 않는 대신, 호출될 때마다(개발자가 직접 호출) 원본 로직을
|
|
49
|
+
* 그대로 실행하고 리턴값(또는 valueKey로 뽑은 값)이 함수면 그 함수를 새 setTimeout으로 예약한다.
|
|
50
|
+
* 호출할 때마다 별도의 새 타이머가 생기며, disconnect 시 (아직 실행 전인 것들은) 전부 정리된다.
|
|
51
|
+
* 원본 리턴값은 그대로 통과시키므로 다른 데코레이터와 같은 메서드에 스택해도 순서에 상관없이 안전하다.
|
|
52
|
+
*/
|
|
53
|
+
export function setTimeout(delay: number, options: SetTimeoutOptions = {}): MethodDecorator {
|
|
54
|
+
return (target: Object, propertyKey: string | symbol, descriptor?: PropertyDescriptor) => {
|
|
55
|
+
const constructor = target.constructor;
|
|
56
|
+
const normalizedOptions: SetTimeoutOptions = { ...options, type: options.type ?? 'returnValue' };
|
|
57
|
+
|
|
58
|
+
let metaList = ReflectUtils.getOwnMetadata(SET_TIMEOUT_METADATA_KEY, constructor) as SetTimeoutMetadata[];
|
|
59
|
+
if (!metaList) {
|
|
60
|
+
metaList = [];
|
|
61
|
+
ReflectUtils.defineMetadata(SET_TIMEOUT_METADATA_KEY, metaList, constructor);
|
|
62
|
+
}
|
|
63
|
+
metaList.push({ propertyKey, delay, options: normalizedOptions });
|
|
64
|
+
|
|
65
|
+
if (normalizedOptions.type === 'onConnected' || !descriptor) {
|
|
66
|
+
return; // LifeCycler가 connect/disconnect를 전담 - wrapping 불필요
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const original = descriptor.value;
|
|
70
|
+
descriptor.value = function (this: any, ...args: any[]) {
|
|
71
|
+
const res = original.apply(this, args);
|
|
72
|
+
// valueKey를 안 주면 이 데코레이터 전용 METADATA_KEY로 폴백 (applyAttribute.ts/applyNode.ts와 동일한 컨벤션)
|
|
73
|
+
const vk = normalizedOptions.valueKey ?? SET_TIMEOUT_METADATA_KEY;
|
|
74
|
+
const tickFn: ((id: number) => void) | undefined =
|
|
75
|
+
typeof res === 'function' ? res : res && typeof res === 'object' && typeof res[vk] === 'function' ? res[vk] : undefined;
|
|
76
|
+
|
|
77
|
+
if (tickFn) {
|
|
78
|
+
ensureInit(this);
|
|
79
|
+
const conf = getElementConfig(this);
|
|
80
|
+
const win = (this as any)._resolveWindow?.(conf) || ((typeof window !== 'undefined' ? window : undefined) as Window);
|
|
81
|
+
const id = win.setTimeout(() => {
|
|
82
|
+
const entries = getActiveEntries(this);
|
|
83
|
+
const idx = entries.indexOf(id);
|
|
84
|
+
if (idx >= 0) entries.splice(idx, 1);
|
|
85
|
+
try {
|
|
86
|
+
tickFn(id);
|
|
87
|
+
} catch (e) {
|
|
88
|
+
console.error('[SWC] setTimeout fire error:', e);
|
|
89
|
+
}
|
|
90
|
+
}, delay);
|
|
91
|
+
normalizedOptions.created?.(SwcUtils.getHelperAndHostSet(win, this), id);
|
|
92
|
+
getActiveEntries(this).push(id);
|
|
93
|
+
}
|
|
94
|
+
return res;
|
|
95
|
+
};
|
|
96
|
+
return descriptor;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const findAllSetTimeoutMetadata = (target: any): SetTimeoutMetadata[] => {
|
|
101
|
+
const constructor = target instanceof Function ? target : target.constructor;
|
|
102
|
+
return (ReflectUtils.findAllMetadata<SetTimeoutMetadata[]>(SET_TIMEOUT_METADATA_KEY, constructor) || []).flat();
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export class SetTimeoutLifeCycler implements ElementDefineLifeCycler {
|
|
106
|
+
onConnected(helperHostSet: HelperHostSet): void {
|
|
107
|
+
const inst = helperHostSet.$this;
|
|
108
|
+
activeMap.delete(inst); // 재연결 대비 리셋
|
|
109
|
+
for (const meta of findAllSetTimeoutMetadata(inst)) {
|
|
110
|
+
if (meta.options.type !== 'onConnected') continue; // returnValue 타입은 수동 호출을 기다림
|
|
111
|
+
const id = helperHostSet.$w.setTimeout(() => {
|
|
112
|
+
const args = meta.options.parameter?.(helperHostSet) ?? [];
|
|
113
|
+
try {
|
|
114
|
+
const res = inst[meta.propertyKey](...args);
|
|
115
|
+
const vk = meta.options.valueKey ?? SET_TIMEOUT_METADATA_KEY;
|
|
116
|
+
if (res && typeof res === 'object' && typeof res[vk] === 'function') {
|
|
117
|
+
res[vk](id);
|
|
118
|
+
}
|
|
119
|
+
} catch (e) {
|
|
120
|
+
console.error('[SWC] setTimeout fire error:', e);
|
|
121
|
+
}
|
|
122
|
+
}, meta.delay);
|
|
123
|
+
meta.options.created?.(helperHostSet, id);
|
|
124
|
+
getActiveEntries(inst).push(id);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
onDisconnected(helperHostSet: HelperHostSet): void {
|
|
129
|
+
const inst = helperHostSet.$this;
|
|
130
|
+
for (const id of activeMap.get(inst) ?? []) {
|
|
131
|
+
try {
|
|
132
|
+
helperHostSet.$w.clearTimeout(id);
|
|
133
|
+
} catch (e) {
|
|
134
|
+
console.error('[SWC] clearTimeout error:', e);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
activeMap.delete(inst);
|
|
138
|
+
}
|
|
139
|
+
}
|