@coherent.js/client 1.0.0-beta.2
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.
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/dist/client/hmr.d.ts +1 -0
- package/dist/client/hmr.d.ts.map +1 -0
- package/dist/client/hmr.js +107 -0
- package/dist/client/hmr.js.map +1 -0
- package/dist/client/hydration.d.ts +55 -0
- package/dist/client/hydration.d.ts.map +1 -0
- package/dist/client/hydration.js +1593 -0
- package/dist/client/hydration.js.map +1 -0
- package/dist/index.js +964 -0
- package/dist/index.js.map +7 -0
- package/package.json +45 -0
- package/src/hydration.js +1791 -0
- package/types/index.d.ts +498 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,964 @@
|
|
|
1
|
+
// src/hydration.js
|
|
2
|
+
var componentInstances = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
function extractInitialState(element, options = {}) {
|
|
4
|
+
if (typeof window === "undefined") {
|
|
5
|
+
return options.initialState || null;
|
|
6
|
+
}
|
|
7
|
+
if (!element || typeof element.getAttribute !== "function") {
|
|
8
|
+
return options.initialState || null;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const stateAttr = element.getAttribute("data-coherent-state");
|
|
12
|
+
if (stateAttr) {
|
|
13
|
+
return JSON.parse(stateAttr);
|
|
14
|
+
}
|
|
15
|
+
const state = {};
|
|
16
|
+
let hasState = false;
|
|
17
|
+
const countAttr = element.getAttribute("data-count");
|
|
18
|
+
if (countAttr !== null) {
|
|
19
|
+
state.count = parseInt(countAttr, 10) || 0;
|
|
20
|
+
hasState = true;
|
|
21
|
+
}
|
|
22
|
+
const stepAttr = element.getAttribute("data-step");
|
|
23
|
+
if (stepAttr !== null) {
|
|
24
|
+
state.step = parseInt(stepAttr, 10) || 1;
|
|
25
|
+
hasState = true;
|
|
26
|
+
}
|
|
27
|
+
const todosAttr = element.getAttribute("data-todos");
|
|
28
|
+
if (todosAttr) {
|
|
29
|
+
state.todos = JSON.parse(todosAttr);
|
|
30
|
+
hasState = true;
|
|
31
|
+
}
|
|
32
|
+
const valueAttr = element.getAttribute("data-value");
|
|
33
|
+
if (valueAttr !== null) {
|
|
34
|
+
state.value = valueAttr;
|
|
35
|
+
hasState = true;
|
|
36
|
+
}
|
|
37
|
+
if (options.initialState) {
|
|
38
|
+
return { ...options.initialState, ...state };
|
|
39
|
+
}
|
|
40
|
+
return hasState ? state : null;
|
|
41
|
+
} catch (_error) {
|
|
42
|
+
console.warn("Error extracting initial state:", _error);
|
|
43
|
+
return options.initialState || null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function hydrate(element, component, props = {}, options = {}) {
|
|
47
|
+
if (typeof window === "undefined") {
|
|
48
|
+
console.warn("Hydration can only be performed in a browser environment");
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
if (typeof component !== "function") {
|
|
52
|
+
console.error("Hydrate error: component must be a function, received:", typeof component);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
if (componentInstances.has(element)) {
|
|
56
|
+
const existingInstance = componentInstances.get(element);
|
|
57
|
+
return existingInstance;
|
|
58
|
+
}
|
|
59
|
+
const initialState = extractInitialState(element, options);
|
|
60
|
+
const instance = {
|
|
61
|
+
element,
|
|
62
|
+
component,
|
|
63
|
+
props: { ...props },
|
|
64
|
+
state: initialState,
|
|
65
|
+
isHydrated: true,
|
|
66
|
+
eventListeners: [],
|
|
67
|
+
options: { ...options },
|
|
68
|
+
previousVirtualElement: null,
|
|
69
|
+
// Update method for re-rendering
|
|
70
|
+
update(newProps) {
|
|
71
|
+
this.props = { ...this.props, ...newProps };
|
|
72
|
+
this.rerender();
|
|
73
|
+
return this;
|
|
74
|
+
},
|
|
75
|
+
// Re-render the component with current state
|
|
76
|
+
rerender() {
|
|
77
|
+
try {
|
|
78
|
+
this.fallbackRerender();
|
|
79
|
+
} catch (_error) {
|
|
80
|
+
console.error("Error during component re-render:", _error);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
// Fallback re-render method using existing patching
|
|
84
|
+
fallbackRerender() {
|
|
85
|
+
try {
|
|
86
|
+
const componentProps2 = { ...this.props, ...this.state || {} };
|
|
87
|
+
if (typeof this.component !== "function") {
|
|
88
|
+
console.error("Component is not a function:", this.component);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const newVirtualElement = this.component(componentProps2);
|
|
92
|
+
if (!this.previousVirtualElement) {
|
|
93
|
+
this.previousVirtualElement = this.virtualElementFromDOM(this.element);
|
|
94
|
+
}
|
|
95
|
+
this.patchDOM(this.element, this.previousVirtualElement, newVirtualElement);
|
|
96
|
+
attachFunctionEventListeners(this.element, newVirtualElement, this, { inputsOnly: true });
|
|
97
|
+
this.previousVirtualElement = newVirtualElement;
|
|
98
|
+
} catch (_error) {
|
|
99
|
+
console.error("Error during component re-render (fallback):", _error);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
// Create virtual element representation from existing DOM
|
|
103
|
+
virtualElementFromDOM(domElement) {
|
|
104
|
+
if (typeof window === "undefined" || typeof Node === "undefined") {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
if (domElement.nodeType === Node.TEXT_NODE) {
|
|
108
|
+
return domElement.textContent;
|
|
109
|
+
}
|
|
110
|
+
if (domElement.nodeType !== Node.ELEMENT_NODE) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const tagName = domElement.tagName.toLowerCase();
|
|
114
|
+
const props2 = {};
|
|
115
|
+
const children = [];
|
|
116
|
+
if (domElement.attributes) {
|
|
117
|
+
Array.from(domElement.attributes).forEach((attr) => {
|
|
118
|
+
const name = attr.name === "class" ? "className" : attr.name;
|
|
119
|
+
props2[name] = attr.value;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
if (domElement.childNodes) {
|
|
123
|
+
Array.from(domElement.childNodes).forEach((child) => {
|
|
124
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
125
|
+
const text = child.textContent.trim();
|
|
126
|
+
if (text) children.push(text);
|
|
127
|
+
} else if (child.nodeType === Node.ELEMENT_NODE) {
|
|
128
|
+
const childVNode = this.virtualElementFromDOM(child);
|
|
129
|
+
if (childVNode) children.push(childVNode);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
if (children.length > 0) {
|
|
134
|
+
props2.children = children;
|
|
135
|
+
}
|
|
136
|
+
return { [tagName]: props2 };
|
|
137
|
+
},
|
|
138
|
+
// Intelligent DOM patching with minimal changes
|
|
139
|
+
patchDOM(domElement, oldVNode, newVNode) {
|
|
140
|
+
if (typeof newVNode === "string" || typeof newVNode === "number") {
|
|
141
|
+
const newText = String(newVNode);
|
|
142
|
+
if (typeof window === "undefined" || typeof Node === "undefined" || typeof document === "undefined") {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (domElement.nodeType === Node.TEXT_NODE) {
|
|
146
|
+
if (domElement.textContent !== newText) {
|
|
147
|
+
domElement.textContent = newText;
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
const textNode = document.createTextNode(newText);
|
|
151
|
+
if (domElement.parentNode) {
|
|
152
|
+
domElement.parentNode.replaceChild(textNode, domElement);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (!newVNode) {
|
|
158
|
+
domElement.remove();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (Array.isArray(newVNode)) {
|
|
162
|
+
console.warn("Array virtual node at root level");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const newTagName = Object.keys(newVNode)[0];
|
|
166
|
+
if (domElement.tagName.toLowerCase() !== newTagName.toLowerCase()) {
|
|
167
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const newElement = this.createDOMElement(newVNode);
|
|
171
|
+
if (domElement.parentNode) {
|
|
172
|
+
domElement.parentNode.replaceChild(newElement, domElement);
|
|
173
|
+
}
|
|
174
|
+
attachEventListeners(newElement, this);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
this.patchAttributes(domElement, oldVNode, newVNode);
|
|
178
|
+
this.patchChildren(domElement, oldVNode, newVNode);
|
|
179
|
+
attachEventListeners(domElement, this);
|
|
180
|
+
},
|
|
181
|
+
// Patch element attributes efficiently
|
|
182
|
+
patchAttributes(domElement, oldVNode, newVNode) {
|
|
183
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (!domElement || typeof domElement.setAttribute !== "function" || typeof domElement.removeAttribute !== "function") {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const oldTagName = oldVNode ? Object.keys(oldVNode)[0] : null;
|
|
190
|
+
const newTagName = Object.keys(newVNode)[0];
|
|
191
|
+
const oldProps = oldVNode && oldTagName ? oldVNode[oldTagName] || {} : {};
|
|
192
|
+
const newProps = newVNode[newTagName] || {};
|
|
193
|
+
Object.keys(oldProps).forEach((key) => {
|
|
194
|
+
if (key === "children" || key === "text") return;
|
|
195
|
+
if (!(key in newProps)) {
|
|
196
|
+
const attrName = key === "className" ? "class" : key;
|
|
197
|
+
domElement.removeAttribute(attrName);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
Object.keys(newProps).forEach((key) => {
|
|
201
|
+
if (key === "children" || key === "text") return;
|
|
202
|
+
const newValue = newProps[key];
|
|
203
|
+
const oldValue = oldProps[key];
|
|
204
|
+
if (newValue !== oldValue) {
|
|
205
|
+
const attrName = key === "className" ? "class" : key;
|
|
206
|
+
if (newValue === true) {
|
|
207
|
+
domElement.setAttribute(attrName, "");
|
|
208
|
+
} else if (newValue === false || newValue === null) {
|
|
209
|
+
domElement.removeAttribute(attrName);
|
|
210
|
+
} else {
|
|
211
|
+
domElement.setAttribute(attrName, String(newValue));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
// Patch children with intelligent list diffing
|
|
217
|
+
patchChildren(domElement, oldVNode, newVNode) {
|
|
218
|
+
if (typeof window === "undefined" || typeof Node === "undefined" || typeof document === "undefined") {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (!domElement || typeof domElement.childNodes === "undefined" || typeof domElement.appendChild !== "function") {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const oldTagName = oldVNode ? Object.keys(oldVNode)[0] : null;
|
|
225
|
+
const newTagName = Object.keys(newVNode)[0];
|
|
226
|
+
const oldProps = oldVNode && oldTagName ? oldVNode[oldTagName] || {} : {};
|
|
227
|
+
const newProps = newVNode[newTagName] || {};
|
|
228
|
+
let oldChildren = [];
|
|
229
|
+
let newChildren = [];
|
|
230
|
+
if (oldProps.children) {
|
|
231
|
+
oldChildren = Array.isArray(oldProps.children) ? oldProps.children : [oldProps.children];
|
|
232
|
+
} else if (oldProps.text) {
|
|
233
|
+
oldChildren = [oldProps.text];
|
|
234
|
+
}
|
|
235
|
+
if (newProps.children) {
|
|
236
|
+
newChildren = Array.isArray(newProps.children) ? newProps.children : [newProps.children];
|
|
237
|
+
} else if (newProps.text) {
|
|
238
|
+
newChildren = [newProps.text];
|
|
239
|
+
}
|
|
240
|
+
let domChildren = [];
|
|
241
|
+
if (typeof Array.from === "function" && domElement.childNodes) {
|
|
242
|
+
try {
|
|
243
|
+
domChildren = Array.from(domElement.childNodes).filter((node) => {
|
|
244
|
+
return node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim();
|
|
245
|
+
});
|
|
246
|
+
} catch (_error) {
|
|
247
|
+
console.warn("Failed to convert childNodes to array:", _error);
|
|
248
|
+
domChildren = [];
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const maxLength = Math.max(oldChildren.length, newChildren.length, domChildren.length);
|
|
252
|
+
for (let i = 0; i < maxLength; i++) {
|
|
253
|
+
const oldChild = oldChildren[i];
|
|
254
|
+
const newChild = newChildren[i];
|
|
255
|
+
const domChild = domChildren[i];
|
|
256
|
+
if (newChild === void 0) {
|
|
257
|
+
if (domChild && typeof domChild.remove === "function") {
|
|
258
|
+
domChild.remove();
|
|
259
|
+
}
|
|
260
|
+
} else if (domChild === void 0) {
|
|
261
|
+
const newElement = this.createDOMElement(newChild);
|
|
262
|
+
if (newElement) {
|
|
263
|
+
domElement.appendChild(newElement);
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
this.patchDOM(domChild, oldChild, newChild);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
// Create DOM element from virtual element
|
|
271
|
+
createDOMElement(vNode) {
|
|
272
|
+
if (typeof vNode === "string" || typeof vNode === "number") {
|
|
273
|
+
return document.createTextNode(String(vNode));
|
|
274
|
+
}
|
|
275
|
+
if (!vNode || typeof vNode !== "object") {
|
|
276
|
+
return document.createTextNode("");
|
|
277
|
+
}
|
|
278
|
+
if (Array.isArray(vNode)) {
|
|
279
|
+
const fragment = document.createDocumentFragment();
|
|
280
|
+
vNode.forEach((child) => {
|
|
281
|
+
fragment.appendChild(this.createDOMElement(child));
|
|
282
|
+
});
|
|
283
|
+
return fragment;
|
|
284
|
+
}
|
|
285
|
+
const tagName = Object.keys(vNode)[0];
|
|
286
|
+
const props2 = vNode[tagName] || {};
|
|
287
|
+
const element2 = document.createElement(tagName);
|
|
288
|
+
Object.keys(props2).forEach((key) => {
|
|
289
|
+
if (key === "children" || key === "text") return;
|
|
290
|
+
const value = props2[key];
|
|
291
|
+
const attrName = key === "className" ? "class" : key;
|
|
292
|
+
if (value === true) {
|
|
293
|
+
element2.setAttribute(attrName, "");
|
|
294
|
+
} else if (value !== false && value !== null) {
|
|
295
|
+
element2.setAttribute(attrName, String(value));
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
if (props2.children) {
|
|
299
|
+
const children = Array.isArray(props2.children) ? props2.children : [props2.children];
|
|
300
|
+
children.forEach((child) => {
|
|
301
|
+
element2.appendChild(this.createDOMElement(child));
|
|
302
|
+
});
|
|
303
|
+
} else if (props2.text) {
|
|
304
|
+
element2.appendChild(document.createTextNode(String(props2.text)));
|
|
305
|
+
}
|
|
306
|
+
return element2;
|
|
307
|
+
},
|
|
308
|
+
// Render virtual element to HTML string
|
|
309
|
+
renderVirtualElement(element2) {
|
|
310
|
+
if (typeof element2 === "string" || typeof element2 === "number") {
|
|
311
|
+
return String(element2);
|
|
312
|
+
}
|
|
313
|
+
if (!element2 || typeof element2 !== "object") {
|
|
314
|
+
return "";
|
|
315
|
+
}
|
|
316
|
+
if (Array.isArray(element2)) {
|
|
317
|
+
return element2.map((el) => this.renderVirtualElement(el)).join("");
|
|
318
|
+
}
|
|
319
|
+
const tagName = Object.keys(element2)[0];
|
|
320
|
+
const props2 = element2[tagName];
|
|
321
|
+
if (!props2 || typeof props2 !== "object") {
|
|
322
|
+
return `<${tagName}></${tagName}>`;
|
|
323
|
+
}
|
|
324
|
+
let attributes = "";
|
|
325
|
+
const children = [];
|
|
326
|
+
Object.keys(props2).forEach((key) => {
|
|
327
|
+
if (key === "children") {
|
|
328
|
+
if (Array.isArray(props2.children)) {
|
|
329
|
+
children.push(...props2.children);
|
|
330
|
+
} else {
|
|
331
|
+
children.push(props2.children);
|
|
332
|
+
}
|
|
333
|
+
} else if (key === "text") {
|
|
334
|
+
children.push(props2.text);
|
|
335
|
+
} else {
|
|
336
|
+
const attrName = key === "className" ? "class" : key;
|
|
337
|
+
const value = props2[key];
|
|
338
|
+
if (value === true) {
|
|
339
|
+
attributes += ` ${attrName}`;
|
|
340
|
+
} else if (value !== false && value !== null && value !== void 0) {
|
|
341
|
+
attributes += ` ${attrName}="${String(value).replace(/"/g, """)}"`;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
const childrenHTML = children.map((child) => this.renderVirtualElement(child)).join("");
|
|
346
|
+
const voidElements = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]);
|
|
347
|
+
if (voidElements.has(tagName.toLowerCase())) {
|
|
348
|
+
return `<${tagName}${attributes}>`;
|
|
349
|
+
}
|
|
350
|
+
return `<${tagName}${attributes}>${childrenHTML}</${tagName}>`;
|
|
351
|
+
},
|
|
352
|
+
// Destroy the component and clean up
|
|
353
|
+
destroy() {
|
|
354
|
+
this.eventListeners.forEach(({ element: element2, event, handler }) => {
|
|
355
|
+
if (element2.removeEventListener) {
|
|
356
|
+
element2.removeEventListener(event, handler);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
this.state = null;
|
|
360
|
+
this.isHydrated = false;
|
|
361
|
+
componentInstances.delete(this.element);
|
|
362
|
+
},
|
|
363
|
+
// Set state (for components with state)
|
|
364
|
+
setState(newState) {
|
|
365
|
+
if (!this.state) {
|
|
366
|
+
this.state = {};
|
|
367
|
+
}
|
|
368
|
+
const oldState = { ...this.state };
|
|
369
|
+
this.state = typeof newState === "function" ? { ...this.state, ...newState(this.state) } : { ...this.state, ...newState };
|
|
370
|
+
this.rerender();
|
|
371
|
+
if (this.onStateChange) {
|
|
372
|
+
this.onStateChange(this.state, oldState);
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
// Add event listener that will be cleaned up on destroy
|
|
376
|
+
addEventListener(targetElement, event, handler) {
|
|
377
|
+
if (targetElement.addEventListener) {
|
|
378
|
+
targetElement.addEventListener(event, handler);
|
|
379
|
+
this.eventListeners.push({ element: targetElement, event, handler });
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
componentInstances.set(element, instance);
|
|
384
|
+
if (element && typeof element.setAttribute === "function") {
|
|
385
|
+
element.__coherentInstance = instance;
|
|
386
|
+
if (!element.hasAttribute("data-coherent-component")) {
|
|
387
|
+
element.setAttribute("data-coherent-component", "true");
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const componentProps = { ...instance.props };
|
|
391
|
+
if (instance.component.__stateContainer) {
|
|
392
|
+
if (instance.state) {
|
|
393
|
+
instance.component.__stateContainer.setState(instance.state);
|
|
394
|
+
}
|
|
395
|
+
instance.setState = (newState) => {
|
|
396
|
+
instance.component.__stateContainer.setState(newState);
|
|
397
|
+
const updatedState = instance.component.__stateContainer.getState();
|
|
398
|
+
instance.state = updatedState;
|
|
399
|
+
instance.rerender();
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
const freshVirtualElement = instance.component(componentProps);
|
|
403
|
+
if (freshVirtualElement && typeof freshVirtualElement === "object") {
|
|
404
|
+
const tagName = Object.keys(freshVirtualElement)[0];
|
|
405
|
+
if (freshVirtualElement[tagName]) {
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
attachFunctionEventListeners(element, freshVirtualElement, instance);
|
|
409
|
+
return instance;
|
|
410
|
+
}
|
|
411
|
+
var eventRegistry = {};
|
|
412
|
+
function registerEventHandler(id, handler) {
|
|
413
|
+
eventRegistry[id] = handler;
|
|
414
|
+
}
|
|
415
|
+
if (typeof window !== "undefined") {
|
|
416
|
+
window.__coherentEventRegistry = window.__coherentEventRegistry || {};
|
|
417
|
+
window.__coherentActionRegistry = window.__coherentActionRegistry || {};
|
|
418
|
+
window.__coherentEventHandler = function(eventId, element, event) {
|
|
419
|
+
let handlerFunc = window.__coherentEventRegistry[eventId];
|
|
420
|
+
if (!handlerFunc && window.__coherentActionRegistry[eventId]) {
|
|
421
|
+
handlerFunc = window.__coherentActionRegistry[eventId];
|
|
422
|
+
}
|
|
423
|
+
if (handlerFunc) {
|
|
424
|
+
let componentElement = element;
|
|
425
|
+
while (componentElement && !componentElement.hasAttribute("data-coherent-component")) {
|
|
426
|
+
componentElement = componentElement.parentElement;
|
|
427
|
+
}
|
|
428
|
+
if (componentElement && componentElement.__coherentInstance) {
|
|
429
|
+
const instance = componentElement.__coherentInstance;
|
|
430
|
+
const state = instance.state || {};
|
|
431
|
+
const setState = instance.setState ? instance.setState.bind(instance) : (() => {
|
|
432
|
+
});
|
|
433
|
+
try {
|
|
434
|
+
handlerFunc.call(element, event, state, setState);
|
|
435
|
+
} catch (_error) {
|
|
436
|
+
console.warn(`Error executing coherent event handler:`, _error);
|
|
437
|
+
}
|
|
438
|
+
} else {
|
|
439
|
+
try {
|
|
440
|
+
handlerFunc.call(element, event);
|
|
441
|
+
} catch (_error) {
|
|
442
|
+
console.warn(`Error executing coherent event handler (no component context):`, _error);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
} else {
|
|
446
|
+
console.warn(`Event handler not found for ID: ${eventId}`);
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function attachFunctionEventListeners(rootElement, virtualElement, instance, options = {}) {
|
|
451
|
+
if (!rootElement || !virtualElement || typeof window === "undefined") {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
function traverseAndAttach(domElement, vElement, path = []) {
|
|
455
|
+
if (!vElement || typeof vElement !== "object") return;
|
|
456
|
+
if (Array.isArray(vElement)) {
|
|
457
|
+
vElement.forEach((child, index) => {
|
|
458
|
+
const childElement = domElement.children[index];
|
|
459
|
+
if (childElement) {
|
|
460
|
+
traverseAndAttach(childElement, child, [...path, index]);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const tagName = Object.keys(vElement)[0];
|
|
466
|
+
const elementProps = vElement[tagName];
|
|
467
|
+
if (elementProps && typeof elementProps === "object") {
|
|
468
|
+
const eventHandlers = ["onclick", "onchange", "oninput", "onfocus", "onblur", "onsubmit", "onkeypress", "onkeydown", "onkeyup", "onmouseenter", "onmouseleave"];
|
|
469
|
+
eventHandlers.forEach((eventName) => {
|
|
470
|
+
const handler = elementProps[eventName];
|
|
471
|
+
if (typeof handler === "function") {
|
|
472
|
+
const eventType = eventName.substring(2);
|
|
473
|
+
if (options.inputsOnly) {
|
|
474
|
+
const inputEvents = ["input", "change", "keypress"];
|
|
475
|
+
const isDynamicElement = domElement.closest(".todo-item") || domElement.closest("[data-dynamic]");
|
|
476
|
+
if (!inputEvents.includes(eventType) && !(eventType === "click" && isDynamicElement)) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
const handlerKey = `__coherent_${eventType}_handler`;
|
|
481
|
+
if (domElement[handlerKey]) {
|
|
482
|
+
domElement.removeEventListener(eventType, domElement[handlerKey]);
|
|
483
|
+
delete domElement[handlerKey];
|
|
484
|
+
}
|
|
485
|
+
const wrappedHandler = (event) => {
|
|
486
|
+
try {
|
|
487
|
+
if (eventType !== "input" && eventType !== "change" && eventType !== "keypress") {
|
|
488
|
+
event.preventDefault();
|
|
489
|
+
}
|
|
490
|
+
let currentState = {};
|
|
491
|
+
let currentSetState = () => {
|
|
492
|
+
};
|
|
493
|
+
if (instance.component && instance.component.__stateContainer) {
|
|
494
|
+
currentState = instance.component.__stateContainer.getState();
|
|
495
|
+
currentSetState = (newState) => {
|
|
496
|
+
instance.component.__stateContainer.setState(newState);
|
|
497
|
+
if (instance.state && typeof newState === "object") {
|
|
498
|
+
Object.assign(instance.state, newState);
|
|
499
|
+
}
|
|
500
|
+
instance.component.__stateContainer.getState();
|
|
501
|
+
const componentRoot = domElement.closest("[data-coherent-component]");
|
|
502
|
+
if (componentRoot && componentRoot.__coherentInstance) {
|
|
503
|
+
componentRoot.__coherentInstance.rerender();
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
} else if (instance.state) {
|
|
507
|
+
currentState = instance.state;
|
|
508
|
+
currentSetState = (newState) => {
|
|
509
|
+
if (typeof newState === "object") {
|
|
510
|
+
Object.assign(instance.state, newState);
|
|
511
|
+
}
|
|
512
|
+
const componentRoot = domElement.closest("[data-coherent-component]");
|
|
513
|
+
if (componentRoot && componentRoot.__coherentInstance) {
|
|
514
|
+
componentRoot.__coherentInstance.rerender();
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
const result = handler.call(domElement, event, currentState, currentSetState);
|
|
519
|
+
return result;
|
|
520
|
+
} catch (_error) {
|
|
521
|
+
console.error(`Error in ${eventName} handler:`, _error);
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
if (domElement.hasAttribute(eventName)) {
|
|
525
|
+
domElement.removeAttribute(eventName);
|
|
526
|
+
}
|
|
527
|
+
domElement[handlerKey] = wrappedHandler;
|
|
528
|
+
const useCapture = eventType !== "input" && eventType !== "change";
|
|
529
|
+
domElement.addEventListener(eventType, wrappedHandler, useCapture);
|
|
530
|
+
if (instance.eventListeners && Array.isArray(instance.eventListeners)) {
|
|
531
|
+
instance.eventListeners.push({
|
|
532
|
+
element: domElement,
|
|
533
|
+
event: eventType,
|
|
534
|
+
handler: wrappedHandler
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
if (elementProps.children) {
|
|
540
|
+
const children = Array.isArray(elementProps.children) ? elementProps.children : [elementProps.children];
|
|
541
|
+
children.forEach((child, index) => {
|
|
542
|
+
const childElement = domElement.children[index];
|
|
543
|
+
if (childElement && child) {
|
|
544
|
+
traverseAndAttach(childElement, child, [...path, "children", index]);
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
traverseAndAttach(rootElement, virtualElement);
|
|
551
|
+
}
|
|
552
|
+
function attachEventListeners(element, instance) {
|
|
553
|
+
try {
|
|
554
|
+
if (instance && instance.eventListeners && Array.isArray(instance.eventListeners)) {
|
|
555
|
+
instance.eventListeners.forEach(({ element: element2, event, handler }) => {
|
|
556
|
+
if (element2 && typeof element2.removeEventListener === "function") {
|
|
557
|
+
element2.removeEventListener(event, handler);
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
instance.eventListeners = [];
|
|
561
|
+
}
|
|
562
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (!element || typeof element.querySelectorAll !== "function") {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
const actionElements = element.querySelectorAll("[data-action]");
|
|
569
|
+
actionElements.forEach((actionElement) => {
|
|
570
|
+
if (!actionElement || typeof actionElement.getAttribute !== "function") return;
|
|
571
|
+
const action = actionElement.getAttribute("data-action");
|
|
572
|
+
const target = actionElement.getAttribute("data-target") || "default";
|
|
573
|
+
const event = actionElement.getAttribute("data-event") || "click";
|
|
574
|
+
if (action) {
|
|
575
|
+
const handler = (e) => {
|
|
576
|
+
if (e && typeof e.preventDefault === "function") {
|
|
577
|
+
e.preventDefault();
|
|
578
|
+
}
|
|
579
|
+
handleComponentAction(e, action, target, instance);
|
|
580
|
+
};
|
|
581
|
+
if (typeof actionElement.addEventListener === "function") {
|
|
582
|
+
actionElement.addEventListener(event, handler);
|
|
583
|
+
if (instance.eventListeners && Array.isArray(instance.eventListeners)) {
|
|
584
|
+
instance.eventListeners.push({
|
|
585
|
+
element: actionElement,
|
|
586
|
+
event,
|
|
587
|
+
handler
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
const eventAttributes = ["onclick", "onchange", "oninput", "onfocus", "onblur", "onsubmit"];
|
|
594
|
+
eventAttributes.forEach((eventName) => {
|
|
595
|
+
const attributeSelector = `[${eventName}]`;
|
|
596
|
+
const elements = element.querySelectorAll(attributeSelector);
|
|
597
|
+
elements.forEach((elementWithEvent) => {
|
|
598
|
+
if (!elementWithEvent || typeof elementWithEvent.getAttribute !== "function") return;
|
|
599
|
+
const eventAttr = elementWithEvent.getAttribute(eventName);
|
|
600
|
+
if (eventAttr) {
|
|
601
|
+
console.warn(
|
|
602
|
+
`[Coherent.js] Inline event attribute "${eventName}="${eventAttr}" found but not supported.
|
|
603
|
+
For security and CSP compliance, use one of these alternatives:
|
|
604
|
+
1. Function-based handlers: Pass functions directly in virtual DOM
|
|
605
|
+
2. Data-action registry: <button data-action="actionId" data-event="click">
|
|
606
|
+
3. Event registry: <button data-coherent-event="handlerId" data-coherent-event-type="click">
|
|
607
|
+
See documentation for details.`
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
const dataActionElements = element.querySelectorAll("[data-action]");
|
|
613
|
+
dataActionElements.forEach((actionElement) => {
|
|
614
|
+
if (!actionElement || typeof actionElement.getAttribute !== "function") return;
|
|
615
|
+
const actionId = actionElement.getAttribute("data-action");
|
|
616
|
+
const eventType = actionElement.getAttribute("data-event") || "click";
|
|
617
|
+
if (actionId) {
|
|
618
|
+
let handlerFunc = null;
|
|
619
|
+
if (typeof window !== "undefined" && window.__coherentActionRegistry && window.__coherentActionRegistry[actionId]) {
|
|
620
|
+
handlerFunc = window.__coherentActionRegistry[actionId];
|
|
621
|
+
} else {
|
|
622
|
+
console.warn(`No handler found for action ${actionId}`, window.__coherentActionRegistry);
|
|
623
|
+
}
|
|
624
|
+
if (handlerFunc && typeof handlerFunc === "function") {
|
|
625
|
+
if (typeof actionElement.hasAttribute === "function" && !actionElement.hasAttribute(`data-hydrated-${eventType}`)) {
|
|
626
|
+
actionElement.setAttribute(`data-hydrated-${eventType}`, "true");
|
|
627
|
+
const handler = (e) => {
|
|
628
|
+
try {
|
|
629
|
+
let componentElement = actionElement;
|
|
630
|
+
while (componentElement && !componentElement.hasAttribute("data-coherent-component")) {
|
|
631
|
+
componentElement = componentElement.parentElement;
|
|
632
|
+
}
|
|
633
|
+
if (componentElement && componentElement.__coherentInstance) {
|
|
634
|
+
const instance2 = componentElement.__coherentInstance;
|
|
635
|
+
const state = instance2.state || {};
|
|
636
|
+
const setState = instance2.setState || (() => {
|
|
637
|
+
});
|
|
638
|
+
handlerFunc.call(actionElement, e, state, setState);
|
|
639
|
+
} else {
|
|
640
|
+
handlerFunc.call(actionElement, e);
|
|
641
|
+
}
|
|
642
|
+
} catch (_error) {
|
|
643
|
+
console.warn(`Error executing action handler for ${actionId}:`, _error);
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
if (typeof actionElement.addEventListener === "function") {
|
|
647
|
+
actionElement.addEventListener(eventType, handler);
|
|
648
|
+
if (instance && instance.eventListeners && Array.isArray(instance.eventListeners)) {
|
|
649
|
+
instance.eventListeners.push({
|
|
650
|
+
element: actionElement,
|
|
651
|
+
event: eventType,
|
|
652
|
+
handler
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
const coherentEventElements = element.querySelectorAll("[data-coherent-event]");
|
|
661
|
+
coherentEventElements.forEach((elementWithCoherentEvent) => {
|
|
662
|
+
if (!elementWithCoherentEvent || typeof elementWithCoherentEvent.getAttribute !== "function") return;
|
|
663
|
+
const eventId = elementWithCoherentEvent.getAttribute("data-coherent-event");
|
|
664
|
+
const eventType = elementWithCoherentEvent.getAttribute("data-coherent-event-type");
|
|
665
|
+
if (eventId && eventType) {
|
|
666
|
+
let handlerFunc = null;
|
|
667
|
+
if (typeof window !== "undefined" && window.__coherentEventRegistry && window.__coherentEventRegistry[eventId]) {
|
|
668
|
+
handlerFunc = window.__coherentEventRegistry[eventId];
|
|
669
|
+
}
|
|
670
|
+
if (handlerFunc && typeof handlerFunc === "function") {
|
|
671
|
+
if (typeof elementWithCoherentEvent.hasAttribute === "function" && !elementWithCoherentEvent.hasAttribute(`data-hydrated-${eventType}`)) {
|
|
672
|
+
elementWithCoherentEvent.setAttribute(`data-hydrated-${eventType}`, "true");
|
|
673
|
+
const handler = (e) => {
|
|
674
|
+
try {
|
|
675
|
+
const state = instance.state || {};
|
|
676
|
+
const setState = instance.setState || (() => {
|
|
677
|
+
});
|
|
678
|
+
handlerFunc.call(elementWithCoherentEvent, e, state, setState);
|
|
679
|
+
} catch (_error) {
|
|
680
|
+
console.warn(`Error executing coherent event handler:`, _error);
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
if (typeof elementWithCoherentEvent.addEventListener === "function") {
|
|
684
|
+
elementWithCoherentEvent.addEventListener(eventType, handler);
|
|
685
|
+
if (instance.eventListeners && Array.isArray(instance.eventListeners)) {
|
|
686
|
+
instance.eventListeners.push({
|
|
687
|
+
element: elementWithCoherentEvent,
|
|
688
|
+
event: eventType,
|
|
689
|
+
handler
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
} catch (_error) {
|
|
698
|
+
console.warn("Error attaching event listeners:", _error);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
function handleComponentAction(event, action, target, instance) {
|
|
702
|
+
switch (action) {
|
|
703
|
+
case "increment":
|
|
704
|
+
if (instance.state && instance.state.count !== void 0) {
|
|
705
|
+
const step = instance.state.step || 1;
|
|
706
|
+
instance.setState({ count: instance.state.count + step });
|
|
707
|
+
const countElement = instance.element.querySelector('[data-ref="count"]');
|
|
708
|
+
if (countElement) {
|
|
709
|
+
countElement.textContent = `Count: ${instance.state.count + step}`;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
break;
|
|
713
|
+
case "decrement":
|
|
714
|
+
if (instance.state && instance.state.count !== void 0) {
|
|
715
|
+
const step = instance.state.step || 1;
|
|
716
|
+
instance.setState({ count: instance.state.count - step });
|
|
717
|
+
const countElement = instance.element.querySelector('[data-ref="count"]');
|
|
718
|
+
if (countElement) {
|
|
719
|
+
countElement.textContent = `Count: ${instance.state.count - step}`;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
break;
|
|
723
|
+
case "reset":
|
|
724
|
+
if (instance.state) {
|
|
725
|
+
const initialCount = instance.props.initialCount || 0;
|
|
726
|
+
instance.setState({ count: initialCount });
|
|
727
|
+
const countElement = instance.element.querySelector('[data-ref="count"]');
|
|
728
|
+
if (countElement) {
|
|
729
|
+
countElement.textContent = `Count: ${initialCount}`;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
break;
|
|
733
|
+
case "changeStep":
|
|
734
|
+
const inputElement = event.target;
|
|
735
|
+
if (inputElement && inputElement.value) {
|
|
736
|
+
const stepValue = parseInt(inputElement.value, 10);
|
|
737
|
+
if (!isNaN(stepValue) && stepValue >= 1 && stepValue <= 10) {
|
|
738
|
+
instance.setState({ step: stepValue });
|
|
739
|
+
const stepElement = instance.element.querySelector('[data-ref="step"]');
|
|
740
|
+
if (stepElement) {
|
|
741
|
+
stepElement.textContent = `Step: ${stepValue}`;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
break;
|
|
746
|
+
case "toggle":
|
|
747
|
+
const todoIndex = event.target && event.target.getAttribute ? parseInt(event.target.getAttribute("data-todo-index")) : -1;
|
|
748
|
+
if (todoIndex >= 0 && instance.state && instance.state.todos && instance.state.todos[todoIndex]) {
|
|
749
|
+
const newTodos = [...instance.state.todos];
|
|
750
|
+
newTodos[todoIndex].completed = !newTodos[todoIndex].completed;
|
|
751
|
+
instance.setState({ todos: newTodos });
|
|
752
|
+
}
|
|
753
|
+
break;
|
|
754
|
+
case "add":
|
|
755
|
+
if (typeof document !== "undefined" && document.getElementById) {
|
|
756
|
+
const input = document.getElementById(`new-todo-${target}`);
|
|
757
|
+
if (input && input.value && input.value.trim()) {
|
|
758
|
+
if (instance.state && instance.state.todos) {
|
|
759
|
+
const newTodos = [
|
|
760
|
+
...instance.state.todos,
|
|
761
|
+
{ text: input.value.trim(), completed: false }
|
|
762
|
+
];
|
|
763
|
+
instance.setState({ todos: newTodos });
|
|
764
|
+
input.value = "";
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
break;
|
|
769
|
+
default:
|
|
770
|
+
if (instance && typeof instance[action] === "function") {
|
|
771
|
+
try {
|
|
772
|
+
instance[action](event, target);
|
|
773
|
+
} catch (_error) {
|
|
774
|
+
console.warn(`Error executing custom action ${action}:`, _error);
|
|
775
|
+
}
|
|
776
|
+
} else {
|
|
777
|
+
if (typeof window !== "undefined" && window.__coherentActionRegistry && window.__coherentActionRegistry[action]) {
|
|
778
|
+
const handlerFunc = window.__coherentActionRegistry[action];
|
|
779
|
+
const state = instance ? instance.state || {} : {};
|
|
780
|
+
const setState = instance && instance.setState ? instance.setState.bind(instance) : (() => {
|
|
781
|
+
});
|
|
782
|
+
try {
|
|
783
|
+
handlerFunc(event, state, setState);
|
|
784
|
+
} catch (_error) {
|
|
785
|
+
console.warn(`Error executing action handler ${action}:`, _error);
|
|
786
|
+
}
|
|
787
|
+
} else {
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
function hydrateAll(elements, components, propsArray = []) {
|
|
793
|
+
if (elements.length !== components.length) {
|
|
794
|
+
throw new Error("Number of elements must match number of components");
|
|
795
|
+
}
|
|
796
|
+
return elements.map((element, index) => {
|
|
797
|
+
const component = components[index];
|
|
798
|
+
const props = propsArray[index] || {};
|
|
799
|
+
return hydrate(element, component, props);
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
function hydrateBySelector(selector, component, props = {}) {
|
|
803
|
+
if (typeof window === "undefined" || !document.querySelectorAll) {
|
|
804
|
+
return [];
|
|
805
|
+
}
|
|
806
|
+
const elements = document.querySelectorAll(selector);
|
|
807
|
+
return Array.from(elements).map((element) => hydrate(element, component, props));
|
|
808
|
+
}
|
|
809
|
+
function enableClientEvents(rootElement = document) {
|
|
810
|
+
if (typeof window === "undefined" || !rootElement.querySelectorAll) {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
function makeHydratable(component, options = {}) {
|
|
815
|
+
const componentName = options.componentName || component.name || "AnonymousComponent";
|
|
816
|
+
const hydratableComponent = function(props = {}) {
|
|
817
|
+
return component(props);
|
|
818
|
+
};
|
|
819
|
+
Object.defineProperty(hydratableComponent, "name", {
|
|
820
|
+
value: componentName,
|
|
821
|
+
writable: false
|
|
822
|
+
});
|
|
823
|
+
Object.keys(component).forEach((key) => {
|
|
824
|
+
hydratableComponent[key] = component[key];
|
|
825
|
+
});
|
|
826
|
+
if (component.prototype) {
|
|
827
|
+
hydratableComponent.prototype = Object.create(component.prototype);
|
|
828
|
+
}
|
|
829
|
+
if (component.__wrappedComponent && component.__stateContainer) {
|
|
830
|
+
hydratableComponent.__wrappedComponent = component.__wrappedComponent;
|
|
831
|
+
hydratableComponent.__stateContainer = component.__stateContainer;
|
|
832
|
+
}
|
|
833
|
+
hydratableComponent.isHydratable = true;
|
|
834
|
+
hydratableComponent.hydrationOptions = options;
|
|
835
|
+
hydratableComponent.autoHydrate = function(componentRegistry = {}) {
|
|
836
|
+
if (!componentRegistry[hydratableComponent.name || "AnonymousComponent"]) {
|
|
837
|
+
componentRegistry[hydratableComponent.name || "AnonymousComponent"] = hydratableComponent;
|
|
838
|
+
}
|
|
839
|
+
autoHydrate(componentRegistry);
|
|
840
|
+
};
|
|
841
|
+
hydratableComponent.isHydratable = true;
|
|
842
|
+
hydratableComponent.withHydrationData = function(customProps = {}, customState = null) {
|
|
843
|
+
return {
|
|
844
|
+
render: function(props = {}) {
|
|
845
|
+
const mergedProps = { ...customProps, ...props };
|
|
846
|
+
const result = hydratableComponent(mergedProps);
|
|
847
|
+
const hydrationData = hydratableComponent.getHydrationData(mergedProps, customState);
|
|
848
|
+
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
849
|
+
const tagName = Object.keys(result)[0];
|
|
850
|
+
const elementProps = result[tagName];
|
|
851
|
+
if (elementProps && typeof elementProps === "object") {
|
|
852
|
+
Object.keys(hydrationData.hydrationAttributes).forEach((attr) => {
|
|
853
|
+
const value = hydrationData.hydrationAttributes[attr];
|
|
854
|
+
if (value !== null) {
|
|
855
|
+
elementProps[attr] = value;
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return result;
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
};
|
|
864
|
+
hydratableComponent.getHydrationData = function(props = {}, state = null) {
|
|
865
|
+
return {
|
|
866
|
+
componentName,
|
|
867
|
+
props,
|
|
868
|
+
initialState: options.initialState,
|
|
869
|
+
// Add data attributes for hydration
|
|
870
|
+
hydrationAttributes: {
|
|
871
|
+
"data-coherent-component": componentName,
|
|
872
|
+
"data-coherent-state": state ? JSON.stringify(state) : options.initialState ? JSON.stringify(options.initialState) : null,
|
|
873
|
+
"data-coherent-props": Object.keys(props).length > 0 ? JSON.stringify(props) : null
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
};
|
|
877
|
+
hydratableComponent.renderWithHydration = function(props = {}) {
|
|
878
|
+
const result = hydratableComponent(props);
|
|
879
|
+
let state = null;
|
|
880
|
+
if (hydratableComponent.__wrappedComponent && hydratableComponent.__stateContainer) {
|
|
881
|
+
try {
|
|
882
|
+
state = hydratableComponent.__stateContainer.getState();
|
|
883
|
+
} catch (e) {
|
|
884
|
+
console.warn("Could not get component state:", e);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
const hydrationData = hydratableComponent.getHydrationData(props, state);
|
|
888
|
+
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
889
|
+
const tagName = Object.keys(result)[0];
|
|
890
|
+
const elementProps = result[tagName];
|
|
891
|
+
if (elementProps && typeof elementProps === "object") {
|
|
892
|
+
Object.keys(hydrationData.hydrationAttributes).forEach((attr) => {
|
|
893
|
+
const value = hydrationData.hydrationAttributes[attr];
|
|
894
|
+
if (value !== null) {
|
|
895
|
+
elementProps[attr] = value;
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return result;
|
|
901
|
+
};
|
|
902
|
+
return hydratableComponent;
|
|
903
|
+
}
|
|
904
|
+
function autoHydrate(componentRegistry = {}) {
|
|
905
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
if (componentRegistry === window) {
|
|
909
|
+
console.warn("\u26A0\uFE0F Component registry is the window object! This suggests the registry was not properly initialized.");
|
|
910
|
+
componentRegistry = window.componentRegistry || {};
|
|
911
|
+
}
|
|
912
|
+
window.__coherentEventRegistry = window.__coherentEventRegistry || {};
|
|
913
|
+
window.__coherentActionRegistry = window.__coherentActionRegistry || {};
|
|
914
|
+
const hydrateComponents = () => {
|
|
915
|
+
const hydrateableElements = document.querySelectorAll("[data-coherent-component]");
|
|
916
|
+
hydrateableElements.forEach((element) => {
|
|
917
|
+
const componentName = element.getAttribute("data-coherent-component");
|
|
918
|
+
let component = componentRegistry[componentName];
|
|
919
|
+
if (!component) {
|
|
920
|
+
for (const comp of Object.values(componentRegistry)) {
|
|
921
|
+
if (comp && comp.isHydratable) {
|
|
922
|
+
component = comp;
|
|
923
|
+
break;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
if (!component) {
|
|
928
|
+
console.error(`\u274C Component ${componentName} not found in registry`);
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
if (component) {
|
|
932
|
+
try {
|
|
933
|
+
const propsAttr = element.getAttribute("data-coherent-props");
|
|
934
|
+
const props = propsAttr ? JSON.parse(propsAttr) : {};
|
|
935
|
+
const stateAttr = element.getAttribute("data-coherent-state");
|
|
936
|
+
const initialState = stateAttr ? JSON.parse(stateAttr) : null;
|
|
937
|
+
const instance = hydrate(element, component, props, { initialState });
|
|
938
|
+
if (instance) {
|
|
939
|
+
} else {
|
|
940
|
+
console.warn(`\u274C Failed to hydrate component: ${componentName}`);
|
|
941
|
+
}
|
|
942
|
+
} catch (_error) {
|
|
943
|
+
console.error(`\u274C Failed to auto-hydrate component ${componentName}:`, _error);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
enableClientEvents();
|
|
948
|
+
};
|
|
949
|
+
if (document.readyState === "loading") {
|
|
950
|
+
document.addEventListener("DOMContentLoaded", hydrateComponents);
|
|
951
|
+
} else {
|
|
952
|
+
hydrateComponents();
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
export {
|
|
956
|
+
autoHydrate,
|
|
957
|
+
enableClientEvents,
|
|
958
|
+
hydrate,
|
|
959
|
+
hydrateAll,
|
|
960
|
+
hydrateBySelector,
|
|
961
|
+
makeHydratable,
|
|
962
|
+
registerEventHandler
|
|
963
|
+
};
|
|
964
|
+
//# sourceMappingURL=index.js.map
|