@dooboostore/simple-web-component 1.0.44 → 1.0.45

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 (75) hide show
  1. package/README.md +25 -25
  2. package/dist/cjs/decorators/addEventListener.js.map +2 -2
  3. package/dist/cjs/decorators/applyAttribute.js +36 -15
  4. package/dist/cjs/decorators/applyAttribute.js.map +2 -2
  5. package/dist/cjs/decorators/applyClass.js +53 -22
  6. package/dist/cjs/decorators/applyClass.js.map +2 -2
  7. package/dist/cjs/decorators/applyNode.js +51 -23
  8. package/dist/cjs/decorators/applyNode.js.map +2 -2
  9. package/dist/cjs/decorators/applyProperty.js +38 -17
  10. package/dist/cjs/decorators/applyProperty.js.map +2 -2
  11. package/dist/cjs/decorators/applyStyle.js +53 -22
  12. package/dist/cjs/decorators/applyStyle.js.map +2 -2
  13. package/dist/cjs/decorators/elementDefine.js +44 -25
  14. package/dist/cjs/decorators/elementDefine.js.map +2 -2
  15. package/dist/cjs/decorators/emitCustomEvent.js +61 -47
  16. package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
  17. package/dist/cjs/decorators/query.js +91 -56
  18. package/dist/cjs/decorators/query.js.map +2 -2
  19. package/dist/cjs/decorators/queryAll.js +104 -66
  20. package/dist/cjs/decorators/queryAll.js.map +2 -2
  21. package/dist/cjs/types/window.d.js.map +1 -1
  22. package/dist/esm/decorators/addEventListener.js.map +2 -2
  23. package/dist/esm/decorators/applyAttribute.js +36 -15
  24. package/dist/esm/decorators/applyAttribute.js.map +2 -2
  25. package/dist/esm/decorators/applyClass.js +53 -22
  26. package/dist/esm/decorators/applyClass.js.map +2 -2
  27. package/dist/esm/decorators/applyNode.js +51 -23
  28. package/dist/esm/decorators/applyNode.js.map +2 -2
  29. package/dist/esm/decorators/applyProperty.js +38 -17
  30. package/dist/esm/decorators/applyProperty.js.map +2 -2
  31. package/dist/esm/decorators/applyStyle.js +53 -22
  32. package/dist/esm/decorators/applyStyle.js.map +2 -2
  33. package/dist/esm/decorators/elementDefine.js +44 -25
  34. package/dist/esm/decorators/elementDefine.js.map +2 -2
  35. package/dist/esm/decorators/emitCustomEvent.js +61 -47
  36. package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
  37. package/dist/esm/decorators/query.js +91 -56
  38. package/dist/esm/decorators/query.js.map +2 -2
  39. package/dist/esm/decorators/queryAll.js +104 -66
  40. package/dist/esm/decorators/queryAll.js.map +2 -2
  41. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +531 -293
  42. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +3 -3
  43. package/dist/types/decorators/addEventListener.d.ts +7 -6
  44. package/dist/types/decorators/addEventListener.d.ts.map +1 -1
  45. package/dist/types/decorators/applyAttribute.d.ts +12 -8
  46. package/dist/types/decorators/applyAttribute.d.ts.map +1 -1
  47. package/dist/types/decorators/applyClass.d.ts +8 -7
  48. package/dist/types/decorators/applyClass.d.ts.map +1 -1
  49. package/dist/types/decorators/applyNode.d.ts +18 -17
  50. package/dist/types/decorators/applyNode.d.ts.map +1 -1
  51. package/dist/types/decorators/applyProperty.d.ts +13 -9
  52. package/dist/types/decorators/applyProperty.d.ts.map +1 -1
  53. package/dist/types/decorators/applyStyle.d.ts +6 -5
  54. package/dist/types/decorators/applyStyle.d.ts.map +1 -1
  55. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  56. package/dist/types/decorators/emitCustomEvent.d.ts +3 -2
  57. package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
  58. package/dist/types/decorators/query.d.ts +15 -5
  59. package/dist/types/decorators/query.d.ts.map +1 -1
  60. package/dist/types/decorators/queryAll.d.ts +15 -5
  61. package/dist/types/decorators/queryAll.d.ts.map +1 -1
  62. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +531 -293
  63. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +3 -3
  64. package/package.json +3 -3
  65. package/src/decorators/addEventListener.ts +9 -7
  66. package/src/decorators/applyAttribute.ts +59 -27
  67. package/src/decorators/applyClass.ts +78 -32
  68. package/src/decorators/applyNode.ts +115 -72
  69. package/src/decorators/applyProperty.ts +62 -30
  70. package/src/decorators/applyStyle.ts +76 -30
  71. package/src/decorators/elementDefine.ts +49 -26
  72. package/src/decorators/emitCustomEvent.ts +63 -39
  73. package/src/decorators/query.ts +111 -49
  74. package/src/decorators/queryAll.ts +121 -61
  75. package/src/types/window.d.ts +3 -0
@@ -15,17 +15,86 @@ export interface StyleApplyOptions extends SwcQueryOptions {
15
15
  valueKey?: symbol | string;
16
16
  }
17
17
 
18
+ export type StyleSelector = string | ((currentThis: any, helper: HelperHostSet) => NodeList | Element | Element[] | null);
19
+
18
20
  export interface StyleApplyMetadata {
19
21
  propertyKey: string | symbol;
20
- selector: string;
22
+ selector: StyleSelector;
21
23
  action: StyleAction;
22
24
  options: StyleApplyOptions;
23
25
  }
