@dooboostore/simple-web-component 1.0.16 → 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 (98) hide show
  1. package/README.md +93 -0
  2. package/dist/cjs/decorators/addEventListener.js.map +2 -2
  3. package/dist/cjs/decorators/elementDefine.js +133 -62
  4. package/dist/cjs/decorators/elementDefine.js.map +3 -3
  5. package/dist/cjs/decorators/query.js +22 -0
  6. package/dist/cjs/decorators/query.js.map +2 -2
  7. package/dist/cjs/decorators/queryAll.js +14 -0
  8. package/dist/cjs/decorators/queryAll.js.map +2 -2
  9. package/dist/cjs/elements/SwcAppEngine.js +2 -1
  10. package/dist/cjs/elements/SwcAppEngine.js.map +2 -2
  11. package/dist/cjs/elements/SwcAsync.js +188 -0
  12. package/dist/cjs/elements/SwcAsync.js.map +7 -0
  13. package/dist/cjs/elements/SwcChoose.js +155 -0
  14. package/dist/cjs/elements/SwcChoose.js.map +7 -0
  15. package/dist/cjs/elements/SwcIf.js +167 -0
  16. package/dist/cjs/elements/SwcIf.js.map +7 -0
  17. package/dist/cjs/elements/SwcLoop.js +246 -0
  18. package/dist/cjs/elements/SwcLoop.js.map +7 -0
  19. package/dist/cjs/elements/SwcSubTemplates.js +65 -0
  20. package/dist/cjs/elements/SwcSubTemplates.js.map +7 -0
  21. package/dist/cjs/elements/index.js +7 -0
  22. package/dist/cjs/elements/index.js.map +2 -2
  23. package/dist/cjs/index.js +1 -1
  24. package/dist/cjs/index.js.map +2 -2
  25. package/dist/cjs/types.js.map +1 -1
  26. package/dist/cjs/utils/Utils.js +74 -37
  27. package/dist/cjs/utils/Utils.js.map +3 -3
  28. package/dist/esm/decorators/addEventListener.js.map +2 -2
  29. package/dist/esm/decorators/elementDefine.js +133 -62
  30. package/dist/esm/decorators/elementDefine.js.map +3 -3
  31. package/dist/esm/decorators/query.js +22 -0
  32. package/dist/esm/decorators/query.js.map +2 -2
  33. package/dist/esm/decorators/queryAll.js +14 -0
  34. package/dist/esm/decorators/queryAll.js.map +2 -2
  35. package/dist/esm/elements/SwcAppEngine.js +2 -1
  36. package/dist/esm/elements/SwcAppEngine.js.map +2 -2
  37. package/dist/esm/elements/SwcAsync.js +169 -0
  38. package/dist/esm/elements/SwcAsync.js.map +7 -0
  39. package/dist/esm/elements/SwcChoose.js +136 -0
  40. package/dist/esm/elements/SwcChoose.js.map +7 -0
  41. package/dist/esm/elements/SwcIf.js +148 -0
  42. package/dist/esm/elements/SwcIf.js.map +7 -0
  43. package/dist/esm/elements/SwcLoop.js +227 -0
  44. package/dist/esm/elements/SwcLoop.js.map +7 -0
  45. package/dist/esm/elements/SwcSubTemplates.js +46 -0
  46. package/dist/esm/elements/SwcSubTemplates.js.map +7 -0
  47. package/dist/esm/elements/index.js +7 -0
  48. package/dist/esm/elements/index.js.map +2 -2
  49. package/dist/esm/index.js +1 -1
  50. package/dist/esm/index.js.map +1 -1
  51. package/dist/esm/types.js.map +1 -1
  52. package/dist/esm/utils/Utils.js +74 -37
  53. package/dist/esm/utils/Utils.js.map +3 -3
  54. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +1047 -140
  55. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  56. package/dist/types/decorators/addEventListener.d.ts +1 -0
  57. package/dist/types/decorators/addEventListener.d.ts.map +1 -1
  58. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  59. package/dist/types/decorators/query.d.ts +0 -2
  60. package/dist/types/decorators/query.d.ts.map +1 -1
  61. package/dist/types/decorators/queryAll.d.ts +0 -2
  62. package/dist/types/decorators/queryAll.d.ts.map +1 -1
  63. package/dist/types/elements/SwcAppEngine.d.ts.map +1 -1
  64. package/dist/types/elements/SwcAsync.d.ts +16 -0
  65. package/dist/types/elements/SwcAsync.d.ts.map +1 -0
  66. package/dist/types/elements/SwcChoose.d.ts +13 -0
  67. package/dist/types/elements/SwcChoose.d.ts.map +1 -0
  68. package/dist/types/elements/SwcIf.d.ts +15 -0
  69. package/dist/types/elements/SwcIf.d.ts.map +1 -0
  70. package/dist/types/elements/SwcLoop.d.ts +20 -0
  71. package/dist/types/elements/SwcLoop.d.ts.map +1 -0
  72. package/dist/types/elements/SwcSubTemplates.d.ts +13 -0
  73. package/dist/types/elements/SwcSubTemplates.d.ts.map +1 -0
  74. package/dist/types/elements/index.d.ts +7 -0
  75. package/dist/types/elements/index.d.ts.map +1 -1
  76. package/dist/types/index.d.ts +1 -1
  77. package/dist/types/index.d.ts.map +1 -1
  78. package/dist/types/types.d.ts +8 -0
  79. package/dist/types/types.d.ts.map +1 -1
  80. package/dist/types/utils/Utils.d.ts +16 -10
  81. package/dist/types/utils/Utils.d.ts.map +1 -1
  82. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +1047 -140
  83. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  84. package/package.json +10 -3
  85. package/src/decorators/addEventListener.ts +1 -0
  86. package/src/decorators/elementDefine.ts +137 -66
  87. package/src/decorators/query.ts +20 -2
  88. package/src/decorators/queryAll.ts +16 -2
  89. package/src/elements/SwcAppEngine.ts +2 -1
  90. package/src/elements/SwcAsync.ts +163 -0
  91. package/src/elements/SwcChoose.ts +127 -0
  92. package/src/elements/SwcIf.ts +134 -0
  93. package/src/elements/SwcLoop.ts +215 -0
  94. package/src/elements/SwcSubTemplates.ts +19 -0
  95. package/src/elements/index.ts +8 -3
  96. package/src/index.ts +1 -1
  97. package/src/types.ts +9 -1
  98. package/src/utils/Utils.ts +86 -36
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.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",
@@ -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 {
@@ -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
  }
