@weaverse/react 5.24.1 → 5.25.0

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
@@ -509,6 +509,82 @@ const ItemInstance = (0, react.memo)(({ id, parentId }) => {
509
509
  });
510
510
  ItemInstance.displayName = "WeaverseInstanceProvider";
511
511
  //#endregion
512
+ //#region src/studio-selection.ts
513
+ const NO_SELECTION = Object.freeze({
514
+ selectedItemId: null,
515
+ selectedThemeGroup: null
516
+ });
517
+ /**
518
+ * Holds the Studio selection for one storefront root. The Studio bridge writes
519
+ * it; themes read it through {@link useStudioSelection}.
520
+ */
521
+ var StudioSelectionStore = class {
522
+ selection = NO_SELECTION;
523
+ listeners = /* @__PURE__ */ new Set();
524
+ /** Returns the current selection; the same object until it changes. */
525
+ getSnapshot = () => this.selection;
526
+ /** Returns the empty selection rendered on the server and at hydration. */
527
+ getServerSnapshot = () => NO_SELECTION;
528
+ /**
529
+ * Registers a change listener.
530
+ *
531
+ * @param listener - Called after each selection change.
532
+ * @returns A function that removes the listener.
533
+ */
534
+ subscribe = (listener) => {
535
+ this.listeners.add(listener);
536
+ return () => {
537
+ this.listeners.delete(listener);
538
+ };
539
+ };
540
+ /**
541
+ * Replaces the selection and notifies listeners when a value changed. An item
542
+ * and a theme group are mutually exclusive, so a selection naming both is
543
+ * rejected and leaves the store unchanged.
544
+ *
545
+ * @param next - The new selection.
546
+ */
547
+ setSelection = (next) => {
548
+ let { selectedItemId, selectedThemeGroup } = next;
549
+ if (selectedItemId !== null && selectedThemeGroup !== null) {
550
+ console.error("[Weaverse] A Studio selection cannot name both an item and a theme group");
551
+ return;
552
+ }
553
+ let current = this.selection;
554
+ if (current.selectedItemId === selectedItemId && current.selectedThemeGroup === selectedThemeGroup) return;
555
+ this.selection = selectedItemId === null && selectedThemeGroup === null ? NO_SELECTION : Object.freeze({
556
+ selectedItemId,
557
+ selectedThemeGroup
558
+ });
559
+ for (let listener of this.listeners) listener();
560
+ };
561
+ };
562
+ /** Provides the root's {@link StudioSelectionStore}; set by each adapter. */
563
+ let StudioSelectionStoreContext = (0, react.createContext)(null);
564
+ const DETACHED_STORE = new StudioSelectionStore();
565
+ /**
566
+ * Returns what the merchant has selected in Weaverse Studio: a page item ID or
567
+ * a theme settings group name, never both. Both are `null` on the storefront,
568
+ * during server rendering and outside a Weaverse root.
569
+ *
570
+ * @returns The current Studio selection.
571
+ *
572
+ * @example
573
+ * ```tsx
574
+ * function NewsletterPopup() {
575
+ * const { selectedThemeGroup } = useStudioSelection()
576
+ * const open = selectedThemeGroup === 'Newsletter Popup'
577
+ * // ...
578
+ * }
579
+ * ```
580
+ */
581
+ function useStudioSelection() {
582
+ let store = (0, react.useContext)(StudioSelectionStoreContext) ?? DETACHED_STORE;
583
+ return useSafeExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot);
584
+ }
585
+ //#endregion
586
+ exports.StudioSelectionStore = StudioSelectionStore;
587
+ exports.StudioSelectionStoreContext = StudioSelectionStoreContext;
512
588
  exports.WeaverseContext = WeaverseContext;
513
589
  exports.WeaverseContextProvider = WeaverseContextProvider;
514
590
  exports.WeaverseItemContext = WeaverseItemContext;
@@ -519,6 +595,7 @@ exports.useChildInstances = useChildInstances;
519
595
  exports.useItemInstance = useItemInstance;
520
596
  exports.useParentInstance = useParentInstance;
521
597
  exports.useSafeExternalStore = useSafeExternalStore;
598
+ exports.useStudioSelection = useStudioSelection;
522
599
  exports.useWeaverse = useWeaverse;
