@dooboostore/simple-web-component 1.0.16 → 1.0.18

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 (130) hide show
  1. package/README.md +109 -48
  2. package/dist/cjs/decorators/addEventListener.js.map +2 -2
  3. package/dist/cjs/decorators/appendChild.js +48 -0
  4. package/dist/cjs/decorators/appendChild.js.map +7 -0
  5. package/dist/cjs/decorators/elementDefine.js +216 -67
  6. package/dist/cjs/decorators/elementDefine.js.map +3 -3
  7. package/dist/cjs/decorators/index.js +2 -1
  8. package/dist/cjs/decorators/index.js.map +2 -2
  9. package/dist/cjs/decorators/{innerHtml.js → onConnectedInnerHtml.js} +8 -8
  10. package/dist/cjs/decorators/onConnectedInnerHtml.js.map +7 -0
  11. package/dist/cjs/decorators/query.js +38 -0
  12. package/dist/cjs/decorators/query.js.map +2 -2
  13. package/dist/cjs/decorators/queryAll.js +32 -0
  14. package/dist/cjs/decorators/queryAll.js.map +2 -2
  15. package/dist/cjs/decorators/replaceChildren.js +53 -0
  16. package/dist/cjs/decorators/replaceChildren.js.map +7 -0
  17. package/dist/cjs/elements/SwcAppEngine.js +2 -1
  18. package/dist/cjs/elements/SwcAppEngine.js.map +2 -2
  19. package/dist/cjs/elements/SwcAsync.js +188 -0
  20. package/dist/cjs/elements/SwcAsync.js.map +7 -0
  21. package/dist/cjs/elements/SwcChoose.js +155 -0
  22. package/dist/cjs/elements/SwcChoose.js.map +7 -0
  23. package/dist/cjs/elements/SwcIf.js +167 -0
  24. package/dist/cjs/elements/SwcIf.js.map +7 -0
  25. package/dist/cjs/elements/SwcLoop.js +246 -0
  26. package/dist/cjs/elements/SwcLoop.js.map +7 -0
  27. package/dist/cjs/elements/SwcSubTemplates.js +65 -0
  28. package/dist/cjs/elements/SwcSubTemplates.js.map +7 -0
  29. package/dist/cjs/elements/index.js +7 -0
  30. package/dist/cjs/elements/index.js.map +2 -2
  31. package/dist/cjs/index.js +12 -2
  32. package/dist/cjs/index.js.map +2 -2
  33. package/dist/cjs/types.js.map +1 -1
  34. package/dist/cjs/utils/Utils.js +74 -37
  35. package/dist/cjs/utils/Utils.js.map +3 -3
  36. package/dist/esm/decorators/addEventListener.js.map +2 -2
  37. package/dist/esm/decorators/appendChild.js +29 -0
  38. package/dist/esm/decorators/appendChild.js.map +7 -0
  39. package/dist/esm/decorators/elementDefine.js +216 -67
  40. package/dist/esm/decorators/elementDefine.js.map +3 -3
  41. package/dist/esm/decorators/index.js +2 -1
  42. package/dist/esm/decorators/index.js.map +2 -2
  43. package/dist/esm/decorators/{innerHtml.js → onConnectedInnerHtml.js} +5 -5
  44. package/dist/esm/decorators/onConnectedInnerHtml.js.map +7 -0
  45. package/dist/esm/decorators/query.js +38 -0
  46. package/dist/esm/decorators/query.js.map +2 -2
  47. package/dist/esm/decorators/queryAll.js +32 -0
  48. package/dist/esm/decorators/queryAll.js.map +2 -2
  49. package/dist/esm/decorators/replaceChildren.js +34 -0
  50. package/dist/esm/decorators/replaceChildren.js.map +7 -0
  51. package/dist/esm/elements/SwcAppEngine.js +2 -1
  52. package/dist/esm/elements/SwcAppEngine.js.map +2 -2
  53. package/dist/esm/elements/SwcAsync.js +169 -0
  54. package/dist/esm/elements/SwcAsync.js.map +7 -0
  55. package/dist/esm/elements/SwcChoose.js +136 -0
  56. package/dist/esm/elements/SwcChoose.js.map +7 -0
  57. package/dist/esm/elements/SwcIf.js +148 -0
  58. package/dist/esm/elements/SwcIf.js.map +7 -0
  59. package/dist/esm/elements/SwcLoop.js +227 -0
  60. package/dist/esm/elements/SwcLoop.js.map +7 -0
  61. package/dist/esm/elements/SwcSubTemplates.js +46 -0
  62. package/dist/esm/elements/SwcSubTemplates.js.map +7 -0
  63. package/dist/esm/elements/index.js +7 -0
  64. package/dist/esm/elements/index.js.map +2 -2
  65. package/dist/esm/index.js +8 -2
  66. package/dist/esm/index.js.map +2 -2
  67. package/dist/esm/types.js.map +1 -1
  68. package/dist/esm/utils/Utils.js +74 -37
  69. package/dist/esm/utils/Utils.js.map +3 -3
  70. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +1226 -149
  71. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  72. package/dist/types/decorators/addEventListener.d.ts +1 -0
  73. package/dist/types/decorators/addEventListener.d.ts.map +1 -1
  74. package/dist/types/decorators/appendChild.d.ts +17 -0
  75. package/dist/types/decorators/appendChild.d.ts.map +1 -0
  76. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  77. package/dist/types/decorators/index.d.ts +2 -1
  78. package/dist/types/decorators/index.d.ts.map +1 -1
  79. package/dist/types/decorators/onConnectedInnerHtml.d.ts +15 -0
  80. package/dist/types/decorators/onConnectedInnerHtml.d.ts.map +1 -0
  81. package/dist/types/decorators/query.d.ts +0 -2
  82. package/dist/types/decorators/query.d.ts.map +1 -1
  83. package/dist/types/decorators/queryAll.d.ts +0 -2
  84. package/dist/types/decorators/queryAll.d.ts.map +1 -1
  85. package/dist/types/decorators/replaceChildren.d.ts +9 -0
  86. package/dist/types/decorators/replaceChildren.d.ts.map +1 -0
  87. package/dist/types/elements/SwcAppEngine.d.ts.map +1 -1
  88. package/dist/types/elements/SwcAsync.d.ts +16 -0
  89. package/dist/types/elements/SwcAsync.d.ts.map +1 -0
  90. package/dist/types/elements/SwcChoose.d.ts +13 -0
  91. package/dist/types/elements/SwcChoose.d.ts.map +1 -0
  92. package/dist/types/elements/SwcIf.d.ts +15 -0
  93. package/dist/types/elements/SwcIf.d.ts.map +1 -0
  94. package/dist/types/elements/SwcLoop.d.ts +20 -0
  95. package/dist/types/elements/SwcLoop.d.ts.map +1 -0
  96. package/dist/types/elements/SwcSubTemplates.d.ts +13 -0
  97. package/dist/types/elements/SwcSubTemplates.d.ts.map +1 -0
  98. package/dist/types/elements/index.d.ts +7 -0
  99. package/dist/types/elements/index.d.ts.map +1 -1
  100. package/dist/types/index.d.ts +5 -2
  101. package/dist/types/index.d.ts.map +1 -1
  102. package/dist/types/types.d.ts +8 -0
  103. package/dist/types/types.d.ts.map +1 -1
  104. package/dist/types/utils/Utils.d.ts +16 -10
  105. package/dist/types/utils/Utils.d.ts.map +1 -1
  106. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +1226 -149
  107. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  108. package/package.json +10 -3
  109. package/src/decorators/addEventListener.ts +1 -0
  110. package/src/decorators/appendChild.ts +42 -0
  111. package/src/decorators/elementDefine.ts +225 -71
  112. package/src/decorators/index.ts +2 -1
  113. package/src/decorators/{innerHtml.ts → onConnectedInnerHtml.ts} +7 -6
  114. package/src/decorators/query.ts +32 -2
  115. package/src/decorators/queryAll.ts +32 -2
  116. package/src/decorators/replaceChildren.ts +35 -0
  117. package/src/elements/SwcAppEngine.ts +2 -1
  118. package/src/elements/SwcAsync.ts +163 -0
  119. package/src/elements/SwcChoose.ts +127 -0
  120. package/src/elements/SwcIf.ts +134 -0
  121. package/src/elements/SwcLoop.ts +215 -0
  122. package/src/elements/SwcSubTemplates.ts +19 -0
  123. package/src/elements/index.ts +8 -3
  124. package/src/index.ts +5 -2
  125. package/src/types.ts +9 -1
  126. package/src/utils/Utils.ts +86 -36
  127. package/dist/cjs/decorators/innerHtml.js.map +0 -7
  128. package/dist/esm/decorators/innerHtml.js.map +0 -7
  129. package/dist/types/decorators/innerHtml.d.ts +0 -12
  130. package/dist/types/decorators/innerHtml.d.ts.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dooboostore/simple-web-component",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -14,6 +14,13 @@
