@dooboostore/simple-web-component 1.0.30 → 1.0.32

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 (93) hide show
  1. package/README.md +220 -1
  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 +29 -12
  7. package/dist/cjs/decorators/elementDefine.js.map +3 -3
  8. package/dist/cjs/elements/SwcAppEngine.js +3 -3
  9. package/dist/cjs/elements/SwcAppEngine.js.map +2 -2
  10. package/dist/cjs/elements/SwcAppMixin.js +1 -8
  11. package/dist/cjs/elements/SwcAppMixin.js.map +2 -2
  12. package/dist/cjs/elements/SwcAsync.js +71 -78
  13. package/dist/cjs/elements/SwcAsync.js.map +2 -2
  14. package/dist/cjs/elements/SwcChoose.js +66 -81
  15. package/dist/cjs/elements/SwcChoose.js.map +2 -2
  16. package/dist/cjs/elements/SwcIf.js +52 -96
  17. package/dist/cjs/elements/SwcIf.js.map +2 -2
  18. package/dist/cjs/elements/SwcLoop.js +60 -103
  19. package/dist/cjs/elements/SwcLoop.js.map +2 -2
  20. package/dist/cjs/elements/SwcSubTemplates.js +0 -27
  21. package/dist/cjs/elements/SwcSubTemplates.js.map +2 -2
  22. package/dist/cjs/elements/register.js +314 -343
  23. package/dist/cjs/elements/register.js.map +2 -2
  24. package/dist/cjs/router/ElementRouter.js +1 -8
  25. package/dist/cjs/router/ElementRouter.js.map +2 -2
  26. package/dist/cjs/types.js.map +1 -1
  27. package/dist/cjs/utils/Utils.js +31 -24
  28. package/dist/cjs/utils/Utils.js.map +2 -2
  29. package/dist/esm/decorators/applyStyle.js +1 -1
  30. package/dist/esm/decorators/applyStyle.js.map +2 -2
  31. package/dist/esm/decorators/attribute.js +18 -2
  32. package/dist/esm/decorators/attribute.js.map +2 -2
  33. package/dist/esm/decorators/elementDefine.js +27 -10
  34. package/dist/esm/decorators/elementDefine.js.map +3 -3
  35. package/dist/esm/elements/SwcAppEngine.js +3 -3
  36. package/dist/esm/elements/SwcAppEngine.js.map +2 -2
  37. package/dist/esm/elements/SwcAppMixin.js +1 -8
  38. package/dist/esm/elements/SwcAppMixin.js.map +2 -2
  39. package/dist/esm/elements/SwcAsync.js +72 -60
  40. package/dist/esm/elements/SwcAsync.js.map +2 -2
  41. package/dist/esm/elements/SwcChoose.js +67 -63
  42. package/dist/esm/elements/SwcChoose.js.map +2 -2
  43. package/dist/esm/elements/SwcIf.js +53 -78
  44. package/dist/esm/elements/SwcIf.js.map +2 -2
  45. package/dist/esm/elements/SwcLoop.js +61 -85
  46. package/dist/esm/elements/SwcLoop.js.map +2 -2
  47. package/dist/esm/elements/SwcSubTemplates.js +0 -8
  48. package/dist/esm/elements/SwcSubTemplates.js.map +2 -2
  49. package/dist/esm/elements/register.js +246 -275
  50. package/dist/esm/elements/register.js.map +2 -2
  51. package/dist/esm/router/ElementRouter.js +1 -8
  52. package/dist/esm/router/ElementRouter.js.map +2 -2
  53. package/dist/esm/types.js.map +1 -1
  54. package/dist/esm/utils/Utils.js +31 -24
  55. package/dist/esm/utils/Utils.js.map +2 -2
  56. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +2223 -2009
  57. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  58. package/dist/types/decorators/attribute.d.ts.map +1 -1
  59. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  60. package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
  61. package/dist/types/elements/SwcAsync.d.ts +1 -11
  62. package/dist/types/elements/SwcAsync.d.ts.map +1 -1
  63. package/dist/types/elements/SwcChoose.d.ts +1 -13
  64. package/dist/types/elements/SwcChoose.d.ts.map +1 -1
  65. package/dist/types/elements/SwcIf.d.ts +1 -13
  66. package/dist/types/elements/SwcIf.d.ts.map +1 -1
  67. package/dist/types/elements/SwcLoop.d.ts +1 -13
  68. package/dist/types/elements/SwcLoop.d.ts.map +1 -1
  69. package/dist/types/elements/SwcSubTemplates.d.ts +6 -6
  70. package/dist/types/elements/SwcSubTemplates.d.ts.map +1 -1
  71. package/dist/types/elements/register.d.ts.map +1 -1
  72. package/dist/types/router/ElementRouter.d.ts.map +1 -1
  73. package/dist/types/types.d.ts +11 -1
  74. package/dist/types/types.d.ts.map +1 -1
  75. package/dist/types/utils/Utils.d.ts +10 -0
  76. package/dist/types/utils/Utils.d.ts.map +1 -1
  77. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +2223 -2009
  78. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  79. package/package.json +3 -3
  80. package/src/decorators/applyStyle.ts +1 -1
  81. package/src/decorators/attribute.ts +35 -30
  82. package/src/decorators/elementDefine.ts +32 -10
  83. package/src/elements/SwcAppEngine.ts +3 -3
  84. package/src/elements/SwcAppMixin.ts +6 -27
  85. package/src/elements/SwcAsync.ts +75 -57
  86. package/src/elements/SwcChoose.ts +69 -77
  87. package/src/elements/SwcIf.ts +50 -75
  88. package/src/elements/SwcLoop.ts +56 -82
  89. package/src/elements/SwcSubTemplates.ts +6 -6
  90. package/src/elements/register.ts +234 -285
  91. package/src/router/ElementRouter.ts +1 -12
  92. package/src/types.ts +9 -2
  93. package/src/utils/Utils.ts +48 -37
