@dooboostore/simple-web-component 1.0.30 → 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 (82) hide show
  1. package/README.md +220 -1
  2. package/dist/cjs/decorators/elementDefine.js +3 -3
  3. package/dist/cjs/decorators/elementDefine.js.map +2 -2
  4. package/dist/cjs/elements/SwcAppEngine.js +3 -3
  5. package/dist/cjs/elements/SwcAppEngine.js.map +2 -2
  6. package/dist/cjs/elements/SwcAppMixin.js +0 -7
  7. package/dist/cjs/elements/SwcAppMixin.js.map +2 -2
  8. package/dist/cjs/elements/SwcAsync.js +35 -54
  9. package/dist/cjs/elements/SwcAsync.js.map +2 -2
  10. package/dist/cjs/elements/SwcChoose.js +2 -23
  11. package/dist/cjs/elements/SwcChoose.js.map +2 -2
  12. package/dist/cjs/elements/SwcIf.js +31 -60
  13. package/dist/cjs/elements/SwcIf.js.map +2 -2
  14. package/dist/cjs/elements/SwcLoop.js +20 -43
  15. package/dist/cjs/elements/SwcLoop.js.map +2 -2
  16. package/dist/cjs/elements/SwcSubTemplates.js +0 -27
  17. package/dist/cjs/elements/SwcSubTemplates.js.map +2 -2
  18. package/dist/cjs/elements/register.js +88 -92
  19. package/dist/cjs/elements/register.js.map +2 -2
  20. package/dist/cjs/router/ElementRouter.js +1 -8
  21. package/dist/cjs/router/ElementRouter.js.map +2 -2
  22. package/dist/cjs/types.js.map +1 -1
  23. package/dist/cjs/utils/Utils.js +24 -11
  24. package/dist/cjs/utils/Utils.js.map +2 -2
  25. package/dist/esm/decorators/elementDefine.js +3 -3
  26. package/dist/esm/decorators/elementDefine.js.map +2 -2
  27. package/dist/esm/elements/SwcAppEngine.js +3 -3
  28. package/dist/esm/elements/SwcAppEngine.js.map +2 -2
  29. package/dist/esm/elements/SwcAppMixin.js +0 -7
  30. package/dist/esm/elements/SwcAppMixin.js.map +2 -2
  31. package/dist/esm/elements/SwcAsync.js +35 -35
  32. package/dist/esm/elements/SwcAsync.js.map +2 -2
  33. package/dist/esm/elements/SwcChoose.js +2 -4
  34. package/dist/esm/elements/SwcChoose.js.map +2 -2
  35. package/dist/esm/elements/SwcIf.js +31 -41
  36. package/dist/esm/elements/SwcIf.js.map +2 -2
  37. package/dist/esm/elements/SwcLoop.js +20 -24
  38. package/dist/esm/elements/SwcLoop.js.map +2 -2
  39. package/dist/esm/elements/SwcSubTemplates.js +0 -8
  40. package/dist/esm/elements/SwcSubTemplates.js.map +2 -2
  41. package/dist/esm/elements/register.js +88 -92
  42. package/dist/esm/elements/register.js.map +2 -2
  43. package/dist/esm/router/ElementRouter.js +1 -8
  44. package/dist/esm/router/ElementRouter.js.map +2 -2
  45. package/dist/esm/types.js.map +1 -1
  46. package/dist/esm/utils/Utils.js +24 -11
  47. package/dist/esm/utils/Utils.js.map +2 -2
  48. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +27 -14
  49. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +2 -2
  50. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  51. package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
  52. package/dist/types/elements/SwcAsync.d.ts +1 -11
  53. package/dist/types/elements/SwcAsync.d.ts.map +1 -1
  54. package/dist/types/elements/SwcChoose.d.ts +1 -13
  55. package/dist/types/elements/SwcChoose.d.ts.map +1 -1
  56. package/dist/types/elements/SwcIf.d.ts +1 -13
  57. package/dist/types/elements/SwcIf.d.ts.map +1 -1
  58. package/dist/types/elements/SwcLoop.d.ts +1 -13
  59. package/dist/types/elements/SwcLoop.d.ts.map +1 -1
  60. package/dist/types/elements/SwcSubTemplates.d.ts +6 -6
  61. package/dist/types/elements/SwcSubTemplates.d.ts.map +1 -1
  62. package/dist/types/elements/register.d.ts.map +1 -1
  63. package/dist/types/router/ElementRouter.d.ts.map +1 -1
  64. package/dist/types/types.d.ts +3 -0
  65. package/dist/types/types.d.ts.map +1 -1
  66. package/dist/types/utils/Utils.d.ts +10 -0
  67. package/dist/types/utils/Utils.d.ts.map +1 -1
  68. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +27 -14
  69. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +2 -2
  70. package/package.json +3 -3
  71. package/src/decorators/elementDefine.ts +6 -3
  72. package/src/elements/SwcAppEngine.ts +3 -3
  73. package/src/elements/SwcAppMixin.ts +5 -26
  74. package/src/elements/SwcAsync.ts +43 -31
  75. package/src/elements/SwcChoose.ts +8 -3
  76. package/src/elements/SwcIf.ts +38 -38
  77. package/src/elements/SwcLoop.ts +24 -19
  78. package/src/elements/SwcSubTemplates.ts +6 -6
  79. package/src/elements/register.ts +107 -86
  80. package/src/router/ElementRouter.ts +1 -12
  81. package/src/types.ts +3 -1
  82. package/src/utils/Utils.ts +37 -20
