@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 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
- if (from && typeof from === "object" || typeof from === "function") {
13
- for (let key of __getOwnPropNames(from))
14
- if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
- }
17
- return to;
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 __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- WeaverseContext: () => WeaverseContext,
34
- WeaverseContextProvider: () => WeaverseContextProvider,
35
- WeaverseItemContext: () => WeaverseItemContext,
36
- WeaverseRoot: () => WeaverseRoot,
37
- replaceContentDataConnectors: () => replaceContentDataConnectors,
38
- replaceContentDataConnectorsDeep: () => replaceContentDataConnectorsDeep,
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
- module.exports = __toCommonJS(index_exports);
46
- __reExport(index_exports, require("@weaverse/core"), module.exports);
47
-
48
- // src/context.ts
49
- var import_react = require("react");
50
- var WeaverseContext = (0, import_react.createContext)({});
51
- var WeaverseContextProvider = WeaverseContext.Provider;
52
- var WeaverseItemContext = (0, import_react.createContext)({ id: "", parentId: "" });
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
- const dangerousPatterns = ["__proto__", "constructor", "prototype"];
64
- return !dangerousPatterns.includes(key);
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
- if (!dataContext || typeof path !== "string") {
68
- return { routeKey: null, propertyPath: path };
69
- }
70
- const trimmedPath = path.trim();
71
- const dataHash = getDataContextHash(dataContext);
72
- const cacheKey = `${trimmedPath}::${dataHash}`;
73
- const cachedResult = routeParsingCache.get(cacheKey);
74
- if (cachedResult) {
75
- cachedResult.timestamp = Date.now();
76
- return {
77
- routeKey: cachedResult.routeKey,
78
- propertyPath: cachedResult.propertyPath
79
- };
80
- }
81
- const availableRoutes = Object.keys(dataContext).sort(
82
- (a, b) => b.length - a.length
83
- );
84
- let result = {
85
- routeKey: null,
86
- propertyPath: trimmedPath
87
- };
88
- let found = false;
89
- for (const routeKey of availableRoutes) {
90
- if (trimmedPath === routeKey) {
91
- result = { routeKey, propertyPath: "" };
92
- found = true;
93
- break;
94
- }
95
- if (trimmedPath.startsWith(`${routeKey}.`)) {
96
- const propertyPath = trimmedPath.slice(routeKey.length + 1);
97
- result = { routeKey, propertyPath };
98
- found = true;
99
- break;
100
- }
101
- }
102
- if (!found) {
103
- result = { routeKey: null, propertyPath: trimmedPath };
104
- }
105
- if (routeParsingCache.size >= ROUTE_PARSING_CACHE_MAX_SIZE) {
106
- const entries = Array.from(routeParsingCache.entries());
107
- entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
108
- const toRemove = Math.floor(entries.length * CACHE_EVICTION_RATIO);
109
- for (let i = 0; i < toRemove; i++) {
110
- routeParsingCache.delete(entries[i][0]);
111
- }
112
- }
113
- routeParsingCache.set(cacheKey, {
114
- routeKey: result.routeKey,
115
- propertyPath: result.propertyPath,
116
- timestamp: Date.now()
117
- });
118
- return result;
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
- var templateCache = /* @__PURE__ */ new Map();
121
- var MAX_CACHE_SIZE = 100;
122
- var fallbackCache = /* @__PURE__ */ new Map();
123
- var FALLBACK_CACHE_MAX_SIZE = 50;
124
- var routeParsingCache = /* @__PURE__ */ new Map();
125
- var ROUTE_PARSING_CACHE_MAX_SIZE = 100;
126
- var dataContextHashCache = /* @__PURE__ */ new WeakMap();
127
- var hashCounter = 0;
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
- return path.split(".").reduce((current, key) => {
130
- if (!current || typeof current !== "object") {
131
- return;
132
- }
133
- if (visited.has(current)) {
134
- console.warn("Circular reference detected in data connector path:", path);
135
- return;
136
- }
137
- visited.add(current);
138
- const arrayMatch = key.match(ARRAY_INDEX_REGEX);
139
- if (arrayMatch) {
140
- const [, arrayKey, index] = arrayMatch;
141
- const arrayObj = current[arrayKey];
142
- if (Array.isArray(arrayObj)) {
143
- const indexNum = Number.parseInt(index, 10);
144
- if (indexNum >= 0 && indexNum < arrayObj.length) {
145
- return arrayObj[indexNum];
146
- }
147
- }
148
- return;
149
- }
150
- if (!isSafePropertyName(key)) {
151
- console.warn(`Unsafe property access attempted: ${key}`);
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
- if (value === null || value === void 0) {
159
- return "";
160
- }
161
- const stringValue = String(value);
162
- return stringValue.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;").replace(/\//g, "&#x2F;");
168
+ if (value === null || value === void 0) return "";
169
+ return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;").replace(/\//g, "&#x2F;");
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
- if (!dataContextHashCache.has(data)) {
166
- dataContextHashCache.set(data, `ctx_${++hashCounter}`);
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
- const dataHash = getDataContextHash(data);
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
- if (!dataContext) {
177
- return;
178
- }
179
- const trimmedPath = path.trim();
180
- const { routeKey, propertyPath } = parseRouteAndProperty(
181
- trimmedPath,
182
- dataContext
183
- );
184
- if (routeKey && dataContext[routeKey]) {
185
- const routeData = dataContext[routeKey];
186
- if (typeof routeData === "object" && routeData !== null) {
187
- if (propertyPath) {
188
- return getNestedValue(routeData, propertyPath);
189
- }
190
- return routeData;
191
- }
192
- }
193
- const cachedResult = fallbackCache.get(trimmedPath);
194
- if (cachedResult && dataContext[cachedResult.routeKey]) {
195
- const value = getNestedValue(
196
- dataContext[cachedResult.routeKey],
197
- trimmedPath
198
- );
199
- if (value !== void 0) {
200
- cachedResult.timestamp = Date.now();
201
- return value;
202
- }
203
- }
204
- const priorityRoutes = ["root", "routes/product", "routes/collection"];
205
- const allRoutes = [
206
- ...priorityRoutes,
207
- ...Object.keys(dataContext).filter((k) => !priorityRoutes.includes(k))
208
- ];
209
- for (const routeKey2 of allRoutes) {
210
- const routeData = dataContext[routeKey2];
211
- if (routeData && typeof routeData === "object" && routeData !== null) {
212
- const value = getNestedValue(routeData, trimmedPath);
213
- if (value !== void 0) {
214
- if (fallbackCache.size >= FALLBACK_CACHE_MAX_SIZE) {
215
- const entries = Array.from(fallbackCache.entries());
216
- entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
217
- const toRemove = Math.floor(entries.length * CACHE_EVICTION_RATIO);
218
- for (let i = 0; i < toRemove; i++) {
219
- fallbackCache.delete(entries[i][0]);
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
- if (!dataContext || typeof content !== "string") {
235
- return content;
236
- }
237
- try {
238
- const cacheKey = createCacheKey(content, dataContext);
239
- const cachedContent = templateCache.get(cacheKey);
240
- if (cachedContent) {
241
- return cachedContent;
242
- }
243
- const newContent = content.replace(TEMPLATE_REGEX, (match, path) => {
244
- try {
245
- const value = resolveDataFromContext(path, dataContext);
246
- if (value === void 0 || value === null) {
247
- return match;
248
- }
249
- return sanitizeValue(value);
250
- } catch (pathError) {
251
- console.warn(
252
- `Error resolving data connector path "${path}":`,
253
- pathError
254
- );
255
- return match;
256
- }
257
- });
258
- if (templateCache.size >= MAX_CACHE_SIZE) {
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
- if (typeof data === "string") {
271
- TEMPLATE_REGEX.lastIndex = 0;
272
- return TEMPLATE_REGEX.test(data);
273
- }
274
- if (typeof data !== "object" || data === null) {
275
- return false;
276
- }
277
- if (data instanceof Date) {
278
- return false;
279
- }
280
- if (visited.has(data)) {
281
- return false;
282
- }
283
- visited.add(data);
284
- try {
285
- if (Array.isArray(data)) {
286
- return data.some((item) => hasPlaceholders(item, visited));
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
- if (!dataContext) {
304
- return data;
305
- }
306
- const dataHasPlaceholders = hasPlaceholders(data, /* @__PURE__ */ new WeakSet());
307
- if (!(dataHasPlaceholders || forceClone)) {
308
- return data;
309
- }
310
- if (typeof data === "string") {
311
- return replaceContentDataConnectors(data, dataContext);
312
- }
313
- if (typeof data !== "object" || data === null) {
314
- return data;
315
- }
316
- if (visited.has(data)) {
317
- console.warn(
318
- "Circular reference detected in deep data connector replacement"
319
- );
320
- return data;
321
- }
322
- visited.add(data);
323
- try {
324
- if (data instanceof Date) {
325
- if (forceClone || dataHasPlaceholders) {
326
- return new Date(data.getTime());
327
- }
328
- return data;
329
- }
330
- if (Array.isArray(data)) {
331
- const result2 = data.map(
332
- (item) => replaceContentDataConnectorsDeep(
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
- // src/hooks.ts
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
- let weaverse = (0, import_react2.useContext)(WeaverseContext);
367
- return weaverse;
360
+ return (0, react.useContext)(WeaverseContext);
368
361
  }
369
- var useItemInstance = (id) => {
370
- let { id: currentId } = (0, import_react2.useContext)(WeaverseItemContext);
371
- let instance = import_core.Weaverse.itemInstances.get(id || currentId);
372
- if (!instance) {
373
- console.log(`Item instance ${id} not found`);
374
- return null;
375
- }
376
- return instance;
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
- var useParentInstance = () => {
379
- let { parentId } = (0, import_react2.useContext)(WeaverseItemContext);
380
- return useItemInstance(parentId || "");
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
- var useChildInstances = (id) => {
383
- let currentInstance = useItemInstance(id);
384
- if (!currentInstance) {
385
- return [];
386
- }
387
- let { itemInstances } = import_core.Weaverse;
388
- let {
389
- data: { children }
390
- } = currentInstance;
391
- return children.map(({ id: id2 }) => itemInstances.get(id2)).filter(Boolean);
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
- // src/renderer.tsx
395
- var import_clsx = __toESM(require("clsx"));
396
- var import_react3 = __toESM(require("react"));
397
- var import_jsx_runtime = require("react/jsx-runtime");
398
- var import_react4 = require("react");
399
- var REACT_VERSION_THRESHOLD = 18;
400
- var reactVersion = Number(import_react3.default.version?.split(".")[0]) || 0;
401
- var useSafeExternalStore = (subscribe, getSnapshot, getServerSnapshot = getSnapshot) => {
402
- if (typeof window === "undefined") {
403
- return getServerSnapshot();
404
- }
405
- if (import_react3.default.useSyncExternalStore) {
406
- return import_react3.default.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
407
- }
408
- return getSnapshot();
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
- var getRootId = (context) => {
411
- if (context.data.rootId) {
412
- return context.data.rootId;
413
- }
414
- let mainItem = context.data?.items?.find((item) => item.type === "main");
415
- if (mainItem?.id) {
416
- return mainItem.id;
417
- }
418
- return context.projectId;
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
- var WeaverseRoot = (0, import_react3.memo)(({ context }) => {
421
- const data = useSafeExternalStore(
422
- context.subscribe,
423
- context.getSnapShot,
424
- context.getSnapShot
425
- );
426
- const rootRef = (0, import_react3.useRef)(null);
427
- (0, import_react3.useEffect)(() => {
428
- context.contentRootElement = rootRef.current;
429
- }, [context]);
430
- const eventHandlers = context?.studioBridge?.eventHandlers || {};
431
- if (context.projectId) {
432
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
433
- "div",
434
- {
435
- className: "weaverse-content-root",
436
- ...eventHandlers,
437
- "data-weaverse-project-id": context.projectId,
438
- "data-weaverse-template-id": data.id,
439
- ref: rootRef,
440
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WeaverseContextProvider, { value: context, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ItemInstance, { id: getRootId(context), parentId: "" }) })
441
- }
442
- );
443
- }
444
- return null;
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
- var ItemComponent = (0, import_react3.memo)(({ instance }) => {
448
- const context = useWeaverse();
449
- const { elementRegistry } = context;
450
- const data = useSafeExternalStore(
451
- instance.subscribe,
452
- instance.getSnapShot,
453
- instance.getSnapShot
454
- );
455
- const {
456
- id,
457
- type,
458
- childIds = [],
459
- children = [],
460
- createdAt,
461
- updatedAt,
462
- deletedAt,
463
- css,
464
- __hidden,
465
- // Need to get rid of this `data` property
466
- data: _,
467
- ...rest
468
- } = data;
469
- const processedRest = (0, import_react3.useMemo)(
470
- () => replaceContentDataConnectorsDeep(rest, context.dataContext),
471
- [data, context.dataContext]
472
- );
473
- (0, import_react3.useEffect)(() => {
474
- if (!instance.ref.current) {
475
- Object.assign(instance.ref, {
476
- current: document.querySelector(`[data-wv-id="${id}"]`)
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
- var ItemInstance = (0, import_react3.memo)(
513
- ({ id, parentId }) => {
514
- const instance = useItemInstance(id);
515
- if (!instance) {
516
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "none" }, children: [
517
- "Item instance ",
518
- id,
519
- " not found"
520
- ] });
521
- }
522
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WeaverseItemContext.Provider, { value: { parentId, id }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ItemComponent, { instance }, id) });
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
- // Annotate the CommonJS export names for ESM import in node:
527
- 0 && (module.exports = {
528
- WeaverseContext,
529
- WeaverseContextProvider,
530
- WeaverseItemContext,
531
- WeaverseRoot,
532
- replaceContentDataConnectors,
533
- replaceContentDataConnectorsDeep,
534
- useChildInstances,
535
- useItemInstance,
536
- useParentInstance,
537
- useSafeExternalStore,
538
- useWeaverse,
539
- ...require("@weaverse/core")
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