@dooboostore/simple-web-component 1.0.15 → 1.0.17

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 +275 -91
  2. package/dist/cjs/decorators/addEventListener.js +2 -3
  3. package/dist/cjs/decorators/addEventListener.js.map +2 -2
  4. package/dist/cjs/decorators/elementDefine.js +161 -34
  5. package/dist/cjs/decorators/elementDefine.js.map +3 -3
  6. package/dist/cjs/decorators/emitCustomEvent.js +2 -14
  7. package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
  8. package/dist/cjs/decorators/query.js +47 -36
  9. package/dist/cjs/decorators/query.js.map +2 -2
  10. package/dist/cjs/decorators/queryAll.js +42 -36
  11. package/dist/cjs/decorators/queryAll.js.map +2 -2
  12. package/dist/cjs/elements/SwcAppEngine.js +2 -1
  13. package/dist/cjs/elements/SwcAppEngine.js.map +2 -2
  14. package/dist/cjs/elements/SwcAsync.js +188 -0
  15. package/dist/cjs/elements/SwcAsync.js.map +7 -0
  16. package/dist/cjs/elements/SwcChoose.js +155 -0
  17. package/dist/cjs/elements/SwcChoose.js.map +7 -0
  18. package/dist/cjs/elements/SwcIf.js +167 -0
  19. package/dist/cjs/elements/SwcIf.js.map +7 -0
  20. package/dist/cjs/elements/SwcLoop.js +246 -0
  21. package/dist/cjs/elements/SwcLoop.js.map +7 -0
  22. package/dist/cjs/elements/SwcSubTemplates.js +65 -0
  23. package/dist/cjs/elements/SwcSubTemplates.js.map +7 -0
  24. package/dist/cjs/elements/index.js +7 -0
  25. package/dist/cjs/elements/index.js.map +2 -2
  26. package/dist/cjs/index.js +1 -1
  27. package/dist/cjs/index.js.map +2 -2
  28. package/dist/cjs/types.js +1 -0
  29. package/dist/cjs/types.js.map +2 -2
  30. package/dist/cjs/utils/Utils.js +92 -162
  31. package/dist/cjs/utils/Utils.js.map +3 -3
  32. package/dist/esm/decorators/addEventListener.js +2 -3
  33. package/dist/esm/decorators/addEventListener.js.map +2 -2
  34. package/dist/esm/decorators/elementDefine.js +161 -34
  35. package/dist/esm/decorators/elementDefine.js.map +3 -3
  36. package/dist/esm/decorators/emitCustomEvent.js +2 -14
  37. package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
  38. package/dist/esm/decorators/query.js +47 -36
  39. package/dist/esm/decorators/query.js.map +2 -2
  40. package/dist/esm/decorators/queryAll.js +42 -36
  41. package/dist/esm/decorators/queryAll.js.map +2 -2
  42. package/dist/esm/elements/SwcAppEngine.js +2 -1
  43. package/dist/esm/elements/SwcAppEngine.js.map +2 -2
  44. package/dist/esm/elements/SwcAsync.js +169 -0
  45. package/dist/esm/elements/SwcAsync.js.map +7 -0
  46. package/dist/esm/elements/SwcChoose.js +136 -0
  47. package/dist/esm/elements/SwcChoose.js.map +7 -0
  48. package/dist/esm/elements/SwcIf.js +148 -0
  49. package/dist/esm/elements/SwcIf.js.map +7 -0
  50. package/dist/esm/elements/SwcLoop.js +227 -0
  51. package/dist/esm/elements/SwcLoop.js.map +7 -0
  52. package/dist/esm/elements/SwcSubTemplates.js +46 -0
  53. package/dist/esm/elements/SwcSubTemplates.js.map +7 -0
  54. package/dist/esm/elements/index.js +7 -0
  55. package/dist/esm/elements/index.js.map +2 -2
  56. package/dist/esm/index.js +1 -1
  57. package/dist/esm/index.js.map +1 -1
  58. package/dist/esm/types.js +1 -0
  59. package/dist/esm/types.js.map +2 -2
  60. package/dist/esm/utils/Utils.js +92 -162
  61. package/dist/esm/utils/Utils.js.map +3 -3
  62. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +1150 -326
  63. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  64. package/dist/types/decorators/addEventListener.d.ts +10 -4
  65. package/dist/types/decorators/addEventListener.d.ts.map +1 -1
  66. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  67. package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
  68. package/dist/types/decorators/query.d.ts +4 -5
  69. package/dist/types/decorators/query.d.ts.map +1 -1
  70. package/dist/types/decorators/queryAll.d.ts +4 -5
  71. package/dist/types/decorators/queryAll.d.ts.map +1 -1
  72. package/dist/types/elements/SwcAppEngine.d.ts.map +1 -1
  73. package/dist/types/elements/SwcAsync.d.ts +16 -0
  74. package/dist/types/elements/SwcAsync.d.ts.map +1 -0
  75. package/dist/types/elements/SwcChoose.d.ts +13 -0
  76. package/dist/types/elements/SwcChoose.d.ts.map +1 -0
  77. package/dist/types/elements/SwcIf.d.ts +15 -0
  78. package/dist/types/elements/SwcIf.d.ts.map +1 -0
  79. package/dist/types/elements/SwcLoop.d.ts +20 -0
  80. package/dist/types/elements/SwcLoop.d.ts.map +1 -0
  81. package/dist/types/elements/SwcSubTemplates.d.ts +13 -0
  82. package/dist/types/elements/SwcSubTemplates.d.ts.map +1 -0
  83. package/dist/types/elements/index.d.ts +7 -0
  84. package/dist/types/elements/index.d.ts.map +1 -1
  85. package/dist/types/index.d.ts +1 -1
  86. package/dist/types/index.d.ts.map +1 -1
  87. package/dist/types/types.d.ts +11 -1
  88. package/dist/types/types.d.ts.map +1 -1
  89. package/dist/types/utils/Utils.d.ts +19 -21
  90. package/dist/types/utils/Utils.d.ts.map +1 -1
  91. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +1150 -326
  92. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  93. package/package.json +10 -3
  94. package/src/decorators/addEventListener.ts +11 -7
  95. package/src/decorators/elementDefine.ts +165 -39
  96. package/src/decorators/emitCustomEvent.ts +5 -21
  97. package/src/decorators/query.ts +46 -41
  98. package/src/decorators/queryAll.ts +45 -41
  99. package/src/elements/SwcAppEngine.ts +2 -1
  100. package/src/elements/SwcAsync.ts +163 -0
  101. package/src/elements/SwcChoose.ts +127 -0
  102. package/src/elements/SwcIf.ts +134 -0
  103. package/src/elements/SwcLoop.ts +215 -0
  104. package/src/elements/SwcSubTemplates.ts +19 -0
  105. package/src/elements/index.ts +8 -3
  106. package/src/index.ts +1 -1
  107. package/src/types.ts +15 -5
  108. package/src/utils/Utils.ts +111 -197