@@ -159,52 +159,39 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
159
159
  this.render();
160
160
  }
161
161
 
162
- private _resolvePortal(): Element | null {
163
- const portalScript = this.getAttribute('on-get-portal');
164
- if (!portalScript) return this.parentElement;
165
-
166
- 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;
176
- }
177
-
178
162
  private _executeCloneCallback(attr: string, args: any) {
179
163
  const script = this.getAttribute(attr);
180
164
  if (!script) return;
181
165
  FunctionUtils.execute({ script, context: this, args });
182
166
  }
183
167
 
184
- @changedAttributeHost('on-get-value')
185
- private refreshValue() {
186
- const script = this.getAttribute('on-get-value');
187
- if (!script) {
168
+ @changedAttributeHost('value')
169
+ private refreshValue(valueAttr?: string | null) {
170
+ valueAttr ??= this.getAttribute('value');
171
+ if (valueAttr === null) {
188
172
  this.value = false;
189
173
  return;
190
174
  }
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
175
 
198
- if (res instanceof Promise) {
199
- 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;
200
186
  } else {
201
- this.value = res;
187
+ // 일반 문자열 값
188
+ this.value = Boolean(valueAttr);
202
189
  }
203
190
  }
204
191
 
205
192
  private render() {
206
193
  this.cleanup();
207
- const portal = this._resolvePortal();
194
+ const portal = this.parentElement;
208
195
  if (!portal) return;
209
196
 
210
197
  const win = this.ownerDocument?.defaultView || window;
@@ -228,18 +215,31 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
228
215
  const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
229
216
  const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0] };
230
217
 
218
+ // 부모 체인을 정상적으로 유지하기 위해 __swc_host 할당 제거
231
219
  this._nodes.forEach((node, nodeIdx) => {
232
- if (node instanceof HTMLElement) (node as any).__swc_host = this;
220
+ // if (node instanceof HTMLElement) (node as any).__swc_host = this; // 제거: 부모 체인 유지
233
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
+ }
234
238
  });
235
239
 
236
240
  this._executeCloneCallback('on-clone-nodes', groupArgs);
237
241
 
238
- if (portal === this.parentElement) {
239
- portal.insertBefore(clone, this.nextSibling);
240
- } else {
241
- portal.appendChild(clone);
242
- }
242
+ portal.insertBefore(clone, this.nextSibling);
243
243
  }
244
244
  }
245
245
 
@@ -269,21 +269,7 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
269
269
  this.render();
270
270
  }
271
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
272
 
283
- if (res instanceof HTMLElement) return res;
284
- if (typeof res === 'string') return win.document.querySelector(res);
285
- return this.parentElement;
286
- }
287
273
 
288
274
  private _executeCloneCallback(attr: string, args: any) {
289
275
  const script = this.getAttribute(attr);
@@ -296,13 +282,17 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
296
282
  this._nodeGroups = [];
297
283
  }
298
284
 
