@dooboostore/simple-web-component 1.0.21 → 1.0.22

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 (151) hide show
  1. package/README.md +771 -95
  2. package/dist/cjs/decorators/addEventListener.js +4 -0
  3. package/dist/cjs/decorators/addEventListener.js.map +2 -2
  4. package/dist/cjs/decorators/{appendChild.js → applyNode.js} +25 -17
  5. package/dist/cjs/decorators/applyNode.js.map +7 -0
  6. package/dist/cjs/decorators/attribute.js +31 -10
  7. package/dist/cjs/decorators/attribute.js.map +3 -3
  8. package/dist/cjs/decorators/{changedAttribute.js → changedAttributeHost.js} +6 -6
  9. package/dist/cjs/decorators/changedAttributeHost.js.map +7 -0
  10. package/dist/cjs/decorators/classList.js +11 -1
  11. package/dist/cjs/decorators/classList.js.map +2 -2
  12. package/dist/cjs/decorators/elementDefine.js +106 -55
  13. package/dist/cjs/decorators/elementDefine.js.map +2 -2
  14. package/dist/cjs/decorators/emitCustomEvent.js +4 -3
  15. package/dist/cjs/decorators/emitCustomEvent.js.map +2 -2
  16. package/dist/cjs/decorators/index.js +2 -3
  17. package/dist/cjs/decorators/index.js.map +2 -2
  18. package/dist/cjs/decorators/lifecycles.js +4 -0
  19. package/dist/cjs/decorators/lifecycles.js.map +2 -2
  20. package/dist/cjs/decorators/query.js +5 -1
  21. package/dist/cjs/decorators/query.js.map +2 -2
  22. package/dist/cjs/decorators/queryAll.js +5 -1
  23. package/dist/cjs/decorators/queryAll.js.map +2 -2
  24. package/dist/cjs/decorators/setAttribute.js +5 -1
  25. package/dist/cjs/decorators/setAttribute.js.map +2 -2
  26. package/dist/cjs/elements/SwcAppEngine.js.map +2 -2
  27. package/dist/cjs/elements/SwcAppMixin.js.map +2 -2
  28. package/dist/cjs/elements/SwcAsync.js +2 -2
  29. package/dist/cjs/elements/SwcAsync.js.map +2 -2
  30. package/dist/cjs/elements/SwcChoose.js +2 -2
  31. package/dist/cjs/elements/SwcChoose.js.map +2 -2
  32. package/dist/cjs/elements/SwcIf.js +2 -2
  33. package/dist/cjs/elements/SwcIf.js.map +2 -2
  34. package/dist/cjs/elements/SwcLoop.js +2 -2
  35. package/dist/cjs/elements/SwcLoop.js.map +2 -2
  36. package/dist/cjs/elements/register.js +6 -5
  37. package/dist/cjs/elements/register.js.map +2 -2
  38. package/dist/cjs/index.js +6 -18
  39. package/dist/cjs/index.js.map +2 -2
  40. package/dist/cjs/types.js.map +2 -2
  41. package/dist/esm/decorators/addEventListener.js +4 -0
  42. package/dist/esm/decorators/addEventListener.js.map +2 -2
  43. package/dist/esm/decorators/{appendChild.js → applyNode.js} +22 -14
  44. package/dist/esm/decorators/applyNode.js.map +7 -0
  45. package/dist/esm/decorators/attribute.js +31 -10
  46. package/dist/esm/decorators/attribute.js.map +3 -3
  47. package/dist/esm/decorators/{changedAttribute.js → changedAttributeHost.js} +3 -3
  48. package/dist/esm/decorators/changedAttributeHost.js.map +7 -0
  49. package/dist/esm/decorators/classList.js +11 -1
  50. package/dist/esm/decorators/classList.js.map +2 -2
  51. package/dist/esm/decorators/elementDefine.js +106 -55
  52. package/dist/esm/decorators/elementDefine.js.map +2 -2
  53. package/dist/esm/decorators/emitCustomEvent.js +4 -3
  54. package/dist/esm/decorators/emitCustomEvent.js.map +2 -2
  55. package/dist/esm/decorators/index.js +2 -3
  56. package/dist/esm/decorators/index.js.map +2 -2
  57. package/dist/esm/decorators/lifecycles.js +4 -0
  58. package/dist/esm/decorators/lifecycles.js.map +2 -2
  59. package/dist/esm/decorators/query.js +5 -1
  60. package/dist/esm/decorators/query.js.map +2 -2
  61. package/dist/esm/decorators/queryAll.js +5 -1
  62. package/dist/esm/decorators/queryAll.js.map +2 -2
  63. package/dist/esm/decorators/setAttribute.js +5 -1
  64. package/dist/esm/decorators/setAttribute.js.map +2 -2
  65. package/dist/esm/elements/SwcAppEngine.js.map +2 -2
  66. package/dist/esm/elements/SwcAppMixin.js.map +2 -2
  67. package/dist/esm/elements/SwcAsync.js +2 -2
  68. package/dist/esm/elements/SwcAsync.js.map +2 -2
  69. package/dist/esm/elements/SwcChoose.js +2 -2
  70. package/dist/esm/elements/SwcChoose.js.map +2 -2
  71. package/dist/esm/elements/SwcIf.js +2 -2
  72. package/dist/esm/elements/SwcIf.js.map +2 -2
  73. package/dist/esm/elements/SwcLoop.js +2 -2
  74. package/dist/esm/elements/SwcLoop.js.map +2 -2
  75. package/dist/esm/elements/register.js +6 -5
  76. package/dist/esm/elements/register.js.map +2 -2
  77. package/dist/esm/index.js +6 -18
  78. package/dist/esm/index.js.map +2 -2
  79. package/dist/esm/types.js.map +2 -2
  80. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +195 -139
  81. package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
  82. package/dist/types/decorators/addEventListener.d.ts +6 -0
  83. package/dist/types/decorators/addEventListener.d.ts.map +1 -1
  84. package/dist/types/decorators/applyNode.d.ts +38 -0
  85. package/dist/types/decorators/applyNode.d.ts.map +1 -0
  86. package/dist/types/decorators/attribute.d.ts +6 -0
  87. package/dist/types/decorators/attribute.d.ts.map +1 -1
  88. package/dist/types/decorators/{changedAttribute.d.ts → changedAttributeHost.d.ts} +6 -2
  89. package/dist/types/decorators/changedAttributeHost.d.ts.map +1 -0
  90. package/dist/types/decorators/classList.d.ts +25 -0
  91. package/dist/types/decorators/classList.d.ts.map +1 -1
  92. package/dist/types/decorators/elementDefine.d.ts.map +1 -1
  93. package/dist/types/decorators/emitCustomEvent.d.ts +14 -2
  94. package/dist/types/decorators/emitCustomEvent.d.ts.map +1 -1
  95. package/dist/types/decorators/index.d.ts +2 -3
  96. package/dist/types/decorators/index.d.ts.map +1 -1
  97. package/dist/types/decorators/lifecycles.d.ts +2 -0
  98. package/dist/types/decorators/lifecycles.d.ts.map +1 -1
  99. package/dist/types/decorators/query.d.ts +6 -0
  100. package/dist/types/decorators/query.d.ts.map +1 -1
  101. package/dist/types/decorators/queryAll.d.ts +6 -0
  102. package/dist/types/decorators/queryAll.d.ts.map +1 -1
  103. package/dist/types/decorators/setAttribute.d.ts +5 -0
  104. package/dist/types/decorators/setAttribute.d.ts.map +1 -1
  105. package/dist/types/elements/SwcAppEngine.d.ts.map +1 -1
  106. package/dist/types/elements/SwcAppMixin.d.ts.map +1 -1
  107. package/dist/types/elements/register.d.ts +1 -1
  108. package/dist/types/elements/register.d.ts.map +1 -1
  109. package/dist/types/index.d.ts +3 -15
  110. package/dist/types/index.d.ts.map +1 -1
  111. package/dist/types/types.d.ts +2 -1
  112. package/dist/types/types.d.ts.map +1 -1
  113. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +195 -139
  114. package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
  115. package/package.json +3 -3
  116. package/src/decorators/addEventListener.ts +9 -0
  117. package/src/decorators/applyNode.ts +127 -0
  118. package/src/decorators/attribute.ts +46 -12
  119. package/src/decorators/{changedAttribute.ts → changedAttributeHost.ts} +5 -1
  120. package/src/decorators/classList.ts +32 -0
  121. package/src/decorators/elementDefine.ts +130 -62
  122. package/src/decorators/emitCustomEvent.ts +13 -5
  123. package/src/decorators/index.ts +2 -3
  124. package/src/decorators/lifecycles.ts +3 -1
  125. package/src/decorators/query.ts +9 -0
  126. package/src/decorators/queryAll.ts +9 -0
  127. package/src/decorators/setAttribute.ts +8 -0
  128. package/src/elements/SwcAppEngine.ts +1 -0
  129. package/src/elements/SwcAppMixin.ts +2 -1
  130. package/src/elements/SwcAsync.ts +2 -2
  131. package/src/elements/SwcChoose.ts +2 -2
  132. package/src/elements/SwcIf.ts +2 -2
  133. package/src/elements/SwcLoop.ts +2 -2
  134. package/src/elements/register.ts +7 -5
  135. package/src/index.ts +8 -17
  136. package/src/types.ts +2 -1
  137. package/dist/cjs/decorators/appendChild.js.map +0 -7
  138. package/dist/cjs/decorators/changedAttribute.js.map +0 -7
  139. package/dist/cjs/decorators/replaceChildren.js +0 -79
  140. package/dist/cjs/decorators/replaceChildren.js.map +0 -7
  141. package/dist/esm/decorators/appendChild.js.map +0 -7
  142. package/dist/esm/decorators/changedAttribute.js.map +0 -7
  143. package/dist/esm/decorators/replaceChildren.js +0 -60
  144. package/dist/esm/decorators/replaceChildren.js.map +0 -7
  145. package/dist/types/decorators/appendChild.d.ts +0 -17
  146. package/dist/types/decorators/appendChild.d.ts.map +0 -1
  147. package/dist/types/decorators/changedAttribute.d.ts.map +0 -1
  148. package/dist/types/decorators/replaceChildren.d.ts +0 -9
  149. package/dist/types/decorators/replaceChildren.d.ts.map +0 -1
  150. package/src/decorators/appendChild.ts +0 -86
  151. package/src/decorators/replaceChildren.ts +0 -64
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dooboostore/simple-web-component",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -92,10 +92,10 @@
92
92
  "webpack": "^5.93.0",