@@ -142,9 +138,13 @@ export const elementDefine =
142
138
 
143
139
  const handler = (event: any) => {
144
140
  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());
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 });
148
148
  };
149
149
 
150
150
  el.addEventListener(eventName, handler);
@@ -166,6 +166,10 @@ export const elementDefine =
166
166
  }
167
167
 
168
168
  async connectedCallback() {
169
+ const conf = getElementConfig(this);
170
+ const currentWin = conf?.window || window;
171
+ const currentDoc = currentWin.document;
172
+
169
173
  const hostSet = SwcUtils.getHostSet(this as any);
170
174
 
171
175
  this._executeSwcScript('swc-on-before-connected', hostSet);
@@ -190,77 +194,141 @@ export const elementDefine =
190
194
  if (lContent) this.innerHTML = lContent;
191
195
  }
192
196
 
193
- // --- @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) ---
194
248
  addEventListenerList.forEach(meta => {
195
249
  const { selector, type, options } = meta;
196
- const { root, capture, once, passive, stopPropagation, stopImmediatePropagation, preventDefault, delegate } = options;
197
-
198
- if (delegate && selector && selector !== ':host') {
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) {
199
274
  const bindRoots: (HTMLElement | ShadowRoot)[] = [];
200
275
  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') {
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') {
206
279
  if (this.shadowRoot) bindRoots.push(this.shadowRoot as any);
207
280
  } else if (r === 'all') {
208
281
  bindRoots.push(this as any);
209
282
  if (this.shadowRoot) bindRoots.push(this.shadowRoot as any);
210
283
  }
211
284
 
212
- bindRoots.forEach(bindRoot => {
285
+ bindRoots.forEach(br => {
213
286
  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)) {
287
+ const matchedEl = (event.target as HTMLElement).closest(selector);
288
+ if (matchedEl && (br as any).contains(matchedEl)) {
217
289
  if (stopPropagation) event.stopPropagation();
218
290
  if (stopImmediatePropagation) event.stopImmediatePropagation();
219
291
  if (preventDefault) event.preventDefault();
220
-
221
- const hostSet = SwcUtils.getHostSet(this as any);
222
- this._invokeLifecycleMethod(meta.propertyKey, hostSet, [event, hostSet, matchedEl]);
292
+ const currentHostSet = SwcUtils.getHostSet(this as any);
293
+ this._invokeLifecycleMethod(meta.propertyKey, currentHostSet, [event, currentHostSet, matchedEl]);
223
294
  }
224
295
  };
225
- bindRoot.addEventListener(type, handler, { capture, once, passive });
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
+ }
226
301
  });
227
302
  } 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