@@ -0,0 +1,215 @@
1
+ import { elementDefine } from '../decorators/elementDefine';
2
+ import { changedAttribute } from '../decorators/changedAttribute';
3
+ import { SwcUtils } from '../utils/Utils';
4
+ import { FunctionUtils } from '@dooboostore/core/function/FunctionUtils';
5
+
6
+ @elementDefine({ name: 'swc-loop', extends: 'template' })
7
+ export class SwcLoop extends HTMLTemplateElement {
8
+ private _value: any[] = [];
9
+ private _nodes: (Node[] | null)[] = [];
10
+ private _defaultNodes: Node[] = [];
11
+
12
+ get value(): any[] {
13
+ return this._value;
14
+ }
15
+
16
+ set value(nv: any[]) {
17
+ this._value = Array.isArray(nv) ? [...nv] : [];
18
+ this.render();
19
+ }
20
+
21
+ getNodes(index: number): Node[] | null {
22
+ return this._nodes[index] || null;
23
+ }
24
+
25
+ private _getTarget(): Element | DocumentFragment | null {
26
+ const script = this.getAttribute('on-get-portal');
27
+ if (!script) return null;
28
+ try {
29
+ const res = FunctionUtils.executeReturn({
30
+ script,
31
+ context: this,
32
+ args: SwcUtils.getHelperAndHostSet(window, this)
33
+ });
34
+ if (typeof res === 'string') return document.querySelector(res);
35
+ if (res instanceof Node) return res as any;
36
+ } catch (e) {
37
+ console.error('[SwcLoop] Portal target error:', e);
38
+ }
39
+ return null;
40
+ }
41
+
42
+ append(...items: any[]) {
43
+ if (!items.length || !this.parentElement) return;
44
+ if (this._defaultNodes.length > 0) this._cleanupDefault();
45
+
46
+ const fragment = document.createDocumentFragment();
47
+ const startIndex = this._value.length;
48
+
49
+ items.forEach((item, i) => {
50
+ const index = startIndex + i;
51
+ const nodes = this._createItemNodes(item, index);
52
+ if (nodes) {
53
+ this._nodes.push(nodes);
54
+ nodes.forEach(n => fragment.appendChild(n));
55
+ this._value.push(item);
56
+ } else {
57
+ this._nodes.push(null);
58
+ this._value.push(null);
59
+ }
60
+ });
61
+
62
+ const target = this._getTarget();
63
+ if (target) {
64
+ target.appendChild(fragment);
65
+ } else {
66
+ const lastValidIdx = this._nodes.length - items.length - 1;
67
+ let refNode: Node = this;
68
+ for (let i = lastValidIdx; i >= 0; i--) {
69
+ const group = this._nodes[i];
70
+ if (group && group.length > 0) {
71
+ refNode = group[group.length - 1];
72
+ break;
73
+ }
74
+ }
75
+ this.parentElement.insertBefore(fragment, refNode.nextSibling);
76
+ }
77
+ }
78
+
79
+ remove(index?: number) {
80
+ const targetIdx = index !== undefined ? index : this._nodes.length - 1;
81
+ if (targetIdx < 0 || targetIdx >= this._nodes.length) return;
82
+
83
+ const nodes = this._nodes[targetIdx];
84
+ if (nodes) {
85
+ nodes.forEach(n => {
86
+ if (n.parentElement) n.parentElement.removeChild(n);
87
+ });
88
+ this._nodes[targetIdx] = null;
89
+ this._value[targetIdx] = null;
90
+ }
91
+
92
+ const activeRows = this._value.filter(v => v !== null);
93
+ if (activeRows.length === 0) {
94
+ this.render();
95
+ }
96
+ }
97
+
98
+ private _createItemNodes(item: any, index: number): Node[] {
99
+ const clone = this.content.cloneNode(true) as DocumentFragment;
100
+ Array.from(clone.querySelectorAll('template[is="swc-default"]')).forEach(t => t.remove());
101
+
102
+ const nodes = Array.from(clone.childNodes);
103
+ const elements = nodes.filter(n => n.nodeType === 1) as HTMLElement[];
104
+
105
+ const groupCtx = {
106
+ $index: index,
107
+ $item: item,
108
+ $nodes: nodes,
109
+ $elements: elements,
110
+ $firstNode: nodes[0],
111
+ $lastNode: nodes[nodes.length - 1],
112
+ $firstElement: elements[0],
113
+ $lastElement: elements[elements.length - 1],
114
+ $isFirst: index === 0,
115
+ $isLast: index === this._value.length - 1,
116
+ $isEven: index % 2 === 0,
117
+ $isOdd: index % 2 !== 0,
118
+ $count: this._value.length
119
+ };
120
+
121
+ this._executeCallback('on-clone-nodes', groupCtx);
122
+ nodes.forEach((node, nodeIndex) => {
123
+ if (node instanceof HTMLElement) (node as any).__swc_host = this;
124
+ this._executeCallback('on-clone-node', { ...groupCtx, $node: node, $nodeIndex: nodeIndex, $isElement: node.nodeType === 1 });
125
+ });
126
+
127
+ return nodes;
128
+ }
129
+
130
+ private _executeCallback(attrName: string, extraArgs: Record<string, any>) {
131
+ const script = this.getAttribute(attrName);
132
+ if (!script) return;
133
+ const helperHostSet = SwcUtils.getHelperAndHostSet(window, this);
134
+ try {
135
+ FunctionUtils.execute({ script, context: this, args: { ...helperHostSet, ...extraArgs } });
136
+ } catch (e) {
137
+ console.error(`[SwcLoop] Error in ${attrName}:`, e);
138
+ }
139
+ }
140
+
141
+ @changedAttribute('on-get-value')
142
+ private refreshValue(newValue?: string | null) {
143
+ const script = newValue !== undefined ? newValue : this.getAttribute('on-get-value');
144
+ if (!script) {
145
+ this.value = [];
146
+ return;
147
+ }
148
+ const helperHostSet = SwcUtils.getHelperAndHostSet(window, this);
149
+ try {
150
+ const res = FunctionUtils.executeReturn({ script, context: this, args: helperHostSet });
151
+ if (Array.isArray(res)) {
152
+ this.value = res;
153
+ }
154
+ } catch (e) {
155
+ console.error('[SwcLoop] Failed to get value from on-get-value script:', e);
156
+ this.value = [];
157
+ }
158
+ }
159
+
160
+ render() {
161
+ this._cleanup();
162
+ if (!this.parentElement) return;
163
+
164
+ const activeRows = this._value.filter(v => v !== null);
165
+ const target = this._getTarget();
166
+
167
+ if (activeRows.length === 0) {
168
+ const t = this.content.querySelector('template[is="swc-default"]') as HTMLTemplateElement;
169
+ if (t) {
170
+ const clone = t.content.cloneNode(true) as DocumentFragment;
171
+ this._defaultNodes = Array.from(clone.childNodes);
172
+ this._defaultNodes.forEach(n => {
173
+ if (n instanceof HTMLElement) (n as any).__swc_host = this;
174
+ });
175
+ if (target) target.appendChild(clone);
176
+ else this.parentElement.insertBefore(clone, this.nextSibling);
177
+ }
178
+ return;
179
+ }
180
+
181
+ const fragment = document.createDocumentFragment();
182
+ this._value.forEach((item, index) => {
183
+ if (item === null) {
184
+ this._nodes.push(null);
185
+ return;
186
+ }
187
+ const nodes = this._createItemNodes(item, index);
188
+ this._nodes.push(nodes);
189
+ nodes.forEach(n => fragment.appendChild(n));
190
+ });
191
+
192
+ if (target) target.appendChild(fragment);
193
+ else this.parentElement.insertBefore(fragment, this.nextSibling);
194
+ }
195
+
196
+ private _cleanup() {
197
+ this._nodes.forEach(group => {
198
+ if (group) group.forEach(node => node.parentElement?.removeChild(node));
199
+ });
200
+ this._nodes = [];
201
+ this._cleanupDefault();
202
+ }
203
+
204
+ private _cleanupDefault() {
205
+ this._defaultNodes.forEach(n => n.parentElement?.removeChild(n));
206
+ this._defaultNodes = [];
207
+ }
208
+
209
+ connectedCallback() {
210
+ this.refreshValue();
211
+ }
212
+ disconnectedCallback() {
213
+ this._cleanup();
214
+ }
215
+ }
@@ -0,0 +1,19 @@
1
+ import { elementDefine } from '../decorators/elementDefine';
2
+
3
+ @elementDefine({ name: 'swc-loading', extends: 'template' })
4
+ export class SwcLoading extends HTMLTemplateElement {}
5
+
6
+ @elementDefine({ name: 'swc-error', extends: 'template' })
7
+ export class SwcError extends HTMLTemplateElement {}
8
+
9
+ @elementDefine({ name: 'swc-success', extends: 'template' })
10
+ export class SwcSuccess extends HTMLTemplateElement {}
11
+
12
+ @elementDefine({ name: 'swc-when', extends: 'template' })
13
+ export class SwcWhen extends HTMLTemplateElement {}
14
+
15
+ @elementDefine({ name: 'swc-otherwise', extends: 'template' })
16
+ export class SwcOtherwise extends HTMLTemplateElement {}
17
+
18
+ @elementDefine({ name: 'swc-default', extends: 'template' })
19
+ export class SwcDefault extends HTMLTemplateElement {}
@@ -1,3 +1,8 @@
1
- // import {SwcHTMLElement} from "./SwcHTMLElement";
2
-
3
- export * from './is'
1
+ export * from './is';
2
+ export * from './SwcSubTemplates';
3
+ export * from './SwcLoop';
4
+ export * from './SwcIf';
5
+ export * from './SwcChoose';
6
+ export * from './SwcAsync';
7
+ export * from './SwcApp';
8
+ export * from './SwcAppEngine';
package/src/index.ts CHANGED
@@ -15,7 +15,7 @@ export * from './decorators/addEventListener';
15
15
  export * from './types';