93
93
  "webpack-cli": "^5.1.4",
94
94
  "webpack-dev-server": "^5.2.0",
95
+ "@dooboostore/core-web": "1.0.10",
95
96
  "@dooboostore/core": "1.0.30",
96
97
  "@dooboostore/core-node": "1.0.12",
97
- "@dooboostore/simple-boot": "1.0.54",
98
- "@dooboostore/core-web": "1.0.10"
98
+ "@dooboostore/simple-boot": "1.0.54"
99
99
  },
100
100
  "scripts": {
101
101
  "clean": "rm -rf dist",
@@ -44,3 +44,12 @@ export const getAddEventListenerMetadata = (target: any): AddEventListenerMetada
44
44
  const constructor = target instanceof Function ? target : target.constructor;
45
45
  return ReflectUtils.getMetadata(ADD_EVENT_LISTENER_METADATA_KEY, constructor);
46
46
  };
47
+
48
+ /**
49
+ * @addEventListenerHost decorator - simplified version of @addEventListener for :host selector
50
+ * Usage: @addEventListenerHost('click')
51
+ * Usage: @addEventListenerHost('click', { stopPropagation: true })
52
+ */
53
+ export function addEventListenerHost(type: string, options?: AddEventListenerBaseOptions & SwcQueryOptions): MethodDecorator {
54
+ return addEventListener(':host', type, options);
55
+ }
@@ -0,0 +1,127 @@
1
+ import { ReflectUtils } from '@dooboostore/core';
2
+ import { ensureInit, getElementConfig } from './elementDefine';
3
+
4
+ export type ApplyNodePosition = 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd' | 'replace' | 'replaceChildren' | 'innerHtml' | 'innerText';
5
+
6
+ export interface ApplyNodeOptions {
7
+ position?: ApplyNodePosition;
8
+ }
9
+
10
+ export interface ApplyNodeMetadata {
11
+ propertyKey: string | symbol;
12
+ selector: string;
13
+ options: ApplyNodeOptions;
14
+ }
15
+
16
+ export const APPLY_NODE_METADATA_KEY = Symbol('simple-web-component:apply-node');
17
+
18
+ const normalizeNodes = (res: any, doc: Document): Node[] => {
19
+ const items = Array.isArray(res) ? res : [res];
20
+ return items.map(it => {
21
+ if (it instanceof Node) return it;
22
+ return doc.createTextNode(it !== undefined && it !== null ? String(it) : '');
23
+ });
24
+ };
25
+
26
+ /**
27
+ * @applyNode decorator to surgically add/replace nodes to a target element.
28
+ *
29
+ * Usage examples:
30
+ * @applyNode('#list') addItem() { return '<li>New Item</li>'; } // append at end
31
+ * @applyNode('#header', { position: 'afterBegin' }) prependItem() { return '<h1>Title</h1>'; } // prepend at start
32
+ * @applyNode('#item', { position: 'replace' }) replaceItem() { return '<div>Replaced</div>'; } // replace element
33
+ * @applyNode('#list', { position: 'replaceChildren' }) updateList() { return [item1, item2]; } // replace all children
34
+ *
35
+ * Positions:
36
+ * - 'beforeEnd' (default): Append at end of target
37
+ * - 'afterBegin': Prepend at start of target
38
+ * - 'beforeBegin': Insert before target
39
+ * - 'afterEnd': Insert after target
40
+ * - 'replace': Replace the entire target element
41
+ * - 'replaceChildren': Replace all children of target element
42
+ * - 'innerHtml': Set target's innerHTML
43
+ * - 'innerText': Set target's innerText
44
+ */
45
+ export function applyNode(selector: string, options: ApplyNodeOptions = { position: 'beforeEnd' }): MethodDecorator {
46
+ return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
47
+ const constructor = target.constructor;
48
+ let metaList = ReflectUtils.getOwnMetadata(APPLY_NODE_METADATA_KEY, constructor) as Map<string | symbol, ApplyNodeMetadata>;
49
+ if (!metaList) {
50
+ metaList = new Map<string | symbol, ApplyNodeMetadata>();
51
+ ReflectUtils.defineMetadata(APPLY_NODE_METADATA_KEY, metaList, constructor);
52
+ }
53
+ metaList.set(propertyKey, { propertyKey, selector, options });
54
+
55
+ const original = descriptor.value;
56
+ descriptor.value = async function (...args: any[]) {
57
+ ensureInit(this);
58
+ const res = await original.apply(this, args);
59
+ if (res !== undefined) {
60
+ const conf = getElementConfig(this);
61
+ const currentWin = (this as any)._resolveWindow?.(conf) || ((typeof window !== 'undefined' ? window : undefined) as Window);
62
+ const currentDoc = currentWin.document;
63
+
64
+ let targetEl: HTMLElement | null = null;
65
+ if (selector === ':host' || !selector) {
66
+ targetEl = this as any;
67
+ } else {
68
+ targetEl = (this.shadowRoot || (this as any)).querySelector(selector);
69
+ }
70
+
71
+ if (targetEl) {
72
+ const nodes = normalizeNodes(res, currentDoc);
73
+ const pos = options.position || 'beforeEnd';
74
+
75
+ if (pos === 'replace') {
76
+ // Replace the entire target element with new nodes
77
+ targetEl.replaceWith(...nodes);
78
+ } else if (pos === 'replaceChildren') {
79
+ // Replace all children of target element
80
+ targetEl.replaceChildren(...nodes);
81
+ } else if (pos === 'innerHtml') {
82
+ // Set innerHTML
83
+ targetEl.innerHTML = res !== undefined && res !== null ? String(res) : '';
84
+ } else if (pos === 'innerText') {
85
+ // Set innerText
86
+ targetEl.innerText = res !== undefined && res !== null ? String(res) : '';
87
+ } else {
88
+ /*
89
+ <!-- beforebegin -->
90
+ <p>
91
+ <!-- afterbegin -->
92
+ foo
93
+ <!-- beforeend -->
94
+ </p>
95
+ <!-- afterend -->
96
+ */
97
+ if (pos === 'beforeEnd') targetEl.append(...nodes);
98
+ else if (pos === 'afterBegin') targetEl.prepend(...nodes);
99
+ else if (pos === 'beforeBegin') targetEl.before(...nodes);
100
+ else if (pos === 'afterEnd') targetEl.after(...nodes);
101
+ }
102
+ }
103
+ }
104
+ return res;
105
+ };
106
+ };
107
+ }
108
+
109
+ export const findAllApplyNodeMetadata = (target: any): Map<string | symbol, ApplyNodeMetadata> => {
110
+ const result = new Map<string | symbol, ApplyNodeMetadata>();
111
+ const maps = ReflectUtils.findAllMetadata<Map<string | symbol, ApplyNodeMetadata>>(APPLY_NODE_METADATA_KEY, target);
112
+ maps.forEach(map => {
113
+ if (map instanceof Map) {
114
+ map.forEach((v, k) => result.set(k, v));
115
+ }
116
+ });
117
+ return result;
118
+ };
119
+
120
+ /**
121
+ * @applyNodeHost decorator - simplified version of @applyNode for :host selector
122
+ * Usage: @applyNodeHost() addItem() { return '<li>New Item</li>'; }
123
+ * Usage: @applyNodeHost({ position: 'afterBegin' }) prependItem() { return '<h1>Title</h1>'; }
124
+ */
125
+ export function applyNodeHost(options: ApplyNodeOptions = { position: 'beforeEnd' }): MethodDecorator {
126
+ return applyNode(':host', options);
127
+ }
@@ -99,6 +99,22 @@ export function attribute(selector: string, options: AttributeOptions = {}): Pro
99
99
  }