523
600
  Object.keys(_weaverse_core).forEach(function(k) {
524
601
  if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["Weaverse","React"],"sources":["../src/context.ts","../src/utils/data-connector.ts","../src/hooks.ts","../src/renderer.tsx"],"sourcesContent":["import type { Weaverse } from '@weaverse/core'\nimport { createContext } from 'react'\n\n/** Provides the active Weaverse runtime to React components. */\nexport let WeaverseContext = createContext<Weaverse>({} as Weaverse)\n\n/** Provider component for supplying the active Weaverse runtime. */\nexport let WeaverseContextProvider = WeaverseContext.Provider\n\n/** Identifies the current rendered item and its parent item. */\nexport let WeaverseItemContext = createContext<{\n /** ID of the current Weaverse item. */\n id: string\n /** ID of the current item's parent, or an empty string at the root. */\n parentId: string\n}>({ id: '', parentId: '' })\n","// Data connector utilities for replacing placeholders with real data\n\n/** Route-keyed or flat data available when resolving content placeholders. */\nexport type DataContext = Record<string, unknown>\n\n// Cached regex pattern for better performance\nconst TEMPLATE_REGEX = /\\{\\{([^}]+)\\}\\}/g\nconst ARRAY_INDEX_REGEX = /^(\\w+)\\[(\\d+)\\]$/\n\n// Cache eviction ratio - remove oldest 25% when cache is full\nconst CACHE_EVICTION_RATIO = 0.25\n\n/**\n * Validates that a property name is safe to access (prevents prototype pollution)\n * Enhanced to handle route-specific patterns with special characters\n */\nfunction isSafePropertyName(key: string): boolean {\n // Block dangerous prototype pollution patterns\n const dangerousPatterns = ['__proto__', 'constructor', 'prototype']\n return !dangerousPatterns.includes(key)\n}\n\n/**\n * Parses a template path into route key and property path components\n * Handles complex routes like: routes/($locale).blogs.$blogHandle.$articleHandle\n * Enhanced with caching for improved performance\n *\n * @param path - The full path from template (e.g., \"routes/($locale)._index.weaverseData.page.name\")\n * @param dataContext - Available route data context\n * @returns Object with routeKey and propertyPath\n */\nfunction parseRouteAndProperty(\n path: string,\n dataContext: DataContext\n): {\n routeKey: string | null\n propertyPath: string\n} {\n if (!dataContext || typeof path !== 'string') {\n return { routeKey: null, propertyPath: path }\n }\n\n const trimmedPath = path.trim()\n\n // Create a cache key using stable hash instead of serializing route keys\n const dataHash = getDataContextHash(dataContext)\n const cacheKey = `${trimmedPath}::${dataHash}`\n\n // Check cache first\n const cachedResult = routeParsingCache.get(cacheKey)\n if (cachedResult) {\n // Update timestamp on cache hit\n cachedResult.timestamp = Date.now()\n return {\n routeKey: cachedResult.routeKey,\n propertyPath: cachedResult.propertyPath,\n }\n }\n\n // Get all available routes and sort by length (longest first for specificity)\n const availableRoutes = Object.keys(dataContext).sort(\n (a, b) => b.length - a.length\n )\n\n let result: { routeKey: string | null; propertyPath: string } = {\n routeKey: null,\n propertyPath: trimmedPath,\n }\n\n // Try to find the longest matching route key\n let found = false\n for (const routeKey of availableRoutes) {\n // Exact match\n if (trimmedPath === routeKey) {\n result = { routeKey, propertyPath: '' }\n found = true\n break\n }\n\n // Route key with property path\n if (trimmedPath.startsWith(`${routeKey}.`)) {\n const propertyPath = trimmedPath.slice(routeKey.length + 1)\n result = { routeKey, propertyPath }\n found = true\n break\n }\n }\n\n if (!found) {\n // No route key found, treat entire path as property path\n result = { routeKey: null, propertyPath: trimmedPath }\n }\n\n // Cache the result\n if (routeParsingCache.size >= ROUTE_PARSING_CACHE_MAX_SIZE) {\n // Clean cache by removing oldest entries\n const entries = Array.from(routeParsingCache.entries())\n entries.sort((a, b) => a[1].timestamp - b[1].timestamp)\n const toRemove = Math.floor(entries.length * CACHE_EVICTION_RATIO)\n for (let i = 0; i < toRemove; i++) {\n routeParsingCache.delete(entries[i][0])\n }\n }\n\n routeParsingCache.set(cacheKey, {\n routeKey: result.routeKey,\n propertyPath: result.propertyPath,\n timestamp: Date.now(),\n })\n\n return result\n}\n\n// Simple cache for parsed templates (max 100 entries)\nconst templateCache = new Map<string, string>()\nconst MAX_CACHE_SIZE = 100\n\n// Cache for fallback lookups to improve performance (moved to module level to prevent memory leaks)\nconst fallbackCache = new Map<\n string,\n { routeKey: string; value: unknown; timestamp: number }\n>()\nconst FALLBACK_CACHE_MAX_SIZE = 50\n\n// Route parsing cache for performance optimization\nconst routeParsingCache = new Map<\n string,\n { routeKey: string | null; propertyPath: string; timestamp: number }\n>()\nconst ROUTE_PARSING_CACHE_MAX_SIZE = 100\n\n// WeakMap for stable dataContext hashing - prevents repeated JSON.stringify\nconst dataContextHashCache = new WeakMap<object, string>()\nlet hashCounter = 0\n\n/**\n * Safely gets a nested value from an object using dot notation path\n * Supports array indexing with [index] syntax\n * @param obj - The object to traverse\n * @param path - Dot notation path (e.g., \"user.name\" or \"items[0].title\")\n * @param visited - Set to track visited objects for circular reference protection\n * @returns The resolved value or undefined if not found\n */\nfunction getNestedValue(\n obj: unknown,\n path: string,\n visited = new WeakSet()\n): unknown {\n return path.split('.').reduce((current, key) => {\n if (!current || typeof current !== 'object') {\n return\n }\n\n // Protect against circular references\n if (visited.has(current as WeakKey)) {\n console.warn('Circular reference detected in data connector path:', path)\n return\n }\n\n // Add to circular reference tracking\n visited.add(current as WeakKey)\n\n // Handle array indexing like items[0] or items[1]\n const arrayMatch = key.match(ARRAY_INDEX_REGEX)\n if (arrayMatch) {\n const [, arrayKey, index] = arrayMatch\n const arrayObj = (current as Record<string, unknown>)[arrayKey]\n if (Array.isArray(arrayObj)) {\n const indexNum = Number.parseInt(index, 10)\n if (indexNum >= 0 && indexNum < arrayObj.length) {\n return arrayObj[indexNum]\n }\n }\n return\n }\n\n // Handle regular object property access with security validation\n if (!isSafePropertyName(key)) {\n console.warn(`Unsafe property access attempted: ${key}`)\n return\n }\n\n return current !== null && key in current\n ? (current as Record<string, unknown>)[key]\n : undefined\n }, obj)\n}\n\n/**\n * Sanitizes a string value to prevent XSS attacks\n * @param value - The value to sanitize\n * @returns Sanitized string safe for HTML rendering\n */\nfunction sanitizeValue(value: unknown): string {\n if (value === null || value === undefined) {\n return ''\n }\n\n const stringValue = String(value)\n\n // Basic HTML entity encoding to prevent XSS\n return stringValue\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#x27;')\n .replace(/\\//g, '&#x2F;')\n}\n\n/**\n * Creates a stable hash for dataContext object\n * Uses WeakMap to cache hash per object reference (no serialization needed)\n */\nfunction getDataContextHash(data: Record<string, unknown>): string {\n if (!dataContextHashCache.has(data)) {\n // Generate unique hash for this dataContext instance\n dataContextHashCache.set(data, `ctx_${++hashCounter}`)\n }\n const hash = dataContextHashCache.get(data)\n return hash || `ctx_${++hashCounter}`\n}\n\n/**\n * Creates a safe cache key from content and data\n * Uses stable hashing instead of JSON.stringify for massive performance gain\n */\nfunction createCacheKey(\n content: string,\n data: Record<string, unknown>\n): string {\n // Use stable hash instead of expensive JSON.stringify\n const dataHash = getDataContextHash(data)\n return `${content}:${dataHash}`\n}\n\n/**\n * Resolves data from the flat route-keyed data context\n * Enhanced to support complex route patterns with special characters\n * Supports: {{root.layout.shop.name}}, {{routes/($locale)._index.data}}, {{routes/($locale).blogs.$handle.$article.content}}\n */\nfunction resolveDataFromContext(\n path: string,\n dataContext: DataContext | null | undefined\n): unknown {\n if (!dataContext) {\n return\n }\n\n const trimmedPath = path.trim()\n\n // Use intelligent route parsing to separate route key from property path\n const { routeKey, propertyPath } = parseRouteAndProperty(\n trimmedPath,\n dataContext\n )\n\n // If we found a specific route key, resolve from that route's data\n if (routeKey && dataContext[routeKey]) {\n const routeData = dataContext[routeKey]\n if (typeof routeData === 'object' && routeData !== null) {\n if (propertyPath) {\n return getNestedValue(routeData, propertyPath)\n }\n return routeData\n }\n }\n\n // Fallback: search across all route data for backward compatibility\n // This handles cases like {{shop.name}} without route prefix\n\n // Check cache first\n const cachedResult = fallbackCache.get(trimmedPath)\n if (cachedResult && dataContext[cachedResult.routeKey]) {\n const value = getNestedValue(\n dataContext[cachedResult.routeKey],\n trimmedPath\n )\n if (value !== undefined) {\n // Update timestamp on cache hit\n cachedResult.timestamp = Date.now()\n return value\n }\n }\n\n // Search with priority order (most common routes first)\n const priorityRoutes = ['root', 'routes/product', 'routes/collection']\n const allRoutes = [\n ...priorityRoutes,\n ...Object.keys(dataContext).filter((k) => !priorityRoutes.includes(k)),\n ]\n\n for (const routeKey of allRoutes) {\n const routeData = dataContext[routeKey]\n if (routeData && typeof routeData === 'object' && routeData !== null) {\n const value = getNestedValue(routeData, trimmedPath)\n if (value !== undefined) {\n // Clean cache if it's getting too large\n if (fallbackCache.size >= FALLBACK_CACHE_MAX_SIZE) {\n const entries = Array.from(fallbackCache.entries())\n entries.sort((a, b) => a[1].timestamp - b[1].timestamp)\n // Remove oldest 25% of entries\n const toRemove = Math.floor(entries.length * CACHE_EVICTION_RATIO)\n for (let i = 0; i < toRemove; i++) {\n fallbackCache.delete(entries[i][0])\n }\n }\n fallbackCache.set(trimmedPath, {\n routeKey,\n value,\n timestamp: Date.now(),\n })\n return value\n }\n }\n }\n\n // Final fallback: try direct access (for non-route-keyed legacy data)\n return getNestedValue(dataContext, trimmedPath)\n}\n\n/**\n * Replaces data connector placeholders in content with actual values from data context\n * Supports {{path}} syntax with dot notation, array indexing, and route prefixes\n *\n * Enhanced syntax:\n * - {{root.siteSettings.title}} - Access root route data\n * - {{layout.navigation.items}} - Access layout route data\n * - {{parent.category.name}} - Access parent route data\n * - {{current.product.title}} - Access current route data\n * - {{user.name}} - Auto-resolved from combined data (backward compatible)\n *\n * @param content - The content string containing {{}} placeholders\n * @param dataContext - The enhanced WeaverseDataContext with route-keyed data\n * @returns The content with placeholders replaced by actual values\n */\nexport function replaceContentDataConnectors(\n content: string,\n dataContext: DataContext | null | undefined\n): string {\n if (!dataContext || typeof content !== 'string') {\n return content\n }\n\n try {\n // Simplified caching - dataContext is already the combined data\n const cacheKey = createCacheKey(content, dataContext)\n\n const cachedContent = templateCache.get(cacheKey)\n if (cachedContent) {\n return cachedContent\n }\n\n const newContent = content.replace(TEMPLATE_REGEX, (match, path) => {\n try {\n const value = resolveDataFromContext(path, dataContext)\n\n if (value === undefined || value === null) {\n return match // Return original placeholder if value not found\n }\n\n return sanitizeValue(value)\n } catch (pathError) {\n console.warn(\n `Error resolving data connector path \"${path}\":`,\n pathError\n )\n return match\n }\n })\n\n // Cache the result (with size limit)\n if (templateCache.size >= MAX_CACHE_SIZE) {\n const firstKey = templateCache.keys().next().value\n templateCache.delete(firstKey)\n }\n templateCache.set(cacheKey, newContent)\n\n return newContent\n } catch (error) {\n console.warn('Error processing data connectors:', error, { content })\n return content\n }\n}\n\n/**\n * Fast check if data contains any {{...}} placeholders\n * Returns true if any string value contains the pattern\n * This is MUCH faster than processing and allows early exit\n *\n * IMPORTANT: Uses global TEMPLATE_REGEX, must reset lastIndex\n */\nfunction hasPlaceholders(data: unknown, visited = new WeakSet()): boolean {\n if (typeof data === 'string') {\n // Reset regex state before testing to ensure consistent results\n TEMPLATE_REGEX.lastIndex = 0\n return TEMPLATE_REGEX.test(data)\n }\n\n if (typeof data !== 'object' || data === null) {\n return false\n }\n\n // Skip Date objects - they can't contain placeholders\n if (data instanceof Date) {\n return false\n }\n\n // Protect against circular references\n if (visited.has(data as WeakKey)) {\n return false\n }\n visited.add(data as WeakKey)\n\n try {\n if (Array.isArray(data)) {\n return data.some((item) => hasPlaceholders(item, visited))\n }\n\n // Check object values safely (skip property access errors)\n for (const key of Object.keys(data)) {\n try {\n const value = (data as Record<string, unknown>)[key]\n if (hasPlaceholders(value, visited)) {\n return true\n }\n } catch {\n // Ignore property access errors - continue checking other properties\n }\n }\n\n return false\n } finally {\n visited.delete(data as WeakKey)\n }\n}\n\n/**\n * Recursively replaces data connector placeholders in any data structure\n * IMMUTABLE: Creates deep copies during processing to avoid mutating original data\n * Handles strings, objects, arrays, and nested combinations\n *\n * PERFORMANCE: Now includes early exit if no placeholders detected\n *\n * @param data - The data to process (string, object, array, or primitive)\n * @param dataContext - The data context for replacements\n * @param visited - Set to track visited objects for circular reference protection\n * @param forceClone - Internal flag to force cloning even if no placeholders (for parent immutability)\n * @returns IMMUTABLE copy of the data with all string placeholders replaced\n */\nexport function replaceContentDataConnectorsDeep<T>(\n data: T,\n dataContext: DataContext | null | undefined,\n visited = new WeakSet(),\n forceClone = false\n): T {\n if (!dataContext) {\n return data\n }\n\n const dataHasPlaceholders = hasPlaceholders(data, new WeakSet())\n\n // PERFORMANCE: Fast check - if no placeholders exist and not forced to clone, skip ALL processing\n // This saves 90%+ of CPU time for components without data connectors\n if (!(dataHasPlaceholders || forceClone)) {\n return data\n }\n\n // Handle primitive types - no cloning needed for primitives\n if (typeof data === 'string') {\n return replaceContentDataConnectors(data, dataContext) as T\n }\n\n if (typeof data !== 'object' || data === null) {\n return data\n }\n\n // Protect against circular references\n if (visited.has(data as WeakKey)) {\n console.warn(\n 'Circular reference detected in deep data connector replacement'\n )\n return data\n }\n\n // Add to circular reference tracking\n visited.add(data as WeakKey)\n\n try {\n // Handle special objects that should be cloned as-is\n if (data instanceof Date) {\n // Date objects can't contain placeholders, but clone if forced (parent has placeholders)\n // or if somehow detected (shouldn't happen but be safe)\n if (forceClone || dataHasPlaceholders) {\n return new Date(data.getTime()) as T\n }\n return data\n }\n\n // Handle arrays - create new array with processed items\n // Force clone children if parent has placeholders (for immutability)\n if (Array.isArray(data)) {\n const result = data.map((item) =>\n replaceContentDataConnectorsDeep(\n item,\n dataContext,\n visited,\n dataHasPlaceholders\n )\n ) as T\n return result\n }\n\n // Handle plain objects - create new object with processed values\n // Force clone children if parent has placeholders (for immutability)\n const result = {} as T\n\n // Use Object.entries with try-catch per property to handle getter errors\n for (const key of Object.keys(data)) {\n try {\n const value = (data as Record<string, unknown>)[key]\n ;(result as Record<string, unknown>)[key] =\n replaceContentDataConnectorsDeep(\n value,\n dataContext,\n visited,\n dataHasPlaceholders\n )\n } catch (propertyError) {\n // If property access throws, skip it but continue processing\n console.warn(`Error accessing property \"${key}\":`, propertyError)\n // Don't include this property in result\n }\n }\n\n return result\n } catch (error) {\n console.warn('Error in deep data connector replacement:', error)\n return data\n } finally {\n // Remove from tracking when done processing this object\n visited.delete(data as WeakKey)\n }\n}\n","import { Weaverse, type WeaverseItemStore } from '@weaverse/core'\nimport { useContext } from 'react'\nimport { WeaverseContext, WeaverseItemContext } from './context'\n\nexport type { DataContext } from './utils/data-connector'\n// Re-export the data connector utilities\nexport {\n replaceContentDataConnectors,\n replaceContentDataConnectorsDeep,\n} from './utils/data-connector'\n\n/**\n * Returns the active Weaverse runtime from React context.\n *\n * @typeParam T - Runtime subtype expected by the integration.\n */\nexport function useWeaverse<T = Weaverse>() {\n let weaverse = useContext(WeaverseContext)\n return weaverse as T\n}\n\n/**\n * Returns a Weaverse item instance by ID, or the current rendered item when no\n * ID is provided.\n *\n * @param id - Optional item ID to look up.\n * @returns The matching item instance, or `null` when it is unavailable.\n */\nexport let useItemInstance = (id?: string) => {\n let { id: currentId } = useContext(WeaverseItemContext)\n let instance = Weaverse.itemInstances.get(id || currentId)\n if (!instance) {\n console.log(`Item instance ${id} not found`)\n return null\n }\n return instance as WeaverseItemStore\n}\n\n/**\n * Returns the parent instance of the current rendered item.\n *\n * @returns The parent item instance. At the root, the empty parent ID falls\n * back to the current item instance; returns `null` only when that lookup is\n * unavailable.\n */\nexport let useParentInstance = () => {\n let { parentId } = useContext(WeaverseItemContext)\n return useItemInstance(parentId || '')\n}\n\n/**\n * Returns the direct child instances of an item.\n *\n * @param id - Optional parent item ID; defaults to the current rendered item.\n */\nexport let useChildInstances = (id?: string) => {\n let currentInstance = useItemInstance(id)\n if (!currentInstance) {\n return []\n }\n let { itemInstances } = Weaverse\n\n let {\n data: { children },\n } = currentInstance\n return children\n .map(({ id }: { id: string }) => itemInstances.get(id))\n .filter(Boolean) as WeaverseItemStore[]\n}\n","import type { Weaverse } from '@weaverse/core'\nimport clsx from 'clsx'\nimport React, { memo, useEffect, useMemo, useRef } from 'react'\nimport { WeaverseContextProvider, WeaverseItemContext } from './context'\nimport { useItemInstance, useWeaverse } from './hooks'\nimport type { ItemComponentProps, WeaverseRootPropsType } from './types'\nimport { replaceContentDataConnectorsDeep } from './utils/data-connector'\n\nconst REACT_VERSION_THRESHOLD = 18\nconst reactVersion = Number(React.version?.split('.')[0]) || 0\n\n/**\n * Reads an external store in React while remaining safe during server rendering\n * and on React versions without `useSyncExternalStore`.\n *\n * @param subscribe - Registers a callback for store changes.\n * @param getSnapshot - Returns the current client snapshot.\n * @param getServerSnapshot - Returns the server snapshot; defaults to `getSnapshot`.\n * @returns The current external-store snapshot.\n */\nexport const useSafeExternalStore = (\n subscribe: any,\n getSnapshot: any,\n getServerSnapshot: any = getSnapshot\n) => {\n // In server environment or during SSR, just return the snapshot directly\n if (typeof window === 'undefined') {\n return getServerSnapshot()\n }\n\n // In client environment, use React.useSyncExternalStore if available (React 18+)\n if (React.useSyncExternalStore) {\n // biome-ignore lint/correctness/useHookAtTopLevel: <$>\n return React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n }\n\n // Fallback for older React versions - just return the current snapshot\n return getSnapshot()\n}\n\n/**\n * Determines the root element ID for the Weaverse component tree.\n * Falls back through multiple strategies to ensure a valid root is always found:\n * 1. Use explicit rootId from context.data if available\n * 2. Find the first element with type 'main' in items array\n * 3. Use projectId as final fallback\n */\nconst getRootId = (context: Weaverse): string => {\n // Strategy 1: Explicit rootId\n if (context.data.rootId) {\n return context.data.rootId\n }\n\n // Strategy 2: Find main element\n let mainItem = context.data?.items?.find((item) => item.type === 'main')\n if (mainItem?.id) {\n return mainItem.id\n }\n\n // Strategy 3: Fallback to projectId\n return context.projectId\n}\n/**\n * Renders a Weaverse content tree and provides its runtime to descendant\n * components.\n */\nexport const WeaverseRoot = memo(({ context }: WeaverseRootPropsType) => {\n const data = useSafeExternalStore(\n context.subscribe,\n context.getSnapShot,\n context.getSnapShot\n )\n const rootRef = useRef<HTMLElement>(null)\n\n useEffect(() => {\n context.contentRootElement = rootRef.current\n }, [context])\n\n const eventHandlers = context?.studioBridge?.eventHandlers || {}\n\n if (context.projectId) {\n return (\n <div\n className=\"weaverse-content-root\"\n {...eventHandlers}\n data-weaverse-project-id={context.projectId}\n data-weaverse-template-id={data.id}\n ref={rootRef}\n >\n <WeaverseContextProvider value={context}>\n <ItemInstance id={getRootId(context)} parentId={''} />\n </WeaverseContextProvider>\n </div>\n )\n }\n return null\n})\nWeaverseRoot.displayName = 'WeaverseRoot'\nconst ItemComponent = memo(({ instance }: ItemComponentProps) => {\n const context = useWeaverse()\n const { elementRegistry } = context\n const data = useSafeExternalStore(\n instance.subscribe,\n instance.getSnapShot,\n instance.getSnapShot\n )\n const {\n id,\n type,\n childIds = [],\n children = [],\n\n createdAt,\n updatedAt,\n deletedAt,\n css,\n __hidden,\n // Need to get rid of this `data` property\n data: _,\n ...rest\n } = data\n\n // Replace data connectors in all component data (handles strings, objects, and arrays recursively)\n // IMMUTABLE: Process the entire rest object to avoid mutating original content\n // Memoized per snapshot: `data` identity changes exactly when the item's\n // store or translation entry changes (see WeaverseHydrogenItem.getSnapShot),\n // and context-only updates swap the store reference via `setData({})` in\n // syncReusedInstance — so re-renders with an unchanged snapshot skip the\n // deep placeholder scan + clone entirely.\n // biome-ignore lint/correctness/useExhaustiveDependencies: `rest` is a pure projection of `data`\n const processedRest = useMemo(\n () => replaceContentDataConnectorsDeep(rest, context.dataContext),\n [data, context.dataContext]\n )\n\n useEffect(() => {\n if (!instance.ref.current) {\n // Fallback `ref` if the element isn't created by `React.forwardRef`\n Object.assign(instance.ref, {\n current: document.querySelector(`[data-wv-id=\"${id}\"]`),\n })\n }\n }, [instance, id])\n\n if (__hidden) {\n return null\n }\n\n const Element = elementRegistry.get(type)\n\n if (Element?.Component) {\n const Component = Element.Component\n if (Component.displayName !== type) {\n // Devtools nicety — assign once, not on every render.\n Component.displayName = type\n }\n if (\n Component.$$typeof === Symbol.for('react.forward_ref') ||\n reactVersion > REACT_VERSION_THRESHOLD\n ) {\n processedRest.ref = instance.ref\n }\n const childItems = children?.length\n ? children\n : childIds?.length\n ? childIds.map((cid: string) => ({ id: cid }))\n : []\n\n const renderChildren = childItems.map(\n (item: { id: string }, index: number) => (\n <ItemInstance id={item.id} key={`${item.id}-${index}`} parentId={id} />\n )\n )\n\n return (\n <Component\n {...processedRest}\n children={renderChildren.length ? renderChildren : undefined}\n className={clsx(processedRest.data?.className)}\n data-wv-id={id}\n data-wv-type={type}\n key={id}\n />\n )\n }\n instance._store.deleted = true\n return null\n})\nItemComponent.displayName = 'WeaverseComponent'\n\nconst ItemInstance = memo(\n ({ id, parentId }: { id: string; parentId: string }) => {\n const instance = useItemInstance(id)\n if (!instance) {\n return <div style={{ display: 'none' }}>Item instance {id} not found</div>\n }\n return (\n <WeaverseItemContext.Provider value={{ parentId, id }}>\n <ItemComponent instance={instance} key={id} />\n </WeaverseItemContext.Provider>\n )\n }\n)\nItemInstance.displayName = 'WeaverseInstanceProvider'\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["Weaverse","React"],"sources":["../src/context.ts","../src/utils/data-connector.ts","../src/hooks.ts","../src/renderer.tsx","../src/studio-selection.ts"],"sourcesContent":["import type { Weaverse } from '@weaverse/core'\nimport { createContext } from 'react'\n\n/** Provides the active Weaverse runtime to React components. */\nexport let WeaverseContext = createContext<Weaverse>({} as Weaverse)\n\n/** Provider component for supplying the active Weaverse runtime. */\nexport let WeaverseContextProvider = WeaverseContext.Provider\n\n/** Identifies the current rendered item and its parent item. */\nexport let WeaverseItemContext = createContext<{\n /** ID of the current Weaverse item. */\n id: string\n /** ID of the current item's parent, or an empty string at the root. */\n parentId: string\n}>({ id: '', parentId: '' })\n","// Data connector utilities for replacing placeholders with real data\n\n/** Route-keyed or flat data available when resolving content placeholders. */\nexport type DataContext = Record<string, unknown>\n\n// Cached regex pattern for better performance\nconst TEMPLATE_REGEX = /\\{\\{([^}]+)\\}\\}/g\nconst ARRAY_INDEX_REGEX = /^(\\w+)\\[(\\d+)\\]$/\n\n// Cache eviction ratio - remove oldest 25% when cache is full\nconst CACHE_EVICTION_RATIO = 0.25\n\n/**\n * Validates that a property name is safe to access (prevents prototype pollution)\n * Enhanced to handle route-specific patterns with special characters\n */\nfunction isSafePropertyName(key: string): boolean {\n // Block dangerous prototype pollution patterns\n const dangerousPatterns = ['__proto__', 'constructor', 'prototype']\n return !dangerousPatterns.includes(key)\n}\n\n/**\n * Parses a template path into route key and property path components\n * Handles complex routes like: routes/($locale).blogs.$blogHandle.$articleHandle\n * Enhanced with caching for improved performance\n *\n * @param path - The full path from template (e.g., \"routes/($locale)._index.weaverseData.page.name\")\n * @param dataContext - Available route data context\n * @returns Object with routeKey and propertyPath\n */\nfunction parseRouteAndProperty(\n path: string,\n dataContext: DataContext\n): {\n routeKey: string | null\n propertyPath: string\n} {\n if (!dataContext || typeof path !== 'string') {\n return { routeKey: null, propertyPath: path }\n }\n\n const trimmedPath = path.trim()\n\n // Create a cache key using stable hash instead of serializing route keys\n const dataHash = getDataContextHash(dataContext)\n const cacheKey = `${trimmedPath}::${dataHash}`\n\n // Check cache first\n const cachedResult = routeParsingCache.get(cacheKey)\n if (cachedResult) {\n // Update timestamp on cache hit\n cachedResult.timestamp = Date.now()\n return {\n routeKey: cachedResult.routeKey,\n propertyPath: cachedResult.propertyPath,\n }\n }\n\n // Get all available routes and sort by length (longest first for specificity)\n const availableRoutes = Object.keys(dataContext).sort(\n (a, b) => b.length - a.length\n )\n\n let result: { routeKey: string | null; propertyPath: string } = {\n routeKey: null,\n propertyPath: trimmedPath,\n }\n\n // Try to find the longest matching route key\n let found = false\n for (const routeKey of availableRoutes) {\n // Exact match\n if (trimmedPath === routeKey) {\n result = { routeKey, propertyPath: '' }\n found = true\n break\n }\n\n // Route key with property path\n if (trimmedPath.startsWith(`${routeKey}.`)) {\n const propertyPath = trimmedPath.slice(routeKey.length + 1)\n result = { routeKey, propertyPath }\n found = true\n break\n }\n }\n\n if (!found) {\n // No route key found, treat entire path as property path\n result = { routeKey: null, propertyPath: trimmedPath }\n }\n\n // Cache the result\n if (routeParsingCache.size >= ROUTE_PARSING_CACHE_MAX_SIZE) {\n // Clean cache by removing oldest entries\n const entries = Array.from(routeParsingCache.entries())\n entries.sort((a, b) => a[1].timestamp - b[1].timestamp)\n const toRemove = Math.floor(entries.length * CACHE_EVICTION_RATIO)\n for (let i = 0; i < toRemove; i++) {\n routeParsingCache.delete(entries[i][0])\n }\n }\n\n routeParsingCache.set(cacheKey, {\n routeKey: result.routeKey,\n propertyPath: result.propertyPath,\n timestamp: Date.now(),\n })\n\n return result\n}\n\n// Simple cache for parsed templates (max 100 entries)\nconst templateCache = new Map<string, string>()\nconst MAX_CACHE_SIZE = 100\n\n// Cache for fallback lookups to improve performance (moved to module level to prevent memory leaks)\nconst fallbackCache = new Map<\n string,\n { routeKey: string; value: unknown; timestamp: number }\n>()\nconst FALLBACK_CACHE_MAX_SIZE = 50\n\n// Route parsing cache for performance optimization\nconst routeParsingCache = new Map<\n string,\n { routeKey: string | null; propertyPath: string; timestamp: number }\n>()\nconst ROUTE_PARSING_CACHE_MAX_SIZE = 100\n\n// WeakMap for stable dataContext hashing - prevents repeated JSON.stringify\nconst dataContextHashCache = new WeakMap<object, string>()\nlet hashCounter = 0\n\n/**\n * Safely gets a nested value from an object using dot notation path\n * Supports array indexing with [index] syntax\n * @param obj - The object to traverse\n * @param path - Dot notation path (e.g., \"user.name\" or \"items[0].title\")\n * @param visited - Set to track visited objects for circular reference protection\n * @returns The resolved value or undefined if not found\n */\nfunction getNestedValue(\n obj: unknown,\n path: string,\n visited = new WeakSet()\n): unknown {\n return path.split('.').reduce((current, key) => {\n if (!current || typeof current !== 'object') {\n return\n }\n\n // Protect against circular references\n if (visited.has(current as WeakKey)) {\n console.warn('Circular reference detected in data connector path:', path)\n return\n }\n\n // Add to circular reference tracking\n visited.add(current as WeakKey)\n\n // Handle array indexing like items[0] or items[1]\n const arrayMatch = key.match(ARRAY_INDEX_REGEX)\n if (arrayMatch) {\n const [, arrayKey, index] = arrayMatch\n const arrayObj = (current as Record<string, unknown>)[arrayKey]\n if (Array.isArray(arrayObj)) {\n const indexNum = Number.parseInt(index, 10)\n if (indexNum >= 0 && indexNum < arrayObj.length) {\n return arrayObj[indexNum]\n }\n }\n return\n }\n\n // Handle regular object property access with security validation\n if (!isSafePropertyName(key)) {\n console.warn(`Unsafe property access attempted: ${key}`)\n return\n }\n\n return current !== null && key in current\n ? (current as Record<string, unknown>)[key]\n : undefined\n }, obj)\n}\n\n/**\n * Sanitizes a string value to prevent XSS attacks\n * @param value - The value to sanitize\n * @returns Sanitized string safe for HTML rendering\n */\nfunction sanitizeValue(value: unknown): string {\n if (value === null || value === undefined) {\n return ''\n }\n\n const stringValue = String(value)\n\n // Basic HTML entity encoding to prevent XSS\n return stringValue\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#x27;')\n .replace(/\\//g, '&#x2F;')\n}\n\n/**\n * Creates a stable hash for dataContext object\n * Uses WeakMap to cache hash per object reference (no serialization needed)\n */\nfunction getDataContextHash(data: Record<string, unknown>): string {\n if (!dataContextHashCache.has(data)) {\n // Generate unique hash for this dataContext instance\n dataContextHashCache.set(data, `ctx_${++hashCounter}`)\n }\n const hash = dataContextHashCache.get(data)\n return hash || `ctx_${++hashCounter}`\n}\n\n/**\n * Creates a safe cache key from content and data\n * Uses stable hashing instead of JSON.stringify for massive performance gain\n */\nfunction createCacheKey(\n content: string,\n data: Record<string, unknown>\n): string {\n // Use stable hash instead of expensive JSON.stringify\n const dataHash = getDataContextHash(data)\n return `${content}:${dataHash}`\n}\n\n/**\n * Resolves data from the flat route-keyed data context\n * Enhanced to support complex route patterns with special characters\n * Supports: {{root.layout.shop.name}}, {{routes/($locale)._index.data}}, {{routes/($locale).blogs.$handle.$article.content}}\n */\nfunction resolveDataFromContext(\n path: string,\n dataContext: DataContext | null | undefined\n): unknown {\n if (!dataContext) {\n return\n }\n\n const trimmedPath = path.trim()\n\n // Use intelligent route parsing to separate route key from property path\n const { routeKey, propertyPath } = parseRouteAndProperty(\n trimmedPath,\n dataContext\n )\n\n // If we found a specific route key, resolve from that route's data\n if (routeKey && dataContext[routeKey]) {\n const routeData = dataContext[routeKey]\n if (typeof routeData === 'object' && routeData !== null) {\n if (propertyPath) {\n return getNestedValue(routeData, propertyPath)\n }\n return routeData\n }\n }\n\n // Fallback: search across all route data for backward compatibility\n // This handles cases like {{shop.name}} without route prefix\n\n // Check cache first\n const cachedResult = fallbackCache.get(trimmedPath)\n if (cachedResult && dataContext[cachedResult.routeKey]) {\n const value = getNestedValue(\n dataContext[cachedResult.routeKey],\n trimmedPath\n )\n if (value !== undefined) {\n // Update timestamp on cache hit\n cachedResult.timestamp = Date.now()\n return value\n }\n }\n\n // Search with priority order (most common routes first)\n const priorityRoutes = ['root', 'routes/product', 'routes/collection']\n const allRoutes = [\n ...priorityRoutes,\n ...Object.keys(dataContext).filter((k) => !priorityRoutes.includes(k)),\n ]\n\n for (const routeKey of allRoutes) {\n const routeData = dataContext[routeKey]\n if (routeData && typeof routeData === 'object' && routeData !== null) {\n const value = getNestedValue(routeData, trimmedPath)\n if (value !== undefined) {\n // Clean cache if it's getting too large\n if (fallbackCache.size >= FALLBACK_CACHE_MAX_SIZE) {\n const entries = Array.from(fallbackCache.entries())\n entries.sort((a, b) => a[1].timestamp - b[1].timestamp)\n // Remove oldest 25% of entries\n const toRemove = Math.floor(entries.length * CACHE_EVICTION_RATIO)\n for (let i = 0; i < toRemove; i++) {\n fallbackCache.delete(entries[i][0])\n }\n }\n fallbackCache.set(trimmedPath, {\n routeKey,\n value,\n timestamp: Date.now(),\n })\n return value\n }\n }\n }\n\n // Final fallback: try direct access (for non-route-keyed legacy data)\n return getNestedValue(dataContext, trimmedPath)\n}\n\n/**\n * Replaces data connector placeholders in content with actual values from data context\n * Supports {{path}} syntax with dot notation, array indexing, and route prefixes\n *\n * Enhanced syntax:\n * - {{root.siteSettings.title}} - Access root route data\n * - {{layout.navigation.items}} - Access layout route data\n * - {{parent.category.name}} - Access parent route data\n * - {{current.product.title}} - Access current route data\n * - {{user.name}} - Auto-resolved from combined data (backward compatible)\n *\n * @param content - The content string containing {{}} placeholders\n * @param dataContext - The enhanced WeaverseDataContext with route-keyed data\n * @returns The content with placeholders replaced by actual values\n */\nexport function replaceContentDataConnectors(\n content: string,\n dataContext: DataContext | null | undefined\n): string {\n if (!dataContext || typeof content !== 'string') {\n return content\n }\n\n try {\n // Simplified caching - dataContext is already the combined data\n const cacheKey = createCacheKey(content, dataContext)\n\n const cachedContent = templateCache.get(cacheKey)\n if (cachedContent) {\n return cachedContent\n }\n\n const newContent = content.replace(TEMPLATE_REGEX, (match, path) => {\n try {\n const value = resolveDataFromContext(path, dataContext)\n\n if (value === undefined || value === null) {\n return match // Return original placeholder if value not found\n }\n\n return sanitizeValue(value)\n } catch (pathError) {\n console.warn(\n `Error resolving data connector path \"${path}\":`,\n pathError\n )\n return match\n }\n })\n\n // Cache the result (with size limit)\n if (templateCache.size >= MAX_CACHE_SIZE) {\n const firstKey = templateCache.keys().next().value\n templateCache.delete(firstKey)\n }\n templateCache.set(cacheKey, newContent)\n\n return newContent\n } catch (error) {\n console.warn('Error processing data connectors:', error, { content })\n return content\n }\n}\n\n/**\n * Fast check if data contains any {{...}} placeholders\n * Returns true if any string value contains the pattern\n * This is MUCH faster than processing and allows early exit\n *\n * IMPORTANT: Uses global TEMPLATE_REGEX, must reset lastIndex\n */\nfunction hasPlaceholders(data: unknown, visited = new WeakSet()): boolean {\n if (typeof data === 'string') {\n // Reset regex state before testing to ensure consistent results\n TEMPLATE_REGEX.lastIndex = 0\n return TEMPLATE_REGEX.test(data)\n }\n\n if (typeof data !== 'object' || data === null) {\n return false\n }\n\n // Skip Date objects - they can't contain placeholders\n if (data instanceof Date) {\n return false\n }\n\n // Protect against circular references\n if (visited.has(data as WeakKey)) {\n return false\n }\n visited.add(data as WeakKey)\n\n try {\n if (Array.isArray(data)) {\n return data.some((item) => hasPlaceholders(item, visited))\n }\n\n // Check object values safely (skip property access errors)\n for (const key of Object.keys(data)) {\n try {\n const value = (data as Record<string, unknown>)[key]\n if (hasPlaceholders(value, visited)) {\n return true\n }\n } catch {\n // Ignore property access errors - continue checking other properties\n }\n }\n\n return false\n } finally {\n visited.delete(data as WeakKey)\n }\n}\n\n/**\n * Recursively replaces data connector placeholders in any data structure\n * IMMUTABLE: Creates deep copies during processing to avoid mutating original data\n * Handles strings, objects, arrays, and nested combinations\n *\n * PERFORMANCE: Now includes early exit if no placeholders detected\n *\n * @param data - The data to process (string, object, array, or primitive)\n * @param dataContext - The data context for replacements\n * @param visited - Set to track visited objects for circular reference protection\n * @param forceClone - Internal flag to force cloning even if no placeholders (for parent immutability)\n * @returns IMMUTABLE copy of the data with all string placeholders replaced\n */\nexport function replaceContentDataConnectorsDeep<T>(\n data: T,\n dataContext: DataContext | null | undefined,\n visited = new WeakSet(),\n forceClone = false\n): T {\n if (!dataContext) {\n return data\n }\n\n const dataHasPlaceholders = hasPlaceholders(data, new WeakSet())\n\n // PERFORMANCE: Fast check - if no placeholders exist and not forced to clone, skip ALL processing\n // This saves 90%+ of CPU time for components without data connectors\n if (!(dataHasPlaceholders || forceClone)) {\n return data\n }\n\n // Handle primitive types - no cloning needed for primitives\n if (typeof data === 'string') {\n return replaceContentDataConnectors(data, dataContext) as T\n }\n\n if (typeof data !== 'object' || data === null) {\n return data\n }\n\n // Protect against circular references\n if (visited.has(data as WeakKey)) {\n console.warn(\n 'Circular reference detected in deep data connector replacement'\n )\n return data\n }\n\n // Add to circular reference tracking\n visited.add(data as WeakKey)\n\n try {\n // Handle special objects that should be cloned as-is\n if (data instanceof Date) {\n // Date objects can't contain placeholders, but clone if forced (parent has placeholders)\n // or if somehow detected (shouldn't happen but be safe)\n if (forceClone || dataHasPlaceholders) {\n return new Date(data.getTime()) as T\n }\n return data\n }\n\n // Handle arrays - create new array with processed items\n // Force clone children if parent has placeholders (for immutability)\n if (Array.isArray(data)) {\n const result = data.map((item) =>\n replaceContentDataConnectorsDeep(\n item,\n dataContext,\n visited,\n dataHasPlaceholders\n )\n ) as T\n return result\n }\n\n // Handle plain objects - create new object with processed values\n // Force clone children if parent has placeholders (for immutability)\n const result = {} as T\n\n // Use Object.entries with try-catch per property to handle getter errors\n for (const key of Object.keys(data)) {\n try {\n const value = (data as Record<string, unknown>)[key]\n ;(result as Record<string, unknown>)[key] =\n replaceContentDataConnectorsDeep(\n value,\n dataContext,\n visited,\n dataHasPlaceholders\n )\n } catch (propertyError) {\n // If property access throws, skip it but continue processing\n console.warn(`Error accessing property \"${key}\":`, propertyError)\n // Don't include this property in result\n }\n }\n\n return result\n } catch (error) {\n console.warn('Error in deep data connector replacement:', error)\n return data\n } finally {\n // Remove from tracking when done processing this object\n visited.delete(data as WeakKey)\n }\n}\n","import { Weaverse, type WeaverseItemStore } from '@weaverse/core'\nimport { useContext } from 'react'\nimport { WeaverseContext, WeaverseItemContext } from './context'\n\nexport type { DataContext } from './utils/data-connector'\n// Re-export the data connector utilities\nexport {\n replaceContentDataConnectors,\n replaceContentDataConnectorsDeep,\n} from './utils/data-connector'\n\n/**\n * Returns the active Weaverse runtime from React context.\n *\n * @typeParam T - Runtime subtype expected by the integration.\n */\nexport function useWeaverse<T = Weaverse>() {\n let weaverse = useContext(WeaverseContext)\n return weaverse as T\n}\n\n/**\n * Returns a Weaverse item instance by ID, or the current rendered item when no\n * ID is provided.\n *\n * @param id - Optional item ID to look up.\n * @returns The matching item instance, or `null` when it is unavailable.\n */\nexport let useItemInstance = (id?: string) => {\n let { id: currentId } = useContext(WeaverseItemContext)\n let instance = Weaverse.itemInstances.get(id || currentId)\n if (!instance) {\n console.log(`Item instance ${id} not found`)\n return null\n }\n return instance as WeaverseItemStore\n}\n\n/**\n * Returns the parent instance of the current rendered item.\n *\n * @returns The parent item instance. At the root, the empty parent ID falls\n * back to the current item instance; returns `null` only when that lookup is\n * unavailable.\n */\nexport let useParentInstance = () => {\n let { parentId } = useContext(WeaverseItemContext)\n return useItemInstance(parentId || '')\n}\n\n/**\n * Returns the direct child instances of an item.\n *\n * @param id - Optional parent item ID; defaults to the current rendered item.\n */\nexport let useChildInstances = (id?: string) => {\n let currentInstance = useItemInstance(id)\n if (!currentInstance) {\n return []\n }\n let { itemInstances } = Weaverse\n\n let {\n data: { children },\n } = currentInstance\n return children\n .map(({ id }: { id: string }) => itemInstances.get(id))\n .filter(Boolean) as WeaverseItemStore[]\n}\n","import type { Weaverse } from '@weaverse/core'\nimport clsx from 'clsx'\nimport React, { memo, useEffect, useMemo, useRef } from 'react'\nimport { WeaverseContextProvider, WeaverseItemContext } from './context'\nimport { useItemInstance, useWeaverse } from './hooks'\nimport type { ItemComponentProps, WeaverseRootPropsType } from './types'\nimport { replaceContentDataConnectorsDeep } from './utils/data-connector'\n\nconst REACT_VERSION_THRESHOLD = 18\nconst reactVersion = Number(React.version?.split('.')[0]) || 0\n\n/**\n * Reads an external store in React while remaining safe during server rendering\n * and on React versions without `useSyncExternalStore`.\n *\n * @param subscribe - Registers a callback for store changes.\n * @param getSnapshot - Returns the current client snapshot.\n * @param getServerSnapshot - Returns the server snapshot; defaults to `getSnapshot`.\n * @returns The current external-store snapshot.\n */\nexport const useSafeExternalStore = (\n subscribe: any,\n getSnapshot: any,\n getServerSnapshot: any = getSnapshot\n) => {\n // In server environment or during SSR, just return the snapshot directly\n if (typeof window === 'undefined') {\n return getServerSnapshot()\n }\n\n // In client environment, use React.useSyncExternalStore if available (React 18+)\n if (React.useSyncExternalStore) {\n // biome-ignore lint/correctness/useHookAtTopLevel: <$>\n return React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n }\n\n // Fallback for older React versions - just return the current snapshot\n return getSnapshot()\n}\n\n/**\n * Determines the root element ID for the Weaverse component tree.\n * Falls back through multiple strategies to ensure a valid root is always found:\n * 1. Use explicit rootId from context.data if available\n * 2. Find the first element with type 'main' in items array\n * 3. Use projectId as final fallback\n */\nconst getRootId = (context: Weaverse): string => {\n // Strategy 1: Explicit rootId\n if (context.data.rootId) {\n return context.data.rootId\n }\n\n // Strategy 2: Find main element\n let mainItem = context.data?.items?.find((item) => item.type === 'main')\n if (mainItem?.id) {\n return mainItem.id\n }\n\n // Strategy 3: Fallback to projectId\n return context.projectId\n}\n/**\n * Renders a Weaverse content tree and provides its runtime to descendant\n * components.\n */\nexport const WeaverseRoot = memo(({ context }: WeaverseRootPropsType) => {\n const data = useSafeExternalStore(\n context.subscribe,\n context.getSnapShot,\n context.getSnapShot\n )\n const rootRef = useRef<HTMLElement>(null)\n\n useEffect(() => {\n context.contentRootElement = rootRef.current\n }, [context])\n\n const eventHandlers = context?.studioBridge?.eventHandlers || {}\n\n if (context.projectId) {\n return (\n <div\n className=\"weaverse-content-root\"\n {...eventHandlers}\n data-weaverse-project-id={context.projectId}\n data-weaverse-template-id={data.id}\n ref={rootRef}\n >\n <WeaverseContextProvider value={context}>\n <ItemInstance id={getRootId(context)} parentId={''} />\n </WeaverseContextProvider>\n </div>\n )\n }\n return null\n})\nWeaverseRoot.displayName = 'WeaverseRoot'\nconst ItemComponent = memo(({ instance }: ItemComponentProps) => {\n const context = useWeaverse()\n const { elementRegistry } = context\n const data = useSafeExternalStore(\n instance.subscribe,\n instance.getSnapShot,\n instance.getSnapShot\n )\n const {\n id,\n type,\n childIds = [],\n children = [],\n\n createdAt,\n updatedAt,\n deletedAt,\n css,\n __hidden,\n // Need to get rid of this `data` property\n data: _,\n ...rest\n } = data\n\n // Replace data connectors in all component data (handles strings, objects, and arrays recursively)\n // IMMUTABLE: Process the entire rest object to avoid mutating original content\n // Memoized per snapshot: `data` identity changes exactly when the item's\n // store or translation entry changes (see WeaverseHydrogenItem.getSnapShot),\n // and context-only updates swap the store reference via `setData({})` in\n // syncReusedInstance — so re-renders with an unchanged snapshot skip the\n // deep placeholder scan + clone entirely.\n // biome-ignore lint/correctness/useExhaustiveDependencies: `rest` is a pure projection of `data`\n const processedRest = useMemo(\n () => replaceContentDataConnectorsDeep(rest, context.dataContext),\n [data, context.dataContext]\n )\n\n useEffect(() => {\n if (!instance.ref.current) {\n // Fallback `ref` if the element isn't created by `React.forwardRef`\n Object.assign(instance.ref, {\n current: document.querySelector(`[data-wv-id=\"${id}\"]`),\n })\n }\n }, [instance, id])\n\n if (__hidden) {\n return null\n }\n\n const Element = elementRegistry.get(type)\n\n if (Element?.Component) {\n const Component = Element.Component\n if (Component.displayName !== type) {\n // Devtools nicety — assign once, not on every render.\n Component.displayName = type\n }\n if (\n Component.$$typeof === Symbol.for('react.forward_ref') ||\n reactVersion > REACT_VERSION_THRESHOLD\n ) {\n processedRest.ref = instance.ref\n }\n const childItems = children?.length\n ? children\n : childIds?.length\n ? childIds.map((cid: string) => ({ id: cid }))\n : []\n\n const renderChildren = childItems.map(\n (item: { id: string }, index: number) => (\n <ItemInstance id={item.id} key={`${item.id}-${index}`} parentId={id} />\n )\n )\n\n return (\n <Component\n {...processedRest}\n children={renderChildren.length ? renderChildren : undefined}\n className={clsx(processedRest.data?.className)}\n data-wv-id={id}\n data-wv-type={type}\n key={id}\n />\n )\n }\n instance._store.deleted = true\n return null\n})\nItemComponent.displayName = 'WeaverseComponent'\n\nconst ItemInstance = memo(\n ({ id, parentId }: { id: string; parentId: string }) => {\n const instance = useItemInstance(id)\n if (!instance) {\n return <div style={{ display: 'none' }}>Item instance {id} not found</div>\n }\n return (\n <WeaverseItemContext.Provider value={{ parentId, id }}>\n <ItemComponent instance={instance} key={id} />\n </WeaverseItemContext.Provider>\n )\n }\n)\nItemInstance.displayName = 'WeaverseInstanceProvider'\n","import { createContext, useContext } from 'react'\nimport { useSafeExternalStore } from './renderer'\n\n/** What the merchant has selected in Weaverse Studio. */\nexport type StudioSelection = {\n /** ID of the selected page item, or `null`. */\n selectedItemId: string | null\n /**\n * Name of the selected theme settings group, exactly as declared in the\n * schema `group` field, or `null`.\n */\n selectedThemeGroup: string | null\n}\n\nconst NO_SELECTION: StudioSelection = Object.freeze({\n selectedItemId: null,\n selectedThemeGroup: null,\n})\n\n/**\n * Holds the Studio selection for one storefront root. The Studio bridge writes\n * it; themes read it through {@link useStudioSelection}.\n */\nexport class StudioSelectionStore {\n private selection: StudioSelection = NO_SELECTION\n private readonly listeners = new Set<() => void>()\n\n /** Returns the current selection; the same object until it changes. */\n getSnapshot = (): StudioSelection => this.selection\n\n /** Returns the empty selection rendered on the server and at hydration. */\n getServerSnapshot = (): StudioSelection => NO_SELECTION\n\n /**\n * Registers a change listener.\n *\n * @param listener - Called after each selection change.\n * @returns A function that removes the listener.\n */\n subscribe = (listener: () => void) => {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n /**\n * Replaces the selection and notifies listeners when a value changed. An item\n * and a theme group are mutually exclusive, so a selection naming both is\n * rejected and leaves the store unchanged.\n *\n * @param next - The new selection.\n */\n setSelection = (next: StudioSelection) => {\n let { selectedItemId, selectedThemeGroup } = next\n if (selectedItemId !== null && selectedThemeGroup !== null) {\n console.error(\n '[Weaverse] A Studio selection cannot name both an item and a theme group'\n )\n return\n }\n let current = this.selection\n if (\n current.selectedItemId === selectedItemId &&\n current.selectedThemeGroup === selectedThemeGroup\n ) {\n return\n }\n this.selection =\n selectedItemId === null && selectedThemeGroup === null\n ? NO_SELECTION\n : Object.freeze({ selectedItemId, selectedThemeGroup })\n for (let listener of this.listeners) {\n listener()\n }\n }\n}\n\n/** Provides the root's {@link StudioSelectionStore}; set by each adapter. */\nexport let StudioSelectionStoreContext =\n createContext<StudioSelectionStore | null>(null)\n\nconst DETACHED_STORE = new StudioSelectionStore()\n\n/**\n * Returns what the merchant has selected in Weaverse Studio: a page item ID or\n * a theme settings group name, never both. Both are `null` on the storefront,\n * during server rendering and outside a Weaverse root.\n *\n * @returns The current Studio selection.\n *\n * @example\n * ```tsx\n * function NewsletterPopup() {\n * const { selectedThemeGroup } = useStudioSelection()\n * const open = selectedThemeGroup === 'Newsletter Popup'\n * // ...\n * }\n * ```\n */\nexport function useStudioSelection(): StudioSelection {\n let store = useContext(StudioSelectionStoreContext) ?? DETACHED_STORE\n return useSafeExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getServerSnapshot\n )\n}\n"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -485,6 +485,80 @@ const ItemInstance = memo(({ id, parentId }) => {
485
485
  });
486
486
  ItemInstance.displayName = "WeaverseInstanceProvider";
487
487
  //#endregion
488
- export { WeaverseContext, WeaverseContextProvider, WeaverseItemContext, WeaverseRoot, replaceContentDataConnectors, replaceContentDataConnectorsDeep, useChildInstances, useItemInstance, useParentInstance, useSafeExternalStore, useWeaverse };
488
+ //#region src/studio-selection.ts
489
+ const NO_SELECTION = Object.freeze({
490
+ selectedItemId: null,
491
+ selectedThemeGroup: null
492
+ });
493
+ /**
494
+ * Holds the Studio selection for one storefront root. The Studio bridge writes
495
+ * it; themes read it through {@link useStudioSelection}.
496
+ */
497
+ var StudioSelectionStore = class {
498
+ selection = NO_SELECTION;
499
+ listeners = /* @__PURE__ */ new Set();
500
+ /** Returns the current selection; the same object until it changes. */
501
+ getSnapshot = () => this.selection;
502
+ /** Returns the empty selection rendered on the server and at hydration. */
503
+ getServerSnapshot = () => NO_SELECTION;
504
+ /**
505
+ * Registers a change listener.
506
+ *
507
+ * @param listener - Called after each selection change.
508
+ * @returns A function that removes the listener.
509
+ */
510
+ subscribe = (listener) => {
511
+ this.listeners.add(listener);
512
+ return () => {
513
+ this.listeners.delete(listener);
514
+ };
515
+ };
516
+ /**
517
+ * Replaces the selection and notifies listeners when a value changed. An item
518
+ * and a theme group are mutually exclusive, so a selection naming both is
519
+ * rejected and leaves the store unchanged.
520
+ *
521
+ * @param next - The new selection.
522
+ */
523
+ setSelection = (next) => {
524
+ let { selectedItemId, selectedThemeGroup } = next;
525
+ if (selectedItemId !== null && selectedThemeGroup !== null) {
526
+ console.error("[Weaverse] A Studio selection cannot name both an item and a theme group");
527
+ return;
528
+ }
529
+ let current = this.selection;
530
+ if (current.selectedItemId === selectedItemId && current.selectedThemeGroup === selectedThemeGroup) return;
531
+ this.selection = selectedItemId === null && selectedThemeGroup === null ? NO_SELECTION : Object.freeze({
532
+ selectedItemId,
533
+ selectedThemeGroup
534
+ });
535
+ for (let listener of this.listeners) listener();
536
+ };
537
+ };
538
+ /** Provides the root's {@link StudioSelectionStore}; set by each adapter. */
539
+ let StudioSelectionStoreContext = createContext(null);
540
+ const DETACHED_STORE = new StudioSelectionStore();
541
+ /**
542
+ * Returns what the merchant has selected in Weaverse Studio: a page item ID or
543
+ * a theme settings group name, never both. Both are `null` on the storefront,
544
+ * during server rendering and outside a Weaverse root.
545
+ *
546
+ * @returns The current Studio selection.
547
+ *
548
+ * @example
549
+ * ```tsx
550
+ * function NewsletterPopup() {
551
+ * const { selectedThemeGroup } = useStudioSelection()
552
+ * const open = selectedThemeGroup === 'Newsletter Popup'
553
+ * // ...
554
+ * }
555
+ * ```
556
+ */
557
+ function useStudioSelection() {
558
+ let store = useContext(StudioSelectionStoreContext) ?? DETACHED_STORE;
559
+ return useSafeExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot);
560
+ }
561
+ //#endregion
562
+ export { StudioSelectionStore, StudioSelectionStoreContext, WeaverseContext, WeaverseContextProvider, WeaverseItemContext, WeaverseRoot, replaceContentDataConnectors, replaceContentDataConnectorsDeep, useChildInstances, useItemInstance, useParentInstance, useSafeExternalStore, useStudioSelection, useWeaverse };
489
563
 
