@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,8 +1,8 @@
1
- import { elementDefine } from '../decorators/elementDefine';
1
+ import { elementDefine, attributeHost, changedAttributeHost } from '../decorators';
2
2
  import { SwcAppMixin } from './SwcAppMixin';
3
3
  import { SwcUtils } from '../utils/Utils';
4
- import { FunctionUtils } from '@dooboostore/core';
5
- import { changedAttributeHost } from '../decorators/changedAttributeHost';
4
+ import { FunctionUtils,ActionExpression } from '@dooboostore/core';
5
+ import { ConvertUtils } from '@dooboostore/core-web';
6
6
 
7
7
  type Constructor<T> = new (...args: any[]) => T;
8
8
 
@@ -148,99 +148,74 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
148
148
 
149
149
  @elementDefine('swc-if', { extends: 'template', window: w })
150
150
  class SwcIf extends HTMLTemplateElement {
151
- private _value = false;
151
+ @attributeHost('value')
152
+ _value: any;
153
+
154
+ @attributeHost('replace-wrap')
155
+ _replaceWrap: string | null;
156
+
152
157
  private _nodes: Node[] = [];
153
158
 
154
159
  get value(): boolean {
155
160
  return this._value;
156
161
  }
162
+
157
163
  set value(nv: any) {
158
- this._value = Boolean(nv);
159
- this.render();
164
+ // this._value = nv;
165
+ this.refresh(nv);
160
166
  }
161
167
 
162
- private _executeCloneCallback(attr: string, args: any) {
163
- const script = this.getAttribute(attr);
164
- if (!script) return;
165
- FunctionUtils.execute({ script, context: this, args });
168
+ private replaceExpressions(html: string, groupArgs: any, context: any, wrap?: string | null): string {
169
+ const config = wrap ? { wrapExpression: wrap } : undefined;
170
+ const ae = new ActionExpression(html, config);
171
+ const filterOptions = wrap ? { type: 'replace' as const, wrap } : 'replace';
172
+ const win = this.ownerDocument?.defaultView || window;
173
+
174
+ // @ts-ignore
175
+ for (const expr of ae.getExpressions(filterOptions)) {
176
+ try {
177
+ const value = FunctionUtils.executeReturn({
178
+ script: ConvertUtils.decodeHtmlEntity(expr.script, win.document),
179
+ context,
180
+ args: groupArgs
181
+ });
182
+ ae.replace(expr, String(value));
183
+ } catch (e) {
184
+ console.error(`[SWC] Failed to execute ${expr.original}:`, e);
185
+ }
186
+ }
187
+ return ae.getUnprocessedTemplate();
166
188
  }
167
189
 
168
190
  @changedAttributeHost('value')
169
- private refreshValue(valueAttr?: string | null) {
170
- valueAttr ??= this.getAttribute('value');
171
- if (valueAttr === null) {
172
- this.value = false;
173
- return;
174
- }
191
+ private setValue(valueAttr?: string | null) {
192
+ this.refresh();
193
+ }
175
194
 
176
- // {{ }} 브레이스로 감싸진 값은 스크립트 실행
177
- if (valueAttr.startsWith('{{') && valueAttr.endsWith('}}')) {
178
- const script = valueAttr.slice(2, -2).trim();
179
- const win = this.ownerDocument?.defaultView || window;
180
- const result = FunctionUtils.executeReturn({
181
- script,
182
- context: this,
183
- args: SwcUtils.getHelperAndHostSet(win, this)
184
- });
185
- this.value = result;
186
- } else {
187
- // 일반 문자열 값
188
- this.value = Boolean(valueAttr);
189
- }
195
+ @changedAttributeHost('replace-wrap')
196
+ private setReplaceWrap(replaceWrapAttr?: string | null) {
197
+ this.refresh();
190
198
  }
191
199
 
192
- private render() {
200
+ refresh(value = this._value) {
193
201
  this.cleanup();
194
202
  const portal = this.parentElement;
195
- if (!portal) return;
203
+ if (!portal || !value) return;
196
204
 
197
205
  const win = this.ownerDocument?.defaultView || window;
198
206
  const helperSet = SwcUtils.getHelperAndHostSet(win, this);
199
207
 
200
- let targetTpl: HTMLTemplateElement | null = null;
201
- if (this._value) {
202
- targetTpl = this;
203
- } else {
204
- targetTpl = this.content.querySelector('template[is="swc-default"]') as HTMLTemplateElement;
205
- }
208
+ let htmlString = this.innerHTML;
209
+ const groupArgs = { ...helperSet, $value: value };
206
210
 
207
- if (targetTpl) {
208
- const clone = targetTpl.content.cloneNode(true) as DocumentFragment;
209
- // If it's the host template, remove sub-templates from clone
210
- if (targetTpl === this) {
211
- clone.querySelectorAll('template[is^="swc-"]').forEach(t => t.remove());
212
- }
213
-
214
- this._nodes = Array.from(clone.childNodes);
215
- const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
216
- const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0] };
217
-
218
- // 부모 체인을 정상적으로 유지하기 위해 __swc_host 할당 제거
219
- this._nodes.forEach((node, nodeIdx) => {
220
- // if (node instanceof HTMLElement) (node as any).__swc_host = this; // 제거: 부모 체인 유지
221
- this._executeCloneCallback('on-clone-node', { ...groupArgs, $node: node, $nodeIndex: nodeIdx, $isElement: node.nodeType === 1 });
222
-
223
- // Attribute {{ }} 치환 - SwcChoose처럼 동적 치환
224
- if (node instanceof HTMLElement) {
225
- Array.from(node.attributes).forEach(attr => {
226
- const value = attr.value || '';
227
- if (value.startsWith('{{') && value.endsWith('}}')) {
228
- const script = value.slice(2, -2).trim();
229
- const result = FunctionUtils.executeReturn({
230
- script,
231
- context: node,
232
- args: { ...groupArgs, $value: this._value }
233
- });
234
- node.setAttribute(attr.name, String(result));
235
- }
236
- });
237
- }
238
- });
211
+ htmlString = this.replaceExpressions(htmlString, groupArgs, this, this._replaceWrap);
239
212
 
240
- this._executeCloneCallback('on-clone-nodes', groupArgs);
213
+ const result = this.ownerDocument?.createElement('template') || document.createElement('template');
214
+ result.innerHTML = htmlString;
215
+ const processedFragment = result.content;
241
216
 
242
- portal.insertBefore(clone, this.nextSibling);
243
- }
217
+ this._nodes = Array.from(processedFragment.childNodes);
218
+ portal.insertBefore(processedFragment, this.nextSibling);
244
219
  }
245
220
 
246
221
  private cleanup() {
@@ -248,9 +223,6 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
248
223
  this._nodes = [];
249
224
  }
250
225
 
251
- connectedCallback() {
252
- this.refreshValue();
253
- }
254
226
  disconnectedCallback() {
255
227
  this.cleanup();
256
228
  }
@@ -258,57 +230,57 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
258
230
 
259
231
  @elementDefine('swc-loop', { extends: 'template', window: w })
260
232
  class SwcLoop extends HTMLTemplateElement {
261
- private _value: any[] = [];
233
+ @attributeHost('value')
234
+ _value: any[];
235
+
236
+ @attributeHost('replace-wrap')
237
+ _replaceWrap: string | null;
238
+
262
239
  private _nodeGroups: Node[][] = [];
263
240
 
264
241
  get value(): any[] {
265
242
  return this._value;
266
243
  }
267
244
  set value(nv: any[]) {
268
- this._value = Array.isArray(nv) ? [...nv] : [];
269
- this.render();
245
+ // this._value = Array.isArray(nv) ? [...nv] : [];
246
+ this.refresh(nv);
270
247
  }
271
248
 
272
-
273
-
274
- private _executeCloneCallback(attr: string, args: any) {
275
- const script = this.getAttribute(attr);
276
- if (!script) return;
277
- FunctionUtils.execute({ script, context: this, args });
249
+ @changedAttributeHost('value')
250
+ private setValue() {
251
+ this.refresh();
278
252
  }
279
253
 
280
- private _cleanup() {
281
- this._nodeGroups.forEach(nodes => nodes.forEach(n => n.parentElement?.removeChild(n)));
282
- this._nodeGroups = [];
254
+ @changedAttributeHost('replace-wrap')
255
+ private setReplaceWrap() {
256
+ this.refresh();
283
257
  }
284
258
 
285
- @changedAttributeHost('value')
286
- private refreshValue() {
287
- let script = this.getAttribute('value');
288
- if (!script) {
289
- this.value = [];
290
- return;
291
- }
292
- // Parse {{ }} braces
293
- if (script.startsWith('{{') && script.endsWith('}}')) {
294
- script = script.slice(2, -2).trim();
295
- }
259
+ private replaceExpressions(html: string, groupArgs: any, context: any, wrap?: string | null): string {
260
+ const config = wrap ? { wrapExpression: wrap } : undefined;
261
+ const ae = new ActionExpression(html, config);
262
+ const filterOptions = wrap ? { type: 'replace' as const, wrap } : 'replace';
296
263
  const win = this.ownerDocument?.defaultView || window;
297
- const res = FunctionUtils.executeReturn({
298
- script,
299
- context: this,
300
- args: SwcUtils.getHelperAndHostSet(win, this)
301
- });
302
264
 
303
- if (res instanceof Promise) {
304
- res.then(v => (this.value = v)).catch(() => (this.value = []));
305
- } else {
306
- this.value = res;
265
+ // @ts-ignore
266
+ for (const expr of ae.getExpressions(filterOptions)) {
267
+ try {
268
+ const value = FunctionUtils.executeReturn({
269
+ script: ConvertUtils.decodeHtmlEntity(expr.script, win.document),
270
+ context,
271
+ args: groupArgs
272
+ });
273
+ ae.replace(expr, String(value));
274
+ } catch (e) {
275
+ console.error(`[SWC] Failed to execute ${expr.original}:`, e);
276
+ }
307
277
  }
278
+ return ae.getUnprocessedTemplate();
308
279
  }
309
280
 
310
- render() {
311
- this._cleanup();
281
+ refresh(value = this._value) {
282
+ this.cleanup();
283
+ if (value?.length<=0) return;
312
284
  const portal = this.parentElement;
313
285
  if (!portal) return;
314
286
 
@@ -316,135 +288,115 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
316
288
  const helperSet = SwcUtils.getHelperAndHostSet(win, this);
317
289
  const fragment = win.document.createDocumentFragment();
318
290
 
319
- if (this._value.length === 0) {
320
- const defaultTpl = this.content.querySelector('template[is="swc-default"]') as HTMLTemplateElement;
321
- if (defaultTpl) {
322
- const clone = defaultTpl.content.cloneNode(true);
323
- const nodes = Array.from(clone.childNodes);
324
- this._nodeGroups.push(nodes);
325
- fragment.appendChild(clone);
326
- }
327
- } else {
328
- this._value.forEach((item, index) => {
329
- const clone = this.content.cloneNode(true) as DocumentFragment;
330
- // Filter out sub-templates
331
- const subTemplates = clone.querySelectorAll('template[is^="swc-"]');
332
- subTemplates.forEach(t => t.remove());
333
-
334
- const nodes = Array.from(clone.childNodes);
335
- const elements = nodes.filter(n => n.nodeType === 1) as HTMLElement[];
336
-
337
- const groupArgs = { ...helperSet, $item: item, $index: index, $value: this._value, $nodes: nodes, $elements: elements, $firstElement: elements[0] };
338
-
339
- nodes.forEach((node, nodeIdx) => {
340
- this._executeCloneCallback('on-clone-node', { ...groupArgs, $node: node, $nodeIndex: nodeIdx, $isElement: node.nodeType === 1 });
341
-
342
- // Attribute {{ }} 치환 - SwcChoose처럼 동적 치환
343
- if (node instanceof HTMLElement) {
344
- Array.from(node.attributes).forEach(attr => {
345
- const value = attr.value || '';
346
- if (value.startsWith('{{') && value.endsWith('}}')) {
347
- const script = value.slice(2, -2).trim();
348
- const result = FunctionUtils.executeReturn({
349
- script,
350
- context: node,
351
- args: groupArgs
352
- });
353
- node.setAttribute(attr.name, String(result));
354
- }
355
- });
356
- }
357
- });
291
+ value.forEach((item, index) => {
292
+ let htmlString = this.innerHTML;
293
+ const groupArgs = { ...helperSet, $item: item, $index: index, $value: this._value };
358
294
 
359
- this._executeCloneCallback('on-clone-nodes', groupArgs);
360
- this._nodeGroups.push(nodes);
361
- fragment.appendChild(clone);
362
- });
363
- }
295
+ htmlString = this.replaceExpressions(htmlString, groupArgs, this, this._replaceWrap);
364
296
 
365
- if (portal === this.parentElement) {
366
- portal.insertBefore(fragment, this.nextSibling);
367
- } else {
368
- portal.appendChild(fragment);
369
- }
297
+ const temp = this.ownerDocument?.createElement('template') || document.createElement('template');
298
+ temp.innerHTML = htmlString;
299
+
300
+ const content = temp.content;
301
+ const nodes = Array.from(content.childNodes);
302
+ this._nodeGroups.push(nodes);
303
+ fragment.appendChild(content);
304
+ });
305
+
306
+ portal.insertBefore(fragment, this.nextSibling);
370
307
  }
371
308
 
372
- connectedCallback() {
373
- this.refreshValue();
309
+ private cleanup() {
310
+ this._nodeGroups.forEach(nodes => nodes.forEach(n => n.parentElement?.removeChild(n)));
311
+ this._nodeGroups = [];
374
312
  }
313
+
375
314
  disconnectedCallback() {
376
- this._cleanup();
315
+ this.cleanup();
377
316
  }
378
317
  }
379
318
 
380
319
  @elementDefine('swc-choose', { extends: 'template', window: w })
381
320
  class SwcChoose extends HTMLTemplateElement {
321
+ @attributeHost('value')
322
+ _value: any;
323
+
324
+ @attributeHost('replace-wrap')
325
+ _replaceWrap: string | null;
326
+
382
327
  private _nodes: Node[] = [];
383
- private _value: any = null;
384
328
  private _previousTemplate: HTMLTemplateElement | null = null;
385
329
 
386
330
  get value(): any {
387
331
  return this._value;
388
332
  }
333
+
389
334
  set value(nv: any) {
390
- this._value = nv;
391
- this.render();
335
+ this.refresh(nv);
392
336
  }
393
337
 
394
- private _executeCloneCallback(attr: string, args: any) {
395
- const script = this.getAttribute(attr);
396
- if (!script) return;
397
- FunctionUtils.execute({ script, context: this, args });
338
+ @changedAttributeHost('value')
339
+ private setValue() {
340
+ this.refresh();
398
341
  }
399
342
 
400
- @changedAttributeHost('value')
401
- refresh(valueAttr?: string | null) {
402
- valueAttr ??= this.getAttribute('value');
403
- if (valueAttr === null) {
404
- this.value = null;
405
- return;
406
- }
343
+ @changedAttributeHost('replace-wrap')
344
+ private setReplaceWrap(replaceWrapAttr?: string | null) {
345
+ this.refresh();
346
+ }
407
347
 
408
- // {{ }} 브레이스로 감싸진 값은 스크립트 실행
409
- if (valueAttr.startsWith('{{') && valueAttr.endsWith('}}')) {
410
- const script = valueAttr.slice(2, -2).trim();
411
- const win = this.ownerDocument?.defaultView || window;
412
- const helperAndHostSet = SwcUtils.getHelperAndHostSet(win, SwcUtils.findNearestSwcHost(this));
413
- // console.log('vvvvvvvvvvvvvvvvvv323', helperAndHostSet);
414
- const result = FunctionUtils.executeReturn({ script, context: this, args: helperAndHostSet });
415
- this.value = result;
416
- // console.log('----------vv', this.value);
417
- } else {
418
- // 일반 문자열 값
419
- this.value = valueAttr;
348
+ private replaceExpressions(html: string, groupArgs: any, context: any, wrap?: string | null): string {
349
+ const config = wrap ? { wrapExpression: wrap } : undefined;
350
+ const ae = new ActionExpression(html, config);
351
+ const filterOptions = wrap ? { type: 'replace' as const, wrap } : 'replace';
352
+ const win = this.ownerDocument?.defaultView || window;
353
+
354
+ // @ts-ignore
355
+ for (const expr of ae.getExpressions(filterOptions)) {
356
+ try {
357
+ const value = FunctionUtils.executeReturn({
358
+ script: ConvertUtils.decodeHtmlEntity(expr.script, win.document),
359
+ context,
360
+ args: groupArgs
361
+ });
362
+ ae.replace(expr, String(value));
363
+ } catch (e) {
364
+ console.error(`[SWC] Failed to execute ${expr.original}:`, e);
365
+ }
420
366
  }
367
+ return ae.getUnprocessedTemplate();
421
368
  }
422
369
 
423
- private render() {
424
- // console.log('-------------', this.value);
370
+ refresh(value = this._value) {
371
+ const portal = this.parentElement;
372
+ if (!portal) return;
373
+
425
374
  const win = this.ownerDocument?.defaultView || window;
426
375
  const helperSet = SwcUtils.getHelperAndHostSet(win, this);
376
+ const groupArgs = { ...helperSet, $value: value };
377
+
427
378
  const templates = Array.from(this.content.querySelectorAll('template'));
428
379
  let selected: HTMLTemplateElement | null = null;
429
380
 
430
381
  for (const t of templates) {
431
382
  if (t.getAttribute('is') === 'swc-when') {
383
+ const attrVal = t.getAttribute('value') || '';
432
384
  let matches = false;
433
- const value = t.getAttribute('value') || '';
434
385
 
435
- // {{ }} 브레이스로 감싸진 값은 스크립트 실행
436
- if (value.startsWith('{{') && value.endsWith('}}')) {
437
- const script = value.slice(2, -2).trim();
386
+ // Use ActionExpression to parse and extract script from value attribute
387
+ const ae = new ActionExpression(attrVal);
388
+ const expr = ae.getFirstExpression('replace');
389
+
390
+ if (expr) {
391
+ const decodedScript = ConvertUtils.decodeHtmlEntity(expr.script, win.document);
438
392
  const result = FunctionUtils.executeReturn({
439
- script,
393
+ script: decodedScript,
440
394
  context: t,
441
- args: { ...helperSet, $value: this._value }
395
+ args: groupArgs
442
396
  });
443
- // console.log('rrrrrrrrrrrrrr', result);
444
- matches = result === true;
397
+ matches = !!result;
445
398
  } else {
446
- // 일반 문자열 비교 (기존 방식)
447
- matches = String(value) === String(this._value);
399
+ matches = String(attrVal) === String(value);
448
400
  }
449
401
 
450
402
  if (matches) {
@@ -456,11 +408,12 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
456
408
 
457
409
  const target = selected || (this.content.querySelector('template[is="swc-otherwise"]') as HTMLTemplateElement);
458
410
 
459
- // skip-if-same attribute가 있고 이전과 같은 template이면 렌더링 스킵
460
- // 최상위 skip-if-same 또는 각 when/otherwise의 skip-if-same 체크
461
- const targetHasSkipIfSame = target?.hasAttribute('skip-if-same');
462
- const shouldSkip = (this.hasAttribute('skip-if-same') || targetHasSkipIfSame) && this._previousTemplate === target;
463
- if (shouldSkip) {
411
+ const skipValue = target?.getAttribute?.('skip-if-same');
412
+ const skipValue2 = this._previousTemplate?.getAttribute?.('skip-if-same');
413
+ const hasSkipIfSame = target?.hasAttribute?.('skip-if-same');
414
+ const isSame = this._previousTemplate === target;
415
+ // console.log('---rrrr---', this._previousTemplate, target, hasSkipIfSame, isSame, skipValue, skipValue2);
416
+ if (hasSkipIfSame && isSame) {
464
417
  return;
465
418
  }
466
419
 
@@ -468,41 +421,15 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
468
421
  this._previousTemplate = target;
469
422
 
470
423
  if (target) {
471
- const clone = target.content.cloneNode(true) as DocumentFragment;
472
- this._nodes = Array.from(clone.childNodes);
473
- const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
474
- const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0] };
475
-
476
- // 복제된 최상위 요소들은 SwcChoose의 직접 자식이 될 것이므로 __swc_host 설정 안 함
477
- this._nodes.forEach((n, nodeIdx) => {
478
- // if (n instanceof HTMLElement) (n as any).__swc_host = this; // 제거: 부모 체인 유지
479
- this._executeCloneCallback('on-clone-node', { ...groupArgs, $node: n, $nodeIndex: nodeIdx, $isElement: n.nodeType === 1 });
480
-
481
- // Attribute {{ }} 치환 - HTML string 차원에서 동적 치환
482
- if (n instanceof HTMLElement) {
483
- Array.from(n.attributes).forEach(attr => {
484
- const value = attr.value || '';
485
- if (value.startsWith('{{') && value.endsWith('}}')) {
486
- const script = value.slice(2, -2).trim();
487
- const result = FunctionUtils.executeReturn({
488
- script,
489
- context: n,
490
- args: { ...groupArgs, $value: this._value }
491
- });
492
- n.setAttribute(attr.name, String(result));
493
- }
494
- });
495
- }
496
- });
424
+ let htmlString = target.innerHTML;
425
+ htmlString = this.replaceExpressions(htmlString, groupArgs, this, this._replaceWrap);
497
426
 
498
- this._executeCloneCallback('on-clone-nodes', groupArgs);
427
+ const result = this.ownerDocument?.createElement('template') || document.createElement('template');
428
+ result.innerHTML = htmlString;
429
+ const processedFragment = result.content;
499
430
 
500
- this.parentElement?.insertBefore(clone, this.nextSibling);
501
- // if (portal === this.parentElement) {
502
- // portal.insertBefore(clone, this.nextSibling);
503
- // } else {
504
- // portal.appendChild(clone);
505
- // }
431
+ this._nodes = Array.from(processedFragment.childNodes);
432
+ portal.insertBefore(processedFragment, this.nextSibling);
506
433
  }
507
434
  }
508
435
 
@@ -511,9 +438,6 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
511
438
  this._nodes = [];
512
439
  }
513
440
 
514
- connectedCallback() {
515
- this.refresh();
516
- }
517
441
  disconnectedCallback() {
518
442
  this.cleanup();
519
443
  }
@@ -521,56 +445,82 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
521
445
 
522
446
  @elementDefine('swc-async', { extends: 'template', window: w })
523
447
  class SwcAsync extends HTMLTemplateElement {
524
- private _nodes: Node[] = [];
525
- private _value: any = null;
448
+ @attributeHost('value')
449
+ _value: any;
526
450
 
527
- private _executeCloneCallback(attr: string, args: any) {
528
- const script = this.getAttribute(attr);
529
- if (!script) return;
530
- FunctionUtils.execute({ script, context: this, args });
451
+ @attributeHost('replace-wrap')
452
+ _replaceWrap: string | null;
453
+
454
+ resultValue?: any;
455
+
456
+ get value(): boolean {
457
+ return this._value;
458
+ }
459
+
460
+ set value(nv: any) {
461
+ this.executeAsync(nv);
531
462
  }
532
463
 
464
+ private _nodes: Node[] = [];
465
+
533
466
  @changedAttributeHost('value')
534
- private refresh(valueAttr?: string | null) {
535
- valueAttr ??= this.getAttribute('value');
536
- if (!valueAttr) {
537
- this.render('default');
538
- return;
539
- }
467
+ private setValue(valueAttr?: string | null) {
468
+ this.executeAsync(this._value);
469
+ }
540
470
 
541
- // {{ }} 브레이스로 감싸진 값은 스크립트 실행
542
- if (valueAttr.startsWith('{{') && valueAttr.endsWith('}}')) {
543
- const script = valueAttr.slice(2, -2).trim();
544
- const win = this.ownerDocument?.defaultView || window;
545
- const result = FunctionUtils.executeReturn({
546
- script,
547
- context: this,
548
- args: SwcUtils.getHelperAndHostSet(win, this)
549
- });
550
-
551
- this.executeAsync(result);
552
- } else {
553
- // 일반 문자열 값 또는 직접 Promise 속성
554
- this.executeAsync(valueAttr);
555
- }
471
+ @changedAttributeHost('replace-wrap')
472
+ private setReplaceWrap() {
473
+ this.executeAsync(this._value);
556
474
  }
557
475
 
558
- private executeAsync(result: any) {
476
+ private executeAsync(result: any = this._value) {
477
+ if (result === undefined || result === null) {
478
+ this.refresh('default');
479
+ return;
480
+ }
481
+
559
482
  if (result instanceof Promise) {
560
- this.render('loading');
483
+ this.refresh('loading');
561
484
  result
562
485
  .then(v => {
563
- this._value = v;
564
- this.render('success');
486
+ this.resultValue = v;
487
+ this.refresh('success');
565
488
  })
566
- .catch(e => this.render('error'));
489
+ .catch(e => {
490
+ console.error('[SWC-ASYNC] Promise rejected:', e);
491
+ this.resultValue = e;
492
+ this.refresh('error');
493
+ });
567
494
  } else {
568
- this._value = result;
569
- this.render('success');
495
+ this.resultValue = result;
496
+ this.refresh('success');
570
497
  }
571
498
  }
572
499
 
573
- private render(state: string) {
500
+ private replaceExpressions(html: string, groupArgs: any, context: any, wrap?: string | null): string {
501
+ const config = wrap ? { wrapExpression: wrap } : undefined;
502
+ const ae = new ActionExpression(html, config);
503
+ const filterOptions = wrap ? { type: 'replace' as const, wrap } : 'replace';
504
+ const win = this.ownerDocument?.defaultView || window;
505
+
506
+ // @ts-ignore
507
+ for (const expr of ae.getExpressions(filterOptions)) {
508
+ try {
509
+ const value = FunctionUtils.executeReturn({
510
+ script: ConvertUtils.decodeHtmlEntity(expr.script, win.document),
511
+ context,
512
+ args: groupArgs
513
+ });
514
+ ae.replace(expr, String(value));
515
+ } catch (e) {
516
+ console.error(`[SWC] Failed to execute ${expr.original}:`, e);
517
+ }
518
+ }
519
+ return ae.getUnprocessedTemplate();
520
+ }
521
+
522
+ refresh(state: string = 'default') {
523
+ console.log('rrrrrrrrrrrrrrrrr', state);
574
524
  this.cleanup();
575
525
  const portal = this.parentElement;
576
526
  if (!portal) return;
@@ -580,36 +530,17 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
580
530
 
581
531
  const t = this.content.querySelector(`template[is="swc-${state}"]`) as HTMLTemplateElement;
582
532
  if (t) {
583
- const clone = t.content.cloneNode(true) as DocumentFragment;
584
- this._nodes = Array.from(clone.childNodes);
585
- const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
586
- const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0], $state: state };
587
-
588
- // 부모 체인을 정상적으로 유지하기 위해 __swc_host 할당 제거
589
- this._nodes.forEach((n, nodeIdx) => {
590
- // if (n instanceof HTMLElement) (n as any).__swc_host = this; // 제거: 부모 체인 유지
591
- this._executeCloneCallback('on-clone-node', { ...groupArgs, $node: n, $nodeIndex: nodeIdx, $isElement: n.nodeType === 1 });
592
-
593
- // Attribute {{ }} 치환 - SwcChoose처럼 동적 치환
594
- if (n instanceof HTMLElement) {
595
- Array.from(n.attributes).forEach(attr => {
596
- const value = attr.value || '';
597
- if (value.startsWith('{{') && value.endsWith('}}')) {
598
- const script = value.slice(2, -2).trim();
599
- const result = FunctionUtils.executeReturn({
600
- script,
601
- context: n,
602
- args: groupArgs
603
- });
604
- n.setAttribute(attr.name, String(result));
605
- }
606
- });
607
- }
608
- });
533
+ let htmlString = t.innerHTML;
534
+ const groupArgs = { ...helperSet, $value: this._value, $state: state };
609
535
 
610
- this._executeCloneCallback('on-clone-nodes', groupArgs);
536
+ htmlString = this.replaceExpressions(htmlString, groupArgs, this, this._replaceWrap);
611
537
 
612
- portal.insertBefore(clone, this.nextSibling);
538
+ const temp = this.ownerDocument?.createElement('template') || document.createElement('template');
539
+ temp.innerHTML = htmlString;
540
+ const content = temp.content;
541
+
542
+ this._nodes = Array.from(content.childNodes);
543
+ portal.insertBefore(content, this.nextSibling);
613
544
  }
614
545
  }
615
546
 
@@ -618,9 +549,6 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
618
549
  this._nodes = [];
619
550
  }
620
551
 
621
- connectedCallback() {
622
- this.refresh();
623
- }
624
552
  disconnectedCallback() {
625
553
  this.cleanup();
626
554
  }