@dooboostore/simple-web-component 1.0.29 → 1.0.31

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.
Files changed (108) hide show
  1. package/README.md +651 -27
  2. package/dist/cjs/decorators/elementDefine.js +5 -3
  3. package/dist/cjs/decorators/elementDefine.js.map +2 -2
  4. package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
  5. package/dist/cjs/decorators/index.js +1 -0
  6. package/dist/cjs/decorators/index.js.map +2 -2
  7. package/dist/cjs/decorators/setProperty.js +97 -0
  8. package/dist/cjs/decorators/setProperty.js.map +7 -0
  9. package/dist/cjs/elements/SwcAppEngine.js +3 -3
  10. package/dist/cjs/elements/SwcAppEngine.js.map +2 -2
  11. package/dist/cjs/elements/SwcAppMixin.js +1 -8
  12. package/dist/cjs/elements/SwcAppMixin.js.map +2 -2
  13. package/dist/cjs/elements/SwcAsync.js +37 -53
  14. package/dist/cjs/elements/SwcAsync.js.map +2 -2
  15. package/dist/cjs/elements/SwcChoose.js +55 -60
  16. package/dist/cjs/elements/SwcChoose.js.map +2 -2
  17. package/dist/cjs/elements/SwcIf.js +31 -60
  18. package/dist/cjs/elements/SwcIf.js.map +2 -2
  19. package/dist/cjs/elements/SwcLoop.js +20 -43
  20. package/dist/cjs/elements/SwcLoop.js.map +2 -2
  21. package/dist/cjs/elements/SwcSubTemplates.js +0 -27
  22. package/dist/cjs/elements/SwcSubTemplates.js.map +2 -2
  23. package/dist/cjs/elements/register.js +216 -128
  24. package/dist/cjs/elements/register.js.map +2 -2
  25. package/dist/cjs/index.js +2 -1
  26. package/dist/cjs/index.js.map +2 -2
  27. package/dist/cjs/router/ElementRouter.js +1 -8
  28. package/dist/cjs/router/ElementRouter.js.map +2 -2
  29. package/dist/cjs/types.js.map +1 -1
  30. package/dist/cjs/utils/Utils.js +26 -10
  31. package/dist/cjs/utils/Utils.js.map +2 -2
  32. package/dist/esm/decorators/elementDefine.js +5 -3
  33. package/dist/esm/decorators/elementDefine.js.map +2 -2
  34. package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
  35. package/dist/esm/decorators/index.js +1 -0
  36. package/dist/esm/decorators/index.js.map +2 -2
  37. package/dist/esm/decorators/setProperty.js +78 -0
  38. package/dist/esm/decorators/setProperty.js.map +7 -0
  39. package/dist/esm/elements/SwcAppEngine.js +3 -3
  40. package/dist/esm/elements/SwcAppEngine.js.map +2 -2
  41. package/dist/esm/elements/SwcAppMixin.js +1 -8
  42. package/dist/esm/elements/SwcAppMixin.js.map +2 -2
  43. package/dist/esm/elements/SwcAsync.js +37 -34
  44. package/dist/esm/elements/SwcAsync.js.map +2 -2
  45. package/dist/esm/elements/SwcChoose.js +55 -41
  46. package/dist/esm/elements/SwcChoose.js.map +2 -2
  47. package/dist/esm/elements/SwcIf.js +31 -41
  48. package/dist/esm/elements/SwcIf.js.map +2 -2
  49. package/dist/esm/elements/SwcLoop.js +20 -24
  50. package/dist/esm/elements/SwcLoop.js.map +2 -2
  51. package/dist/esm/elements/SwcSubTemplates.js +0 -8
  52. package/dist/esm/elements/SwcSubTemplates.js.map +2 -2
  53. package/dist/esm/elements/register.js +216 -128
  54. package/dist/esm/elements/register.js.map +2 -2
  55. package/dist/esm/index.js +2 -1
  56. package/dist/esm/index.js.map +2 -2
  57. package/dist/esm/router/ElementRouter.js +1 -8
  58. package/dist/esm/router/ElementRouter.js.map +2 -2
  59. package/dist/esm/types.js.map +1 -1
  60. package/dist/esm/utils/Utils.js +26 -10
  61. package/dist/esm/utils/Utils.js.map +2 -2
  62. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +105 -13
  63. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  64. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  65. package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
  66. package/dist/types/decorators/index.d.ts +1 -0
  67. package/dist/types/decorators/index.d.ts.map +1 -1
  68. package/dist/types/decorators/setProperty.d.ts +13 -0
  69. package/dist/types/decorators/setProperty.d.ts.map +1 -0
  70. package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
  71. package/dist/types/elements/SwcAsync.d.ts +1 -11
  72. package/dist/types/elements/SwcAsync.d.ts.map +1 -1
  73. package/dist/types/elements/SwcChoose.d.ts +1 -13
  74. package/dist/types/elements/SwcChoose.d.ts.map +1 -1
  75. package/dist/types/elements/SwcIf.d.ts +1 -13
  76. package/dist/types/elements/SwcIf.d.ts.map +1 -1
  77. package/dist/types/elements/SwcLoop.d.ts +1 -13
  78. package/dist/types/elements/SwcLoop.d.ts.map +1 -1
  79. package/dist/types/elements/SwcSubTemplates.d.ts +6 -6
  80. package/dist/types/elements/SwcSubTemplates.d.ts.map +1 -1
  81. package/dist/types/elements/register.d.ts +3 -1
  82. package/dist/types/elements/register.d.ts.map +1 -1
  83. package/dist/types/index.d.ts +1 -1
  84. package/dist/types/index.d.ts.map +1 -1
  85. package/dist/types/router/ElementRouter.d.ts.map +1 -1
  86. package/dist/types/types.d.ts +3 -0
  87. package/dist/types/types.d.ts.map +1 -1
  88. package/dist/types/utils/Utils.d.ts +10 -0
  89. package/dist/types/utils/Utils.d.ts.map +1 -1
  90. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +105 -13
  91. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  92. package/package.json +3 -3
  93. package/src/decorators/elementDefine.ts +10 -3
  94. package/src/decorators/emitCustomEvent.ts +1 -0
  95. package/src/decorators/index.ts +1 -0
  96. package/src/decorators/setProperty.ts +97 -0
  97. package/src/elements/SwcAppEngine.ts +3 -3
  98. package/src/elements/SwcAppMixin.ts +7 -28
  99. package/src/elements/SwcAsync.ts +46 -30
  100. package/src/elements/SwcChoose.ts +82 -38
  101. package/src/elements/SwcIf.ts +38 -38
  102. package/src/elements/SwcLoop.ts +24 -19
  103. package/src/elements/SwcSubTemplates.ts +6 -6
  104. package/src/elements/register.ts +261 -121
  105. package/src/index.ts +3 -1
  106. package/src/router/ElementRouter.ts +1 -12
  107. package/src/types.ts +3 -1
  108. package/src/utils/Utils.ts +40 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dooboostore/simple-web-component",
