@dooboostore/simple-web-component 1.0.31 → 1.0.33

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 (100) hide show
  1. package/README.md +280 -43
  2. package/dist/cjs/decorators/applyStyle.js +1 -1
  3. package/dist/cjs/decorators/applyStyle.js.map +2 -2
  4. package/dist/cjs/decorators/attribute.js +17 -1
  5. package/dist/cjs/decorators/attribute.js.map +2 -2
  6. package/dist/cjs/decorators/elementDefine.js +27 -10
  7. package/dist/cjs/decorators/elementDefine.js.map +3 -3
  8. package/dist/cjs/decorators/index.js +3 -1
  9. package/dist/cjs/decorators/index.js.map +2 -2
  10. package/dist/cjs/decorators/publishSwcAppMessage.js +86 -0
  11. package/dist/cjs/decorators/publishSwcAppMessage.js.map +7 -0
  12. package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js +62 -0
  13. package/dist/cjs/decorators/subscribeSwcAppMessageWhileConnected.js.map +7 -0
  14. package/dist/cjs/decorators/{subscribeSwcAppRouteChange.js → subscribeSwcAppRouteChangeWhileConnected.js} +16 -16
  15. package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +7 -0
  16. package/dist/cjs/elements/SwcAppMixin.js +32 -3
  17. package/dist/cjs/elements/SwcAppMixin.js.map +2 -2
  18. package/dist/cjs/elements/SwcAsync.js +69 -57
  19. package/dist/cjs/elements/SwcAsync.js.map +2 -2
  20. package/dist/cjs/elements/SwcChoose.js +66 -60
  21. package/dist/cjs/elements/SwcChoose.js.map +2 -2
  22. package/dist/cjs/elements/SwcIf.js +51 -66
  23. package/dist/cjs/elements/SwcIf.js.map +2 -2
  24. package/dist/cjs/elements/SwcLoop.js +58 -78
  25. package/dist/cjs/elements/SwcLoop.js.map +2 -2
  26. package/dist/cjs/elements/register.js +310 -335
  27. package/dist/cjs/elements/register.js.map +2 -2
  28. package/dist/cjs/types.js.map +1 -1
  29. package/dist/cjs/utils/Utils.js +7 -13
  30. package/dist/cjs/utils/Utils.js.map +2 -2
  31. package/dist/esm/decorators/applyStyle.js +1 -1
  32. package/dist/esm/decorators/applyStyle.js.map +2 -2
  33. package/dist/esm/decorators/attribute.js +18 -2
  34. package/dist/esm/decorators/attribute.js.map +2 -2
  35. package/dist/esm/decorators/elementDefine.js +25 -8
  36. package/dist/esm/decorators/elementDefine.js.map +3 -3
  37. package/dist/esm/decorators/index.js +3 -1
  38. package/dist/esm/decorators/index.js.map +2 -2
  39. package/dist/esm/decorators/publishSwcAppMessage.js +67 -0
  40. package/dist/esm/decorators/publishSwcAppMessage.js.map +7 -0
  41. package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js +43 -0
  42. package/dist/esm/decorators/subscribeSwcAppMessageWhileConnected.js.map +7 -0
  43. package/dist/esm/decorators/{subscribeSwcAppRouteChange.js → subscribeSwcAppRouteChangeWhileConnected.js} +13 -13
  44. package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +7 -0
  45. package/dist/esm/elements/SwcAppMixin.js +32 -3
  46. package/dist/esm/elements/SwcAppMixin.js.map +2 -2
  47. package/dist/esm/elements/SwcAsync.js +70 -58
  48. package/dist/esm/elements/SwcAsync.js.map +2 -2
  49. package/dist/esm/elements/SwcChoose.js +67 -61
  50. package/dist/esm/elements/SwcChoose.js.map +2 -2
  51. package/dist/esm/elements/SwcIf.js +52 -67
  52. package/dist/esm/elements/SwcIf.js.map +2 -2
  53. package/dist/esm/elements/SwcLoop.js +59 -79
  54. package/dist/esm/elements/SwcLoop.js.map +2 -2
  55. package/dist/esm/elements/register.js +239 -264
  56. package/dist/esm/elements/register.js.map +2 -2
  57. package/dist/esm/types.js.map +1 -1
  58. package/dist/esm/utils/Utils.js +7 -13
  59. package/dist/esm/utils/Utils.js.map +2 -2
  60. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +2804 -2498
  61. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  62. package/dist/types/decorators/attribute.d.ts.map +1 -1
  63. package/dist/types/decorators/elementDefine.d.ts +1 -0
  64. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  65. package/dist/types/decorators/index.d.ts +3 -1
  66. package/dist/types/decorators/index.d.ts.map +1 -1
  67. package/dist/types/decorators/publishSwcAppMessage.d.ts +13 -0
  68. package/dist/types/decorators/publishSwcAppMessage.d.ts.map +1 -0
  69. package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts +16 -0
  70. package/dist/types/decorators/subscribeSwcAppMessageWhileConnected.d.ts.map +1 -0
  71. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts +18 -0
  72. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts.map +1 -0
  73. package/dist/types/elements/SwcAppMixin.d.ts +3 -0
  74. package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
  75. package/dist/types/elements/register.d.ts.map +1 -1
  76. package/dist/types/types.d.ts +14 -1
  77. package/dist/types/types.d.ts.map +1 -1
  78. package/dist/types/utils/Utils.d.ts.map +1 -1
  79. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +2804 -2498
  80. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  81. package/package.json +4 -4
  82. package/src/decorators/applyStyle.ts +1 -1
  83. package/src/decorators/attribute.ts +35 -30
  84. package/src/decorators/elementDefine.ts +28 -8
  85. package/src/decorators/index.ts +3 -1
  86. package/src/decorators/publishSwcAppMessage.ts +100 -0
  87. package/src/decorators/subscribeSwcAppMessageWhileConnected.ts +73 -0
  88. package/src/decorators/{subscribeSwcAppRouteChange.ts → subscribeSwcAppRouteChangeWhileConnected.ts} +19 -19
  89. package/src/elements/SwcAppMixin.ts +43 -4
  90. package/src/elements/SwcAsync.ts +75 -69
  91. package/src/elements/SwcChoose.ts +67 -80
  92. package/src/elements/SwcIf.ts +51 -76
  93. package/src/elements/SwcLoop.ts +56 -87
  94. package/src/elements/register.ts +235 -307
  95. package/src/types.ts +14 -1
  96. package/src/utils/Utils.ts +11 -17
  97. package/dist/cjs/decorators/subscribeSwcAppRouteChange.js.map +0 -7
  98. package/dist/esm/decorators/subscribeSwcAppRouteChange.js.map +0 -7
  99. package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts +0 -18
  100. package/dist/types/decorators/subscribeSwcAppRouteChange.d.ts.map +0 -1