@@ -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 _resolvePortal(): Element | null {
163
- const portalScript = this.getAttribute('on-get-portal');
164
- if (!portalScript) return this.parentElement;
165
-
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';
166
172
  const win = this.ownerDocument?.defaultView || window;
167
- const res = FunctionUtils.executeReturn({
168
- script: portalScript,
169
- context: this,
170
- args: SwcUtils.getHelperAndHostSet(win, this)
171
- });
172
-
173
- if (res instanceof HTMLElement) return res;
174
- if (typeof res === 'string') return win.document.querySelector(res);
175
- return this.parentElement;
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();
176
188
  }
177
189
 
178
- private _executeCloneCallback(attr: string, args: any) {
179
- const script = this.getAttribute(attr);
180
- if (!script) return;
181
- FunctionUtils.execute({ script, context: this, args });
190
+ @changedAttributeHost('value')
191
+ private setValue(valueAttr?: string | null) {
192
+ this.refresh();
182
193
  }
183
194
 
184
- @changedAttributeHost('on-get-value')
185
- private refreshValue() {
186
- const script = this.getAttribute('on-get-value');
187
- if (!script) {
188
- this.value = false;
189
- return;
190
- }
191
- const win = this.ownerDocument?.defaultView || window;
192
- const res = FunctionUtils.executeReturn({
193
- script,
194
- context: this,
195
- args: SwcUtils.getHelperAndHostSet(win, this)
196
- });
197
-
198
- if (res instanceof Promise) {
199
- res.then(v => (this.value = v)).catch(() => (this.value = false));
200
- } else {
201
- this.value = res;
202
- }
195
+ @changedAttributeHost('replace-wrap')
196
+ private setReplaceWrap(replaceWrapAttr?: string | null) {
197
+ this.refresh();
203
198
  }
204
199
 
205
- private render() {
200
+ refresh(value = this._value) {
206
201
  this.cleanup();
207
- const portal = this._resolvePortal();
208
- if (!portal) return;
202
+ const portal = this.parentElement;
203
+ if (!portal || !value) return;
209
204
 
210
205
  const win = this.ownerDocument?.defaultView || window;
211
206
  const helperSet = SwcUtils.getHelperAndHostSet(win, this);
212
207
 
213
- let targetTpl: HTMLTemplateElement | null = null;
214
- if (this._value) {
215
- targetTpl = this;
216
- } else {
217
- targetTpl = this.content.querySelector('template[is="swc-default"]') as HTMLTemplateElement;
218
- }
219
-
220
- if (targetTpl) {
221
- const clone = targetTpl.content.cloneNode(true) as DocumentFragment;
222
- // If it's the host template, remove sub-templates from clone
223
- if (targetTpl === this) {
224
- clone.querySelectorAll('template[is^="swc-"]').forEach(t => t.remove());
225
- }
226
-
227
- this._nodes = Array.from(clone.childNodes);
228
- const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
229
- const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0] };
208
+ let htmlString = this.innerHTML;
209
+ const groupArgs = { ...helperSet, $value: value };
230
210
 
231
- this._nodes.forEach((node, nodeIdx) => {
232
- if (node instanceof HTMLElement) (node as any).__swc_host = this;
233
- this._executeCloneCallback('on-clone-node', { ...groupArgs, $node: node, $nodeIndex: nodeIdx, $isElement: node.nodeType === 1 });
234
- });
211
+ htmlString = this.replaceExpressions(htmlString, groupArgs, this, this._replaceWrap);
235
212
 
236
- 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;
237
216
 
238
- if (portal === this.parentElement) {
239
- portal.insertBefore(clone, this.nextSibling);
240
- } else {
241
- portal.appendChild(clone);
242
- }
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,184 +230,173 @@ 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();
270
- }
271
-
272
- private _resolvePortal(): Element | null {
273
- const portalScript = this.getAttribute('on-get-portal');
274
- if (!portalScript) return this.parentElement;
275
-
276
- const win = this.ownerDocument?.defaultView || window;
277
- const res = FunctionUtils.executeReturn({
278
- script: portalScript,
279
- context: this,
280
- args: SwcUtils.getHelperAndHostSet(win, this)
281
- });
282
-
283
- if (res instanceof HTMLElement) return res;
284
- if (typeof res === 'string') return win.document.querySelector(res);
285
- return this.parentElement;
245
+ // this._value = Array.isArray(nv) ? [...nv] : [];
246
+ this.refresh(nv);
286
247
  }
287
248
 
288
- private _executeCloneCallback(attr: string, args: any) {
289
- const script = this.getAttribute(attr);
290
- if (!script) return;
291
- FunctionUtils.execute({ script, context: this, args });
249
+ @changedAttributeHost('value')
250
+ private setValue() {
251
+ this.refresh();
292
252
  }
293
253
 
294
- private _cleanup() {
295
- this._nodeGroups.forEach(nodes => nodes.forEach(n => n.parentElement?.removeChild(n)));
296
- this._nodeGroups = [];
254
+ @changedAttributeHost('replace-wrap')
255
+ private setReplaceWrap() {
256
+ this.refresh();
297
257
  }
298
258
 
299
- @changedAttributeHost('on-get-value')
300
- private refreshValue() {
301
- const script = this.getAttribute('on-get-value');
302
- if (!script) {
303
- this.value = [];
304
- return;
305
- }
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';
306
263
  const win = this.ownerDocument?.defaultView || window;
307
- const res = FunctionUtils.executeReturn({
308
- script,
309
- context: this,
310
- args: SwcUtils.getHelperAndHostSet(win, this)
311
- });
312
264
 
313
- if (res instanceof Promise) {
314
- res.then(v => (this.value = v)).catch(() => (this.value = []));
315
- } else {
316
- 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
+ }
317
277
  }
278
+ return ae.getUnprocessedTemplate();
318
279
  }
319
280
 
320
- render() {
321
- this._cleanup();
322
- const portal = this._resolvePortal();
281
+ refresh(value = this._value) {
282
+ this.cleanup();
283
+ if (value?.length<=0) return;
284
+ const portal = this.parentElement;
323
285
  if (!portal) return;
324
286
 
325
287
  const win = this.ownerDocument?.defaultView || window;
326
288
  const helperSet = SwcUtils.getHelperAndHostSet(win, this);
327
289
  const fragment = win.document.createDocumentFragment();
328
290
 
329
- if (this._value.length === 0) {
330
- const defaultTpl = this.content.querySelector('template[is="swc-default"]') as HTMLTemplateElement;
331
- if (defaultTpl) {
332
- const clone = defaultTpl.content.cloneNode(true);
333
- const nodes = Array.from(clone.childNodes);
334
- this._nodeGroups.push(nodes);
335
- fragment.appendChild(clone);
336
- }
337
- } else {
338
- this._value.forEach((item, index) => {
339
- const clone = this.content.cloneNode(true) as DocumentFragment;
340
- // Filter out sub-templates
341
- const subTemplates = clone.querySelectorAll('template[is^="swc-"]');
342
- subTemplates.forEach(t => t.remove());
343
-
344
- const nodes = Array.from(clone.childNodes);
345
- const elements = nodes.filter(n => n.nodeType === 1) as HTMLElement[];
291
+ value.forEach((item, index) => {
292
+ let htmlString = this.innerHTML;
293
+ const groupArgs = { ...helperSet, $item: item, $index: index, $value: this._value };
346
294
 
347
- const groupArgs = { ...helperSet, $item: item, $index: index, $value: this._value, $nodes: nodes, $elements: elements, $firstElement: elements[0] };
295
+ htmlString = this.replaceExpressions(htmlString, groupArgs, this, this._replaceWrap);
348
296
 
349
- nodes.forEach((node, nodeIdx) => {
350
- if (node instanceof HTMLElement) (node as any).__swc_host = this;
351
- this._executeCloneCallback('on-clone-node', { ...groupArgs, $node: node, $nodeIndex: nodeIdx, $isElement: node.nodeType === 1 });
352
- });
297
+ const temp = this.ownerDocument?.createElement('template') || document.createElement('template');
298
+ temp.innerHTML = htmlString;
353
299
 
354
- this._executeCloneCallback('on-clone-nodes', groupArgs);
355
- this._nodeGroups.push(nodes);
356
- fragment.appendChild(clone);
357
- });
358
- }
300
+ const content = temp.content;
301
+ const nodes = Array.from(content.childNodes);
302
+ this._nodeGroups.push(nodes);
303
+ fragment.appendChild(content);
304
+ });
359
305
 
360
- if (portal === this.parentElement) {
361
- portal.insertBefore(fragment, this.nextSibling);
362
- } else {
363
- portal.appendChild(fragment);
364
- }
306
+ portal.insertBefore(fragment, this.nextSibling);
365
307
  }
366
308
 
367
- connectedCallback() {
368
- this.refreshValue();
309
+ private cleanup() {
310
+ this._nodeGroups.forEach(nodes => nodes.forEach(n => n.parentElement?.removeChild(n)));
311
+ this._nodeGroups = [];
369
312
  }
313
+
370
314
  disconnectedCallback() {
371
- this._cleanup();
315
+ this.cleanup();
372
316
  }
373
317
  }
374
318
 
375
319
  @elementDefine('swc-choose', { extends: 'template', window: w })
376
320
  class SwcChoose extends HTMLTemplateElement {
321
+ @attributeHost('value')
322
+ _value: any;
323
+
324
+ @attributeHost('replace-wrap')
325
+ _replaceWrap: string | null;
326
+
377
327
  private _nodes: Node[] = [];
378
- private _value: any = null;
379
328
  private _previousTemplate: HTMLTemplateElement | null = null;
380
329
 
381
330
  get value(): any {
382
331
  return this._value;
383
332
  }
333
+
384
334
  set value(nv: any) {
385
- this._value = nv;
386
- this.render();
335
+ this.refresh(nv);
387
336
  }
388
337
 
389
- private _executeCloneCallback(attr: string, args: any) {
390
- const script = this.getAttribute(attr);
391
- if (!script) return;
392
- FunctionUtils.execute({ script, context: this, args });
338
+ @changedAttributeHost('value')
339
+ private setValue() {
340
+ this.refresh();
393
341
  }
394
342
 
395
- @changedAttributeHost('value')
396
- private refresh(valueAttr?: string | null) {
397
- valueAttr ??= this.getAttribute('value');
398
- if (valueAttr === null) {
399
- this.value = null;
400
- return;
401
- }
343
+ @changedAttributeHost('replace-wrap')
344
+ private setReplaceWrap(replaceWrapAttr?: string | null) {
345
+ this.refresh();
346
+ }
402
347
 
403
- // {{ }} 브레이스로 감싸진 값은 스크립트 실행
404
- if (valueAttr.startsWith('{{') && valueAttr.endsWith('}}')) {
405
- const script = valueAttr.slice(2, -2).trim();
406
- const win = this.ownerDocument?.defaultView || window;
407
- const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
408
- this.value = result;
409
- } else {
410
- // 일반 문자열 값
411
- this.value = valueAttr;
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
+ }
412
366
  }
367
+ return ae.getUnprocessedTemplate();
413
368
  }
414
369
 
415
- private render() {
370
+ refresh(value = this._value) {
371
+ const portal = this.parentElement;
372
+ if (!portal) return;
373
+
416
374
  const win = this.ownerDocument?.defaultView || window;
417
375
  const helperSet = SwcUtils.getHelperAndHostSet(win, this);
376
+ const groupArgs = { ...helperSet, $value: value };
377
+
418
378
  const templates = Array.from(this.content.querySelectorAll('template'));
419
379
  let selected: HTMLTemplateElement | null = null;
420
380
 
421
381
  for (const t of templates) {
422
382
  if (t.getAttribute('is') === 'swc-when') {
383
+ const attrVal = t.getAttribute('value') || '';
423
384
  let matches = false;
424
- const value = t.getAttribute('value') || '';
425
385
 
426
- // {{ }} 브레이스로 감싸진 값은 스크립트 실행
427
- if (value.startsWith('{{') && value.endsWith('}}')) {
428
- 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);
429
392
  const result = FunctionUtils.executeReturn({
430
- script,
393
+ script: decodedScript,
431
394
  context: t,
432
- args: { ...helperSet, $value: this._value }
395
+ args: groupArgs
433
396
  });
434
- // console.log('rrrrrrrrrrrrrr', result);
435
- matches = result === true;
397
+ matches = !!result;
436
398
  } else {
437
- // 일반 문자열 비교 (기존 방식)
438
- matches = String(value) === String(this._value);
399
+ matches = String(attrVal) === String(value);
439
400
  }
440
401
 
441
402
  if (matches) {
@@ -447,11 +408,12 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
447
408
 
448
409
  const target = selected || (this.content.querySelector('template[is="swc-otherwise"]') as HTMLTemplateElement);
449
410
 
450
- // skip-if-same attribute가 있고 이전과 같은 template이면 렌더링 스킵
451
- // 최상위 skip-if-same 또는 각 when/otherwise의 skip-if-same 체크
452
- const targetHasSkipIfSame = target?.hasAttribute('skip-if-same');
453
- const shouldSkip = (this.hasAttribute('skip-if-same') || targetHasSkipIfSame) && this._previousTemplate === target;
454
- if (shouldSkip) {
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) {
455
417
  return;
456
418
  }
457
419
 
@@ -459,41 +421,15 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
459
421
  this._previousTemplate = target;
460
422
 
461
423
  if (target) {
462
- const clone = target.content.cloneNode(true) as DocumentFragment;
463
- this._nodes = Array.from(clone.childNodes);
464
- const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
465
- const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0] };
466
-
467
- // 복제된 최상위 요소들은 SwcChoose의 직접 자식이 될 것이므로 __swc_host 설정 안 함
468
- this._nodes.forEach((n, nodeIdx) => {
469
- // if (n instanceof HTMLElement) (n as any).__swc_host = this; // 제거: 부모 체인 유지
470
- this._executeCloneCallback('on-clone-node', { ...groupArgs, $node: n, $nodeIndex: nodeIdx, $isElement: n.nodeType === 1 });
471
-
472
- // Attribute {{ }} 치환 - HTML string 차원에서 동적 치환
473
- if (n instanceof HTMLElement) {
474
- Array.from(n.attributes).forEach(attr => {
475
- const value = attr.value || '';
476
- if (value.startsWith('{{') && value.endsWith('}}')) {
477
- const script = value.slice(2, -2).trim();
478
- const result = FunctionUtils.executeReturn({
479
- script,
480
- context: n,
481
- args: { ...groupArgs, $value: this._value }
482
- });
483
- n.setAttribute(attr.name, String(result));
484
- }
485
- });
486
- }
487
- });
424
+ let htmlString = target.innerHTML;
425
+ htmlString = this.replaceExpressions(htmlString, groupArgs, this, this._replaceWrap);
488
426
 
489
- 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;
490
430
 
491
- this.parentElement?.insertBefore(clone, this.nextSibling);
492
- // if (portal === this.parentElement) {
493
- // portal.insertBefore(clone, this.nextSibling);
494
- // } else {
495
- // portal.appendChild(clone);
496
- // }
431
+ this._nodes = Array.from(processedFragment.childNodes);
432
+ portal.insertBefore(processedFragment, this.nextSibling);
497
433
  }
498
434
  }
499
435
 
@@ -502,9 +438,6 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
502
438
  this._nodes = [];
503
439
  }
504
440
 
505
- connectedCallback() {
506
- this.refresh();
507
- }
508
441
  disconnectedCallback() {
509
442
  this.cleanup();
510
443
  }
@@ -512,58 +445,84 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
512
445
 
513
446
  @elementDefine('swc-async', { extends: 'template', window: w })
514
447
  class SwcAsync extends HTMLTemplateElement {
515
- private _nodes: Node[] = [];
516
- private _value: any = null;
448
+ @attributeHost('value')
449
+ _value: any;
517
450
 
518
- private _resolvePortal(): Element | null {
519
- const portalScript = this.getAttribute('on-get-portal');
520
- if (!portalScript) return this.parentElement;
451
+ @attributeHost('replace-wrap')
452
+ _replaceWrap: string | null;
521
453
 
522
- const win = this.ownerDocument?.defaultView || window;
523
- const res = FunctionUtils.executeReturn({
524
- script: portalScript,
525
- context: this,
526
- args: SwcUtils.getHelperAndHostSet(win, this)
527
- });
454
+ resultValue?: any;
455
+
456
+ get value(): boolean {
457
+ return this._value;
458
+ }
528
459
 
529
- if (res instanceof HTMLElement) return res;
530
- if (typeof res === 'string') return win.document.querySelector(res);
531
- return this.parentElement;
460
+ set value(nv: any) {
461
+ this.executeAsync(nv);
532
462
  }
533
463
 
534
- private _executeCloneCallback(attr: string, args: any) {
535
- const script = this.getAttribute(attr);
536
- if (!script) return;
537
- FunctionUtils.execute({ script, context: this, args });
464
+ private _nodes: Node[] = [];
465
+
466
+ @changedAttributeHost('value')
467
+ private setValue(valueAttr?: string | null) {
468
+ this.executeAsync(this._value);
538
469
  }
539
470
 
540
- @changedAttributeHost('on-get-value')
541
- private refresh() {
542
- const script = this.getAttribute('on-get-value');
543
- if (!script) {
544
- this.render('default');
471
+ @changedAttributeHost('replace-wrap')
472
+ private setReplaceWrap() {
473
+ this.executeAsync(this._value);
474
+ }
475
+
476
+ private executeAsync(result: any = this._value) {
477
+ if (result === undefined || result === null) {
478
+ this.refresh('default');
545
479
  return;
546
480
  }
547
- const win = this.ownerDocument?.defaultView || window;
548
- const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
549
481
 
550
482
  if (result instanceof Promise) {
551
- this.render('loading');
483
+ this.refresh('loading');
552
484
  result
553
485
  .then(v => {
554
- this._value = v;
555
- this.render('success');
486
+ this.resultValue = v;
487
+ this.refresh('success');
556
488
  })
557
- .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
+ });
558
494
  } else {
559
- this._value = result;
560
- this.render('success');
495
+ this.resultValue = result;
496
+ this.refresh('success');
497
+ }
498
+ }
499
+
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
+ }
561
518
  }
519
+ return ae.getUnprocessedTemplate();
562
520
  }
563
521
 
564
- private render(state: string) {
522
+ refresh(state: string = 'default') {
523
+ console.log('rrrrrrrrrrrrrrrrr', state);
565
524
  this.cleanup();
566
- const portal = this._resolvePortal();
525
+ const portal = this.parentElement;
567
526
  if (!portal) return;
568
527
 
569
528
  const win = this.ownerDocument?.defaultView || window;
@@ -571,23 +530,17 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
571
530
 
572
531
  const t = this.content.querySelector(`template[is="swc-${state}"]`) as HTMLTemplateElement;
573
532
  if (t) {
574
- const clone = t.content.cloneNode(true) as DocumentFragment;
575
- this._nodes = Array.from(clone.childNodes);
576
- const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
577
- const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0], $state: state };
578
-
579
- this._nodes.forEach((n, nodeIdx) => {
580
- if (n instanceof HTMLElement) (n as any).__swc_host = this;
581
- this._executeCloneCallback('on-clone-node', { ...groupArgs, $node: n, $nodeIndex: nodeIdx, $isElement: n.nodeType === 1 });
582
- });
583
-
584
- this._executeCloneCallback('on-clone-nodes', groupArgs);
585
-
586
- if (portal === this.parentElement) {
587
- portal.insertBefore(clone, this.nextSibling);
588
- } else {
589
- portal.appendChild(clone);
590
- }
533
+ let htmlString = t.innerHTML;
534
+ const groupArgs = { ...helperSet, $value: this._value, $state: state };
535
+
536
+ htmlString = this.replaceExpressions(htmlString, groupArgs, this, this._replaceWrap);
537
+
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);
591
544
  }
592
545
  }
593
546
 
@@ -596,10 +549,6 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
596
549
  this._nodes = [];
597
550
  }
598
551
 
599
- connectedCallback() {
600
- console.log('[SwcAsync] connectedCallback called');
601
- this.refresh();
602
- }
603
552
  disconnectedCallback() {
604
553
  this.cleanup();
605
554
  }