16
16
 
17
17
  // Apps (Customized Built-in Elements)
18
- export * from './elements/is';
18
+ export * from './elements';
19
19
 
20
20
  // Utilities
21
21
  export * from './utils/Utils';
package/src/types.ts CHANGED
@@ -6,24 +6,34 @@ export enum InjectSituationType {
6
6
  APP_HOST = 'SIMPLE_WEB_COMPONENT://APPHOST',
7
7
  APP_HOSTS = 'SIMPLE_WEB_COMPONENT://APPHOSTS',
8
8
  HOST = 'SIMPLE_WEB_COMPONENT://HOST',
9
+ PARENT_HOST = 'SIMPLE_WEB_COMPONENT://PARENTHOST',
9
10
  HOSTS = 'SIMPLE_WEB_COMPONENT://HOSTS',
10
11
  FIRST_HOST = 'SIMPLE_WEB_COMPONENT://FIRSTHOST',
11
12
  LAST_HOST = 'SIMPLE_WEB_COMPONENT://LASTHOST',
12
13
  FIRST_APP_HOST = 'SIMPLE_WEB_COMPONENT://FIRSTAPPHOST',
13
14
  LAST_APP_HOST = 'SIMPLE_WEB_COMPONENT://LASTAPPHOST'
14
15
  }