490
564
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/context.ts","../src/utils/data-connector.ts","../src/hooks.ts","../src/renderer.tsx"],"sourcesContent":["import type { Weaverse } from '@weaverse/core'\nimport { createContext } from 'react'\n\n/** Provides the active Weaverse runtime to React components. */\nexport let WeaverseContext = createContext<Weaverse>({} as Weaverse)\n\n/** Provider component for supplying the active Weaverse runtime. */\nexport let WeaverseContextProvider = WeaverseContext.Provider\n\n/** Identifies the current rendered item and its parent item. */\nexport let WeaverseItemContext = createContext<{\n /** ID of the current Weaverse item. */\n id: string\n /** ID of the current item's parent, or an empty string at the root. */\n parentId: string\n}>({ id: '', parentId: '' })\n","// Data connector utilities for replacing placeholders with real data\n\n/** Route-keyed or flat data available when resolving content placeholders. */\nexport type DataContext = Record<string, unknown>\n\n// Cached regex pattern for better performance\nconst TEMPLATE_REGEX = /\\{\\{([^}]+)\\}\\}/g\nconst ARRAY_INDEX_REGEX = /^(\\w+)\\[(\\d+)\\]$/\n\n// Cache eviction ratio - remove oldest 25% when cache is full\nconst CACHE_EVICTION_RATIO = 0.25\n\n/**\n * Validates that a property name is safe to access (prevents prototype pollution)\n * Enhanced to handle route-specific patterns with special characters\n */\nfunction isSafePropertyName(key: string): boolean {\n // Block dangerous prototype pollution patterns\n const dangerousPatterns = ['__proto__', 'constructor', 'prototype']\n return !dangerousPatterns.includes(key)\n}\n\n/**\n * Parses a template path into route key and property path components\n * Handles complex routes like: routes/($locale).blogs.$blogHandle.$articleHandle\n * Enhanced with caching for improved performance\n *\n * @param path - The full path from template (e.g., \"routes/($locale)._index.weaverseData.page.name\")\n * @param dataContext - Available route data context\n * @returns Object with routeKey and propertyPath\n */\nfunction parseRouteAndProperty(\n path: string,\n dataContext: DataContext\n): {\n routeKey: string | null\n propertyPath: string\n} {\n if (!dataContext || typeof path !== 'string') {\n return { routeKey: null, propertyPath: path }\n }\n\n const trimmedPath = path.trim()\n\n // Create a cache key using stable hash instead of serializing route keys\n const dataHash = getDataContextHash(dataContext)\n const cacheKey = `${trimmedPath}::${dataHash}`\n\n // Check cache first\n const cachedResult = routeParsingCache.get(cacheKey)\n if (cachedResult) {\n // Update timestamp on cache hit\n cachedResult.timestamp = Date.now()\n return {\n routeKey: cachedResult.routeKey,\n propertyPath: cachedResult.propertyPath,\n }\n }\n\n // Get all available routes and sort by length (longest first for specificity)\n const availableRoutes = Object.keys(dataContext).sort(\n (a, b) => b.length - a.length\n )\n\n let result: { routeKey: string | null; propertyPath: string } = {\n routeKey: null,\n propertyPath: trimmedPath,\n }\n\n // Try to find the longest matching route key\n let found = false\n for (const routeKey of availableRoutes) {\n // Exact match\n if (trimmedPath === routeKey) {\n result = { routeKey, propertyPath: '' }\n found = true\n break\n }\n\n // Route key with property path\n if (trimmedPath.startsWith(`${routeKey}.`)) {\n const propertyPath = trimmedPath.slice(routeKey.length + 1)\n result = { routeKey, propertyPath }\n found = true\n break\n }\n }\n\n if (!found) {\n // No route key found, treat entire path as property path\n result = { routeKey: null, propertyPath: trimmedPath }\n }\n\n // Cache the result\n if (routeParsingCache.size >= ROUTE_PARSING_CACHE_MAX_SIZE) {\n // Clean cache by removing oldest entries\n const entries = Array.from(routeParsingCache.entries())\n entries.sort((a, b) => a[1].timestamp - b[1].timestamp)\n const toRemove = Math.floor(entries.length * CACHE_EVICTION_RATIO)\n for (let i = 0; i < toRemove; i++) {\n routeParsingCache.delete(entries[i][0])\n }\n }\n\n routeParsingCache.set(cacheKey, {\n routeKey: result.routeKey,\n propertyPath: result.propertyPath,\n timestamp: Date.now(),\n })\n\n return result\n}\n\n// Simple cache for parsed templates (max 100 entries)\nconst templateCache = new Map<string, string>()\nconst MAX_CACHE_SIZE = 100\n\n// Cache for fallback lookups to improve performance (moved to module level to prevent memory leaks)\nconst fallbackCache = new Map<\n string,\n { routeKey: string; value: unknown; timestamp: number }\n>()\nconst FALLBACK_CACHE_MAX_SIZE = 50\n\n// Route parsing cache for performance optimization\nconst routeParsingCache = new Map<\n string,\n { routeKey: string | null; propertyPath: string; timestamp: number }\n>()\nconst ROUTE_PARSING_CACHE_MAX_SIZE = 100\n\n// WeakMap for stable dataContext hashing - prevents repeated JSON.stringify\nconst dataContextHashCache = new WeakMap<object, string>()\nlet hashCounter = 0\n\n/**\n * Safely gets a nested value from an object using dot notation path\n * Supports array indexing with [index] syntax\n * @param obj - The object to traverse\n * @param path - Dot notation path (e.g., \"user.name\" or \"items[0].title\")\n * @param visited - Set to track visited objects for circular reference protection\n * @returns The resolved value or undefined if not found\n */\nfunction getNestedValue(\n obj: unknown,\n path: string,\n visited = new WeakSet()\n): unknown {\n return path.split('.').reduce((current, key) => {\n if (!current || typeof current !== 'object') {\n return\n }\n\n // Protect against circular references\n if (visited.has(current as WeakKey)) {\n console.warn('Circular reference detected in data connector path:', path)\n return\n }\n\n // Add to circular reference tracking\n visited.add(current as WeakKey)\n\n // Handle array indexing like items[0] or items[1]\n const arrayMatch = key.match(ARRAY_INDEX_REGEX)\n if (arrayMatch) {\n const [, arrayKey, index] = arrayMatch\n const arrayObj = (current as Record<string, unknown>)[arrayKey]\n if (Array.isArray(arrayObj)) {\n const indexNum = Number.parseInt(index, 10)\n if (indexNum >= 0 && indexNum < arrayObj.length) {\n return arrayObj[indexNum]\n }\n }\n return\n }\n\n // Handle regular object property access with security validation\n if (!isSafePropertyName(key)) {\n console.warn(`Unsafe property access attempted: ${key}`)\n return\n }\n\n return current !== null && key in current\n ? (current as Record<string, unknown>)[key]\n : undefined\n }, obj)\n}\n\n/**\n * Sanitizes a string value to prevent XSS attacks\n * @param value - The value to sanitize\n * @returns Sanitized string safe for HTML rendering\n */\nfunction sanitizeValue(value: unknown): string {\n if (value === null || value === undefined) {\n return ''\n }\n\n const stringValue = String(value)\n\n // Basic HTML entity encoding to prevent XSS\n return stringValue\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#x27;')\n .replace(/\\//g, '&#x2F;')\n}\n\n/**\n * Creates a stable hash for dataContext object\n * Uses WeakMap to cache hash per object reference (no serialization needed)\n */\nfunction getDataContextHash(data: Record<string, unknown>): string {\n if (!dataContextHashCache.has(data)) {\n // Generate unique hash for this dataContext instance\n dataContextHashCache.set(data, `ctx_${++hashCounter}`)\n }\n const hash = dataContextHashCache.get(data)\n return hash || `ctx_${++hashCounter}`\n}\n\n/**\n * Creates a safe cache key from content and data\n * Uses stable hashing instead of JSON.stringify for massive performance gain\n */\nfunction createCacheKey(\n content: string,\n data: Record<string, unknown>\n): string {\n // Use stable hash instead of expensive JSON.stringify\n const dataHash = getDataContextHash(data)\n return `${content}:${dataHash}`\n}\n\n/**\n * Resolves data from the flat route-keyed data context\n * Enhanced to support complex route patterns with special characters\n * Supports: {{root.layout.shop.name}}, {{routes/($locale)._index.data}}, {{routes/($locale).blogs.$handle.$article.content}}\n */\nfunction resolveDataFromContext(\n path: string,\n dataContext: DataContext | null | undefined\n): unknown {\n if (!dataContext) {\n return\n }\n\n const trimmedPath = path.trim()\n\n // Use intelligent route parsing to separate route key from property path\n const { routeKey, propertyPath } = parseRouteAndProperty(\n trimmedPath,\n dataContext\n )\n\n // If we found a specific route key, resolve from that route's data\n if (routeKey && dataContext[routeKey]) {\n const routeData = dataContext[routeKey]\n if (typeof routeData === 'object' && routeData !== null) {\n if (propertyPath) {\n return getNestedValue(routeData, propertyPath)\n }\n return routeData\n }\n }\n\n // Fallback: search across all route data for backward compatibility\n // This handles cases like {{shop.name}} without route prefix\n\n // Check cache first\n const cachedResult = fallbackCache.get(trimmedPath)\n if (cachedResult && dataContext[cachedResult.routeKey]) {\n const value = getNestedValue(\n dataContext[cachedResult.routeKey],\n trimmedPath\n )\n if (value !== undefined) {\n // Update timestamp on cache hit\n cachedResult.timestamp = Date.now()\n return value\n }\n }\n\n // Search with priority order (most common routes first)\n const priorityRoutes = ['root', 'routes/product', 'routes/collection']\n const allRoutes = [\n ...priorityRoutes,\n ...Object.keys(dataContext).filter((k) => !priorityRoutes.includes(k)),\n ]\n\n for (const routeKey of allRoutes) {\n const routeData = dataContext[routeKey]\n if (routeData && typeof routeData === 'object' && routeData !== null) {\n const value = getNestedValue(routeData, trimmedPath)\n if (value !== undefined) {\n // Clean cache if it's getting too large\n if (fallbackCache.size >= FALLBACK_CACHE_MAX_SIZE) {\n const entries = Array.from(fallbackCache.entries())\n entries.sort((a, b) => a[1].timestamp - b[1].timestamp)\n // Remove oldest 25% of entries\n const toRemove = Math.floor(entries.length * CACHE_EVICTION_RATIO)\n for (let i = 0; i < toRemove; i++) {\n fallbackCache.delete(entries[i][0])\n }\n }\n fallbackCache.set(trimmedPath, {\n routeKey,\n value,\n timestamp: Date.now(),\n })\n return value\n }\n }\n }\n\n // Final fallback: try direct access (for non-route-keyed legacy data)\n return getNestedValue(dataContext, trimmedPath)\n}\n\n/**\n * Replaces data connector placeholders in content with actual values from data context\n * Supports {{path}} syntax with dot notation, array indexing, and route prefixes\n *\n * Enhanced syntax:\n * - {{root.siteSettings.title}} - Access root route data\n * - {{layout.navigation.items}} - Access layout route data\n * - {{parent.category.name}} - Access parent route data\n * - {{current.product.title}} - Access current route data\n * - {{user.name}} - Auto-resolved from combined data (backward compatible)\n *\n * @param content - The content string containing {{}} placeholders\n * @param dataContext - The enhanced WeaverseDataContext with route-keyed data\n * @returns The content with placeholders replaced by actual values\n */\nexport function replaceContentDataConnectors(\n content: string,\n dataContext: DataContext | null | undefined\n): string {\n if (!dataContext || typeof content !== 'string') {\n return content\n }\n\n try {\n // Simplified caching - dataContext is already the combined data\n const cacheKey = createCacheKey(content, dataContext)\n\n const cachedContent = templateCache.get(cacheKey)\n if (cachedContent) {\n return cachedContent\n }\n\n const newContent = content.replace(TEMPLATE_REGEX, (match, path) => {\n try {\n const value = resolveDataFromContext(path, dataContext)\n\n if (value === undefined || value === null) {\n return match // Return original placeholder if value not found\n }\n\n return sanitizeValue(value)\n } catch (pathError) {\n console.warn(\n `Error resolving data connector path \"${path}\":`,\n pathError\n )\n return match\n }\n })\n\n // Cache the result (with size limit)\n if (templateCache.size >= MAX_CACHE_SIZE) {\n const firstKey = templateCache.keys().next().value\n templateCache.delete(firstKey)\n }\n templateCache.set(cacheKey, newContent)\n\n return newContent\n } catch (error) {\n console.warn('Error processing data connectors:', error, { content })\n return content\n }\n}\n\n/**\n * Fast check if data contains any {{...}} placeholders\n * Returns true if any string value contains the pattern\n * This is MUCH faster than processing and allows early exit\n *\n * IMPORTANT: Uses global TEMPLATE_REGEX, must reset lastIndex\n */\nfunction hasPlaceholders(data: unknown, visited = new WeakSet()): boolean {\n if (typeof data === 'string') {\n // Reset regex state before testing to ensure consistent results\n TEMPLATE_REGEX.lastIndex = 0\n return TEMPLATE_REGEX.test(data)\n }\n\n if (typeof data !== 'object' || data === null) {\n return false\n }\n\n // Skip Date objects - they can't contain placeholders\n if (data instanceof Date) {\n return false\n }\n\n // Protect against circular references\n if (visited.has(data as WeakKey)) {\n return false\n }\n visited.add(data as WeakKey)\n\n try {\n if (Array.isArray(data)) {\n return data.some((item) => hasPlaceholders(item, visited))\n }\n\n // Check object values safely (skip property access errors)\n for (const key of Object.keys(data)) {\n try {\n const value = (data as Record<string, unknown>)[key]\n if (hasPlaceholders(value, visited)) {\n return true\n }\n } catch {\n // Ignore property access errors - continue checking other properties\n }\n }\n\n return false\n } finally {\n visited.delete(data as WeakKey)\n }\n}\n\n/**\n * Recursively replaces data connector placeholders in any data structure\n * IMMUTABLE: Creates deep copies during processing to avoid mutating original data\n * Handles strings, objects, arrays, and nested combinations\n *\n * PERFORMANCE: Now includes early exit if no placeholders detected\n *\n * @param data - The data to process (string, object, array, or primitive)\n * @param dataContext - The data context for replacements\n * @param visited - Set to track visited objects for circular reference protection\n * @param forceClone - Internal flag to force cloning even if no placeholders (for parent immutability)\n * @returns IMMUTABLE copy of the data with all string placeholders replaced\n */\nexport function replaceContentDataConnectorsDeep<T>(\n data: T,\n dataContext: DataContext | null | undefined,\n visited = new WeakSet(),\n forceClone = false\n): T {\n if (!dataContext) {\n return data\n }\n\n const dataHasPlaceholders = hasPlaceholders(data, new WeakSet())\n\n // PERFORMANCE: Fast check - if no placeholders exist and not forced to clone, skip ALL processing\n // This saves 90%+ of CPU time for components without data connectors\n if (!(dataHasPlaceholders || forceClone)) {\n return data\n }\n\n // Handle primitive types - no cloning needed for primitives\n if (typeof data === 'string') {\n return replaceContentDataConnectors(data, dataContext) as T\n }\n\n if (typeof data !== 'object' || data === null) {\n return data\n }\n\n // Protect against circular references\n if (visited.has(data as WeakKey)) {\n console.warn(\n 'Circular reference detected in deep data connector replacement'\n )\n return data\n }\n\n // Add to circular reference tracking\n visited.add(data as WeakKey)\n\n try {\n // Handle special objects that should be cloned as-is\n if (data instanceof Date) {\n // Date objects can't contain placeholders, but clone if forced (parent has placeholders)\n // or if somehow detected (shouldn't happen but be safe)\n if (forceClone || dataHasPlaceholders) {\n return new Date(data.getTime()) as T\n }\n return data\n }\n\n // Handle arrays - create new array with processed items\n // Force clone children if parent has placeholders (for immutability)\n if (Array.isArray(data)) {\n const result = data.map((item) =>\n replaceContentDataConnectorsDeep(\n item,\n dataContext,\n visited,\n dataHasPlaceholders\n )\n ) as T\n return result\n }\n\n // Handle plain objects - create new object with processed values\n // Force clone children if parent has placeholders (for immutability)\n const result = {} as T\n\n // Use Object.entries with try-catch per property to handle getter errors\n for (const key of Object.keys(data)) {\n try {\n const value = (data as Record<string, unknown>)[key]\n ;(result as Record<string, unknown>)[key] =\n replaceContentDataConnectorsDeep(\n value,\n dataContext,\n visited,\n dataHasPlaceholders\n )\n } catch (propertyError) {\n // If property access throws, skip it but continue processing\n console.warn(`Error accessing property \"${key}\":`, propertyError)\n // Don't include this property in result\n }\n }\n\n return result\n } catch (error) {\n console.warn('Error in deep data connector replacement:', error)\n return data\n } finally {\n // Remove from tracking when done processing this object\n visited.delete(data as WeakKey)\n }\n}\n","import { Weaverse, type WeaverseItemStore } from '@weaverse/core'\nimport { useContext } from 'react'\nimport { WeaverseContext, WeaverseItemContext } from './context'\n\nexport type { DataContext } from './utils/data-connector'\n// Re-export the data connector utilities\nexport {\n replaceContentDataConnectors,\n replaceContentDataConnectorsDeep,\n} from './utils/data-connector'\n\n/**\n * Returns the active Weaverse runtime from React context.\n *\n * @typeParam T - Runtime subtype expected by the integration.\n */\nexport function useWeaverse<T = Weaverse>() {\n let weaverse = useContext(WeaverseContext)\n return weaverse as T\n}\n\n/**\n * Returns a Weaverse item instance by ID, or the current rendered item when no\n * ID is provided.\n *\n * @param id - Optional item ID to look up.\n * @returns The matching item instance, or `null` when it is unavailable.\n */\nexport let useItemInstance = (id?: string) => {\n let { id: currentId } = useContext(WeaverseItemContext)\n let instance = Weaverse.itemInstances.get(id || currentId)\n if (!instance) {\n console.log(`Item instance ${id} not found`)\n return null\n }\n return instance as WeaverseItemStore\n}\n\n/**\n * Returns the parent instance of the current rendered item.\n *\n * @returns The parent item instance. At the root, the empty parent ID falls\n * back to the current item instance; returns `null` only when that lookup is\n * unavailable.\n */\nexport let useParentInstance = () => {\n let { parentId } = useContext(WeaverseItemContext)\n return useItemInstance(parentId || '')\n}\n\n/**\n * Returns the direct child instances of an item.\n *\n * @param id - Optional parent item ID; defaults to the current rendered item.\n */\nexport let useChildInstances = (id?: string) => {\n let currentInstance = useItemInstance(id)\n if (!currentInstance) {\n return []\n }\n let { itemInstances } = Weaverse\n\n let {\n data: { children },\n } = currentInstance\n return children\n .map(({ id }: { id: string }) => itemInstances.get(id))\n .filter(Boolean) as WeaverseItemStore[]\n}\n","import type { Weaverse } from '@weaverse/core'\nimport clsx from 'clsx'\nimport React, { memo, useEffect, useMemo, useRef } from 'react'\nimport { WeaverseContextProvider, WeaverseItemContext } from './context'\nimport { useItemInstance, useWeaverse } from './hooks'\nimport type { ItemComponentProps, WeaverseRootPropsType } from './types'\nimport { replaceContentDataConnectorsDeep } from './utils/data-connector'\n\nconst REACT_VERSION_THRESHOLD = 18\nconst reactVersion = Number(React.version?.split('.')[0]) || 0\n\n/**\n * Reads an external store in React while remaining safe during server rendering\n * and on React versions without `useSyncExternalStore`.\n *\n * @param subscribe - Registers a callback for store changes.\n * @param getSnapshot - Returns the current client snapshot.\n * @param getServerSnapshot - Returns the server snapshot; defaults to `getSnapshot`.\n * @returns The current external-store snapshot.\n */\nexport const useSafeExternalStore = (\n subscribe: any,\n getSnapshot: any,\n getServerSnapshot: any = getSnapshot\n) => {\n // In server environment or during SSR, just return the snapshot directly\n if (typeof window === 'undefined') {\n return getServerSnapshot()\n }\n\n // In client environment, use React.useSyncExternalStore if available (React 18+)\n if (React.useSyncExternalStore) {\n // biome-ignore lint/correctness/useHookAtTopLevel: <$>\n return React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n }\n\n // Fallback for older React versions - just return the current snapshot\n return getSnapshot()\n}\n\n/**\n * Determines the root element ID for the Weaverse component tree.\n * Falls back through multiple strategies to ensure a valid root is always found:\n * 1. Use explicit rootId from context.data if available\n * 2. Find the first element with type 'main' in items array\n * 3. Use projectId as final fallback\n */\nconst getRootId = (context: Weaverse): string => {\n // Strategy 1: Explicit rootId\n if (context.data.rootId) {\n return context.data.rootId\n }\n\n // Strategy 2: Find main element\n let mainItem = context.data?.items?.find((item) => item.type === 'main')\n if (mainItem?.id) {\n return mainItem.id\n }\n\n // Strategy 3: Fallback to projectId\n return context.projectId\n}\n/**\n * Renders a Weaverse content tree and provides its runtime to descendant\n * components.\n */\nexport const WeaverseRoot = memo(({ context }: WeaverseRootPropsType) => {\n const data = useSafeExternalStore(\n context.subscribe,\n context.getSnapShot,\n context.getSnapShot\n )\n const rootRef = useRef<HTMLElement>(null)\n\n useEffect(() => {\n context.contentRootElement = rootRef.current\n }, [context])\n\n const eventHandlers = context?.studioBridge?.eventHandlers || {}\n\n if (context.projectId) {\n return (\n <div\n className=\"weaverse-content-root\"\n {...eventHandlers}\n data-weaverse-project-id={context.projectId}\n data-weaverse-template-id={data.id}\n ref={rootRef}\n >\n <WeaverseContextProvider value={context}>\n <ItemInstance id={getRootId(context)} parentId={''} />\n </WeaverseContextProvider>\n </div>\n )\n }\n return null\n})\nWeaverseRoot.displayName = 'WeaverseRoot'\nconst ItemComponent = memo(({ instance }: ItemComponentProps) => {\n const context = useWeaverse()\n const { elementRegistry } = context\n const data = useSafeExternalStore(\n instance.subscribe,\n instance.getSnapShot,\n instance.getSnapShot\n )\n const {\n id,\n type,\n childIds = [],\n children = [],\n\n createdAt,\n updatedAt,\n deletedAt,\n css,\n __hidden,\n // Need to get rid of this `data` property\n data: _,\n ...rest\n } = data\n\n // Replace data connectors in all component data (handles strings, objects, and arrays recursively)\n // IMMUTABLE: Process the entire rest object to avoid mutating original content\n // Memoized per snapshot: `data` identity changes exactly when the item's\n // store or translation entry changes (see WeaverseHydrogenItem.getSnapShot),\n // and context-only updates swap the store reference via `setData({})` in\n // syncReusedInstance — so re-renders with an unchanged snapshot skip the\n // deep placeholder scan + clone entirely.\n // biome-ignore lint/correctness/useExhaustiveDependencies: `rest` is a pure projection of `data`\n const processedRest = useMemo(\n () => replaceContentDataConnectorsDeep(rest, context.dataContext),\n [data, context.dataContext]\n )\n\n useEffect(() => {\n if (!instance.ref.current) {\n // Fallback `ref` if the element isn't created by `React.forwardRef`\n Object.assign(instance.ref, {\n current: document.querySelector(`[data-wv-id=\"${id}\"]`),\n })\n }\n }, [instance, id])\n\n if (__hidden) {\n return null\n }\n\n const Element = elementRegistry.get(type)\n\n if (Element?.Component) {\n const Component = Element.Component\n if (Component.displayName !== type) {\n // Devtools nicety — assign once, not on every render.\n Component.displayName = type\n }\n if (\n Component.$$typeof === Symbol.for('react.forward_ref') ||\n reactVersion > REACT_VERSION_THRESHOLD\n ) {\n processedRest.ref = instance.ref\n }\n const childItems = children?.length\n ? children\n : childIds?.length\n ? childIds.map((cid: string) => ({ id: cid }))\n : []\n\n const renderChildren = childItems.map(\n (item: { id: string }, index: number) => (\n <ItemInstance id={item.id} key={`${item.id}-${index}`} parentId={id} />\n )\n )\n\n return (\n <Component\n {...processedRest}\n children={renderChildren.length ? renderChildren : undefined}\n className={clsx(processedRest.data?.className)}\n data-wv-id={id}\n data-wv-type={type}\n key={id}\n />\n )\n }\n instance._store.deleted = true\n return null\n})\nItemComponent.displayName = 'WeaverseComponent'\n\nconst ItemInstance = memo(\n ({ id, parentId }: { id: string; parentId: string }) => {\n const instance = useItemInstance(id)\n if (!instance) {\n return <div style={{ display: 'none' }}>Item instance {id} not found</div>\n }\n return (\n <WeaverseItemContext.Provider value={{ parentId, id }}>\n <ItemComponent instance={instance} key={id} />\n </WeaverseItemContext.Provider>\n )\n }\n)\nItemInstance.displayName = 'WeaverseInstanceProvider'\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/context.ts","../src/utils/data-connector.ts","../src/hooks.ts","../src/renderer.tsx","../src/studio-selection.ts"],"sourcesContent":["import type { Weaverse } from '@weaverse/core'\nimport { createContext } from 'react'\n\n/** Provides the active Weaverse runtime to React components. */\nexport let WeaverseContext = createContext<Weaverse>({} as Weaverse)\n\n/** Provider component for supplying the active Weaverse runtime. */\nexport let WeaverseContextProvider = WeaverseContext.Provider\n\n/** Identifies the current rendered item and its parent item. */\nexport let WeaverseItemContext = createContext<{\n /** ID of the current Weaverse item. */\n id: string\n /** ID of the current item's parent, or an empty string at the root. */\n parentId: string\n}>({ id: '', parentId: '' })\n","// Data connector utilities for replacing placeholders with real data\n\n/** Route-keyed or flat data available when resolving content placeholders. */\nexport type DataContext = Record<string, unknown>\n\n// Cached regex pattern for better performance\nconst TEMPLATE_REGEX = /\\{\\{([^}]+)\\}\\}/g\nconst ARRAY_INDEX_REGEX = /^(\\w+)\\[(\\d+)\\]$/\n\n// Cache eviction ratio - remove oldest 25% when cache is full\nconst CACHE_EVICTION_RATIO = 0.25\n\n/**\n * Validates that a property name is safe to access (prevents prototype pollution)\n * Enhanced to handle route-specific patterns with special characters\n */\nfunction isSafePropertyName(key: string): boolean {\n // Block dangerous prototype pollution patterns\n const dangerousPatterns = ['__proto__', 'constructor', 'prototype']\n return !dangerousPatterns.includes(key)\n}\n\n/**\n * Parses a template path into route key and property path components\n * Handles complex routes like: routes/($locale).blogs.$blogHandle.$articleHandle\n * Enhanced with caching for improved performance\n *\n * @param path - The full path from template (e.g., \"routes/($locale)._index.weaverseData.page.name\")\n * @param dataContext - Available route data context\n * @returns Object with routeKey and propertyPath\n */\nfunction parseRouteAndProperty(\n path: string,\n dataContext: DataContext\n): {\n routeKey: string | null\n propertyPath: string\n} {\n if (!dataContext || typeof path !== 'string') {\n return { routeKey: null, propertyPath: path }\n }\n\n const trimmedPath = path.trim()\n\n // Create a cache key using stable hash instead of serializing route keys\n const dataHash = getDataContextHash(dataContext)\n const cacheKey = `${trimmedPath}::${dataHash}`\n\n // Check cache first\n const cachedResult = routeParsingCache.get(cacheKey)\n if (cachedResult) {\n // Update timestamp on cache hit\n cachedResult.timestamp = Date.now()\n return {\n routeKey: cachedResult.routeKey,\n propertyPath: cachedResult.propertyPath,\n }\n }\n\n // Get all available routes and sort by length (longest first for specificity)\n const availableRoutes = Object.keys(dataContext).sort(\n (a, b) => b.length - a.length\n )\n\n let result: { routeKey: string | null; propertyPath: string } = {\n routeKey: null,\n propertyPath: trimmedPath,\n }\n\n // Try to find the longest matching route key\n let found = false\n for (const routeKey of availableRoutes) {\n // Exact match\n if (trimmedPath === routeKey) {\n result = { routeKey, propertyPath: '' }\n found = true\n break\n }\n\n // Route key with property path\n if (trimmedPath.startsWith(`${routeKey}.`)) {\n const propertyPath = trimmedPath.slice(routeKey.length + 1)\n result = { routeKey, propertyPath }\n found = true\n break\n }\n }\n\n if (!found) {\n // No route key found, treat entire path as property path\n result = { routeKey: null, propertyPath: trimmedPath }\n }\n\n // Cache the result\n if (routeParsingCache.size >= ROUTE_PARSING_CACHE_MAX_SIZE) {\n // Clean cache by removing oldest entries\n const entries = Array.from(routeParsingCache.entries())\n entries.sort((a, b) => a[1].timestamp - b[1].timestamp)\n const toRemove = Math.floor(entries.length * CACHE_EVICTION_RATIO)\n for (let i = 0; i < toRemove; i++) {\n routeParsingCache.delete(entries[i][0])\n }\n }\n\n routeParsingCache.set(cacheKey, {\n routeKey: result.routeKey,\n propertyPath: result.propertyPath,\n timestamp: Date.now(),\n })\n\n return result\n}\n\n// Simple cache for parsed templates (max 100 entries)\nconst templateCache = new Map<string, string>()\nconst MAX_CACHE_SIZE = 100\n\n// Cache for fallback lookups to improve performance (moved to module level to prevent memory leaks)\nconst fallbackCache = new Map<\n string,\n { routeKey: string; value: unknown; timestamp: number }\n>()\nconst FALLBACK_CACHE_MAX_SIZE = 50\n\n// Route parsing cache for performance optimization\nconst routeParsingCache = new Map<\n string,\n { routeKey: string | null; propertyPath: string; timestamp: number }\n>()\nconst ROUTE_PARSING_CACHE_MAX_SIZE = 100\n\n// WeakMap for stable dataContext hashing - prevents repeated JSON.stringify\nconst dataContextHashCache = new WeakMap<object, string>()\nlet hashCounter = 0\n\n/**\n * Safely gets a nested value from an object using dot notation path\n * Supports array indexing with [index] syntax\n * @param obj - The object to traverse\n * @param path - Dot notation path (e.g., \"user.name\" or \"items[0].title\")\n * @param visited - Set to track visited objects for circular reference protection\n * @returns The resolved value or undefined if not found\n */\nfunction getNestedValue(\n obj: unknown,\n path: string,\n visited = new WeakSet()\n): unknown {\n return path.split('.').reduce((current, key) => {\n if (!current || typeof current !== 'object') {\n return\n }\n\n // Protect against circular references\n if (visited.has(current as WeakKey)) {\n console.warn('Circular reference detected in data connector path:', path)\n return\n }\n\n // Add to circular reference tracking\n visited.add(current as WeakKey)\n\n // Handle array indexing like items[0] or items[1]\n const arrayMatch = key.match(ARRAY_INDEX_REGEX)\n if (arrayMatch) {\n const [, arrayKey, index] = arrayMatch\n const arrayObj = (current as Record<string, unknown>)[arrayKey]\n if (Array.isArray(arrayObj)) {\n const indexNum = Number.parseInt(index, 10)\n if (indexNum >= 0 && indexNum < arrayObj.length) {\n return arrayObj[indexNum]\n }\n }\n return\n }\n\n // Handle regular object property access with security validation\n if (!isSafePropertyName(key)) {\n console.warn(`Unsafe property access attempted: ${key}`)\n return\n }\n\n return current !== null && key in current\n ? (current as Record<string, unknown>)[key]\n : undefined\n }, obj)\n}\n\n/**\n * Sanitizes a string value to prevent XSS attacks\n * @param value - The value to sanitize\n * @returns Sanitized string safe for HTML rendering\n */\nfunction sanitizeValue(value: unknown): string {\n if (value === null || value === undefined) {\n return ''\n }\n\n const stringValue = String(value)\n\n // Basic HTML entity encoding to prevent XSS\n return stringValue\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#x27;')\n .replace(/\\//g, '&#x2F;')\n}\n\n/**\n * Creates a stable hash for dataContext object\n * Uses WeakMap to cache hash per object reference (no serialization needed)\n */\nfunction getDataContextHash(data: Record<string, unknown>): string {\n if (!dataContextHashCache.has(data)) {\n // Generate unique hash for this dataContext instance\n dataContextHashCache.set(data, `ctx_${++hashCounter}`)\n }\n const hash = dataContextHashCache.get(data)\n return hash || `ctx_${++hashCounter}`\n}\n\n/**\n * Creates a safe cache key from content and data\n * Uses stable hashing instead of JSON.stringify for massive performance gain\n */\nfunction createCacheKey(\n content: string,\n data: Record<string, unknown>\n): string {\n // Use stable hash instead of expensive JSON.stringify\n const dataHash = getDataContextHash(data)\n return `${content}:${dataHash}`\n}\n\n/**\n * Resolves data from the flat route-keyed data context\n * Enhanced to support complex route patterns with special characters\n * Supports: {{root.layout.shop.name}}, {{routes/($locale)._index.data}}, {{routes/($locale).blogs.$handle.$article.content}}\n */\nfunction resolveDataFromContext(\n path: string,\n dataContext: DataContext | null | undefined\n): unknown {\n if (!dataContext) {\n return\n }\n\n const trimmedPath = path.trim()\n\n // Use intelligent route parsing to separate route key from property path\n const { routeKey, propertyPath } = parseRouteAndProperty(\n trimmedPath,\n dataContext\n )\n\n // If we found a specific route key, resolve from that route's data\n if (routeKey && dataContext[routeKey]) {\n const routeData = dataContext[routeKey]\n if (typeof routeData === 'object' && routeData !== null) {\n if (propertyPath) {\n return getNestedValue(routeData, propertyPath)\n }\n return routeData\n }\n }\n\n // Fallback: search across all route data for backward compatibility\n // This handles cases like {{shop.name}} without route prefix\n\n // Check cache first\n const cachedResult = fallbackCache.get(trimmedPath)\n if (cachedResult && dataContext[cachedResult.routeKey]) {\n const value = getNestedValue(\n dataContext[cachedResult.routeKey],\n trimmedPath\n )\n if (value !== undefined) {\n // Update timestamp on cache hit\n cachedResult.timestamp = Date.now()\n return value\n }\n }\n\n // Search with priority order (most common routes first)\n const priorityRoutes = ['root', 'routes/product', 'routes/collection']\n const allRoutes = [\n ...priorityRoutes,\n ...Object.keys(dataContext).filter((k) => !priorityRoutes.includes(k)),\n ]\n\n for (const routeKey of allRoutes) {\n const routeData = dataContext[routeKey]\n if (routeData && typeof routeData === 'object' && routeData !== null) {\n const value = getNestedValue(routeData, trimmedPath)\n if (value !== undefined) {\n // Clean cache if it's getting too large\n if (fallbackCache.size >= FALLBACK_CACHE_MAX_SIZE) {\n const entries = Array.from(fallbackCache.entries())\n entries.sort((a, b) => a[1].timestamp - b[1].timestamp)\n // Remove oldest 25% of entries\n const toRemove = Math.floor(entries.length * CACHE_EVICTION_RATIO)\n for (let i = 0; i < toRemove; i++) {\n fallbackCache.delete(entries[i][0])\n }\n }\n fallbackCache.set(trimmedPath, {\n routeKey,\n value,\n timestamp: Date.now(),\n })\n return value\n }\n }\n }\n\n // Final fallback: try direct access (for non-route-keyed legacy data)\n return getNestedValue(dataContext, trimmedPath)\n}\n\n/**\n * Replaces data connector placeholders in content with actual values from data context\n * Supports {{path}} syntax with dot notation, array indexing, and route prefixes\n *\n * Enhanced syntax:\n * - {{root.siteSettings.title}} - Access root route data\n * - {{layout.navigation.items}} - Access layout route data\n * - {{parent.category.name}} - Access parent route data\n * - {{current.product.title}} - Access current route data\n * - {{user.name}} - Auto-resolved from combined data (backward compatible)\n *\n * @param content - The content string containing {{}} placeholders\n * @param dataContext - The enhanced WeaverseDataContext with route-keyed data\n * @returns The content with placeholders replaced by actual values\n */\nexport function replaceContentDataConnectors(\n content: string,\n dataContext: DataContext | null | undefined\n): string {\n if (!dataContext || typeof content !== 'string') {\n return content\n }\n\n try {\n // Simplified caching - dataContext is already the combined data\n const cacheKey = createCacheKey(content, dataContext)\n\n const cachedContent = templateCache.get(cacheKey)\n if (cachedContent) {\n return cachedContent\n }\n\n const newContent = content.replace(TEMPLATE_REGEX, (match, path) => {\n try {\n const value = resolveDataFromContext(path, dataContext)\n\n if (value === undefined || value === null) {\n return match // Return original placeholder if value not found\n }\n\n return sanitizeValue(value)\n } catch (pathError) {\n console.warn(\n `Error resolving data connector path \"${path}\":`,\n pathError\n )\n return match\n }\n })\n\n // Cache the result (with size limit)\n if (templateCache.size >= MAX_CACHE_SIZE) {\n const firstKey = templateCache.keys().next().value\n templateCache.delete(firstKey)\n }\n templateCache.set(cacheKey, newContent)\n\n return newContent\n } catch (error) {\n console.warn('Error processing data connectors:', error, { content })\n return content\n }\n}\n\n/**\n * Fast check if data contains any {{...}} placeholders\n * Returns true if any string value contains the pattern\n * This is MUCH faster than processing and allows early exit\n *\n * IMPORTANT: Uses global TEMPLATE_REGEX, must reset lastIndex\n */\nfunction hasPlaceholders(data: unknown, visited = new WeakSet()): boolean {\n if (typeof data === 'string') {\n // Reset regex state before testing to ensure consistent results\n TEMPLATE_REGEX.lastIndex = 0\n return TEMPLATE_REGEX.test(data)\n }\n\n if (typeof data !== 'object' || data === null) {\n return false\n }\n\n // Skip Date objects - they can't contain placeholders\n if (data instanceof Date) {\n return false\n }\n\n // Protect against circular references\n if (visited.has(data as WeakKey)) {\n return false\n }\n visited.add(data as WeakKey)\n\n try {\n if (Array.isArray(data)) {\n return data.some((item) => hasPlaceholders(item, visited))\n }\n\n // Check object values safely (skip property access errors)\n for (const key of Object.keys(data)) {\n try {\n const value = (data as Record<string, unknown>)[key]\n if (hasPlaceholders(value, visited)) {\n return true\n }\n } catch {\n // Ignore property access errors - continue checking other properties\n }\n }\n\n return false\n } finally {\n visited.delete(data as WeakKey)\n }\n}\n\n/**\n * Recursively replaces data connector placeholders in any data structure\n * IMMUTABLE: Creates deep copies during processing to avoid mutating original data\n * Handles strings, objects, arrays, and nested combinations\n *\n * PERFORMANCE: Now includes early exit if no placeholders detected\n *\n * @param data - The data to process (string, object, array, or primitive)\n * @param dataContext - The data context for replacements\n * @param visited - Set to track visited objects for circular reference protection\n * @param forceClone - Internal flag to force cloning even if no placeholders (for parent immutability)\n * @returns IMMUTABLE copy of the data with all string placeholders replaced\n */\nexport function replaceContentDataConnectorsDeep<T>(\n data: T,\n dataContext: DataContext | null | undefined,\n visited = new WeakSet(),\n forceClone = false\n): T {\n if (!dataContext) {\n return data\n }\n\n const dataHasPlaceholders = hasPlaceholders(data, new WeakSet())\n\n // PERFORMANCE: Fast check - if no placeholders exist and not forced to clone, skip ALL processing\n // This saves 90%+ of CPU time for components without data connectors\n if (!(dataHasPlaceholders || forceClone)) {\n return data\n }\n\n // Handle primitive types - no cloning needed for primitives\n if (typeof data === 'string') {\n return replaceContentDataConnectors(data, dataContext) as T\n }\n\n if (typeof data !== 'object' || data === null) {\n return data\n }\n\n // Protect against circular references\n if (visited.has(data as WeakKey)) {\n console.warn(\n 'Circular reference detected in deep data connector replacement'\n )\n return data\n }\n\n // Add to circular reference tracking\n visited.add(data as WeakKey)\n\n try {\n // Handle special objects that should be cloned as-is\n if (data instanceof Date) {\n // Date objects can't contain placeholders, but clone if forced (parent has placeholders)\n // or if somehow detected (shouldn't happen but be safe)\n if (forceClone || dataHasPlaceholders) {\n return new Date(data.getTime()) as T\n }\n return data\n }\n\n // Handle arrays - create new array with processed items\n // Force clone children if parent has placeholders (for immutability)\n if (Array.isArray(data)) {\n const result = data.map((item) =>\n replaceContentDataConnectorsDeep(\n item,\n dataContext,\n visited,\n dataHasPlaceholders\n )\n ) as T\n return result\n }\n\n // Handle plain objects - create new object with processed values\n // Force clone children if parent has placeholders (for immutability)\n const result = {} as T\n\n // Use Object.entries with try-catch per property to handle getter errors\n for (const key of Object.keys(data)) {\n try {\n const value = (data as Record<string, unknown>)[key]\n ;(result as Record<string, unknown>)[key] =\n replaceContentDataConnectorsDeep(\n value,\n dataContext,\n visited,\n dataHasPlaceholders\n )\n } catch (propertyError) {\n // If property access throws, skip it but continue processing\n console.warn(`Error accessing property \"${key}\":`, propertyError)\n // Don't include this property in result\n }\n }\n\n return result\n } catch (error) {\n console.warn('Error in deep data connector replacement:', error)\n return data\n } finally {\n // Remove from tracking when done processing this object\n visited.delete(data as WeakKey)\n }\n}\n","import { Weaverse, type WeaverseItemStore } from '@weaverse/core'\nimport { useContext } from 'react'\nimport { WeaverseContext, WeaverseItemContext } from './context'\n\nexport type { DataContext } from './utils/data-connector'\n// Re-export the data connector utilities\nexport {\n replaceContentDataConnectors,\n replaceContentDataConnectorsDeep,\n} from './utils/data-connector'\n\n/**\n * Returns the active Weaverse runtime from React context.\n *\n * @typeParam T - Runtime subtype expected by the integration.\n */\nexport function useWeaverse<T = Weaverse>() {\n let weaverse = useContext(WeaverseContext)\n return weaverse as T\n}\n\n/**\n * Returns a Weaverse item instance by ID, or the current rendered item when no\n * ID is provided.\n *\n * @param id - Optional item ID to look up.\n * @returns The matching item instance, or `null` when it is unavailable.\n */\nexport let useItemInstance = (id?: string) => {\n let { id: currentId } = useContext(WeaverseItemContext)\n let instance = Weaverse.itemInstances.get(id || currentId)\n if (!instance) {\n console.log(`Item instance ${id} not found`)\n return null\n }\n return instance as WeaverseItemStore\n}\n\n/**\n * Returns the parent instance of the current rendered item.\n *\n * @returns The parent item instance. At the root, the empty parent ID falls\n * back to the current item instance; returns `null` only when that lookup is\n * unavailable.\n */\nexport let useParentInstance = () => {\n let { parentId } = useContext(WeaverseItemContext)\n return useItemInstance(parentId || '')\n}\n\n/**\n * Returns the direct child instances of an item.\n *\n * @param id - Optional parent item ID; defaults to the current rendered item.\n */\nexport let useChildInstances = (id?: string) => {\n let currentInstance = useItemInstance(id)\n if (!currentInstance) {\n return []\n }\n let { itemInstances } = Weaverse\n\n let {\n data: { children },\n } = currentInstance\n return children\n .map(({ id }: { id: string }) => itemInstances.get(id))\n .filter(Boolean) as WeaverseItemStore[]\n}\n","import type { Weaverse } from '@weaverse/core'\nimport clsx from 'clsx'\nimport React, { memo, useEffect, useMemo, useRef } from 'react'\nimport { WeaverseContextProvider, WeaverseItemContext } from './context'\nimport { useItemInstance, useWeaverse } from './hooks'\nimport type { ItemComponentProps, WeaverseRootPropsType } from './types'\nimport { replaceContentDataConnectorsDeep } from './utils/data-connector'\n\nconst REACT_VERSION_THRESHOLD = 18\nconst reactVersion = Number(React.version?.split('.')[0]) || 0\n\n/**\n * Reads an external store in React while remaining safe during server rendering\n * and on React versions without `useSyncExternalStore`.\n *\n * @param subscribe - Registers a callback for store changes.\n * @param getSnapshot - Returns the current client snapshot.\n * @param getServerSnapshot - Returns the server snapshot; defaults to `getSnapshot`.\n * @returns The current external-store snapshot.\n */\nexport const useSafeExternalStore = (\n subscribe: any,\n getSnapshot: any,\n getServerSnapshot: any = getSnapshot\n) => {\n // In server environment or during SSR, just return the snapshot directly\n if (typeof window === 'undefined') {\n return getServerSnapshot()\n }\n\n // In client environment, use React.useSyncExternalStore if available (React 18+)\n if (React.useSyncExternalStore) {\n // biome-ignore lint/correctness/useHookAtTopLevel: <$>\n return React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n }\n\n // Fallback for older React versions - just return the current snapshot\n return getSnapshot()\n}\n\n/**\n * Determines the root element ID for the Weaverse component tree.\n * Falls back through multiple strategies to ensure a valid root is always found:\n * 1. Use explicit rootId from context.data if available\n * 2. Find the first element with type 'main' in items array\n * 3. Use projectId as final fallback\n */\nconst getRootId = (context: Weaverse): string => {\n // Strategy 1: Explicit rootId\n if (context.data.rootId) {\n return context.data.rootId\n }\n\n // Strategy 2: Find main element\n let mainItem = context.data?.items?.find((item) => item.type === 'main')\n if (mainItem?.id) {\n return mainItem.id\n }\n\n // Strategy 3: Fallback to projectId\n return context.projectId\n}\n/**\n * Renders a Weaverse content tree and provides its runtime to descendant\n * components.\n */\nexport const WeaverseRoot = memo(({ context }: WeaverseRootPropsType) => {\n const data = useSafeExternalStore(\n context.subscribe,\n context.getSnapShot,\n context.getSnapShot\n )\n const rootRef = useRef<HTMLElement>(null)\n\n useEffect(() => {\n context.contentRootElement = rootRef.current\n }, [context])\n\n const eventHandlers = context?.studioBridge?.eventHandlers || {}\n\n if (context.projectId) {\n return (\n <div\n className=\"weaverse-content-root\"\n {...eventHandlers}\n data-weaverse-project-id={context.projectId}\n data-weaverse-template-id={data.id}\n ref={rootRef}\n >\n <WeaverseContextProvider value={context}>\n <ItemInstance id={getRootId(context)} parentId={''} />\n </WeaverseContextProvider>\n </div>\n )\n }\n return null\n})\nWeaverseRoot.displayName = 'WeaverseRoot'\nconst ItemComponent = memo(({ instance }: ItemComponentProps) => {\n const context = useWeaverse()\n const { elementRegistry } = context\n const data = useSafeExternalStore(\n instance.subscribe,\n instance.getSnapShot,\n instance.getSnapShot\n )\n const {\n id,\n type,\n childIds = [],\n children = [],\n\n createdAt,\n updatedAt,\n deletedAt,\n css,\n __hidden,\n // Need to get rid of this `data` property\n data: _,\n ...rest\n } = data\n\n // Replace data connectors in all component data (handles strings, objects, and arrays recursively)\n // IMMUTABLE: Process the entire rest object to avoid mutating original content\n // Memoized per snapshot: `data` identity changes exactly when the item's\n // store or translation entry changes (see WeaverseHydrogenItem.getSnapShot),\n // and context-only updates swap the store reference via `setData({})` in\n // syncReusedInstance — so re-renders with an unchanged snapshot skip the\n // deep placeholder scan + clone entirely.\n // biome-ignore lint/correctness/useExhaustiveDependencies: `rest` is a pure projection of `data`\n const processedRest = useMemo(\n () => replaceContentDataConnectorsDeep(rest, context.dataContext),\n [data, context.dataContext]\n )\n\n useEffect(() => {\n if (!instance.ref.current) {\n // Fallback `ref` if the element isn't created by `React.forwardRef`\n Object.assign(instance.ref, {\n current: document.querySelector(`[data-wv-id=\"${id}\"]`),\n })\n }\n }, [instance, id])\n\n if (__hidden) {\n return null\n }\n\n const Element = elementRegistry.get(type)\n\n if (Element?.Component) {\n const Component = Element.Component\n if (Component.displayName !== type) {\n // Devtools nicety — assign once, not on every render.\n Component.displayName = type\n }\n if (\n Component.$$typeof === Symbol.for('react.forward_ref') ||\n reactVersion > REACT_VERSION_THRESHOLD\n ) {\n processedRest.ref = instance.ref\n }\n const childItems = children?.length\n ? children\n : childIds?.length\n ? childIds.map((cid: string) => ({ id: cid }))\n : []\n\n const renderChildren = childItems.map(\n (item: { id: string }, index: number) => (\n <ItemInstance id={item.id} key={`${item.id}-${index}`} parentId={id} />\n )\n )\n\n return (\n <Component\n {...processedRest}\n children={renderChildren.length ? renderChildren : undefined}\n className={clsx(processedRest.data?.className)}\n data-wv-id={id}\n data-wv-type={type}\n key={id}\n />\n )\n }\n instance._store.deleted = true\n return null\n})\nItemComponent.displayName = 'WeaverseComponent'\n\nconst ItemInstance = memo(\n ({ id, parentId }: { id: string; parentId: string }) => {\n const instance = useItemInstance(id)\n if (!instance) {\n return <div style={{ display: 'none' }}>Item instance {id} not found</div>\n }\n return (\n <WeaverseItemContext.Provider value={{ parentId, id }}>\n <ItemComponent instance={instance} key={id} />\n </WeaverseItemContext.Provider>\n )\n }\n)\nItemInstance.displayName = 'WeaverseInstanceProvider'\n","import { createContext, useContext } from 'react'\nimport { useSafeExternalStore } from './renderer'\n\n/** What the merchant has selected in Weaverse Studio. */\nexport type StudioSelection = {\n /** ID of the selected page item, or `null`. */\n selectedItemId: string | null\n /**\n * Name of the selected theme settings group, exactly as declared in the\n * schema `group` field, or `null`.\n */\n selectedThemeGroup: string | null\n}\n\nconst NO_SELECTION: StudioSelection = Object.freeze({\n selectedItemId: null,\n selectedThemeGroup: null,\n})\n\n/**\n * Holds the Studio selection for one storefront root. The Studio bridge writes\n * it; themes read it through {@link useStudioSelection}.\n */\nexport class StudioSelectionStore {\n private selection: StudioSelection = NO_SELECTION\n private readonly listeners = new Set<() => void>()\n\n /** Returns the current selection; the same object until it changes. */\n getSnapshot = (): StudioSelection => this.selection\n\n /** Returns the empty selection rendered on the server and at hydration. */\n getServerSnapshot = (): StudioSelection => NO_SELECTION\n\n /**\n * Registers a change listener.\n *\n * @param listener - Called after each selection change.\n * @returns A function that removes the listener.\n */\n subscribe = (listener: () => void) => {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n /**\n * Replaces the selection and notifies listeners when a value changed. An item\n * and a theme group are mutually exclusive, so a selection naming both is\n * rejected and leaves the store unchanged.\n *\n * @param next - The new selection.\n */\n setSelection = (next: StudioSelection) => {\n let { selectedItemId, selectedThemeGroup } = next\n if (selectedItemId !== null && selectedThemeGroup !== null) {\n console.error(\n '[Weaverse] A Studio selection cannot name both an item and a theme group'\n )\n return\n }\n let current = this.selection\n if (\n current.selectedItemId === selectedItemId &&\n current.selectedThemeGroup === selectedThemeGroup\n ) {\n return\n }\n this.selection =\n selectedItemId === null && selectedThemeGroup === null\n ? NO_SELECTION\n : Object.freeze({ selectedItemId, selectedThemeGroup })\n for (let listener of this.listeners) {\n listener()\n }\n }\n}\n\n/** Provides the root's {@link StudioSelectionStore}; set by each adapter. */\nexport let StudioSelectionStoreContext =\n createContext<StudioSelectionStore | null>(null)\n\nconst DETACHED_STORE = new StudioSelectionStore()\n\n/**\n * Returns what the merchant has selected in Weaverse Studio: a page item ID or\n * a theme settings group name, never both. Both are `null` on the storefront,\n * during server rendering and outside a Weaverse root.\n *\n * @returns The current Studio selection.\n *\n * @example\n * ```tsx\n * function NewsletterPopup() {\n * const { selectedThemeGroup } = useStudioSelection()\n * const open = selectedThemeGroup === 'Newsletter Popup'\n * // ...\n * }\n * ```\n */\nexport function useStudioSelection(): StudioSelection {\n let store = useContext(StudioSelectionStoreContext) ?? DETACHED_STORE\n return useSafeExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getServerSnapshot\n )\n}\n"],"mappings":"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"}
@@ -7,5 +7,6 @@ export * from '@weaverse/core';
7
7
  export * from './context';