14
14
  }
15
15
  },
16
16
  "description": "Simple Web Component library based on dom-render philosophy",
17
+ "sideEffects": [
18
+ "./dist/esm/elements/*.js",
19
+ "./dist/esm/elements/is/*.js",
20
+ "./dist/cjs/elements/*.js",
21
+ "./dist/cjs/elements/is/*.js",
22
+ "*.ts"
23
+ ],
17
24
  "repository": {
18
25
  "type": "git",
19
26
  "url": "git+https://github.com/dooboostore-develop/packages.git",
@@ -81,9 +88,9 @@
81
88
  "webpack-cli": "^5.1.4",
82
89
  "webpack-dev-server": "^5.2.0",
83
90
  "@dooboostore/core": "1.0.28",
84
- "@dooboostore/simple-boot": "1.0.53",
91
+ "@dooboostore/core-node": "1.0.10",
85
92
  "@dooboostore/core-web": "1.0.9",
86
- "@dooboostore/core-node": "1.0.10"
93
+ "@dooboostore/simple-boot": "1.0.53"
87
94
  },
88
95
  "scripts": {
89
96
  "clean": "rm -rf dist",
@@ -9,6 +9,7 @@ export interface AddEventListenerOptions extends EventListenerOptions {
9
9
  stopImmediatePropagation?: boolean;
10
10
  preventDefault?: boolean;
11
11
  delegate?: boolean;
12
+ removeOnDisconnected?: boolean;
12
13
  }
13
14
 
14
15
  export interface AddEventListenerMetadata {
@@ -0,0 +1,42 @@
1
+ import { ReflectUtils } from '@dooboostore/core/reflect/ReflectUtils';
2
+
3
+ export type AppendChildPosition = 'firstChild' | 'lastChild' | 'nextSibling' | 'previousSibling';
4
+
5
+ export interface AppendChildOptions {
6
+ position?: AppendChildPosition;
7
+ }
8
+
9
+ export interface AppendChildMetadata {
10
+ propertyKey: string | symbol;
11
+ selector: string;
12
+ options: AppendChildOptions;
13
+ }
14
+
15
+ export const APPEND_CHILD_METADATA_KEY = Symbol('simple-web-component:append-child');
16
+
17
+ /**
18
+ * @appendChild decorator to surgically add nodes to a target element.
19
+ * Usage: @appendChild('#list') addItem() { return '<li>New Item</li>'; }
20
+ */
21
+ export function appendChild(selector: string, options: AppendChildOptions = { position: 'lastChild' }): MethodDecorator {
22
+ return (target: Object, propertyKey: string | symbol) => {
23
+ const constructor = target.constructor;
24
+ let meta = ReflectUtils.getOwnMetadata(APPEND_CHILD_METADATA_KEY, constructor) as Map<string | symbol, AppendChildMetadata>;
25
+ if (!meta) {
26
+ meta = new Map<string | symbol, AppendChildMetadata>();
27
+ ReflectUtils.defineMetadata(APPEND_CHILD_METADATA_KEY, meta, constructor);
28
+ }
29
+ meta.set(propertyKey, { propertyKey, selector, options });
30
+ };
31
+ }
32
+
33
+ export const findAllAppendChildMetadata = (target: any): Map<string | symbol, AppendChildMetadata> => {
34
+ const result = new Map<string | symbol, AppendChildMetadata>();
35
+ const maps = ReflectUtils.findAllMetadata<Map<string | symbol, AppendChildMetadata>>(APPEND_CHILD_METADATA_KEY, target);
36
+ maps.forEach(map => {
37
+ if (map instanceof Map) {
38
+ map.forEach((v, k) => result.set(k, v));
39
+ }
40
+ });
41
+ return result;
42
+ };
@@ -1,5 +1,6 @@
1
1
  import { ReflectUtils } from '@dooboostore/core/reflect/ReflectUtils';
2
- import { getInnerHtmlMetadataList } from './innerHtml';
2
+ import { FunctionUtils } from '@dooboostore/core/function/FunctionUtils';
3
+ import { getOnConnectedInnerHtmlMetadata } from './onConnectedInnerHtml';
3
4
  import { getQueryMetadata } from './query';
4
5
  import { getQueryAllMetadata } from './queryAll';
5
6
  import { getAddEventListenerMetadata } from './addEventListener';
@@ -7,13 +8,17 @@ import { ON_BEFORE_CONNECTED_METADATA_KEY, ON_AFTER_CONNECTED_METADATA_KEY, ON_B
7
8
  import { getEmitCustomEventMetadataList } from './emitCustomEvent';
8
9
  import { changedAttribute, findAllAttributeChangedMetadata } from './changedAttribute';
9
10
  import { setAttribute, findAllSetAttributeMetadata } from './setAttribute';
11
+ import { findAllReplaceChildrenMetadata } from './replaceChildren';
12
+ import { findAllAppendChildMetadata } from './appendChild';
10
13
  import { SwcUtils } from '../utils/Utils';
11
14
  import { HTML_TAG_ENTRIES, DOM_EVENT_NAMES } from '../config/config';
12
15
  import { SituationType, SituationTypeContainer, SituationTypeContainers } from '@dooboostore/simple-boot/decorators/inject/Inject';
13
16
  import { ConstructorType } from '@dooboostore/core/types';
17
+ import { SimpleApplication } from '@dooboostore/simple-boot/SimpleApplication';
18
+ import { Router } from '@dooboostore/core-web/routers/Router';
14
19
  import { InjectSituationType, HostSet, SwcAppInterface } from '../types';
15
20
 
16
- // --- Core Interfaces & Types (Integrated) ---
21
+ // --- Core Interfaces & Types ---
17
22
 
18
23
  export interface ElementConfig {
19
24
  name: string;
@@ -25,6 +30,9 @@ export interface ElementConfig {
25
30
 
26
31
  export const ELEMENT_CONFIG_KEY = Symbol('simple-web-component:element-config');
27
32
 
33
+ // Shared state for global delegation to prevent multiple listeners on the same root
34
+ const globalDelegatedRoots = new WeakSet<Node>();
35
+
28
36
  // --- Environment Initialization Helper ---
29
37
 
30
38
  function buildEnv(configWindow?: Window) {
@@ -36,14 +44,7 @@ function buildEnv(configWindow?: Window) {
36
44
  if (win?.[cls]) builtInTagMap.set(win[cls], tag);
37
45
  }
38
46
 
39
- const domHelpers = {
40
- $d: doc,
41
- $w: win,
42
- $q: (sel: string, root?: Element | Document | ShadowRoot) => (root ?? doc).querySelector(sel),
43
- $qa: (sel: string, root?: Element | Document | ShadowRoot) => Array.from((root ?? doc).querySelectorAll(sel)),
44
- $qi: (id: string, root?: Document | ShadowRoot) => (root ?? doc).getElementById(id)
45
- } as const;
46
-
47
+ const domHelpers = SwcUtils.getHelperSet(win);
47
48
  return { win, doc, builtInTagMap, domHelpers };
48
49
  }
49
50
 
@@ -58,8 +59,6 @@ export const elementDefine =
58
59
  const config: ElementConfig = typeof inConfig === 'string' ? { name: inConfig } : inConfig;
59
60
  const { win, doc, builtInTagMap: BUILT_IN_TAG_MAP, domHelpers: SWC_DOM_HELPERS } = buildEnv(config.window);
60
61
  config.window = win;
61
- const SWC_DOM_HELPER_KEYS = Object.keys(SWC_DOM_HELPERS) as (keyof typeof SWC_DOM_HELPERS)[];
62
- const SWC_DOM_HELPER_VALS = () => SWC_DOM_HELPER_KEYS.map(k => (SWC_DOM_HELPERS as any)[k]);
63
62
 
64
63
  let extendsTagName = config.extends;
65
64
  if (!extendsTagName) {
@@ -76,25 +75,26 @@ export const elementDefine =
76
75
  const addEventListenerList = getAddEventListenerMetadata(constructor) || [];
77
76
  const attrChangeMap = findAllAttributeChangedMetadata(constructor);
78
77
  const setAttrMap = findAllSetAttributeMetadata(constructor);
78
+ const replaceChildrenMap = findAllReplaceChildrenMetadata(constructor);
79
+ const appendChildMap = findAllAppendChildMetadata(constructor);
79
80
 
80
81
  const swcLifecycleAttributes = ['swc-on-constructor', 'swc-on-connected', 'swc-on-disconnected', 'swc-on-before-connected', 'swc-on-after-connected', 'swc-on-before-disconnected', 'swc-on-after-disconnected', 'swc-on-before-adopted', 'swc-on-after-adopted', 'swc-on-attribute-changed'];
81
82
  const swcOnEvents = DOM_EVENT_NAMES.map(e => `swc-on-${e}`);
82
83
 
83
84
  const mergedObservedAttributes = [...new Set([...(config.observedAttributes ?? []), ...attrChangeMap.keys(), ...setAttrMap.keys(), ...emitCustomEventList.map(it => it.options.attributeName!), ...swcLifecycleAttributes, ...swcOnEvents])];
84
- const innerHtmlList = getInnerHtmlMetadataList(constructor) || [];
85
+ const connectedInnerHtmlList = getOnConnectedInnerHtmlMetadata(constructor) || [];
85
86
 
86
87
  const NewClass = class extends (constructor as any) {
87
88
  private _swcId = Math.random().toString(36).substring(2, 11);
88
89
  private _emitHandlers = new Map<string, EventListener>();
90
+ private _boundListeners: Array<{ target: EventTarget; type: string; handler: EventListener; options?: any }> = [];
89
91
 
90
92
  private _executeSwcScript(attrName: string, hostSet: HostSet, extraArgs: Record<string, any> = {}) {
91
93
  const script = this.getAttribute(attrName);
92
94
  if (script) {
93
95
  try {
94
96
  const args = { ...hostSet, ...SWC_DOM_HELPERS, ...extraArgs };
95
- const argNames = Object.keys(args);
96
- const argValues = Object.values(args);
97
- new Function(...argNames, script).apply(this, argValues);
97
+ FunctionUtils.execute({ script, context: this, args });
98
98
  } catch (e) {
99
99
  console.error(`[SWC] Failed to execute ${attrName}:`, e);
100
100
  }
@@ -142,9 +142,13 @@ export const elementDefine =
142
142
 
143
143
  const handler = (event: any) => {
144
144
  const hostSet = SwcUtils.getHostSet(el);
145
- const argNames = Object.keys(hostSet);
146
- const argValues = Object.values(hostSet);
147
- new Function('event', '$data', ...argNames, ...SWC_DOM_HELPER_KEYS, script).call(el, event, (event as CustomEvent).detail, ...argValues, ...SWC_DOM_HELPER_VALS());
145
+ const args = {
146
+ event,
147
+ $data: (event as CustomEvent).detail,
148
+ ...hostSet,
149
+ ...SWC_DOM_HELPERS
150
+ };
151
+ FunctionUtils.execute({ script, context: el, args });
148
152
  };
149
153
 
150
154
  el.addEventListener(eventName, handler);
@@ -156,7 +160,7 @@ export const elementDefine =
156
160
  const hostSet = SwcUtils.getHostSet(this as any);
157
161
  this._executeSwcScript('swc-on-constructor', hostSet);
158
162
 
159
- if (innerHtmlList.some(it => it.options.useShadow === true) && !this.shadowRoot) {
163
+ if (connectedInnerHtmlList.some(it => it.options.useShadow === true) && !this.shadowRoot) {
160
164
  this.attachShadow({ mode: 'open' });
161
165
  }
162
166
  }
@@ -166,6 +170,10 @@ export const elementDefine =
166
170
  }
167
171
 
168
172
  async connectedCallback() {
173
+ const conf = getElementConfig(this);
174
+ const currentWin = conf?.window || window;
175
+ const currentDoc = currentWin.document;
176
+
169
177
  const hostSet = SwcUtils.getHostSet(this as any);
170
178
 
171
179
  this._executeSwcScript('swc-on-before-connected', hostSet);
@@ -176,10 +184,10 @@ export const elementDefine =
176
184
 
177
185
  if (typeof (this as any).initCore === 'function') (this as any).initCore();
178
186
 
179
- if (innerHtmlList.length > 0) {
187
+ if (connectedInnerHtmlList.length > 0) {
180
188
  let sContent = '',
181
189
  lContent = '';
182
- for (const meta of innerHtmlList) {
190
+ for (const meta of connectedInnerHtmlList) {
183
191
  const res = await (this as any)[meta.propertyKey]();
184
192
  if (res !== undefined) {
185
193
  if (meta.options.useShadow) sContent += res;
@@ -190,77 +198,142 @@ export const elementDefine =
190
198
  if (lContent) this.innerHTML = lContent;
191
199
  }
192
200
 
193
- // --- @addEventListener binding ---
201
+ // --- Optimized Host Delegation ---
202
+ const createDelegationHandler = (delegateHost: any) => (event: Event) => {
203
+ if ((event as any).__swc_handled) return;
204
+
205
+ const path = event.composedPath();
206
+ const type = event.type;
207
+ const attrName = `swc-on-${type}`;
208
+
209
+ for (const node of path) {
210
+ if (node === delegateHost) break;
211
+ if (!(node instanceof HTMLElement)) continue;
212
+
213
+ const script = node.getAttribute?.(attrName);
214
+ if (script && !getElementConfig(node)) {
215
+ const host = SwcUtils.findNearestSwcHost(node);
216
+ if (host) {
217
+ const childHostSet = SwcUtils.getHostSet(node);
218
+ const args = {
219
+ event,
220
+ $data: (event as CustomEvent).detail,
221
+ ...childHostSet,
222
+ ...SWC_DOM_HELPERS
223
+ };
224
+ FunctionUtils.execute({ script, context: node, args });
225
+ (event as any).__swc_handled = true;
226
+ break;
227
+ }
228
+ }
229
+ }
230
+ };
231
+
232
+ // 1. Local Delegation (To itself)
233
+ DOM_EVENT_NAMES.forEach(type => {
234
+ const handler = createDelegationHandler(this);
235
+ this.addEventListener(type, handler);
236
+ this._boundListeners.push({ target: this as any, type, handler });
237
+ });
238
+
239
+ // 2. Structural/Global Delegation (If no parent host exists)
240
+ const hasParentHost = SwcUtils.findAllSwcHosts(this as any).length > 0;
241
+ if (!hasParentHost) {
242
+ const root = this.getRootNode();
243
+ if (!globalDelegatedRoots.has(root)) {
244
+ DOM_EVENT_NAMES.forEach(type => {
245
+ const handler = createDelegationHandler(root);
246
+ root.addEventListener(type, handler);
247
+ });
248
+ globalDelegatedRoots.add(root);
249
+ }
250
+ }
251
+
252
+ // --- @addEventListener binding (Direct/Delegate/Global) ---
194
253
  addEventListenerList.forEach(meta => {
195
254
  const { selector, type, options } = meta;
196
- const { root, capture, once, passive, stopPropagation, stopImmediatePropagation, preventDefault, delegate } = options;
197
-
198
- if (delegate && selector && selector !== ':host') {
255
+ const { root, capture, once, passive, stopPropagation, stopImmediatePropagation, preventDefault, delegate, removeOnDisconnected } = options;
256
+ const opts = { capture, once, passive };
257
+
258
+ const bindTargets: EventTarget[] = [];
259
+
260
+ if (selector === ':window') {
261
+ bindTargets.push(currentWin);
262
+ } else if (selector === ':document') {
263
+ bindTargets.push(currentDoc);
264
+ } else if (selector === ':parentHost') {
265
+ if (hostSet.$parentHost) bindTargets.push(hostSet.$parentHost);
266
+ } else if (selector === ':appHost') {
267
+ if (hostSet.$appHost) bindTargets.push(hostSet.$appHost);
268
+ } else if (selector === ':firstHost') {
269
+ if (hostSet.$firstHost) bindTargets.push(hostSet.$firstHost);
270
+ } else if (selector === ':lastHost') {
271
+ if (hostSet.$lastHost) bindTargets.push(hostSet.$lastHost);
272
+ } else if (selector === ':firstAppHost') {
273
+ if (hostSet.$firstAppHost) bindTargets.push(hostSet.$firstAppHost);
274
+ } else if (selector === ':lastAppHost') {
275
+ if (hostSet.$lastAppHost) bindTargets.push(hostSet.$lastAppHost);
276
+ } else if (!selector || selector === ':host') {
277
+ bindTargets.push(this as any);
278
+ } else if (delegate) {
199
279
  const bindRoots: (HTMLElement | ShadowRoot)[] = [];
200
280
  const r = root || 'auto';
201
- if (r === 'auto') {
202
- bindRoots.push(this.shadowRoot || (this as any));
203
- } else if (r === 'light') {
204
- bindRoots.push(this as any);
205
- } else if (r === 'shadow') {
281
+ if (r === 'auto') bindRoots.push(this.shadowRoot || (this as any));
282
+ else if (r === 'light') bindRoots.push(this as any);
283
+ else if (r === 'shadow') {
206
284
  if (this.shadowRoot) bindRoots.push(this.shadowRoot as any);
207
285
  } else if (r === 'all') {
208
286
  bindRoots.push(this as any);
209
287
  if (this.shadowRoot) bindRoots.push(this.shadowRoot as any);
210
288
  }
211
289
 
212
- bindRoots.forEach(bindRoot => {
290
+ bindRoots.forEach(br => {
213
291
  const handler = (event: Event) => {
214
- const target = event.target as HTMLElement;
215
- const matchedEl = target.closest(selector);
216
- if (matchedEl && (bindRoot as any).contains(matchedEl)) {
292
+ const matchedEl = (event.target as HTMLElement).closest(selector);
293
+ if (matchedEl && (br as any).contains(matchedEl)) {
217
294
  if (stopPropagation) event.stopPropagation();
218
295
  if (stopImmediatePropagation) event.stopImmediatePropagation();
219
296
  if (preventDefault) event.preventDefault();
220
-
221
- const hostSet = SwcUtils.getHostSet(this as any);
222
- this._invokeLifecycleMethod(meta.propertyKey, hostSet, [event, hostSet, matchedEl]);
297
+ const currentHostSet = SwcUtils.getHostSet(this as any);
298
+ this._invokeLifecycleMethod(meta.propertyKey, currentHostSet, [event, currentHostSet, matchedEl]);
223
299
  }
224
300
  };
225
- bindRoot.addEventListener(type, handler, { capture, once, passive });
301
+ br.addEventListener(type, handler, opts);
302
+ const self: any = this;
303
+ if (removeOnDisconnected === true || (br !== self && br !== self.shadowRoot)) {
304
+ this._boundListeners.push({ target: br, type, handler, options: opts });
305
+ }
226
306
  });
227
307
  } else {
228
- const targetEls: (HTMLElement | Element | Document | ShadowRoot | Window)[] = [];
229
- if (!selector || selector === ':host') {
230
- targetEls.push(this as any);
231
- } else {
232
- const searchRoots: (HTMLElement | ShadowRoot)[] = [];
233
- const r = root || 'auto';
234
- if (r === 'auto') {
235
- searchRoots.push((this.shadowRoot || (this as any)) as any);
236
- } else if (r === 'light') {
237
- searchRoots.push(this as any);
238
- } else if (r === 'shadow') {
239
- if (this.shadowRoot) searchRoots.push(this.shadowRoot as any);
240
- } else if (r === 'all') {
241
- searchRoots.push(this as any);
242
- if (this.shadowRoot) searchRoots.push(this.shadowRoot as any);
243
- }
244
-
245
- searchRoots.forEach(sr => {
246
- const found = sr.querySelectorAll(selector);
247
- found.forEach(el => targetEls.push(el as any));
248
- });
308
+ const searchRoots: (HTMLElement | ShadowRoot)[] = [];
309
+ const r = root || 'auto';
310
+ if (r === 'auto') searchRoots.push((this.shadowRoot || (this as any)) as any);
311
+ else if (r === 'light') searchRoots.push(this as any);
312
+ else if (r === 'shadow') {
313
+ if (this.shadowRoot) searchRoots.push(this.shadowRoot as any);
314
+ } else if (r === 'all') {
315
+ searchRoots.push(this as any);
316
+ if (this.shadowRoot) searchRoots.push(this.shadowRoot as any);
249
317
  }
250
318
 
251
- targetEls.forEach(el => {
252
- const handler = (event: Event) => {
253
- if (stopPropagation) event.stopPropagation();
254
- if (stopImmediatePropagation) event.stopImmediatePropagation();
255
- if (preventDefault) event.preventDefault();
256
-
257
- const hostSet = SwcUtils.getHostSet(this as any);
258
- this._invokeLifecycleMethod(meta.propertyKey, hostSet, [event, hostSet]);
259
- };
260
- const opts = { capture, once, passive };
261
- el.addEventListener(type, handler, opts);
319
+ searchRoots.forEach(sr => {
320
+ sr.querySelectorAll(selector).forEach(el => bindTargets.push(el));
262
321
  });
263
322
  }
323
+
324
+ bindTargets.forEach(target => {
325
+ const handler = (event: Event) => {
326
+ if (stopPropagation) event.stopPropagation();
327
+ if (stopImmediatePropagation) event.stopImmediatePropagation();
328
+ if (preventDefault) event.preventDefault();
329
+ const currentHostSet = SwcUtils.getHostSet(this as any);
330
+ this._invokeLifecycleMethod(meta.propertyKey, currentHostSet, [event, currentHostSet]);
331
+ };
332
+ target.addEventListener(type, handler, opts);
333
+ if (removeOnDisconnected === true || target === currentWin || target === currentDoc) {
334
+ this._boundListeners.push({ target, type, handler, options: opts });
335
+ }
336
+ });
264
337
  });
265
338
 
266
339
  if (super.connectedCallback) await super.connectedCallback();
@@ -279,6 +352,9 @@ export const elementDefine =
279
352
  const bMethods = findAllLifecycleMetadata(this, ON_BEFORE_DISCONNECTED_METADATA_KEY);
280
353
  if (bMethods) bMethods.forEach(m => this._invokeLifecycleMethod(m, hostSet));
281
354
 
355
+ this._boundListeners.forEach(l => l.target.removeEventListener(l.type, l.handler, l.options));
356
+ this._boundListeners = [];
357
+
282
358
  if (super.disconnectedCallback) super.disconnectedCallback();
283
359
 
284
360
  const aMethods = findAllLifecycleMetadata(this, ON_AFTER_DISCONNECTED_METADATA_KEY);
@@ -354,6 +430,84 @@ export const elementDefine =
354
430
  }
355
431
  });
356
432
 
433
+ // --- @replaceChildren wrapping logic ---
434
+ replaceChildrenMap.forEach((selector, methodName) => {
435
+ const original = NewClass.prototype[methodName as any];
436
+ if (typeof original === 'function') {
437
+ NewClass.prototype[methodName as any] = function (...args: any[]) {
438
+ const res = original.apply(this, args);
439
+ if (res !== undefined) {
440
+ const conf = getElementConfig(this);
441
+ const currentWin = conf?.window || window;
442
+ const currentDoc = currentWin.document;
443
+
444
+ let targetEl: Element | null = null;
445
+ if (selector === ':host' || !selector) {
446
+ targetEl = this;
447
+ } else {
448
+ targetEl = (this.shadowRoot || this).querySelector(selector);
449
+ }
450
+
451
+ if (targetEl) {
452
+ const nodes: (Node | string)[] = Array.isArray(res) ? res : [res];
453
+ const normalizedNodes = nodes.map(n => {
454
+ if (typeof n === 'string') {
455
+ const t = currentDoc.createElement('template');
456
+ t.innerHTML = n;
457
+ return t.content;
458
+ }
459
+ return n as Node;
460
+ });
461
+ (targetEl as any).replaceChildren(...normalizedNodes);
462
+ }
463
+ }
464
+ return res;
465
+ };
466
+ }
467
+ });
468
+
469
+ // --- @appendChild wrapping logic ---
470
+ appendChildMap.forEach((meta, methodName) => {
471
+ const { selector, options } = meta;
472
+ const original = NewClass.prototype[methodName as any];
473
+ if (typeof original === 'function') {
474
+ NewClass.prototype[methodName as any] = function (...args: any[]) {
475
+ const res = original.apply(this, args);
476
+ if (res !== undefined) {
477
+ const conf = getElementConfig(this);
478
+ const currentWin = conf?.window || window;
479
+ const currentDoc = currentWin.document;
480
+
481
+ let targetEl: Element | null = null;
482
+ if (selector === ':host' || !selector) {
483
+ targetEl = this;
484
+ } else {
485
+ targetEl = (this.shadowRoot || this).querySelector(selector);
486
+ }
487
+
488
+ if (targetEl) {
489
+ const nodes: (Node | string)[] = Array.isArray(res) ? res : [res];
490
+ const normalizedNodes = nodes.map(n => {
491
+ if (typeof n === 'string') {
492
+ const t = currentDoc.createElement('template');
493
+ t.innerHTML = n;
494
+ return t.content;
495
+ }
496
+ return n as Node;
497
+ });
498
+
499
+ const pos = options.position || 'lastChild';
500
+ if (pos === 'lastChild') (targetEl as any).append(...normalizedNodes);
501
+ else if (pos === 'firstChild') (targetEl as any).prepend(...normalizedNodes);
502
+ else if (pos === 'nextSibling') (targetEl as any).after(...normalizedNodes);
503
+ else if (pos === 'previousSibling') (targetEl as any).before(...normalizedNodes);
504
+ }
505
+ }
506
+ return res;
507
+ };
508
+ }
509
+ });
510
+
357
511
  const originalProto = constructor.prototype;
358
512
  const newProto = NewClass.prototype;
359
513
  Object.getOwnPropertyNames(originalProto).forEach(name => {
@@ -3,7 +3,8 @@ export * from './elementDefine';
3
3
  export * from './changedAttribute';
4
4
  export * from './setAttribute';
5
5
  export * from './emitCustomEvent';
6
- export * from './innerHtml';
6
+ export * from './replaceChildren';
7
+ export * from './appendChild';
7
8
  export * from './lifecycles';
8
9
  export * from './query';
9
10
  export * from './queryAll';
@@ -11,9 +11,12 @@ export interface InnerHtmlMetadata {
11
11
 
12
12
  export const INNER_HTML_METADATA_KEY = Symbol('simple-web-component:inner-html');
13
13
 
14
- export function innerHtml(options: InnerHtmlOptions): MethodDecorator;
15
- export function innerHtml(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
16
- export function innerHtml(arg1?: InnerHtmlOptions | Object, arg2?: string | symbol, arg3?: PropertyDescriptor): MethodDecorator | void {
14
+ /**
15
+ * @onConnectedInnerHtml decorator to define the initial HTML content when connected.
16
+ */
17
+ export function onConnectedInnerHtml(options: InnerHtmlOptions): MethodDecorator;
18
+ export function onConnectedInnerHtml(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): void;
19
+ export function onConnectedInnerHtml(arg1?: InnerHtmlOptions | Object, arg2?: string | symbol, arg3?: PropertyDescriptor): MethodDecorator | void {
17
20
  const decorator = (options: InnerHtmlOptions, target: Object, propertyKey: string | symbol) => {
18
21
  const constructor = target.constructor;
19
22
  let list = ReflectUtils.getMetadata<InnerHtmlMetadata[]>(INNER_HTML_METADATA_KEY, constructor);
@@ -25,17 +28,15 @@ export function innerHtml(arg1?: InnerHtmlOptions | Object, arg2?: string | symb
25
28
  };
26
29
 
27
30
  if (arg2) {
28
- // Used as @innerHtml
29
31
  return decorator({}, arg1!, arg2);
30
32
  }
31
33
 
32
- // Used as @innerHtml(options)
33
34
  return (target: Object, propertyKey: string | symbol) => {
34
35
  decorator((arg1 as InnerHtmlOptions) || {}, target, propertyKey);
35
36
  };
36
37
  }
37
38
 
38
- export const getInnerHtmlMetadataList = (target: any): InnerHtmlMetadata[] | undefined => {
39
+ export const getOnConnectedInnerHtmlMetadata = (target: any): InnerHtmlMetadata[] | undefined => {
39
40
  const constructor = target instanceof Function ? target : target.constructor;
40
41
  return ReflectUtils.getMetadata(INNER_HTML_METADATA_KEY, constructor);
41
42
  };
@@ -1,4 +1,6 @@
1
1
  import { ReflectUtils } from '@dooboostore/core/reflect/ReflectUtils';
2
+ import { getElementConfig } from './elementDefine';
3
+ import { SwcUtils } from '../utils/Utils';
2
4
 
3
5
  export interface QueryOptions {
4
6
  root?: 'light' | 'shadow' | 'all' | 'auto';
@@ -14,8 +16,6 @@ export const QUERY_METADATA_KEY = Symbol('simple-web-component:query');
14
16
 
15
17
  /**
16
18
  * @query decorator to inject a single element into a class property.
17
- * Usage: @query('.my-class') myEl!: HTMLElement;
18
- * Usage: @query('#my-id', { root: 'shadow' }) myEl!: HTMLElement;
19
19
  */
20
20
  export function query(selector: string, options: QueryOptions = {}): PropertyDecorator {
21
21
  return (target: Object, propertyKey: string | symbol, descriptor?: never): void => {
@@ -33,7 +33,25 @@ export function query(selector: string, options: QueryOptions = {}): PropertyDec
33
33
 
34
34
  Object.defineProperty(target, propertyKey, {
35
35
  get(this: HTMLElement) {
36
+ // --- Special Selectors: HostSet ---
37
+ const hostSet = SwcUtils.getHostSet(this);
38
+ if (selector === ':parentHost') return hostSet.$parentHost;
39
+ if (selector === ':appHost') return hostSet.$appHost;
40
+ if (selector === ':firstHost') return hostSet.$firstHost;
41
+ if (selector === ':lastHost') return hostSet.$lastHost;
42
+ if (selector === ':firstAppHost') return hostSet.$firstAppHost;
43
+ if (selector === ':lastAppHost') return hostSet.$lastAppHost;
44
+
45
+ // --- Special Selectors: Env ---
46
+ const config = getElementConfig(this);
47
+ const win = config?.window || window;
48
+ const doc = win.document;
49
+
50
+ if (selector === ':window') return win;
51
+ if (selector === ':document') return doc;
36
52
  if (!selector || selector === ':host') return this;
53
+
54
+ // --- Standard Selectors ---
37
55
  const r = options.root || 'auto';
38
56
 
39
57
  if (r === 'shadow') {
@@ -48,6 +66,18 @@ export function query(selector: string, options: QueryOptions = {}): PropertyDec
48
66
  // auto
49
67
  return (this.shadowRoot || this).querySelector(selector);
50
68
  },
69
+ set(this: HTMLElement, nv: any) {
70
+ if (nv === null || nv === undefined) {
71
+ const r = options.root || 'auto';
72
+ let targetEl: Element | null = null;
73
+ if (r === 'shadow') targetEl = this.shadowRoot ? this.shadowRoot.querySelector(selector) : null;
74
+ else if (r === 'light') targetEl = this.querySelector(selector);
75
+ else if (r === 'all') targetEl = this.shadowRoot?.querySelector(selector) || this.querySelector(selector);
76
+ else targetEl = (this.shadowRoot || this).querySelector(selector);
77
+
78
+ if (targetEl) targetEl.remove();
79
+ }
80
+ },
51
81
  enumerable: true,
52
82
  configurable: true
53
83
  });