@weaverse/react 5.23.0 → 5.24.1
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/dist/index.js +489 -498
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +456 -461
- package/dist/index.mjs.map +1 -1
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/renderer.d.ts.map +1 -1
- package/package.json +5 -18
package/dist/index.js
CHANGED
|
@@ -1,541 +1,532 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
1
3
|
var __create = Object.create;
|
|
2
4
|
var __defProp = Object.defineProperty;
|
|
3
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
6
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
-
};
|
|
11
9
|
var __copyProps = (to, from, except, desc) => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
18
|
};
|
|
19
|
-
var
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
useChildInstances: () => useChildInstances,
|
|
40
|
-
useItemInstance: () => useItemInstance,
|
|
41
|
-
useParentInstance: () => useParentInstance,
|
|
42
|
-
useSafeExternalStore: () => useSafeExternalStore,
|
|
43
|
-
useWeaverse: () => useWeaverse
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
//#endregion
|
|
24
|
+
let _weaverse_core = require("@weaverse/core");
|
|
25
|
+
let react = require("react");
|
|
26
|
+
react = __toESM(react);
|
|
27
|
+
let clsx = require("clsx");
|
|
28
|
+
clsx = __toESM(clsx);
|
|
29
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
30
|
+
//#region src/context.ts
|
|
31
|
+
/** Provides the active Weaverse runtime to React components. */
|
|
32
|
+
let WeaverseContext = (0, react.createContext)({});
|
|
33
|
+
/** Provider component for supplying the active Weaverse runtime. */
|
|
34
|
+
let WeaverseContextProvider = WeaverseContext.Provider;
|
|
35
|
+
/** Identifies the current rendered item and its parent item. */
|
|
36
|
+
let WeaverseItemContext = (0, react.createContext)({
|
|
37
|
+
id: "",
|
|
38
|
+
parentId: ""
|
|
44
39
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// src/hooks.ts
|
|
55
|
-
var import_core = require("@weaverse/core");
|
|
56
|
-
var import_react2 = require("react");
|
|
57
|
-
|
|
58
|
-
// src/utils/data-connector.ts
|
|
59
|
-
var TEMPLATE_REGEX = /\{\{([^}]+)\}\}/g;
|
|
60
|
-
var ARRAY_INDEX_REGEX = /^(\w+)\[(\d+)\]$/;
|
|
61
|
-
var CACHE_EVICTION_RATIO = 0.25;
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/utils/data-connector.ts
|
|
42
|
+
const TEMPLATE_REGEX = /\{\{([^}]+)\}\}/g;
|
|
43
|
+
const ARRAY_INDEX_REGEX = /^(\w+)\[(\d+)\]$/;
|
|
44
|
+
const CACHE_EVICTION_RATIO = .25;
|
|
45
|
+
/**
|
|
46
|
+
* Validates that a property name is safe to access (prevents prototype pollution)
|
|
47
|
+
* Enhanced to handle route-specific patterns with special characters
|
|
48
|
+
*/
|
|
62
49
|
function isSafePropertyName(key) {
|
|
63
|
-
|
|
64
|
-
|
|
50
|
+
return ![
|
|
51
|
+
"__proto__",
|
|
52
|
+
"constructor",
|
|
53
|
+
"prototype"
|
|
54
|
+
].includes(key);
|
|
65
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Parses a template path into route key and property path components
|
|
58
|
+
* Handles complex routes like: routes/($locale).blogs.$blogHandle.$articleHandle
|
|
59
|
+
* Enhanced with caching for improved performance
|
|
60
|
+
*
|
|
61
|
+
* @param path - The full path from template (e.g., "routes/($locale)._index.weaverseData.page.name")
|
|
62
|
+
* @param dataContext - Available route data context
|
|
63
|
+
* @returns Object with routeKey and propertyPath
|
|
64
|
+
*/
|
|
66
65
|
function parseRouteAndProperty(path, dataContext) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
66
|
+
if (!dataContext || typeof path !== "string") return {
|
|
67
|
+
routeKey: null,
|
|
68
|
+
propertyPath: path
|
|
69
|
+
};
|
|
70
|
+
const trimmedPath = path.trim();
|
|
71
|
+
const cacheKey = `${trimmedPath}::${getDataContextHash(dataContext)}`;
|
|
72
|
+
const cachedResult = routeParsingCache.get(cacheKey);
|
|
73
|
+
if (cachedResult) {
|
|
74
|
+
cachedResult.timestamp = Date.now();
|
|
75
|
+
return {
|
|
76
|
+
routeKey: cachedResult.routeKey,
|
|
77
|
+
propertyPath: cachedResult.propertyPath
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const availableRoutes = Object.keys(dataContext).sort((a, b) => b.length - a.length);
|
|
81
|
+
let result = {
|
|
82
|
+
routeKey: null,
|
|
83
|
+
propertyPath: trimmedPath
|
|
84
|
+
};
|
|
85
|
+
let found = false;
|
|
86
|
+
for (const routeKey of availableRoutes) {
|
|
87
|
+
if (trimmedPath === routeKey) {
|
|
88
|
+
result = {
|
|
89
|
+
routeKey,
|
|
90
|
+
propertyPath: ""
|
|
91
|
+
};
|
|
92
|
+
found = true;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
if (trimmedPath.startsWith(`${routeKey}.`)) {
|
|
96
|
+
result = {
|
|
97
|
+
routeKey,
|
|
98
|
+
propertyPath: trimmedPath.slice(routeKey.length + 1)
|
|
99
|
+
};
|
|
100
|
+
found = true;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!found) result = {
|
|
105
|
+
routeKey: null,
|
|
106
|
+
propertyPath: trimmedPath
|
|
107
|
+
};
|
|
108
|
+
if (routeParsingCache.size >= ROUTE_PARSING_CACHE_MAX_SIZE) {
|
|
109
|
+
const entries = Array.from(routeParsingCache.entries());
|
|
110
|
+
entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
111
|
+
const toRemove = Math.floor(entries.length * CACHE_EVICTION_RATIO);
|
|
112
|
+
for (let i = 0; i < toRemove; i++) routeParsingCache.delete(entries[i][0]);
|
|
113
|
+
}
|
|
114
|
+
routeParsingCache.set(cacheKey, {
|
|
115
|
+
routeKey: result.routeKey,
|
|
116
|
+
propertyPath: result.propertyPath,
|
|
117
|
+
timestamp: Date.now()
|
|
118
|
+
});
|
|
119
|
+
return result;
|
|
119
120
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
const templateCache = /* @__PURE__ */ new Map();
|
|
122
|
+
const MAX_CACHE_SIZE = 100;
|
|
123
|
+
const fallbackCache = /* @__PURE__ */ new Map();
|
|
124
|
+
const FALLBACK_CACHE_MAX_SIZE = 50;
|
|
125
|
+
const routeParsingCache = /* @__PURE__ */ new Map();
|
|
126
|
+
const ROUTE_PARSING_CACHE_MAX_SIZE = 100;
|
|
127
|
+
const dataContextHashCache = /* @__PURE__ */ new WeakMap();
|
|
128
|
+
let hashCounter = 0;
|
|
129
|
+
/**
|
|
130
|
+
* Safely gets a nested value from an object using dot notation path
|
|
131
|
+
* Supports array indexing with [index] syntax
|
|
132
|
+
* @param obj - The object to traverse
|
|
133
|
+
* @param path - Dot notation path (e.g., "user.name" or "items[0].title")
|
|
134
|
+
* @param visited - Set to track visited objects for circular reference protection
|
|
135
|
+
* @returns The resolved value or undefined if not found
|
|
136
|
+
*/
|
|
128
137
|
function getNestedValue(obj, path, visited = /* @__PURE__ */ new WeakSet()) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
return current !== null && key in current ? current[key] : void 0;
|
|
155
|
-
}, obj);
|
|
138
|
+
return path.split(".").reduce((current, key) => {
|
|
139
|
+
if (!current || typeof current !== "object") return;
|
|
140
|
+
if (visited.has(current)) {
|
|
141
|
+
console.warn("Circular reference detected in data connector path:", path);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
visited.add(current);
|
|
145
|
+
const arrayMatch = key.match(ARRAY_INDEX_REGEX);
|
|
146
|
+
if (arrayMatch) {
|
|
147
|
+
const [, arrayKey, index] = arrayMatch;
|
|
148
|
+
const arrayObj = current[arrayKey];
|
|
149
|
+
if (Array.isArray(arrayObj)) {
|
|
150
|
+
const indexNum = Number.parseInt(index, 10);
|
|
151
|
+
if (indexNum >= 0 && indexNum < arrayObj.length) return arrayObj[indexNum];
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (!isSafePropertyName(key)) {
|
|
156
|
+
console.warn(`Unsafe property access attempted: ${key}`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
return current !== null && key in current ? current[key] : void 0;
|
|
160
|
+
}, obj);
|
|
156
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Sanitizes a string value to prevent XSS attacks
|
|
164
|
+
* @param value - The value to sanitize
|
|
165
|
+
* @returns Sanitized string safe for HTML rendering
|
|
166
|
+
*/
|
|
157
167
|
function sanitizeValue(value) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
const stringValue = String(value);
|
|
162
|
-
return stringValue.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\//g, "/");
|
|
168
|
+
if (value === null || value === void 0) return "";
|
|
169
|
+
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\//g, "/");
|
|
163
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Creates a stable hash for dataContext object
|
|
173
|
+
* Uses WeakMap to cache hash per object reference (no serialization needed)
|
|
174
|
+
*/
|
|
164
175
|
function getDataContextHash(data) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
const hash = dataContextHashCache.get(data);
|
|
169
|
-
return hash || `ctx_${++hashCounter}`;
|
|
176
|
+
if (!dataContextHashCache.has(data)) dataContextHashCache.set(data, `ctx_${++hashCounter}`);
|
|
177
|
+
return dataContextHashCache.get(data) || `ctx_${++hashCounter}`;
|
|
170
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Creates a safe cache key from content and data
|
|
181
|
+
* Uses stable hashing instead of JSON.stringify for massive performance gain
|
|
182
|
+
*/
|
|
171
183
|
function createCacheKey(content, data) {
|
|
172
|
-
|
|
173
|
-
return `${content}:${dataHash}`;
|
|
184
|
+
return `${content}:${getDataContextHash(data)}`;
|
|
174
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Resolves data from the flat route-keyed data context
|
|
188
|
+
* Enhanced to support complex route patterns with special characters
|
|
189
|
+
* Supports: {{root.layout.shop.name}}, {{routes/($locale)._index.data}}, {{routes/($locale).blogs.$handle.$article.content}}
|
|
190
|
+
*/
|
|
175
191
|
function resolveDataFromContext(path, dataContext) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
fallbackCache.set(trimmedPath, {
|
|
223
|
-
routeKey: routeKey2,
|
|
224
|
-
value,
|
|
225
|
-
timestamp: Date.now()
|
|
226
|
-
});
|
|
227
|
-
return value;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
return getNestedValue(dataContext, trimmedPath);
|
|
192
|
+
if (!dataContext) return;
|
|
193
|
+
const trimmedPath = path.trim();
|
|
194
|
+
const { routeKey, propertyPath } = parseRouteAndProperty(trimmedPath, dataContext);
|
|
195
|
+
if (routeKey && dataContext[routeKey]) {
|
|
196
|
+
const routeData = dataContext[routeKey];
|
|
197
|
+
if (typeof routeData === "object" && routeData !== null) {
|
|
198
|
+
if (propertyPath) return getNestedValue(routeData, propertyPath);
|
|
199
|
+
return routeData;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const cachedResult = fallbackCache.get(trimmedPath);
|
|
203
|
+
if (cachedResult && dataContext[cachedResult.routeKey]) {
|
|
204
|
+
const value = getNestedValue(dataContext[cachedResult.routeKey], trimmedPath);
|
|
205
|
+
if (value !== void 0) {
|
|
206
|
+
cachedResult.timestamp = Date.now();
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const priorityRoutes = [
|
|
211
|
+
"root",
|
|
212
|
+
"routes/product",
|
|
213
|
+
"routes/collection"
|
|
214
|
+
];
|
|
215
|
+
const allRoutes = [...priorityRoutes, ...Object.keys(dataContext).filter((k) => !priorityRoutes.includes(k))];
|
|
216
|
+
for (const routeKey of allRoutes) {
|
|
217
|
+
const routeData = dataContext[routeKey];
|
|
218
|
+
if (routeData && typeof routeData === "object" && routeData !== null) {
|
|
219
|
+
const value = getNestedValue(routeData, trimmedPath);
|
|
220
|
+
if (value !== void 0) {
|
|
221
|
+
if (fallbackCache.size >= FALLBACK_CACHE_MAX_SIZE) {
|
|
222
|
+
const entries = Array.from(fallbackCache.entries());
|
|
223
|
+
entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
224
|
+
const toRemove = Math.floor(entries.length * CACHE_EVICTION_RATIO);
|
|
225
|
+
for (let i = 0; i < toRemove; i++) fallbackCache.delete(entries[i][0]);
|
|
226
|
+
}
|
|
227
|
+
fallbackCache.set(trimmedPath, {
|
|
228
|
+
routeKey,
|
|
229
|
+
value,
|
|
230
|
+
timestamp: Date.now()
|
|
231
|
+
});
|
|
232
|
+
return value;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return getNestedValue(dataContext, trimmedPath);
|
|
232
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Replaces data connector placeholders in content with actual values from data context
|
|
240
|
+
* Supports {{path}} syntax with dot notation, array indexing, and route prefixes
|
|
241
|
+
*
|
|
242
|
+
* Enhanced syntax:
|
|
243
|
+
* - {{root.siteSettings.title}} - Access root route data
|
|
244
|
+
* - {{layout.navigation.items}} - Access layout route data
|
|
245
|
+
* - {{parent.category.name}} - Access parent route data
|
|
246
|
+
* - {{current.product.title}} - Access current route data
|
|
247
|
+
* - {{user.name}} - Auto-resolved from combined data (backward compatible)
|
|
248
|
+
*
|
|
249
|
+
* @param content - The content string containing {{}} placeholders
|
|
250
|
+
* @param dataContext - The enhanced WeaverseDataContext with route-keyed data
|
|
251
|
+
* @returns The content with placeholders replaced by actual values
|
|
252
|
+
*/
|
|
233
253
|
function replaceContentDataConnectors(content, dataContext) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const firstKey = templateCache.keys().next().value;
|
|
260
|
-
templateCache.delete(firstKey);
|
|
261
|
-
}
|
|
262
|
-
templateCache.set(cacheKey, newContent);
|
|
263
|
-
return newContent;
|
|
264
|
-
} catch (error) {
|
|
265
|
-
console.warn("Error processing data connectors:", error, { content });
|
|
266
|
-
return content;
|
|
267
|
-
}
|
|
254
|
+
if (!dataContext || typeof content !== "string") return content;
|
|
255
|
+
try {
|
|
256
|
+
const cacheKey = createCacheKey(content, dataContext);
|
|
257
|
+
const cachedContent = templateCache.get(cacheKey);
|
|
258
|
+
if (cachedContent) return cachedContent;
|
|
259
|
+
const newContent = content.replace(TEMPLATE_REGEX, (match, path) => {
|
|
260
|
+
try {
|
|
261
|
+
const value = resolveDataFromContext(path, dataContext);
|
|
262
|
+
if (value === void 0 || value === null) return match;
|
|
263
|
+
return sanitizeValue(value);
|
|
264
|
+
} catch (pathError) {
|
|
265
|
+
console.warn(`Error resolving data connector path "${path}":`, pathError);
|
|
266
|
+
return match;
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
if (templateCache.size >= MAX_CACHE_SIZE) {
|
|
270
|
+
const firstKey = templateCache.keys().next().value;
|
|
271
|
+
templateCache.delete(firstKey);
|
|
272
|
+
}
|
|
273
|
+
templateCache.set(cacheKey, newContent);
|
|
274
|
+
return newContent;
|
|
275
|
+
} catch (error) {
|
|
276
|
+
console.warn("Error processing data connectors:", error, { content });
|
|
277
|
+
return content;
|
|
278
|
+
}
|
|
268
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* Fast check if data contains any {{...}} placeholders
|
|
282
|
+
* Returns true if any string value contains the pattern
|
|
283
|
+
* This is MUCH faster than processing and allows early exit
|
|
284
|
+
*
|
|
285
|
+
* IMPORTANT: Uses global TEMPLATE_REGEX, must reset lastIndex
|
|
286
|
+
*/
|
|
269
287
|
function hasPlaceholders(data, visited = /* @__PURE__ */ new WeakSet()) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
for (const key of Object.keys(data)) {
|
|
289
|
-
try {
|
|
290
|
-
const value = data[key];
|
|
291
|
-
if (hasPlaceholders(value, visited)) {
|
|
292
|
-
return true;
|
|
293
|
-
}
|
|
294
|
-
} catch {
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
return false;
|
|
298
|
-
} finally {
|
|
299
|
-
visited.delete(data);
|
|
300
|
-
}
|
|
288
|
+
if (typeof data === "string") {
|
|
289
|
+
TEMPLATE_REGEX.lastIndex = 0;
|
|
290
|
+
return TEMPLATE_REGEX.test(data);
|
|
291
|
+
}
|
|
292
|
+
if (typeof data !== "object" || data === null) return false;
|
|
293
|
+
if (data instanceof Date) return false;
|
|
294
|
+
if (visited.has(data)) return false;
|
|
295
|
+
visited.add(data);
|
|
296
|
+
try {
|
|
297
|
+
if (Array.isArray(data)) return data.some((item) => hasPlaceholders(item, visited));
|
|
298
|
+
for (const key of Object.keys(data)) try {
|
|
299
|
+
const value = data[key];
|
|
300
|
+
if (hasPlaceholders(value, visited)) return true;
|
|
301
|
+
} catch {}
|
|
302
|
+
return false;
|
|
303
|
+
} finally {
|
|
304
|
+
visited.delete(data);
|
|
305
|
+
}
|
|
301
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* Recursively replaces data connector placeholders in any data structure
|
|
309
|
+
* IMMUTABLE: Creates deep copies during processing to avoid mutating original data
|
|
310
|
+
* Handles strings, objects, arrays, and nested combinations
|
|
311
|
+
*
|
|
312
|
+
* PERFORMANCE: Now includes early exit if no placeholders detected
|
|
313
|
+
*
|
|
314
|
+
* @param data - The data to process (string, object, array, or primitive)
|
|
315
|
+
* @param dataContext - The data context for replacements
|
|
316
|
+
* @param visited - Set to track visited objects for circular reference protection
|
|
317
|
+
* @param forceClone - Internal flag to force cloning even if no placeholders (for parent immutability)
|
|
318
|
+
* @returns IMMUTABLE copy of the data with all string placeholders replaced
|
|
319
|
+
*/
|
|
302
320
|
function replaceContentDataConnectorsDeep(data, dataContext, visited = /* @__PURE__ */ new WeakSet(), forceClone = false) {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
item,
|
|
334
|
-
dataContext,
|
|
335
|
-
visited,
|
|
336
|
-
dataHasPlaceholders
|
|
337
|
-
)
|
|
338
|
-
);
|
|
339
|
-
return result2;
|
|
340
|
-
}
|
|
341
|
-
const result = {};
|
|
342
|
-
for (const key of Object.keys(data)) {
|
|
343
|
-
try {
|
|
344
|
-
const value = data[key];
|
|
345
|
-
result[key] = replaceContentDataConnectorsDeep(
|
|
346
|
-
value,
|
|
347
|
-
dataContext,
|
|
348
|
-
visited,
|
|
349
|
-
dataHasPlaceholders
|
|
350
|
-
);
|
|
351
|
-
} catch (propertyError) {
|
|
352
|
-
console.warn(`Error accessing property "${key}":`, propertyError);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
return result;
|
|
356
|
-
} catch (error) {
|
|
357
|
-
console.warn("Error in deep data connector replacement:", error);
|
|
358
|
-
return data;
|
|
359
|
-
} finally {
|
|
360
|
-
visited.delete(data);
|
|
361
|
-
}
|
|
321
|
+
if (!dataContext) return data;
|
|
322
|
+
const dataHasPlaceholders = hasPlaceholders(data, /* @__PURE__ */ new WeakSet());
|
|
323
|
+
if (!(dataHasPlaceholders || forceClone)) return data;
|
|
324
|
+
if (typeof data === "string") return replaceContentDataConnectors(data, dataContext);
|
|
325
|
+
if (typeof data !== "object" || data === null) return data;
|
|
326
|
+
if (visited.has(data)) {
|
|
327
|
+
console.warn("Circular reference detected in deep data connector replacement");
|
|
328
|
+
return data;
|
|
329
|
+
}
|
|
330
|
+
visited.add(data);
|
|
331
|
+
try {
|
|
332
|
+
if (data instanceof Date) {
|
|
333
|
+
if (forceClone || dataHasPlaceholders) return new Date(data.getTime());
|
|
334
|
+
return data;
|
|
335
|
+
}
|
|
336
|
+
if (Array.isArray(data)) return data.map((item) => replaceContentDataConnectorsDeep(item, dataContext, visited, dataHasPlaceholders));
|
|
337
|
+
const result = {};
|
|
338
|
+
for (const key of Object.keys(data)) try {
|
|
339
|
+
const value = data[key];
|
|
340
|
+
result[key] = replaceContentDataConnectorsDeep(value, dataContext, visited, dataHasPlaceholders);
|
|
341
|
+
} catch (propertyError) {
|
|
342
|
+
console.warn(`Error accessing property "${key}":`, propertyError);
|
|
343
|
+
}
|
|
344
|
+
return result;
|
|
345
|
+
} catch (error) {
|
|
346
|
+
console.warn("Error in deep data connector replacement:", error);
|
|
347
|
+
return data;
|
|
348
|
+
} finally {
|
|
349
|
+
visited.delete(data);
|
|
350
|
+
}
|
|
362
351
|
}
|
|
363
|
-
|
|
364
|
-
|
|
352
|
+
//#endregion
|
|
353
|
+
//#region src/hooks.ts
|
|
354
|
+
/**
|
|
355
|
+
* Returns the active Weaverse runtime from React context.
|
|
356
|
+
*
|
|
357
|
+
* @typeParam T - Runtime subtype expected by the integration.
|
|
358
|
+
*/
|
|
365
359
|
function useWeaverse() {
|
|
366
|
-
|
|
367
|
-
return weaverse;
|
|
360
|
+
return (0, react.useContext)(WeaverseContext);
|
|
368
361
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
362
|
+
/**
|
|
363
|
+
* Returns a Weaverse item instance by ID, or the current rendered item when no
|
|
364
|
+
* ID is provided.
|
|
365
|
+
*
|
|
366
|
+
* @param id - Optional item ID to look up.
|
|
367
|
+
* @returns The matching item instance, or `null` when it is unavailable.
|
|
368
|
+
*/
|
|
369
|
+
let useItemInstance = (id) => {
|
|
370
|
+
let { id: currentId } = (0, react.useContext)(WeaverseItemContext);
|
|
371
|
+
let instance = _weaverse_core.Weaverse.itemInstances.get(id || currentId);
|
|
372
|
+
if (!instance) {
|
|
373
|
+
console.log(`Item instance ${id} not found`);
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
return instance;
|
|
377
377
|
};
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
378
|
+
/**
|
|
379
|
+
* Returns the parent instance of the current rendered item.
|
|
380
|
+
*
|
|
381
|
+
* @returns The parent item instance. At the root, the empty parent ID falls
|
|
382
|
+
* back to the current item instance; returns `null` only when that lookup is
|
|
383
|
+
* unavailable.
|
|
384
|
+
*/
|
|
385
|
+
let useParentInstance = () => {
|
|
386
|
+
let { parentId } = (0, react.useContext)(WeaverseItemContext);
|
|
387
|
+
return useItemInstance(parentId || "");
|
|
381
388
|
};
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
389
|
+
/**
|
|
390
|
+
* Returns the direct child instances of an item.
|
|
391
|
+
*
|
|
392
|
+
* @param id - Optional parent item ID; defaults to the current rendered item.
|
|
393
|
+
*/
|
|
394
|
+
let useChildInstances = (id) => {
|
|
395
|
+
let currentInstance = useItemInstance(id);
|
|
396
|
+
if (!currentInstance) return [];
|
|
397
|
+
let { itemInstances } = _weaverse_core.Weaverse;
|
|
398
|
+
let { data: { children } } = currentInstance;
|
|
399
|
+
return children.map(({ id }) => itemInstances.get(id)).filter(Boolean);
|
|
392
400
|
};
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
401
|
+
//#endregion
|
|
402
|
+
//#region src/renderer.tsx
|
|
403
|
+
const REACT_VERSION_THRESHOLD = 18;
|
|
404
|
+
const reactVersion = Number(react.default.version?.split(".")[0]) || 0;
|
|
405
|
+
/**
|
|
406
|
+
* Reads an external store in React while remaining safe during server rendering
|
|
407
|
+
* and on React versions without `useSyncExternalStore`.
|
|
408
|
+
*
|
|
409
|
+
* @param subscribe - Registers a callback for store changes.
|
|
410
|
+
* @param getSnapshot - Returns the current client snapshot.
|
|
411
|
+
* @param getServerSnapshot - Returns the server snapshot; defaults to `getSnapshot`.
|
|
412
|
+
* @returns The current external-store snapshot.
|
|
413
|
+
*/
|
|
414
|
+
const useSafeExternalStore = (subscribe, getSnapshot, getServerSnapshot = getSnapshot) => {
|
|
415
|
+
if (typeof window === "undefined") return getServerSnapshot();
|
|
416
|
+
if (react.default.useSyncExternalStore) return react.default.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
417
|
+
return getSnapshot();
|
|
409
418
|
};
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
+
/**
|
|
420
|
+
* Determines the root element ID for the Weaverse component tree.
|
|
421
|
+
* Falls back through multiple strategies to ensure a valid root is always found:
|
|
422
|
+
* 1. Use explicit rootId from context.data if available
|
|
423
|
+
* 2. Find the first element with type 'main' in items array
|
|
424
|
+
* 3. Use projectId as final fallback
|
|
425
|
+
*/
|
|
426
|
+
const getRootId = (context) => {
|
|
427
|
+
if (context.data.rootId) return context.data.rootId;
|
|
428
|
+
let mainItem = context.data?.items?.find((item) => item.type === "main");
|
|
429
|
+
if (mainItem?.id) return mainItem.id;
|
|
430
|
+
return context.projectId;
|
|
419
431
|
};
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
432
|
+
/**
|
|
433
|
+
* Renders a Weaverse content tree and provides its runtime to descendant
|
|
434
|
+
* components.
|
|
435
|
+
*/
|
|
436
|
+
const WeaverseRoot = (0, react.memo)(({ context }) => {
|
|
437
|
+
const data = useSafeExternalStore(context.subscribe, context.getSnapShot, context.getSnapShot);
|
|
438
|
+
const rootRef = (0, react.useRef)(null);
|
|
439
|
+
(0, react.useEffect)(() => {
|
|
440
|
+
context.contentRootElement = rootRef.current;
|
|
441
|
+
}, [context]);
|
|
442
|
+
const eventHandlers = context?.studioBridge?.eventHandlers || {};
|
|
443
|
+
if (context.projectId) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
444
|
+
className: "weaverse-content-root",
|
|
445
|
+
...eventHandlers,
|
|
446
|
+
"data-weaverse-project-id": context.projectId,
|
|
447
|
+
"data-weaverse-template-id": data.id,
|
|
448
|
+
ref: rootRef,
|
|
449
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WeaverseContextProvider, {
|
|
450
|
+
value: context,
|
|
451
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ItemInstance, {
|
|
452
|
+
id: getRootId(context),
|
|
453
|
+
parentId: ""
|
|
454
|
+
})
|
|
455
|
+
})
|
|
456
|
+
});
|
|
457
|
+
return null;
|
|
445
458
|
});
|
|
446
459
|
WeaverseRoot.displayName = "WeaverseRoot";
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
}, [instance, id]);
|
|
480
|
-
if (__hidden) {
|
|
481
|
-
return null;
|
|
482
|
-
}
|
|
483
|
-
const Element = elementRegistry.get(type);
|
|
484
|
-
if (Element?.Component) {
|
|
485
|
-
const Component = Element.Component;
|
|
486
|
-
if (Component.displayName !== type) {
|
|
487
|
-
Component.displayName = type;
|
|
488
|
-
}
|
|
489
|
-
if (Component.$$typeof === /* @__PURE__ */ Symbol.for("react.forward_ref") || reactVersion > REACT_VERSION_THRESHOLD) {
|
|
490
|
-
processedRest.ref = instance.ref;
|
|
491
|
-
}
|
|
492
|
-
const childItems = children?.length ? children : childIds?.length ? childIds.map((cid) => ({ id: cid })) : [];
|
|
493
|
-
const renderChildren = childItems.map(
|
|
494
|
-
(item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ItemInstance, { id: item.id, parentId: id }, `${item.id}-${index}`)
|
|
495
|
-
);
|
|
496
|
-
return /* @__PURE__ */ (0, import_react4.createElement)(
|
|
497
|
-
Component,
|
|
498
|
-
{
|
|
499
|
-
...processedRest,
|
|
500
|
-
children: renderChildren.length ? renderChildren : void 0,
|
|
501
|
-
className: (0, import_clsx.default)(processedRest.data?.className),
|
|
502
|
-
"data-wv-id": id,
|
|
503
|
-
"data-wv-type": type,
|
|
504
|
-
key: id
|
|
505
|
-
}
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
|
-
instance._store.deleted = true;
|
|
509
|
-
return null;
|
|
460
|
+
const ItemComponent = (0, react.memo)(({ instance }) => {
|
|
461
|
+
const context = useWeaverse();
|
|
462
|
+
const { elementRegistry } = context;
|
|
463
|
+
const data = useSafeExternalStore(instance.subscribe, instance.getSnapShot, instance.getSnapShot);
|
|
464
|
+
const { id, type, childIds = [], children = [], createdAt, updatedAt, deletedAt, css, __hidden, data: _, ...rest } = data;
|
|
465
|
+
const processedRest = (0, react.useMemo)(() => replaceContentDataConnectorsDeep(rest, context.dataContext), [data, context.dataContext]);
|
|
466
|
+
(0, react.useEffect)(() => {
|
|
467
|
+
if (!instance.ref.current) Object.assign(instance.ref, { current: document.querySelector(`[data-wv-id="${id}"]`) });
|
|
468
|
+
}, [instance, id]);
|
|
469
|
+
if (__hidden) return null;
|
|
470
|
+
const Element = elementRegistry.get(type);
|
|
471
|
+
if (Element?.Component) {
|
|
472
|
+
const Component = Element.Component;
|
|
473
|
+
if (Component.displayName !== type) Component.displayName = type;
|
|
474
|
+
if (Component.$$typeof === Symbol.for("react.forward_ref") || reactVersion > REACT_VERSION_THRESHOLD) processedRest.ref = instance.ref;
|
|
475
|
+
const renderChildren = (children?.length ? children : childIds?.length ? childIds.map((cid) => ({ id: cid })) : []).map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ItemInstance, {
|
|
476
|
+
id: item.id,
|
|
477
|
+
parentId: id
|
|
478
|
+
}, `${item.id}-${index}`));
|
|
479
|
+
return /* @__PURE__ */ (0, react.createElement)(Component, {
|
|
480
|
+
...processedRest,
|
|
481
|
+
children: renderChildren.length ? renderChildren : void 0,
|
|
482
|
+
className: (0, clsx.default)(processedRest.data?.className),
|
|
483
|
+
"data-wv-id": id,
|
|
484
|
+
"data-wv-type": type,
|
|
485
|
+
key: id
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
instance._store.deleted = true;
|
|
489
|
+
return null;
|
|
510
490
|
});
|
|
511
491
|
ItemComponent.displayName = "WeaverseComponent";
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
492
|
+
const ItemInstance = (0, react.memo)(({ id, parentId }) => {
|
|
493
|
+
const instance = useItemInstance(id);
|
|
494
|
+
if (!instance) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
495
|
+
style: { display: "none" },
|
|
496
|
+
children: [
|
|
497
|
+
"Item instance ",
|
|
498
|
+
id,
|
|
499
|
+
" not found"
|
|
500
|
+
]
|
|
501
|
+
});
|
|
502
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WeaverseItemContext.Provider, {
|
|
503
|
+
value: {
|
|
504
|
+
parentId,
|
|
505
|
+
id
|
|
506
|
+
},
|
|
507
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ItemComponent, { instance }, id)
|
|
508
|
+
});
|
|
509
|
+
});
|
|
525
510
|
ItemInstance.displayName = "WeaverseInstanceProvider";
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
511
|
+
//#endregion
|
|
512
|
+
exports.WeaverseContext = WeaverseContext;
|
|
513
|
+
exports.WeaverseContextProvider = WeaverseContextProvider;
|
|
514
|
+
exports.WeaverseItemContext = WeaverseItemContext;
|
|
515
|
+
exports.WeaverseRoot = WeaverseRoot;
|
|
516
|
+
exports.replaceContentDataConnectors = replaceContentDataConnectors;
|
|
517
|
+
exports.replaceContentDataConnectorsDeep = replaceContentDataConnectorsDeep;
|
|
518
|
+
exports.useChildInstances = useChildInstances;
|
|
519
|
+
exports.useItemInstance = useItemInstance;
|
|
520
|
+
exports.useParentInstance = useParentInstance;
|
|
521
|
+
exports.useSafeExternalStore = useSafeExternalStore;
|
|
522
|
+
exports.useWeaverse = useWeaverse;
|
|
523
|
+
Object.keys(_weaverse_core).forEach(function(k) {
|
|
524
|
+
if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
525
|
+
enumerable: true,
|
|
526
|
+
get: function() {
|
|
527
|
+
return _weaverse_core[k];
|
|
528
|
+
}
|
|
529
|
+
});
|
|
540
530
|
});
|
|
531
|
+
|
|
541
532
|
//# sourceMappingURL=index.js.map
|