100
100
  metaList.push({ propertyKey, selector, options, privateKey });
101
101
 
102
+ // Helper function to apply setAttribute to targets
103
+ const applySetAttribute = (inst: any, targets: HTMLElement[], attrName: string, nv: any) => {
104
+ targets.forEach(it => {
105
+ console.log('----->', it)
106
+ if (it && typeof (it as any).setAttribute === 'function') {
107
+ if (nv === null) {
108
+ (it as any).removeAttribute(attrName);
109
+ } else {
110
+ (it as any).setAttribute(attrName, String(nv));
111
+ }
112
+ } else {
113
+ inst[privateKey] = nv;
114
+ }
115
+ });
116
+ };
117
+
102
118
  Object.defineProperty(target, propertyKey, {
103
119
  get(this: any) {
104
120
  const attrName = options.name || String(propertyKey);
@@ -127,25 +143,34 @@ export function attribute(selector: string, options: AttributeOptions = {}): Pro
127
143
  },
128
144
  set(this: any, nv: any) {
129
145
  const targetType = options.type || designType;
130
- // const convertedValue = convertValue(nv, targetType);
131
- // console.log('---set', propertyKey, targetType, nv, convertedValue);
146
+ console.log('---set', propertyKey, targetType, nv, this, this.__swc_syncing_init);
132
147
 
133
148
  const attrName = options.name || String(propertyKey);
134
149
  const conf = getElementConfig(this);
135
150
  const currentWin = this._resolveWindow?.(conf) || ((typeof window !== 'undefined' ? window : undefined) as any);
136
- const targets = resolveTargetEls(this, selector, options, currentWin);
137
- targets.forEach(it => {
138
- console.log('----->', it)
139
- if (it && typeof (it as any).setAttribute === 'function') {
140
- if (nv === null) {
141
- (it as any).removeAttribute(attrName);
142
- } else {
143
- (it as any).setAttribute(attrName, String(nv));
144
- }
151
+
152
+ // During initialization/syncing phase
153
+ if (this.__swc_syncing_init) {
154
+ // If connectedInitialize is enabled, force setAttribute (JS -> DOM)
155
+ if (options.connectedInitialize === true) {
156
+ const targets = resolveTargetEls(this, selector, options, currentWin);
157
+ applySetAttribute(this, targets, attrName, nv);
145
158
  } else {
159
+ // Otherwise just update memory
146
160
  this[privateKey] = nv;
147
161
  }
148
- })
162
+ return;
163
+ }
164
+
165
+ // Before connected, save to memory only
166
+ if (!this.__swc_connected) {
167
+ this[privateKey] = nv;
168
+ return;
169
+ }
170
+
171
+ // After connected, normal setAttribute
172
+ const targets = resolveTargetEls(this, selector, options, currentWin);
173
+ applySetAttribute(this, targets, attrName, nv);
149
174
  },
150
175
  enumerable: true,
151
176
  configurable: true
@@ -156,3 +181,12 @@ export function attribute(selector: string, options: AttributeOptions = {}): Pro
156
181
  export const findAllAttributeMetadata = (target: any): AttributeMetadata[] => {
157
182
  return ReflectUtils.findAllMetadata<AttributeMetadata[]>(ATTRIBUTE_METADATA_KEY, target).flat();
158
183
  };
184
+
185
+ /**
186
+ * @hostAttribute decorator - simplified version of @attribute for :host selector
187
+ * Usage: @hostAttribute('data-product-id')
188
+ * Usage: @hostAttribute('disabled', { type: Boolean })
189
+ */
190
+ export function attributeHost(attrName: string, options: AttributeOptions = {}): PropertyDecorator {
191
+ return attribute(':host', { ...options, name: attrName });
192
+ }
@@ -2,7 +2,11 @@ import { ReflectUtils } from '@dooboostore/core';
2
2
 
3
3
  export const ON_ATTRIBUTE_CHANGED_METADATA_KEY = Symbol('simple-web-component:on-attribute-changed');
4
4
 
5
- export function changedAttribute(attributeName?: string): MethodDecorator {
5
+ /**
6
+ * @changedAttributeHost decorator - fires when any attribute on :host changes
7
+ * Usage: @changedAttributeHost('data-product-id')
8
+ */
9
+ export function changedAttributeHost(attributeName?: string): MethodDecorator {
6
10
  return (target: Object, propertyKey: string | symbol) => {
7
11
  const constructor = target.constructor;
8
12
  let meta = ReflectUtils.getOwnMetadata(ON_ATTRIBUTE_CHANGED_METADATA_KEY, constructor) as Map<string, (string | symbol)[]>;
@@ -105,6 +105,38 @@ export const removeClassList = createClassListDecorator('remove');
105
105
  */
106
106
  export const toggleClassList = createClassListDecorator('toggle');
107
107
 
108
+ // Host-specific versions (for :host selector)
109
+
110
+ /**
111
+ * @setClassListHost decorator to set classes on :host element.
112
+ * Returns: string | string[]
113
+ */
114
+ export const setClassListHost = createClassListDecorator('set').bind(null, ':host');
115
+
116
+ /**
117
+ * @updateClassListHost decorator to update specific classes on :host.
118
+ * Returns: Record<string, boolean> (toggle with force) | string | string[] (add)
119
+ */
120
+ export const updateClassListHost = createClassListDecorator('update').bind(null, ':host');
121
+
122
+ /**
123
+ * @addClassListHost decorator to add classes to :host element.
124
+ * Returns: string | string[]
125
+ */
126
+ export const addClassListHost = createClassListDecorator('add').bind(null, ':host');
127
+
128
+ /**
129
+ * @removeClassListHost decorator to remove classes from :host element.
130
+ * Returns: string | string[]
131
+ */
132
+ export const removeClassListHost = createClassListDecorator('remove').bind(null, ':host');
133
+
134
+ /**
135
+ * @toggleClassListHost decorator to toggle classes on :host element.
136
+ * Returns: string | string[]
137
+ */
138
+ export const toggleClassListHost = createClassListDecorator('toggle').bind(null, ':host');
139
+
108
140
  export const findAllClassListMetadata = (target: any): Map<string | symbol, ClassListMetadata> => {
109
141
  const result = new Map<string | symbol, ClassListMetadata>();
110
142
  const maps = ReflectUtils.findAllMetadata<Map<string | symbol, ClassListMetadata>>(CLASS_LIST_METADATA_KEY, target);
@@ -2,14 +2,10 @@ import { ReflectUtils } from '@dooboostore/core';
2
2
  import { FunctionUtils } from '@dooboostore/core';
3
3
  import { getOnConnectedInnerHtmlMetadata } from './onConnectedInnerHtml';
4
4
  import { getAddEventListenerMetadata } from './addEventListener';
5
- import { ON_BEFORE_CONNECTED_METADATA_KEY, ON_AFTER_CONNECTED_METADATA_KEY, ON_BEFORE_DISCONNECTED_METADATA_KEY, ON_AFTER_DISCONNECTED_METADATA_KEY, ON_BEFORE_ADOPTED_METADATA_KEY, ON_AFTER_ADOPTED_METADATA_KEY, findAllLifecycleMetadata } from './lifecycles';
5
+ import { ON_CONSTRUCTOR_METADATA_KEY, ON_BEFORE_CONNECTED_METADATA_KEY, ON_AFTER_CONNECTED_METADATA_KEY, ON_BEFORE_DISCONNECTED_METADATA_KEY, ON_AFTER_DISCONNECTED_METADATA_KEY, ON_BEFORE_ADOPTED_METADATA_KEY, ON_AFTER_ADOPTED_METADATA_KEY, findAllLifecycleMetadata } from './lifecycles';
6
6
  import { getEmitCustomEventMetadataList } from './emitCustomEvent';
7
- import { findAllAttributeChangedMetadata } from './changedAttribute';
7
+ import { findAllAttributeChangedMetadata } from './changedAttributeHost';
8
8
  import { findAllSetAttributeMetadata } from './setAttribute';
9
- import { findAllReplaceChildrenMetadata } from './replaceChildren';
10
- import { findAllAppendChildMetadata } from './appendChild';
11
- import { findAllClassListMetadata } from './classList';
12
- import { findAllStyleMetadata } from './style';
13
9
  import { findAllAttributeMetadata } from './attribute';
14
10
  import { SwcUtils } from '../utils/Utils';
15
11
  import { HTML_TAG_ENTRIES, DOM_EVENT_NAMES } from '../config/config';
@@ -47,6 +43,12 @@ export const ensureInit = (inst: any) => {
47
43
  // Call constructor script if present
48
44
  const hostSet = SwcUtils.getHostSet(inst);
49
45
  inst._executeSwcScript?.('swc-on-constructor', hostSet);
46
+
47
+ // Call @onConstructor lifecycle methods
48
+ const cMethods = findAllLifecycleMetadata(inst, ON_CONSTRUCTOR_METADATA_KEY);
49
+ if (cMethods) {
50
+ for (const m of cMethods) (inst)._invokeLifecycleMethod(m, hostSet);
51
+ }
50
52
  }
51
53
  };
52
54
 
@@ -160,7 +162,9 @@ const setupPrototype = (proto: any) => {
160
162
  if (oldHandler) el.removeEventListener(eventName, oldHandler);
161
163
 
162
164
  const handler = async (event: any) => {
163
- const hostSet = SwcUtils.getHostSet(el);
165
+ console.log('execute handler', event);
166
+ const parent = SwcUtils.findNearestSwcHost(el);
167
+ const hostSet = SwcUtils.getHostSet(parent || el);
164
168
  const conf = getElementConfig(this);
165
169
  const currentWin = (this as any)._resolveWindow(conf);
166
170
  const helpers = SwcUtils.getHelperSet(currentWin);
@@ -181,13 +185,19 @@ const setupPrototype = (proto: any) => {
181
185
 
182
186
  proto.__swc_executeAttributeEvent = async function (el: HTMLElement, attrName: string, script: string, event: Event) {
183
187
  ensureInit(this);
184
- const hostSet = SwcUtils.getHostSet(el);
188
+
189
+ console.log('execute handler2', event);
190
+ const parent = SwcUtils.findNearestSwcHost(el);
191
+ const hostSet = SwcUtils.getHostSet(parent || el);
185
192
  const conf = getElementConfig(this);
186
193
  const currentWin = (this as any)._resolveWindow(conf);
187
194
  const currentHelpers = SwcUtils.getHelperSet(currentWin);
195
+ const detail = (event as CustomEvent).detail;
188
196
  const args = {
189
197
  event,
190
- $data: (event as CustomEvent).detail,
198
+ $event: event,
199
+ $data: detail,
200
+ $detail: detail,
191
201
  ...hostSet,
192
202
  ...currentHelpers,
193
203
  $el: el,
@@ -220,7 +230,9 @@ export const elementDefine =
220
230
  }
221
231
 
222
232
  const emitCustomEventList = getEmitCustomEventMetadataList(constructor) || [];
233
+ const emitHostCustomEventList = emitCustomEventList.filter(meta => meta.selector === ':host' || meta.selector === '');
223
234
  const addEventListenerList = getAddEventListenerMetadata(constructor) || [];
235
+ const addHostEventListenerList = addEventListenerList.filter(meta => meta.selector === ':host' || meta.selector === '');
224
236
  const attrChangeMap = findAllAttributeChangedMetadata(constructor);
225
237
  const attributeList = findAllAttributeMetadata(constructor);
226
238
 
@@ -228,7 +240,7 @@ export const elementDefine =
228
240
  const swcOnEvents = DOM_EVENT_NAMES.map(e => `swc-on-${e}`);
229
241
 
230
242
  // Collect all observed attributes from all decorators
231
- const mergedObservedAttributes = [...new Set([...(metadata.observedAttributes ?? []), ...attrChangeMap.keys(), ...Array.from(findAllSetAttributeMetadata(constructor).values()).map(it => it.name), ...attributeList.map(it => it.options.name || String(it.propertyKey)), ...emitCustomEventList.map(it => it.options.attributeName!), ...swcLifecycleAttributes, ...swcOnEvents])];
243
+ const mergedObservedAttributes = [...new Set([...(metadata.observedAttributes ?? []), ...attrChangeMap.keys(), ...Array.from(findAllSetAttributeMetadata(constructor).values()).map(it => it.name), ...attributeList.map(it => it.options.name || String(it.propertyKey)), ...emitHostCustomEventList.map(it => (it.options as any).attributeName).filter(Boolean), ...swcLifecycleAttributes, ...swcOnEvents])];
232
244
 
233
245
  const connectedInnerHtmlList = getOnConnectedInnerHtmlMetadata(constructor) || [];
234
246
 
@@ -256,29 +268,19 @@ export const elementDefine =
256
268
  // Initialize @attribute fields (DOM <-> JS Sync)
257
269
  attributeList.forEach(meta => {
258
270
  const attrName = meta.options.name || String(meta.propertyKey);
259
- if (meta.options.connectedInitialize) {
260
- // JS -> DOM (FORCE OVERWRITE)
261
- const memoryVal = (this as any)[meta.privateKey];
262
- if (memoryVal !== undefined) {
263
- (this as any).__swc_syncing_init = true;
264
- try {
271
+ if (meta.options.connectedInitialize === true) {
272
+ // JS -> DOM (FORCE OVERWRITE with JS value)
273
+ (this as any).__swc_syncing_init = true;
274
+ try {
275
+ const memoryVal = (this as any)[meta.privateKey];
276
+ if (memoryVal !== undefined) {
265
277
  (this as any)[meta.propertyKey] = memoryVal; // Trigger setter to update DOM
266
- } finally {
267
- (this as any).__swc_syncing_init = false;
268
- }
269
- }
270
- } else if (meta.selector === ':host' || !meta.selector) {
271
- // DOM -> JS (Pull from HTML)
272
- const domVal = this.getAttribute(attrName);
273
- if (domVal !== null) {
274
- (this as any).__swc_syncing_init = true;
275
- try {
276
- (this as any)[meta.propertyKey] = domVal; // Update memory with DOM value
277
- } finally {
278
- (this as any).__swc_syncing_init = false;
279
278
  }
279
+ } finally {
280
+ (this as any).__swc_syncing_init = false;
280
281
  }
281
282
  }
283
+ // else: Do NOT sync - Keep the default value from class initialization
282
284
  });
283
285
 
284
286
  if (connectedInnerHtmlList.length > 0) {
@@ -303,7 +305,76 @@ export const elementDefine =
303
305
  globalDelegatedRoots.add(root);
304
306
  }
305
307
 
308
+ // Separate delegate and non-delegate listeners
309
+ const delegateListeners: typeof addEventListenerList = [];
310
+ const nonDelegateListeners: typeof addEventListenerList = [];
311
+
306
312
  addEventListenerList.forEach(meta => {
313
+ if (meta.options.delegate && meta.selector !== ':window' && meta.selector !== ':document' && meta.selector !== ':parentHost' && meta.selector !== ':appHost' && meta.selector !== ':firstHost' && meta.selector !== ':lastHost' && meta.selector !== ':firstAppHost' && meta.selector !== ':lastAppHost' && meta.selector !== ':hosts' && meta.selector !== ':appHosts' && meta.selector !== ':host' && meta.selector !== '') {
314
+ delegateListeners.push(meta);
315
+ } else {
316
+ nonDelegateListeners.push(meta);
317
+ }
318
+ });
319
+
320
+ // Group delegate listeners by (type, root) combination
321
+ const delegatesByTypeAndRoot = new Map<string, any[]>();
322
+ delegateListeners.forEach(meta => {
323
+ const r = meta.options.root || 'auto';
324
+ const rootKey = `${meta.type}:${r}`;
325
+ if (!delegatesByTypeAndRoot.has(rootKey)) {
326
+ delegatesByTypeAndRoot.set(rootKey, []);
327
+ }
328
+ delegatesByTypeAndRoot.get(rootKey)!.push(meta);
329
+ });
330
+
331
+ // Register unified handlers for delegate listeners
332
+ delegatesByTypeAndRoot.forEach((metaList, typeRootKey) => {
333
+ const [type, rStr] = typeRootKey.split(':');
334
+ const r = rStr === 'auto' ? 'auto' : rStr;
335
+ const opts = { capture: metaList[0].options.capture, once: metaList[0].options.once, passive: metaList[0].options.passive };
336
+
337
+ const bindRoots: (HTMLElement | ShadowRoot)[] = [];
338
+ if (r === 'auto') bindRoots.push(this.shadowRoot || (this as any));
339
+ else if (r === 'light') bindRoots.push(this as any);
340
+ else if (r === 'shadow' && this.shadowRoot) bindRoots.push(this.shadowRoot);
341
+ else if (r === 'all') {
342
+ bindRoots.push(this as any);
343
+ if (this.shadowRoot) bindRoots.push(this.shadowRoot);
344
+ }
345
+
346
+ bindRoots.forEach(br => {
347
+ // Sort: stopPropagation handlers first (more specific/terminal actions)
348
+ const sortedMetaList = [...metaList].sort((a, b) => {
349
+ const aStop = a.options.stopPropagation ? 1 : 0;
350
+ const bStop = b.options.stopPropagation ? 1 : 0;
351
+ return bStop - aStop; // stopPropagation: true comes first
352
+ });
353
+
354
+ const unifiedHandler = async (event: Event) => {
355
+ for (const meta of sortedMetaList) {
356
+ const { selector, options } = meta;
357
+ const matchedEl = (event.target as HTMLElement).closest(selector);
358
+ if (matchedEl && (br as any).contains(matchedEl)) {
359
+ if (options.stopPropagation) event.stopPropagation();
360
+ if (options.stopImmediatePropagation) event.stopImmediatePropagation();
361
+ if (options.preventDefault) event.preventDefault();
362
+ const currentHostSet = SwcUtils.getHostSet(this as any);
363
+ const args = { event, ...currentHostSet, $el: matchedEl, $root: br };
364
+ await (this as any)[meta.propertyKey](event, currentHostSet, matchedEl, args);
365
+
366
+ // Stop processing if event propagation was stopped
367
+ if ((event as any).cancelBubble) break;
368
+ }
369
+ }
370
+ };
371
+ br.addEventListener(type, unifiedHandler, opts);
372
+ (this as any)._boundListeners.push({ target: br, type, handler: unifiedHandler, options: opts });
373
+ });
374
+ });
375
+
376
+ // Process non-delegate listeners
377
+ nonDelegateListeners.forEach(meta => {
307
378
  const { selector, type, options } = meta;
308
379
  const opts = { capture: options.capture, once: options.once, passive: options.passive };
309
380
  const bindTargets: EventTarget[] = [];
@@ -329,34 +400,7 @@ export const elementDefine =
329
400
  else if (selector === ':hosts') hostSet.$hosts.forEach(applyRootOption);
330
401
  else if (selector === ':appHosts') hostSet.$appHosts.forEach(applyRootOption);
331
402
  else if (selector === ':host' || !selector) applyRootOption(this);
332
- else if (options.delegate) {
333
- const bindRoots: (HTMLElement | ShadowRoot)[] = [];
334
- if (r === 'auto') bindRoots.push(this.shadowRoot || (this as any));
335
- else if (r === 'light') bindRoots.push(this as any);
336
- else if (r === 'shadow' && this.shadowRoot) bindRoots.push(this.shadowRoot);
337
- else if (r === 'all') {
338
- bindRoots.push(this as any);
339
- if (this.shadowRoot) bindRoots.push(this.shadowRoot);
340
- }
341
-
342
- bindRoots.forEach(br => {
343
- const handler = async (event: Event) => {
344
- const matchedEl = (event.target as HTMLElement).closest(selector);
345
- if (matchedEl && (br as any).contains(matchedEl)) {
346
- if (options.stopPropagation) event.stopPropagation();
347
- if (options.stopImmediatePropagation) event.stopImmediatePropagation();
348
- if (options.preventDefault) event.preventDefault();
349
- const currentHostSet = SwcUtils.getHostSet(this as any);
350
- const args = { event, ...currentHostSet, $el: matchedEl, $root: br };
351
- await (this as any)._invokeLifecycleMethod(meta.propertyKey, currentHostSet, [event, currentHostSet, matchedEl, args]);
352
- }
353
- };
354
- br.addEventListener(type, handler, opts);
355
- if (options.removeOnDisconnected === true || (br !== this && br !== this.shadowRoot)) {
356
- (this as any)._boundListeners.push({ target: br, type, handler, options: opts });
357
- }
358
- });
359
- } else {
403
+ else {
360
404
  const searchRoots: (HTMLElement | ShadowRoot)[] = [];
361
405
  if (r === 'auto') searchRoots.push(this.shadowRoot || (this as any));
362
406
  else if (r === 'light') searchRoots.push(this as any);
@@ -378,7 +422,7 @@ export const elementDefine =
378
422
  if (options.preventDefault) event.preventDefault();
379
423
  const currentHostSet = SwcUtils.getHostSet(this as any);
380
424
  const args = { event, ...currentHostSet, $el: t, $root: t };
381
- await (this as any)._invokeLifecycleMethod(meta.propertyKey, currentHostSet, [event, currentHostSet, args]);
425
+ await (this as any)[meta.propertyKey](event, currentHostSet, args);
382
426
  };
383
427
  t.addEventListener(type, handler, opts);
384
428
  if (options.removeOnDisconnected === true || t === currentWin || t === currentWin.document) {
@@ -387,6 +431,24 @@ export const elementDefine =
387
431
  });
388
432
  });
389
433
 
434
+ addHostEventListenerList.forEach(meta => {
435
+ const { type, options } = meta;
436
+ const opts = { capture: options.capture, once: options.once, passive: options.passive };
437
+
438
+ const handler = async (event: Event) => {
439
+ if (options.stopPropagation) event.stopPropagation();
440
+ if (options.stopImmediatePropagation) event.stopImmediatePropagation();
441
+ if (options.preventDefault) event.preventDefault();
442
+ const currentHostSet = SwcUtils.getHostSet(this as any);
443
+ const args = { event, ...currentHostSet, $el: this, $root: this.getRootNode() };
444
+ await (this as any)[meta.propertyKey](event, currentHostSet, args);
445
+ };
446
+ this.addEventListener(type, handler, opts);
447
+ if (options.removeOnDisconnected === true) {
448
+ (this as any)._boundListeners.push({ target: this, type, handler, options: opts });
449
+ }
450
+ });
451
+
390
452
  if (originalConnected) originalConnected.apply(this);
391
453
 
392
454
  const aMethods = findAllLifecycleMetadata(this, ON_AFTER_CONNECTED_METADATA_KEY);
@@ -446,20 +508,26 @@ export const elementDefine =
446
508
  proto.attributeChangedCallback = function (name: string, old: string | null, newVal: string | null) {
447
509
  if (originalAttributeChanged) originalAttributeChanged.apply(this, [name, old, newVal]);
448
510
 
511
+ const hSet = SwcUtils.getHostSet(this as any);
512
+
449
513
  // Handle swc-on-* events
450
514
  if (name.startsWith('swc-on-') && !swcLifecycleAttributes.includes(name)) {
451
515
  if (newVal !== null) {
452
- const customEventMeta = emitCustomEventList.find(it => it.options.attributeName === name);
453
- const eventName = customEventMeta ? customEventMeta.type : name.substring(7);
516
+ const eventName = name.substring(7);
454
517
  (this as any)._bindAttributeEvent(this as any, name, newVal, eventName);
455
518
  }
456
519
  }
457
520
 
521
+ // Handle @emitHostCustomEvent attribute changes
522
+ const hostCustomEventMeta = emitHostCustomEventList.find(it => (it.options as any).attributeName === name);
523
+ if (hostCustomEventMeta && newVal !== null) {
524
+ (this as any)._bindAttributeEvent(this as any, name, newVal, hostCustomEventMeta.type);
525
+ }
526
+
458
527
  // Handle @changedAttribute callbacks
459
528
  const mKeys = attrChangeMap.get(name);
460
529
  if (mKeys && Array.isArray(mKeys)) {
461
- const hSet = SwcUtils.getHostSet(this as any);
462
- for (const key of mKeys) (this as any)._invokeLifecycleMethod(key, hSet, [newVal, old, name]);
530
+ for (const key of mKeys) (this as any)[key](newVal, old, name, hSet);
463
531
  }
464
532
  };
465
533
 
@@ -4,7 +4,6 @@ import { ensureInit, getElementConfig } from './elementDefine';
4
4
  import { SwcUtils } from '../utils/Utils';
5
5
 
6
6
  export interface EmitCustomEventBaseOptions {
7
- attributeName?: string;
8
7
  bubbles?: boolean;
9
8
  composed?: boolean;
10
9
  cancelable?: boolean;
@@ -14,7 +13,7 @@ export interface EmitCustomEventMetadata {
14
13
  propertyKey: string | symbol;
15
14
  selector: string;
16
15
  type: string;
17
- options: EmitCustomEventBaseOptions & SwcQueryOptions;
16
+ options: (EmitCustomEventBaseOptions & SwcQueryOptions) | (EmitCustomEventBaseOptions & SwcQueryOptions & { attributeName?: string });
18
17
  }
19
18
 
20
19
  export const EMIT_CUSTOM_EVENT_METADATA_KEY = Symbol('simple-web-component:emit-custom-event');
@@ -29,9 +28,6 @@ export function emitCustomEvent(selectorOrTarget: string, type: string, options:
29
28
 
30
29
  const fullOptions: any = { ...options, type };
31
30
 
32
- if (!fullOptions.attributeName) {
33
- fullOptions.attributeName = `swc-on-${type}`;
34
- }
35
31
  if (fullOptions.bubbles === undefined) fullOptions.bubbles = true;
36
32
  if (fullOptions.composed === undefined) fullOptions.composed = true;
37
33
 
@@ -103,3 +99,15 @@ export const getEmitCustomEventMetadataList = (target: any): EmitCustomEventMeta
103
99
  const constructor = target instanceof Function ? target : target.constructor;
104
100
  return ReflectUtils.getMetadata(EMIT_CUSTOM_EVENT_METADATA_KEY, constructor);
105
101
  };
102
+
103
+ /**
104
+ * @emitCustomEventHost decorator - simplified version of @emitCustomEvent for :host selector
105
+ * Usage: @emitCustomEventHost('navigate')
106
+ * Usage: @emitCustomEventHost('navigate', { attributeName: 'on-navigate' })
107
+ *
108
+ * The attributeName option is used to automatically observe attributes for this event.
109
+ * For example, { attributeName: 'on-navigate' } will add 'on-navigate' to observedAttributes.
110
+ */
111
+ export function emitCustomEventHost(type: string, options?: EmitCustomEventBaseOptions & SwcQueryOptions & { attributeName?: string }): MethodDecorator {
112
+ return emitCustomEvent(':host', type, options);
113
+ }
@@ -1,11 +1,10 @@
1
1
  export * from './addEventListener';
2
2
  export * from './attribute';
3
3
  export * from './elementDefine';
4
- export * from './changedAttribute';
4
+ export * from './changedAttributeHost';
5
5
  export * from './setAttribute';
6
6
  export * from './emitCustomEvent';
7
- export * from './replaceChildren';
8
- export * from './appendChild';
7
+ export * from './applyNode';
9
8
  export * from './lifecycles';
10
9
  export * from './query';
11
10
  export * from './queryAll';