15
-
16
+ export type HelperSet = {
17
+ $d: Document;
18
+ $w: Window;
19
+ $q: (selector: string, root?: Element | Document | ShadowRoot) => HTMLElement | null;
20
+ $qa: (selector: string, root?: Element | Document | ShadowRoot) => HTMLElement[];
21
+ $qi: (selector: string, root?: Element | Document | ShadowRoot) => HTMLElement | null;
22
+ }
16
23
  export type HostSet = {
17
- $host: HTMLElement | null;
18
- $hosts: HTMLElement[];
19
- $firstHost: HTMLElement | null;
20
- $lastHost: HTMLElement | null;
24
+ $host: HTMLElement; // Current component itself
25
+ $parentHost: HTMLElement | null; // Nearest parent SWC component
26
+ $hosts: HTMLElement[]; // [self, parent, ..., top]
27
+ $firstHost: HTMLElement | null; // Top-most SWC ancestor
28
+ $lastHost: HTMLElement | null; // Same as $parentHost (for backward compatibility or clarity)
21
29
  $appHost: SwcAppInterface | null;
22
30
  $appHosts: SwcAppInterface[];
23
31
  $firstAppHost: SwcAppInterface | null;
24
32
  $lastAppHost: SwcAppInterface | null;
25
33
  };
