@dooboostore/simple-web-component 1.0.29 → 1.0.30

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 (68) hide show
  1. package/README.md +432 -27
  2. package/dist/cjs/decorators/elementDefine.js +2 -0
  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/SwcAppMixin.js +1 -1
  10. package/dist/cjs/elements/SwcAppMixin.js.map +2 -2
  11. package/dist/cjs/elements/SwcAsync.js +4 -1
  12. package/dist/cjs/elements/SwcAsync.js.map +2 -2
  13. package/dist/cjs/elements/SwcChoose.js +54 -38
  14. package/dist/cjs/elements/SwcChoose.js.map +2 -2
  15. package/dist/cjs/elements/register.js +131 -39
  16. package/dist/cjs/elements/register.js.map +2 -2
  17. package/dist/cjs/index.js +2 -1
  18. package/dist/cjs/index.js.map +2 -2
  19. package/dist/cjs/utils/Utils.js +11 -8
  20. package/dist/cjs/utils/Utils.js.map +2 -2
  21. package/dist/esm/decorators/elementDefine.js +2 -0
  22. package/dist/esm/decorators/elementDefine.js.map +2 -2
  23. package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
  24. package/dist/esm/decorators/index.js +1 -0
  25. package/dist/esm/decorators/index.js.map +2 -2
  26. package/dist/esm/decorators/setProperty.js +78 -0
  27. package/dist/esm/decorators/setProperty.js.map +7 -0
  28. package/dist/esm/elements/SwcAppMixin.js +1 -1
  29. package/dist/esm/elements/SwcAppMixin.js.map +2 -2
  30. package/dist/esm/elements/SwcAsync.js +4 -1
  31. package/dist/esm/elements/SwcAsync.js.map +2 -2
  32. package/dist/esm/elements/SwcChoose.js +54 -38
  33. package/dist/esm/elements/SwcChoose.js.map +2 -2
  34. package/dist/esm/elements/register.js +131 -39
  35. package/dist/esm/elements/register.js.map +2 -2
  36. package/dist/esm/index.js +2 -1
  37. package/dist/esm/index.js.map +2 -2
  38. package/dist/esm/utils/Utils.js +11 -8
  39. package/dist/esm/utils/Utils.js.map +2 -2
  40. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +87 -8
  41. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  42. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  43. package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
  44. package/dist/types/decorators/index.d.ts +1 -0
  45. package/dist/types/decorators/index.d.ts.map +1 -1
  46. package/dist/types/decorators/setProperty.d.ts +13 -0
  47. package/dist/types/decorators/setProperty.d.ts.map +1 -0
  48. package/dist/types/elements/SwcAsync.d.ts.map +1 -1
  49. package/dist/types/elements/SwcChoose.d.ts +1 -1
  50. package/dist/types/elements/SwcChoose.d.ts.map +1 -1
  51. package/dist/types/elements/register.d.ts +3 -1
  52. package/dist/types/elements/register.d.ts.map +1 -1
  53. package/dist/types/index.d.ts +1 -1
  54. package/dist/types/index.d.ts.map +1 -1
  55. package/dist/types/utils/Utils.d.ts.map +1 -1
  56. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +87 -8
  57. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  58. package/package.json +1 -1
  59. package/src/decorators/elementDefine.ts +4 -0
  60. package/src/decorators/emitCustomEvent.ts +1 -0
  61. package/src/decorators/index.ts +1 -0
  62. package/src/decorators/setProperty.ts +97 -0
  63. package/src/elements/SwcAppMixin.ts +2 -2
  64. package/src/elements/SwcAsync.ts +5 -1
  65. package/src/elements/SwcChoose.ts +76 -37
  66. package/src/elements/register.ts +158 -39
  67. package/src/index.ts +3 -1
  68. package/src/utils/Utils.ts +15 -11
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.30",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -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
@@ -494,6 +495,9 @@ export const elementDefine =
494
495
  (this as any).__swc_connected = true;
495
496
 
496
497
  // Register to appHost LAST when all lifecycle completed
498
+ // console.log('!!!!!!!!!!!!!!', this, appHost)
499
+ const appHostss = SwcUtils.findAllParentAppHostsDirect(this)
500
+ // const nAppHostSet = SwcUtils.getHostSet(this as any);
497
501
  if (appHost && typeof (appHost as any)._connected === 'function') {
498
502
  (appHost as any)._connected(this);
499
503
  } 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
