@dooboostore/simple-web-component 1.0.34 → 1.0.36

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 (170) hide show
  1. package/README.md +44 -51
  2. package/dist/cjs/decorators/addEventListener.js +12 -4
  3. package/dist/cjs/decorators/addEventListener.js.map +2 -2
  4. package/dist/cjs/decorators/applyAttribute.js +19 -18
  5. package/dist/cjs/decorators/applyAttribute.js.map +2 -2
  6. package/dist/cjs/decorators/applyClass.js +23 -22
  7. package/dist/cjs/decorators/applyClass.js.map +2 -2
  8. package/dist/cjs/decorators/applyNode.js +22 -17
  9. package/dist/cjs/decorators/applyNode.js.map +2 -2
  10. package/dist/cjs/decorators/applyStyle.js +20 -19
  11. package/dist/cjs/decorators/applyStyle.js.map +2 -2
  12. package/dist/cjs/decorators/attribute.js +17 -16
  13. package/dist/cjs/decorators/attribute.js.map +2 -2
  14. package/dist/cjs/decorators/{changedAttributeHost.js → changedAttributeThis.js} +6 -6
  15. package/dist/cjs/decorators/changedAttributeThis.js.map +7 -0
  16. package/dist/cjs/decorators/elementDefine.js +234 -188
  17. package/dist/cjs/decorators/elementDefine.js.map +2 -2
  18. package/dist/cjs/decorators/emitCustomEvent.js +24 -16
  19. package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
  20. package/dist/cjs/decorators/index.js +1 -1
  21. package/dist/cjs/decorators/index.js.map +1 -1
  22. package/dist/cjs/decorators/lifecycles.js +15 -17
  23. package/dist/cjs/decorators/lifecycles.js.map +2 -2
  24. package/dist/cjs/decorators/query.js +38 -60
  25. package/dist/cjs/decorators/query.js.map +2 -2
  26. package/dist/cjs/decorators/queryAll.js +31 -63
  27. package/dist/cjs/decorators/queryAll.js.map +2 -2
  28. package/dist/cjs/decorators/setProperty.js +13 -12
  29. package/dist/cjs/decorators/setProperty.js.map +2 -2
  30. package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js +2 -2
  31. package/dist/cjs/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +2 -2
  32. package/dist/cjs/elements/SwcAppEngine.js +8 -2
  33. package/dist/cjs/elements/SwcAppEngine.js.map +2 -2
  34. package/dist/cjs/elements/SwcAppMixin.js +120 -65
  35. package/dist/cjs/elements/SwcAppMixin.js.map +2 -2
  36. package/dist/cjs/elements/SwcAsync.js +31 -21
  37. package/dist/cjs/elements/SwcAsync.js.map +2 -2
  38. package/dist/cjs/elements/SwcChoose.js +53 -34
  39. package/dist/cjs/elements/SwcChoose.js.map +2 -2
  40. package/dist/cjs/elements/SwcIf.js +54 -32
  41. package/dist/cjs/elements/SwcIf.js.map +2 -2
  42. package/dist/cjs/elements/SwcLoop.js +111 -37
  43. package/dist/cjs/elements/SwcLoop.js.map +2 -2
  44. package/dist/cjs/elements/register.js +249 -122
  45. package/dist/cjs/elements/register.js.map +2 -2
  46. package/dist/cjs/router/ElementRouter.js +4 -2
  47. package/dist/cjs/router/ElementRouter.js.map +1 -1
  48. package/dist/cjs/types.js.map +1 -1
  49. package/dist/cjs/utils/Utils.js +31 -17
  50. package/dist/cjs/utils/Utils.js.map +2 -2
  51. package/dist/esm/decorators/addEventListener.js +12 -4
  52. package/dist/esm/decorators/addEventListener.js.map +2 -2
  53. package/dist/esm/decorators/applyAttribute.js +19 -18
  54. package/dist/esm/decorators/applyAttribute.js.map +2 -2
  55. package/dist/esm/decorators/applyClass.js +23 -22
  56. package/dist/esm/decorators/applyClass.js.map +2 -2
  57. package/dist/esm/decorators/applyNode.js +22 -17
  58. package/dist/esm/decorators/applyNode.js.map +2 -2
  59. package/dist/esm/decorators/applyStyle.js +20 -19
  60. package/dist/esm/decorators/applyStyle.js.map +2 -2
  61. package/dist/esm/decorators/attribute.js +17 -16
  62. package/dist/esm/decorators/attribute.js.map +2 -2
  63. package/dist/esm/decorators/{changedAttributeHost.js → changedAttributeThis.js} +3 -3
  64. package/dist/esm/decorators/changedAttributeThis.js.map +7 -0
  65. package/dist/esm/decorators/elementDefine.js +233 -187
  66. package/dist/esm/decorators/elementDefine.js.map +2 -2
  67. package/dist/esm/decorators/emitCustomEvent.js +24 -16
  68. package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
  69. package/dist/esm/decorators/index.js +1 -1
  70. package/dist/esm/decorators/index.js.map +1 -1
  71. package/dist/esm/decorators/lifecycles.js +15 -17
  72. package/dist/esm/decorators/lifecycles.js.map +2 -2
  73. package/dist/esm/decorators/query.js +38 -60
  74. package/dist/esm/decorators/query.js.map +2 -2
  75. package/dist/esm/decorators/queryAll.js +31 -63
  76. package/dist/esm/decorators/queryAll.js.map +2 -2
  77. package/dist/esm/decorators/setProperty.js +13 -12
  78. package/dist/esm/decorators/setProperty.js.map +2 -2
  79. package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js +2 -2
  80. package/dist/esm/decorators/subscribeSwcAppRouteChangeWhileConnected.js.map +2 -2
  81. package/dist/esm/elements/SwcAppEngine.js +9 -2
  82. package/dist/esm/elements/SwcAppEngine.js.map +2 -2
  83. package/dist/esm/elements/SwcAppMixin.js +122 -66
  84. package/dist/esm/elements/SwcAppMixin.js.map +2 -2
  85. package/dist/esm/elements/SwcAsync.js +32 -22
  86. package/dist/esm/elements/SwcAsync.js.map +2 -2
  87. package/dist/esm/elements/SwcChoose.js +54 -35
  88. package/dist/esm/elements/SwcChoose.js.map +2 -2
  89. package/dist/esm/elements/SwcIf.js +55 -33
  90. package/dist/esm/elements/SwcIf.js.map +2 -2
  91. package/dist/esm/elements/SwcLoop.js +112 -38
  92. package/dist/esm/elements/SwcLoop.js.map +2 -2
  93. package/dist/esm/elements/register.js +250 -123
  94. package/dist/esm/elements/register.js.map +2 -2
  95. package/dist/esm/router/ElementRouter.js +5 -2
  96. package/dist/esm/router/ElementRouter.js.map +1 -1
  97. package/dist/esm/types.js.map +1 -1
  98. package/dist/esm/utils/Utils.js +31 -17
  99. package/dist/esm/utils/Utils.js.map +2 -2
  100. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +563 -557
  101. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  102. package/dist/types/decorators/addEventListener.d.ts +5 -7
  103. package/dist/types/decorators/addEventListener.d.ts.map +1 -1
  104. package/dist/types/decorators/applyAttribute.d.ts +4 -4
  105. package/dist/types/decorators/applyAttribute.d.ts.map +1 -1
  106. package/dist/types/decorators/applyClass.d.ts +6 -6
  107. package/dist/types/decorators/applyClass.d.ts.map +1 -1
  108. package/dist/types/decorators/applyNode.d.ts +7 -6
  109. package/dist/types/decorators/applyNode.d.ts.map +1 -1
  110. package/dist/types/decorators/applyStyle.d.ts +4 -4
  111. package/dist/types/decorators/applyStyle.d.ts.map +1 -1
  112. package/dist/types/decorators/attribute.d.ts +5 -5
  113. package/dist/types/decorators/attribute.d.ts.map +1 -1
  114. package/dist/types/decorators/changedAttributeThis.d.ts +18 -0
  115. package/dist/types/decorators/changedAttributeThis.d.ts.map +1 -0
  116. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  117. package/dist/types/decorators/emitCustomEvent.d.ts +11 -12
  118. package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
  119. package/dist/types/decorators/index.d.ts +1 -1
  120. package/dist/types/decorators/lifecycles.d.ts +10 -7
  121. package/dist/types/decorators/lifecycles.d.ts.map +1 -1
  122. package/dist/types/decorators/query.d.ts +17 -63
  123. package/dist/types/decorators/query.d.ts.map +1 -1
  124. package/dist/types/decorators/queryAll.d.ts +6 -63
  125. package/dist/types/decorators/queryAll.d.ts.map +1 -1
  126. package/dist/types/decorators/setProperty.d.ts +1 -1
  127. package/dist/types/decorators/setProperty.d.ts.map +1 -1
  128. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts +4 -5
  129. package/dist/types/decorators/subscribeSwcAppRouteChangeWhileConnected.d.ts.map +1 -1
  130. package/dist/types/elements/SwcAppEngine.d.ts +4 -1
  131. package/dist/types/elements/SwcAppEngine.d.ts.map +1 -1
  132. package/dist/types/elements/SwcAppMixin.d.ts +11 -8
  133. package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
  134. package/dist/types/elements/register.d.ts.map +1 -1
  135. package/dist/types/types.d.ts +3 -2
  136. package/dist/types/types.d.ts.map +1 -1
  137. package/dist/types/utils/Utils.d.ts +11 -8
  138. package/dist/types/utils/Utils.d.ts.map +1 -1
  139. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +563 -557
  140. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  141. package/package.json +3 -3
  142. package/src/decorators/addEventListener.ts +11 -9
  143. package/src/decorators/applyAttribute.ts +16 -15
  144. package/src/decorators/applyClass.ts +18 -17
  145. package/src/decorators/applyNode.ts +34 -21
  146. package/src/decorators/applyStyle.ts +17 -16
  147. package/src/decorators/attribute.ts +21 -20
  148. package/src/decorators/{changedAttributeHost.ts → changedAttributeThis.ts} +13 -14
  149. package/src/decorators/elementDefine.ts +402 -358
  150. package/src/decorators/emitCustomEvent.ts +40 -31
  151. package/src/decorators/index.ts +1 -1
  152. package/src/decorators/lifecycles.ts +11 -10
  153. package/src/decorators/query.ts +51 -124
  154. package/src/decorators/queryAll.ts +30 -127
  155. package/src/decorators/setProperty.ts +12 -11
  156. package/src/decorators/subscribeSwcAppRouteChangeWhileConnected.ts +10 -10
  157. package/src/elements/SwcAppEngine.ts +10 -3
  158. package/src/elements/SwcAppMixin.ts +177 -97
  159. package/src/elements/SwcAsync.ts +32 -21
  160. package/src/elements/SwcChoose.ts +74 -37
  161. package/src/elements/SwcIf.ts +60 -23
  162. package/src/elements/SwcLoop.ts +141 -28
  163. package/src/elements/register.ts +300 -102
  164. package/src/types.ts +6 -5
  165. package/src/utils/Utils.ts +40 -23
  166. package/tsconfig.json +2 -2
  167. package/dist/cjs/decorators/changedAttributeHost.js.map +0 -7
  168. package/dist/esm/decorators/changedAttributeHost.js.map +0 -7
  169. package/dist/types/decorators/changedAttributeHost.d.ts +0 -19
  170. package/dist/types/decorators/changedAttributeHost.d.ts.map +0 -1