24
26
 
25
27
  export const STYLE_METADATA_KEY = Symbol.for('simple-web-component:style');
26
28
 
29
+ /**
30
+ * Resolve selector to string or elements
31
+ * Handles function-based selectors and returns elements if applicable
32
+ */
33
+ const resolveSelector = (selector: StyleSelector, inst: any, win: Window): string | HTMLElement[] => {
34
+ if (typeof selector === 'function') {
35
+ const hostSet = SwcUtils.getHelperAndHostSet(win, inst);
36
+ const result = selector(inst, hostSet);
37
+
38
+ if (result instanceof win.Element) {
39
+ return [result as HTMLElement];
40
+ }
41
+ if (result instanceof win.NodeList) {
42
+ return Array.from(result) as HTMLElement[];
43
+ }
44
+ if (Array.isArray(result)) {
45
+ return result as HTMLElement[];
46
+ }
47
+ if (result === null) {
48
+ return [];
49
+ }
50
+ return result as string;
51
+ }
52
+ return selector as string;
53
+ };
54
+
55
+ /**
56
+ * Get target elements from selector
57
+ */
58
+ const getTargetElements = (selector: StyleSelector, inst: any, win: Window, root: string): HTMLElement[] => {
59
+ const resolved = resolveSelector(selector, inst, win);
60
+
61
+ if (Array.isArray(resolved)) {
62
+ return resolved;
63
+ }
64
+
65
+ const stringSelector = resolved as string;
66
+ const targetEls: HTMLElement[] = [];
67
+ const host = inst as unknown as HTMLElement;
68
+ const hostSet = SwcUtils.getHelperAndHostSet(win, inst);
69
+
70
+ const applyRoot = (t: any) => {
71
+ if (!t || !(t instanceof win.HTMLElement)) return;
72
+ targetEls.push(t);
73
+ };
74
+
75
+ if (stringSelector === '$this' || !stringSelector) applyRoot(host);
76
+ else if (stringSelector === '$host') applyRoot(hostSet.$host);
77
+ else if (stringSelector === '$parentHost') applyRoot(hostSet.$parentHost);
78
+ else if (stringSelector === '$appHost') applyRoot(hostSet.$appHost);
79
+ else if (stringSelector === '$firstHost') applyRoot(hostSet.$firstHost);
80
+ else if (stringSelector === '$lastHost') applyRoot(hostSet.$lastHost);
81
+ else if (stringSelector === '$firstAppHost') applyRoot(hostSet.$firstAppHost);
82
+ else if (stringSelector === '$lastAppHost') applyRoot(hostSet.$lastAppHost);
83
+ else if (stringSelector === '$hosts') hostSet.$hosts.forEach(applyRoot);
84
+ else if (stringSelector === '$appHosts') hostSet.$appHosts.forEach(applyRoot);
85
+ else {
86
+ const targetRoot = root === 'auto' ? (host.shadowRoot || host) : (root === 'shadow' ? host.shadowRoot : host);
87
+ if (targetRoot) {
88
+ const found = targetRoot.querySelectorAll(stringSelector);
89
+ if (found) targetEls.push(...(Array.from(found) as HTMLElement[]));
90
+ }
91
+ }
92
+
93
+ return targetEls;
94
+ };
95
+
27
96
  function createStyleDecorator(action: StyleAction) {
28
- return (selector: string, options: StyleApplyOptions = {}): MethodDecorator => {
97
+ return (selector: StyleSelector, options: StyleApplyOptions = {}): MethodDecorator => {
29
98
  return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
30
99
  const constructor = target.constructor;
31
100
  let metaList = ReflectUtils.getOwnMetadata(STYLE_METADATA_KEY, constructor) as Map<string | symbol, StyleApplyMetadata>;
@@ -62,30 +131,7 @@ function createStyleDecorator(action: StyleAction) {
62
131
  const host = this as unknown as HTMLElement;
63
132
  const hostSet = { ...SwcUtils.getHelperAndHostSet(win, host), $this: this };
64
133
  const r = options.root || 'auto';
65
- const targetEls: HTMLElement[] = [];
66
-
67
- const applyRoot = (t: any) => {
68
- if (!t || !(t instanceof win.HTMLElement)) return;
69
- targetEls.push(t);
70
- };
71
-
72
- if (selector === '$this' || !selector) applyRoot(host);
73
- else if (selector === '$host') applyRoot(hostSet.$host);
74
- else if (selector === '$parentHost') applyRoot(hostSet.$parentHost);
75
- else if (selector === '$appHost') applyRoot(hostSet.$appHost);
76
- else if (selector === '$firstHost') applyRoot(hostSet.$firstHost);
77
- else if (selector === '$lastHost') applyRoot(hostSet.$lastHost);
78
- else if (selector === '$firstAppHost') applyRoot(hostSet.$firstAppHost);
79
- else if (selector === '$lastAppHost') applyRoot(hostSet.$lastAppHost);
80
- else if (selector === '$hosts') hostSet.$hosts.forEach(applyRoot);
81
- else if (selector === '$appHosts') hostSet.$appHosts.forEach(applyRoot);
82
- else {
83
- const targetRoot = r === 'auto' ? (host.shadowRoot || host) : (r === 'shadow' ? host.shadowRoot : host);
84
- if (targetRoot) {
85
- const found = targetRoot.querySelectorAll(selector);
86
- if (found) targetEls.push(...(Array.from(found) as HTMLElement[]));
87
- }
88
- }
134
+ const targetEls = getTargetElements(selector, this, win, r);
89
135
 
90
136
  targetEls.forEach(targetEl => {
91
137
  if (options.filter && !options.filter(targetEl, resolvedValue, {currentThis: this, helper: hostSet})) {
@@ -138,7 +184,7 @@ function createStyleDecorator(action: StyleAction) {
138
184
  }
139
185
 
140
186
  export function applyStyle(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): PropertyDescriptor | void;
141
- export function applyStyle(selector: string, action?: StyleAction, options?: StyleApplyOptions): MethodDecorator;
187
+ export function applyStyle(selector: StyleSelector, action?: StyleAction, options?: StyleApplyOptions): MethodDecorator;
142
188
  export function applyStyle(selectorOrTarget?: any, actionOrPropertyKey?: any, optionsOrDescriptor?: any): any {
143
189
  // Bare decorator: @applyStyle
144
190
  if (optionsOrDescriptor !== undefined && (typeof actionOrPropertyKey === 'string' || typeof actionOrPropertyKey === 'symbol')) {
@@ -157,7 +203,7 @@ export function applyStyle(selectorOrTarget?: any, actionOrPropertyKey?: any, op
157
203
  }
158
204
 
159
205
  export function setStyle(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): PropertyDescriptor | void;
160
- export function setStyle(selector: string, options?: StyleApplyOptions): MethodDecorator;
206
+ export function setStyle(selector: StyleSelector, options?: StyleApplyOptions): MethodDecorator;
161
207
  export function setStyle(selectorOrTarget?: any, optionsOrPropertyKey?: any, descriptor?: any): any {
162
208
  // Bare decorator: @setStyle
163
209
  if (descriptor !== undefined && (typeof optionsOrPropertyKey === 'string' || typeof optionsOrPropertyKey === 'symbol')) {
@@ -172,7 +218,7 @@ export function setStyle(selectorOrTarget?: any, optionsOrPropertyKey?: any, des
172
218
  }
173
219
 
174
220
  export function updateStyle(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): PropertyDescriptor | void;
175
- export function updateStyle(selector: string, options?: StyleApplyOptions): MethodDecorator;
221
+ export function updateStyle(selector: StyleSelector, options?: StyleApplyOptions): MethodDecorator;
176
222
  export function updateStyle(selectorOrTarget?: any, optionsOrPropertyKey?: any, descriptor?: any): any {
177
223
  // Bare decorator: @updateStyle
178
224
  if (descriptor !== undefined && (typeof optionsOrPropertyKey === 'string' || typeof optionsOrPropertyKey === 'symbol')) {
@@ -187,7 +233,7 @@ export function updateStyle(selectorOrTarget?: any, optionsOrPropertyKey?: any,
187
233
  }
188
234
 
189
235
  export function removeStyle(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): PropertyDescriptor | void;
190
- export function removeStyle(selector: string, options?: StyleApplyOptions): MethodDecorator;
236
+ export function removeStyle(selector: StyleSelector, options?: StyleApplyOptions): MethodDecorator;
191
237
  export function removeStyle(selectorOrTarget?: any, optionsOrPropertyKey?: any, descriptor?: any): any {
192
238
  // Bare decorator: @removeStyle
193
239
  if (descriptor !== undefined && (typeof optionsOrPropertyKey === 'string' || typeof optionsOrPropertyKey === 'symbol')) {
@@ -467,7 +467,9 @@ export const elementDefine =
467
467
  const nonDelegateListeners: typeof addEventListenerList = [];
468
468
 
469
469
  addEventListenerList.forEach(meta => {
470
- if (meta.options.delegate && meta.selector !== '$window' && meta.selector !== '$document' && meta.selector !== '$host' && meta.selector !== '$appHost' && meta.selector !== '$firstHost' && meta.selector !== '$lastHost' && meta.selector !== '$firstAppHost' && meta.selector !== '$lastAppHost' && meta.selector !== '$hosts' && meta.selector !== '$appHosts' && meta.selector !== '$this' && meta.selector !== '') {
470
+ // Function-based selectors cannot be used with delegate listeners
471
+ const isStringSelector = typeof meta.selector === 'string';
472
+ if (meta.options.delegate && isStringSelector && meta.selector !== '$window' && meta.selector !== '$document' && meta.selector !== '$host' && meta.selector !== '$appHost' && meta.selector !== '$firstHost' && meta.selector !== '$lastHost' && meta.selector !== '$firstAppHost' && meta.selector !== '$lastAppHost' && meta.selector !== '$hosts' && meta.selector !== '$appHosts' && meta.selector !== '$this' && meta.selector !== '') {
471
473
  delegateListeners.push(meta);
472
474
  } else {
473
475
  nonDelegateListeners.push(meta);
@@ -537,6 +539,25 @@ export const elementDefine =
537
539
  const opts = {capture: options.capture, once: options.once, passive: options.passive};
538
540
  const bindTargets: EventTarget[] = [];
539
541
  const r = options.root || 'auto';
542
+
543
+ // Resolve selector if it's a function
544
+ let resolvedSelector: string | null = null;
545
+ if (typeof selector === 'function') {
546
+ const hostSet = SwcUtils.getHelperAndHostSet(currentWin, this);
547
+ const result = selector(this, hostSet);
548
+ if (typeof result === 'string') {
549
+ resolvedSelector = result;
550
+ } else if (result instanceof currentWin.Element) {
551
+ bindTargets.push(result as EventTarget);
552
+ } else if (result instanceof currentWin.NodeList) {
553
+ bindTargets.push(...Array.from(result as EventTarget[]));
554
+ } else if (Array.isArray(result)) {
555
+ bindTargets.push(...(result as EventTarget[]));
556
+ }
557
+ } else {
558
+ resolvedSelector = selector;
559
+ }
560
+
540
561
  const applyRootOption = (target: any) => {
541
562
  if (!target) return;
542
563
  if (r === 'auto') {
@@ -547,31 +568,33 @@ export const elementDefine =
547
568
  }
548
569
  };
549
570
 
550
- if (selector === '$window') bindTargets.push(currentWin);
551
- else if (selector === '$document') bindTargets.push(currentWin.document);
552
- else if (selector === '$host') applyRootOption(helperHostSet.$host);
553
- else if (selector === '$parentHost') applyRootOption(helperHostSet.$parentHost);
554
- else if (selector === '$appHost') applyRootOption(helperHostSet.$appHost);
555
- else if (selector === '$firstHost') applyRootOption(helperHostSet.$firstHost);
556
- else if (selector === '$lastHost') applyRootOption(helperHostSet.$lastHost);
557
- else if (selector === '$firstAppHost') applyRootOption(helperHostSet.$firstAppHost);
558
- else if (selector === '$lastAppHost') applyRootOption(helperHostSet.$lastAppHost);
559
- else if (selector === '$hosts') helperHostSet.$hosts.forEach(applyRootOption);
560
- else if (selector === '$appHosts') helperHostSet.$appHosts.forEach(applyRootOption);
561
- else if (selector === '$this' || !selector) applyRootOption(this);
562
- else {
563
- const searchRoots: (HTMLElement | ShadowRoot)[] = [];
564
- if (r === 'auto') searchRoots.push(this.shadowRoot || (this as any));
565
- else if (r === 'light') searchRoots.push(this as any);
566
- else if (r === 'shadow' && this.shadowRoot) searchRoots.push(this.shadowRoot);
567
- else if (r === 'all') {
568
- searchRoots.push(this as any);
569
- if (this.shadowRoot) searchRoots.push(this.shadowRoot);
570
- }
571
+ if (resolvedSelector) {
572
+ if (resolvedSelector === '$window') bindTargets.push(currentWin);
573
+ else if (resolvedSelector === '$document') bindTargets.push(currentWin.document);
574
+ else if (resolvedSelector === '$host') applyRootOption(helperHostSet.$host);
575
+ else if (resolvedSelector === '$parentHost') applyRootOption(helperHostSet.$parentHost);
576
+ else if (resolvedSelector === '$appHost') applyRootOption(helperHostSet.$appHost);
577
+ else if (resolvedSelector === '$firstHost') applyRootOption(helperHostSet.$firstHost);
578
+ else if (resolvedSelector === '$lastHost') applyRootOption(helperHostSet.$lastHost);
579
+ else if (resolvedSelector === '$firstAppHost') applyRootOption(helperHostSet.$firstAppHost);
580
+ else if (resolvedSelector === '$lastAppHost') applyRootOption(helperHostSet.$lastAppHost);
581
+ else if (resolvedSelector === '$hosts') helperHostSet.$hosts.forEach(applyRootOption);
582
+ else if (resolvedSelector === '$appHosts') helperHostSet.$appHosts.forEach(applyRootOption);
583
+ else if (resolvedSelector === '$this' || !resolvedSelector) applyRootOption(this);
584
+ else {
585
+ const searchRoots: (HTMLElement | ShadowRoot)[] = [];
586
+ if (r === 'auto') searchRoots.push(this.shadowRoot || (this as any));
587
+ else if (r === 'light') searchRoots.push(this as any);
588
+ else if (r === 'shadow' && this.shadowRoot) searchRoots.push(this.shadowRoot);
589
+ else if (r === 'all') {
590
+ searchRoots.push(this as any);
591
+ if (this.shadowRoot) searchRoots.push(this.shadowRoot);
592
+ }
571
593
 
572
- searchRoots.forEach(sr => {
573
- sr.querySelectorAll(selector).forEach(el => bindTargets.push(el));
574
- });
594
+ searchRoots.forEach(sr => {
595
+ sr.querySelectorAll(resolvedSelector).forEach(el => bindTargets.push(el));
596
+ });
597
+ }
575
598
  }
576
599
 
577
600
  bindTargets.forEach(t => {
@@ -588,7 +611,7 @@ export const elementDefine =
588
611
  if (options.preventDefault) event.preventDefault();
589
612
  const currentHostSet = SwcUtils.getHostSet(this as any);
590
613
  const args = {event, ...currentHostSet, $el: t, $root: t};
591
- await (this as any)[meta.propertyKey](event, {currentHostSet, $$matchedElement: event.currentTarget}, args);
614
+ await (this as any)[meta.propertyKey](event, {currentHostSet, $matchedElement: event.currentTarget}, args);
592
615
  };
593
616
 
594
617
  // Create event stream with Observable-based operators
@@ -3,6 +3,8 @@ import { SpecialSelector, SwcQueryOptions, HelperHostSet } from '../types';
3
3
  import {ensureInit, getElementConfig} from './elementDefine';
4
4
  import {SwcUtils} from '../utils/Utils';
5
5
 
6
+ export type EmitCustomEventSelector = string | ((currentThis: any, helper: HelperHostSet) => NodeList | Element | Element[] | null);
7
+
6
8
  export interface EmitCustomEventBaseOptions {
7
9
  bubbles?: boolean;
8
10
  composed?: boolean;
@@ -30,7 +32,7 @@ type Options = (EmitCustomEventBaseOptions & SwcQueryOptions) | (EmitCustomEvent
30
32
 
31
33
  export interface EmitCustomEventMetadata {
32
34
  propertyKey: string | symbol;
33
- selector: string;
35
+ selector: EmitCustomEventSelector;
34
36
  type: string;
35
37
  options: Options;
36
38
  }
@@ -38,7 +40,7 @@ export interface EmitCustomEventMetadata {
38
40
  export const EMIT_CUSTOM_EVENT_METADATA_KEY = Symbol.for('simple-web-component:emit-custom-event');
39
41
 
40
42
  export function emitCustomEvent(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor): PropertyDescriptor | void;
41
- export function emitCustomEvent(selector: string, type: string, options?: Options): MethodDecorator;
43
+ export function emitCustomEvent(selector: EmitCustomEventSelector, type: string, options?: Options): MethodDecorator;
42
44
  export function emitCustomEvent(type: string, options?: Options): MethodDecorator;
43
45
  /**
44
46
  * @emitCustomEvent decorator to dispatch custom events to a target.
@@ -46,6 +48,7 @@ export function emitCustomEvent(type: string, options?: Options): MethodDecorato
46
48
  * Usage:
47
49
  * - @emitCustomEvent('$this', 'event-name', options) - Emit from $this
48
50
  * - @emitCustomEvent('$appHost', 'event-name', options) - Emit from $appHost
51
+ * - @emitCustomEvent((this, helper) => '.button', 'event-name', options) - Function-based selector
49
52
  * - @emitCustomEvent('event-name', options) - Emit from $this (defaults to $this)
50
53
  * - @emitCustomEvent('event-name') - Emit from $this with no options
51
54
  */
@@ -56,7 +59,7 @@ export function emitCustomEvent(selectorOrTarget: any, typeOrPropertyKey?: any,
56
59
  }
57
60
 
58
61
  // Case 2: With selector and type - @emitCustomEvent('selector', 'event-name', options)
59
- if (typeof selectorOrTarget === 'string' && typeof typeOrPropertyKey === 'string') {
62
+ if ((typeof selectorOrTarget === 'string' || typeof selectorOrTarget === 'function') && typeof typeOrPropertyKey === 'string') {
60
63
  return createEmitDecorator(selectorOrTarget, typeOrPropertyKey, optionsOrDescriptor as Options);
61
64
  }
62
65
 
@@ -69,7 +72,7 @@ export function emitCustomEvent(selectorOrTarget: any, typeOrPropertyKey?: any,
69
72
  throw new Error('Invalid @emitCustomEvent usage');
70
73
  }
71
74
 
72
- function createEmitDecorator(selector: string, type: string, options?: Options) {
75
+ function createEmitDecorator(selector: EmitCustomEventSelector, type: string, options?: Options) {
73
76
  return (targetObj: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
74
77
  const constructor = targetObj.constructor;
75
78
 
@@ -98,47 +101,68 @@ function createEmitDecorator(selector: string, type: string, options?: Options)
98
101
  cancelable: fullOptions.cancelable
99
102
  });
100
103
 
101
- const hostSet = SwcUtils.getHostSet(this as any);
102
104
  const conf = getElementConfig(this);
103
105
  const currentWin = (this as any)._resolveWindow?.(conf) || ((typeof window !== 'undefined' ? window : undefined) as Window);
106
+ const hostSet = SwcUtils.getHelperAndHostSet(currentWin, this as any);
104
107
 
105
108
  const eventTargets: EventTarget[] = [];
106
- if (selector === '$window') eventTargets.push(currentWin);
107
- else if (selector === '$document') eventTargets.push(currentWin.document);
108
- else if (selector === '$host') {
109
- if (hostSet.$host) eventTargets.push(hostSet.$host);
110
- } else if (selector === '$parentHost') {
111
- if (hostSet.$parentHost) eventTargets.push(hostSet.$parentHost);
112
- } else if (selector === '$appHost') {
113
- if (hostSet.$appHost) eventTargets.push(hostSet.$appHost);
114
- } else if (selector === '$firstHost') {
115
- if (hostSet.$firstHost) eventTargets.push(hostSet.$firstHost);
116
- } else if (selector === '$lastHost') {
117
- if (hostSet.$lastHost) eventTargets.push(hostSet.$lastHost);
118
- } else if (selector === '$firstAppHost') {
119
- if (hostSet.$firstAppHost) eventTargets.push(hostSet.$firstAppHost);
120
- } else if (selector === '$lastAppHost') {
121
- if (hostSet.$lastAppHost) eventTargets.push(hostSet.$lastAppHost);
122
- } else if (selector === '$hosts') {
123
- hostSet.$hosts.forEach(h => eventTargets.push(h));
124
- } else if (selector === '$appHosts') {
125
- hostSet.$appHosts.forEach(h => eventTargets.push(h));
126
- } else if (selector === '$this' || !selector) {
127
- eventTargets.push(this as any);
109
+
110
+ // Resolve selector if it's a function
111
+ let resolvedSelector: string | Element | NodeList | Element[] | null = selector as any;
112
+ if (typeof selector === 'function') {
113
+ resolvedSelector = selector(this, hostSet);
114
+ }
115
+
116
+ // If selector function returned elements directly, use them
117
+ if (resolvedSelector instanceof currentWin.Element) {
118
+ eventTargets.push(resolvedSelector as EventTarget);
119
+ } else if (resolvedSelector instanceof currentWin.NodeList) {
120
+ eventTargets.push(...Array.from(resolvedSelector as EventTarget[]));
121
+ } else if (Array.isArray(resolvedSelector)) {
122
+ eventTargets.push(...(resolvedSelector as EventTarget[]));
123
+ } else if (resolvedSelector === null) {
124
+ // No targets
128
125
  } else {
129
- const r = fullOptions.root || 'auto';
130
- const searchRoots: (HTMLElement | ShadowRoot)[] = [];
131
- if (r === 'auto') searchRoots.push(this.shadowRoot || (this as any));
132
- else if (r === 'light') searchRoots.push(this as any);
133
- else if (r === 'shadow' && this.shadowRoot) searchRoots.push(this.shadowRoot);
134
- else if (r === 'all') {
135
- searchRoots.push(this as any);
136
- if (this.shadowRoot) searchRoots.push(this.shadowRoot);
126
+ // Handle string selector
127
+ const stringSelector = resolvedSelector as string;
128
+
129
+ if (stringSelector === '$window') eventTargets.push(currentWin);
130
+ else if (stringSelector === '$document') eventTargets.push(currentWin.document);
131
+ else if (stringSelector === '$host') {
132
+ if (hostSet.$host) eventTargets.push(hostSet.$host);
133
+ } else if (stringSelector === '$parentHost') {
134
+ if (hostSet.$parentHost) eventTargets.push(hostSet.$parentHost);
135
+ } else if (stringSelector === '$appHost') {
136
+ if (hostSet.$appHost) eventTargets.push(hostSet.$appHost);
137
+ } else if (stringSelector === '$firstHost') {
138
+ if (hostSet.$firstHost) eventTargets.push(hostSet.$firstHost);
139
+ } else if (stringSelector === '$lastHost') {
140
+ if (hostSet.$lastHost) eventTargets.push(hostSet.$lastHost);
141
+ } else if (stringSelector === '$firstAppHost') {
142
+ if (hostSet.$firstAppHost) eventTargets.push(hostSet.$firstAppHost);
143
+ } else if (stringSelector === '$lastAppHost') {
144
+ if (hostSet.$lastAppHost) eventTargets.push(hostSet.$lastAppHost);
145
+ } else if (stringSelector === '$hosts') {
146
+ hostSet.$hosts.forEach(h => eventTargets.push(h));
147
+ } else if (stringSelector === '$appHosts') {
148
+ hostSet.$appHosts.forEach(h => eventTargets.push(h));
149
+ } else if (stringSelector === '$this' || !stringSelector) {
150
+ eventTargets.push(this as any);
151
+ } else {
152
+ const r = fullOptions.root || 'auto';
153
+ const searchRoots: (HTMLElement | ShadowRoot)[] = [];
154
+ if (r === 'auto') searchRoots.push(this.shadowRoot || (this as any));
155
+ else if (r === 'light') searchRoots.push(this as any);
156
+ else if (r === 'shadow' && this.shadowRoot) searchRoots.push(this.shadowRoot);
157
+ else if (r === 'all') {
158
+ searchRoots.push(this as any);
159
+ if (this.shadowRoot) searchRoots.push(this.shadowRoot);
160
+ }
161
+
162
+ searchRoots.forEach(sr => {
163
+ sr.querySelectorAll(stringSelector).forEach(el => eventTargets.push(el));
164
+ });
137
165
  }
138
-
139
- searchRoots.forEach(sr => {
140
- sr.querySelectorAll(selector).forEach(el => eventTargets.push(el));
141
- });
142
166
  }
143
167
 
144
168
  eventTargets.forEach(t => {
@@ -1,29 +1,44 @@
1
1
  import { ReflectUtils } from '@dooboostore/core';
2
2
  import { getElementConfig, ensureInit } from './elementDefine';
3
3
  import { SwcUtils } from '../utils/Utils';
4
- import { SpecialSelector, SwcQueryOptions, HelperHostSet } from '../types';
4
+ import { SwcQueryOptions, HelperHostSet } from '../types';
5
+
6
+ export interface QueryOptions extends SwcQueryOptions {
7
+ /**
8
+ * Filter function to determine whether to include the element.
9
+ * If it returns false, the element is skipped.
10
+ */
11
+ filter?: (target: HTMLElement, meta: { currentThis: any, helper: HelperHostSet }) => boolean;
12
+ }
5
13
 
6
14
  export interface QueryMetadata {
7
15
  propertyKey: string | symbol;
8
- selector: string;
9
- options: SwcQueryOptions ;
16
+ selector: string | ((currentThis: any, helper: HelperHostSet) => Node | NodeList | Element | Element[] | null);
17
+ options: QueryOptions ;
10
18
  }
11
19
 
12
20
  export const QUERY_METADATA_KEY = Symbol.for('simple-web-component:query');
13
21
 
14
22
  export function query(target: Object, propertyKey: string | symbol, descriptor?: PropertyDescriptor): PropertyDescriptor | void;
15
- export function query(selector: string, options?: SwcQueryOptions): PropertyDecorator;
16
- export function query(options?: SwcQueryOptions): PropertyDecorator;
23
+ export function query(selector: string | ((currentThis: any, helper: HelperHostSet) => Node | NodeList | Element | Element[] | null), options?: QueryOptions): PropertyDecorator;
24
+ export function query(options?: QueryOptions): PropertyDecorator;
17
25
  /**
18
26
  * @query decorator to inject a single element into a class property.
27
+ *
28
+ * Supports both string selectors and function-based selectors:
29
+ * - String selector: @query('#my-element')
30
+ * - Function selector: @query((currentThis, helper) => currentThis.shadowRoot?.querySelector('.item'))
31
+ *
32
+ * Supports filtering:
33
+ * - @query('#my-element', { filter: (target, meta) => target.hasAttribute('active') })
19
34
  */
20
- export function query(selectorOrTarget?: string | Object, optionsOrPropertyKey?: SwcQueryOptions | string | symbol, descriptor?: PropertyDescriptor): any {
35
+ export function query(selectorOrTarget?: string | ((currentThis: any, helper: HelperHostSet) => Node | NodeList | Element | Element[] | null) | Object, optionsOrPropertyKey?: QueryOptions | string | symbol, descriptor?: PropertyDescriptor): any {
21
36
  // Bare decorator: @query
22
37
  if (descriptor !== undefined && (typeof optionsOrPropertyKey === 'string' || typeof optionsOrPropertyKey === 'symbol')) {
23
38
  throw new Error(`@query decorator cannot be used on methods. (Method: ${String(optionsOrPropertyKey)})`);
24
39
  }
25
40
  // With selector
26
- if (typeof selectorOrTarget === 'string') {
41
+ if (typeof selectorOrTarget === 'string' || typeof selectorOrTarget === 'function') {
27
42
  return (targetObj: Object, propertyKey: string | symbol, descriptor?: never): void => {
28
43
  if (descriptor !== undefined) {
29
44
  throw new Error(`@query decorator cannot be used on methods. (Method: ${String(propertyKey)})`);
@@ -35,67 +50,114 @@ export function query(selectorOrTarget?: string | Object, optionsOrPropertyKey?:
35
50
  queries = [];
36
51
  ReflectUtils.defineMetadata(QUERY_METADATA_KEY, queries, constructor);
37
52
  }
38
- queries.push({ propertyKey, selector: selectorOrTarget, options: optionsOrPropertyKey as SwcQueryOptions || {} });
53
+ queries.push({ propertyKey, selector: selectorOrTarget as any, options: optionsOrPropertyKey as QueryOptions || {} });
39
54
 
40
55
  Object.defineProperty(targetObj, propertyKey, {
41
56
  get(this: HTMLElement) {
42
57
  ensureInit(this);
43
- const selector = selectorOrTarget;
44
-
45
- const r = (optionsOrPropertyKey as SwcQueryOptions)?.root || 'auto';
46
- const applyRoot = (t: any) => {
47
- if (!t || !(t instanceof HTMLElement)) return t;
48
- if (r === 'auto') return t.shadowRoot || t;
49
- if (r === 'shadow') return t.shadowRoot;
50
- if (r === 'light') return t;
51
- if (r === 'all') return t.shadowRoot || t;
52
- return t;
53
- };
54
-
55
- // --- Special Selectors: HostSet ---
56
- const hostSet = SwcUtils.getHostSet(this);
57
- if (selector === '$this') return applyRoot(this);
58
- if (selector === '$host') return applyRoot(hostSet.$host);
59
- if (selector === '$parentHost') return applyRoot(hostSet.$parentHost);
60
- if (selector === '$appHost') return applyRoot(hostSet.$appHost);
61
- if (selector === '$firstHost') return applyRoot(hostSet.$firstHost);
62
- if (selector === '$lastHost') return applyRoot(hostSet.$lastHost);
63
- if (selector === '$firstAppHost') return applyRoot(hostSet.$firstAppHost);
64
- if (selector === '$lastAppHost') return applyRoot(hostSet.$lastAppHost);
65
-
66
- // --- Special Selectors: Env ---
67
58
  const config = getElementConfig(this);
68
59
  const win = config?.window || window;
69
- if (selector === '$window') return win;
70
- if (selector === '$document') return win.document;
60
+ const hostSet = SwcUtils.getHelperAndHostSet(win, this);
61
+
62
+ // Resolve selector - can be string or function
63
+ let resolvedSelector: string;
64
+ let resolvedElement: HTMLElement | null = null;
65
+
66
+ if (typeof selectorOrTarget === 'function') {
67
+ const result = (selectorOrTarget as any)(this, hostSet);
68
+ // If function returns a Node/Element, return it directly
69
+ if (result instanceof win.Node || result instanceof win.Element) {
70
+ resolvedElement = result instanceof win.HTMLElement ? (result as HTMLElement) : null;
71
+ }
72
+ // If function returns NodeList or Element[], return first element
73
+ else if (result instanceof win.NodeList || Array.isArray(result)) {
74
+ resolvedElement = result.length > 0 && result[0] instanceof win.HTMLElement ? (result[0] as HTMLElement) : null;
75
+ }
76
+ } else {
77
+ resolvedSelector = selectorOrTarget;
71
78
 
72
- // --- Standard Selectors ---
73
- if (r === 'shadow') return this.shadowRoot ? this.shadowRoot.querySelector(selector) : null;
74
- if (r === 'light') return this.querySelector(selector);
75
- if (r === 'all') {
76
- const shadowMatch = this.shadowRoot ? this.shadowRoot.querySelector(selector) : null;
77
- return shadowMatch || this.querySelector(selector);
79
+ const r = (optionsOrPropertyKey as QueryOptions)?.root || 'auto';
80
+ const applyRoot = (t: any) => {
81
+ if (!t || !(t instanceof HTMLElement)) return t;
82
+ if (r === 'auto') return t.shadowRoot || t;
83
+ if (r === 'shadow') return t.shadowRoot;
84
+ if (r === 'light') return t;
85
+ if (r === 'all') return t.shadowRoot || t;
86
+ return t;
87
+ };
88
+
89
+ // --- Special Selectors: HostSet ---
90
+ if (resolvedSelector === '$this') resolvedElement = applyRoot(this);
91
+ else if (resolvedSelector === '$host') resolvedElement = applyRoot(hostSet.$host);
92
+ else if (resolvedSelector === '$parentHost') resolvedElement = applyRoot(hostSet.$parentHost);
93
+ else if (resolvedSelector === '$appHost') resolvedElement = applyRoot(hostSet.$appHost);
94
+ else if (resolvedSelector === '$firstHost') resolvedElement = applyRoot(hostSet.$firstHost);
95
+ else if (resolvedSelector === '$lastHost') resolvedElement = applyRoot(hostSet.$lastHost);
96
+ else if (resolvedSelector === '$firstAppHost') resolvedElement = applyRoot(hostSet.$firstAppHost);
97
+ else if (resolvedSelector === '$lastAppHost') resolvedElement = applyRoot(hostSet.$lastAppHost);
98
+ // --- Special Selectors: Env ---
99
+ else if (resolvedSelector === '$window') return win;
100
+ else if (resolvedSelector === '$document') return win.document;
101
+ // --- Standard Selectors ---
102
+ else {
103
+ if (r === 'shadow') resolvedElement = this.shadowRoot ? (this.shadowRoot.querySelector(resolvedSelector) as HTMLElement | null) : null;
104
+ else if (r === 'light') resolvedElement = this.querySelector(resolvedSelector) as HTMLElement | null;
105
+ else if (r === 'all') {
106
+ const shadowMatch = this.shadowRoot ? (this.shadowRoot.querySelector(resolvedSelector) as HTMLElement | null) : null;
107
+ resolvedElement = shadowMatch || (this.querySelector(resolvedSelector) as HTMLElement | null);
108
+ }
109
+ else resolvedElement = ((this.shadowRoot || this).querySelector(resolvedSelector) as HTMLElement | null);
110
+ }
78
111
  }
79
- return (this.shadowRoot || this).querySelector(selector);
112
+
113
+ // Apply filter if provided
114
+ if (resolvedElement instanceof win.HTMLElement && (optionsOrPropertyKey as QueryOptions)?.filter) {
115
+ const shouldInclude = (optionsOrPropertyKey as QueryOptions).filter!(resolvedElement, { currentThis: this, helper: hostSet });
116
+ if (!shouldInclude) {
117
+ return null;
118
+ }
119
+ }
120
+
121
+ return resolvedElement;
80
122
  },
81
123
  set(this: HTMLElement, nv: any) {
82
124
  ensureInit(this);
83
125
  if (nv === null || nv === undefined) {
84
- const selector = selectorOrTarget;
85
- if (selector.startsWith('$') && selector !== '$this') return;
126
+ const config = getElementConfig(this);
127
+ const win = config?.window || window;
128
+ const hostSet = SwcUtils.getHelperAndHostSet(win, this);
129
+
130
+ // Resolve selector - can be string or function
131
+ if (typeof selectorOrTarget === 'function') {
132
+ // Get the element from function and remove it
133
+ const result = (selectorOrTarget as any)(this, hostSet);
134
+ if (result instanceof win.Element) {
135
+ (result as Element).remove();
136
+ } else if (result instanceof win.NodeList || Array.isArray(result)) {
137
+ const el = result.length > 0 ? result[0] : null;
138
+ if (el instanceof win.Element) {
139
+ (el as Element).remove();
140
+ }
141
+ }
142
+ return;
143
+ }
144
+
145
+ const resolvedSelector = selectorOrTarget;
146
+
147
+ if (resolvedSelector.startsWith('$') && resolvedSelector !== '$this') return;
86
148
 
87
- const r = (optionsOrPropertyKey as SwcQueryOptions)?.root || 'auto';
149
+ const r = (optionsOrPropertyKey as QueryOptions)?.root || 'auto';
88
150
  const targets: Element[] = [];
89
151
 
90
152
  if (r === 'shadow') {
91
- if (this.shadowRoot?.querySelector(selector)) targets.push(this.shadowRoot.querySelector(selector)!);
153
+ if (this.shadowRoot?.querySelector(resolvedSelector)) targets.push(this.shadowRoot.querySelector(resolvedSelector)!);
92
154
  } else if (r === 'light') {
93
- if (this.querySelector(selector)) targets.push(this.querySelector(selector)!);
155
+ if (this.querySelector(resolvedSelector)) targets.push(this.querySelector(resolvedSelector)!);
94
156
  } else if (r === 'all') {
95
- if (this.shadowRoot?.querySelector(selector)) targets.push(this.shadowRoot.querySelector(selector)!);
96
- if (this.querySelector(selector)) targets.push(this.querySelector(selector)!);
157
+ if (this.shadowRoot?.querySelector(resolvedSelector)) targets.push(this.shadowRoot.querySelector(resolvedSelector)!);
158
+ if (this.querySelector(resolvedSelector)) targets.push(this.querySelector(resolvedSelector)!);
97
159
  } else {
98
- const el = (this.shadowRoot || this).querySelector(selector);
160
+ const el = (this.shadowRoot || this).querySelector(resolvedSelector);
99
161
  if (el) targets.push(el);
100
162
  }
101
163