@@ -1,58 +1,86 @@
1
1
  import { changedAttributeHost } from '../decorators/changedAttributeHost';
2
2
  import { SwcUtils } from '../utils/Utils';
3
- import { FunctionUtils } from '@dooboostore/core';
3
+ import { FunctionUtils, ActionExpression } from '@dooboostore/core';
4
+ import { ConvertUtils } from '@dooboostore/core-web';
5
+ import { attributeHost } from '../decorators';
4
6
 
5
7
  class SwcAsync extends HTMLTemplateElement {
6
- private _nodes: Node[] = [];
7
- private _value: any = null;
8
+ @attributeHost('value')
9
+ _value: any;
10
+
11
+ @attributeHost('replace-wrap')
12
+ _replaceWrap: string | null;
13
+
14
+ resultValue?: any;
8
15
 
9
- private _executeCloneCallback(attr: string, args: any) {
10
- const script = this.getAttribute(attr);
11
- if (!script) return;
12
- FunctionUtils.execute({ script, context: this, args });
16
+ get value(): boolean {
17
+ return this._value;
13
18
  }
14
19
 
20
+ set value(nv: any) {
21
+ this.executeAsync(nv);
22
+ }
23
+
24
+ private _nodes: Node[] = [];
25
+
15
26
  @changedAttributeHost('value')
16
- private refresh(valueAttr?: string | null) {
17
- valueAttr ??= this.getAttribute('value');
18
- if (!valueAttr) {
19
- this.render('default');
20
- return;
21
- }
27
+ private setValue(valueAttr?: string | null) {
28
+ this.executeAsync(this._value);
29
+ }
22
30
 
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
- }
31
+ @changedAttributeHost('replace-wrap')
32
+ private setReplaceWrap() {
33
+ this.executeAsync(this._value);
38
34
  }
39
35
 
40
- private executeAsync(result: any) {
36
+ private executeAsync(result: any = this._value) {
37
+ if (result === undefined || result === null) {
38
+ this.refresh('default');
39
+ return;
40
+ }
41
+
41
42
  if (result instanceof Promise) {
42
- this.render('loading');
43
+ this.refresh('loading');
43
44
  result
44
45
  .then(v => {
45
- this._value = v;
46
- this.render('success');
46
+ this.resultValue = v;
47
+ this.refresh('success');
47
48
  })
48
- .catch(e => this.render('error'));
49
+ .catch(e => {
50
+ console.error('[SWC-ASYNC] Promise rejected:', e);
51
+ this.resultValue = e;
52
+ this.refresh('error');
53
+ });
49
54
  } else {
50
- this._value = result;
51
- this.render('success');
55
+ this.resultValue = result;
56
+ this.refresh('success');
57
+ }
58
+ }
59
+
60
+ private replaceExpressions(html: string, groupArgs: any, context: any, wrap?: string | null): string {
61
+ const config = wrap ? { wrapExpression: wrap } : undefined;
62
+ const ae = new ActionExpression(html, config);
63
+ const filterOptions = wrap ? { type: 'replace' as const, wrap } : 'replace';
64
+ const win = this.ownerDocument?.defaultView || window;
65
+
66
+ // @ts-ignore
67
+ for (const expr of ae.getExpressions(filterOptions)) {
68
+ try {
69
+ const value = FunctionUtils.executeReturn({
70
+ script: ConvertUtils.decodeHtmlEntity(expr.script, win.document),
71
+ context,
72
+ args: groupArgs
73
+ });
74
+ ae.replace(expr, String(value));
75
+ } catch (e) {
76
+ console.error(`[SWC] Failed to execute ${expr.original}:`, e);
77
+ }
52
78
  }
79
+ return ae.getUnprocessedTemplate();
53
80
  }
54
81
 
55
- private render(state: string) {
82
+ refresh(state: string = 'default') {
83
+ console.log('rrrrrrrrrrrrrrrrr', state);
56
84
  this.cleanup();
57
85
  const portal = this.parentElement;
58
86
  if (!portal) return;
@@ -62,36 +90,17 @@ class SwcAsync extends HTMLTemplateElement {
62
90
 
63
91
  const t = this.content.querySelector(`template[is="swc-${state}"]`) as HTMLTemplateElement;
64
92
  if (t) {
65
- const clone = t.content.cloneNode(true) as DocumentFragment;
66
- this._nodes = Array.from(clone.childNodes);
67
- const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
68
- const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0], $state: state };
69
-
70
- // 부모 체인을 정상적으로 유지하기 위해 __swc_host 할당 제거
71
- this._nodes.forEach((n, nodeIdx) => {
72
- // if (n instanceof HTMLElement) (n as any).__swc_host = this; // 제거: 부모 체인 유지
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
- }
90
- });
91
-
92
- this._executeCloneCallback('on-clone-nodes', groupArgs);
93
-
94
- portal.insertBefore(clone, this.nextSibling);
93
+ let htmlString = t.innerHTML;
94
+ const groupArgs = { ...helperSet, $value: this._value, $state: state };
95
+
96
+ htmlString = this.replaceExpressions(htmlString, groupArgs, this, this._replaceWrap);
97
+
98
+ const temp = this.ownerDocument?.createElement('template') || document.createElement('template');
99
+ temp.innerHTML = htmlString;
100
+ const content = temp.content;
101
+
102
+ this._nodes = Array.from(content.childNodes);
103
+ portal.insertBefore(content, this.nextSibling);
95
104
  }
96
105
  }
97
106
 
@@ -100,9 +109,6 @@ class SwcAsync extends HTMLTemplateElement {
100
109
  this._nodes = [];
101
110
  }
102
111
 
103
- connectedCallback() {
104
- this.refresh();
105
- }
106
112
  disconnectedCallback() {
107
113
  this.cleanup();
108
114
  }
@@ -1,75 +1,90 @@
1
1
  import { changedAttributeHost } from '../decorators/changedAttributeHost';
2
2
  import { SwcUtils } from '../utils/Utils';
3
- import { FunctionUtils } from '@dooboostore/core';
3
+ import { FunctionUtils, ActionExpression } from '@dooboostore/core';
4
+ import { ConvertUtils } from '@dooboostore/core-web';
4
5
  import { SwcChooseInterface } from '../types';
6
+ import { attributeHost } from '../decorators';
5
7
 
6
8
  class SwcChoose extends HTMLTemplateElement implements SwcChooseInterface {
9
+ @attributeHost('value')
10
+ _value: any;
11
+
12
+ @attributeHost('replace-wrap')
13
+ _replaceWrap: string | null;
14
+
7
15
  private _nodes: Node[] = [];
8
- private _value: any = null;
9
16
  private _previousTemplate: HTMLTemplateElement | null = null;
10
17
 
11
18
  get value(): any {
12
19
  return this._value;
13
20
  }
21
+
14
22
  set value(nv: any) {
15
- this._value = nv;
16
- this.render();
23
+ this.refresh(nv);
17
24
  }
18
25
 
19
- private _executeCloneCallback(attr: string, args: any) {
20
- const script = this.getAttribute(attr);
21
- if (!script) return;
22
- FunctionUtils.execute({ script, context: this, args });
26
+ @changedAttributeHost('value')
27
+ private setValue() {
28
+ this.refresh();
23
29
  }
24
30
 
25
- @changedAttributeHost('value')
26
- refresh(valueAttr?: string | null) {
27
- valueAttr ??= this.getAttribute('value');
28
- if (valueAttr === null) {
29
- this.value = null;
30
- return;
31
- }
31
+ @changedAttributeHost('replace-wrap')
32
+ private setReplaceWrap(replaceWrapAttr?: string | null) {
33
+ this.refresh();
34
+ }
32
35
 
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;
36
+ private replaceExpressions(html: string, groupArgs: any, context: any, wrap?: string | null): string {
37
+ const config = wrap ? { wrapExpression: wrap } : undefined;
38
+ const ae = new ActionExpression(html, config);
39
+ const filterOptions = wrap ? { type: 'replace' as const, wrap } : 'replace';
40
+ const win = this.ownerDocument?.defaultView || window;
41
+
42
+ // @ts-ignore
43
+ for (const expr of ae.getExpressions(filterOptions)) {
44
+ try {
45
+ const value = FunctionUtils.executeReturn({
46
+ script: ConvertUtils.decodeHtmlEntity(expr.script, win.document),
47
+ context,
48
+ args: groupArgs
49
+ });
50
+ ae.replace(expr, String(value));
51
+ } catch (e) {
52
+ console.error(`[SWC] Failed to execute ${expr.original}:`, e);
53
+ }
45
54
  }
55
+ return ae.getUnprocessedTemplate();
46
56
  }
47
57
 
48
- private render() {
49
- // console.log('-------------', this.value);
58
+ refresh(value = this._value) {
59
+ const portal = this.parentElement;
60
+ if (!portal) return;
61
+
50
62
  const win = this.ownerDocument?.defaultView || window;
51
63
  const helperSet = SwcUtils.getHelperAndHostSet(win, this);
64
+ const groupArgs = { ...helperSet, $value: value };
65
+
52
66
  const templates = Array.from(this.content.querySelectorAll('template'));
53
67
  let selected: HTMLTemplateElement | null = null;
54
68
 
55
69
  for (const t of templates) {
56
70
  if (t.getAttribute('is') === 'swc-when') {
71
+ const attrVal = t.getAttribute('value') || '';
57
72
  let matches = false;
58
- const value = t.getAttribute('value') || '';
59
73
 
60
- // {{ }} 브레이스로 감싸진 값은 스크립트 실행
61
- if (value.startsWith('{{') && value.endsWith('}}')) {
62
- const script = value.slice(2, -2).trim();
74
+ // Use ActionExpression to parse and extract script from value attribute
75
+ const ae = new ActionExpression(attrVal);
76
+ const expr = ae.getFirstExpression('replace');
77
+
78
+ if (expr) {
79
+ const decodedScript = ConvertUtils.decodeHtmlEntity(expr.script, win.document);
63
80
  const result = FunctionUtils.executeReturn({
64
- script,
81
+ script: decodedScript,
65
82
  context: t,
66
- args: { ...helperSet, $value: this._value }
83
+ args: groupArgs
67
84
  });
68
- // console.log('rrrrrrrrrrrrrr', result);
69
- matches = result === true;
85
+ matches = !!result;
70
86
  } else {
71
- // 일반 문자열 비교 (기존 방식)
72
- matches = String(value) === String(this._value);
87
+ matches = String(attrVal) === String(value);
73
88
  }
74
89
 
75
90
  if (matches) {
@@ -81,11 +96,12 @@ class SwcChoose extends HTMLTemplateElement implements SwcChooseInterface {
81
96
 
82
97
  const target = selected || (this.content.querySelector('template[is="swc-otherwise"]') as HTMLTemplateElement);
83
98
 
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) {
99
+ const skipValue = target?.getAttribute?.('skip-if-same');
100
+ const skipValue2 = this._previousTemplate?.getAttribute?.('skip-if-same');
101
+ const hasSkipIfSame = target?.hasAttribute?.('skip-if-same');
102
+ const isSame = this._previousTemplate === target;
103
+ // console.log('---rrrr---', this._previousTemplate, target, hasSkipIfSame, isSame, skipValue, skipValue2);
104
+ if (hasSkipIfSame && isSame) {
89
105
  return;
90
106
  }
91
107
 
@@ -93,41 +109,15 @@ class SwcChoose extends HTMLTemplateElement implements SwcChooseInterface {
93
109
  this._previousTemplate = target;
94
110
 
95
111
  if (target) {
96
- const clone = target.content.cloneNode(true) as DocumentFragment;
97
- this._nodes = Array.from(clone.childNodes);
98
- const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
99
- const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0] };
100
-
101
- // 복제된 최상위 요소들은 SwcChoose의 직접 자식이 될 것이므로 __swc_host 설정 안 함
102
- this._nodes.forEach((n, nodeIdx) => {
103
- // if (n instanceof HTMLElement) (n as any).__swc_host = this; // 제거: 부모 체인 유지
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
- }
121
- });
112
+ let htmlString = target.innerHTML;
113
+ htmlString = this.replaceExpressions(htmlString, groupArgs, this, this._replaceWrap);
122
114
 
123
- this._executeCloneCallback('on-clone-nodes', groupArgs);
115
+ const result = this.ownerDocument?.createElement('template') || document.createElement('template');
116
+ result.innerHTML = htmlString;
117
+ const processedFragment = result.content;
124
118
 
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
- // }
119
+ this._nodes = Array.from(processedFragment.childNodes);
120
+ portal.insertBefore(processedFragment, this.nextSibling);
131
121
  }
132
122
  }
133
123
 
@@ -136,9 +126,6 @@ class SwcChoose extends HTMLTemplateElement implements SwcChooseInterface {
136
126
  this._nodes = [];
137
127
  }
138
128
 
139
- connectedCallback() {
140
- this.refresh();
141
- }
142
129
  disconnectedCallback() {
143
130
  this.cleanup();
144
131
  }
@@ -1,101 +1,79 @@
1
1
  import { changedAttributeHost } from '../decorators/changedAttributeHost';
2
2
  import { SwcUtils } from '../utils/Utils';
3
- import { FunctionUtils } from '@dooboostore/core';
3
+ import { FunctionUtils, ActionExpression } from '@dooboostore/core';
4
+ import { ConvertUtils } from '@dooboostore/core-web';
5
+ import { SwcIfInterface } from '../types';
6
+ import { attributeHost } from '../decorators';
7
+
8
+ class SwcIf extends HTMLTemplateElement implements SwcIfInterface {
9
+ @attributeHost('value')
10
+ _value: any;
11
+
12
+ @attributeHost('replace-wrap')
13
+ _replaceWrap: string | null;
4
14
 
5
- class SwcIf extends HTMLTemplateElement {
6
- private _value = false;
7
15
  private _nodes: Node[] = [];
8
16
 
9
17
  get value(): boolean {
10
18
  return this._value;
11
19
  }
20
+
12
21
  set value(nv: any) {
13
- this._value = Boolean(nv);
14
- this.render();
22
+ // this._value = nv;
23
+ this.refresh(nv);
15
24
  }
16
25
 
17
- private _executeCloneCallback(attr: string, args: any) {
18
- const script = this.getAttribute(attr);
19
- if (!script) return;
20
- FunctionUtils.execute({ script, context: this, args });
26
+ private replaceExpressions(html: string, groupArgs: any, context: any, wrap?: string | null): string {
27
+ const config = wrap ? { wrapExpression: wrap } : undefined;
28
+ const ae = new ActionExpression(html, config);
29
+ const filterOptions = wrap ? { type: 'replace' as const, wrap } : 'replace';
30
+ const win = this.ownerDocument?.defaultView || window;
31
+
32
+ // @ts-ignore
33
+ for (const expr of ae.getExpressions(filterOptions)) {
34
+ try {
35
+ const value = FunctionUtils.executeReturn({
36
+ script: ConvertUtils.decodeHtmlEntity(expr.script, win.document),
37
+ context,
38
+ args: groupArgs
39
+ });
40
+ ae.replace(expr, String(value));
41
+ } catch (e) {
42
+ console.error(`[SWC] Failed to execute ${expr.original}:`, e);
43
+ }
44
+ }
45
+ return ae.getUnprocessedTemplate();
21
46
  }
22
47
 
23
48
  @changedAttributeHost('value')
24
- private refreshValue(valueAttr?: string | null) {
25
- valueAttr ??= this.getAttribute('value');
26
- if (valueAttr === null) {
27
- this.value = false;
28
- return;
29
- }
49
+ private setValue(valueAttr?: string | null) {
50
+ this.refresh();
51
+ }
30
52
 
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;
41
- } else {
42
- // 일반 문자열 값
43
- this.value = Boolean(valueAttr);
44
- }
53
+ @changedAttributeHost('replace-wrap')
54
+ private setReplaceWrap(replaceWrapAttr?: string | null) {
55
+ this.refresh();
45
56
  }
46
57
 
47
- private render() {
58
+ refresh(value = this._value) {
48
59
  this.cleanup();
49
60
  const portal = this.parentElement;
50
- if (!portal) return;
61
+ if (!portal || !value) return;
51
62
 
52
63
  const win = this.ownerDocument?.defaultView || window;
53
64
  const helperSet = SwcUtils.getHelperAndHostSet(win, this);
54
65
 
55
- let targetTpl: HTMLTemplateElement | null = null;
56
- if (this._value) {
57
- targetTpl = this;
58
- } else {
59
- targetTpl = this.content.querySelector('template[is="swc-default"]') as HTMLTemplateElement;
60
- }
66
+ let htmlString = this.innerHTML;
67
+ const groupArgs = { ...helperSet, $value: value };
61
68
 
62
- if (targetTpl) {
63
- const clone = targetTpl.content.cloneNode(true) as DocumentFragment;
64
- // If it's the host template, remove sub-templates from clone
65
- if (targetTpl === this) {
66
- clone.querySelectorAll('template[is^="swc-"]').forEach(t => t.remove());
67
- }
69
+ htmlString = this.replaceExpressions(htmlString, groupArgs, this, this._replaceWrap);
68
70
 
69
- this._nodes = Array.from(clone.childNodes);
70
- const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
71
- const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0] };
72
-
73
- // 부모 체인을 정상적으로 유지하기 위해 __swc_host 할당 제거
74
- this._nodes.forEach((node, nodeIdx) => {
75
- // if (node instanceof HTMLElement) (node as any).__swc_host = this; // 제거: 부모 체인 유지
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
- }
93
- });
94
-
95
- this._executeCloneCallback('on-clone-nodes', groupArgs);
96
-
97
- portal.insertBefore(clone, this.nextSibling);
98
- }
71
+ const result = this.ownerDocument?.createElement('template') || document.createElement('template');
72
+ result.innerHTML = htmlString;
73
+ const processedFragment = result.content;
74
+
75
+ this._nodes = Array.from(processedFragment.childNodes);
76
+ portal.insertBefore(processedFragment, this.nextSibling);
99
77
  }
100
78
 
101
79
  private cleanup() {
@@ -103,9 +81,6 @@ class SwcIf extends HTMLTemplateElement {
103
81
  this._nodes = [];
104
82
  }
105
83
 
106
- connectedCallback() {
107
- this.refreshValue();
108
- }
109
84
  disconnectedCallback() {
110
85
  this.cleanup();
111
86
  }