@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
@@ -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 SwcLoop extends HTMLTemplateElement {
5
+ class SwcLoop extends HTMLTemplateElement {
6
6
  private _value: any[] = [];
7
7
  private _nodeGroups: Node[][] = [];
8
8
 
@@ -14,21 +14,7 @@ export class SwcLoop 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
17
 
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
18
 
33
19
  private _executeCloneCallback(attr: string, args: any) {
34
20
  const script = this.getAttribute(attr);
@@ -41,13 +27,17 @@ export class SwcLoop extends HTMLTemplateElement {
41
27
  this._nodeGroups = [];
42
28
  }
43
29
 
44
- @changedAttributeHost('on-get-value')
30
+ @changedAttributeHost('value')
45
31
  private refreshValue() {
46
- const script = this.getAttribute('on-get-value');
32
+ let script = this.getAttribute('value');
47
33
  if (!script) {
48
34
  this.value = [];
49
35
  return;
50
36
  }
37
+ // Parse {{ }} braces
38
+ if (script.startsWith('{{') && script.endsWith('}}')) {
39
+ script = script.slice(2, -2).trim();
40
+ }
51
41
  const win = this.ownerDocument?.defaultView || window;
52
42
  const res = FunctionUtils.executeReturn({
53
43
  script,
@@ -64,7 +54,7 @@ export class SwcLoop extends HTMLTemplateElement {
64
54
 
65
55
  render() {
66
56
  this._cleanup();
67
- const portal = this._resolvePortal();
57
+ const portal = this.parentElement;
68
58
  if (!portal) return;
69
59
 
70
60
  const win = this.ownerDocument?.defaultView || window;
@@ -92,8 +82,23 @@ export class SwcLoop extends HTMLTemplateElement {
92
82
  const groupArgs = { ...helperSet, $item: item, $index: index, $value: this._value, $nodes: nodes, $elements: elements, $firstElement: elements[0] };
93
83
 
94
84
  nodes.forEach((node, nodeIdx) => {
95
- if (node instanceof HTMLElement) (node as any).__swc_host = this;
96
85
  this._executeCloneCallback('on-clone-node', { ...groupArgs, $node: node, $nodeIndex: nodeIdx, $isElement: node.nodeType === 1 });
86
+
87
+ // Attribute {{ }} 치환 - SwcChoose처럼 동적 치환
88
+ if (node instanceof HTMLElement) {
89
+ Array.from(node.attributes).forEach(attr => {
90
+ const value = attr.value || '';
91
+ if (value.startsWith('{{') && value.endsWith('}}')) {
92
+ const script = value.slice(2, -2).trim();
93
+ const result = FunctionUtils.executeReturn({
94
+ script,
95
+ context: node,
96
+ args: groupArgs
97
+ });
98
+ node.setAttribute(attr.name, String(result));
99
+ }
100
+ });
101
+ }
97
102
  });
98
103
 
99
104
  this._executeCloneCallback('on-clone-nodes', groupArgs);
@@ -1,6 +1,6 @@
1
- export class SwcLoading extends HTMLTemplateElement {}
2
- export class SwcError extends HTMLTemplateElement {}
3
- export class SwcSuccess extends HTMLTemplateElement {}
4
- export class SwcWhen extends HTMLTemplateElement {}
5
- export class SwcOtherwise extends HTMLTemplateElement {}
6
- export class SwcDefault extends HTMLTemplateElement {}
1
+ class SwcLoading extends HTMLTemplateElement {}
2
+ class SwcError extends HTMLTemplateElement {}
3
+ class SwcSuccess extends HTMLTemplateElement {}
4
+ class SwcWhen extends HTMLTemplateElement {}
5
+ class SwcOtherwise extends HTMLTemplateElement {}
6
+ class SwcDefault extends HTMLTemplateElement {}
@@ -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,
@@ -157,52 +159,39 @@ export const registerAllElements = (w: any) => {
157
159
  this.render();
158
160
  }
159
161
 
160
- private _resolvePortal(): Element | null {
161
- const portalScript = this.getAttribute('on-get-portal');
162
- if (!portalScript) return this.parentElement;
163
-
164
- const win = this.ownerDocument?.defaultView || window;
165
- const res = FunctionUtils.executeReturn({
166
- script: portalScript,
167
- context: this,
168
- args: SwcUtils.getHelperAndHostSet(win, this)
169
- });
170
-
171
- if (res instanceof HTMLElement) return res;
172
- if (typeof res === 'string') return win.document.querySelector(res);
173
- return this.parentElement;
174
- }
175
-
176
162
  private _executeCloneCallback(attr: string, args: any) {
177
163
  const script = this.getAttribute(attr);
178
164
  if (!script) return;
179
165
  FunctionUtils.execute({ script, context: this, args });
180
166
  }
181
167
 
182
- @changedAttributeHost('on-get-value')
183
- private refreshValue() {
184
- const script = this.getAttribute('on-get-value');
185
- if (!script) {
168
+ @changedAttributeHost('value')
169
+ private refreshValue(valueAttr?: string | null) {
170
+ valueAttr ??= this.getAttribute('value');
171
+ if (valueAttr === null) {
186
172
  this.value = false;
187
173
  return;
188
174
  }
189
- const win = this.ownerDocument?.defaultView || window;
190
- const res = FunctionUtils.executeReturn({
191
- script,
192
- context: this,
193
- args: SwcUtils.getHelperAndHostSet(win, this)
194
- });
195
175
 
196
- if (res instanceof Promise) {
197
- res.then(v => (this.value = v)).catch(() => (this.value = false));
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;
198
186
  } else {
199
- this.value = res;
187
+ // 일반 문자열 값
188
+ this.value = Boolean(valueAttr);
200
189
  }
201
190
  }
202
191
 
203
192
  private render() {
204
193
  this.cleanup();
205
- const portal = this._resolvePortal();
194
+ const portal = this.parentElement;
206
195
  if (!portal) return;
207
196
 
208
197
  const win = this.ownerDocument?.defaultView || window;
@@ -226,18 +215,31 @@ export const registerAllElements = (w: any) => {
226
215
  const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
227
216
  const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0] };
228
217
 
218
+ // 부모 체인을 정상적으로 유지하기 위해 __swc_host 할당 제거
229
219
  this._nodes.forEach((node, nodeIdx) => {
230
- if (node instanceof HTMLElement) (node as any).__swc_host = this;
220
+ // if (node instanceof HTMLElement) (node as any).__swc_host = this; // 제거: 부모 체인 유지
231
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
+ }
232
238
  });
233
239
 
234
240
  this._executeCloneCallback('on-clone-nodes', groupArgs);
235
241
 
236
- if (portal === this.parentElement) {
237
- portal.insertBefore(clone, this.nextSibling);
238
- } else {
239
- portal.appendChild(clone);
240
- }
242
+ portal.insertBefore(clone, this.nextSibling);
241
243
  }
242
244
  }
243
245
 
@@ -267,21 +269,7 @@ export const registerAllElements = (w: any) => {
267
269
  this.render();
268
270
  }
269
271
 
270
- private _resolvePortal(): Element | null {
271
- const portalScript = this.getAttribute('on-get-portal');
272
- if (!portalScript) return this.parentElement;
273
-
274
- const win = this.ownerDocument?.defaultView || window;
275
- const res = FunctionUtils.executeReturn({
276
- script: portalScript,
277
- context: this,
278
- args: SwcUtils.getHelperAndHostSet(win, this)
279
- });
280
272
 
281
- if (res instanceof HTMLElement) return res;
282
- if (typeof res === 'string') return win.document.querySelector(res);
283
- return this.parentElement;
284
- }
285
273
 
286
274
  private _executeCloneCallback(attr: string, args: any) {
287
275
  const script = this.getAttribute(attr);
@@ -294,13 +282,17 @@ export const registerAllElements = (w: any) => {
294
282
  this._nodeGroups = [];
295
283
  }
296
284
 
297
- @changedAttributeHost('on-get-value')
285
+ @changedAttributeHost('value')
298
286
  private refreshValue() {
299
- const script = this.getAttribute('on-get-value');
287
+ let script = this.getAttribute('value');
300
288
  if (!script) {
301
289
  this.value = [];
302
290
  return;
303
291
  }
292
+ // Parse {{ }} braces
293
+ if (script.startsWith('{{') && script.endsWith('}}')) {
294
+ script = script.slice(2, -2).trim();
295
+ }
304
296
  const win = this.ownerDocument?.defaultView || window;
305
297
  const res = FunctionUtils.executeReturn({
306
298
  script,
@@ -317,7 +309,7 @@ export const registerAllElements = (w: any) => {
317
309
 
318
310
  render() {
319
311
  this._cleanup();
320
- const portal = this._resolvePortal();
312
+ const portal = this.parentElement;
321
313
  if (!portal) return;
322
314
 
323
315
  const win = this.ownerDocument?.defaultView || window;
@@ -345,8 +337,23 @@ export const registerAllElements = (w: any) => {
345
337
  const groupArgs = { ...helperSet, $item: item, $index: index, $value: this._value, $nodes: nodes, $elements: elements, $firstElement: elements[0] };
346
338
 
347
339
  nodes.forEach((node, nodeIdx) => {
348
- if (node instanceof HTMLElement) (node as any).__swc_host = this;
349
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
+ }
350
357
  });
351
358
 
352
359
  this._executeCloneCallback('on-clone-nodes', groupArgs);
@@ -374,6 +381,7 @@ export const registerAllElements = (w: any) => {
374
381
  class SwcChoose extends HTMLTemplateElement {
375
382
  private _nodes: Node[] = [];
376
383
  private _value: any = null;
384
+ private _previousTemplate: HTMLTemplateElement | null = null;
377
385
 
378
386
  get value(): any {
379
387
  return this._value;
@@ -383,76 +391,118 @@ export const registerAllElements = (w: any) => {
383
391
  this.render();
384
392
  }
385
393
 
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
394
  private _executeCloneCallback(attr: string, args: any) {
403
395
  const script = this.getAttribute(attr);
404
396
  if (!script) return;
405
397
  FunctionUtils.execute({ script, context: this, args });
406
398
  }
407
399
 
408
- @changedAttributeHost('on-get-value')
409
- private refresh() {
410
- const script = this.getAttribute('on-get-value');
411
- if (!script) {
400
+ @changedAttributeHost('value')
401
+ refresh(valueAttr?: string | null) {
402
+ valueAttr ??= this.getAttribute('value');
403
+ if (valueAttr === null) {
412
404
  this.value = null;
413
405
  return;
414
406
  }
415
- const win = this.ownerDocument?.defaultView || window;
416
- const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
417
- this.value = result;
407
+
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;
420
+ }
418
421
  }
419
422
 
420
423
  private render() {
421
- this.cleanup();
422
- const portal = this._resolvePortal();
423
- if (!portal) return;
424
-
424
+ // console.log('-------------', this.value);
425
425
  const win = this.ownerDocument?.defaultView || window;
426
426
  const helperSet = SwcUtils.getHelperAndHostSet(win, this);
427
-
428
427
  const templates = Array.from(this.content.querySelectorAll('template'));
429
428
  let selected: HTMLTemplateElement | null = null;
429
+
430
430
  for (const t of templates) {
431
- if (t.getAttribute('is') === 'swc-when' && String(t.getAttribute('value')) === String(this._value)) {
432
- selected = t;
433
- break;
431
+ if (t.getAttribute('is') === 'swc-when') {
432
+ let matches = false;
433
+ const value = t.getAttribute('value') || '';
434
+
435
+ // {{ }} 브레이스로 감싸진 값은 스크립트 실행
436
+ if (value.startsWith('{{') && value.endsWith('}}')) {
437
+ const script = value.slice(2, -2).trim();
438
+ const result = FunctionUtils.executeReturn({
439
+ script,
440
+ context: t,
441
+ args: { ...helperSet, $value: this._value }
442
+ });
443
+ // console.log('rrrrrrrrrrrrrr', result);
444
+ matches = result === true;
445
+ } else {
446
+ // 일반 문자열 비교 (기존 방식)
447
+ matches = String(value) === String(this._value);
448
+ }
449
+
450
+ if (matches) {
451
+ selected = t;
452
+ break;
453
+ }
434
454
  }
435
455
  }
456
+
436
457
  const target = selected || (this.content.querySelector('template[is="swc-otherwise"]') as HTMLTemplateElement);
437
458
 
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) {
464
+ return;
465
+ }
466
+
467
+ this.cleanup();
468
+ this._previousTemplate = target;
469
+
438
470
  if (target) {
439
471
  const clone = target.content.cloneNode(true) as DocumentFragment;
440
472
  this._nodes = Array.from(clone.childNodes);
441
473
  const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
442
474
  const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0] };
443
475
 
476
+ // 복제된 최상위 요소들은 SwcChoose의 직접 자식이 될 것이므로 __swc_host 설정 안 함
444
477
  this._nodes.forEach((n, nodeIdx) => {
445
- if (n instanceof HTMLElement) (n as any).__swc_host = this;
478
+ // if (n instanceof HTMLElement) (n as any).__swc_host = this; // 제거: 부모 체인 유지
446
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
+ }
447
496
  });
448
497
 
449
498
  this._executeCloneCallback('on-clone-nodes', groupArgs);
450
499
 
451
- if (portal === this.parentElement) {
452
- portal.insertBefore(clone, this.nextSibling);
453
- } else {
454
- portal.appendChild(clone);
455
- }
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
+ // }
456
506
  }
457
507
  }
458
508
 
@@ -474,35 +524,38 @@ export const registerAllElements = (w: any) => {
474
524
  private _nodes: Node[] = [];
475
525
  private _value: any = null;
476
526
 
477
- private _resolvePortal(): Element | null {
478
- const portalScript = this.getAttribute('on-get-portal');
479
- if (!portalScript) return this.parentElement;
480
-
481
- const win = this.ownerDocument?.defaultView || window;
482
- const res = FunctionUtils.executeReturn({
483
- script: portalScript,
484
- context: this,
485
- args: SwcUtils.getHelperAndHostSet(win, this)
486
- });
487
-
488
- if (res instanceof HTMLElement) return res;
489
- if (typeof res === 'string') return win.document.querySelector(res);
490
- return this.parentElement;
491
- }
492
-
493
527
  private _executeCloneCallback(attr: string, args: any) {
494
528
  const script = this.getAttribute(attr);
495
529
  if (!script) return;
496
530
  FunctionUtils.execute({ script, context: this, args });
497
531
  }
498
532
 
499
- @changedAttributeHost('on-get-value')
500
- private refresh() {
501
- const script = this.getAttribute('on-get-value');
502
- if (!script) return;
503
- const win = this.ownerDocument?.defaultView || window;
504
- const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
533
+ @changedAttributeHost('value')
534
+ private refresh(valueAttr?: string | null) {
535
+ valueAttr ??= this.getAttribute('value');
536
+ if (!valueAttr) {
537
+ this.render('default');
538
+ return;
539
+ }
540
+
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
+ }
556
+ }
505
557
 
558
+ private executeAsync(result: any) {
506
559
  if (result instanceof Promise) {
507
560
  this.render('loading');
508
561
  result
@@ -519,7 +572,7 @@ export const registerAllElements = (w: any) => {
519
572
 
520
573
  private render(state: string) {
521
574
  this.cleanup();
522
- const portal = this._resolvePortal();
575
+ const portal = this.parentElement;
523
576
  if (!portal) return;
524
577
 
525
578
  const win = this.ownerDocument?.defaultView || window;
@@ -532,18 +585,31 @@ export const registerAllElements = (w: any) => {
532
585
  const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
533
586
  const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0], $state: state };
534
587
 
588
+ // 부모 체인을 정상적으로 유지하기 위해 __swc_host 할당 제거
535
589
  this._nodes.forEach((n, nodeIdx) => {
536
- if (n instanceof HTMLElement) (n as any).__swc_host = this;
590
+ // if (n instanceof HTMLElement) (n as any).__swc_host = this; // 제거: 부모 체인 유지
537
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
+ }
538
608
  });
539
609
 
540
610
  this._executeCloneCallback('on-clone-nodes', groupArgs);
541
611
 
542
- if (portal === this.parentElement) {
543
- portal.insertBefore(clone, this.nextSibling);
544
- } else {
545
- portal.appendChild(clone);
546
- }
612
+ portal.insertBefore(clone, this.nextSibling);
547
613
  }
548
614
  }
549
615
 
@@ -732,4 +798,78 @@ export const registerAllElements = (w: any) => {
732
798
  @elementDefine('swc-app-body', { extends: 'body', window: w })
733
799
  class SwcAppBody extends SwcAppMixin(HTMLBodyElement) {}
734
800
 
801
+
802
+ return {
803
+ // Sub Templates
804
+ SwcLoading,
805
+ SwcError,
806
+ SwcSuccess,
807
+ SwcWhen,
808
+ SwcOtherwise,
809
+ SwcDefault,
810
+ // Core Elements
811
+ SwcApp,
812
+ SwcIf,
813
+ SwcLoop,
814
+ SwcChoose,
815
+ SwcAsync,
816
+ // App Elements
817
+ SwcAppAnchor,
818
+ SwcAppArea,
819
+ SwcAppAudio,
820
+ SwcAppBase,
821
+ SwcAppButton,
822
+ SwcAppCanvas,
823
+ SwcAppData,
824
+ SwcAppDataList,
825
+ SwcAppDetails,
826
+ SwcAppDialog,
827
+ SwcAppDiv,
828
+ SwcAppDList,
829
+ SwcAppEmbed,
830
+ SwcAppFieldSet,
831
+ SwcAppForm,
832
+ SwcAppHR,
833
+ SwcAppIFrame,
834
+ SwcAppImage,
835
+ SwcAppInput,
836
+ SwcAppLabel,
837
+ SwcAppLegend,
838
+ SwcAppLI,
839
+ SwcAppLink,
840
+ SwcAppMap,
841
+ SwcAppMeta,
842
+ SwcAppMeter,
843
+ SwcAppMod,
844
+ SwcAppObject,
845
+ SwcAppOList,
846
+ SwcAppOptGroup,
847
+ SwcAppOption,
848
+ SwcAppOutput,
849
+ SwcAppParagraph,
850
+ SwcAppParam,
851
+ SwcAppPicture,
852
+ SwcAppPre,
853
+ SwcAppProgress,
854
+ SwcAppQuote,
855
+ SwcAppScript,
856
+ SwcAppSelect,
857
+ SwcAppSlot,
858
+ SwcAppSource,
859
+ SwcAppSpan,
860
+ SwcAppStyle,
861
+ SwcAppTable,
862
+ SwcAppTableSection,
863
+ SwcAppTableCell,
864
+ SwcAppTemplate,
865
+ SwcAppTextArea,
866
+ SwcAppTime,
867
+ SwcAppTitle,
868
+ SwcAppTableRow,
869
+ SwcAppTrack,
870
+ SwcAppUList,
871
+ SwcAppVideo,
872
+ SwcAppHeading,
873
+ SwcAppBody,
874
+ };
735
875
  };
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
  };
@@ -6,18 +6,7 @@ export class ElementRouter extends Router {
6
6
  private _currentIndex = -1;
7
7
 
8
8
  constructor(config: RouterConfig) {
9
- // Router 부모 클래스가 생성자에서 go()를 호출하여 초기화되지 않은 필드에 접근하는 것을 방지하기 위해
10
- // firstUrl을 제거하고 넘긴 뒤 자식에서 수동 처리합니다.
11
- const { firstUrl, ...rest } = config;
12
- super(rest);
13
-
14
- const startUrl = firstUrl ?? '/';
15
- this._history = [{ url: startUrl, data: null }];
16
- this._currentIndex = 0;
17
-
18
- if (firstUrl) {
19
- this.replace(firstUrl, { config: { noEventAndPublish: true } });
20
- }
9
+ super(config);
21
10
  }
22
11
 
23
12
  // Override pushState to manage internal history
package/src/types.ts CHANGED
@@ -42,7 +42,9 @@ export type HostSet = {
42
42
  };
43
43
 
44
44
  export type HelperHostSet = HelperSet & HostSet;
45
-
45
+ export interface SwcChooseInterface extends HTMLTemplateElement {
46
+ refresh(): void;
47
+ }
46
48
  export interface SwcAppInterface extends HTMLElement {
47
49
  simpleApplication?: SimpleApplication;
48
50
  router?: Router;