8
8
  export * from './hooks';
9
9
  export * from './renderer';
10
+ export * from './studio-selection';
10
11
  export * from './types';
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,gBAAgB,CAAA;AAC9B,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,gBAAgB,CAAA;AAC9B,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,YAAY,CAAA;AAC1B,cAAc,oBAAoB,CAAA;AAClC,cAAc,SAAS,CAAA"}
@@ -0,0 +1,57 @@
1
+ /** What the merchant has selected in Weaverse Studio. */
2
+ export type StudioSelection = {
3
+ /** ID of the selected page item, or `null`. */
4
+ selectedItemId: string | null;
5
+ /**
6
+ * Name of the selected theme settings group, exactly as declared in the
7
+ * schema `group` field, or `null`.
8
+ */
9
+ selectedThemeGroup: string | null;
10
+ };
11
+ /**
12
+ * Holds the Studio selection for one storefront root. The Studio bridge writes
13
+ * it; themes read it through {@link useStudioSelection}.
14
+ */
15
+ export declare class StudioSelectionStore {
16
+ private selection;
17
+ private readonly listeners;
18
+ /** Returns the current selection; the same object until it changes. */
19
+ getSnapshot: () => StudioSelection;
20
+ /** Returns the empty selection rendered on the server and at hydration. */
21
+ getServerSnapshot: () => StudioSelection;
22
+ /**
23
+ * Registers a change listener.
24
+ *
25
+ * @param listener - Called after each selection change.
26
+ * @returns A function that removes the listener.
27
+ */
28
+ subscribe: (listener: () => void) => () => void;
29
+ /**
30
+ * Replaces the selection and notifies listeners when a value changed. An item
31
+ * and a theme group are mutually exclusive, so a selection naming both is
32
+ * rejected and leaves the store unchanged.
33
+ *
34
+ * @param next - The new selection.
35
+ */
36
+ setSelection: (next: StudioSelection) => void;
37
+ }
38
+ /** Provides the root's {@link StudioSelectionStore}; set by each adapter. */
39
+ export declare let StudioSelectionStoreContext: import("react").Context<StudioSelectionStore | null>;
40
+ /**
41
+ * Returns what the merchant has selected in Weaverse Studio: a page item ID or
42
+ * a theme settings group name, never both. Both are `null` on the storefront,
43
+ * during server rendering and outside a Weaverse root.
44
+ *
45
+ * @returns The current Studio selection.
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * function NewsletterPopup() {
50
+ * const { selectedThemeGroup } = useStudioSelection()
51
+ * const open = selectedThemeGroup === 'Newsletter Popup'
52
+ * // ...
53
+ * }
54
+ * ```
55
+ */
56
+ export declare function useStudioSelection(): StudioSelection;
57
+ //# sourceMappingURL=studio-selection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"studio-selection.d.ts","sourceRoot":"","sources":["../../src/studio-selection.ts"],"names":[],"mappings":"AAGA,yDAAyD;AACzD,MAAM,MAAM,eAAe,GAAG;IAC5B,+CAA+C;IAC/C,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B;;;OAGG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC,CAAA;AAOD;;;GAGG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAElD,uEAAuE;IACvE,WAAW,QAAO,eAAe,CAAkB;IAEnD,2EAA2E;IAC3E,iBAAiB,QAAO,eAAe,CAAgB;IAEvD;;;;;OAKG;IACH,SAAS,aAAc,MAAM,IAAI,gBAKhC;IAED;;;;;;OAMG;IACH,YAAY,SAAU,eAAe,UAsBpC;CACF;AAED,6EAA6E;AAC7E,eAAO,IAAI,2BAA2B,sDACY,CAAA;AAIlD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAOpD"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@weaverse/react",
3
3
  "author": "Weaverse Team",
