@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dooboostore/simple-web-component",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
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",
@@ -82,8 +89,8 @@
82
89
  "webpack-dev-server": "^5.2.0",
83
90
  "@dooboostore/core": "1.0.28",
84
91
  "@dooboostore/core-node": "1.0.10",
85
- "@dooboostore/simple-boot": "1.0.53",
86
- "@dooboostore/core-web": "1.0.9"
92
+ "@dooboostore/core-web": "1.0.9",
93
+ "@dooboostore/simple-boot": "1.0.53"
87
94
  },
88
95
  "scripts": {
89
96
  "clean": "rm -rf dist",
@@ -1,8 +1,6 @@
1
1
  import { ReflectUtils } from '@dooboostore/core/reflect/ReflectUtils';
2
2
 
3
3
  export interface AddEventListenerOptions extends EventListenerOptions {
4
- query?: string;
5
- type: string;
6
4
  root?: 'light' | 'shadow' | 'all' | 'auto';
7
5
  capture?: boolean;
8
6
  once?: boolean;
@@ -10,28 +8,34 @@ export interface AddEventListenerOptions extends EventListenerOptions {
10
8
  stopPropagation?: boolean;
11
9
  stopImmediatePropagation?: boolean;
12
10
  preventDefault?: boolean;
11
+ delegate?: boolean;
12
+ removeOnDisconnected?: boolean;
13
13
  }
14
14
 
15
15
  export interface AddEventListenerMetadata {
16
+ selector: string;
17
+ type: string;
16
18
  options: AddEventListenerOptions;
17
19
  propertyKey: string | symbol;
18
20
  }
19
21
 
20
22
  export const ADD_EVENT_LISTENER_METADATA_KEY = Symbol('simple-web-component:add-event-listener');
21
23
 
22
- export function addEventListener(type: string, query?: string): MethodDecorator;
23
- export function addEventListener(options: AddEventListenerOptions): MethodDecorator;
24
- export function addEventListener(arg1: string | AddEventListenerOptions, arg2?: string): MethodDecorator {
24
+ /**
25
+ * @addEventListener decorator to bind events to elements.
26
+ * Usage: @addEventListener('.btn', 'click') onClick() { ... }
27
+ * Usage: @addEventListener('', 'scroll', { passive: true }) onScroll() { ... }
28
+ */
29
+ export function addEventListener(selector: string, type: string, options: AddEventListenerOptions = {}): MethodDecorator {
25
30
  return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
26
31
  const constructor = target.constructor;
27
- const options: AddEventListenerOptions = typeof arg1 === 'string' ? { type: arg1, query: arg2 } : arg1;
28
32
 
29
33
  let listeners = ReflectUtils.getMetadata<AddEventListenerMetadata[]>(ADD_EVENT_LISTENER_METADATA_KEY, constructor);
30
34
  if (!listeners) {
31
35
  listeners = [];
32
36
  ReflectUtils.defineMetadata(ADD_EVENT_LISTENER_METADATA_KEY, listeners, constructor);
33
37
  }
34
- listeners.push({ options, propertyKey });
38
+ listeners.push({ selector, type, options, propertyKey });
35
39
  };
36
40
  }
37
41
 
@@ -1,4 +1,5 @@
1
1
  import { ReflectUtils } from '@dooboostore/core/reflect/ReflectUtils';
2
+ import { FunctionUtils } from '@dooboostore/core/function/FunctionUtils';
2
3
  import { getInnerHtmlMetadataList } from './innerHtml';
3
4
  import { getQueryMetadata } from './query';
4
5
  import { getQueryAllMetadata } from './queryAll';
@@ -11,9 +12,11 @@ import { SwcUtils } from '../utils/Utils';
11
12
  import { HTML_TAG_ENTRIES, DOM_EVENT_NAMES } from '../config/config';
12
13
  import { SituationType, SituationTypeContainer, SituationTypeContainers } from '@dooboostore/simple-boot/decorators/inject/Inject';
13
14
  import { ConstructorType } from '@dooboostore/core/types';
15
+ import { SimpleApplication } from '@dooboostore/simple-boot/SimpleApplication';
16
+ import { Router } from '@dooboostore/core-web/routers/Router';
14
17
  import { InjectSituationType, HostSet, SwcAppInterface } from '../types';
15
18
 
16
- // --- Core Interfaces & Types (Integrated) ---
19
+ // --- Core Interfaces & Types ---
17
20
 
18
21
  export interface ElementConfig {
19
22
  name: string;
@@ -25,6 +28,9 @@ export interface ElementConfig {
25
28
 
26
29
  export const ELEMENT_CONFIG_KEY = Symbol('simple-web-component:element-config');
27
30
 
31
+ // Shared state for global delegation to prevent multiple listeners on the same root
32
+ const globalDelegatedRoots = new WeakSet<Node>();
33
+
28
34
  // --- Environment Initialization Helper ---
29
35
 
30
36
  function buildEnv(configWindow?: Window) {
@@ -36,14 +42,7 @@ function buildEnv(configWindow?: Window) {
36
42
  if (win?.[cls]) builtInTagMap.set(win[cls], tag);
37
43
  }
38
44
 
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
-
45
+ const domHelpers = SwcUtils.getHelperSet(win);
47
46
  return { win, doc, builtInTagMap, domHelpers };
48
47
  }
49
48
 
@@ -58,8 +57,6 @@ export const elementDefine =
58
57
  const config: ElementConfig = typeof inConfig === 'string' ? { name: inConfig } : inConfig;
59
58
  const { win, doc, builtInTagMap: BUILT_IN_TAG_MAP, domHelpers: SWC_DOM_HELPERS } = buildEnv(config.window);
60
59
  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
60
 
64
61
  let extendsTagName = config.extends;
65
62
  if (!extendsTagName) {
@@ -86,15 +83,14 @@ export const elementDefine =
86
83
  const NewClass = class extends (constructor as any) {
87
84
  private _swcId = Math.random().toString(36).substring(2, 11);
88
85
  private _emitHandlers = new Map<string, EventListener>();
86
+ private _boundListeners: Array<{ target: EventTarget; type: string; handler: EventListener; options?: any }> = [];
89
87
 
90
88
  private _executeSwcScript(attrName: string, hostSet: HostSet, extraArgs: Record<string, any> = {}) {
91
89
  const script = this.getAttribute(attrName);
92
90
  if (script) {
93
91
  try {
94
92
  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);
93
+ FunctionUtils.execute({ script, context: this, args });
98
94
  } catch (e) {
99
95
  console.error(`[SWC] Failed to execute ${attrName}:`, e);
100
96
  }
@@ -123,9 +119,11 @@ export const elementDefine =
123
119
  }
124
120
  }
125
121
 
126
- private _bindAttributeEvent(el: HTMLElement, attrName: string, script: string) {
127
- if (!attrName.startsWith('swc-on-')) return;
128
- const eventName = attrName.substring(7);
122
+ private _bindAttributeEvent(el: HTMLElement, attrName: string, script: string, eventName?: string) {
123
+ if (!eventName) {
124
+ if (!attrName.startsWith('swc-on-')) return;
125
+ eventName = attrName.substring(7);
126
+ }
129
127
  if (swcLifecycleAttributes.includes(attrName)) return;
130
128
 
131
129
  const elHandlers = getHandlers(this);
@@ -140,9 +138,13 @@ export const elementDefine =
140
138
 
141
139
  const handler = (event: any) => {
142
140
  const hostSet = SwcUtils.getHostSet(el);
143
- const argNames = Object.keys(hostSet);
144
- const argValues = Object.values(hostSet);
145
- new Function('event', '$data', ...argNames, ...SWC_DOM_HELPER_KEYS, script).call(el, event, (event as CustomEvent).detail, ...argValues, ...SWC_DOM_HELPER_VALS());
141
+ const args = {
142
+ event,
143
+ $data: (event as CustomEvent).detail,
144
+ ...hostSet,
145
+ ...SWC_DOM_HELPERS
146
+ };
147
+ FunctionUtils.execute({ script, context: el, args });
146
148
  };
147
149
 
148
150
  el.addEventListener(eventName, handler);
@@ -164,6 +166,10 @@ export const elementDefine =
164
166
  }
165
167
 
166
168
  async connectedCallback() {
169
+ const conf = getElementConfig(this);
170
+ const currentWin = conf?.window || window;
171
+ const currentDoc = currentWin.document;
172
+
167
173
  const hostSet = SwcUtils.getHostSet(this as any);
168
174
 
169
175
  this._executeSwcScript('swc-on-before-connected', hostSet);
@@ -188,21 +194,117 @@ export const elementDefine =
188
194
  if (lContent) this.innerHTML = lContent;
189
195
  }
190
196
 
191
- // --- @addEventListener binding ---
197
+ // --- Optimized Host Delegation ---
198
+ const createDelegationHandler = (delegateHost: any) => (event: Event) => {
199
+ if ((event as any).__swc_handled) return;
200
+
201
+ const path = event.composedPath();
202
+ const type = event.type;
203
+ const attrName = `swc-on-${type}`;
204
+
205
+ for (const node of path) {
206
+ if (node === delegateHost) break;
207
+ if (!(node instanceof HTMLElement)) continue;
208
+
209
+ const script = node.getAttribute?.(attrName);
210
+ if (script && !getElementConfig(node)) {
211
+ const host = SwcUtils.findNearestSwcHost(node);
212
+ if (host) {
213
+ const childHostSet = SwcUtils.getHostSet(node);
214
+ const args = {
215
+ event,
216
+ $data: (event as CustomEvent).detail,
217
+ ...childHostSet,
218
+ ...SWC_DOM_HELPERS
219
+ };
220
+ FunctionUtils.execute({ script, context: node, args });
221
+ (event as any).__swc_handled = true;
222
+ break;
223
+ }
224
+ }
225
+ }
226
+ };
227
+
228
+ // 1. Local Delegation (To itself)
229
+ DOM_EVENT_NAMES.forEach(type => {
230
+ this.addEventListener(type, createDelegationHandler(this));
231
+ this._boundListeners.push({ target: this as any, type, handler: createDelegationHandler(this) });
232
+ });
233
+
234
+ // 2. Structural/Global Delegation (If no parent host exists)
235
+ const hasParentHost = SwcUtils.findAllSwcHosts(this as any).length > 0;
236
+ if (!hasParentHost) {
237
+ const root = this.getRootNode();
238
+ if (!globalDelegatedRoots.has(root)) {
239
+ DOM_EVENT_NAMES.forEach(type => {
240
+ const handler = createDelegationHandler(root);
241
+ root.addEventListener(type, handler);
242
+ });
243
+ globalDelegatedRoots.add(root);
244
+ }
245
+ }
246
+
247
+ // --- @addEventListener binding (Direct/Delegate/Global) ---
192
248
  addEventListenerList.forEach(meta => {
193
- const { type, query, root, capture, once, passive, stopPropagation, stopImmediatePropagation, preventDefault } = meta.options;
194
- const targetEls: (HTMLElement | Element | Document | ShadowRoot | Window)[] = [];
249
+ const { selector, type, options } = meta;
250
+ const { root, capture, once, passive, stopPropagation, stopImmediatePropagation, preventDefault, delegate, removeOnDisconnected } = options;
251
+ const opts = { capture, once, passive };
252
+
253
+ const bindTargets: EventTarget[] = [];
254
+
255
+ if (selector === ':window') {
256
+ bindTargets.push(currentWin);
257
+ } else if (selector === ':document') {
258
+ bindTargets.push(currentDoc);
259
+ } else if (selector === ':parentHost') {
260
+ if (hostSet.$parentHost) bindTargets.push(hostSet.$parentHost);
261
+ } else if (selector === ':appHost') {
262
+ if (hostSet.$appHost) bindTargets.push(hostSet.$appHost);
263
+ } else if (selector === ':firstHost') {
264
+ if (hostSet.$firstHost) bindTargets.push(hostSet.$firstHost);
265
+ } else if (selector === ':lastHost') {
266
+ if (hostSet.$lastHost) bindTargets.push(hostSet.$lastHost);
267
+ } else if (selector === ':firstAppHost') {
268
+ if (hostSet.$firstAppHost) bindTargets.push(hostSet.$firstAppHost);
269
+ } else if (selector === ':lastAppHost') {
270
+ if (hostSet.$lastAppHost) bindTargets.push(hostSet.$lastAppHost);
271
+ } else if (!selector || selector === ':host') {
272
+ bindTargets.push(this as any);
273
+ } else if (delegate) {
274
+ const bindRoots: (HTMLElement | ShadowRoot)[] = [];
275
+ const r = root || 'auto';
276
+ if (r === 'auto') bindRoots.push(this.shadowRoot || (this as any));
277
+ else if (r === 'light') bindRoots.push(this as any);
278
+ else if (r === 'shadow') {
279
+ if (this.shadowRoot) bindRoots.push(this.shadowRoot as any);
280
+ } else if (r === 'all') {
281
+ bindRoots.push(this as any);
282
+ if (this.shadowRoot) bindRoots.push(this.shadowRoot as any);
283
+ }
195
284
 
196
- if (!query) {
197
- targetEls.push(this as any);
285
+ bindRoots.forEach(br => {
286
+ const handler = (event: Event) => {
287
+ const matchedEl = (event.target as HTMLElement).closest(selector);
288
+ if (matchedEl && (br as any).contains(matchedEl)) {
289
+ if (stopPropagation) event.stopPropagation();
290
+ if (stopImmediatePropagation) event.stopImmediatePropagation();
291
+ if (preventDefault) event.preventDefault();
292
+ const currentHostSet = SwcUtils.getHostSet(this as any);
293
+ this._invokeLifecycleMethod(meta.propertyKey, currentHostSet, [event, currentHostSet, matchedEl]);
294
+ }
295
+ };
296
+ br.addEventListener(type, handler, opts);
297
+ const self: any = this;
298
+ if (removeOnDisconnected === true || (br !== self && br !== self.shadowRoot)) {
299
+ this._boundListeners.push({ target: br, type, handler, options: opts });
300
+ }
301
+ });
198
302
  } else {
199
303
  const searchRoots: (HTMLElement | ShadowRoot)[] = [];
200
304
  const r = root || 'auto';
201
- if (r === 'auto') {
202
- searchRoots.push((this.shadowRoot || (this as any)) as any);
203
- } else if (r === 'light') {
204
- searchRoots.push(this as any);
205
- } else if (r === 'shadow') {
305
+ if (r === 'auto') searchRoots.push((this.shadowRoot || (this as any)) as any);
306
+ else if (r === 'light') searchRoots.push(this as any);
307
+ else if (r === 'shadow') {
206
308
  if (this.shadowRoot) searchRoots.push(this.shadowRoot as any);
207
309
  } else if (r === 'all') {
208
310
  searchRoots.push(this as any);
@@ -210,22 +312,22 @@ export const elementDefine =
210
312
  }
211
313
 
212
314
  searchRoots.forEach(sr => {
213
- const found = sr.querySelectorAll(query);
214
- found.forEach(el => targetEls.push(el as any));
315
+ sr.querySelectorAll(selector).forEach(el => bindTargets.push(el));
215
316
  });
216
317
  }
217
318
 
218
- targetEls.forEach(el => {
319
+ bindTargets.forEach(target => {
219
320
  const handler = (event: Event) => {
220
321
  if (stopPropagation) event.stopPropagation();
221
322
  if (stopImmediatePropagation) event.stopImmediatePropagation();
222
323
  if (preventDefault) event.preventDefault();
223
-
224
- const hostSet = SwcUtils.getHostSet(this as any);
225
- this._invokeLifecycleMethod(meta.propertyKey, hostSet, [event, hostSet]);
324
+ const currentHostSet = SwcUtils.getHostSet(this as any);
325
+ this._invokeLifecycleMethod(meta.propertyKey, currentHostSet, [event, currentHostSet]);
226
326
  };
227
- const options = { capture, once, passive };
228
- el.addEventListener(type, handler, options);
327
+ target.addEventListener(type, handler, opts);
328
+ if (removeOnDisconnected === true || target === currentWin || target === currentDoc) {
329
+ this._boundListeners.push({ target, type, handler, options: opts });
330
+ }
229
331
  });
230
332
  });
231
333
 
@@ -245,6 +347,9 @@ export const elementDefine =
245
347
  const bMethods = findAllLifecycleMetadata(this, ON_BEFORE_DISCONNECTED_METADATA_KEY);
246
348
  if (bMethods) bMethods.forEach(m => this._invokeLifecycleMethod(m, hostSet));
247
349
 
350
+ this._boundListeners.forEach(l => l.target.removeEventListener(l.type, l.handler, l.options));
351
+ this._boundListeners = [];
352
+
248
353
  if (super.disconnectedCallback) super.disconnectedCallback();
249
354
 
250
355
  const aMethods = findAllLifecycleMetadata(this, ON_AFTER_DISCONNECTED_METADATA_KEY);
@@ -272,7 +377,9 @@ export const elementDefine =
272
377
 
273
378
  if (name.startsWith('swc-on-') && !swcLifecycleAttributes.includes(name)) {
274
379
  if (newVal !== null) {
275
- this._bindAttributeEvent(this as any, name, newVal);
380
+ const customEventMeta = emitCustomEventList.find(it => it.options.attributeName === name);
381
+ const eventName = customEventMeta ? customEventMeta.options.type : name.substring(7);
382
+ this._bindAttributeEvent(this as any, name, newVal, eventName);
276
383
  }
277
384
  }
278
385
 
@@ -299,6 +406,25 @@ export const elementDefine =
299
406
  }
300
407
  });
301
408
 
409
+ // --- @emitCustomEvent wrapping logic ---
410
+ emitCustomEventList.forEach(meta => {
411
+ const { propertyKey, options } = meta;
412
+ const original = NewClass.prototype[propertyKey as any];
413
+ if (typeof original === 'function') {
414
+ NewClass.prototype[propertyKey as any] = function (...args: any[]) {
415
+ const detail = original.apply(this, args);
416
+ const event = new CustomEvent(options.type, {
417
+ detail,
418
+ bubbles: options.bubbles,
419
+ composed: options.composed,
420
+ cancelable: options.cancelable
421
+ });
422
+ this.dispatchEvent(event);
423
+ return detail;
424
+ };
425
+ }
426
+ });
427
+
302
428
  const originalProto = constructor.prototype;
303
429
  const newProto = NewClass.prototype;
304
430
  Object.getOwnPropertyNames(originalProto).forEach(name => {
@@ -2,9 +2,9 @@ import { ReflectUtils } from '@dooboostore/core/reflect/ReflectUtils';
2
2
 
3
3
  export interface EmitCustomEventOptions {
4
4
  type: string;
5
- attributeName?: string; // Default: on + type
6
- bubbles?: boolean; // Default: true
7
- composed?: boolean; // Default: true
5
+ attributeName?: string;
6
+ bubbles?: boolean;
7
+ composed?: boolean;
8
8
  cancelable?: boolean;
9
9
  }
10
10
 
@@ -16,12 +16,12 @@ export interface EmitCustomEventMetadata {
16
16
  export const EMIT_CUSTOM_EVENT_METADATA_KEY = Symbol('simple-web-component:emit-custom-event');
17
17
 
18
18
  export function emitCustomEvent(typeOrOptions: string | EmitCustomEventOptions): MethodDecorator {
19
- return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
19
+ return (target: Object, propertyKey: string | symbol) => {
20
20
  const constructor = target.constructor;
21
21
  const options: EmitCustomEventOptions = typeof typeOrOptions === 'string' ? { type: typeOrOptions } : typeOrOptions;
22
22
 
23
23
  if (!options.attributeName) {
24
- options.attributeName = `on${options.type}`;
24
+ options.attributeName = `swc-on-${options.type}`;
25
25
  }
26
26
  if (options.bubbles === undefined) options.bubbles = true;
27
27
  if (options.composed === undefined) options.composed = true;
@@ -32,22 +32,6 @@ export function emitCustomEvent(typeOrOptions: string | EmitCustomEventOptions):
32
32
  ReflectUtils.defineMetadata(EMIT_CUSTOM_EVENT_METADATA_KEY, list, constructor);
33
33
  }
34
34
  list.push({ propertyKey, options });
35
-
36
- // Wrap the original method to dispatch event after execution
37
- const originalMethod = descriptor.value;
38
- descriptor.value = function (this: HTMLElement, ...args: any[]) {
39
- const detail = originalMethod.apply(this, args);
40
-
41
- const event = new CustomEvent(options.type, {
42
- detail,
43
- bubbles: options.bubbles,
44
- composed: options.composed,
45
- cancelable: options.cancelable
46
- });
47
-
48
- this.dispatchEvent(event);
49
- return detail;
50
- };
51
35
  };
52
36
  }
53
37
 
@@ -1,7 +1,8 @@
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
- selector?: string;
5
6
  root?: 'light' | 'shadow' | 'all' | 'auto';
6
7
  }
7
8
 
@@ -9,61 +10,65 @@ export interface QueryMetadata {
9
10
  selector: string;
10
11
  options: QueryOptions;
11
12
  propertyKey: string | symbol;
12
- isMethod: boolean;
13
13
  }
14
14
 
15
15
  export const QUERY_METADATA_KEY = Symbol('simple-web-component:query');
16
16
 
17
- export function query(selector: string): any;
18
- export function query(options: QueryOptions): any;
19
- export function query(target: Object, propertyKey: string | symbol): void;
20
- export function query(arg1?: string | QueryOptions | Object, arg2?: string | symbol): any {
21
- const decorator = (selector: string, options: QueryOptions, target: Object, propertyKey: string | symbol, descriptor?: PropertyDescriptor) => {
22
- const isMethod = !!descriptor;
23
- const constructor = target.constructor;
17
+ /**
18
+ * @query decorator to inject a single element into a class property.
19
+ */
20
+ export function query(selector: string, options: QueryOptions = {}): PropertyDecorator {
21
+ return (target: Object, propertyKey: string | symbol, descriptor?: never): void => {
22
+ if (descriptor !== undefined) {
23
+ throw new Error(`@query decorator cannot be used on methods. (Method: ${String(propertyKey)})`);
24
+ }
24
25
 
26
+ const constructor = target.constructor;
25
27
  let queries = ReflectUtils.getMetadata<QueryMetadata[]>(QUERY_METADATA_KEY, constructor);
26
28
  if (!queries) {
27
29
  queries = [];
28
30
  ReflectUtils.defineMetadata(QUERY_METADATA_KEY, queries, constructor);
29
31
  }
30
- queries.push({ selector, options, propertyKey, isMethod });
32
+ queries.push({ selector, options, propertyKey });
31
33
 
32
- if (!isMethod) {
33
- Object.defineProperty(target, propertyKey, {
34
- get(this: HTMLElement) {
35
- if (!selector) return this;
36
- let searchRoot: Node = this;
37
- if (options.root === 'shadow') searchRoot = this.shadowRoot || this;
38
- else if (options.root === 'light') searchRoot = this;
39
- else searchRoot = this.shadowRoot || this; // Default: Auto (Shadow-First)
34
+ Object.defineProperty(target, propertyKey, {
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;
40
44
 
41
- return (searchRoot as HTMLElement).querySelector(selector);
42
- },
43
- enumerable: true,
44
- configurable: true
45
- });
46
- }
47
- };
48
-
49
- // Case: @query (parameterless decorator)
50
- if (arg1 && typeof arg2 === 'string') {
51
- return decorator('', {}, arg1 as Object, arg2 as string | symbol);
52
- }
45
+ // --- Special Selectors: Env ---
46
+ const config = getElementConfig(this);
47
+ const win = config?.window || window;
48
+ const doc = win.document;
53
49
 
54
- // Case: @query('selector') or @query({ selector: '...', ... })
55
- return (target: Object, propertyKey: string | symbol, descriptor?: PropertyDescriptor) => {
56
- let selector = '';
57
- let options: QueryOptions = {};
50
+ if (selector === ':window') return win;
51
+ if (selector === ':document') return doc;
52
+ if (!selector || selector === ':host') return this;
58
53
 
59
- if (typeof arg1 === 'string') {
60
- selector = arg1;
61
- } else if (typeof arg1 === 'object') {
62
- options = arg1 as QueryOptions;
63
- selector = options.selector || '';
64
- }
54
+ // --- Standard Selectors ---
55
+ const r = options.root || 'auto';
65
56
 
66
- decorator(selector, options, target, propertyKey, descriptor);
57
+ if (r === 'shadow') {
58
+ return this.shadowRoot ? this.shadowRoot.querySelector(selector) : null;
59
+ }
60
+ if (r === 'light') {
61
+ return this.querySelector(selector);
62
+ }
63
+ if (r === 'all') {
64
+ return this.shadowRoot?.querySelector(selector) || this.querySelector(selector);
65
+ }
66
+ // auto
67
+ return (this.shadowRoot || this).querySelector(selector);
68
+ },
69
+ enumerable: true,
70
+ configurable: true
71
+ });
67
72
  };
68
73
  }
69
74