+ };
@@ -70,14 +70,14 @@ export function SwcAppMixin<T extends { new (...args: any[]): HTMLElement }>(Bas
70
70
 
71
71
  // Invoke method if conditions pass
72
72
  if (pathMatched && filterPassed) {
73
- instance[methodName]?.(re, pathData || {});
73
+ instance[methodName]?.({...re, pathData: pathData});
74
74
  }
75
75
  });
76
76
  }
77
77
  }
78
78
 
79
79
  _connected(instance: any) {
80
- // console.log('appmixin--> connected', instance);
80
+ // console.log('appmixin--> connected', this, instance);
81
81
  if (instance) {
82
82
  this._swc_connected_instance.add(instance);
83
83
  if (this.simpleApplication) {
@@ -31,7 +31,10 @@ export class SwcAsync extends HTMLTemplateElement {
31
31
  @changedAttributeHost('on-get-value')
32
32
  private refresh() {
33
33
  const script = this.getAttribute('on-get-value');
34
- if (!script) return;
34
+ if (!script) {
35
+ this.render('default');
36
+ return;
37
+ }
35
38
  const win = this.ownerDocument?.defaultView || window;
36
39
  const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
37
40
 
@@ -85,6 +88,7 @@ export class SwcAsync extends HTMLTemplateElement {
85
88
  }
86
89
 
87
90
  connectedCallback() {
91
+ console.log('[SwcAsync] connectedCallback called');
88
92
  this.refresh();
89
93
  }
90
94
  disconnectedCallback() {
@@ -5,6 +5,7 @@ import { FunctionUtils } from '@dooboostore/core';
5
5
  export class SwcChoose extends HTMLTemplateElement {
6
6
  private _nodes: Node[] = [];
7
7
  private _value: any = null;
8
+ private _previousTemplate: HTMLTemplateElement | null = null;
8
9
 
9
10
  get value(): any {
10
11
  return this._value;
@@ -14,76 +15,114 @@ export class SwcChoose extends HTMLTemplateElement {
14
15
  this.render();
15
16
  }
16
17
 
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
18
  private _executeCloneCallback(attr: string, args: any) {
34
19
  const script = this.getAttribute(attr);
35
20
  if (!script) return;
36
21
  FunctionUtils.execute({ script, context: this, args });
37
22
  }
38
23
 
39
- @changedAttributeHost('on-get-value')
40
- private refresh() {
41
- const script = this.getAttribute('on-get-value');
42
- if (!script) {
24
+ @changedAttributeHost('value')
25
+ private refresh(valueAttr?: string | null) {
26
+ valueAttr ??= this.getAttribute('value');
27
+ if (valueAttr === null) {
43
28
  this.value = null;
44
29
  return;
45
30
  }
46
- const win = this.ownerDocument?.defaultView || window;
47
- const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
48
- this.value = result;
31
+
32
+ // {{ }} 브레이스로 감싸진 값은 스크립트 실행
33
+ if (valueAttr.startsWith('{{') && valueAttr.endsWith('}}')) {
34
+ const script = valueAttr.slice(2, -2).trim();
35
+ const win = this.ownerDocument?.defaultView || window;
36
+ const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
37
+ this.value = result;
38
+ } else {
39
+ // 일반 문자열 값
40
+ this.value = valueAttr;
41
+ }
49
42
  }
50
43
 
51
44
  private render() {
52
- this.cleanup();
53
- const portal = this._resolvePortal();
54
- if (!portal) return;
55
-
56
45
  const win = this.ownerDocument?.defaultView || window;
57
46
  const helperSet = SwcUtils.getHelperAndHostSet(win, this);
58
-
59
47
  const templates = Array.from(this.content.querySelectorAll('template'));
60
48
  let selected: HTMLTemplateElement | null = null;
49
+
61
50
  for (const t of templates) {
62
- if (t.getAttribute('is') === 'swc-when' && String(t.getAttribute('value')) === String(this._value)) {
63
- selected = t;
64
- break;
51
+ if (t.getAttribute('is') === 'swc-when') {
52
+ let matches = false;
53
+ const value = t.getAttribute('value') || '';
54
+
55
+ // {{ }} 브레이스로 감싸진 값은 스크립트 실행
56
+ if (value.startsWith('{{') && value.endsWith('}}')) {
57
+ const script = value.slice(2, -2).trim();
58
+ const result = FunctionUtils.executeReturn({
59
+ script,
60
+ context: t,
61
+ args: { ...helperSet, $value: this._value }
62
+ });
63
+ // console.log('rrrrrrrrrrrrrr', result);
64
+ matches = result === true;
65
+ } else {
66
+ // 일반 문자열 비교 (기존 방식)
67
+ matches = String(value) === String(this._value);
68
+ }
69
+
70
+ if (matches) {
71
+ selected = t;
72
+ break;
73
+ }
65
74
  }
66
75
  }
76
+
67
77
  const target = selected || (this.content.querySelector('template[is="swc-otherwise"]') as HTMLTemplateElement);
68
78
 
79
+ // skip-if-same attribute가 있고 이전과 같은 template이면 렌더링 스킵
80
+ // 최상위 skip-if-same 또는 각 when/otherwise의 skip-if-same 체크
81
+ const targetHasSkipIfSame = target?.hasAttribute('skip-if-same');
82
+ const shouldSkip = (this.hasAttribute('skip-if-same') || targetHasSkipIfSame) && this._previousTemplate === target;
83
+ if (shouldSkip) {
84
+ return;
85
+ }
86
+
87
+ this.cleanup();
88
+ this._previousTemplate = target;
89
+
69
90
  if (target) {
70
91
  const clone = target.content.cloneNode(true) as DocumentFragment;
71
92
  this._nodes = Array.from(clone.childNodes);
72
93
  const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
73
94
  const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0] };
74
95
 
96
+ // 복제된 최상위 요소들은 SwcChoose의 직접 자식이 될 것이므로 __swc_host 설정 안 함
75
97
  this._nodes.forEach((n, nodeIdx) => {
76
- if (n instanceof HTMLElement) (n as any).__swc_host = this;
98
+ // if (n instanceof HTMLElement) (n as any).__swc_host = this; // 제거: 부모 체인 유지
77
99
  this._executeCloneCallback('on-clone-node', { ...groupArgs, $node: n, $nodeIndex: nodeIdx, $isElement: n.nodeType === 1 });
100
+
101
+ // Attribute {{ }} 치환 - HTML string 차원에서 동적 치환
102
+ if (n instanceof HTMLElement) {
103
+ Array.from(n.attributes).forEach(attr => {
104
+ const value = attr.value || '';
105
+ if (value.startsWith('{{') && value.endsWith('}}')) {
106
+ const script = value.slice(2, -2).trim();
107
+ const result = FunctionUtils.executeReturn({
108
+ script,
109
+ context: n,
110
+ args: { ...groupArgs, $value: this._value }
111
+ });
112
+ n.setAttribute(attr.name, String(result));
113
+ }
114
+ });
115
+ }
78
116
  });
79
117
 
80
118
  this._executeCloneCallback('on-clone-nodes', groupArgs);
81
119
 
82
- if (portal === this.parentElement) {
83
- portal.insertBefore(clone, this.nextSibling);
84
- } else {
85
- portal.appendChild(clone);
86
- }
120
+ this.parentElement?.insertBefore(clone, this.nextSibling);
121
+ // if (portal === this.parentElement) {
122
+ // portal.insertBefore(clone, this.nextSibling);
123
+ // } else {
124
+ // portal.appendChild(clone);
125
+ // }
87
126
  }
88
127
  }
89
128
 
@@ -4,7 +4,9 @@ import { SwcUtils } from '../utils/Utils';
4
4
  import { FunctionUtils } from '@dooboostore/core';
5
5
  import { changedAttributeHost } from '../decorators/changedAttributeHost';
6
6
 
7
- export const registerAllElements = (w: any) => {
7
+ type Constructor<T> = new (...args: any[]) => T;
8
+
9
+ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElement>> => {
8
10
  const {
9
11
  HTMLElement: _HTMLElement,
10
12
  HTMLTemplateElement: _HTMLTemplateElement,
@@ -374,6 +376,7 @@ export const registerAllElements = (w: any) => {
374
376
  class SwcChoose extends HTMLTemplateElement {
375
377
  private _nodes: Node[] = [];
376
378
  private _value: any = null;
379
+ private _previousTemplate: HTMLTemplateElement | null = null;
377
380
 
378
381
  get value(): any {
379
382
  return this._value;
@@ -383,76 +386,114 @@ export const registerAllElements = (w: any) => {
383
386
  this.render();
384
387
  }
385
388
 
386
- private _resolvePortal(): Element | null {
387
- const portalScript = this.getAttribute('on-get-portal');
388
- if (!portalScript) return this.parentElement;
389
-
390
- const win = this.ownerDocument?.defaultView || window;
391
- const res = FunctionUtils.executeReturn({
392
- script: portalScript,
393
- context: this,
394
- args: SwcUtils.getHelperAndHostSet(win, this)
395
- });
396
-
397
- if (res instanceof HTMLElement) return res;
398
- if (typeof res === 'string') return win.document.querySelector(res);
399
- return this.parentElement;
400
- }
401
-
402
389
  private _executeCloneCallback(attr: string, args: any) {
403
390
  const script = this.getAttribute(attr);
404
391
  if (!script) return;
405
392
  FunctionUtils.execute({ script, context: this, args });
406
393
  }
407
394
 
408
- @changedAttributeHost('on-get-value')
409
- private refresh() {
410
- const script = this.getAttribute('on-get-value');
411
- if (!script) {
395
+ @changedAttributeHost('value')
396
+ private refresh(valueAttr?: string | null) {
397
+ valueAttr ??= this.getAttribute('value');
398
+ if (valueAttr === null) {
412
399
  this.value = null;
413
400
  return;
414
401
  }
415
- const win = this.ownerDocument?.defaultView || window;
416
- const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
417
- this.value = result;
402
+
403
+ // {{ }} 브레이스로 감싸진 값은 스크립트 실행
404
+ if (valueAttr.startsWith('{{') && valueAttr.endsWith('}}')) {
405
+ const script = valueAttr.slice(2, -2).trim();
406
+ const win = this.ownerDocument?.defaultView || window;
407
+ const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
408
+ this.value = result;
409
+ } else {
410
+ // 일반 문자열 값
411
+ this.value = valueAttr;
412
+ }
418
413
  }
419
414
 
420
415
  private render() {
421
- this.cleanup();
422
- const portal = this._resolvePortal();
423
- if (!portal) return;
424
-
425
416
  const win = this.ownerDocument?.defaultView || window;
426
417
  const helperSet = SwcUtils.getHelperAndHostSet(win, this);
427
-
428
418
  const templates = Array.from(this.content.querySelectorAll('template'));
429
419
  let selected: HTMLTemplateElement | null = null;
420
+
430
421
  for (const t of templates) {
431
- if (t.getAttribute('is') === 'swc-when' && String(t.getAttribute('value')) === String(this._value)) {
432
- selected = t;
433
- break;
422
+ if (t.getAttribute('is') === 'swc-when') {
423
+ let matches = false;
424
+ const value = t.getAttribute('value') || '';
425
+
426
+ // {{ }} 브레이스로 감싸진 값은 스크립트 실행
427
+ if (value.startsWith('{{') && value.endsWith('}}')) {
428
+ const script = value.slice(2, -2).trim();
429
+ const result = FunctionUtils.executeReturn({
430
+ script,
431
+ context: t,
432
+ args: { ...helperSet, $value: this._value }
433
+ });
434
+ // console.log('rrrrrrrrrrrrrr', result);
435
+ matches = result === true;
436
+ } else {
437
+ // 일반 문자열 비교 (기존 방식)
438
+ matches = String(value) === String(this._value);
439
+ }
440
+
441
+ if (matches) {
442
+ selected = t;
443
+ break;
444
+ }
434
445
  }
435
446
  }
447
+
436
448
  const target = selected || (this.content.querySelector('template[is="swc-otherwise"]') as HTMLTemplateElement);
437
449
 
450
+ // skip-if-same attribute가 있고 이전과 같은 template이면 렌더링 스킵
451
+ // 최상위 skip-if-same 또는 각 when/otherwise의 skip-if-same 체크
452
+ const targetHasSkipIfSame = target?.hasAttribute('skip-if-same');
453
+ const shouldSkip = (this.hasAttribute('skip-if-same') || targetHasSkipIfSame) && this._previousTemplate === target;
454
+ if (shouldSkip) {
455
+ return;
456
+ }
457
+
458
+ this.cleanup();
459
+ this._previousTemplate = target;
460
+
438
461
  if (target) {
439
462
  const clone = target.content.cloneNode(true) as DocumentFragment;
440
463
  this._nodes = Array.from(clone.childNodes);
441
464
  const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
442
465
  const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0] };
443
466
 
467
+ // 복제된 최상위 요소들은 SwcChoose의 직접 자식이 될 것이므로 __swc_host 설정 안 함
444
468
  this._nodes.forEach((n, nodeIdx) => {
445
- if (n instanceof HTMLElement) (n as any).__swc_host = this;
469
+ // if (n instanceof HTMLElement) (n as any).__swc_host = this; // 제거: 부모 체인 유지
446
470
  this._executeCloneCallback('on-clone-node', { ...groupArgs, $node: n, $nodeIndex: nodeIdx, $isElement: n.nodeType === 1 });
471
+
472
+ // Attribute {{ }} 치환 - HTML string 차원에서 동적 치환
473
+ if (n instanceof HTMLElement) {
474
+ Array.from(n.attributes).forEach(attr => {
475
+ const value = attr.value || '';
476
+ if (value.startsWith('{{') && value.endsWith('}}')) {
477
+ const script = value.slice(2, -2).trim();
478
+ const result = FunctionUtils.executeReturn({
479
+ script,
480
+ context: n,
481
+ args: { ...groupArgs, $value: this._value }
482
+ });
483
+ n.setAttribute(attr.name, String(result));
484
+ }
485
+ });
486
+ }
447
487
  });
448
488
 
449
489
  this._executeCloneCallback('on-clone-nodes', groupArgs);
450
490
 
451
- if (portal === this.parentElement) {
452
- portal.insertBefore(clone, this.nextSibling);
453
- } else {
454
- portal.appendChild(clone);
455
- }
491
+ this.parentElement?.insertBefore(clone, this.nextSibling);
492
+ // if (portal === this.parentElement) {
493
+ // portal.insertBefore(clone, this.nextSibling);
494
+ // } else {
495
+ // portal.appendChild(clone);
496
+ // }
456
497
  }
457
498
  }
458
499
 
@@ -499,7 +540,10 @@ export const registerAllElements = (w: any) => {
499
540
  @changedAttributeHost('on-get-value')
500
541
  private refresh() {
501
542
  const script = this.getAttribute('on-get-value');
502
- if (!script) return;
543
+ if (!script) {
544
+ this.render('default');
545
+ return;
546
+ }
503
547
  const win = this.ownerDocument?.defaultView || window;
504
548
  const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
505
549
 
@@ -553,6 +597,7 @@ export const registerAllElements = (w: any) => {
553
597
  }
554
598
 
555
599
  connectedCallback() {
600
+ console.log('[SwcAsync] connectedCallback called');
556
601
  this.refresh();
557
602
  }
558
603
  disconnectedCallback() {
@@ -732,4 +777,78 @@ export const registerAllElements = (w: any) => {
732
777
  @elementDefine('swc-app-body', { extends: 'body', window: w })
733
778
  class SwcAppBody extends SwcAppMixin(HTMLBodyElement) {}
734
779
 
780
+
781
+ return {
782
+ // Sub Templates
783
+ SwcLoading,
784
+ SwcError,
785
+ SwcSuccess,
786
+ SwcWhen,
787
+ SwcOtherwise,
788
+ SwcDefault,
789
+ // Core Elements
790
+ SwcApp,
791
+ SwcIf,
792
+ SwcLoop,
793
+ SwcChoose,
794
+ SwcAsync,
795
+ // App Elements
796
+ SwcAppAnchor,
797
+ SwcAppArea,
798
+ SwcAppAudio,
799
+ SwcAppBase,
800
+ SwcAppButton,
801
+ SwcAppCanvas,
802
+ SwcAppData,
803
+ SwcAppDataList,
804
+ SwcAppDetails,
805
+ SwcAppDialog,
806
+ SwcAppDiv,
807
+ SwcAppDList,
808
+ SwcAppEmbed,
809
+ SwcAppFieldSet,
810
+ SwcAppForm,
811
+ SwcAppHR,
812
+ SwcAppIFrame,
813
+ SwcAppImage,
814
+ SwcAppInput,
815
+ SwcAppLabel,
816
+ SwcAppLegend,
817
+ SwcAppLI,
818
+ SwcAppLink,
819
+ SwcAppMap,
820
+ SwcAppMeta,
821
+ SwcAppMeter,
822
+ SwcAppMod,
823
+ SwcAppObject,
824
+ SwcAppOList,
825
+ SwcAppOptGroup,
826
+ SwcAppOption,
827
+ SwcAppOutput,
828
+ SwcAppParagraph,
829
+ SwcAppParam,
830
+ SwcAppPicture,
831
+ SwcAppPre,
832
+ SwcAppProgress,
833
+ SwcAppQuote,
834
+ SwcAppScript,
835
+ SwcAppSelect,
836
+ SwcAppSlot,
837
+ SwcAppSource,
838
+ SwcAppSpan,
839
+ SwcAppStyle,
840
+ SwcAppTable,
841
+ SwcAppTableSection,
842
+ SwcAppTableCell,
843
+ SwcAppTemplate,
844
+ SwcAppTextArea,
845
+ SwcAppTime,
846
+ SwcAppTitle,
847
+ SwcAppTableRow,
848
+ SwcAppTrack,
849
+ SwcAppUList,
850
+ SwcAppVideo,
851
+ SwcAppHeading,
852
+ SwcAppBody,
853
+ };
735
854
  };
package/src/index.ts CHANGED
@@ -16,12 +16,14 @@ export * from './config/config';
16
16
 
17
17
  // Registration Factory
18
18
  export default (w: Window, other?: (((w: Window) => ConstructorType<any>) | any) []) => {
19
- registerAllElements(w);
19
+ const cr = registerAllElements(w);
20
20
  const r = other?.map(fn => {
21
21
  if (typeof fn === 'function') {
22
22
  const a = fn(w);
23
23
  } else {
24
24
  }
25
25
  });
26
+
27
+ return cr;
26
28
  // return r??[];
27
29
  };
@@ -255,16 +255,9 @@ export namespace SwcUtils {
255
255
  export const findAllParentAppHostsDirect = (el: HTMLElement): SwcAppInterface[] => {
256
256
  const appHosts: SwcAppInterface[] = [];
257
257
 
258
- // Start from shadow root's host if el is inside shadow DOM, otherwise from parentElement
259
- let current: HTMLElement | null = null;
260
- const rootNode = el.getRootNode();
261
- if (rootNode instanceof ShadowRoot) {
262
- // Element is inside shadow DOM, start from shadow root's host
263
- current = (rootNode as any).host as HTMLElement;
264
- } else {
265
- // Element is in light DOM, start from parent
266
- current = el.parentElement;
267
- }
258
+ // Shadow DOM 내부의 depth를 정상적으로 탐색하기 위해
259
+ // el.parentElement부터 시작하고, shadow root 경계를 만날 때만 host로 이동
260
+ let current: HTMLElement | null = el.parentElement;
268
261
 
269
262
  while (current) {
270
263
  // console.log('vvvvvvv');
@@ -280,7 +273,17 @@ export namespace SwcUtils {
280
273
  if (current.shadowRoot) {
281
274
  current = ((current.shadowRoot as any).host as HTMLElement).parentElement;
282
275
  } else {
283
- current = current.parentElement;
276
+ // 부모가 없으면 shadow root 경계일 수 있음
277
+ if (!current.parentElement) {
278
+ const parentNode = current.parentNode;
279
+ if (parentNode instanceof ShadowRoot) {
280
+ current = (parentNode as any).host as HTMLElement;
281
+ } else {
282
+ break;
283
+ }
284
+ } else {
285
+ current = current.parentElement;
286
+ }
284
287
  }
285
288
  }
286
289
  return appHosts.reverse(); // [root, ..., closest-parent]
@@ -316,6 +319,7 @@ export namespace SwcUtils {
316
319
  }
317
320
 
318
321
  const $appHosts = SwcUtils.findAllParentAppHostsDirect(el); //$hosts.filter(h => h.tagName.toLowerCase() === 'swc-app' || h.getAttribute('is')?.startsWith('swc-app-')) as SwcAppInterface[];
322
+ // console.log('apphosts00000000', $appHosts)
319
323
  const $appHost = $appHosts.length > 0 ? ($appHosts[$appHosts.length - 1] === el ? ($appHosts.length > 1 ? $appHosts[$appHosts.length - 2] : null) : $appHosts[$appHosts.length - 1]) : null;
320
324
  const $firstAppHost = $appHosts.length > 0 ? $appHosts[0] : null;
321
325