@dooboostore/simple-web-component 1.0.43 → 1.0.44
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 +298 -49
- package/dist/cjs/SwcAppEngine.js +3 -1
- package/dist/cjs/SwcAppEngine.js.map +2 -2
- package/dist/cjs/decorators/addEventListener.js.map +2 -2
- package/dist/cjs/decorators/applyAttribute.js +30 -28
- package/dist/cjs/decorators/applyAttribute.js.map +2 -2
- package/dist/cjs/decorators/applyClass.js +57 -21
- package/dist/cjs/decorators/applyClass.js.map +3 -3
- package/dist/cjs/decorators/applyNode.js +165 -105
- package/dist/cjs/decorators/applyNode.js.map +2 -2
- package/dist/cjs/decorators/applyProperty.js +11 -28
- package/dist/cjs/decorators/applyProperty.js.map +2 -2
- package/dist/cjs/decorators/applyStyle.js +39 -15
- package/dist/cjs/decorators/applyStyle.js.map +3 -3
- package/dist/cjs/decorators/{changedAttributeThis.js → changedAttribute.js} +6 -6
- package/dist/cjs/decorators/changedAttribute.js.map +7 -0
- package/dist/cjs/decorators/elementDefine.js +37 -6
- package/dist/cjs/decorators/elementDefine.js.map +3 -3
- package/dist/cjs/decorators/emitCustomEvent.js +56 -42
- package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
- package/dist/cjs/decorators/index.js +1 -1
- package/dist/cjs/decorators/index.js.map +2 -2
- package/dist/cjs/decorators/publishSwcAppMessage.js +5 -5
- package/dist/cjs/decorators/publishSwcAppMessage.js.map +3 -3
- package/dist/cjs/decorators/query.js +96 -145
- package/dist/cjs/decorators/query.js.map +3 -3
- package/dist/cjs/decorators/queryAll.js +104 -131
- package/dist/cjs/decorators/queryAll.js.map +3 -3
- package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js +2 -0
- package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js.map +2 -2
- package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js +2 -0
- package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +2 -2
- package/dist/cjs/elements/SwcAppMixin.js +17 -2
- package/dist/cjs/elements/SwcAppMixin.js.map +2 -2
- package/dist/cjs/examples/RxJSOperatorsExample.js +285 -0
- package/dist/cjs/examples/RxJSOperatorsExample.js.map +7 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/router/ElementRouter.js +4 -0
- package/dist/cjs/router/ElementRouter.js.map +2 -2
- package/dist/esm/SwcAppEngine.js +3 -1
- package/dist/esm/SwcAppEngine.js.map +2 -2
- package/dist/esm/decorators/addEventListener.js.map +2 -2
- package/dist/esm/decorators/applyAttribute.js +30 -28
- package/dist/esm/decorators/applyAttribute.js.map +2 -2
- package/dist/esm/decorators/applyClass.js +57 -21
- package/dist/esm/decorators/applyClass.js.map +3 -3
- package/dist/esm/decorators/applyNode.js +165 -105
- package/dist/esm/decorators/applyNode.js.map +2 -2
- package/dist/esm/decorators/applyProperty.js +11 -28
- package/dist/esm/decorators/applyProperty.js.map +2 -2
- package/dist/esm/decorators/applyStyle.js +39 -15
- package/dist/esm/decorators/applyStyle.js.map +3 -3
- package/dist/esm/decorators/{changedAttributeThis.js → changedAttribute.js} +3 -3
- package/dist/esm/decorators/changedAttribute.js.map +7 -0
- package/dist/esm/decorators/elementDefine.js +35 -4
- package/dist/esm/decorators/elementDefine.js.map +2 -2
- package/dist/esm/decorators/emitCustomEvent.js +56 -42
- package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
- package/dist/esm/decorators/index.js +1 -1
- package/dist/esm/decorators/index.js.map +1 -1
- package/dist/esm/decorators/publishSwcAppMessage.js +5 -5
- package/dist/esm/decorators/publishSwcAppMessage.js.map +3 -3
- package/dist/esm/decorators/query.js +96 -145
- package/dist/esm/decorators/query.js.map +3 -3
- package/dist/esm/decorators/queryAll.js +104 -131
- package/dist/esm/decorators/queryAll.js.map +3 -3
- package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js +2 -0
- package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js.map +2 -2
- package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js +2 -0
- package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +2 -2
- package/dist/esm/elements/SwcAppMixin.js +18 -3
- package/dist/esm/elements/SwcAppMixin.js.map +2 -2
- package/dist/esm/examples/RxJSOperatorsExample.js +266 -0
- package/dist/esm/examples/RxJSOperatorsExample.js.map +7 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +2 -2
- package/dist/esm/router/ElementRouter.js +4 -0
- package/dist/esm/router/ElementRouter.js.map +2 -2
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +759 -512
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
- package/dist/types/SwcAppEngine.d.ts.map +1 -1
- package/dist/types/decorators/addEventListener.d.ts +17 -14
- package/dist/types/decorators/addEventListener.d.ts.map +1 -1
- package/dist/types/decorators/applyAttribute.d.ts +35 -20
- package/dist/types/decorators/applyAttribute.d.ts.map +1 -1
- package/dist/types/decorators/applyClass.d.ts +16 -36
- package/dist/types/decorators/applyClass.d.ts.map +1 -1
- package/dist/types/decorators/applyNode.d.ts +172 -30
- package/dist/types/decorators/applyNode.d.ts.map +1 -1
- package/dist/types/decorators/applyProperty.d.ts +27 -18
- package/dist/types/decorators/applyProperty.d.ts.map +1 -1
- package/dist/types/decorators/applyStyle.d.ts +7 -7
- package/dist/types/decorators/applyStyle.d.ts.map +1 -1
- package/dist/types/decorators/{changedAttributeThis.d.ts → changedAttribute.d.ts} +2 -2
- package/dist/types/decorators/changedAttribute.d.ts.map +1 -0
- package/dist/types/decorators/elementDefine.d.ts.map +1 -1
- package/dist/types/decorators/emitCustomEvent.d.ts +3 -10
- package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
- package/dist/types/decorators/index.d.ts +1 -1
- package/dist/types/decorators/index.d.ts.map +1 -1
- package/dist/types/decorators/publishSwcAppMessage.d.ts +1 -1
- package/dist/types/decorators/publishSwcAppMessage.d.ts.map +1 -1
- package/dist/types/decorators/query.d.ts +3 -26
- package/dist/types/decorators/query.d.ts.map +1 -1
- package/dist/types/decorators/queryAll.d.ts +3 -20
- package/dist/types/decorators/queryAll.d.ts.map +1 -1
- package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts +1 -0
- package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts.map +1 -1
- package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts +2 -0
- package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts.map +1 -1
- package/dist/types/elements/SwcAppMixin.d.ts +4 -4
- package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
- package/dist/types/examples/RxJSOperatorsExample.d.ts +94 -0
- package/dist/types/examples/RxJSOperatorsExample.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/router/ElementRouter.d.ts.map +1 -1
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +759 -512
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
- package/package.json +3 -3
- package/src/SwcAppEngine.ts +3 -1
- package/src/decorators/addEventListener.ts +28 -24
- package/src/decorators/applyAttribute.ts +109 -63
- package/src/decorators/applyClass.ts +89 -15
- package/src/decorators/applyNode.ts +357 -100
- package/src/decorators/applyProperty.ts +42 -63
- package/src/decorators/applyStyle.ts +59 -11
- package/src/decorators/{changedAttributeThis.ts → changedAttribute.ts} +1 -1
- package/src/decorators/elementDefine.ts +47 -4
- package/src/decorators/emitCustomEvent.ts +44 -36
- package/src/decorators/index.ts +1 -1
- package/src/decorators/publishSwcAppMessage.ts +1 -1
- package/src/decorators/query.ts +90 -159
- package/src/decorators/queryAll.ts +92 -134
- package/src/decorators/subscribeSwcAppMessageWhileConnected.ts +1 -0
- package/src/decorators/subscribeSwcAppRouteChangeWhileConnected.ts +3 -1
- package/src/elements/SwcAppMixin.ts +43 -24
- package/src/examples/RxJSOperatorsExample.ts +279 -0
- package/src/index.ts +2 -0
- package/src/router/ElementRouter.ts +4 -0
- package/dist/cjs/decorators/changedAttributeThis.js.map +0 -7
- package/dist/esm/decorators/changedAttributeThis.js.map +0 -7
- package/dist/types/decorators/changedAttributeThis.d.ts.map +0 -1
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import {SwcAppInterface, SwcAppMessage} from
|
|
2
|
-
import {findAllLifecycleMetadata, getSubscribeSwcAppMessageWhileConnectedMetadata, getSubscribeSwcAppRouteChangeWhileConnectedMetadata, ON_CONNECTED_SWC_APP_METADATA_KEY} from
|
|
3
|
-
import {SwcAppEngine, SwcAttributeConfigType, SwcConfigType} from
|
|
4
|
-
import {debounceTimeIntervalLock, FunctionUtils, Subscription} from
|
|
5
|
-
import {RouterEventType} from
|
|
6
|
-
import {SwcUtils} from
|
|
1
|
+
import { SwcAppInterface, SwcAppMessage } from '../types';
|
|
2
|
+
import { APPLY_NODE_METADATA_KEY, findAllLifecycleMetadata, getSubscribeSwcAppMessageWhileConnectedMetadata, getSubscribeSwcAppRouteChangeWhileConnectedMetadata, ON_CONNECTED_SWC_APP_METADATA_KEY, SUBSCRIBE_SWC_APP_ROUTE_CHANGE_WHILE_CONNECTED_METADATA_KEY } from '../decorators';
|
|
3
|
+
import { SwcAppEngine, SwcAttributeConfigType, SwcConfigType } from '../SwcAppEngine';
|
|
4
|
+
import { debounceTimeIntervalLock, FunctionUtils, Subscription } from '@dooboostore/core';
|
|
5
|
+
import { RouterEventType } from '@dooboostore/core-web';
|
|
6
|
+
import { SwcUtils } from '../utils/Utils';
|
|
7
7
|
|
|
8
8
|
export const isSSR = (i: HTMLElement) => {
|
|
9
9
|
return i.hasAttribute('swc-use-ssr');
|
|
10
|
-
}
|
|
10
|
+
};
|
|
11
11
|
export const setSSRAttribute = (i: HTMLElement) => {
|
|
12
12
|
i.setAttribute('swc-use-ssr', (i as any)._swcId ?? '');
|
|
13
|
-
}
|
|
13
|
+
};
|
|
14
14
|
export const removeSSRAttribute = (i: HTMLElement) => {
|
|
15
15
|
i.removeAttribute('swc-use-ssr');
|
|
16
|
-
}
|
|
16
|
+
};
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* SwcAppMixin: SwcApp 기능을 모든 HTMLElement에 추가할 수 있는 Mixin
|
|
20
|
-
*
|
|
20
|
+
*
|
|
21
21
|
* 사용 예:
|
|
22
22
|
* @elementDefine('custom-app', { window: w })
|
|
23
23
|
* class CustomApp extends SwcAppMixin(w.HTMLElement) implements SwcAppInterface {
|
|
24
24
|
* // 추가 기능 구현
|
|
25
25
|
* }
|
|
26
26
|
*/
|
|
27
|
-
export function SwcAppMixin<T extends { new(...args: any[]): HTMLElement }>(Base: T) {
|
|
27
|
+
export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Base: T) {
|
|
28
28
|
return class SwcAppMixinClass extends Base implements SwcAppInterface {
|
|
29
29
|
__swc_engine = new SwcAppEngine(this as any);
|
|
30
30
|
_swc_connected_instance = new Set<any>();
|
|
@@ -45,7 +45,7 @@ export function SwcAppMixin<T extends { new(...args: any[]): HTMLElement }>(Base
|
|
|
45
45
|
|
|
46
46
|
constructor(...args: any[]) {
|
|
47
47
|
super(...args);
|
|
48
|
-
console.log('[SWC-APP-MIXIN] constructor')
|
|
48
|
+
console.log('[SWC-APP-MIXIN] constructor');
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
get simpleApplication() {
|
|
@@ -68,12 +68,12 @@ export function SwcAppMixin<T extends { new(...args: any[]): HTMLElement }>(Base
|
|
|
68
68
|
if (this._childrenRouteChangedInterval) {
|
|
69
69
|
this.config.window.clearInterval(this._childrenRouteChangedInterval);
|
|
70
70
|
}
|
|
71
|
-
const intervalConfig = {setInterval: this.config.window.setInterval.bind(this.config.window), clearInterval: this.config.window.clearInterval.bind(this.config.window)};
|
|
71
|
+
const intervalConfig = { setInterval: this.config.window.setInterval.bind(this.config.window), clearInterval: this.config.window.clearInterval.bind(this.config.window) };
|
|
72
72
|
this._childrenRouteChangedInterval = debounceTimeIntervalLock(
|
|
73
73
|
() => this._routeChangeInvocations === 0,
|
|
74
74
|
() => {
|
|
75
75
|
this._childrenRouteChangedInterval = null;
|
|
76
|
-
this.config.onChildrenRouteChanged?.(re, this)
|
|
76
|
+
this.config.onChildrenRouteChanged?.(re, this);
|
|
77
77
|
},
|
|
78
78
|
this.config?.childrenConnectedDoneCheckIntervalTime ?? 30,
|
|
79
79
|
intervalConfig
|
|
@@ -81,15 +81,23 @@ export function SwcAppMixin<T extends { new(...args: any[]): HTMLElement }>(Base
|
|
|
81
81
|
|
|
82
82
|
try {
|
|
83
83
|
const routeChangeSubscribers = getSubscribeSwcAppRouteChangeWhileConnectedMetadata(instance);
|
|
84
|
+
|
|
84
85
|
if (routeChangeSubscribers.length > 0) {
|
|
85
|
-
console.log('[SWC-MIXIN] Route change subscribers found')
|
|
86
|
-
|
|
86
|
+
console.log('[SWC-MIXIN] Route change subscribers found');
|
|
87
|
+
|
|
87
88
|
// Subscribers are already sorted by order from getSubscribeSwcAppRouteChangeWhileConnectedMetadata
|
|
88
89
|
// Execute subscribers in order, stop if one returns a value
|
|
89
90
|
for (const metadata of routeChangeSubscribers) {
|
|
90
91
|
const methodName = metadata.propertyKey;
|
|
91
92
|
let pathPattern = metadata.options?.path as any;
|
|
92
93
|
const filter = metadata.options?.filter;
|
|
94
|
+
const extractValue = (v: any) => {
|
|
95
|
+
const keyToUse = metadata.options?.valueKey ?? SUBSCRIBE_SWC_APP_ROUTE_CHANGE_WHILE_CONNECTED_METADATA_KEY;
|
|
96
|
+
if (v && typeof v === 'object' && keyToUse in v) {
|
|
97
|
+
return v[keyToUse];
|
|
98
|
+
}
|
|
99
|
+
return v;
|
|
100
|
+
};
|
|
93
101
|
|
|
94
102
|
if (typeof pathPattern === 'function') {
|
|
95
103
|
pathPattern = (pathPattern as Function)(instance);
|
|
@@ -116,10 +124,11 @@ export function SwcAppMixin<T extends { new(...args: any[]): HTMLElement }>(Base
|
|
|
116
124
|
}
|
|
117
125
|
|
|
118
126
|
const hostSet = SwcUtils.getHelperAndHostSet(this.config.window, this);
|
|
119
|
-
const filterPassed = !filter || filter(this.router!, {helper:hostSet, currentThis:instance});
|
|
127
|
+
const filterPassed = !filter || filter(this.router!, { helper: hostSet, currentThis: instance });
|
|
120
128
|
|
|
121
129
|
if (pathMatched && filterPassed && instance[methodName]) {
|
|
122
|
-
const result = await instance[methodName]({...re, pathData: pathData});
|
|
130
|
+
const result = extractValue(await instance[methodName]({ ...re, pathData: pathData }));
|
|
131
|
+
// if (typeof result === 'object'
|
|
123
132
|
// If handler returns a value, stop propagation to next handlers
|
|
124
133
|
if (result !== undefined && result !== null) {
|
|
125
134
|
break;
|
|
@@ -158,12 +167,12 @@ export function SwcAppMixin<T extends { new(...args: any[]): HTMLElement }>(Base
|
|
|
158
167
|
if (this._childrenConnectedDoneInterval) {
|
|
159
168
|
this.config.window.clearInterval(this._childrenConnectedDoneInterval);
|
|
160
169
|
}
|
|
161
|
-
const intervalConfig = {setInterval: this.config.window.setInterval.bind(this.config.window), clearInterval: this.config.window.clearInterval.bind(this.config.window)};
|
|
170
|
+
const intervalConfig = { setInterval: this.config.window.setInterval.bind(this.config.window), clearInterval: this.config.window.clearInterval.bind(this.config.window) };
|
|
162
171
|
this._childrenConnectedDoneInterval = debounceTimeIntervalLock(
|
|
163
172
|
() => this._connectedInvocations === 0,
|
|
164
173
|
() => {
|
|
165
174
|
this._childrenConnectedDoneInterval = null;
|
|
166
|
-
this.config.onChildrenConnectedDone?.(this)
|
|
175
|
+
this.config.onChildrenConnectedDone?.(this);
|
|
167
176
|
},
|
|
168
177
|
this.config?.childrenConnectedDoneCheckIntervalTime ?? 30,
|
|
169
178
|
intervalConfig
|
|
@@ -213,7 +222,7 @@ export function SwcAppMixin<T extends { new(...args: any[]): HTMLElement }>(Base
|
|
|
213
222
|
}
|
|
214
223
|
|
|
215
224
|
async connect(config?: SwcAttributeConfigType) {
|
|
216
|
-
console.log('[SWC-MIXIN] connect')
|
|
225
|
+
console.log('[SWC-MIXIN] connect');
|
|
217
226
|
const swcConfig = {
|
|
218
227
|
routeType: 'element',
|
|
219
228
|
window: config?.window,
|
|
@@ -224,7 +233,7 @@ export function SwcAppMixin<T extends { new(...args: any[]): HTMLElement }>(Base
|
|
|
224
233
|
|
|
225
234
|
// Safari is 속성 처리
|
|
226
235
|
this._connected_safari_and_standby.forEach((instance: any) => {
|
|
227
|
-
this._connected(instance, {noIncrements: true});
|
|
236
|
+
this._connected(instance, { noIncrements: true });
|
|
228
237
|
});
|
|
229
238
|
|
|
230
239
|
// 라우터 변경 구독
|
|
@@ -258,7 +267,17 @@ export function SwcAppMixin<T extends { new(...args: any[]): HTMLElement }>(Base
|
|
|
258
267
|
context: this,
|
|
259
268
|
args: SwcUtils.getHelperAndHostSet(win, this)
|
|
260
269
|
});
|
|
261
|
-
|
|
270
|
+
if (userConfig instanceof Promise) {
|
|
271
|
+
userConfig
|
|
272
|
+
.then(resolvedConfig => {
|
|
273
|
+
this.connect(resolvedConfig);
|
|
274
|
+
})
|
|
275
|
+
.catch(e => {
|
|
276
|
+
console.error('[SWC-MIXIN] Script execution failed:', e);
|
|
277
|
+
});
|
|
278
|
+
} else {
|
|
279
|
+
this.connect(userConfig);
|
|
280
|
+
}
|
|
262
281
|
} catch (e) {
|
|
263
282
|
console.error('[SWC-MIXIN] Script execution failed:', e);
|
|
264
283
|
}
|
|
@@ -273,7 +292,7 @@ export function SwcAppMixin<T extends { new(...args: any[]): HTMLElement }>(Base
|
|
|
273
292
|
// 부모 클래스의 disconnectedCallback 호출
|
|
274
293
|
// @ts-ignore
|
|
275
294
|
super.disconnectedCallback?.();
|
|
276
|
-
|
|
295
|
+
|
|
277
296
|
this._routerSubscription?.unsubscribe();
|
|
278
297
|
this._routerSubscription = undefined;
|
|
279
298
|
this._lastRouterEvent = undefined;
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: Using RxJS-like Operators with @addEventListener
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates how to use debounceTime, throttleTime, and distinctUntilChanged
|
|
5
|
+
* operators with the @addEventListener decorator.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { elementDefine, addEventListener } from '../decorators';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Example 1: Search Component with Debounce
|
|
12
|
+
*
|
|
13
|
+
* Waits 1 second after user stops typing before performing search.
|
|
14
|
+
* Prevents excessive API calls during typing.
|
|
15
|
+
*/
|
|
16
|
+
@elementDefine('search-component')
|
|
17
|
+
export class SearchComponent extends HTMLElement {
|
|
18
|
+
private results: any[] = [];
|
|
19
|
+
|
|
20
|
+
@addEventListener('$this', 'input', { debounceTime: 1000 })
|
|
21
|
+
async onSearchInput(event: Event) {
|
|
22
|
+
const input = event.target as HTMLInputElement;
|
|
23
|
+
const query = input.value.trim();
|
|
24
|
+
|
|
25
|
+
if (query.length > 2) {
|
|
26
|
+
try {
|
|
27
|
+
// Simulate API call
|
|
28
|
+
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
|
|
29
|
+
this.results = await response.json();
|
|
30
|
+
this.renderResults();
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Search failed:', error);
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
this.results = [];
|
|
36
|
+
this.renderResults();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private renderResults() {
|
|
41
|
+
// Render search results
|
|
42
|
+
console.log('Rendering results:', this.results);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Example 2: Window Resize Handler with Throttle
|
|
48
|
+
*
|
|
49
|
+
* Recalculates layout at most 4 times per second (every 250ms).
|
|
50
|
+
* Prevents performance issues from excessive resize calculations.
|
|
51
|
+
*/
|
|
52
|
+
@elementDefine('responsive-layout')
|
|
53
|
+
export class ResponsiveLayout extends HTMLElement {
|
|
54
|
+
@addEventListener('$window', 'resize', { throttleTime: 250 })
|
|
55
|
+
onWindowResize(event: Event) {
|
|
56
|
+
const width = window.innerWidth;
|
|
57
|
+
const height = window.innerHeight;
|
|
58
|
+
|
|
59
|
+
console.log(`Window resized to: ${width}x${height}`);
|
|
60
|
+
this.updateLayout(width, height);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private updateLayout(width: number, height: number) {
|
|
64
|
+
// Update layout based on new dimensions
|
|
65
|
+
if (width < 768) {
|
|
66
|
+
this.classList.add('mobile');
|
|
67
|
+
this.classList.remove('desktop');
|
|
68
|
+
} else {
|
|
69
|
+
this.classList.add('desktop');
|
|
70
|
+
this.classList.remove('mobile');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Example 3: Form Input with Debounce + Distinct
|
|
77
|
+
*
|
|
78
|
+
* Validates field only when:
|
|
79
|
+
* 1. User stops typing (debounce 300ms)
|
|
80
|
+
* 2. AND the value actually changed (distinct)
|
|
81
|
+
*/
|
|
82
|
+
@elementDefine('form-input')
|
|
83
|
+
export class FormInput extends HTMLElement {
|
|
84
|
+
@addEventListener('$this', 'input', {
|
|
85
|
+
debounceTime: 300,
|
|
86
|
+
distinctUntilChanged: true
|
|
87
|
+
})
|
|
88
|
+
onFormInput(event: Event) {
|
|
89
|
+
const input = event.target as HTMLInputElement;
|
|
90
|
+
const fieldName = input.name;
|
|
91
|
+
const value = input.value;
|
|
92
|
+
|
|
93
|
+
console.log(`Validating ${fieldName}: ${value}`);
|
|
94
|
+
this.validateField(fieldName, value);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private validateField(name: string, value: string) {
|
|
98
|
+
// Perform validation
|
|
99
|
+
const isValid = value.length > 0;
|
|
100
|
+
console.log(`Field ${name} is ${isValid ? 'valid' : 'invalid'}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Example 4: Scroll Event with Throttle
|
|
106
|
+
*
|
|
107
|
+
* Checks if user scrolled to bottom at most 10 times per second.
|
|
108
|
+
* Prevents excessive "load more" API calls.
|
|
109
|
+
*/
|
|
110
|
+
@elementDefine('infinite-scroll')
|
|
111
|
+
export class InfiniteScroll extends HTMLElement {
|
|
112
|
+
private isLoading = false;
|
|
113
|
+
|
|
114
|
+
@addEventListener('$window', 'scroll', { throttleTime: 100 })
|
|
115
|
+
onScroll(event: Event) {
|
|
116
|
+
if (this.isLoading) return;
|
|
117
|
+
|
|
118
|
+
if (this.isNearBottom()) {
|
|
119
|
+
console.log('Near bottom, loading more content...');
|
|
120
|
+
this.loadMoreContent();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private isNearBottom(): boolean {
|
|
125
|
+
const scrollTop = window.scrollY;
|
|
126
|
+
const windowHeight = window.innerHeight;
|
|
127
|
+
const documentHeight = document.documentElement.scrollHeight;
|
|
128
|
+
|
|
129
|
+
return scrollTop + windowHeight >= documentHeight - 500;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private async loadMoreContent() {
|
|
133
|
+
this.isLoading = true;
|
|
134
|
+
try {
|
|
135
|
+
// Simulate API call
|
|
136
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
137
|
+
console.log('Content loaded');
|
|
138
|
+
} finally {
|
|
139
|
+
this.isLoading = false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Example 5: Custom Comparison with Distinct
|
|
146
|
+
*
|
|
147
|
+
* Only fires when the input value length changes.
|
|
148
|
+
* Ignores changes that don't affect the length.
|
|
149
|
+
*/
|
|
150
|
+
@elementDefine('length-tracker')
|
|
151
|
+
export class LengthTracker extends HTMLElement {
|
|
152
|
+
@addEventListener('$this', 'input', {
|
|
153
|
+
distinctUntilChanged: (prev: any, curr: any) => {
|
|
154
|
+
// Only consider it changed if the length differs
|
|
155
|
+
return prev?.length === curr?.length;
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
onInputChange(event: Event) {
|
|
159
|
+
const input = event.target as HTMLInputElement;
|
|
160
|
+
const length = input.value.length;
|
|
161
|
+
|
|
162
|
+
console.log(`Input length changed to: ${length}`);
|
|
163
|
+
this.updateLengthDisplay(length);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private updateLengthDisplay(length: number) {
|
|
167
|
+
// Update UI with new length
|
|
168
|
+
const display = this.querySelector('.length-display');
|
|
169
|
+
if (display) {
|
|
170
|
+
display.textContent = `${length} characters`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Example 6: Complex Scenario - Auto-save with Multiple Operators
|
|
177
|
+
*
|
|
178
|
+
* Combines debounce and distinct for intelligent auto-save:
|
|
179
|
+
* - Waits 2 seconds after user stops typing
|
|
180
|
+
* - Only saves if content actually changed
|
|
181
|
+
* - Prevents unnecessary save operations
|
|
182
|
+
*/
|
|
183
|
+
@elementDefine('auto-save-editor')
|
|
184
|
+
export class AutoSaveEditor extends HTMLElement {
|
|
185
|
+
private lastSavedContent = '';
|
|
186
|
+
|
|
187
|
+
@addEventListener('$this', 'input', {
|
|
188
|
+
debounceTime: 2000,
|
|
189
|
+
distinctUntilChanged: (prev: any, curr: any) => {
|
|
190
|
+
// Consider changed if the content is different
|
|
191
|
+
return prev === curr;
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
async onContentChange(event: Event) {
|
|
195
|
+
const textarea = event.target as HTMLTextAreaElement;
|
|
196
|
+
const content = textarea.value;
|
|
197
|
+
|
|
198
|
+
if (content !== this.lastSavedContent) {
|
|
199
|
+
console.log('Auto-saving content...');
|
|
200
|
+
await this.saveContent(content);
|
|
201
|
+
this.lastSavedContent = content;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private async saveContent(content: string) {
|
|
206
|
+
try {
|
|
207
|
+
// Simulate API call
|
|
208
|
+
const response = await fetch('/api/save', {
|
|
209
|
+
method: 'POST',
|
|
210
|
+
headers: { 'Content-Type': 'application/json' },
|
|
211
|
+
body: JSON.stringify({ content })
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
if (response.ok) {
|
|
215
|
+
console.log('Content saved successfully');
|
|
216
|
+
this.showSaveIndicator();
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('Save failed:', error);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private showSaveIndicator() {
|
|
224
|
+
const indicator = this.querySelector('.save-indicator');
|
|
225
|
+
if (indicator) {
|
|
226
|
+
indicator.classList.add('show');
|
|
227
|
+
setTimeout(() => indicator.classList.remove('show'), 2000);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Example 7: Keyboard Shortcut Handler with Throttle
|
|
234
|
+
*
|
|
235
|
+
* Prevents rapid repeated execution of keyboard shortcuts.
|
|
236
|
+
*/
|
|
237
|
+
@elementDefine('keyboard-handler')
|
|
238
|
+
export class KeyboardHandler extends HTMLElement {
|
|
239
|
+
@addEventListener('$document', 'keydown', {
|
|
240
|
+
throttleTime: 500,
|
|
241
|
+
filter: (event: KeyboardEvent) => {
|
|
242
|
+
// Only handle Ctrl+S or Cmd+S
|
|
243
|
+
return (event.ctrlKey || event.metaKey) && event.key === 's';
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
onSaveShortcut(event: KeyboardEvent) {
|
|
247
|
+
event.preventDefault();
|
|
248
|
+
console.log('Save shortcut triggered');
|
|
249
|
+
this.handleSave();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private handleSave() {
|
|
253
|
+
// Handle save operation
|
|
254
|
+
console.log('Saving...');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Example 8: Mouse Move Tracking with Throttle
|
|
260
|
+
*
|
|
261
|
+
* Tracks mouse position at most 10 times per second.
|
|
262
|
+
* Useful for analytics or interactive features.
|
|
263
|
+
*/
|
|
264
|
+
@elementDefine('mouse-tracker')
|
|
265
|
+
export class MouseTracker extends HTMLElement {
|
|
266
|
+
@addEventListener('$document', 'mousemove', { throttleTime: 100 })
|
|
267
|
+
onMouseMove(event: MouseEvent) {
|
|
268
|
+
const x = event.clientX;
|
|
269
|
+
const y = event.clientY;
|
|
270
|
+
|
|
271
|
+
// Update cursor position indicator
|
|
272
|
+
this.updateCursorPosition(x, y);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private updateCursorPosition(x: number, y: number) {
|
|
276
|
+
// Update UI with cursor position
|
|
277
|
+
console.log(`Cursor at: ${x}, ${y}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,8 @@ export * from './utils/Utils';
|
|
|
15
15
|
// Config
|
|
16
16
|
export * from './config/config';
|
|
17
17
|
|
|
18
|
+
export * from './SwcAppEngine'
|
|
19
|
+
|
|
18
20
|
// Registration Factory
|
|
19
21
|
export default async (w: Window, other?: (((w: Window) => Promise<CustomElementConstructor> | CustomElementConstructor) | any) []) => {
|
|
20
22
|
const cr = await defineSwcAppAll(w);
|
|
@@ -7,6 +7,10 @@ export class ElementRouter extends Router {
|
|
|
7
7
|
|
|
8
8
|
constructor(config: RouterConfig) {
|
|
9
9
|
super(config);
|
|
10
|
+
if (config.firstUrl) {
|
|
11
|
+
this._history.push({ url: config.firstUrl, data: null, title: undefined });
|
|
12
|
+
this._currentIndex = 0;
|
|
13
|
+
}
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
// Override pushState to manage internal history
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/decorators/changedAttributeThis.ts"],
|
|
4
|
-
"sourcesContent": ["import { ReflectUtils } from '@dooboostore/core';\n\nexport const ON_ATTRIBUTE_CHANGED_METADATA_KEY = Symbol.for('simple-web-component:on-attribute-changed');\n\nexport interface ChangedAttributeThisOptions {\n type?: typeof Number | typeof Boolean | typeof String;\n while?: 'connected';\n}\n\nexport interface ChangedAttributeThisMetadata {\n attributeName: string;\n propertyKey: string | symbol;\n options: ChangedAttributeThisOptions;\n}\n\nconst convertValue = (val: any, type: any): any => {\n if (val === null || val === undefined) return val;\n if (type === Number) return Number(val);\n if (type === Boolean) return val === 'false' || val === '0' ? false : true;\n return val;\n};\n\n/**\n * @changedAttributeThis decorator - fires when any attribute on $this changes\n */\nexport function changedAttributeThis(attributeName?: string, options: ChangedAttributeThisOptions = {}): MethodDecorator {\n return (target: Object, propertyKey: string | symbol) => {\n const constructor = target.constructor;\n let metaList = ReflectUtils.getOwnMetadata(ON_ATTRIBUTE_CHANGED_METADATA_KEY, constructor) as ChangedAttributeThisMetadata[];\n if (!metaList) {\n metaList = [];\n ReflectUtils.defineMetadata(ON_ATTRIBUTE_CHANGED_METADATA_KEY, metaList, constructor);\n }\n\n const name = attributeName || String(propertyKey);\n metaList.push({\n attributeName: name,\n propertyKey,\n options\n });\n };\n}\n\nexport const getChangedAttributeMetadata = (target: any): ChangedAttributeThisMetadata[] | undefined => {\n const constructor = target instanceof Function ? target : target.constructor;\n return ReflectUtils.getMetadata(ON_ATTRIBUTE_CHANGED_METADATA_KEY, constructor);\n};\n\nexport const findAllAttributeChangedMetadata = (target: any): Map<string, ChangedAttributeThisMetadata[]> => {\n const constructor = target instanceof Function ? target : target.constructor;\n const metaList = ReflectUtils.findAllMetadata<ChangedAttributeThisMetadata[]>(ON_ATTRIBUTE_CHANGED_METADATA_KEY, constructor) || [];\n\n const result = new Map<string, ChangedAttributeThisMetadata[]>();\n metaList.forEach(meta => {\n meta.forEach(item => {\n if (!result.has(item.attributeName)) {\n result.set(item.attributeName, []);\n }\n result.get(item.attributeName)!.push(item);\n });\n });\n return result;\n};\n\nexport const convertAttributeValue = (val: any, type?: typeof Number | typeof Boolean | typeof String): any => {\n return convertValue(val, type);\n};\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAA6B;AAEtB,MAAM,oCAAoC,OAAO,IAAI,2CAA2C;AAavG,MAAM,eAAe,CAAC,KAAU,SAAmB;AACjD,MAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;AAC9C,MAAI,SAAS,OAAQ,QAAO,OAAO,GAAG;AACtC,MAAI,SAAS,QAAS,QAAO,QAAQ,WAAW,QAAQ,MAAM,QAAQ;AACtE,SAAO;AACT;AAKO,SAAS,qBAAqB,eAAwB,UAAuC,CAAC,GAAoB;AACvH,SAAO,CAAC,QAAgB,gBAAiC;AACvD,UAAM,cAAc,OAAO;AAC3B,QAAI,WAAW,yBAAa,eAAe,mCAAmC,WAAW;AACzF,QAAI,CAAC,UAAU;AACb,iBAAW,CAAC;AACZ,+BAAa,eAAe,mCAAmC,UAAU,WAAW;AAAA,IACtF;AAEA,UAAM,OAAO,iBAAiB,OAAO,WAAW;AAChD,aAAS,KAAK;AAAA,MACZ,eAAe;AAAA,MACf;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEO,MAAM,8BAA8B,CAAC,WAA4D;AACtG,QAAM,cAAc,kBAAkB,WAAW,SAAS,OAAO;AACjE,SAAO,yBAAa,YAAY,mCAAmC,WAAW;AAChF;AAEO,MAAM,kCAAkC,CAAC,WAA6D;AAC3G,QAAM,cAAc,kBAAkB,WAAW,SAAS,OAAO;AACjE,QAAM,WAAW,yBAAa,gBAAgD,mCAAmC,WAAW,KAAK,CAAC;AAElI,QAAM,SAAS,oBAAI,IAA4C;AAC/D,WAAS,QAAQ,UAAQ;AACvB,SAAK,QAAQ,UAAQ;AACnB,UAAI,CAAC,OAAO,IAAI,KAAK,aAAa,GAAG;AACnC,eAAO,IAAI,KAAK,eAAe,CAAC,CAAC;AAAA,MACnC;AACA,aAAO,IAAI,KAAK,aAAa,EAAG,KAAK,IAAI;AAAA,IAC3C,CAAC;AAAA,EACH,CAAC;AACD,SAAO;AACT;AAEO,MAAM,wBAAwB,CAAC,KAAU,SAA+D;AAC7G,SAAO,aAAa,KAAK,IAAI;AAC/B;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/decorators/changedAttributeThis.ts"],
|
|
4
|
-
"sourcesContent": ["import { ReflectUtils } from '@dooboostore/core';\n\nexport const ON_ATTRIBUTE_CHANGED_METADATA_KEY = Symbol.for('simple-web-component:on-attribute-changed');\n\nexport interface ChangedAttributeThisOptions {\n type?: typeof Number | typeof Boolean | typeof String;\n while?: 'connected';\n}\n\nexport interface ChangedAttributeThisMetadata {\n attributeName: string;\n propertyKey: string | symbol;\n options: ChangedAttributeThisOptions;\n}\n\nconst convertValue = (val: any, type: any): any => {\n if (val === null || val === undefined) return val;\n if (type === Number) return Number(val);\n if (type === Boolean) return val === 'false' || val === '0' ? false : true;\n return val;\n};\n\n/**\n * @changedAttributeThis decorator - fires when any attribute on $this changes\n */\nexport function changedAttributeThis(attributeName?: string, options: ChangedAttributeThisOptions = {}): MethodDecorator {\n return (target: Object, propertyKey: string | symbol) => {\n const constructor = target.constructor;\n let metaList = ReflectUtils.getOwnMetadata(ON_ATTRIBUTE_CHANGED_METADATA_KEY, constructor) as ChangedAttributeThisMetadata[];\n if (!metaList) {\n metaList = [];\n ReflectUtils.defineMetadata(ON_ATTRIBUTE_CHANGED_METADATA_KEY, metaList, constructor);\n }\n\n const name = attributeName || String(propertyKey);\n metaList.push({\n attributeName: name,\n propertyKey,\n options\n });\n };\n}\n\nexport const getChangedAttributeMetadata = (target: any): ChangedAttributeThisMetadata[] | undefined => {\n const constructor = target instanceof Function ? target : target.constructor;\n return ReflectUtils.getMetadata(ON_ATTRIBUTE_CHANGED_METADATA_KEY, constructor);\n};\n\nexport const findAllAttributeChangedMetadata = (target: any): Map<string, ChangedAttributeThisMetadata[]> => {\n const constructor = target instanceof Function ? target : target.constructor;\n const metaList = ReflectUtils.findAllMetadata<ChangedAttributeThisMetadata[]>(ON_ATTRIBUTE_CHANGED_METADATA_KEY, constructor) || [];\n\n const result = new Map<string, ChangedAttributeThisMetadata[]>();\n metaList.forEach(meta => {\n meta.forEach(item => {\n if (!result.has(item.attributeName)) {\n result.set(item.attributeName, []);\n }\n result.get(item.attributeName)!.push(item);\n });\n });\n return result;\n};\n\nexport const convertAttributeValue = (val: any, type?: typeof Number | typeof Boolean | typeof String): any => {\n return convertValue(val, type);\n};\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAEtB,MAAM,oCAAoC,OAAO,IAAI,2CAA2C;AAavG,MAAM,eAAe,CAAC,KAAU,SAAmB;AACjD,MAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;AAC9C,MAAI,SAAS,OAAQ,QAAO,OAAO,GAAG;AACtC,MAAI,SAAS,QAAS,QAAO,QAAQ,WAAW,QAAQ,MAAM,QAAQ;AACtE,SAAO;AACT;AAKO,SAAS,qBAAqB,eAAwB,UAAuC,CAAC,GAAoB;AACvH,SAAO,CAAC,QAAgB,gBAAiC;AACvD,UAAM,cAAc,OAAO;AAC3B,QAAI,WAAW,aAAa,eAAe,mCAAmC,WAAW;AACzF,QAAI,CAAC,UAAU;AACb,iBAAW,CAAC;AACZ,mBAAa,eAAe,mCAAmC,UAAU,WAAW;AAAA,IACtF;AAEA,UAAM,OAAO,iBAAiB,OAAO,WAAW;AAChD,aAAS,KAAK;AAAA,MACZ,eAAe;AAAA,MACf;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEO,MAAM,8BAA8B,CAAC,WAA4D;AACtG,QAAM,cAAc,kBAAkB,WAAW,SAAS,OAAO;AACjE,SAAO,aAAa,YAAY,mCAAmC,WAAW;AAChF;AAEO,MAAM,kCAAkC,CAAC,WAA6D;AAC3G,QAAM,cAAc,kBAAkB,WAAW,SAAS,OAAO;AACjE,QAAM,WAAW,aAAa,gBAAgD,mCAAmC,WAAW,KAAK,CAAC;AAElI,QAAM,SAAS,oBAAI,IAA4C;AAC/D,WAAS,QAAQ,UAAQ;AACvB,SAAK,QAAQ,UAAQ;AACnB,UAAI,CAAC,OAAO,IAAI,KAAK,aAAa,GAAG;AACnC,eAAO,IAAI,KAAK,eAAe,CAAC,CAAC;AAAA,MACnC;AACA,aAAO,IAAI,KAAK,aAAa,EAAG,KAAK,IAAI;AAAA,IAC3C,CAAC;AAAA,EACH,CAAC;AACD,SAAO;AACT;AAEO,MAAM,wBAAwB,CAAC,KAAU,SAA+D;AAC7G,SAAO,aAAa,KAAK,IAAI;AAC/B;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"changedAttributeThis.d.ts","sourceRoot":"","sources":["../../../src/decorators/changedAttributeThis.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iCAAiC,eAA0D,CAAC;AAEzG,MAAM,WAAW,2BAA2B;IAC1C,IAAI,CAAC,EAAE,OAAO,MAAM,GAAG,OAAO,OAAO,GAAG,OAAO,MAAM,CAAC;IACtD,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,4BAA4B;IAC3C,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,OAAO,EAAE,2BAA2B,CAAC;CACtC;AASD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE,2BAAgC,GAAG,eAAe,CAgBvH;AAED,eAAO,MAAM,2BAA2B,GAAI,QAAQ,GAAG,KAAG,4BAA4B,EAAE,GAAG,SAG1F,CAAC;AAEF,eAAO,MAAM,+BAA+B,GAAI,QAAQ,GAAG,KAAG,GAAG,CAAC,MAAM,EAAE,4BAA4B,EAAE,CAcvG,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAAI,KAAK,GAAG,EAAE,OAAO,OAAO,MAAM,GAAG,OAAO,OAAO,GAAG,OAAO,MAAM,KAAG,GAEvG,CAAC"}
|