@dooboostore/simple-web-component 1.0.49 → 1.0.51
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 +790 -337
- package/dist/cjs/SwcAppEngine.js.map +2 -2
- package/dist/cjs/decorators/addEventListener.js +1925 -22
- package/dist/cjs/decorators/addEventListener.js.map +2 -2
- package/dist/cjs/decorators/applyAttribute.js +41 -47
- package/dist/cjs/decorators/applyAttribute.js.map +2 -2
- package/dist/cjs/decorators/applyClass.js +14 -11
- package/dist/cjs/decorators/applyClass.js.map +2 -2
- package/dist/cjs/decorators/applyNode.js +19 -21
- package/dist/cjs/decorators/applyNode.js.map +2 -2
- package/dist/cjs/decorators/applyProperty.js +24 -48
- package/dist/cjs/decorators/applyProperty.js.map +2 -2
- package/dist/cjs/decorators/applySlot.js +9 -8
- package/dist/cjs/decorators/applySlot.js.map +2 -2
- package/dist/cjs/decorators/applyStyle.js +14 -11
- package/dist/cjs/decorators/applyStyle.js.map +2 -2
- package/dist/cjs/decorators/around.js +124 -0
- package/dist/cjs/decorators/around.js.map +7 -0
- package/dist/cjs/decorators/changedAttribute.js +61 -24
- package/dist/cjs/decorators/changedAttribute.js.map +2 -2
- package/dist/cjs/decorators/elementDefine.js +240 -196
- package/dist/cjs/decorators/elementDefine.js.map +3 -3
- package/dist/cjs/decorators/emitCustomEvent.js +14 -12
- package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
- package/dist/cjs/decorators/eventMedia.js +112 -0
- package/dist/cjs/decorators/eventMedia.js.map +7 -0
- package/dist/cjs/decorators/fetch.js +278 -0
- package/dist/cjs/decorators/fetch.js.map +7 -0
- package/dist/cjs/decorators/index.js +8 -2
- package/dist/cjs/decorators/index.js.map +2 -2
- package/dist/cjs/decorators/intersectionObserver.js +76 -65
- package/dist/cjs/decorators/intersectionObserver.js.map +2 -2
- package/dist/cjs/decorators/lifecycles.js +11 -11
- package/dist/cjs/decorators/lifecycles.js.map +2 -2
- package/dist/cjs/decorators/mutationObserver.js +97 -90
- package/dist/cjs/decorators/mutationObserver.js.map +3 -3
- package/dist/cjs/decorators/parameter.js +156 -0
- package/dist/cjs/decorators/parameter.js.map +7 -0
- package/dist/cjs/decorators/publishSwcAppMessage.js +2 -2
- package/dist/cjs/decorators/publishSwcAppMessage.js.map +2 -2
- package/dist/cjs/decorators/query.js +11 -5
- package/dist/cjs/decorators/query.js.map +2 -2
- package/dist/cjs/decorators/requestAnimationFrame.js +115 -0
- package/dist/cjs/decorators/requestAnimationFrame.js.map +7 -0
- package/dist/cjs/decorators/resizeObserver.js +84 -69
- package/dist/cjs/decorators/resizeObserver.js.map +3 -3
- package/dist/cjs/decorators/setInterval.js +65 -22
- package/dist/cjs/decorators/setInterval.js.map +2 -2
- package/dist/cjs/decorators/setTimeout.js +68 -22
- package/dist/cjs/decorators/setTimeout.js.map +2 -2
- package/dist/cjs/decorators/state.js +13 -10
- package/dist/cjs/decorators/state.js.map +2 -2
- package/dist/cjs/decorators/{subscribeSwcAppMessageWhileConnected.js → subscribeSwcAppMessage.js} +20 -21
- package/dist/cjs/decorators/subscribeSwcAppMessage.js.map +7 -0
- package/dist/cjs/decorators/{subscribeSwcAppRouteChangeWhileConnected.js → subscribeSwcAppRouteChange.js} +23 -19
- package/dist/cjs/decorators/subscribeSwcAppRouteChange.js.map +7 -0
- package/dist/cjs/decorators/swcAppRoute.js +149 -0
- package/dist/cjs/decorators/swcAppRoute.js.map +7 -0
- package/dist/cjs/elements/SwcApp.js +70 -0
- package/dist/cjs/elements/SwcApp.js.map +2 -2
- package/dist/cjs/elements/SwcAppMixin.js +256 -67
- package/dist/cjs/elements/SwcAppMixin.js.map +3 -3
- package/dist/cjs/types/index.js +5 -1
- package/dist/cjs/types/index.js.map +2 -2
- package/dist/cjs/types/window.d.js.map +1 -1
- package/dist/cjs/utils/Utils.js +36 -15
- package/dist/cjs/utils/Utils.js.map +2 -2
- package/dist/esm/SwcAppEngine.js.map +2 -2
- package/dist/esm/decorators/addEventListener.js +1925 -22
- package/dist/esm/decorators/addEventListener.js.map +2 -2
- package/dist/esm/decorators/applyAttribute.js +41 -47
- package/dist/esm/decorators/applyAttribute.js.map +2 -2
- package/dist/esm/decorators/applyClass.js +14 -11
- package/dist/esm/decorators/applyClass.js.map +2 -2
- package/dist/esm/decorators/applyNode.js +20 -22
- package/dist/esm/decorators/applyNode.js.map +2 -2
- package/dist/esm/decorators/applyProperty.js +24 -48
- package/dist/esm/decorators/applyProperty.js.map +2 -2
- package/dist/esm/decorators/applySlot.js +9 -8
- package/dist/esm/decorators/applySlot.js.map +2 -2
- package/dist/esm/decorators/applyStyle.js +14 -11
- package/dist/esm/decorators/applyStyle.js.map +2 -2
- package/dist/esm/decorators/around.js +105 -0
- package/dist/esm/decorators/around.js.map +7 -0
- package/dist/esm/decorators/changedAttribute.js +61 -24
- package/dist/esm/decorators/changedAttribute.js.map +2 -2
- package/dist/esm/decorators/elementDefine.js +238 -194
- package/dist/esm/decorators/elementDefine.js.map +3 -3
- package/dist/esm/decorators/emitCustomEvent.js +15 -13
- package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
- package/dist/esm/decorators/eventMedia.js +94 -0
- package/dist/esm/decorators/eventMedia.js.map +7 -0
- package/dist/esm/decorators/fetch.js +259 -0
- package/dist/esm/decorators/fetch.js.map +7 -0
- package/dist/esm/decorators/index.js +8 -2
- package/dist/esm/decorators/index.js.map +2 -2
- package/dist/esm/decorators/intersectionObserver.js +76 -65
- package/dist/esm/decorators/intersectionObserver.js.map +2 -2
- package/dist/esm/decorators/lifecycles.js +11 -11
- package/dist/esm/decorators/lifecycles.js.map +2 -2
- package/dist/esm/decorators/mutationObserver.js +97 -90
- package/dist/esm/decorators/mutationObserver.js.map +3 -3
- package/dist/esm/decorators/parameter.js +137 -0
- package/dist/esm/decorators/parameter.js.map +7 -0
- package/dist/esm/decorators/publishSwcAppMessage.js +2 -2
- package/dist/esm/decorators/publishSwcAppMessage.js.map +2 -2
- package/dist/esm/decorators/query.js +11 -5
- package/dist/esm/decorators/query.js.map +2 -2
- package/dist/esm/decorators/requestAnimationFrame.js +96 -0
- package/dist/esm/decorators/requestAnimationFrame.js.map +7 -0
- package/dist/esm/decorators/resizeObserver.js +84 -69
- package/dist/esm/decorators/resizeObserver.js.map +2 -2
- package/dist/esm/decorators/setInterval.js +65 -23
- package/dist/esm/decorators/setInterval.js.map +2 -2
- package/dist/esm/decorators/setTimeout.js +68 -23
- package/dist/esm/decorators/setTimeout.js.map +2 -2
- package/dist/esm/decorators/state.js +13 -10
- package/dist/esm/decorators/state.js.map +2 -2
- package/dist/esm/decorators/subscribeSwcAppMessage.js +53 -0
- package/dist/esm/decorators/subscribeSwcAppMessage.js.map +7 -0
- package/dist/esm/decorators/{subscribeSwcAppRouteChangeWhileConnected.js → subscribeSwcAppRouteChange.js} +20 -16
- package/dist/esm/decorators/subscribeSwcAppRouteChange.js.map +7 -0
- package/dist/esm/decorators/swcAppRoute.js +130 -0
- package/dist/esm/decorators/swcAppRoute.js.map +7 -0
- package/dist/esm/elements/SwcApp.js +70 -0
- package/dist/esm/elements/SwcApp.js.map +2 -2
- package/dist/esm/elements/SwcAppMixin.js +258 -69
- package/dist/esm/elements/SwcAppMixin.js.map +3 -3
- package/dist/esm/types/index.js +5 -1
- package/dist/esm/types/index.js.map +2 -2
- package/dist/esm/utils/Utils.js +36 -15
- package/dist/esm/utils/Utils.js.map +2 -2
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +4395 -1266
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
- package/dist/types/SwcAppEngine.d.ts +1 -0
- package/dist/types/SwcAppEngine.d.ts.map +1 -1
- package/dist/types/decorators/addEventListener.d.ts +936 -11
- package/dist/types/decorators/addEventListener.d.ts.map +1 -1
- package/dist/types/decorators/applyAttribute.d.ts +27 -37
- package/dist/types/decorators/applyAttribute.d.ts.map +1 -1
- package/dist/types/decorators/applyClass.d.ts +2 -2
- package/dist/types/decorators/applyClass.d.ts.map +1 -1
- package/dist/types/decorators/applyNode.d.ts +16 -16
- package/dist/types/decorators/applyNode.d.ts.map +1 -1
- package/dist/types/decorators/applyProperty.d.ts +28 -44
- 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 +2 -2
- package/dist/types/decorators/applyStyle.d.ts.map +1 -1
- package/dist/types/decorators/around.d.ts +87 -0
- package/dist/types/decorators/around.d.ts.map +1 -0
- package/dist/types/decorators/changedAttribute.d.ts +22 -4
- package/dist/types/decorators/changedAttribute.d.ts.map +1 -1
- package/dist/types/decorators/elementDefine.d.ts.map +1 -1
- package/dist/types/decorators/emitCustomEvent.d.ts +0 -1
- package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
- package/dist/types/decorators/eventMedia.d.ts +48 -0
- package/dist/types/decorators/eventMedia.d.ts.map +1 -0
- package/dist/types/decorators/fetch.d.ts +139 -0
- package/dist/types/decorators/fetch.d.ts.map +1 -0
- package/dist/types/decorators/index.d.ts +8 -2
- package/dist/types/decorators/index.d.ts.map +1 -1
- package/dist/types/decorators/intersectionObserver.d.ts +32 -10
- package/dist/types/decorators/intersectionObserver.d.ts.map +1 -1
- package/dist/types/decorators/lifecycles.d.ts +8 -7
- package/dist/types/decorators/lifecycles.d.ts.map +1 -1
- package/dist/types/decorators/mutationObserver.d.ts +34 -12
- package/dist/types/decorators/mutationObserver.d.ts.map +1 -1
- package/dist/types/decorators/parameter.d.ts +113 -0
- package/dist/types/decorators/parameter.d.ts.map +1 -0
- package/dist/types/decorators/publishSwcAppMessage.d.ts +0 -1
- package/dist/types/decorators/publishSwcAppMessage.d.ts.map +1 -1
- package/dist/types/decorators/query.d.ts +7 -7
- 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 +34 -12
- package/dist/types/decorators/resizeObserver.d.ts.map +1 -1
- package/dist/types/decorators/setInterval.d.ts +27 -5
- package/dist/types/decorators/setInterval.d.ts.map +1 -1
- package/dist/types/decorators/setTimeout.d.ts +27 -5
- package/dist/types/decorators/setTimeout.d.ts.map +1 -1
- package/dist/types/decorators/state.d.ts.map +1 -1
- package/dist/types/decorators/subscribeSwcAppMessage.d.ts +44 -0
- package/dist/types/decorators/subscribeSwcAppMessage.d.ts.map +1 -0
- package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts +60 -0
- package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts.map +1 -0
- package/dist/types/decorators/swcAppRoute.d.ts +94 -0
- package/dist/types/decorators/swcAppRoute.d.ts.map +1 -0
- package/dist/types/elements/SwcApp.d.ts.map +1 -1
- package/dist/types/elements/SwcAppMixin.d.ts +423 -388
- package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
- package/dist/types/types/index.d.ts +43 -4
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/types/utils/Utils.d.ts +15 -5
- package/dist/types/utils/Utils.d.ts.map +1 -1
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +4395 -1266
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
- package/package.json +8 -6
- package/src/SwcAppEngine.ts +1 -0
- package/src/decorators/addEventListener.ts +1086 -28
- package/src/decorators/applyAttribute.ts +82 -134
- package/src/decorators/applyClass.ts +14 -10
- package/src/decorators/applyNode.ts +39 -37
- package/src/decorators/applyProperty.ts +72 -100
- package/src/decorators/applySlot.ts +9 -8
- package/src/decorators/applyStyle.ts +14 -10
- package/src/decorators/around.ts +196 -0
- package/src/decorators/changedAttribute.ts +65 -26
- package/src/decorators/elementDefine.ts +298 -220
- package/src/decorators/emitCustomEvent.ts +17 -13
- package/src/decorators/eventMedia.ts +119 -0
- package/src/decorators/fetch.ts +454 -0
- package/src/decorators/index.ts +8 -2
- package/src/decorators/intersectionObserver.ts +108 -78
- package/src/decorators/lifecycles.ts +24 -22
- package/src/decorators/mutationObserver.ts +140 -104
- package/src/decorators/parameter.ts +212 -0
- package/src/decorators/publishSwcAppMessage.ts +3 -4
- package/src/decorators/query.ts +22 -14
- package/src/decorators/requestAnimationFrame.ts +149 -0
- package/src/decorators/resizeObserver.ts +119 -82
- package/src/decorators/setInterval.ts +90 -23
- package/src/decorators/setTimeout.ts +93 -23
- package/src/decorators/state.ts +19 -13
- package/src/decorators/subscribeSwcAppMessage.ts +106 -0
- package/src/decorators/subscribeSwcAppRouteChange.ts +131 -0
- package/src/decorators/swcAppRoute.ts +231 -0
- package/src/elements/SwcApp.ts +40 -10
- package/src/elements/SwcAppMixin.ts +329 -85
- package/src/types/index.ts +38 -5
- package/src/types/window.d.ts +2 -18
- package/src/utils/Utils.ts +50 -18
- package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js.map +0 -7
- package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.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/subscribeSwcAppMessageWhileConnected.js +0 -54
- package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js.map +0 -7
- package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.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/subscribeSwcAppMessageWhileConnected.d.ts +0 -23
- package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts.map +0 -1
- package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts +0 -31
- package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.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/subscribeSwcAppMessageWhileConnected.ts +0 -91
- package/src/decorators/subscribeSwcAppRouteChangeWhileConnected.ts +0 -107
- package/src/examples/RxJSOperatorsExample.ts +0 -279
|
@@ -11,15 +11,21 @@ import {getQueryMetadata, getQueryAllMetadata} from './query';
|
|
|
11
11
|
import {SwcUtils} from '../utils/Utils';
|
|
12
12
|
import {DOM_EVENT_NAMES, HTML_TAG_ENTRIES} from '../config/config';
|
|
13
13
|
import {SituationTypeContainer, SituationTypeContainers} from '@dooboostore/simple-boot/decorators/inject/Inject';
|
|
14
|
-
import {
|
|
14
|
+
import {FirstCheckMaker} from '@dooboostore/simple-boot';
|
|
15
|
+
import {buildSwcParameterArgs, getParameterMetadata} from './parameter';
|
|
16
|
+
import {ElementDefineLifeCycler, HelperHostSet, HostSet, InjectSituationType, IntersectionObserverSet, MutationObserverSet, MutationObserverSetEntry, ObserverScope, OnConnectedResult, ResizeObserverSet, ResizeObserverSetEntry, SwcRootType} from '../types';
|
|
15
17
|
import {ConvertUtils, ElementApply} from '@dooboostore/core-web';
|
|
16
18
|
import {isSSR} from "../elements/SwcAppMixin";
|
|
17
19
|
import {findAllStateMetadata} from "./state";
|
|
20
|
+
import {findAllAroundMetadata} from "./around";
|
|
18
21
|
import {findAllPropertyMetadata} from "./applyProperty";
|
|
19
|
-
import {MessageSubscribeLifeCycler} from "./
|
|
20
|
-
import {RouteSubscribeLifeCycler} from "./
|
|
22
|
+
import {MessageSubscribeLifeCycler} from "./subscribeSwcAppMessage";
|
|
23
|
+
import {RouteSubscribeLifeCycler} from "./subscribeSwcAppRouteChange";
|
|
21
24
|
import {SetIntervalLifeCycler} from "./setInterval";
|
|
22
25
|
import {SetTimeoutLifeCycler} from "./setTimeout";
|
|
26
|
+
import {RequestAnimationFrameLifeCycler} from "./requestAnimationFrame";
|
|
27
|
+
import {EventMediaLifeCycler} from "./eventMedia";
|
|
28
|
+
import {FetchLifeCycler} from "./fetch";
|
|
23
29
|
|
|
24
30
|
// --- Core Interfaces & Types ---
|
|
25
31
|
|
|
@@ -39,7 +45,7 @@ export interface ElementMetadata extends Omit<ElementConfig, 'window'> {
|
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
export const getElementConfig = (target: any): ElementMetadata | undefined => {
|
|
42
|
-
const constructor = target
|
|
48
|
+
const constructor = typeof target === 'function' ? target : target.constructor;
|
|
43
49
|
return ReflectUtils.getMetadata(ELEMENT_CONFIG_KEY, constructor);
|
|
44
50
|
};
|
|
45
51
|
|
|
@@ -53,7 +59,7 @@ export const ensureInit = (inst: any) => { // HTMLElement
|
|
|
53
59
|
// delete (inst as any)[meta.propertyKey];
|
|
54
60
|
// });
|
|
55
61
|
|
|
56
|
-
const target = inst
|
|
62
|
+
const target = typeof inst === 'function' ? inst : inst.constructor;
|
|
57
63
|
|
|
58
64
|
// ╔════════════════════════════════════════════════════════════════════════════════╗
|
|
59
65
|
// ║ decorator 필드의 own property 삭제 ║
|
|
@@ -112,6 +118,19 @@ export const ensureInit = (inst: any) => { // HTMLElement
|
|
|
112
118
|
const propertyAllList = findAllPropertyMetadata(target).filter(it => it.type === 'property');
|
|
113
119
|
if (propertyAllList) {
|
|
114
120
|
propertyAllList.forEach(meta => {
|
|
121
|
+
// getter가 없으면(순수 필드: declare·하이드레이션 값 등) 지우지 않음.
|
|
122
|
+
// getter가 있을 때만 own 값을 지워 getter가 DOM을 새로 읽게 한다.
|
|
123
|
+
let proto = Object.getPrototypeOf(inst);
|
|
124
|
+
let hasGetter = false;
|
|
125
|
+
while (proto && proto !== Object.prototype) {
|
|
126
|
+
const d = Object.getOwnPropertyDescriptor(proto, meta.propertyKey);
|
|
127
|
+
if (d) {
|
|
128
|
+
hasGetter = typeof d.get === 'function';
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
proto = Object.getPrototypeOf(proto);
|
|
132
|
+
}
|
|
133
|
+
if (!hasGetter) return;
|
|
115
134
|
delete (inst as any)[meta.propertyKey];
|
|
116
135
|
});
|
|
117
136
|
}
|
|
@@ -119,9 +138,31 @@ export const ensureInit = (inst: any) => { // HTMLElement
|
|
|
119
138
|
const stateAllList = findAllStateMetadata(target);
|
|
120
139
|
if (stateAllList) {
|
|
121
140
|
stateAllList.forEach(meta => {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
(
|
|
141
|
+
// getter로 읽으면 안 됨 — own이 남아있을 때 getter가 이주까지 해버려
|
|
142
|
+
// setter 재대입과 합치면 DOM 동기화가 중복 실행됨. descriptor에서 raw를 꺼낸다.
|
|
143
|
+
// own이 없으면(이미 정리됨) 스킵.
|
|
144
|
+
const own = Object.getOwnPropertyDescriptor(inst, meta.propertyKey);
|
|
145
|
+
if (own && 'value' in own) {
|
|
146
|
+
const initUserData = own.value;
|
|
147
|
+
delete (inst as any)[meta.propertyKey];
|
|
148
|
+
(inst as any)[meta.propertyKey] = initUserData;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// @around 필드의 own property 정리 → setter 경유로 재대입 (초기값에 set() 적용).
|
|
154
|
+
// attribute/query처럼 plain delete하면 초기값이 날아간다 — 반드시 살려서 setter로 넣는다.
|
|
155
|
+
// getter로 읽으면 안 됨(before 변환값이 섞임) — descriptor에서 raw를 꺼낸다.
|
|
156
|
+
// own이 없으면(이미 정리됨) 스킵 — 그래야 중복 변환이 안 생긴다.
|
|
157
|
+
const aroundAllList = findAllAroundMetadata(target);
|
|
158
|
+
if (aroundAllList) {
|
|
159
|
+
aroundAllList.forEach(meta => {
|
|
160
|
+
const own = Object.getOwnPropertyDescriptor(inst, meta.propertyKey);
|
|
161
|
+
if (own && 'value' in own) {
|
|
162
|
+
const initUserData = own.value;
|
|
163
|
+
delete (inst as any)[meta.propertyKey];
|
|
164
|
+
(inst as any)[meta.propertyKey] = initUserData;
|
|
165
|
+
}
|
|
125
166
|
});
|
|
126
167
|
}
|
|
127
168
|
|
|
@@ -151,7 +192,7 @@ const handleGlobalSwcEvent = async (event: Event) => {
|
|
|
151
192
|
if (!(node instanceof HTMLElement)) continue;
|
|
152
193
|
const script = node.getAttribute?.(attrName);
|
|
153
194
|
if (script && !getElementConfig(node)) {
|
|
154
|
-
const host: any = SwcUtils.
|
|
195
|
+
const host: any = SwcUtils.findNearestSwcAncestorHost(node);
|
|
155
196
|
if (host && host.isConnected && typeof host.__swc_executeAttributeEvent === 'function') {
|
|
156
197
|
await host.__swc_executeAttributeEvent(node, attrName, script, event);
|
|
157
198
|
(event as any).__swc_handled = true;
|
|
@@ -209,8 +250,7 @@ const setupPrototype = (proto: any, win: Window) => {
|
|
|
209
250
|
const script = this.getAttribute(attrName);
|
|
210
251
|
if (script) {
|
|
211
252
|
try {
|
|
212
|
-
const
|
|
213
|
-
const currentWin = (this as any)._resolveWindow(conf);
|
|
253
|
+
const currentWin = SwcUtils.resolveWindow(this);
|
|
214
254
|
const helpers = SwcUtils.getHelperSet(currentWin);
|
|
215
255
|
const args = {...hostSet, ...helpers, ...extraArgs, $el: this, $root: this.getRootNode()};
|
|
216
256
|
FunctionUtils.execute({script, context: this, args});
|
|
@@ -220,22 +260,18 @@ const setupPrototype = (proto: any, win: Window) => {
|
|
|
220
260
|
}
|
|
221
261
|
};
|
|
222
262
|
|
|
223
|
-
proto._resolveWindow = function (localConfig?: ElementConfig): Window {
|
|
224
|
-
ensureInit(this);
|
|
225
|
-
if (localConfig?.window) return localConfig.window;
|
|
226
|
-
const ancestors = SwcUtils.findAllSwcHostsIncludingSelf(this as any);
|
|
227
|
-
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
228
|
-
const aConf = getElementConfig(ancestors[i]);
|
|
229
|
-
if (aConf?.window) return aConf.window;
|
|
230
|
-
}
|
|
231
|
-
return getElementConfig(this).window ?? ((typeof window !== 'undefined' ? window : undefined) as Window);
|
|
232
|
-
};
|
|
233
|
-
|
|
234
263
|
proto._invokeLifecycleMethod = function (methodName: string | symbol, hostSet?: HostSet, extraArgs: any[] = []) {
|
|
235
264
|
ensureInit(this);
|
|
236
265
|
if (typeof (this as any)[methodName] !== 'function') return;
|
|
237
|
-
const useHostSet = hostSet ?? SwcUtils.getHelperAndHostSet(
|
|
266
|
+
const useHostSet = hostSet ?? SwcUtils.getHelperAndHostSet(this);
|
|
238
267
|
const app = useHostSet?.$appHost?.simpleApplication;
|
|
268
|
+
|
|
269
|
+
// @hostSet/@helperHostSet/@helperSet(parameter.ts)를 위한 값. lifecycle 메서드는
|
|
270
|
+
// event/matched 개념이 없으므로 이 세 가지만 채운다.
|
|
271
|
+
const helperSetValue = SwcUtils.getHelperSet(win);
|
|
272
|
+
const helperHostSetValue = {...helperSetValue, ...useHostSet, $this: this};
|
|
273
|
+
const kindValues = {hostSet: useHostSet, helperHostSet: helperHostSetValue, helperSet: helperSetValue};
|
|
274
|
+
|
|
239
275
|
// console.log('---->hh',app, this, methodName);
|
|
240
276
|
if (app) {
|
|
241
277
|
const otherStorage = new Map<any, any>();
|
|
@@ -248,16 +284,33 @@ const setupPrototype = (proto: any, win: Window) => {
|
|
|
248
284
|
}), new SituationTypeContainer({situationType: InjectSituationType.LAST_APP_HOST, data: useHostSet.$lastAppHost})]);
|
|
249
285
|
otherStorage.set(SituationTypeContainers, situations);
|
|
250
286
|
|
|
287
|
+
// @hostSet/@helperHostSet/@helperSet가 붙은 파라미터는 @Inject/situationType 기반
|
|
288
|
+
// 해석보다 먼저 처리되고(firstCheckMaker), 그 외 파라미터는 기존처럼 @Inject/타입
|
|
289
|
+
// 기반으로 정상 해석된다 — 같은 메서드에서 두 방식을 섞어 써도 된다.
|
|
290
|
+
// swc 파라미터 데코레이터가 붙은 자리는 DI 로 넘기지 않는다. lifecycle 이 못 채우는 kind
|
|
291
|
+
// (예: @fetchSettled — @fetch 가 나중에 채움)는 null. undefined 를 돌려주면
|
|
292
|
+
// "처리 안 함"으로 보고 DI 가 파라미터 타입(Object 등)을 resolve 하려다 SimNoSuch 로 죽는다.
|
|
293
|
+
const firstCheckMaker: FirstCheckMaker = ({target, targetKey}, token, idx) => {
|
|
294
|
+
const saves = getParameterMetadata(target, targetKey!);
|
|
295
|
+
const found = saves.find(s => s.index === idx);
|
|
296
|
+
return found ? ((kindValues as any)[found.kind] ?? null) : undefined;
|
|
297
|
+
};
|
|
298
|
+
|
|
251
299
|
return app.simstanceManager.executeBindParameterSim(
|
|
252
300
|
{
|
|
253
301
|
target: this,
|
|
254
302
|
targetKey: methodName,
|
|
255
|
-
inputParameters: extraArgs
|
|
303
|
+
inputParameters: extraArgs,
|
|
304
|
+
firstCheckMaker: [firstCheckMaker]
|
|
256
305
|
},
|
|
257
306
|
otherStorage
|
|
258
307
|
);
|
|
259
308
|
} else {
|
|
260
|
-
|
|
309
|
+
// SimpleApplication(DI 컨테이너)이 없는 standalone 사용 시에도 @hostSet/@helperHostSet/
|
|
310
|
+
// @helperSet만큼은 buildSwcParameterArgs로 주입해준다 — 해당 데코레이터가 하나도 없으면
|
|
311
|
+
// extraArgs를 그대로 반환하므로(기존 동작 그대로) 하위호환 깨지지 않는다.
|
|
312
|
+
const args = buildSwcParameterArgs(this, methodName, kindValues, extraArgs);
|
|
313
|
+
return (this as any)[methodName](...args);
|
|
261
314
|
}
|
|
262
315
|
};
|
|
263
316
|
|
|
@@ -279,8 +332,7 @@ const setupPrototype = (proto: any, win: Window) => {
|
|
|
279
332
|
|
|
280
333
|
const handler = async (event: any) => {
|
|
281
334
|
const hostSet = SwcUtils.getHostSet(el);
|
|
282
|
-
const
|
|
283
|
-
const currentWin = (this as any)._resolveWindow(conf);
|
|
335
|
+
const currentWin = SwcUtils.resolveWindow(this);
|
|
284
336
|
const helpers = SwcUtils.getHelperSet(currentWin);
|
|
285
337
|
const args = {
|
|
286
338
|
event,
|
|
@@ -301,8 +353,7 @@ const setupPrototype = (proto: any, win: Window) => {
|
|
|
301
353
|
ensureInit(this);
|
|
302
354
|
|
|
303
355
|
const hostSet = SwcUtils.getHostSet(el);
|
|
304
|
-
const
|
|
305
|
-
const currentWin = (this as any)._resolveWindow(conf);
|
|
356
|
+
const currentWin = SwcUtils.resolveWindow(this);
|
|
306
357
|
const currentHelpers = SwcUtils.getHelperSet(currentWin);
|
|
307
358
|
const detail = (event as CustomEvent).detail;
|
|
308
359
|
const args = {
|
|
@@ -319,6 +370,11 @@ const setupPrototype = (proto: any, win: Window) => {
|
|
|
319
370
|
};
|
|
320
371
|
};
|
|
321
372
|
|
|
373
|
+
// 부모 클래스도 elementDefine 됐으면 proto.xxxCallback 은 부모의 swc 래퍼다 — 그걸 원래 함수로 부르면 라이프사이클이 두 번 돈다.
|
|
374
|
+
// 래퍼에 기억해 둔 사용자 원래 함수(없으면 undefined)를 쓴다.
|
|
375
|
+
const SWC_ORIGINAL_CALLBACK = Symbol.for('simple-web-component:original-callback');
|
|
376
|
+
const userCallback = (fn: any) => (typeof fn === 'function' && SWC_ORIGINAL_CALLBACK in fn ? fn[SWC_ORIGINAL_CALLBACK] : fn);
|
|
377
|
+
|
|
322
378
|
export const elementDefine =
|
|
323
379
|
(name: string, config: Partial<ElementConfig> = {}): ClassDecorator =>
|
|
324
380
|
(constructor: any) => {
|
|
@@ -357,34 +413,87 @@ export const elementDefine =
|
|
|
357
413
|
const proto = constructor.prototype;
|
|
358
414
|
setupPrototype(proto, win);
|
|
359
415
|
|
|
360
|
-
//
|
|
361
|
-
|
|
416
|
+
// helperHostSet 은 인스턴스마다 다르다 — 클래스 단위 변수로 두면 다른 인스턴스의 connect 가 덮어써서
|
|
417
|
+
// disconnect 때 엉뚱한 인스턴스가 정리된다. connect 시점 값을 인스턴스에 저장해 disconnect/adopt 에서 쓴다.
|
|
418
|
+
const connectedHelperHostSet = (inst: any): HelperHostSet => inst.__swc_helperHostSet ?? SwcUtils.getHelperAndHostSet(inst);
|
|
362
419
|
// ── cyclers: 데코레이터별 라이프사이클 위임 클래스 (elementDefine 시 1회 생성, 계속 재사용) ──
|
|
363
420
|
const eventCycler = new EventListenerLifeCycler();
|
|
421
|
+
const resizeCycler = new ResizeObserverLifeCycler();
|
|
422
|
+
const intersectionCycler = new IntersectionObserverLifeCycler();
|
|
423
|
+
const mutationCycler = new MutationObserverLifeCycler();
|
|
364
424
|
const cyclers: ElementDefineLifeCycler[] = [
|
|
365
425
|
eventCycler,
|
|
366
426
|
new ChangedAttributeLifeCycler(),
|
|
367
427
|
new EmitCustomEventLifeCycler(),
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
428
|
+
resizeCycler,
|
|
429
|
+
intersectionCycler,
|
|
430
|
+
mutationCycler,
|
|
371
431
|
new MessageSubscribeLifeCycler(),
|
|
372
432
|
new RouteSubscribeLifeCycler(),
|
|
373
433
|
new SetIntervalLifeCycler(),
|
|
374
434
|
new SetTimeoutLifeCycler(),
|
|
435
|
+
new RequestAnimationFrameLifeCycler(),
|
|
436
|
+
new EventMediaLifeCycler(),
|
|
437
|
+
new FetchLifeCycler(),
|
|
375
438
|
];
|
|
376
439
|
|
|
377
440
|
// observedAttributes 기여 cycler (define-time, constructor 기반)
|
|
378
441
|
const cyclerObservedAttributes = cyclers.flatMap(c => c.getObservedAttributeNames?.(constructor) ?? []);
|
|
379
442
|
const mergedObservedAttributes = [...new Set([...(metadata.observedAttributes ?? []), ...originalStaticObservedAttributes, ...cyclerObservedAttributes, ...attributeApplyNames, ...hostAttributes, ...swcLifecycleAttributes, ...swcOnEvents])];
|
|
380
443
|
|
|
381
|
-
//
|
|
382
|
-
|
|
444
|
+
// ── observer 빌드 (connect 1회당 상태 1개) ──
|
|
445
|
+
// MutationObserver·ResizeObserver 는 인스턴스당 1개씩만 만들고 모든 데코레이터가 공유한다 (delegate 추적 포함).
|
|
446
|
+
// 'self'(렌더 전): 자기 자신 대상 mutation/resize 만 붙여 첫 렌더부터 본다.
|
|
447
|
+
// 'rest'(렌더 후): 나머지 전부를 같은 옵저버에 덧붙인다.
|
|
448
|
+
// 시클러가 scope 별로 자기 메타만 담은 콜백을 주고, 메타는 자기 대상 레코드/엔트리만 받으므로 따로 나눌 필요가 없다.
|
|
449
|
+
// IntersectionObserver 는 옵션 그룹별로 1개 ('rest' 전용).
|
|
450
|
+
type IntersectionDelegate = { target: string; delegateRoot?: SwcRootType; observe: (el: Element) => void; unobserve: (el: Element) => void };
|
|
451
|
+
interface ObserverState {
|
|
452
|
+
observers: Array<MutationObserver | ResizeObserver | IntersectionObserver>;
|
|
453
|
+
ro?: ResizeObserver;
|
|
454
|
+
roCallbacks: Set<ResizeObserverSetEntry['callback']>;
|
|
455
|
+
roObserved: WeakSet<Element>;
|
|
456
|
+
resizeDelegates: Array<ResizeObserverSetEntry & { target: string }>;
|
|
457
|
+
intersectionDelegates: IntersectionDelegate[];
|
|
458
|
+
mo?: MutationObserver;
|
|
459
|
+
moCallbacks: Set<MutationObserverSetEntry['callback']>;
|
|
460
|
+
moTargets: Map<Element | ShadowRoot, MutationObserverInit>;
|
|
461
|
+
}
|
|
462
|
+
const newObserverState = (): ObserverState => ({
|
|
463
|
+
observers: [], roCallbacks: new Set(), roObserved: new WeakSet(), resizeDelegates: [], intersectionDelegates: [],
|
|
464
|
+
moCallbacks: new Set(), moTargets: new Map(),
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// 같은 target 이 여러 소스에서 지정되면 MutationObserverInit 을 합집합(최대 감지범위)으로 병합한다.
|
|
468
|
+
const mergeMutationInit = (existing: MutationObserverInit | undefined, next: MutationObserverInit): MutationObserverInit => {
|
|
469
|
+
if (!existing) return next;
|
|
470
|
+
const mergedFilter = existing.attributeFilter || next.attributeFilter
|
|
471
|
+
? [...new Set([...(existing.attributeFilter ?? []), ...(next.attributeFilter ?? [])])]
|
|
472
|
+
: undefined;
|
|
473
|
+
return {
|
|
474
|
+
childList: existing.childList || next.childList,
|
|
475
|
+
attributes: existing.attributes || next.attributes,
|
|
476
|
+
characterData: existing.characterData || next.characterData,
|
|
477
|
+
subtree: existing.subtree || next.subtree,
|
|
478
|
+
attributeOldValue: existing.attributeOldValue || next.attributeOldValue,
|
|
479
|
+
characterDataOldValue: existing.characterDataOldValue || next.characterDataOldValue,
|
|
480
|
+
attributeFilter: mergedFilter,
|
|
481
|
+
};
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
// delegate 셀렉터에 맞는 요소(자신 또는 하위)를 찾아 observe/unobserve — MutationObserver 콜백에서 동적 추적용
|
|
485
|
+
const forDelegateMatches = (n: Node, selector: string, fn: (el: Element) => void) => {
|
|
486
|
+
if (n.nodeType !== 1) return;
|
|
487
|
+
const el = n as HTMLElement;
|
|
488
|
+
const isThis = selector === '$this' || selector === '';
|
|
489
|
+
if (isThis || el.matches?.(selector) || el.closest?.(selector)) fn(el);
|
|
490
|
+
else el.querySelectorAll(selector).forEach(fn);
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
const buildObservers = async (helperHostSet: HelperHostSet, scope: ObserverScope, st: ObserverState, alive: () => boolean): Promise<void> => {
|
|
383
494
|
const inst = helperHostSet.$this;
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
let mutationObserverSet: MutationObserverSet = [];
|
|
387
|
-
let intersectionObserverSet: IntersectionObserverSet = [];
|
|
495
|
+
// 'self' 는 순수 수집기(observer 시클러)만 돌린다 — 리스너·타이머 등 부작용 시클러는 'rest' 에서 한 번만.
|
|
496
|
+
const activeCyclers = scope === 'self' ? [resizeCycler, mutationCycler] : cyclers;
|
|
388
497
|
|
|
389
498
|
// delegate 추적 root 해석: shadow/light/auto/all → 실제 root 목록
|
|
390
499
|
const resolveDelegateRoots = (delegateRoot: SwcRootType | undefined): (HTMLElement | ShadowRoot)[] => {
|
|
@@ -397,225 +506,143 @@ export const elementDefine =
|
|
|
397
506
|
return roots;
|
|
398
507
|
};
|
|
399
508
|
|
|
400
|
-
// 1.
|
|
401
|
-
|
|
402
|
-
|
|
509
|
+
// 1. 시클러 onConnected → observer Set 수집 (여러 시클러가 반환한 Set 을 전부 누적)
|
|
510
|
+
let resizeObserverSet: ResizeObserverSet = [];
|
|
511
|
+
let mutationObserverSet: MutationObserverSet = [];
|
|
512
|
+
let intersectionObserverSet: IntersectionObserverSet = [];
|
|
513
|
+
for (const c of activeCyclers) {
|
|
514
|
+
const result = (await c.onConnected?.(helperHostSet, resizeObserverSet, scope)) as OnConnectedResult | void;
|
|
515
|
+
// await 사이에 떼어졌으면(새 연결이 차례를 가져갔으면) 여기서 멈춘다 — 옵저버를 만들면 아무도 disconnect 하지 않아 샌다.
|
|
516
|
+
if (!alive()) return;
|
|
403
517
|
if (!result) continue;
|
|
404
518
|
if (result.resizeObserverSet?.length) resizeObserverSet = [...resizeObserverSet, ...result.resizeObserverSet];
|
|
405
519
|
if (result.mutationObserverSet?.length) mutationObserverSet = [...mutationObserverSet, ...result.mutationObserverSet];
|
|
406
520
|
if (result.intersectionObserverSet?.length) intersectionObserverSet = [...intersectionObserverSet, ...result.intersectionObserverSet];
|
|
407
521
|
}
|
|
408
522
|
|
|
409
|
-
// 2.
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
523
|
+
// 2. ResizeObserver (1개). delegate 항목은 셀렉터 문자열이라 초기 observe 하지 않고 MutationObserver 로 동적 추적한다.
|
|
524
|
+
const resizeObserve = (el: Element, opts?: ResizeObserverOptions) => {
|
|
525
|
+
if (!st.ro || st.roObserved.has(el)) return;
|
|
526
|
+
st.ro.observe(el, opts);
|
|
527
|
+
st.roObserved.add(el);
|
|
528
|
+
};
|
|
529
|
+
const resizeUnobserve = (el: Element) => {
|
|
530
|
+
if (!st.ro || !st.roObserved.has(el)) return;
|
|
531
|
+
st.ro.unobserve(el);
|
|
532
|
+
st.roObserved.delete(el);
|
|
533
|
+
};
|
|
534
|
+
const newResizeDelegates: ObserverState['resizeDelegates'] = [];
|
|
535
|
+
if (resizeObserverSet.length) {
|
|
536
|
+
if (!st.ro) {
|
|
537
|
+
st.ro = new win.ResizeObserver((entries, obs) => { for (const cb of st.roCallbacks) cb(entries, obs); });
|
|
538
|
+
st.observers.push(st.ro);
|
|
539
|
+
}
|
|
422
540
|
for (const e of resizeObserverSet) {
|
|
423
|
-
|
|
541
|
+
st.roCallbacks.add(e.callback);
|
|
542
|
+
if (e.delegate) {
|
|
543
|
+
if (typeof e.target === 'string') newResizeDelegates.push(e as ResizeObserverSetEntry & { target: string });
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
424
546
|
if (typeof e.target === 'string') {
|
|
425
547
|
// 문자열(non-delegate)이면 root 에서 검색된 요소들을 observe 대상으로 추가한다.
|
|
426
|
-
const
|
|
427
|
-
for (const sr of scope) {
|
|
428
|
-
sr.querySelectorAll(e.target).forEach(el => {
|
|
429
|
-
if (!resizeObserved.has(el)) { ro.observe(el, e.options); resizeObserved.add(el); }
|
|
430
|
-
});
|
|
431
|
-
}
|
|
548
|
+
for (const r of resolveDelegateRoots(e.delegateRoot)) r.querySelectorAll(e.target).forEach(el => resizeObserve(el, e.options));
|
|
432
549
|
continue;
|
|
433
550
|
}
|
|
434
|
-
ro.observe(e.target as Element, e.options);
|
|
435
|
-
if (e.target instanceof Element)
|
|
551
|
+
st.ro.observe(e.target as Element, e.options);
|
|
552
|
+
if (e.target instanceof Element) st.roObserved.add(e.target);
|
|
436
553
|
}
|
|
437
|
-
|
|
438
|
-
if (resizeObserved.has(el)) return;
|
|
439
|
-
ro.observe(el, opts);
|
|
440
|
-
resizeObserved.add(el);
|
|
441
|
-
};
|
|
442
|
-
resizeUnobserve = (el) => {
|
|
443
|
-
if (!resizeObserved.has(el)) return;
|
|
444
|
-
ro.unobserve(el);
|
|
445
|
-
resizeObserved.delete(el);
|
|
446
|
-
};
|
|
447
|
-
observers.push(ro);
|
|
554
|
+
st.resizeDelegates.push(...newResizeDelegates);
|
|
448
555
|
}
|
|
449
556
|
|
|
450
|
-
// IntersectionObserver: 옵션 그룹별로 1
|
|
451
|
-
|
|
452
|
-
// 동적 observe/unobserve 중복 방지를 위해 그룹별 WeakSet 으로 추적한다.
|
|
453
|
-
const intersectionDelegates: Array<{ target: string; delegateRoot?: SwcRootType; observe: (el: Element) => void; unobserve: (el: Element) => void }> = [];
|
|
557
|
+
// 3. IntersectionObserver: 옵션 그룹별로 1개. delegate 항목은 MutationObserver 로 동적 추적한다.
|
|
558
|
+
const newIntersectionDelegates: IntersectionDelegate[] = [];
|
|
454
559
|
for (const group of intersectionObserverSet) {
|
|
455
560
|
const ioCallbacks = [...new Set(group.observeTargets.map(e => e.callback))];
|
|
456
|
-
const io = new
|
|
457
|
-
for (const cb of ioCallbacks) cb(entries, obs);
|
|
458
|
-
}, group.options);
|
|
561
|
+
const io = new win.IntersectionObserver((entries, obs) => { for (const cb of ioCallbacks) cb(entries, obs); }, group.options);
|
|
459
562
|
const ioObserved = new WeakSet<Element>();
|
|
563
|
+
const observe = (el: Element) => { if (ioObserved.has(el)) return; io.observe(el); ioObserved.add(el); };
|
|
564
|
+
const unobserve = (el: Element) => { if (!ioObserved.has(el)) return; io.unobserve(el); ioObserved.delete(el); };
|
|
460
565
|
for (const e of group.observeTargets) {
|
|
461
|
-
if (e.delegate) {
|
|
462
|
-
const target = e.target as string;
|
|
463
|
-
const observe = (el: Element) => {
|
|
464
|
-
if (ioObserved.has(el)) return;
|
|
465
|
-
io.observe(el);
|
|
466
|
-
ioObserved.add(el);
|
|
467
|
-
};
|
|
468
|
-
const unobserve = (el: Element) => {
|
|
469
|
-
if (!ioObserved.has(el)) return;
|
|
470
|
-
io.unobserve(el);
|
|
471
|
-
ioObserved.delete(el);
|
|
472
|
-
};
|
|
473
|
-
intersectionDelegates.push({ target, delegateRoot: e.delegateRoot, observe, unobserve });
|
|
474
|
-
continue;
|
|
475
|
-
}
|
|
566
|
+
if (e.delegate) { newIntersectionDelegates.push({ target: e.target as string, delegateRoot: e.delegateRoot, observe, unobserve }); continue; }
|
|
476
567
|
if (typeof e.target === 'string') {
|
|
477
|
-
|
|
478
|
-
const scope = resolveDelegateRoots(e.delegateRoot);
|
|
479
|
-
for (const sr of scope) {
|
|
480
|
-
sr.querySelectorAll(e.target).forEach(el => {
|
|
481
|
-
if (!ioObserved.has(el)) { io.observe(el); ioObserved.add(el); }
|
|
482
|
-
});
|
|
483
|
-
}
|
|
568
|
+
for (const r of resolveDelegateRoots(e.delegateRoot)) r.querySelectorAll(e.target).forEach(observe);
|
|
484
569
|
continue;
|
|
485
570
|
}
|
|
486
|
-
|
|
487
|
-
if (e.target instanceof Element) ioObserved.add(e.target);
|
|
571
|
+
observe(e.target as Element);
|
|
488
572
|
}
|
|
489
|
-
observers.push(io);
|
|
573
|
+
st.observers.push(io);
|
|
490
574
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
observe ? resizeObserve(sub as HTMLElement, d.options)
|
|
509
|
-
: resizeUnobserve(sub as HTMLElement);
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
};
|
|
514
|
-
const handleIntersectionDelegate = (n: Node, observe: boolean) => {
|
|
515
|
-
if (n.nodeType !== 1) return;
|
|
516
|
-
const el = n as HTMLElement;
|
|
517
|
-
for (const d of intersectionDelegates) {
|
|
518
|
-
const isThis = d.target === '$this' || d.target === '';
|
|
519
|
-
const matchesSel = (node: any) => {
|
|
520
|
-
if (!node || node.nodeType !== 1) return false;
|
|
521
|
-
if (isThis) return true;
|
|
522
|
-
return (node as HTMLElement).matches?.(d.target) || !!(node as HTMLElement).closest?.(d.target);
|
|
523
|
-
};
|
|
524
|
-
if (matchesSel(el)) {
|
|
525
|
-
observe ? d.observe(el) : d.unobserve(el);
|
|
526
|
-
} else if (!isThis) {
|
|
527
|
-
el.querySelectorAll(d.target).forEach(sub => {
|
|
528
|
-
observe ? d.observe(sub as HTMLElement) : d.unobserve(sub as HTMLElement);
|
|
529
|
-
});
|
|
530
|
-
}
|
|
575
|
+
st.intersectionDelegates.push(...newIntersectionDelegates);
|
|
576
|
+
|
|
577
|
+
// 4. MutationObserver (1개): mutation 콜백 + resize/intersection delegate 동적 추적을 함께 처리한다.
|
|
578
|
+
if (mutationObserverSet.length || newResizeDelegates.length || newIntersectionDelegates.length) {
|
|
579
|
+
if (!st.mo) {
|
|
580
|
+
st.mo = new win.MutationObserver((mutations, obs) => {
|
|
581
|
+
for (const mut of mutations) {
|
|
582
|
+
for (const [nodes, on] of [[mut.addedNodes, true], [mut.removedNodes, false]] as const) {
|
|
583
|
+
for (const n of Array.from(nodes)) {
|
|
584
|
+
for (const d of st.resizeDelegates) forDelegateMatches(n, d.target, el => on ? resizeObserve(el, d.options) : resizeUnobserve(el));
|
|
585
|
+
for (const d of st.intersectionDelegates) forDelegateMatches(n, d.target, el => on ? d.observe(el) : d.unobserve(el));
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
for (const cb of st.moCallbacks) cb(mutations, obs);
|
|
590
|
+
});
|
|
591
|
+
st.observers.push(st.mo);
|
|
531
592
|
}
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
if (mutationObserverSet?.length || resizeDelegates.length > 0 || intersectionDelegates.length > 0) {
|
|
535
|
-
// 하나의 MutationObserver 로 resize/intersection delegate 동적 추적 + mutation 콜백을 함께 처리
|
|
536
|
-
const callbacks = [...new Set((mutationObserverSet ?? []).map(e => e.callback))];
|
|
537
|
-
const mo = new (win as any).MutationObserver((mutations, obs) => {
|
|
538
|
-
for (const mut of mutations) {
|
|
539
|
-
Array.from(mut.addedNodes || []).forEach(n => { handleResizeDelegate(n as Node, true); handleIntersectionDelegate(n as Node, true); });
|
|
540
|
-
Array.from(mut.removedNodes || []).forEach(n => { handleResizeDelegate(n as Node, false); handleIntersectionDelegate(n as Node, false); });
|
|
541
|
-
}
|
|
542
|
-
for (const cb of callbacks) cb(mutations, obs);
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
// observe 대상 전체(엔트리 + delegate 추적 root)를 하나로 모아 중복 observe 를 제거한다.
|
|
546
|
-
// 같은 target 이 여러 소스에서 지정되면 MutationObserverInit 을 합집합(최대 감지범위)으로 병합한다.
|
|
547
|
-
const moObserveTargets = new Map<Element | ShadowRoot, MutationObserverInit>();
|
|
548
|
-
const mergeMutationInit = (existing: MutationObserverInit | undefined, next: MutationObserverInit): MutationObserverInit => {
|
|
549
|
-
if (!existing) return next;
|
|
550
|
-
const mergedFilter = existing.attributeFilter || next.attributeFilter
|
|
551
|
-
? [...new Set([...(existing.attributeFilter ?? []), ...(next.attributeFilter ?? [])])]
|
|
552
|
-
: undefined;
|
|
553
|
-
return {
|
|
554
|
-
childList: existing.childList || next.childList,
|
|
555
|
-
attributes: existing.attributes || next.attributes,
|
|
556
|
-
characterData: existing.characterData || next.characterData,
|
|
557
|
-
subtree: existing.subtree || next.subtree,
|
|
558
|
-
attributeOldValue: existing.attributeOldValue || next.attributeOldValue,
|
|
559
|
-
characterDataOldValue: existing.characterDataOldValue || next.characterDataOldValue,
|
|
560
|
-
attributeFilter: mergedFilter,
|
|
561
|
-
};
|
|
562
|
-
};
|
|
563
593
|
const addObserveTarget = (target: Element | ShadowRoot, options: MutationObserverInit) => {
|
|
564
|
-
|
|
594
|
+
st.moTargets.set(target, mergeMutationInit(st.moTargets.get(target), options));
|
|
565
595
|
};
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
for (const e of mutationObserverSet ?? []) {
|
|
596
|
+
for (const e of mutationObserverSet) {
|
|
597
|
+
st.moCallbacks.add(e.callback);
|
|
569
598
|
if (e.delegate) continue;
|
|
570
599
|
const init = e.options ?? { childList: true };
|
|
571
600
|
if (typeof e.target === 'string') {
|
|
572
|
-
|
|
573
|
-
const scope = resolveDelegateRoots(e.delegateRoot);
|
|
574
|
-
for (const sr of scope) {
|
|
575
|
-
sr.querySelectorAll(e.target).forEach(el => addObserveTarget(el, init));
|
|
576
|
-
}
|
|
601
|
+
for (const r of resolveDelegateRoots(e.delegateRoot)) r.querySelectorAll(e.target).forEach(el => addObserveTarget(el, init));
|
|
577
602
|
continue;
|
|
578
603
|
}
|
|
579
604
|
addObserveTarget(e.target, init);
|
|
580
605
|
}
|
|
581
|
-
|
|
582
|
-
// 2) delegate 추적 root: shadow/light/all/auto 로 해석해 전부 observe
|
|
583
|
-
for (const d of resizeDelegates) {
|
|
584
|
-
for (const r of resolveDelegateRoots(d.delegateRoot)) addObserveTarget(r, { childList: true, subtree: true });
|
|
585
|
-
}
|
|
586
|
-
for (const d of intersectionDelegates) {
|
|
606
|
+
for (const d of [...newResizeDelegates, ...newIntersectionDelegates]) {
|
|
587
607
|
for (const r of resolveDelegateRoots(d.delegateRoot)) addObserveTarget(r, { childList: true, subtree: true });
|
|
588
608
|
}
|
|
589
|
-
|
|
590
|
-
for (const [target, options] of
|
|
591
|
-
mo.observe(target, options);
|
|
592
|
-
}
|
|
593
|
-
observers.push(mo);
|
|
609
|
+
// 같은 노드를 다시 observe 하면 옵션만 교체된다 (등록·전달 중복 없음) → 'self' 분과 합쳐진 옵션으로 갱신
|
|
610
|
+
for (const [target, options] of st.moTargets) st.mo.observe(target, options);
|
|
594
611
|
}
|
|
595
612
|
|
|
596
|
-
inst.__swc_observers = observers;
|
|
613
|
+
inst.__swc_observers = st.observers;
|
|
597
614
|
};
|
|
598
615
|
|
|
599
|
-
const originalConnected = proto.connectedCallback;
|
|
616
|
+
const originalConnected = userCallback(proto.connectedCallback);
|
|
600
617
|
proto.connectedCallback = async function () {
|
|
601
618
|
ensureInit(this);
|
|
602
619
|
// 재연결 시 누적 방지 — cycler 내부 리소스 초기화
|
|
603
|
-
helperHostSet = SwcUtils.getHelperAndHostSet(
|
|
620
|
+
const helperHostSet = SwcUtils.getHelperAndHostSet(this as any);
|
|
621
|
+
(this as any).__swc_helperHostSet = helperHostSet;
|
|
604
622
|
const appHost = helperHostSet.$appHost;
|
|
605
623
|
const useSsr = isSSR(this);
|
|
624
|
+
// 연결 처리는 await 를 거친다. 그 사이 떨어지거나 다시 붙으면(disconnect / 새 connect 가 차례 번호를 올리면) 이 연결은 멈춘다 —
|
|
625
|
+
// 안 그러면 떼어진 뒤에 리스너·타이머가 등록돼 새고, 다시 붙은 연결과 겹쳐 두 번씩 돈다.
|
|
626
|
+
// (isConnected 는 보지 않는다 — 직접 호출이나 SSR 처럼 붙어 있지 않아도 라이프사이클은 돌아야 한다)
|
|
627
|
+
const gen = ((this as any).__swc_connectGen = ((this as any).__swc_connectGen ?? 0) + 1);
|
|
628
|
+
const alive = () => (this as any).__swc_connectGen === gen;
|
|
629
|
+
const obsState = newObserverState();
|
|
606
630
|
try {
|
|
607
631
|
if (appHost && typeof (appHost as any)._connected === 'function') {
|
|
608
632
|
await (appHost as any)._connected(this);
|
|
633
|
+
if (!alive()) return;
|
|
609
634
|
} else if (appHost && typeof (appHost as any)) {
|
|
610
635
|
(appHost as any)._connected_safari_and_standby ??= [];
|
|
611
636
|
(appHost as any)._connected_safari_and_standby.push(this);
|
|
612
637
|
}
|
|
613
638
|
|
|
614
639
|
const conf = getElementConfig(this);
|
|
615
|
-
const currentWin = (this as any)._resolveWindow(conf);
|
|
616
640
|
|
|
617
641
|
// before-connected 라이프사이클 메서드 (@onConnectedBefore)
|
|
618
|
-
for (const m of findAllOnConnectedBeforeMetadata(this))
|
|
642
|
+
for (const m of findAllOnConnectedBeforeMetadata(this)) {
|
|
643
|
+
await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
|
|
644
|
+
if (!alive()) return;
|
|
645
|
+
}
|
|
619
646
|
(this as any)._executeSwcScript('swc-on-before-connected', helperHostSet);
|
|
620
647
|
|
|
621
648
|
// ── 렌더 (@onConnected / @onConnectedBody*) — elementDefine 책임 ──
|
|
@@ -626,6 +653,10 @@ export const elementDefine =
|
|
|
626
653
|
this.attachShadow({ mode: mode as ShadowRootMode });
|
|
627
654
|
}
|
|
628
655
|
|
|
656
|
+
// 자기 자신 대상 mutation/resize 는 렌더 전에 붙여 첫 렌더부터 본다 (shadow root 가 생긴 뒤라 $this 가 맞는 root 를 잡는다)
|
|
657
|
+
await buildObservers(helperHostSet, 'self', obsState, alive);
|
|
658
|
+
if (!alive()) return;
|
|
659
|
+
|
|
629
660
|
const stateContext: any = { ...helperHostSet };
|
|
630
661
|
findAllStateMetadata(this).forEach(it => {
|
|
631
662
|
stateContext[it.name] = this[it.propertyKey];
|
|
@@ -635,8 +666,40 @@ export const elementDefine =
|
|
|
635
666
|
const lightChildren: Node[] = [];
|
|
636
667
|
|
|
637
668
|
if (targetConnectedList.length > 0) {
|
|
669
|
+
const toNodes = (value: any): Node[] => {
|
|
670
|
+
if (!value) return [];
|
|
671
|
+
if (typeof value === 'string') {
|
|
672
|
+
const htmlTemplateElement = doc.createElement('template');
|
|
673
|
+
htmlTemplateElement.innerHTML = value;
|
|
674
|
+
return Array.from(htmlTemplateElement.content.childNodes);
|
|
675
|
+
}
|
|
676
|
+
if (Array.isArray(value)) return value;
|
|
677
|
+
return [value];
|
|
678
|
+
};
|
|
679
|
+
// fallback: 비동기 렌더 대기 중에 먼저 보여준다. 결과는 모든 렌더 메서드가 끝난 뒤 한 번에 들어가므로
|
|
680
|
+
// 그때 같이 걷어낸다 (메서드별로 걷으면 다음 메서드를 기다리는 동안 빈 화면이 된다).
|
|
681
|
+
const shownFallbacks: Node[] = [];
|
|
682
|
+
const removeFallbacks = () => shownFallbacks.splice(0).forEach(n => (n as ChildNode).remove());
|
|
638
683
|
for (const meta of targetConnectedList) {
|
|
684
|
+
const useShadowRoot = meta.options.useShadow || conf?.useShadow;
|
|
685
|
+
if (meta.options.fallback) {
|
|
686
|
+
const fb = typeof meta.options.fallback === 'function'
|
|
687
|
+
? (meta.options.fallback as any)(helperHostSet)
|
|
688
|
+
: meta.options.fallback;
|
|
689
|
+
const fallbackNodes = toNodes(fb);
|
|
690
|
+
const parent = useShadowRoot ? this.shadowRoot : this;
|
|
691
|
+
if (fallbackNodes.length && parent) {
|
|
692
|
+
try {
|
|
693
|
+
const processed = SwcUtils.projectProcessHtml(this._swcId, fallbackNodes, doc);
|
|
694
|
+
parent.append(...processed);
|
|
695
|
+
shownFallbacks.push(...processed);
|
|
696
|
+
} catch (e) {
|
|
697
|
+
console.error('[ElementDefine] fallback setting error:', e);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
639
701
|
let res = await (this as any)._invokeLifecycleMethod(meta.propertyKey, helperHostSet);
|
|
702
|
+
if (!alive()) { removeFallbacks(); return; }
|
|
640
703
|
if (typeof res === 'string') {
|
|
641
704
|
const htmlTemplateElement = doc.createElement('template');
|
|
642
705
|
htmlTemplateElement.innerHTML = res;
|
|
@@ -651,6 +714,7 @@ export const elementDefine =
|
|
|
651
714
|
}
|
|
652
715
|
}
|
|
653
716
|
}
|
|
717
|
+
removeFallbacks();
|
|
654
718
|
try {
|
|
655
719
|
if (this.shadowRoot) {
|
|
656
720
|
const applyShadowChildren = SwcUtils.projectProcessHtml(this._swcId, shadowChildren, doc);
|
|
@@ -677,22 +741,27 @@ export const elementDefine =
|
|
|
677
741
|
globalDelegatedRoots.add(root);
|
|
678
742
|
}
|
|
679
743
|
|
|
680
|
-
// ── cycler 실행 (렌더 후):
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
await buildObservers(SwcUtils.getHelperAndHostSet(win, this as any));
|
|
744
|
+
// ── cycler 실행 (렌더 후): 나머지 시클러 onConnected → observer Set 수집 → 같은 옵저버에 덧붙임 ──
|
|
745
|
+
await buildObservers(helperHostSet, 'rest', obsState, alive);
|
|
746
|
+
if (!alive()) return;
|
|
684
747
|
|
|
685
748
|
if (originalConnected) await originalConnected.apply(this);
|
|
749
|
+
if (!alive()) return;
|
|
686
750
|
|
|
687
751
|
// after-connected 라이프사이클 메서드 (@onConnectedAfter)
|
|
688
|
-
for (const m of findAllOnConnectedAfterMetadata(this))
|
|
752
|
+
for (const m of findAllOnConnectedAfterMetadata(this)) {
|
|
753
|
+
await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
|
|
754
|
+
if (!alive()) return;
|
|
755
|
+
}
|
|
689
756
|
(this as any)._executeSwcScript('swc-on-connected', helperHostSet);
|
|
690
757
|
(this as any)._executeSwcScript('swc-on-after-connected', helperHostSet);
|
|
691
758
|
(this as any).__swc_connected = true;
|
|
692
759
|
} finally {
|
|
693
|
-
|
|
760
|
+
// 멈춘 연결은 완료 훅을 부르지 않고, 앱 host 에는 중단만 알린다 (자식 연결 수만 맞추고 재생은 안 함)
|
|
761
|
+
const aborted = !alive();
|
|
762
|
+
if (!aborted) for (const m of findAllLifecycleMetadata(this, ON_CONNECTED_COMPLETED_METADATA_KEY)) await (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
|
|
694
763
|
if (appHost && typeof (appHost as any)._connectedDone === 'function') {
|
|
695
|
-
await (appHost as any)._connectedDone(this);
|
|
764
|
+
await (appHost as any)._connectedDone(this, { aborted });
|
|
696
765
|
}
|
|
697
766
|
}
|
|
698
767
|
};
|
|
@@ -700,16 +769,19 @@ export const elementDefine =
|
|
|
700
769
|
/////////////////////////////////////////////////
|
|
701
770
|
// disconnectedCallback
|
|
702
771
|
////////////////////////////////////////////////
|
|
703
|
-
const originalDisconnected = proto.disconnectedCallback;
|
|
772
|
+
const originalDisconnected = userCallback(proto.disconnectedCallback);
|
|
704
773
|
proto.disconnectedCallback = function () {
|
|
705
|
-
|
|
774
|
+
(this as any).__swc_connectGen = ((this as any).__swc_connectGen ?? 0) + 1; // 진행 중인 연결은 멈춘다
|
|
775
|
+
// 떨어진 뒤라 DOM 으로는 $appHost 를 다시 못 찾는다 → connect 때 저장한 이 인스턴스의 값
|
|
776
|
+
const helperHostSet = connectedHelperHostSet(this);
|
|
777
|
+
const appHost = helperHostSet.$appHost;
|
|
706
778
|
if (appHost && typeof (appHost as any)._disconnected === 'function') {
|
|
707
779
|
(appHost as any)._disconnected(this);
|
|
708
780
|
}
|
|
709
781
|
|
|
710
782
|
(this as any)._executeSwcScript('swc-on-before-disconnected', helperHostSet);
|
|
711
783
|
for (const m of findAllLifecycleMetadata(this, ON_BEFORE_DISCONNECTED_METADATA_KEY)) (this as any)._invokeLifecycleMethod(m.propertyKey, helperHostSet);
|
|
712
|
-
for (const c of cyclers) c.onDisconnected?.(helperHostSet
|
|
784
|
+
for (const c of cyclers) c.onDisconnected?.(helperHostSet);
|
|
713
785
|
|
|
714
786
|
for (const o of (this as any).__swc_observers ?? []) {
|
|
715
787
|
try { (o as any)?.disconnect?.(); } catch (e) { console.error('[SWC] observer disconnect error:', e); }
|
|
@@ -727,12 +799,12 @@ export const elementDefine =
|
|
|
727
799
|
(this as any).__swc_connected = false;
|
|
728
800
|
};
|
|
729
801
|
|
|
730
|
-
const originalAdopted = proto.adoptedCallback;
|
|
802
|
+
const originalAdopted = userCallback(proto.adoptedCallback);
|
|
731
803
|
proto.adoptedCallback = function () {
|
|
732
804
|
const hostSet = SwcUtils.getHostSet(this as any);
|
|
733
805
|
(this as any)._executeSwcScript('swc-on-before-adopted', hostSet);
|
|
734
806
|
for (const m of findAllLifecycleMetadata(this, ON_BEFORE_ADOPTED_METADATA_KEY)) (this as any)._invokeLifecycleMethod(m.propertyKey, hostSet);
|
|
735
|
-
for (const c of cyclers) c.onAdopted?.(
|
|
807
|
+
for (const c of cyclers) c.onAdopted?.(connectedHelperHostSet(this));
|
|
736
808
|
|
|
737
809
|
if (originalAdopted) originalAdopted.apply(this);
|
|
738
810
|
|
|
@@ -741,13 +813,13 @@ export const elementDefine =
|
|
|
741
813
|
(this as any)._executeSwcScript('swc-on-after-adopted', hostSet);
|
|
742
814
|
};
|
|
743
815
|
|
|
744
|
-
const originalAttributeChanged = proto.attributeChangedCallback;
|
|
816
|
+
const originalAttributeChanged = userCallback(proto.attributeChangedCallback);
|
|
745
817
|
proto.attributeChangedCallback = function (name: string, old: string | null, newVal: string | null) {
|
|
746
818
|
if (originalAttributeChanged) originalAttributeChanged.apply(this, [name, old, newVal]);
|
|
747
819
|
|
|
748
820
|
// attributeChangedCallback 은 connected 이전에도 호출될 수 있으므로
|
|
749
821
|
// 클로저의 helperHostSet(null 가능) 대신 이 시점에 새로 계산한다.
|
|
750
|
-
const helperAndHostSet = SwcUtils.getHelperAndHostSet(
|
|
822
|
+
const helperAndHostSet = SwcUtils.getHelperAndHostSet(this as any);
|
|
751
823
|
|
|
752
824
|
// Process expression directive before passing to handlers
|
|
753
825
|
let processedVal: any = newVal;
|
|
@@ -755,8 +827,8 @@ export const elementDefine =
|
|
|
755
827
|
const ae = new ActionExpression(newVal);
|
|
756
828
|
const expr = ae.getFirstExpression('callReturn');
|
|
757
829
|
if (expr) {
|
|
758
|
-
const win = (this
|
|
759
|
-
const exprHelperAndHostSet = SwcUtils.getHelperAndHostSet(
|
|
830
|
+
const win = SwcUtils.resolveWindow(this);
|
|
831
|
+
const exprHelperAndHostSet = SwcUtils.getHelperAndHostSet(this as any);
|
|
760
832
|
const script = ConvertUtils.decodeHtmlEntity(expr.script, win.document);
|
|
761
833
|
try {
|
|
762
834
|
const result = FunctionUtils.executeReturn({
|
|
@@ -788,6 +860,12 @@ export const elementDefine =
|
|
|
788
860
|
configurable: true
|
|
789
861
|
});
|
|
790
862
|
|
|
863
|
+
// 이 클래스를 다시 상속해 elementDefine 하면 자식이 이 래퍼 대신 원래 사용자 함수를 부르도록 기억해 둔다
|
|
864
|
+
proto.connectedCallback[SWC_ORIGINAL_CALLBACK] = originalConnected;
|
|
865
|
+
proto.disconnectedCallback[SWC_ORIGINAL_CALLBACK] = originalDisconnected;
|
|
866
|
+
proto.adoptedCallback[SWC_ORIGINAL_CALLBACK] = originalAdopted;
|
|
867
|
+
proto.attributeChangedCallback[SWC_ORIGINAL_CALLBACK] = originalAttributeChanged;
|
|
868
|
+
|
|
791
869
|
ReflectUtils.defineMetadata(ELEMENT_CONFIG_KEY, metadata, constructor);
|
|
792
870
|
const registry = metadata.customElementRegistry || (win as any)?.customElements;
|
|
793
871
|
if (registry && !registry.get(metadata.name)) {
|