4
4
  "description": "React bindings for Weaverse",
5
- "version": "5.24.1",
5
+ "version": "5.25.0",
6
6
  "license": "MIT",
7
7
  "main": "dist/index.js",
8
8
  "typings": "dist/types/index.d.ts",
@@ -30,7 +30,7 @@
30
30
  "typecheck": "tsrs --noEmit"
31
31
  },
32
32
  "dependencies": {
33
- "@weaverse/core": "5.24.1",
33
+ "@weaverse/core": "5.25.0",
34
34
  "clsx": "^2.1.1",
35
35
  "react": ">=19",
36
36
  "react-dom": ">=19"
package/src/index.ts CHANGED
@@ -7,4 +7,5 @@ export * from '@weaverse/core'
7
7
  export * from './context'
8
8
  export * from './hooks'
9
9
  export * from './renderer'
10
+ export * from './studio-selection'
10
11
  export * from './types'
@@ -0,0 +1,108 @@
1
+ import { createContext, useContext } from 'react'
2
+ import { useSafeExternalStore } from './renderer'
3
+
4
+ /** What the merchant has selected in Weaverse Studio. */
5
+ export type StudioSelection = {
6
+ /** ID of the selected page item, or `null`. */
7
+ selectedItemId: string | null
8
+ /**
9
+ * Name of the selected theme settings group, exactly as declared in the
10
+ * schema `group` field, or `null`.
11
+ */
12
+ selectedThemeGroup: string | null
13
+ }
14
+
15
+ const NO_SELECTION: StudioSelection = Object.freeze({
16
+ selectedItemId: null,
17
+ selectedThemeGroup: null,
18
+ })
19
+
20
+ /**
21
+ * Holds the Studio selection for one storefront root. The Studio bridge writes
22
+ * it; themes read it through {@link useStudioSelection}.
23
+ */
24
+ export class StudioSelectionStore {
25
+ private selection: StudioSelection = NO_SELECTION
26
+ private readonly listeners = new Set<() => void>()
27
+
28
+ /** Returns the current selection; the same object until it changes. */
29
+ getSnapshot = (): StudioSelection => this.selection
30
+
31
+ /** Returns the empty selection rendered on the server and at hydration. */
32
+ getServerSnapshot = (): StudioSelection => NO_SELECTION
33
+
34
+ /**
35
+ * Registers a change listener.
36
+ *
37
+ * @param listener - Called after each selection change.
38
+ * @returns A function that removes the listener.
39
+ */
40
+ subscribe = (listener: () => void) => {
41
+ this.listeners.add(listener)
42
+ return () => {
43
+ this.listeners.delete(listener)
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Replaces the selection and notifies listeners when a value changed. An item
49
+ * and a theme group are mutually exclusive, so a selection naming both is
50
+ * rejected and leaves the store unchanged.
51
+ *
52
+ * @param next - The new selection.
53
+ */
54
+ setSelection = (next: StudioSelection) => {
55
+ let { selectedItemId, selectedThemeGroup } = next
56
+ if (selectedItemId !== null && selectedThemeGroup !== null) {
57
+ console.error(
58
+ '[Weaverse] A Studio selection cannot name both an item and a theme group'
59
+ )
60
+ return
61
+ }
62
+ let current = this.selection
63
+ if (
64
+ current.selectedItemId === selectedItemId &&
65
+ current.selectedThemeGroup === selectedThemeGroup
66
+ ) {
67
+ return
68
+ }
69
+ this.selection =
70
+ selectedItemId === null && selectedThemeGroup === null
71
+ ? NO_SELECTION
72
+ : Object.freeze({ selectedItemId, selectedThemeGroup })
73
+ for (let listener of this.listeners) {
74
+ listener()
75
+ }
76
+ }
77
+ }
78
+
79
+ /** Provides the root's {@link StudioSelectionStore}; set by each adapter. */
80
+ export let StudioSelectionStoreContext =
81
+ createContext<StudioSelectionStore | null>(null)
82
+
83
+ const DETACHED_STORE = new StudioSelectionStore()
84
+
85
+ /**
86
+ * Returns what the merchant has selected in Weaverse Studio: a page item ID or
87
+ * a theme settings group name, never both. Both are `null` on the storefront,
88
+ * during server rendering and outside a Weaverse root.
89
+ *
90
+ * @returns The current Studio selection.
91
+ *
92
+ * @example
93
+ * ```tsx
94
+ * function NewsletterPopup() {
95
+ * const { selectedThemeGroup } = useStudioSelection()
96
+ * const open = selectedThemeGroup === 'Newsletter Popup'
97
+ * // ...
98
+ * }
99
+ * ```
100
+ */
101
+ export function useStudioSelection(): StudioSelection {
102
+ let store = useContext(StudioSelectionStoreContext) ?? DETACHED_STORE
103
+ return useSafeExternalStore(
104
+ store.subscribe,
105
+ store.getSnapshot,
106
+ store.getServerSnapshot
107
+ )
108
+ }