299
- @changedAttributeHost('on-get-value')
285
+ @changedAttributeHost('value')
300
286
  private refreshValue() {
301
- const script = this.getAttribute('on-get-value');
287
+ let script = this.getAttribute('value');
302
288
  if (!script) {
303
289
  this.value = [];
304
290
  return;
305
291
  }
292
+ // Parse {{ }} braces
293
+ if (script.startsWith('{{') && script.endsWith('}}')) {
294
+ script = script.slice(2, -2).trim();
295
+ }
306
296
  const win = this.ownerDocument?.defaultView || window;
307
297
  const res = FunctionUtils.executeReturn({
308
298
  script,
@@ -319,7 +309,7 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
319
309
 
320
310
  render() {
321
311
  this._cleanup();
322
- const portal = this._resolvePortal();
312
+ const portal = this.parentElement;
323
313
  if (!portal) return;
324
314
 
325
315
  const win = this.ownerDocument?.defaultView || window;
@@ -347,8 +337,23 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
347
337
  const groupArgs = { ...helperSet, $item: item, $index: index, $value: this._value, $nodes: nodes, $elements: elements, $firstElement: elements[0] };
348
338
 
349
339
  nodes.forEach((node, nodeIdx) => {
350
- if (node instanceof HTMLElement) (node as any).__swc_host = this;
351
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
+ }
352
357
  });
353
358
 
354
359
  this._executeCloneCallback('on-clone-nodes', groupArgs);
@@ -393,7 +398,7 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
393
398
  }
394
399
 
395
400
  @changedAttributeHost('value')
396
- private refresh(valueAttr?: string | null) {
401
+ refresh(valueAttr?: string | null) {
397
402
  valueAttr ??= this.getAttribute('value');
398
403
  if (valueAttr === null) {
399
404
  this.value = null;
@@ -404,8 +409,11 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
404
409
  if (valueAttr.startsWith('{{') && valueAttr.endsWith('}}')) {
405
410
  const script = valueAttr.slice(2, -2).trim();
406
411
  const win = this.ownerDocument?.defaultView || window;
407
- const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
412
+ const helperAndHostSet = SwcUtils.getHelperAndHostSet(win, SwcUtils.findNearestSwcHost(this));
413
+ // console.log('vvvvvvvvvvvvvvvvvv323', helperAndHostSet);
414
+ const result = FunctionUtils.executeReturn({ script, context: this, args: helperAndHostSet });
408
415
  this.value = result;
416
+ // console.log('----------vv', this.value);
409
417
  } else {
410
418
  // 일반 문자열 값
411
419
  this.value = valueAttr;
@@ -413,6 +421,7 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
413
421
  }
414
422
 
415
423
  private render() {
424
+ // console.log('-------------', this.value);
416
425
  const win = this.ownerDocument?.defaultView || window;
417
426
  const helperSet = SwcUtils.getHelperAndHostSet(win, this);
418
427
  const templates = Array.from(this.content.querySelectorAll('template'));
@@ -515,38 +524,38 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
515
524
  private _nodes: Node[] = [];
516
525
  private _value: any = null;
517
526
 
518
- private _resolvePortal(): Element | null {
519
- const portalScript = this.getAttribute('on-get-portal');
520
- if (!portalScript) return this.parentElement;
521
-
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
- });
528
-
529
- if (res instanceof HTMLElement) return res;
530
- if (typeof res === 'string') return win.document.querySelector(res);
531
- return this.parentElement;
532
- }
533
-
534
527
  private _executeCloneCallback(attr: string, args: any) {
535
528
  const script = this.getAttribute(attr);
536
529
  if (!script) return;
537
530
  FunctionUtils.execute({ script, context: this, args });
538
531
  }
539
532
 
540
- @changedAttributeHost('on-get-value')
541
- private refresh() {
542
- const script = this.getAttribute('on-get-value');
543
- if (!script) {
533
+ @changedAttributeHost('value')
534
+ private refresh(valueAttr?: string | null) {
535
+ valueAttr ??= this.getAttribute('value');
536
+ if (!valueAttr) {
544
537
  this.render('default');
545
538
  return;
546
539
  }
547
- const win = this.ownerDocument?.defaultView || window;
548
- const result = FunctionUtils.executeReturn({ script, context: this, args: SwcUtils.getHelperAndHostSet(win, this) });
549
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
+ }
557
+
558
+ private executeAsync(result: any) {
550
559
  if (result instanceof Promise) {
551
560
  this.render('loading');
552
561
  result
@@ -563,7 +572,7 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
563
572
 
564
573
  private render(state: string) {
565
574
  this.cleanup();
566
- const portal = this._resolvePortal();
575
+ const portal = this.parentElement;
567
576
  if (!portal) return;
568
577
 
569
578
  const win = this.ownerDocument?.defaultView || window;
@@ -576,18 +585,31 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
576
585
  const elements = this._nodes.filter(n => n.nodeType === 1) as HTMLElement[];
577
586
  const groupArgs = { ...helperSet, $value: this._value, $nodes: this._nodes, $elements: elements, $firstElement: elements[0], $state: state };
578
587
 
588
+ // 부모 체인을 정상적으로 유지하기 위해 __swc_host 할당 제거
579
589
  this._nodes.forEach((n, nodeIdx) => {
580
- if (n instanceof HTMLElement) (n as any).__swc_host = this;
590
+ // if (n instanceof HTMLElement) (n as any).__swc_host = this; // 제거: 부모 체인 유지
581
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
+ }
582
608
  });
583
609
 
584
610
  this._executeCloneCallback('on-clone-nodes', groupArgs);
585
611
 
586
- if (portal === this.parentElement) {
587
- portal.insertBefore(clone, this.nextSibling);
588
- } else {
589
- portal.appendChild(clone);
590
- }
612
+ portal.insertBefore(clone, this.nextSibling);
591
613
  }
592
614
  }
593
615
 
@@ -597,7 +619,6 @@ export const registerAllElements = (w: any): Record<string, Constructor<HTMLElem
597
619
  }
598
620
 
599
621
  connectedCallback() {
600
- console.log('[SwcAsync] connectedCallback called');
601
622
  this.refresh();
602
623
  }
603
624
  disconnectedCallback() {
@@ -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;
@@ -148,6 +148,27 @@ export const htmlArticleElement = (options?: { doc?: Document; attrs?: Record<st
148
148
  };
149
149
 
150
150
  export namespace SwcUtils {
151
+
152
+
153
+ export const parsePathPatternsSetAttributeString = (pathPattern: string[], path: string) => {
154
+ return JSON.stringify(SwcUtils.parsePathPatternsSet(pathPattern, path)).replace(/"/g, "'");
155
+ }
156
+
157
+ export const parsePathPatternsSet = (pathPattern: string[], path: string) => {
158
+ return {
159
+ path: path,
160
+ pathData: SwcUtils.parsePathPatterns(pathPattern, path)
161
+ };
162
+ };
163
+ export const parsePathPatterns = (pathPattern: string[], path: string) => {
164
+ for (const pattern of pathPattern) {
165
+ const data = SwcUtils.parsePathPattern(pattern, path || '/');
166
+ if (data !== null) {
167
+ return data;
168
+ }
169
+ }
170
+ };
171
+
151
172
  export const getHelperSet = (win: Window): HelperSet => {
152
173
  const doc: Document = win.document;
153
174
  return {
@@ -255,34 +276,30 @@ export namespace SwcUtils {
255
276
  export const findAllParentAppHostsDirect = (el: HTMLElement): SwcAppInterface[] => {
256
277
  const appHosts: SwcAppInterface[] = [];
257
278
 
258
- // Shadow DOM 내부의 depth를 정상적으로 탐색하기 위해
259
- // el.parentElement부터 시작하고, shadow root 경계를 만날 때만 host로 이동
260
- let current: HTMLElement | null = el.parentElement;
261
-
279
+ // el 자체부터 시작 (el이 app host일 수도 있음)
280
+ let current: HTMLElement | null = el;
281
+
262
282
  while (current) {
263
- // console.log('vvvvvvv');
264
- // alert(1)
265
- // debugger;
266
283
  const isAppHost =
267
- current.tagName.toLowerCase() === 'swc-app' ||
284
+ current.tagName.toLowerCase() === 'swc-app' ||
268
285
  current.getAttribute('is')?.startsWith('swc-app-');
269
286
  if (isAppHost) {
270
287
  appHosts.push(current as SwcAppInterface);
271
288
  }
272
- // Handle shadow DOM
273
- if (current.shadowRoot) {
274
- current = ((current.shadowRoot as any).host as HTMLElement).parentElement;
289
+
290
+ // 다음 부모 찾기
291
+ if (current.parentElement) {
292
+ // Light DOM: 정상적인 부모 요소
293
+ current = current.parentElement;
275
294
  } else {
276
- // 부모가 없으면 shadow root 경계일 수 있음
277
- if (!current.parentElement) {
278
- const parentNode = current.parentNode;
279
- if (parentNode instanceof ShadowRoot) {
280
- current = (parentNode as any).host as HTMLElement;
281
- } else {
282
- break;
283
- }
295
+ // parentElement가 없음 = Shadow root의 직접 자식일 수 있음
296
+ const root = current.getRootNode();
297
+ if (root instanceof ShadowRoot) {
298
+ // Shadow host로 올라가기
299
+ current = root.host as HTMLElement;
284
300
  } else {
285
- current = current.parentElement;
301
+ // Document root 도달
302
+ break;
286
303
  }
287
304
  }
288
305
  }