@@ -1,4 +1,4 @@
1
- import { getElementConfig, elementDefine, attributeHost, changedAttributeHost, getAttributeValue } from '../decorators';
1
+ import { getElementConfig, elementDefine, attributeThis, changedAttributeThis, getAttributeValue } from '../decorators';
2
2
  import { SwcAppMixin } from './SwcAppMixin';
3
3
  import { SwcUtils } from '../utils/Utils';
4
4
  import { FunctionUtils,ActionExpression } from '@dooboostore/core';
@@ -148,11 +148,17 @@ 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
- @attributeHost('value')
151
+ // id =
152
+ // @attributeThis('value')
152
153
  _value: any;
153
154
 
154
- @attributeHost('replace-wrap')
155
- _replaceWrap: string | null;
155
+ @attributeThis('replace-wrap-start')
156
+ _replaceWrapStart: string | null;
157
+ @attributeThis('replace-wrap-end')
158
+ _replaceWrapEnd: string | null;
159
+
160
+ @attributeThis('skip-if-same')
161
+ _skipIfSame: string | null;
156
162
 
157
163
  private _nodes: Node[] = [];
158
164
 
@@ -165,14 +171,37 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
165
171
  this.refresh(nv);
166
172
  }
167
173
 
168
- private replaceExpressions(html: string, groupArgs: any, context: any, wrap?: string | null): string {
169
- const config = wrap ? { wrapExpression: wrap } : undefined;
174
+ constructor() {
175
+ super();
176
+ }
177
+
178
+ @changedAttributeThis('value')
179
+ private setValue(value: any, o: string, n: string) {
180
+ this._value = value;
181
+ this.refresh(this._value);
182
+ }
183
+
184
+ // @changedAttributeThis('replace-wrap')
185
+ // private setReplaceWrap(replaceWrapAttr?: string | null) {
186
+ // this.refresh();
187
+ // }
188
+
189
+ private replaceExpressions(html: string, groupArgs: any, context: any, wrap: {start: string, end: string}): string {
190
+ let config: any = undefined;
191
+ if (wrap) {
192
+ config = {
193
+ expression: {
194
+ replace: { start: `${wrap.start}`, end: `${wrap.end}` },
195
+ callReturn: { start: `${wrap.start}=`, end: `${wrap.end}` },
196
+ call: { start: `${wrap.start}@`, end: `${wrap.end}` }
197
+ }
198
+ };
199
+ }
170
200
  const ae = new ActionExpression(html, config);
171
- const filterOptions = wrap ? { type: 'replace' as const, wrap } : 'replace';
172
201
  const win = this.ownerDocument?.defaultView || window;
173
-
202
+
174
203
  // @ts-ignore
175
- for (const expr of ae.getExpressions(filterOptions)) {
204
+ for (const expr of ae.getExpressions('replace')) {
176
205
  try {
177
206
  const value = FunctionUtils.executeReturn({
178
207
  script: ConvertUtils.decodeHtmlEntity(expr.script, win.document),
@@ -187,39 +216,47 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
187
216
  return ae.getUnprocessedTemplate();
188
217
  }
189
218
 
190
- @changedAttributeHost('value')
191
- private setValue(valueAttr?: string | null) {
192
- this.refresh();
193
- }
219
+ refresh(value?: any) {
220
+ const targetValue = value !== undefined ? value : (getAttributeValue(this, 'value') ?? this._value);
194
221
 
195
- @changedAttributeHost('replace-wrap')
196
- private setReplaceWrap(replaceWrapAttr?: string | null) {
197
- this.refresh();
198
- }
222
+ // skip-if-same 속성이 존재하고(true/빈 문자열), 이전 값과 새로운 값이 완전히 동일하면 렌더링 스킵!
223
+ if (this.hasAttribute('skip-if-same') && this._value === targetValue && this._nodes.length > 0) {
224
+ return;
225
+ }
199
226
 
200
- refresh(value = this._value) {
201
227
  this.cleanup();
228
+ this._value = targetValue;
229
+
202
230
  const portal = this.parentElement;
203
- if (!portal || !value) return;
231
+ if (!portal || !this._value) return;
204
232
 
205
233
  const win = this.ownerDocument?.defaultView || window;
206
234
  const helperSet = SwcUtils.getHelperAndHostSet(win, this);
207
235
 
208
236
  let htmlString = this.innerHTML;
209
- const groupArgs = { ...helperSet, $value: value };
237
+ const groupArgs = { ...helperSet, $value: this._value, $templateHost: this };
210
238
 
211
- htmlString = this.replaceExpressions(htmlString, groupArgs, this, this._replaceWrap);
239
+ htmlString = this.replaceExpressions(htmlString, groupArgs, this, {start:this._replaceWrapStart ?? '{#{', end: this._replaceWrapEnd ?? '}#}'});
212
240
 
213
241
  const result = this.ownerDocument?.createElement('template') || document.createElement('template');
214
242
  result.innerHTML = htmlString;
215
243
  const processedFragment = result.content;
216
244
 
217
245
  this._nodes = Array.from(processedFragment.childNodes);
246
+ this._nodes.forEach((node: any) => {
247
+ if (node instanceof HTMLElement) (node as any).__swc_template_host = this;
248
+ });
249
+
218
250
  portal.insertBefore(processedFragment, this.nextSibling);
219
251
  }
220
252
 
221
253
  private cleanup() {
222
- this._nodes.forEach(n => n.parentElement?.removeChild(n));
254
+ this._nodes.forEach(n => {
255
+ const realNode = (n as any).__upgraded_instance || n;
256
+ if (realNode.parentElement) {
257
+ realNode.parentElement.removeChild(realNode);
258
+ }
259
+ });
223
260
  this._nodes = [];
224
261
  }
225
262
 
@@ -230,43 +267,64 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
230
267
 
231
268
  @elementDefine('swc-loop', { extends: 'template', window: w })
232
269
  class SwcLoop extends HTMLTemplateElement {
233
- @attributeHost('value')
234
- _value: any[];
270
+ _value: any[] = [];
235
271
 
236
- @attributeHost('replace-wrap')
237
- _replaceWrap: string | null;
272
+ @attributeThis('replace-wrap-start')
273
+ _replaceWrapStart: string | null;
274
+ @attributeThis('replace-wrap-end')
275
+ _replaceWrapEnd: string | null;
238
276
 
239
277
  private _nodeGroups: Node[][] = [];
240
278
 
241
279
  get value(): any[] {
242
280
  return this._value;
243
281
  }
282
+
244
283
  set value(nv: any[]) {
245
- // this._value = Array.isArray(nv) ? [...nv] : [];
284
+ this._value = nv;
246
285
  this.refresh(nv);
247
286
  }
248
287
 
249
- @changedAttributeHost('value')
250
- private setValue() {
251
- this.refresh();
288
+ getValue(index: number) {
289
+ return this._value?.[index];
252
290
  }
253
291
 
254
- @changedAttributeHost('replace-wrap')
255
- private setReplaceWrap() {
256
- this.refresh();
292
+ constructor() {
293
+ super();
294
+ // console.log('swc SwcLoop constructor called!');
295
+ // console.log('swc SwcLoop', this.getAttribute('value'));
296
+ }
297
+
298
+ @changedAttributeThis('value')
299
+ private setValue(value: any, o: string, n: string) {
300
+ this._value = value;
301
+ this.refresh(this._value);
257
302
  }
258
303
 
259
- private replaceExpressions(html: string, groupArgs: any, context: any, wrap?: string | null): string {
260
- const config = wrap ? { wrapExpression: wrap } : undefined;
304
+ // @changedAttributeThis('replace-wrap')
305
+ // private setReplaceWrap(replaceWrapAttr?: string | null) {
306
+ // this.refresh();
307
+ // }
308
+
309
+ private replaceExpressions(html: string, groupArgs: any, context: any, wrap: {start: string, end: string}): string {
310
+ let config: any = undefined;
311
+ config = {
312
+ expression: {
313
+ replace: { start: `${wrap.start}`, end: `${wrap.end}` },
314
+ callReturn: { start: `${wrap.start}=`, end: `${wrap.end}` },
315
+ call: { start: `${wrap.start}@`, end: `${wrap.end}` }
316
+ }
317
+ };
261
318
  const ae = new ActionExpression(html, config);
262
- const filterOptions = wrap ? { type: 'replace' as const, wrap } : 'replace';
263
319
  const win = this.ownerDocument?.defaultView || window;
264
320
 
265
321
  // @ts-ignore
266
- for (const expr of ae.getExpressions(filterOptions)) {
322
+ const expressions = ae.getExpressions('replace');
323
+ for (const expr of expressions) {
324
+ const script = ConvertUtils.decodeHtmlEntity(expr.script, win.document);
267
325
  try {
268
326
  const value = FunctionUtils.executeReturn({
269
- script: ConvertUtils.decodeHtmlEntity(expr.script, win.document),
327
+ script: script,
270
328
  context,
271
329
  args: groupArgs
272
330
  });
@@ -278,35 +336,127 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
278
336
  return ae.getUnprocessedTemplate();
279
337
  }
280
338
 
281
- refresh(value = this._value) {
282
- this.cleanup();
283
- if (value?.length<=0) return;
284
- const portal = this.parentElement;
285
- if (!portal) return;
286
-
339
+ private createFragmentAndNodes(items: any[], helperSet: any, startIndex: number = 0): { fragment: DocumentFragment; newNodesGroup: Node[][] } {
287
340
  const win = this.ownerDocument?.defaultView || window;
288
- const helperSet = SwcUtils.getHelperAndHostSet(win, this);
289
341
  const fragment = win.document.createDocumentFragment();
342
+ const newNodesGroup: Node[][] = [];
290
343
 
291
- value.forEach((item, index) => {
344
+ items.forEach((item, i) => {
345
+ const index = startIndex + i;
292
346
  let htmlString = this.innerHTML;
293
347
  const groupArgs = { ...helperSet, $item: item, $index: index, $value: this._value };
294
348
 
295
- htmlString = this.replaceExpressions(htmlString, groupArgs, this, this._replaceWrap);
349
+ htmlString = this.replaceExpressions(htmlString, groupArgs, this, {start:this._replaceWrapStart ?? '{#{', end: this._replaceWrapEnd ?? '}#}'});
296
350
 
297
351
  const temp = this.ownerDocument?.createElement('template') || document.createElement('template');
298
352
  temp.innerHTML = htmlString;
299
353
 
300
354
  const content = temp.content;
301
355
  const nodes = Array.from(content.childNodes);
302
- this._nodeGroups.push(nodes);
356
+
357
+ const setTemplateHost = (element: HTMLElement) => {
358
+ (element as any).__swc_template_host = this;
359
+ Array.from(element.children).forEach((child: any) => {
360
+ if (child instanceof HTMLElement) {
361
+ setTemplateHost(child);
362
+ }
363
+ });
364
+ };
365
+
366
+ nodes.forEach((node: any) => {
367
+ if (node instanceof HTMLElement) {
368
+ setTemplateHost(node as HTMLElement);
369
+ }
370
+ });
371
+ newNodesGroup.push(nodes);
303
372
  fragment.appendChild(content);
304
373
  });
305
374
 
375
+ return { fragment, newNodesGroup };
376
+ }
377
+
378
+ appendItem(...items: any[]) {
379
+ if (!items || items.length <= 0) return;
380
+ const portal = SwcUtils.getParentContainer(this);
381
+ if (!portal) return;
382
+
383
+ const win = this.ownerDocument?.defaultView || window;
384
+ const helperSet = SwcUtils.getHelperAndHostSet(win, this);
385
+
386
+ const startIndex = this._value ? this._value.length : 0;
387
+
388
+ // 내부 상태(value) 업데이트 (기존 배열에 새 아이템 추가)
389
+ if (this._value !== items) {
390
+ this._value = [...(this._value || []), ...items];
391
+ }
392
+
393
+ const { fragment, newNodesGroup } = this.createFragmentAndNodes(items, helperSet, startIndex);
394
+ this._nodeGroups.push(...newNodesGroup);
395
+
396
+ console.log('뭐야 씨발');
397
+ // append 위치 계산: 기존에 그려진 마지막 노드 뒤에 삽입, 없으면 템플릿 바로 뒤에 삽입
398
+ let insertReference: Node | null = this.nextSibling;
399
+ if (this._nodeGroups.length > items.length) {
400
+ const lastExistingGroup = this._nodeGroups[this._nodeGroups.length - items.length - 1];
401
+ if (lastExistingGroup && lastExistingGroup.length > 0) {
402
+ const lastNode = lastExistingGroup[lastExistingGroup.length - 1];
403
+ insertReference = lastNode.nextSibling;
404
+ }
405
+ }
406
+
407
+ portal.insertBefore(fragment, insertReference);
408
+ }
409
+
410
+ async prependItem(...items: any[]) {
411
+ if (!items || items.length <= 0) return;
412
+ const portal = SwcUtils.getParentContainer(this);
413
+ if (!portal) return;
414
+
415
+ const win = this.ownerDocument?.defaultView || window;
416
+ const helperSet = SwcUtils.getHelperAndHostSet(win, this);
417
+
418
+ // 내부 상태(value) 업데이트 (기존 배열의 앞쪽에 새 아이템 추가)
419
+ if (this._value !== items) {
420
+ this._value = [...items, ...(this._value || [])];
421
+ }
422
+
423
+ const { fragment, newNodesGroup } = this.createFragmentAndNodes(items, helperSet, 0);
424
+
425
+ // 기존의 nodeGroups 배열 맨 앞에 새로운 그룹들을 추가
426
+ this._nodeGroups = [...newNodesGroup, ...this._nodeGroups];
427
+
428
+ // prepend 위치: 이 템플릿(SwcLoop) 노드의 바로 다음 위치 (가장 위)
306
429
  portal.insertBefore(fragment, this.nextSibling);
307
430
  }
308
431
 
432
+ refresh(value?: any) {
433
+ // 1. 그릴 대상 확정
434
+ const targetValue = value !== undefined ? value : (getAttributeValue(this, 'value') ?? this._value ?? []);
435
+
436
+ // 2. 화면 정리
437
+ this.cleanup();
438
+
439
+ // 3. 상태 완전 교체 (이어붙이지 않음!)
440
+ this._value = targetValue;
441
+
442
+ if (!this._value || this._value.length <= 0) return;
443
+
444
+ // 4. 화면 다시 그리기 (appendItem을 쓰지 않고 바로 그리기)
445
+ const win = this.ownerDocument?.defaultView || window;
446
+ const helperSet = SwcUtils.getHelperAndHostSet(win, this);
447
+ helperSet.$templateHost = this;
448
+ const { fragment, newNodesGroup } = this.createFragmentAndNodes(this._value, helperSet, 0);
449
+
450
+ this._nodeGroups = newNodesGroup;
451
+ const portal = SwcUtils.getParentContainer(this);
452
+ if (portal) {
453
+ // console.log('append????!~~~~~!?',fragment)
454
+ portal.insertBefore(fragment, this.nextSibling);
455
+ }
456
+ }
457
+
309
458
  private cleanup() {
459
+ console.log('누가 cleanup');
310
460
  this._nodeGroups.forEach(nodes => nodes.forEach(n => n.parentElement?.removeChild(n)));
311
461
  this._nodeGroups = [];
312
462
  }
@@ -318,13 +468,12 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
318
468
 
319
469
  @elementDefine('swc-choose', { extends: 'template', window: w })
320
470
  class SwcChoose extends HTMLTemplateElement {
321
- // @attributeHost('value')
322
- // fetchValue: any;
323
-
324
471
  _value: any;
325
472
 
326
- @attributeHost('replace-wrap')
327
- _replaceWrap: string | null;
473
+ @attributeThis('replace-wrap-start')
474
+ _replaceWrapStart: string | null;
475
+ @attributeThis('replace-wrap-end')
476
+ _replaceWrapEnd: string | null;
328
477
 
329
478
  private _nodes: Node[] = [];
330
479
  private _previousTemplate: HTMLTemplateElement | null = null;
@@ -352,26 +501,34 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
352
501
  // console.log('-----------------choooooooose attr origin', na, o, n);
353
502
  }
354
503
 
355
- @changedAttributeHost('value')
504
+ @changedAttributeThis('value')
356
505
  private setValue(value: any, o: string, n: string) {
357
- // console.log('-----------------choooooooose attr setValue', value, o, n);
506
+ console.log('chooooooose value', value)
358
507
  this._value = value;
359
508
  this.refresh(this._value);
360
509
  }
361
510
 
362
- @changedAttributeHost('replace-wrap')
363
- private setReplaceWrap(replaceWrapAttr?: string | null) {
364
- this.refresh();
365
- }
511
+ // @changedAttributeThis('replace-wrap')
512
+ // private setReplaceWrap(replaceWrapAttr?: string | null) {
513
+ // this.refresh();
514
+ // }
366
515
 
367
- private replaceExpressions(html: string, groupArgs: any, context: any, wrap?: string | null): string {
368
- const config = wrap ? { wrapExpression: wrap } : undefined;
516
+ private replaceExpressions(html: string, groupArgs: any, context: any, wrap: {start: string, end: string}): string {
517
+ let config: any = undefined;
518
+ if (wrap) {
519
+ config = {
520
+ expression: {
521
+ replace: { start: `${wrap.start}`, end: `${wrap.end}` },
522
+ callReturn: { start: `${wrap.start}=`, end: `${wrap.end}` },
523
+ call: { start: `${wrap.start}@`, end: `${wrap.end}` }
524
+ }
525
+ };
526
+ }
369
527
  const ae = new ActionExpression(html, config);
370
- const filterOptions = wrap ? { type: 'replace' as const, wrap } : 'replace';
371
528
  const win = this.ownerDocument?.defaultView || window;
372
529
 
373
530
  // @ts-ignore
374
- for (const expr of ae.getExpressions(filterOptions)) {
531
+ for (const expr of ae.getExpressions('replace')) {
375
532
  try {
376
533
  const value = FunctionUtils.executeReturn({
377
534
  script: ConvertUtils.decodeHtmlEntity(expr.script, win.document),
@@ -388,37 +545,48 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
388
545
 
389
546
  refresh(value?: any) {
390
547
  value = getAttributeValue(this, 'value') ?? this._value;
391
- // console.log('chooooooooo refresh->', value);
392
- const portal = this.parentElement;
393
- if (!portal) return;
548
+ console.log('[DEBUG] SwcChoose.refresh called, value:', value);
549
+ const portal = SwcUtils.getParentContainer(this);
550
+ if (!portal) {
551
+ console.log('[DEBUG] SwcChoose portal is null!');
552
+ return;
553
+ }
394
554
 
395
555
  const win = (getElementConfig(this).window ?? this.ownerDocument?.defaultView) || window;
396
556
  const helperSet = SwcUtils.getHelperAndHostSet(win, this);
397
- const groupArgs = { ...helperSet, $value: value };
557
+ const groupArgs = { ...helperSet, $value: value, $templateHost: this };
398
558
 
399
559
  const templates = Array.from(this.content.querySelectorAll('template'));
400
560
  let selected: HTMLTemplateElement | null = null;
401
561
 
402
562
  for (const t of templates) {
403
563
  if (t.getAttribute('is') === 'swc-when') {
404
- const attrVal = t.getAttribute('value') || '';
564
+ const attrVal = t.getAttribute('test') || '';
405
565
  let matches = false;
406
566
 
567
+
568
+
569
+ const result = FunctionUtils.executeReturn({
570
+ script: attrVal,
571
+ context: t,
572
+ args: groupArgs
573
+ });
574
+ matches = !!result;
407
575
  // Use ActionExpression to parse and extract script from value attribute
408
- const ae = new ActionExpression(attrVal);
409
- const expr = ae.getFirstExpression('replace');
410
-
411
- if (expr) {
412
- const decodedScript = ConvertUtils.decodeHtmlEntity(expr.script, win.document);
413
- const result = FunctionUtils.executeReturn({
414
- script: decodedScript,
415
- context: t,
416
- args: groupArgs
417
- });
418
- matches = !!result;
419
- } else {
420
- matches = String(attrVal) === String(value);
421
- }
576
+ // const ae = new ActionExpression(attrVal);
577
+ // const expr = ae.getFirstExpression('replace');
578
+
579
+ // if (expr) {
580
+ // const decodedScript = ConvertUtils.decodeHtmlEntity(expr.script, win.document);
581
+ // const result = FunctionUtils.executeReturn({
582
+ // script: decodedScript,
583
+ // context: t,
584
+ // args: groupArgs
585
+ // });
586
+ // matches = !!result;
587
+ // } else {
588
+ // matches = String(attrVal) === String(value);
589
+ // }
422
590
 
423
591
  if (matches) {
424
592
  selected = t;
@@ -443,14 +611,33 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
443
611
 
444
612
  if (target) {
445
613
  let htmlString = target.innerHTML;
446
- htmlString = this.replaceExpressions(htmlString, groupArgs, this, this._replaceWrap);
614
+ htmlString = this.replaceExpressions(htmlString, groupArgs, this, {start:this._replaceWrapStart ?? '{#{', end: this._replaceWrapEnd ?? '}#}'});
447
615
 
448
616
  const result = this.ownerDocument?.createElement('template') || document.createElement('template');
449
617
  result.innerHTML = htmlString;
450
618
  const processedFragment = result.content;
451
619
 
620
+ // console.log('[DEBUG] SwcChoose processedFragment children:', processedFragment._childNodesInternal?.length);
452
621
  this._nodes = Array.from(processedFragment.childNodes);
622
+
623
+ const setTemplateHost = (element: HTMLElement) => {
624
+ (element as any).__swc_template_host = this;
625
+ Array.from(element.children).forEach((child: any) => {
626
+ if (child instanceof HTMLElement) {
627
+ setTemplateHost(child);
628
+ }
629
+ });
630
+ };
631
+
632
+ this._nodes.forEach((node: any) => {
633
+ if (node instanceof HTMLElement) {
634
+ setTemplateHost(node as HTMLElement);
635
+ }
636
+ });
637
+
638
+ console.log(`[DEBUG] SwcChoose before insertBefore, portal: ${portal?.tagName}, isConnected: ${(portal as any)?.isConnected}`);
453
639
  portal.insertBefore(processedFragment, this.nextSibling);
640
+ console.log('[DEBUG] SwcChoose after insertBefore');
454
641
  }
455
642
  }
456
643
 
@@ -466,13 +653,12 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
466
653
 
467
654
  @elementDefine('swc-async', { extends: 'template', window: w })
468
655
  class SwcAsync extends HTMLTemplateElement {
469
- @attributeHost('value')
656
+ @attributeThis('value')
470
657
  _value: any;
471
-
472
- @attributeHost('replace-wrap')
473
- _replaceWrap: string | null;
474
-
475
- resultValue?: any;
658
+ @attributeThis('replace-wrap-start')
659
+ _replaceWrapStart: string | null;
660
+ @attributeThis('replace-wrap-end')
661
+ _replaceWrapEnd: string | null; resultValue?: any;
476
662
 
477
663
  get value(): boolean {
478
664
  return this._value;
@@ -484,15 +670,15 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
484
670
 
485
671
  private _nodes: Node[] = [];
486
672
 
487
- @changedAttributeHost('value')
673
+ @changedAttributeThis('value')
488
674
  private setValue(valueAttr?: string | null) {
489
675
  this.executeAsync(this._value);
490
676
  }
491
677
 
492
- @changedAttributeHost('replace-wrap')
493
- private setReplaceWrap() {
494
- this.executeAsync(this._value);
495
- }
678
+ // @changedAttributeThis('replace-wrap')
679
+ // private setReplaceWrap() {
680
+ // this.executeAsync(this._value);
681
+ // }
496
682
 
497
683
  private executeAsync(result: any = this._value) {
498
684
  if (result === undefined || result === null) {
@@ -518,14 +704,22 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
518
704
  }
519
705
  }
520
706
 
521
- private replaceExpressions(html: string, groupArgs: any, context: any, wrap?: string | null): string {
522
- const config = wrap ? { wrapExpression: wrap } : undefined;
707
+ private replaceExpressions(html: string, groupArgs: any, context: any, wrap: {start: string, end: string}): string {
708
+ let config: any = undefined;
709
+ if (wrap) {
710
+ config = {
711
+ expression: {
712
+ replace: { start: `${wrap.start}`, end: `${wrap.end}` },
713
+ callReturn: { start: `${wrap.start}=`, end: `${wrap.end}` },
714
+ call: { start: `${wrap.start}@`, end: `${wrap.end}` }
715
+ }
716
+ };
717
+ }
523
718
  const ae = new ActionExpression(html, config);
524
- const filterOptions = wrap ? { type: 'replace' as const, wrap } : 'replace';
525
719
  const win = this.ownerDocument?.defaultView || window;
526
720
 
527
721
  // @ts-ignore
528
- for (const expr of ae.getExpressions(filterOptions)) {
722
+ for (const expr of ae.getExpressions('replace')) {
529
723
  try {
530
724
  const value = FunctionUtils.executeReturn({
531
725
  script: ConvertUtils.decodeHtmlEntity(expr.script, win.document),
@@ -541,9 +735,9 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
541
735
  }
542
736
 
543
737
  refresh(state: string = 'default') {
544
- console.log('rrrrrrrrrrrrrrrrr', state);
738
+ // console.log('rrrrrrrrrrrrrrrrr', state);
545
739
  this.cleanup();
546
- const portal = this.parentElement;
740
+ const portal = SwcUtils.getParentContainer(this);
547
741
  if (!portal) return;
548
742
 
549
743
  const win = this.ownerDocument?.defaultView || window;
@@ -552,15 +746,19 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
552
746
  const t = this.content.querySelector(`template[is="swc-${state}"]`) as HTMLTemplateElement;
553
747
  if (t) {
554
748
  let htmlString = t.innerHTML;
555
- const groupArgs = { ...helperSet, $value: this._value, $state: state };
749
+ const groupArgs = { ...helperSet, $value: this._value, $state: state, $templateHost: this };
556
750
 
557
- htmlString = this.replaceExpressions(htmlString, groupArgs, this, this._replaceWrap);
751
+ htmlString = this.replaceExpressions(htmlString, groupArgs, this, {start:this._replaceWrapStart ?? '{#{', end: this._replaceWrapEnd ?? '}#}'});
558
752
 
559
753
  const temp = this.ownerDocument?.createElement('template') || document.createElement('template');
560
754
  temp.innerHTML = htmlString;
561
755
  const content = temp.content;
562
756
 
563
757
  this._nodes = Array.from(content.childNodes);
758
+ this._nodes.forEach((node: any) => {
759
+ if (node instanceof HTMLElement) (node as any).__template_swc_host = this;
760
+ });
761
+
564
762
  portal.insertBefore(content, this.nextSibling);
565
763
  }
566
764
  }
package/src/types.ts CHANGED
@@ -17,7 +17,7 @@ export enum InjectSituationType {
17
17
 
18
18
  export type SwcRootType = 'light' | 'shadow' | 'all' | 'auto';
19
19
 
20
- export type SpecialSelector = ':host' | ':window' | ':document' | ':parentHost' | ':appHost' | ':firstHost' | ':lastHost' | ':firstAppHost' | ':lastAppHost' | ':hosts' | ':appHosts';
20
+ export type SpecialSelector = '$this' | '$window' | '$document' | '$host' | '$appHost' | '$firstHost' | '$lastHost' | '$firstAppHost' | '$lastAppHost' | '$hosts' | '$appHosts';
21
21
 
22
22
  export type SwcQueryOptions = { root?: SwcRootType };
23
23
 
@@ -30,11 +30,12 @@ export type HelperSet = {
30
30
  };
31
31
 
32
32
  export type HostSet = {
33
- $host: HTMLElement; // Current component itself
34
- $parentHost: HTMLElement | null; // Nearest parent SWC component
35
- $hosts: HTMLElement[]; // [root, ..., parent, self]
33
+ $host: HTMLElement | null; // Nearest parent SWC component
34
+ $parentHost: HTMLElement | null; // Grandparent SWC ancestor
35
+ $hosts: HTMLElement[]; // All SWC ancestors [root, ..., parent]
36
36
  $firstHost: HTMLElement | null; // Top-most SWC ancestor
37
- $lastHost: HTMLElement | null; // Same as $parentHost (for backward compatibility or clarity)
37
+ $lastHost: HTMLElement | null; // Same as $host
38
+ $templateHost: HTMLTemplateElement | null | undefined;
38
39
  $appHost: SwcAppInterface | null;
39
40
  $appHosts: SwcAppInterface[];
40
41
  $firstAppHost: SwcAppInterface | null;