UNPKG

@playcanvas/react

Version:

A React renderer for PlayCanvas – build interactive 3D applications using React's declarative paradigm.

167 lines 5.9 kB
export class PathMatcher { caseSensitive; constructor(options = {}) { this.caseSensitive = options.caseSensitive ?? true; } /** * Normalizes a path for comparison */ normalizePath(path) { return this.caseSensitive ? path : path.toLowerCase(); } /** * Checks if a path matches a pattern */ match(pattern, entityPath, entity) { // Check for component filter [componentType] anywhere in the pattern const componentMatch = pattern.match(/\[([^\]]+)\]/); let pathPattern = pattern; let componentType = null; if (componentMatch) { componentType = componentMatch[1]; // Replace the filter from any position and clean up resulting dots pathPattern = pattern .replace(componentMatch[0], '') .replace(/^\.+/, '') // Remove leading dots .replace(/\.+$/, '') // Remove trailing dots .replace(/\.\.+/g, '.'); // Collapse multiple dots to single dot } // If there's a component filter, check it first if (componentType) { if (!entity.c?.[componentType]) { return false; } } // If only component filter (no path), return true if (!pathPattern) { return !!componentType; } // Match path pattern const pathMatch = this.matchPath(pathPattern, entityPath); return pathMatch; } /** * Matches a path against a pattern with wildcards * Handles exact paths, single-level wildcards (*), and multi-level wildcards (**) */ matchPath(pattern, path) { const normalizedPattern = this.normalizePath(pattern); const normalizedPath = this.normalizePath(path); // Special case: pattern is just "**" which matches any path if (normalizedPattern === '**') { return true; } // Handle empty pattern - should not match anything if (normalizedPattern === '') { return false; } // Split pattern into parts and filter out empty segments const patternParts = normalizedPattern.split('.').filter(p => p !== ''); // If all parts were empty (e.g., ".." or "."), return false if (patternParts.length === 0) { return false; } // Build regex string with special handling for ** let regexStr = '^'; for (let i = 0; i < patternParts.length; i++) { const part = patternParts[i]; if (part === '**') { // ** matches zero or more complete segments // Need to handle: A.**.B should match A.B, A.X.B, A.X.Y.B if (i === 0) { // At start: match zero or more segments (each ending with a dot) regexStr += '(?:[^.]+\\.)*'; } else if (i === patternParts.length - 1) { // At end: match zero or more segments with required leading dot regexStr += '(?:\\.(?:[^.]+))*'; } else { // In middle: match zero or more segments, handling dots carefully regexStr += '(?:(?:\\.(?:[^.]+))*\\.)?'; } } else if (part === '*') { // * matches exactly one segment if (i > 0) regexStr += '\\.'; regexStr += '[^.]+'; } else { // Literal part if (i > 0 && patternParts[i - 1] !== '**') { regexStr += '\\.'; } // Escape special regex characters regexStr += part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } } regexStr += '$'; const regex = new RegExp(regexStr); return regex.test(normalizedPath); } /** * Calculates specificity score for a pattern (used for conflict resolution) */ getSpecificity(pattern) { if (typeof pattern === 'function') { return 0; // Predicates have lowest specificity } let score = 0; // Remove component filter from anywhere in the pattern const pathPattern = pattern.replace(/\[([^\]]+)\]/, ''); // Count exact parts (not wildcards) const parts = pathPattern.split('.').filter(p => p !== ''); for (const part of parts) { if (part === '**') { score += 1; } else if (part === '*') { score += 10; } else if (part) { score += 100; } } // Component filter adds specificity if (pattern.includes('[')) { score += 50; } return score; } /** * Finds all entities matching a pattern in a hierarchy */ findMatching(pattern, hierarchyCache) { const matches = []; for (const metadata of hierarchyCache.values()) { if (this.matches(pattern, metadata)) { matches.push(metadata); } } return matches; } /** * Checks if a pattern matches an entity */ matches(pattern, metadata) { if (typeof pattern === 'function') { return pattern(metadata.entity, metadata); } return this.match(pattern, metadata.path, metadata.entity); } /** * Resolves a path relative to a parent path */ resolvePath(parentPath, relativePath) { if (!parentPath) { return relativePath; } return `${parentPath}.${relativePath}`; } } /** * Default path matcher instance */ export const defaultPathMatcher = new PathMatcher(); //# sourceMappingURL=path-matcher.js.map