- });
303
+ const searchRoots: (HTMLElement | ShadowRoot)[] = [];
304
+ const r = root || 'auto';
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') {
308
+ if (this.shadowRoot) searchRoots.push(this.shadowRoot as any);
309
+ } else if (r === 'all') {
310
+ searchRoots.push(this as any);
311
+ if (this.shadowRoot) searchRoots.push(this.shadowRoot as any);
249
312
  }
250
313
 
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);
314
+ searchRoots.forEach(sr => {
315
+ sr.querySelectorAll(selector).forEach(el => bindTargets.push(el));
262
316
  });
263
317
  }
318
+
319
+ bindTargets.forEach(target => {
320
+ const handler = (event: Event) => {
321
+ if (stopPropagation) event.stopPropagation();
322
+ if (stopImmediatePropagation) event.stopImmediatePropagation();
323
+ if (preventDefault) event.preventDefault();
324
+ const currentHostSet = SwcUtils.getHostSet(this as any);
325
+ this._invokeLifecycleMethod(meta.propertyKey, currentHostSet, [event, currentHostSet]);
326
+ };
327
+ target.addEventListener(type, handler, opts);
328
+ if (removeOnDisconnected === true || target === currentWin || target === currentDoc) {
329
+ this._boundListeners.push({ target, type, handler, options: opts });
330
+ }
331
+ });
264
332
  });
265
333
 
266
334
  if (super.connectedCallback) await super.connectedCallback();
@@ -279,6 +347,9 @@ export const elementDefine =
279
347
  const bMethods = findAllLifecycleMetadata(this, ON_BEFORE_DISCONNECTED_METADATA_KEY);
280
348
  if (bMethods) bMethods.forEach(m => this._invokeLifecycleMethod(m, hostSet));
281
349
 
350
+ this._boundListeners.forEach(l => l.target.removeEventListener(l.type, l.handler, l.options));
351
+ this._boundListeners = [];
352
+
282
353
  if (super.disconnectedCallback) super.disconnectedCallback();
283
354
 
284
355
  const aMethods = findAllLifecycleMetadata(this, ON_AFTER_DISCONNECTED_METADATA_KEY);