3
- "version": "1.0.29",
3
+ "version": "1.0.31",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -93,9 +93,9 @@
93
93
  "webpack-cli": "^5.1.4",
94
94
  "webpack-dev-server": "^5.2.0",
95
95
  "@dooboostore/core": "1.0.30",
96
- "@dooboostore/simple-boot": "1.0.54",
96
+ "@dooboostore/core-node": "1.0.12",
97
97
  "@dooboostore/core-web": "1.0.10",
98
- "@dooboostore/core-node": "1.0.12"
98
+ "@dooboostore/simple-boot": "1.0.54"
99
99
  },
100
100
  "scripts": {
101
101
  "clean": "rm -rf dist",
@@ -91,6 +91,7 @@ export const ensureInit = (inst: any) => {
91
91
 
92
92
  // Call constructor script if present
93
93
  const hostSet = SwcUtils.getHostSet(inst);
94
+ const appHosts = SwcUtils.findAllParentAppHostsDirect(inst);
94
95
  inst._executeSwcScript?.('swc-on-constructor', hostSet);
95
96
 
96
97
  // Call @onInitialize lifecycle methods
@@ -176,14 +177,14 @@ const setupPrototype = (proto: any) => {
176
177
  ensureInit(this);
177
178
  if (typeof (this as any)[methodName] !== 'function') return;
178
179
  const useHostSet = hostSet ?? SwcUtils.getHostSet(this);
179
- const app = useHostSet?.$appHost.simpleApplication;
180
+ const app = useHostSet?.$appHost?.simpleApplication;
180
181
  // console.log('---->hh',app, this, methodName);
181
182
  if (app) {
182
183
  const otherStorage = new Map<any, any>();
183
184
  const situations = new SituationTypeContainers([new SituationTypeContainer({ situationType: InjectSituationType.HOST_SET, data: useHostSet }), new SituationTypeContainer({ situationType: InjectSituationType.APP_HOST, data: useHostSet.$appHost }), new SituationTypeContainer({ situationType: InjectSituationType.APP_HOSTS, data: useHostSet.$appHosts }), new SituationTypeContainer({ situationType: InjectSituationType.HOST, data: useHostSet.$host }), new SituationTypeContainer({ situationType: InjectSituationType.HOSTS, data: useHostSet.$hosts }), new SituationTypeContainer({ situationType: InjectSituationType.FIRST_HOST, data: useHostSet.$firstHost }), new SituationTypeContainer({ situationType: InjectSituationType.LAST_HOST, data: useHostSet.$lastHost }), new SituationTypeContainer({ situationType: InjectSituationType.FIRST_APP_HOST, data: useHostSet.$firstAppHost }), new SituationTypeContainer({ situationType: InjectSituationType.LAST_APP_HOST, data: useHostSet.$lastAppHost })]);
184
185
  otherStorage.set(SituationTypeContainers, situations);
185
186
 
186
- return app.simstanceManager.executeBindParameterSimPromise(
187
+ return app.simstanceManager.executeBindParameterSim(
187
188
  {
188
189
  target: this,
189
190
  targetKey: methodName,
@@ -331,7 +332,10 @@ export const elementDefine =
331
332
  let sContent = '',
332
333
  lContent = '';
333
334
  for (const meta of connectedInnerHtmlList) {
334
- const res = (this as any)[meta.propertyKey]();
335
+ // const res = (this as any)[meta.propertyKey]();
336
+ const res = (this as any)._invokeLifecycleMethod(meta.propertyKey, hostSet);
337
+ // console.log('------res0', res);
338
+ // console.log('------res1', res1);
335
339
  if (res !== undefined) {
336
340
  if (meta.options.useShadow) sContent += res;
337
341
  else lContent += res;
@@ -494,6 +498,9 @@ export const elementDefine =
494
498
  (this as any).__swc_connected = true;
495
499
 
496
500
  // Register to appHost LAST when all lifecycle completed
501
+ // console.log('!!!!!!!!!!!!!!', this, appHost)
502
+ const appHostss = SwcUtils.findAllParentAppHostsDirect(this)
503
+ // const nAppHostSet = SwcUtils.getHostSet(this as any);
497
504
  if (appHost && typeof (appHost as any)._connected === 'function') {
498
505
  (appHost as any)._connected(this);
499
506
  } else if (appHost && typeof (appHost as any)) {
@@ -52,6 +52,7 @@ export function emitCustomEvent(selectorOrTarget: string, type: string, options:
52
52
  const hostSet = SwcUtils.getHostSet(this as any);
53
53
  const conf = getElementConfig(this);
54
54
  const currentWin = (this as any)._resolveWindow?.(conf) || ((typeof window !== 'undefined' ? window : undefined) as Window);
55
+ // console.log('emitEEEEEEEEEEE', hostSet);
55
56
 
56
57
  const eventTargets: EventTarget[] = [];
57
58
  if (selectorOrTarget === ':window') eventTargets.push(currentWin);
@@ -10,4 +10,5 @@ export * from './queryAll';
10
10
  export * from './applyClass';
11
11
  export * from './applyStyle';
12
12
  export * from './applyAttribute';
13
+ export * from './setProperty';
13
14
  export * from './subscribeSwcAppRouteChange';
@@ -0,0 +1,97 @@
1
+ import { ReflectUtils } from '@dooboostore/core';
2
+ import { ensureInit } from './elementDefine';
3
+ import { SwcUtils } from '../utils/Utils';
4
+ import { SwcQueryOptions } from '../types';
5
+
6
+ export interface PropertySetOptions extends SwcQueryOptions {
7
+ name?: string;
8
+ }
9
+
10
+ export interface PropertySetMetadata {
11
+ propertyKey: string | symbol;
12
+ selector: string;
13
+ options: PropertySetOptions;
14
+ }
15
+
16
+ export const PROPERTY_SET_METADATA_KEY = Symbol.for('simple-web-component:property-set');
17
+
18
+ export const setProperty = (selector: string, propertyNameOrOptions?: string | PropertySetOptions, options: PropertySetOptions = {}): MethodDecorator => {
19
+ return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
20
+ const constructor = target.constructor;
21
+ let metaList = ReflectUtils.getOwnMetadata(PROPERTY_SET_METADATA_KEY, constructor) as Map<string | symbol, PropertySetMetadata>;
22
+ if (!metaList) {
23
+ metaList = new Map<string | symbol, PropertySetMetadata>();
24
+ ReflectUtils.defineMetadata(PROPERTY_SET_METADATA_KEY, metaList, constructor);
25
+ }
26
+
27
+ let propertyName = typeof propertyNameOrOptions === 'string' ? propertyNameOrOptions : undefined;
28
+ let finalOptions = options;
29
+
30
+ if (typeof propertyNameOrOptions === 'object' && propertyNameOrOptions !== null) {
31
+ finalOptions = propertyNameOrOptions as PropertySetOptions;
32
+ }
33
+
34
+ metaList.set(propertyKey, {
35
+ propertyKey,
36
+ selector,
37
+ options: { ...finalOptions, name: propertyName }
38
+ });
39
+
40
+ const original = descriptor.value;
41
+ descriptor.value = async function (...args: any[]) {
42
+ ensureInit(this);
43
+ const res = await (original as any).apply(this, args);
44
+ if (res !== undefined) {
45
+ const win = (this as any).ownerDocument?.defaultView || window;
46
+ const host = this as unknown as HTMLElement;
47
+ const hostSet = { ...SwcUtils.getHelperAndHostSet(win, host), $this: this };
48
+ const r = finalOptions.root || 'auto';
49
+ const targetEls: HTMLElement[] = [];
50
+
51
+ const applyRoot = (t: any) => {
52
+ if (!t || !(t instanceof win.HTMLElement)) return;
53
+ targetEls.push(t);
54
+ };
55
+
56
+ if (selector === ':parentHost') applyRoot(hostSet.$parentHost);
57
+ else if (selector === ':appHost') applyRoot(hostSet.$appHost);
58
+ else if (selector === ':firstHost') applyRoot(hostSet.$firstHost);
59
+ else if (selector === ':lastHost') applyRoot(hostSet.$lastHost);
60
+ else if (selector === ':firstAppHost') applyRoot(hostSet.$firstAppHost);
61
+ else if (selector === ':lastAppHost') applyRoot(hostSet.$lastAppHost);
62
+ else if (selector === ':hosts') hostSet.$hosts.forEach(applyRoot);
63
+ else if (selector === ':appHosts') hostSet.$appHosts.forEach(applyRoot);
64
+ else if (selector === ':host' || !selector) applyRoot(host);
65
+ else {
66
+ const targetRoot = r === 'auto' ? (host.shadowRoot || host) : (r === 'shadow' ? host.shadowRoot : host);
67
+ if (targetRoot) {
68
+ const found = targetRoot.querySelectorAll(selector);
69
+ if (found) targetEls.push(...(Array.from(found) as HTMLElement[]));
70
+ }
71
+ }
72
+
73
+ targetEls.forEach(targetEl => {
74
+ const resolvedRes = typeof res === 'function' ? (res as any)(targetEl, hostSet) : res;
75
+
76
+ if (propertyName) {
77
+ // Case: @setProperty(selector, 'propertyName')
78
+ (targetEl as any)[propertyName] = resolvedRes;
79
+ } else if (typeof resolvedRes === 'object' && resolvedRes !== null && !Array.isArray(resolvedRes)) {
80
+ // Case: @setProperty(selector) with Object return - assign multiple properties
81
+ Object.entries(resolvedRes as any).forEach(([prop, val]) => {
82
+ const finalVal = typeof val === 'function' ? val(targetEl, hostSet) : val;
83
+ (targetEl as any)[prop] = finalVal;
84
+ });
85
+ } else {
86
+ // Simple value - assign to 'value' property by default
87
+ (targetEl as any).value = resolvedRes;
88
+ }
89
+ });
90
+ }
91
+ };
92
+ };
93
+ };
94
+
95
+ export const setPropertyHost = (propertyNameOrOptions?: string | PropertySetOptions, options: PropertySetOptions = {}): MethodDecorator => {
96
+ return setProperty(':host', propertyNameOrOptions, options);
97
+ };
@@ -42,11 +42,11 @@ export class SwcAppEngine {
42
42
 
43
43
  // Router Selection
44
44
  if (this._config.routeType === 'path') {
45
- this.router = new PathRouter({ window: this._config.window });
45
+ this.router = new PathRouter({ window: this._config.window, firstUrl: this._config.path });
46
46
  } else if (this._config.routeType === 'hash') {
47
- this.router = new HashRouter({ window: this._config.window });
47
+ this.router = new HashRouter({ window: this._config.window, firstUrl: this._config.path });
48
48
  } else if (this._config.routeType === 'element') {
49
- this.router = new ElementRouter({ window: this._config.window });
49
+ this.router = new ElementRouter({ window: this._config.window, firstUrl: this._config.path });
50
50
  }
51
51
 
52
52
  // console.log('vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv', this.router)
@@ -28,11 +28,6 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
28
28
  return this.__swc_engine.router;
29
29
  }
30
30
 
31
- // constructor() {
32
- // super();
33
- // console.log('cccccccccccccccccccccc');
34
- // }
35
-
36
31
  _invokeRouteChangeSubscribers(instance: any, re: RouterEventType) {
37
32
  const routeChangeSubscribers = getSubscribeSwcAppRouteChangeMetadata(instance);
38
33
  if (routeChangeSubscribers && Array.isArray(routeChangeSubscribers)) {
@@ -70,14 +65,14 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
70
65
 
71
66
  // Invoke method if conditions pass
72
67
  if (pathMatched && filterPassed) {
73
- instance[methodName]?.(re, pathData || {});
68
+ instance[methodName]?.({...re, pathData: pathData});
74
69
  }
75
70
  });
76
71
  }
77
72
  }
78
73
 
79
74
  _connected(instance: any) {
80
- // console.log('appmixin--> connected', instance);
75
+ // console.log('appmixin--> connected', this, instance);
81
76
  if (instance) {
82
77
  this._swc_connected_instance.add(instance);
83
78
  if (this.simpleApplication) {
@@ -153,35 +148,19 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
153
148
  this._invokeOnConnectedSwcAppStarted(instance);
154
149
  });
155
150
 
156
- // Safari fix: Manually scan DOM tree and register any SWC components that couldn't find parent
157
- // This handles the case where is="" elements get upgraded in child-first order
158
- // const allElements = (this as any).querySelectorAll('*');
159
- // for (const el of allElements) {
160
- // const isConnected = (el as any).__swc_connected;
161
- // if (!isConnected && typeof (el as any)._resolveWindow === 'function') {
162
- // // Element is an SWC component but hasn't registered with app yet
163
- // try {
164
- // this._connected(el);
165
- // if ((el as any)._invokeOnConnectedSwcAppStarted) {
166
- // this._invokeOnConnectedSwcAppStarted(el);
167
- // }
168
- // } catch (e) {
169
- // console.warn('[SWC] Error registering discovered element:', el.tagName, e);
170
- // }
171
- // }
172
- // }
173
151
 
174
152
  // Subscribe to router changes after connect
175
153
  if (!this._routerSubscription && this.router) {
176
154
  this._routerSubscription = this.router.observable.subscribe((route: RouterEventType) => {
177
- // console.log('Route changed:', route);
155
+ //@ts-ignore
156
+ // console.log('Route changed:', route, this._swcId);
178
157
  this._handleRouteChange(route);
179
158
  });
180
159
  // Navigate to initial path if specified
181
160
  const config = this.__swc_engine.config;
182
- if (config?.path) {
183
- this.router.go(config.path);
184
- }
161
+ // if (config?.path) {
162
+ // this.router.go(config.path);
163
+ // }
185
164
  }
186
165
  }
187
166
 
@@ -2,39 +2,42 @@ import { changedAttributeHost } from '../decorators/changedAttributeHost';
2
2
  import { SwcUtils } from '../utils/Utils';
3
3
  import { FunctionUtils } from '@dooboostore/core';
4
4
 
5
- export class SwcAsync extends HTMLTemplateElement {
5
+ class SwcAsync extends HTMLTemplateElement {
6
6
  private _nodes: Node[] = [];
7
7
  private _value: any = null;
8
8
 
9
- private _resolvePortal(): Element | null {
10
- const portalScript = this.getAttribute('on-get-portal');
11
- if (!portalScript) return this.parentElement;
12
-
13
- const win = this.ownerDocument?.defaultView || window;
14
- const res = FunctionUtils.executeReturn({
15
- script: portalScript,
16
- context: this,
17
- args: SwcUtils.getHelperAndHostSet(win, this)
18
- });
19
-
20
- if (res instanceof HTMLElement) return res;
21
- if (typeof res === 'string') return win.document.querySelector(res);
22
- return this.parentElement;
23
- }
24
-
25
9
  private _executeCloneCallback(attr: string, args: any) {
26
10
  const script = this.getAttribute(attr);
27
11
  if (!script) return;
28
12
  FunctionUtils.execute({ script, context: this, args });
29
13
  }
30
14
 
31
- @changedAttributeHost('on-get-value')
32
- private refresh() {
33
- const script = this.getAttribute('on-get-value');
34
- if (!script) return;
35
- const win = this.ownerDocument?.defaultView || window;
36
- const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
15
+ @changedAttributeHost('value')
16
+ private refresh(valueAttr?: string | null) {
17
+ valueAttr ??= this.getAttribute('value');
18
+ if (!valueAttr) {
19
+ this.render('default');
20
+ return;
21
+ }
37
22
 
23
+ // {{ }} 브레이스로 감싸진 값은 스크립트 실행
24
+ if (valueAttr.startsWith('{{') && valueAttr.endsWith('}}')) {
25
+ const script = valueAttr.slice(2, -2).trim();
26
+ const win = this.ownerDocument?.defaultView || window;
27
+ const result = FunctionUtils.executeReturn({
28
+ script,
29
+ context: this,
30
+ args: SwcUtils.getHelperAndHostSet(win, this)
31
+ });
32
+
33
+ this.executeAsync(result);
34
+ } else {
35
+ // 일반 문자열 값 또는 직접 Promise 속성
36
+ this.executeAsync(valueAttr);
37
+ }
38
+ }
39
+
40
+ private executeAsync(result: any) {
38
41
  if (result instanceof Promise) {
39
42
  this.render('loading');
40
43
  result
@@ -51,7 +54,7 @@ export class SwcAsync extends HTMLTemplateElement {
51
54
 
52
55
  private render(state: string) {
53
56
  this.cleanup();
54
- const portal = this._resolvePortal();
57
+ const portal = this.parentElement;
55
58
  if (!portal) return;
56
59
 
57
60
  const win = this.ownerDocument?.defaultView || window;
@@ -64,18 +67,31 @@ export class SwcAsync extends HTMLTemplateElement {
64
67
  const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
65
68
  const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0], $state: state };
66
69
 
70
+ // 부모 체인을 정상적으로 유지하기 위해 __swc_host 할당 제거
67
71
  this._nodes.forEach((n, nodeIdx) => {
68
- if (n instanceof HTMLElement) (n as any).__swc_host = this;
72
+ // if (n instanceof HTMLElement) (n as any).__swc_host = this; // 제거: 부모 체인 유지
69
73
  this._executeCloneCallback('on-clone-node', { ...groupArgs, $node: n, $nodeIndex: nodeIdx, $isElement: n.nodeType === 1 });
74
+
75
+ // Attribute {{ }} 치환 - SwcChoose처럼 동적 치환
76
+ if (n instanceof HTMLElement) {
77
+ Array.from(n.attributes).forEach(attr => {
78
+ const value = attr.value || '';
79
+ if (value.startsWith('{{') && value.endsWith('}}')) {
80
+ const script = value.slice(2, -2).trim();
81
+ const result = FunctionUtils.executeReturn({
82
+ script,
83
+ context: n,
84
+ args: groupArgs
85
+ });
86
+ n.setAttribute(attr.name, String(result));
87
+ }
88
+ });
89
+ }
70
90
  });
71
91
 
72
92
  this._executeCloneCallback('on-clone-nodes', groupArgs);
73
93
 
74
- if (portal === this.parentElement) {
75
- portal.insertBefore(clone, this.nextSibling);
76
- } else {
77
- portal.appendChild(clone);
78
- }
94
+ portal.insertBefore(clone, this.nextSibling);
79
95
  }
80
96
  }
81
97
 
@@ -1,10 +1,12 @@
1
1
  import { changedAttributeHost } from '../decorators/changedAttributeHost';
2
2
  import { SwcUtils } from '../utils/Utils';
3
3
  import { FunctionUtils } from '@dooboostore/core';
4
+ import { SwcChooseInterface } from '../types';
4
5
 
5
- export class SwcChoose extends HTMLTemplateElement {
6
+ class SwcChoose extends HTMLTemplateElement implements SwcChooseInterface {
6
7
  private _nodes: Node[] = [];
7
8
  private _value: any = null;
9
+ private _previousTemplate: HTMLTemplateElement | null = null;
8
10
 
9
11
  get value(): any {
10
12
  return this._value;
@@ -14,76 +16,118 @@ export class SwcChoose extends HTMLTemplateElement {
14
16
  this.render();
15
17
  }
16
18
 
17
- private _resolvePortal(): Element | null {
18
- const portalScript = this.getAttribute('on-get-portal');
19
- if (!portalScript) return this.parentElement;
20
-
21
- const win = this.ownerDocument?.defaultView || window;
22
- const res = FunctionUtils.executeReturn({
23
- script: portalScript,
24
- context: this,
25
- args: SwcUtils.getHelperAndHostSet(win, this)
26
- });
27
-
28
- if (res instanceof HTMLElement) return res;
29
- if (typeof res === 'string') return win.document.querySelector(res);
30
- return this.parentElement;
31
- }
32
-
33
19
  private _executeCloneCallback(attr: string, args: any) {
34
20
  const script = this.getAttribute(attr);
35
21
  if (!script) return;
36
22
  FunctionUtils.execute({ script, context: this, args });
37
23
  }
38
24
 
39
- @changedAttributeHost('on-get-value')
40
- private refresh() {
41
- const script = this.getAttribute('on-get-value');
42
- if (!script) {
25
+ @changedAttributeHost('value')
26
+ refresh(valueAttr?: string | null) {
27
+ valueAttr ??= this.getAttribute('value');
28
+ if (valueAttr === null) {
43
29
  this.value = null;
44
30
  return;
45
31
  }
46
- const win = this.ownerDocument?.defaultView || window;
47
- const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
48
- this.value = result;
32
+
33
+ // {{ }} 브레이스로 감싸진 값은 스크립트 실행
34
+ if (valueAttr.startsWith('{{') && valueAttr.endsWith('}}')) {
35
+ const script = valueAttr.slice(2, -2).trim();
36
+ const win = this.ownerDocument?.defaultView || window;
37
+ const helperAndHostSet = SwcUtils.getHelperAndHostSet(win, SwcUtils.findNearestSwcHost(this));
38
+ // console.log('vvvvvvvvvvvvvvvvvv323', helperAndHostSet);
39
+ const result = FunctionUtils.executeReturn({ script, context: this, args: helperAndHostSet });
40
+ this.value = result;
41
+ // console.log('----------vv', this.value);
42
+ } else {
43
+ // 일반 문자열 값
44
+ this.value = valueAttr;
45
+ }
49
46
  }
50
47
 
51
48
  private render() {
52
- this.cleanup();
53
- const portal = this._resolvePortal();
54
- if (!portal) return;
55
-
49
+ // console.log('-------------', this.value);
56
50
  const win = this.ownerDocument?.defaultView || window;
57
51
  const helperSet = SwcUtils.getHelperAndHostSet(win, this);
58
-
59
52
  const templates = Array.from(this.content.querySelectorAll('template'));
60
53
  let selected: HTMLTemplateElement | null = null;
54
+
61
55
  for (const t of templates) {
62
- if (t.getAttribute('is') === 'swc-when' && String(t.getAttribute('value')) === String(this._value)) {
63
- selected = t;
64
- break;
56
+ if (t.getAttribute('is') === 'swc-when') {
57
+ let matches = false;
58
+ const value = t.getAttribute('value') || '';
59
+
60
+ // {{ }} 브레이스로 감싸진 값은 스크립트 실행
61
+ if (value.startsWith('{{') && value.endsWith('}}')) {
62
+ const script = value.slice(2, -2).trim();
63
+ const result = FunctionUtils.executeReturn({
64
+ script,
65
+ context: t,
66
+ args: { ...helperSet, $value: this._value }
67
+ });
68
+ // console.log('rrrrrrrrrrrrrr', result);
69
+ matches = result === true;
70
+ } else {
71
+ // 일반 문자열 비교 (기존 방식)
72
+ matches = String(value) === String(this._value);
73
+ }
74
+
75
+ if (matches) {
76
+ selected = t;
77
+ break;
78
+ }
65
79
  }
66
80
  }
81
+
67
82
  const target = selected || (this.content.querySelector('template[is="swc-otherwise"]') as HTMLTemplateElement);
68
83
 
84
+ // skip-if-same attribute가 있고 이전과 같은 template이면 렌더링 스킵
85
+ // 최상위 skip-if-same 또는 각 when/otherwise의 skip-if-same 체크
86
+ const targetHasSkipIfSame = target?.hasAttribute('skip-if-same');
87
+ const shouldSkip = (this.hasAttribute('skip-if-same') || targetHasSkipIfSame) && this._previousTemplate === target;
88
+ if (shouldSkip) {
89
+ return;
90
+ }
91
+
92
+ this.cleanup();
93
+ this._previousTemplate = target;
94
+
69
95
  if (target) {
70
96
  const clone = target.content.cloneNode(true) as DocumentFragment;
71
97
  this._nodes = Array.from(clone.childNodes);
72
98
  const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
73
99
  const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0] };
74
100
 
101
+ // 복제된 최상위 요소들은 SwcChoose의 직접 자식이 될 것이므로 __swc_host 설정 안 함
75
102
  this._nodes.forEach((n, nodeIdx) => {
76
- if (n instanceof HTMLElement) (n as any).__swc_host = this;
103
+ // if (n instanceof HTMLElement) (n as any).__swc_host = this; // 제거: 부모 체인 유지
77
104
  this._executeCloneCallback('on-clone-node', { ...groupArgs, $node: n, $nodeIndex: nodeIdx, $isElement: n.nodeType === 1 });
105
+
106
+ // Attribute {{ }} 치환 - HTML string 차원에서 동적 치환
107
+ if (n instanceof HTMLElement) {
108
+ Array.from(n.attributes).forEach(attr => {
109
+ const value = attr.value || '';
110
+ if (value.startsWith('{{') && value.endsWith('}}')) {
111
+ const script = value.slice(2, -2).trim();
112
+ const result = FunctionUtils.executeReturn({
113
+ script,
114
+ context: n,
115
+ args: { ...groupArgs, $value: this._value }
116
+ });
117
+ n.setAttribute(attr.name, String(result));
118
+ }
119
+ });
120
+ }
78
121
  });
79
122
 
80
123
  this._executeCloneCallback('on-clone-nodes', groupArgs);
81
124
 
82
- if (portal === this.parentElement) {
83
- portal.insertBefore(clone, this.nextSibling);
84
- } else {
85
- portal.appendChild(clone);
86
- }
125
+ this.parentElement?.insertBefore(clone, this.nextSibling);
126
+ // if (portal === this.parentElement) {
127
+ // portal.insertBefore(clone, this.nextSibling);
128
+ // } else {
129
+ // portal.appendChild(clone);
130
+ // }
87
131
  }
88
132
  }
89
133
 
@@ -2,7 +2,7 @@ import { changedAttributeHost } from '../decorators/changedAttributeHost';
2
2
  import { SwcUtils } from '../utils/Utils';
3
3
  import { FunctionUtils } from '@dooboostore/core';
4
4
 
5
- export class SwcIf extends HTMLTemplateElement {
5
+ class SwcIf extends HTMLTemplateElement {
6
6
  private _value = false;
7
7
  private _nodes: Node[] = [];
8
8
 
@@ -14,52 +14,39 @@ export class SwcIf extends HTMLTemplateElement {
14
14
  this.render();
15
15
  }
16
16
 
17
- private _resolvePortal(): Element | null {
18
- const portalScript = this.getAttribute('on-get-portal');
19
- if (!portalScript) return this.parentElement;
20
-
21
- const win = this.ownerDocument?.defaultView || window;
22
- const res = FunctionUtils.executeReturn({
23
- script: portalScript,
24
- context: this,
25
- args: SwcUtils.getHelperAndHostSet(win, this)
26
- });
27
-
28
- if (res instanceof HTMLElement) return res;
29
- if (typeof res === 'string') return win.document.querySelector(res);
30
- return this.parentElement;
31
- }
32
-
33
17
  private _executeCloneCallback(attr: string, args: any) {
34
18
  const script = this.getAttribute(attr);
35
19
  if (!script) return;
36
20
  FunctionUtils.execute({ script, context: this, args });
37
21
  }
38
22
 
39
- @changedAttributeHost('on-get-value')
40
- private refreshValue() {
41
- const script = this.getAttribute('on-get-value');
42
- if (!script) {
23
+ @changedAttributeHost('value')
24
+ private refreshValue(valueAttr?: string | null) {
25
+ valueAttr ??= this.getAttribute('value');
26
+ if (valueAttr === null) {
43
27
  this.value = false;
44
28
  return;
45
29
  }
46
- const win = this.ownerDocument?.defaultView || window;
47
- const res = FunctionUtils.executeReturn({
48
- script,
49
- context: this,
50
- args: SwcUtils.getHelperAndHostSet(win, this)
51
- });
52
-
53
- if (res instanceof Promise) {
54
- res.then(v => (this.value = v)).catch(() => (this.value = false));
30
+
31
+ // {{ }} 브레이스로 감싸진 값은 스크립트 실행
32
+ if (valueAttr.startsWith('{{') && valueAttr.endsWith('}}')) {
33
+ const script = valueAttr.slice(2, -2).trim();
34
+ const win = this.ownerDocument?.defaultView || window;
35
+ const result = FunctionUtils.executeReturn({
36
+ script,
37
+ context: this,
38
+ args: SwcUtils.getHelperAndHostSet(win, this)
39
+ });
40
+ this.value = result;
55
41
  } else {
56
- this.value = res;
42
+ // 일반 문자열 값
43
+ this.value = Boolean(valueAttr);
57
44
  }
58
45
  }
59
46
 
60
47
  private render() {
61
48
  this.cleanup();
62
- const portal = this._resolvePortal();
49
+ const portal = this.parentElement;
63
50
  if (!portal) return;
64
51
 
65
52
  const win = this.ownerDocument?.defaultView || window;
@@ -83,18 +70,31 @@ export class SwcIf extends HTMLTemplateElement {
83
70
  const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
84
71
  const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0] };
85
72
 
73
+ // 부모 체인을 정상적으로 유지하기 위해 __swc_host 할당 제거
86
74
  this._nodes.forEach((node, nodeIdx) => {
87
- if (node instanceof HTMLElement) (node as any).__swc_host = this;
75
+ // if (node instanceof HTMLElement) (node as any).__swc_host = this; // 제거: 부모 체인 유지
88
76
  this._executeCloneCallback('on-clone-node', { ...groupArgs, $node: node, $nodeIndex: nodeIdx, $isElement: node.nodeType === 1 });
77
+
78
+ // Attribute {{ }} 치환 - SwcChoose처럼 동적 치환
79
+ if (node instanceof HTMLElement) {
80
+ Array.from(node.attributes).forEach(attr => {
81
+ const value = attr.value || '';
82
+ if (value.startsWith('{{') && value.endsWith('}}')) {
83
+ const script = value.slice(2, -2).trim();
84
+ const result = FunctionUtils.executeReturn({
85
+ script,
86
+ context: node,
87
+ args: { ...groupArgs, $value: this._value }
88
+ });
89
+ node.setAttribute(attr.name, String(result));
90
+ }
91
+ });
92
+ }
89
93
  });
90
94
 
91
95
  this._executeCloneCallback('on-clone-nodes', groupArgs);
92
96
 
93
- if (portal === this.parentElement) {
94
- portal.insertBefore(clone, this.nextSibling);
95
- } else {
96
- portal.appendChild(clone);
97
- }
97
+ portal.insertBefore(clone, this.nextSibling);
98
98
  }
99
99
  }
100
100