26
34
 
35
+ export type HelperHostSet = HelperSet & HostSet;
36
+
27
37
  export interface SwcAppInterface extends HTMLElement {
28
38
  simpleApplication?: SimpleApplication;
29
39
  router?: Router;
@@ -1,223 +1,137 @@
1
- import {HostSet, SwcAppInterface} from '../types';
1
+ import { SwcAppInterface, HostSet, HelperSet, HelperHostSet } from '../types';
2
+ import { getElementConfig } from '../decorators/elementDefine';
2
3
 
3
4
  export const SWC_NOT_FOUND = Symbol('SWC_NOT_FOUND');
4
5
 
5
- export class SwcUtils {
6
- static getValueByPath(obj: any, path: string, rootName: string) {
6
+ export namespace SwcUtils {
7
+ export const getHelperSet = (win: Window): HelperSet => {
8
+ const doc: Document = win.document;
9
+ return {
10
+ $d: doc,
11
+ $w: win,
12
+ $q: (sel: string, root?: Element | Document | ShadowRoot) => (root ?? doc).querySelector(sel),
13
+ $qa: (sel: string, root?: Element | Document | ShadowRoot) => Array.from((root ?? doc).querySelectorAll(sel)) as HTMLElement[],
14
+ $qi: (id: string, root?: Document | ShadowRoot) => (root ?? doc).getElementById(id) as HTMLElement
15
+ } as const;
16
+ };
17
+
18
+ export const getHelperAndHostSet = (win: Window, el: HTMLElement): HelperHostSet => {
19
+ const helperSet = getHelperSet(win);
20
+ const hostSet = getHostSet(el);
21
+ return {
22
+ ...helperSet,
23
+ ...hostSet
24
+ };
25
+ };
26
+
27
+ export const getValueByPath = (obj: any, path: string, rootName: string) => {
7
28
  if (!obj || !path) return SWC_NOT_FOUND;
8
29
 
9
30
  const parts = path.split('.');
31
+ let current = obj;
10
32
 
11
- // 1. If the path starts with the rootName (alias), strip it and resolve within obj
12
- if (parts[0] === rootName && parts.length > 1) {
13
- const subParts = parts.slice(1);
14
- let subResult = obj;
15
- for (const part of subParts) {
16
- if (subResult === null || typeof subResult !== 'object' || !(part in subResult)) return SWC_NOT_FOUND;
17
- subResult = subResult[part];
18
- }
19
- return subResult;
20
- }
21
-
22
- // 2. Fallback: resolve directly on obj (standard behavior)
23
- let result = obj;
24
33
  for (const part of parts) {
25
- if (result === null || typeof result !== 'object' || !(part in result)) {
26
- return SWC_NOT_FOUND;
27
- }
28
- result = result[part];
34
+ if (current === null || current === undefined) return SWC_NOT_FOUND;
35
+ current = current[part];
29
36
  }
30
-
31
- // Safety: don't return the instance itself or a DOM element as a template value
32
- if (result !== obj && !(result instanceof HTMLElement)) {
33
- return result;
37
+ return current;
38
+ };
39
+
40
+ /**
41
+ * Finds the nearest logical SWC host for an element.
42
+ * Checks __swc_host property first, then climbs the DOM tree.
43
+ */
44
+ export const findNearestSwcHost = (el: HTMLElement | Node): HTMLElement | undefined => {
45
+ if ((el as any).__swc_host) return (el as any).__swc_host;
46
+
47
+ let current: any = el.parentElement || (el.getRootNode?.() as any)?.host;
48
+ while (current && current !== document && current !== window) {
49
+ if (current.__swc_host) return current.__swc_host;
50
+ if (getElementConfig(current)) return current as HTMLElement;
51
+ current = current.parentElement || (current.getRootNode?.() as any)?.host;
34
52
  }
35
-
36
- return SWC_NOT_FOUND;
37
- }
38
-
39
- static evalExpression(expr: string, context: Record<string, any>): any {
40
- try {
41
- const keys = Object.keys(context);
42
- const values = Object.values(context);
43
- // eslint-disable-next-line no-new-func
44
- return new Function(...keys, `return (${expr})`).apply(null, values);
45
- } catch {
46
- return SWC_NOT_FOUND;
47
- }
48
- }
49
-
50
- static applyData(
51
- node: Node,
52
- data: any,
53
- options: {
54
- asKey: string;
55
- asIndexKey: string;
56
- index?: number;
57
- hostContext?: Record<string, any>;
58
- }
59
- ) {
60
- const { asKey, asIndexKey, index, hostContext } = options;
61
- const context: Record<string, any> = {};
62
- if (index !== undefined) {
63
- context[asIndexKey] = index;
64
- }
65
- if (data !== null && data !== undefined) {
66
- context[asKey] = data;
67
- context['value'] = data; // Always provide 'value' as well
68
- }
69
- const fullContext: Record<string, any> = { ...(hostContext || {}), ...context };
70
-
71
- const resolveExpr = (expr: string, match: string): string => {
72
- expr = expr.trim();
73
- // 1. Local path resolve
74
- const simpleVal = SwcUtils.getValueByPath(data, expr, asKey);
75
- if (simpleVal !== SWC_NOT_FOUND && typeof simpleVal !== 'symbol') {
76
- return simpleVal === null || simpleVal === undefined ? '' : String(simpleVal);
77
- }
78
- // 2. Full context eval (alias, host states, etc.)
79
- const exprVal = SwcUtils.evalExpression(expr, fullContext);
80
- if (exprVal !== SWC_NOT_FOUND && typeof exprVal !== 'symbol') {
81
- return exprVal === null || exprVal === undefined ? '' : String(exprVal);
82
- }
83
- return match;
84
- };
85
-
86
- const walk = (n: Node) => {
87
- if (n.nodeType === Node.TEXT_NODE) {
88
- if (!(n as any)._original) (n as any)._original = n.textContent;
89
- let text = (n as any)._original;
90
- text = text.replace(/{{(.*?)}}/g, (match: string, path: string) => {
91
- return resolveExpr(path, match);
92
- });
93
- if (n.textContent !== text) n.textContent = text;
94
- } else if (n.nodeType === Node.ELEMENT_NODE) {
95
- const el = n as HTMLElement;
96
- Array.from(el.attributes).forEach(a => {
97
- if (!(a as any)._original) (a as any)._original = a.value;
98
- let val = (a as any)._original;
99
- val = val.replace(/{{(.*?)}}/g, (match: string, path: string) => {
100
- return resolveExpr(path, match);
101
- });
102
- if (a.value !== val) a.value = val;
103
- });
104
- el.childNodes.forEach(walk);
105
- }
106
- };
107
- walk(node);
108
- }
109
-
110
- static createReactiveProxy(target: any, onChange: () => void, onIndexChange?: (index: number, val: any) => void) {
111
- const makeRecursiveProxy = (obj: any): any => {
112
- if (typeof obj !== 'object' || obj === null || obj instanceof Node) return obj;
113
- if ((obj as any).__swc_is_proxy) return obj;
114
-
115
- const IS_PROXY_KEY = '__swc_is_proxy';
116
-
117
- const proxy = new Proxy(obj, {
118
- get: (t, prop) => {
119
- if (prop === IS_PROXY_KEY) return true;
120
- if (typeof prop === 'symbol') return t[prop];
121
- return t[prop];
122
- },
123
- set: (t, prop, val) => {
124
- if (prop === IS_PROXY_KEY) return true;
125
- if (typeof prop === 'symbol') {
126
- t[prop] = val;
127
- return true;
128
- }
129
- const oldVal = t[prop];
130
- if (oldVal === val) return true;
131
- t[prop] = makeRecursiveProxy(val);
132
- const isIndex = !isNaN(Number(prop)) && Array.isArray(t);
133
- if (isIndex && onIndexChange) {
134
- onIndexChange(Number(prop), val);
135
- } else {
136
- onChange();
137
- }
138
- return true;
139
- },
140
- deleteProperty: (t, prop) => {
141
- delete t[prop];
142
- onChange();
143
- return true;
144
- }
145
- });
146
- return proxy;
147
- };
148
-
149
- return makeRecursiveProxy(target);
150
- }
151
-
152
- static findNearestSwcHost(el: HTMLElement | Node): any {
153
- const hosts = this.findAllSwcHosts(el);
154
- return hosts.length > 0 ? hosts[hosts.length - 1] : null;
155
- }
156
-
157
- static findAllSwcHosts(el: HTMLElement | Node): any[] {
158
- const hosts: any[] = [];
159
- let curr: any = el instanceof Node && !(el instanceof HTMLElement) ? (el as any).host : el;
160
- curr = curr?.parentElement || (curr?.getRootNode() as any)?.host;
161
-
162
- while (curr) {
163
- if (curr._swcId) hosts.push(curr);
164
- curr = curr.parentElement || (curr?.getRootNode() as any)?.host;
53
+ return undefined;
54
+ };
55
+
56
+ /**
57
+ * Returns all SWC ancestors (excluding self) in [root, ..., parent] order.
58
+ */
59
+ export const findAllSwcHosts = (el: HTMLElement | Node): HTMLElement[] => {
60
+ const hosts: HTMLElement[] = [];
61
+ let current = findNearestSwcHost(el);
62
+ while (current) {
63
+ hosts.push(current);
64
+ // To find the next ancestor, we must start searching from the PARENT of the found host
65
+ current = findNearestSwcHost(current);
165
66
  }
166
67
  return hosts.reverse();
167
- }
68
+ };
168
69
 
169
- static getHosts(el: HTMLElement): HTMLElement[] {
170
- return this.findAllSwcHosts(el);
171
- }
70
+ export const getHosts = (el: HTMLElement): HTMLElement[] => {
71
+ const ancestors = findAllSwcHosts(el);
72
+ if (getElementConfig(el)) return [...ancestors, el];
73
+ return ancestors;
74
+ };
172
75
 
173
- static getHost(el: HTMLElement): HTMLElement | undefined {
174
- const hosts = this.getHosts(el);
175
- return hosts.length > 0 ? hosts[hosts.length - 1] : undefined;
176
- }
76
+ export const getHost = (el: HTMLElement): HTMLElement | undefined => {
77
+ if (getElementConfig(el)) return el;
78
+ return findNearestSwcHost(el);
79
+ };
177
80
 
178
- static getFirstHost(el: HTMLElement): HTMLElement | undefined {
179
- const hosts = this.getHosts(el);
180
- return hosts.length > 0 ? hosts[0] : undefined;
181
- }
182
-
183
- static getAppHosts(el: HTMLElement): SwcAppInterface[] {
184
- const hosts = this.getHosts(el);
81
+ export const getAppHosts = (el: HTMLElement): SwcAppInterface[] => {
82
+ const hosts = getHosts(el);
185
83
  return hosts.filter(host => host.tagName.toLowerCase() === 'swc-app' || host.getAttribute('is')?.startsWith('swc-app-')) as SwcAppInterface[];
186
- }
187
-
188
- static getAppHost(el: HTMLElement): SwcAppInterface | undefined {
189
- const appHosts = this.getAppHosts(el);
190
- return appHosts.length > 0 ? appHosts[appHosts.length - 1] : undefined;
191
- }
192
-
193
- static getFirstAppHost(el: HTMLElement): SwcAppInterface | undefined {
194
- const appHosts = this.getAppHosts(el);
195
- return appHosts.length > 0 ? appHosts[0] : undefined;
196
- }
197
-
198
- static getLastAppHost(el: HTMLElement): SwcAppInterface | undefined {
199
- return this.getAppHost(el);
200
- }
84
+ };
85
+
86
+ export const getAppHost = (el: HTMLElement): SwcAppInterface | undefined => {
87
+ const appHosts = getAppHosts(el);
88
+ const appHostsWithoutSelf = appHosts.filter(h => h !== el);
89
+ return appHostsWithoutSelf.length > 0 ? appHostsWithoutSelf[appHostsWithoutSelf.length - 1] : undefined;
90
+ };
91
+
92
+ export const getHostSet = (el: HTMLElement): HostSet => {
93
+ const isSwc = !!getElementConfig(el);
94
+ const ancestors = findAllSwcHosts(el); // [root, ..., parent]
95
+
96
+ let $host: HTMLElement;
97
+ let $parentHost: HTMLElement | null;
98
+
99
+ if (isSwc) {
100
+ $host = el;
101
+ $parentHost = ancestors.length > 0 ? ancestors[ancestors.length - 1] : null;
102
+ } else {
103
+ $host = ancestors.length > 0 ? ancestors[ancestors.length - 1] : el;
104
+ $parentHost = ancestors.length > 1 ? ancestors[ancestors.length - 2] : null;
105
+ }
201
106
 
202
- static getHostSet(el: HTMLElement): HostSet {
203
- const $hosts = this.getHosts(el);
204
- const $appHosts = this.getAppHosts(el);
107
+ const $hosts = isSwc ? [...ancestors, el] : ancestors;
108
+ const $firstHost = $hosts.length > 0 ? $hosts[0] : null;
205
109
 
206
- const $firstHost = $hosts[0] || null;
207
- const $lastHost = $hosts[$hosts.length - 1] || null;
110
+ // Collect loop contexts
111
+ const loopContext: Record<string, any> = {};
112
+ let curr: any = el;
113
+ while (curr && curr !== document.documentElement) {
114
+ if (curr.__swc_loop_context) {
115
+ Object.assign(loopContext, curr.__swc_loop_context);
116
+ }
117
+ curr = curr.parentElement || (curr.getRootNode() as any).host;
118
+ }
208
119
 
209
- const $firstAppHost = $appHosts[0] || null;
210
- const $lastAppHost = $appHosts[$appHosts.length - 1] || null;
120
+ const $appHosts = $hosts.filter(h => h.tagName.toLowerCase() === 'swc-app' || h.getAttribute('is')?.startsWith('swc-app-')) as SwcAppInterface[];
121
+ const $appHost = $appHosts.length > 0 ? ($appHosts[$appHosts.length - 1] === el ? ($appHosts.length > 1 ? $appHosts[$appHosts.length - 2] : null) : $appHosts[$appHosts.length - 1]) : null;
122
+ const $firstAppHost = $appHosts.length > 0 ? $appHosts[0] : null;
211
123
 
212
124
  return {
213
- $host: $lastHost,
125
+ $host,
126
+ $parentHost,
214
127
  $hosts,
215
128
  $firstHost,
216
- $lastHost,
217
- $appHost: $lastAppHost,
129
+ $lastHost: $parentHost,
130
+ $appHost,
218
131
  $appHosts,
219
132
  $firstAppHost,
220
- $lastAppHost
221
- };
222
- }
133
+ $lastAppHost: $appHost,
134
+ ...loopContext
135
+ } as any;
136
+ };
223
137
  }