@@ -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') {
@@ -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 QueryAllOptions {
4
6
  root?: 'light' | 'shadow' | 'all' | 'auto';
@@ -14,8 +16,6 @@ export const QUERY_ALL_METADATA_KEY = Symbol('simple-web-component:query-all');
14
16
 
15
17
  /**
16
18
  * @queryAll decorator to inject a list of elements into a class property.
17
- * Usage: @queryAll('.items') list!: NodeListOf<HTMLElement>;
18
- * Usage: @queryAll('li', { root: 'shadow' }) list!: HTMLElement[];
19
19
  */
20
20
  export function queryAll(selector: string, options: QueryAllOptions = {}): PropertyDecorator {
21
21
  return (target: Object, propertyKey: string | symbol, descriptor?: never): void => {
@@ -33,7 +33,21 @@ export function queryAll(selector: string, options: QueryAllOptions = {}): Prope
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 === ':hosts') return hostSet.$hosts;
39
+ if (selector === ':appHosts') return hostSet.$appHosts;
40
+
41
+ // --- Special Selectors: Env ---
42
+ const config = getElementConfig(this);
43
+ const win = config?.window || window;
44
+ const doc = win.document;
45
+
46
+ if (selector === ':window') return [win];
47
+ if (selector === ':document') return [doc];
36
48
  if (!selector || selector === ':host') return [this];
49
+
50
+ // --- Standard Selectors ---
37
51
  const r = options.root || 'auto';
38
52
 
39
53
  if (r === 'shadow') {
@@ -6,6 +6,7 @@ import { SimOption } from '@dooboostore/simple-boot/SimOption';
6
6
  import { Router, RouterEventType } from '@dooboostore/core-web/routers/Router';
7
7
  import { Subscription } from '@dooboostore/core/message/Subscription';
8
8
  import { ConstructorType, Optional } from '@dooboostore/core/types';
9
+ import { FunctionUtils } from '@dooboostore/core/function/FunctionUtils';
9
10
 
10
11
  export type SwcConfigType = {
11
12
  rootRouter: ConstructorType<any>;
@@ -35,7 +36,7 @@ export class SwcAppEngine {
35
36
  if (!configStr) return;
36
37
 
37
38
  try {
38
- const userConfig: SwcAttributeConfigType = new Function(`return ${configStr}`).apply(this.host);
39
+ const userConfig: SwcAttributeConfigType = FunctionUtils.executeReturn({ script: configStr, context: this.host });
39
40
  if (userConfig && userConfig.rootRouter) {
40
41
  const _config: SwcConfigType = {
41
42
  routeType: 'element',
@@ -0,0 +1,163 @@
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
+ import { SwcLoading, SwcError, SwcSuccess, SwcDefault } from './SwcSubTemplates';
6
+
7
+ @elementDefine({ name: 'swc-async', extends: 'template' })
8
+ export class SwcAsync extends HTMLTemplateElement {
9
+ private _nodes: Node[] = [];
10
+ private _currentPromise: Promise<any> | null = null;
11
+ private _value: any = null;
12
+
13
+ get value(): any {
14
+ return this._value;
15
+ }
16
+
17
+ set value(nv: any) {
18
+ if (nv instanceof Promise) {
19
+ this.handlePromise(nv);
20
+ } else {
21
+ this._value = nv;
22
+ this.render(nv === null ? 'default' : 'success', nv);
23
+ }
24
+ }
25
+
26
+ @changedAttribute('on-get-value')
27
+ private refresh(newValue?: string | null) {
28
+ const script = newValue !== undefined ? newValue : this.getAttribute('on-get-value');
29
+ if (!script) {
30
+ this.value = null; // Triggers default render
31
+ return;
32
+ }
33
+ if (!this.parentElement) return;
34
+
35
+ const helperHostSet = SwcUtils.getHelperAndHostSet(window, this);
36
+ try {
37
+ const result = FunctionUtils.executeReturn({
38
+ script,
39
+ context: this,
40
+ args: helperHostSet
41
+ });
42
+
43
+ if (result instanceof Promise) {
44
+ this.handlePromise(result);
45
+ } else {
46
+ this.value = result;
47
+ }
48
+ } catch (e) {
49
+ this.render('error', e);
50
+ }
51
+ }
52
+
53
+ private async handlePromise(promise: Promise<any>) {
54
+ this._currentPromise = promise;
55
+ this.render('loading');
56
+
57
+ try {
58
+ const result = await promise;
59
+ if (this._currentPromise !== promise) return;
60
+ this._value = result;
61
+ this.render('success', result);
62
+ } catch (e) {
63
+ if (this._currentPromise !== promise) return;
64
+ this.render('error', e);
65
+ }
66
+ }
67
+
68
+ private render(state: 'loading' | 'error' | 'success' | 'default', data?: any) {
69
+ this.cleanup();
70
+ if (!this.parentElement) return;
71
+
72
+ let fragment: DocumentFragment;
73
+ const ctx: any = {
74
+ $nodes: [],
75
+ $elements: [],
76
+ $state: state,
77
+ $value: null,
78
+ $error: null
79
+ };
80
+
81
+ if (state === 'default') {
82
+ const t = this.content.querySelector('template[is="swc-default"]') as HTMLTemplateElement;
83
+ fragment = t ? (t.content.cloneNode(true) as DocumentFragment) : document.createDocumentFragment();
84
+ } else if (state === 'loading') {
85
+ const t = this.content.querySelector('template[is="swc-loading"]') as HTMLTemplateElement;
86
+ fragment = t ? (t.content.cloneNode(true) as DocumentFragment) : document.createDocumentFragment();
87
+ } else if (state === 'error') {
88
+ const t = this.content.querySelector('template[is="swc-error"]') as HTMLTemplateElement;
89
+ ctx.$error = data;
90
+ fragment = t ? (t.content.cloneNode(true) as DocumentFragment) : document.createDocumentFragment();
91
+ } else {
92
+ // Success
93
+ const t = this.content.querySelector('template[is="swc-success"]') as HTMLTemplateElement;
94
+ ctx.$value = data;
95
+ if (t) {
96
+ fragment = t.content.cloneNode(true) as DocumentFragment;
97
+ } else {
98
+ const clone = this.content.cloneNode(true) as DocumentFragment;
99
+ Array.from(clone.querySelectorAll('template[is="swc-loading"], template[is="swc-error"], template[is="swc-success"], template[is="swc-default"]')).forEach(tmp => tmp.remove());
100
+ fragment = clone;
101
+ }
102
+ }
103
+
104
+ this.mount(fragment, ctx);
105
+ }
106
+
107
+ private mount(fragment: DocumentFragment, ctx: any) {
108
+ const nodes = Array.from(fragment.childNodes);
109
+ const elements = nodes.filter(n => n.nodeType === 1) as HTMLElement[];
110
+
111
+ Object.assign(ctx, {
112
+ $nodes: nodes,
113
+ $elements: elements,
114
+ $firstNode: nodes[0],
115
+ $lastNode: nodes[nodes.length - 1],
116
+ $firstElement: elements[0],
117
+ $lastElement: elements[elements.length - 1]
118
+ });
119
+
120
+ // Execution Callbacks
121
+ this._executeCallback('on-clone-nodes', ctx);
122
+ nodes.forEach((node, nodeIndex) => {
123
+ if (node instanceof HTMLElement) {
124
+ (node as any).__swc_host = this;
125
+ }
126
+ this._executeCallback('on-clone-node', {
127
+ ...ctx,
128
+ $node: node,
129
+ $nodeIndex: nodeIndex,
130
+ $isElement: node.nodeType === 1
131
+ });
132
+ });
133
+
134
+ this._nodes = nodes;
135
+ this.parentElement?.insertBefore(fragment, this.nextSibling);
136
+ }
137
+
138
+ private _executeCallback(attrName: string, extraArgs: Record<string, any>) {
139
+ const script = this.getAttribute(attrName);
140
+ if (!script) return;
141
+ const helperHostSet = SwcUtils.getHelperAndHostSet(window, this);
142
+ try {
143
+ FunctionUtils.execute({ script, context: this, args: { ...helperHostSet, ...extraArgs } });
144
+ } catch (e) {
145
+ console.error(`[SwcAsync] Error in ${attrName}:`, e);
146
+ }
147
+ }
148
+
149
+ private cleanup() {
150
+ this._nodes.forEach(n => {
151
+ if (n.parentElement) n.parentElement.removeChild(n);
152
+ });
153
+ this._nodes = [];
154
+ }
155
+
156
+ connectedCallback() {
157
+ this.refresh();
158
+ }
159
+
160
+ disconnectedCallback() {
161
+ this.cleanup();
162
+ }
163
+ }