UNPKG

apple-hig-mcp

Version:

High-performance MCP server providing instant access to Apple's Human Interface Guidelines via hybrid static/dynamic content delivery

746 lines 37.7 kB
/** * MCP Tools implementation for Apple HIG interactive functionality */ export class HIGToolProvider { crawleeService; _cache; resourceProvider; staticContentProvider; constructor(crawleeService, cache, resourceProvider, staticContentProvider) { this.crawleeService = crawleeService; this._cache = cache; this.resourceProvider = resourceProvider; this.staticContentProvider = staticContentProvider; } /** * Search HIG content by keywords/topics with input validation */ async searchGuidelines(args) { // Input validation if (!args || typeof args !== 'object') { throw new Error('Invalid arguments: expected object'); } const { query, platform, category, limit = 10 } = args; // Validate required parameters if (typeof query !== 'string') { throw new Error('Invalid query: must be a string'); } // Handle empty/whitespace queries gracefully if (query.trim().length === 0) { return { results: [], total: 0, query: query.trim(), filters: { platform, category } }; } if (query.length > 100) { throw new Error('Query too long: maximum 100 characters allowed'); } // Validate optional parameters if (platform && !['iOS', 'macOS', 'watchOS', 'tvOS', 'visionOS', 'universal'].includes(platform)) { throw new Error(`Invalid platform: ${platform}. Must be one of: iOS, macOS, watchOS, tvOS, visionOS, universal`); } if (category && ![ 'foundations', 'layout', 'navigation', 'presentation', 'selection-and-input', 'status', 'system-capabilities', 'visual-design', 'icons-and-images', 'color-and-materials', 'typography', 'motion', 'technologies' ].includes(category)) { throw new Error(`Invalid category: ${category}`); } if (typeof limit !== 'number' || limit < 1 || limit > 50) { throw new Error('Invalid limit: must be a number between 1 and 50'); } try { let results = []; // Try static content search first (more reliable, less aggressive timeouts) try { if (this.staticContentProvider && await this.staticContentProvider.isAvailable()) { // Try regular static search with longer timeout for complex searches results = await Promise.race([ this.staticContentProvider.searchContent(query.trim(), platform, category, limit), new Promise((_, reject) => setTimeout(() => reject(new Error('Search timeout')), 8000)) ]); if (process.env.NODE_ENV === 'development') { console.log(`[HIGTools] Using static content search for: "${query}"`); } } } catch (staticError) { if (process.env.NODE_ENV === 'development') { console.warn('[HIGTools] Static search failed or timed out, falling back to keyword search:', staticError); } // Continue to keyword search fallback } // If static search failed or returned no results, try keyword search on static content if (!results || results.length === 0) { try { // Try simple keyword search on static content first if (this.staticContentProvider && await this.staticContentProvider.isAvailable()) { results = await this.staticContentProvider.keywordSearchContent(query.trim(), platform, category, limit); if (process.env.NODE_ENV === 'development') { console.log(`[HIGTools] Using static keyword search for: "${query}"`); } } } catch (keywordError) { if (process.env.NODE_ENV === 'development') { console.warn('[HIGTools] Static keyword search failed, using minimal fallback:', keywordError); } } // Only use hardcoded fallback if static content completely unavailable if (!results || results.length === 0) { results = this.getMinimalFallbackResults(query.trim(), platform, category, limit); if (process.env.NODE_ENV === 'development') { console.log(`[HIGTools] Using minimal fallback search for: "${query}"`); } } } return { results: results.slice(0, limit), total: results.length, query: query.trim(), filters: { platform, category } }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; if (process.env.NODE_ENV === 'development') { console.error('[HIGTools] Search failed:', error); } throw new Error(`Search failed: ${errorMessage}`); } } /** * Minimal fallback search with hardcoded results (last resort only) */ getMinimalFallbackResults(query, platform, category, limit = 10) { const queryLower = query.toLowerCase(); const fallbackData = [ // Buttons & Touch Targets { keywords: ['button', 'btn', 'press', 'tap', 'click'], title: 'Buttons', platform: 'iOS', category: 'visual-design', url: 'https://developer.apple.com/design/human-interface-guidelines/buttons', snippet: 'Buttons initiate app-specific actions, have customizable backgrounds, and can include a title or an icon. Minimum touch target size is 44pt x 44pt.' }, { keywords: ['touch', 'targets', '44pt', 'minimum', 'size', 'accessibility'], title: 'Touch Targets & Accessibility', platform: 'iOS', category: 'foundations', url: 'https://developer.apple.com/design/human-interface-guidelines/accessibility', snippet: 'Interactive elements must be large enough for people to interact with easily. A minimum touch target size of 44pt x 44pt ensures accessibility.' }, // Navigation { keywords: ['navigation', 'nav', 'navigate', 'menu', 'bar'], title: 'Navigation Bars', platform: 'iOS', category: 'navigation', url: 'https://developer.apple.com/design/human-interface-guidelines/navigation-bars', snippet: 'A navigation bar appears at the top of an app screen, enabling navigation through a hierarchy of content.' }, { keywords: ['tab', 'tabs', 'bottom'], title: 'Tab Bars', platform: 'iOS', category: 'navigation', url: 'https://developer.apple.com/design/human-interface-guidelines/tab-bars', snippet: 'A tab bar appears at the bottom of an app screen and provides the ability to quickly switch between different sections of an app.' }, // Layout & Design { keywords: ['layout', 'grid', 'spacing', 'margin'], title: 'Layout', platform: 'universal', category: 'layout', url: 'https://developer.apple.com/design/human-interface-guidelines/layout', snippet: 'A consistent layout that adapts to various devices and contexts makes your app easier to use and helps people feel confident.' }, { keywords: ['color', 'colours', 'theme', 'dark', 'light'], title: 'Color', platform: 'universal', category: 'color-and-materials', url: 'https://developer.apple.com/design/human-interface-guidelines/color', snippet: 'Color can indicate interactivity, impart vitality, and provide visual continuity.' }, { keywords: ['typography', 'text', 'font', 'size'], title: 'Typography', platform: 'universal', category: 'typography', url: 'https://developer.apple.com/design/human-interface-guidelines/typography', snippet: 'Typography can help you clarify a hierarchy of information and make it easy for people to find what they\'re looking for.' }, // Accessibility & Contrast { keywords: ['accessibility', 'a11y', 'voiceover', 'accessible'], title: 'Accessibility', platform: 'universal', category: 'foundations', url: 'https://developer.apple.com/design/human-interface-guidelines/accessibility', snippet: 'People use Apple accessibility features to personalize how they interact with their devices in ways that work for them.' }, { keywords: ['contrast', 'color', 'wcag', 'visibility', 'readability'], title: 'Color Contrast & Accessibility', platform: 'universal', category: 'foundations', url: 'https://developer.apple.com/design/human-interface-guidelines/accessibility', snippet: 'Ensure sufficient color contrast for text and UI elements. Follow WCAG guidelines with minimum 4.5:1 contrast ratio for normal text.' }, // Custom Interface Patterns { keywords: ['custom', 'interface', 'patterns', 'design', 'user', 'expectations'], title: 'Custom Interface Patterns', platform: 'universal', category: 'foundations', url: 'https://developer.apple.com/design/human-interface-guidelines/', snippet: 'When creating custom interfaces, maintain consistency with platform conventions and user expectations to ensure familiarity and usability.' }, { keywords: ['user', 'interface', 'standards', 'guidelines', 'principles'], title: 'User Interface Standards', platform: 'universal', category: 'foundations', url: 'https://developer.apple.com/design/human-interface-guidelines/', snippet: 'Follow established interface standards and design principles to create intuitive, accessible, and consistent user experiences across Apple platforms.' }, // Visual Effects { keywords: ['gradients', 'materials', 'visual', 'effects'], title: 'Materials & Visual Effects', platform: 'universal', category: 'color-and-materials', url: 'https://developer.apple.com/design/human-interface-guidelines/materials', snippet: 'Use system materials and visual effects thoughtfully to create depth and hierarchy while maintaining clarity and performance.' }, // Input & Controls { keywords: ['input', 'field', 'form', 'text'], title: 'Text Fields', platform: 'iOS', category: 'selection-and-input', url: 'https://developer.apple.com/design/human-interface-guidelines/text-fields', snippet: 'A text field is a rectangular area in which people enter or edit small, specific pieces of text.' }, { keywords: ['picker', 'select', 'choose'], title: 'Pickers', platform: 'iOS', category: 'selection-and-input', url: 'https://developer.apple.com/design/human-interface-guidelines/pickers', snippet: 'A picker displays one or more scrollable lists of distinct values that people can choose from.' }, // Platform specific { keywords: ['vision', 'visionos', 'spatial', 'immersive', 'ar', 'vr'], title: 'Designing for visionOS', platform: 'visionOS', category: 'foundations', url: 'https://developer.apple.com/design/human-interface-guidelines/designing-for-visionos', snippet: 'visionOS brings together digital and physical worlds, creating opportunities for new types of immersive experiences.' }, { keywords: ['watch', 'watchos', 'complication', 'crown'], title: 'Designing for watchOS', platform: 'watchOS', category: 'foundations', url: 'https://developer.apple.com/design/human-interface-guidelines/designing-for-watchos', snippet: 'Apple Watch is a highly personal device that people wear on their wrist, making it instantly accessible.' } ]; const results = []; fallbackData.forEach((item, index) => { let relevanceScore = 0; // Check for keyword matches const hasKeywordMatch = item.keywords.some(keyword => queryLower.includes(keyword) || keyword.includes(queryLower)); if (hasKeywordMatch) { relevanceScore = 1.0; } // Check title match if (item.title.toLowerCase().includes(queryLower)) { relevanceScore = Math.max(relevanceScore, 0.8); } // Apply platform filter if (platform && platform !== 'universal' && item.platform !== platform && item.platform !== 'universal') { return; } // Apply category filter if (category && item.category !== category) { return; } if (relevanceScore > 0) { results.push({ id: `fallback-${index}`, title: item.title, url: item.url, platform: item.platform, relevanceScore, snippet: item.snippet, type: 'guideline' }); } }); // Sort by relevance score and return top results return results .sort((a, b) => b.relevanceScore - a.relevanceScore) .slice(0, limit); } /** * Get detailed specifications for a UI component with input validation */ async getComponentSpec(args) { // Input validation if (!args || typeof args !== 'object') { throw new Error('Invalid arguments: expected object'); } const { componentName, platform } = args; // Validate required parameters if (!componentName || typeof componentName !== 'string' || componentName.trim().length === 0) { throw new Error('Invalid componentName: must be a non-empty string'); } if (componentName.length > 50) { throw new Error('Component name too long: maximum 50 characters allowed'); } // Validate optional parameters if (platform && !['iOS', 'macOS', 'watchOS', 'tvOS', 'visionOS', 'universal'].includes(platform)) { throw new Error(`Invalid platform: ${platform}. Must be one of: iOS, macOS, watchOS, tvOS, visionOS, universal`); } try { const trimmedComponentName = componentName.trim(); if (process.env.NODE_ENV === 'development') { console.log(`[HIGTools] Getting component spec for: ${trimmedComponentName} (platform: ${platform || 'any'})`); } // Try to get component from static content first let component = null; if (this.staticContentProvider) { try { component = await this.getComponentFromStaticContent(trimmedComponentName, platform); } catch (error) { console.warn(`Failed to get component from static content: ${error}`); } } // Fall back to predefined components if static content fails if (!component) { component = this.getComponentSpecFallback(trimmedComponentName, platform); } if (!component) { return { component: null, relatedComponents: [], platforms: [], lastUpdated: new Date().toISOString() }; } return { component, relatedComponents: component.guidelines || [], platforms: component.platforms || [], lastUpdated: new Date().toISOString(), liquidGlassUpdates: 'No Liquid Glass updates available' }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; if (process.env.NODE_ENV === 'development') { console.error('[HIGTools] Get component spec failed:', error); } throw new Error(`Failed to get component specification: ${errorMessage}`); } } async getComponentFromStaticContent(componentName, platform) { if (!this.staticContentProvider) { return null; } // Search for the component in static content const searchQuery = componentName.toLowerCase().replace(/\s+/g, ' '); const searchResults = await this.staticContentProvider.searchContent(searchQuery, platform, undefined, 3); if (searchResults.length === 0) { return null; } // Find the best match (highest relevance score) const bestMatch = searchResults[0]; // Get the full content for this component let fullContent = ''; try { const section = await this.staticContentProvider.getSection(bestMatch.id); fullContent = section?.content || bestMatch.snippet; } catch { fullContent = bestMatch.snippet; } // Extract guidelines and examples from content const guidelines = this.extractGuidelines(fullContent); const examples = this.extractExamples(fullContent); const specifications = this.extractSpecifications(fullContent); return { id: bestMatch.id, title: bestMatch.title, description: bestMatch.snippet || `${bestMatch.title} component specifications and guidelines.`, platforms: [bestMatch.platform], url: bestMatch.url, specifications, guidelines, examples, lastUpdated: new Date() }; } extractGuidelines(content) { const guidelines = []; // Look for common guideline patterns const guidelinePatterns = [ /(?:^|\n)\s*[-•]\s*(.+?)(?=\n|$)/gm, /(?:^|\n)\s*\d+\.\s*(.+?)(?=\n|$)/gm, /(?:consider|should|must|avoid|ensure)\s+(.+?)(?:[.!]|$)/gim ]; for (const pattern of guidelinePatterns) { const matches = content.match(pattern); if (matches) { matches.forEach(match => { const cleaned = match.replace(/^[-•\d.\s]+/, '').trim(); if (cleaned.length > 10 && cleaned.length < 200) { guidelines.push(cleaned); } }); } } return guidelines.slice(0, 5); // Return top 5 guidelines } extractExamples(content) { const examples = []; // Look for example patterns const examplePatterns = [ /example[s]?[:\s]+(.+?)(?=\n\n|$)/gim, /for example[,\s]+(.+?)(?=[.!]|$)/gim, /such as[:\s]+(.+?)(?=[.!]|$)/gim ]; for (const pattern of examplePatterns) { const matches = content.match(pattern); if (matches) { matches.forEach(match => { const cleaned = match.replace(/^(examples?[:\s]+|for example[,\s]+|such as[:\s]+)/i, '').trim(); if (cleaned.length > 5 && cleaned.length < 100) { examples.push(cleaned); } }); } } return examples.slice(0, 3); // Return top 3 examples } extractSpecifications(content) { const specs = {}; // Look for measurement patterns const heightMatch = content.match(/height[:\s]+(\d+(?:\.\d+)?)\s*pt/i); if (heightMatch) { specs.height = heightMatch[1] + 'pt'; } const widthMatch = content.match(/width[:\s]+(\d+(?:\.\d+)?)\s*pt/i); if (widthMatch) { specs.width = widthMatch[1] + 'pt'; } const minSizeMatch = content.match(/minimum[:\s]+(\d+(?:\.\d+)?)\s*pt/i); if (minSizeMatch) { specs.minimumSize = minSizeMatch[1] + 'pt'; } // Touch target size is commonly 44pt if (content.toLowerCase().includes('touch target') || content.toLowerCase().includes('44')) { specs.touchTarget = '44pt x 44pt'; } return specs; } /** * Fallback method for component specs to avoid timeouts */ getComponentSpecFallback(componentName, platform) { const componentLower = componentName.toLowerCase(); // Define known components with their specs const knownComponents = { 'button': { id: 'buttons-fallback', title: 'Buttons', description: 'Buttons initiate app-specific actions, have customizable backgrounds, and can include a title or an icon.', platforms: ['iOS', 'macOS', 'watchOS', 'tvOS', 'visionOS'], url: 'https://developer.apple.com/design/human-interface-guidelines/buttons', specifications: { dimensions: { height: '44pt', minWidth: '44pt' } }, guidelines: ['Make buttons easy to identify and predict', 'Size buttons appropriately for their importance', 'Use consistent styling throughout your app'], examples: ['Primary action buttons', 'Secondary action buttons', 'Destructive action buttons'], lastUpdated: new Date() }, 'navigation': { id: 'navigation-fallback', title: 'Navigation Bars', description: 'A navigation bar appears at the top of an app screen, enabling navigation through a hierarchy of content.', platforms: ['iOS', 'macOS', 'watchOS', 'tvOS'], url: 'https://developer.apple.com/design/human-interface-guidelines/navigation-bars', specifications: { dimensions: { height: '44pt' } }, guidelines: ['Use a navigation bar to help people navigate hierarchical screens', 'Show the current location in the navigation hierarchy', 'Use the title area to clarify the current screen'], examples: ['Standard navigation bar', 'Large title navigation bar', 'Search-enabled navigation bar'], lastUpdated: new Date() }, 'tab': { id: 'tabs-fallback', title: 'Tab Bars', description: 'A tab bar appears at the bottom of an app screen and provides the ability to quickly switch between different sections.', platforms: ['iOS'], url: 'https://developer.apple.com/design/human-interface-guidelines/tab-bars', specifications: { dimensions: { height: '49pt' } }, guidelines: ['Use tab bars for peer categories of content', 'Avoid using a tab bar for actions', 'Badge tabs sparingly'], examples: ['Standard tab bar', 'Customizable tab bar', 'Translucent tab bar'], lastUpdated: new Date() }, 'text field': { id: 'text-fields-fallback', title: 'Text Fields', description: 'Text fields let people enter and edit text in a single line or multiple lines.', platforms: ['iOS', 'macOS', 'watchOS', 'tvOS', 'visionOS'], url: 'https://developer.apple.com/design/human-interface-guidelines/text-fields', specifications: { dimensions: { height: '44pt', minHeight: '36pt' }, touchTarget: '44pt x 44pt' }, guidelines: ['Make text fields recognizable and easy to target', 'Use secure text fields for sensitive data', 'Provide clear feedback for validation errors', 'Use appropriate keyboard types for different content'], examples: ['Standard text field', 'Search field', 'Secure text field', 'Multi-line text field'], lastUpdated: new Date() }, 'textfield': { id: 'text-fields-fallback', title: 'Text Fields', description: 'Text fields let people enter and edit text in a single line or multiple lines.', platforms: ['iOS', 'macOS', 'watchOS', 'tvOS', 'visionOS'], url: 'https://developer.apple.com/design/human-interface-guidelines/text-fields', specifications: { dimensions: { height: '44pt', minHeight: '36pt' }, touchTarget: '44pt x 44pt' }, guidelines: ['Make text fields recognizable and easy to target', 'Use secure text fields for sensitive data', 'Provide clear feedback for validation errors', 'Use appropriate keyboard types for different content'], examples: ['Standard text field', 'Search field', 'Secure text field', 'Multi-line text field'], lastUpdated: new Date() } }; // Try exact match first if (knownComponents[componentLower]) { const component = knownComponents[componentLower]; // Filter by platform if specified if (platform && !component.platforms?.includes(platform)) { return null; } return component; } // Try partial matches for (const [key, component] of Object.entries(knownComponents)) { if (componentLower.includes(key) || key.includes(componentLower)) { if (platform && !component.platforms?.includes(platform)) { continue; } return component; } } return null; } /** * Get design tokens for specific components */ async getDesignTokens(args) { const { component, platform, tokenType = 'all' } = args; if (process.env.NODE_ENV === 'development') { console.log(`[HIGTools] Getting design tokens for ${component} on ${platform}`); } const componentLower = component.toLowerCase(); const designTokens = this.getDesignTokenDatabase(componentLower, platform); const tokens = {}; if (tokenType === 'all' || tokenType === 'colors') { tokens.colors = designTokens.colors; } if (tokenType === 'all' || tokenType === 'spacing') { tokens.spacing = designTokens.spacing; } if (tokenType === 'all' || tokenType === 'typography') { tokens.typography = designTokens.typography; } if (tokenType === 'all' || tokenType === 'dimensions') { tokens.dimensions = designTokens.dimensions; } return { component, platform, tokens }; } /** * Get accessibility requirements for specific components */ async getAccessibilityRequirements(args) { const { component, platform } = args; if (process.env.NODE_ENV === 'development') { console.log(`[HIGTools] Getting accessibility requirements for ${component} on ${platform}`); } const componentLower = component.toLowerCase(); const a11yRequirements = this.getAccessibilityDatabase(componentLower, platform); return { component, platform, requirements: a11yRequirements }; } /** * Extract enhanced snippet with more context */ extractEnhancedSnippet(content, query, maxLength = 300) { const queryLower = query.toLowerCase(); const contentLower = content.toLowerCase(); const queryIndex = contentLower.indexOf(queryLower); if (queryIndex === -1) { return content.substring(0, maxLength) + (content.length > maxLength ? '...' : ''); } const start = Math.max(0, queryIndex - 100); const end = Math.min(content.length, start + maxLength); const snippet = content.substring(start, end); return (start > 0 ? '...' : '') + snippet + (end < content.length ? '...' : ''); } /** * Find related components */ async findRelatedComponents(componentName, platform) { const sections = await this.crawleeService.discoverSections(); const platformSections = sections.filter(s => s.platform === platform); // Simple related component finding based on similar titles const related = platformSections .filter(s => s.title.toLowerCase() !== componentName.toLowerCase()) .filter(s => { const titleWords = s.title.toLowerCase().split(/\s+/); const componentWords = componentName.toLowerCase().split(/\s+/); return titleWords.some(word => componentWords.includes(word)); }) .map(s => s.title) .slice(0, 5); return related; } /** * Extract current design system information from content */ extractLiquidGlassInfo(content) { // Look for current design system terms const designSystemMatch = content.match(/(design system|advanced material|enhanced interface)[^.]*\./gi); return designSystemMatch ? designSystemMatch.join(' ') : undefined; } /** * Find common elements across arrays */ findCommonElements(arrays) { if (arrays.length === 0) return []; return arrays[0].filter(item => arrays.every(array => array.includes(item))); } /** * Identify key differences between platforms */ identifyKeyDifferences(platformData) { const differences = []; // Compare specifications platformData.forEach((data, index) => { const otherPlatforms = platformData.filter((_, i) => i !== index); const uniqueGuidelines = data.guidelines.filter((guideline) => !otherPlatforms.some(other => other.guidelines.includes(guideline))); if (uniqueGuidelines.length > 0) { differences.push(`${data.platform} has unique guidelines: ${uniqueGuidelines.slice(0, 3).join(', ')}`); } }); return differences; } /** * Get design token database for components */ getDesignTokenDatabase(component, platform) { const tokens = { colors: {}, spacing: {}, typography: {}, dimensions: {} }; // Platform-specific system colors const systemColors = { iOS: { primary: '#007AFF', secondary: '#5856D6', success: '#34C759', warning: '#FF9500', destructive: '#FF3B30', label: '#000000', secondaryLabel: '#3C3C43', background: '#FFFFFF', secondaryBackground: '#F2F2F7' }, macOS: { primary: '#007AFF', secondary: '#5856D6', success: '#28CD41', warning: '#FF9500', destructive: '#FF3B30', label: '#000000', secondaryLabel: '#808080', background: '#FFFFFF', secondaryBackground: '#F5F5F5' } }; // Component-specific tokens switch (component) { case 'button': tokens.colors = systemColors[platform] || systemColors.iOS; tokens.spacing = { paddingHorizontal: '16pt', paddingVertical: '11pt', marginMinimum: '8pt' }; tokens.typography = { fontSize: '17pt', fontWeight: '600', lineHeight: '22pt' }; tokens.dimensions = { minHeight: '44pt', minWidth: '44pt', cornerRadius: '8pt' }; break; case 'navigation': case 'navigation bar': tokens.colors = { background: systemColors[platform]?.background || '#FFFFFF', tint: systemColors[platform]?.primary || '#007AFF', title: systemColors[platform]?.label || '#000000' }; tokens.spacing = { contentInset: '16pt', titleSpacing: '8pt' }; tokens.typography = { titleFontSize: '17pt', titleFontWeight: '600' }; tokens.dimensions = { height: platform === 'iOS' ? '44pt' : '52pt', maxTitleWidth: '200pt' }; break; case 'tab': case 'tab bar': tokens.colors = { background: systemColors[platform]?.secondaryBackground || '#F2F2F7', selectedTint: systemColors[platform]?.primary || '#007AFF', unselectedTint: systemColors[platform]?.secondaryLabel || '#8E8E93' }; tokens.spacing = { iconSpacing: '4pt', horizontalPadding: '12pt' }; tokens.typography = { labelFontSize: '10pt', labelFontWeight: '400' }; tokens.dimensions = { height: '49pt', iconSize: '25pt', maxTabs: '5' }; break; default: // Generic component tokens tokens.colors = systemColors[platform] || systemColors.iOS; tokens.spacing = { padding: '16pt', margin: '8pt' }; tokens.typography = { fontSize: '17pt', fontWeight: '400' }; tokens.dimensions = { minHeight: '44pt' }; } return tokens; } /** * Get accessibility requirements database */ getAccessibilityDatabase(component, _platform) { const baseRequirements = { minimumTouchTarget: '44pt x 44pt', contrastRatio: '4.5:1 (WCAG AA)', wcagCompliance: 'WCAG 2.1 AA', voiceOverSupport: ['Accessible label', 'Accessible hint', 'Accessible value'], keyboardNavigation: ['Tab navigation', 'Return key activation'], additionalGuidelines: [] }; switch (component) { case 'button': return { ...baseRequirements, voiceOverSupport: [ 'Clear button label describing action', 'Button trait for VoiceOver', 'State changes announced (enabled/disabled)' ], keyboardNavigation: [ 'Tab order follows reading order', 'Space bar or Return key activation', 'Focus indicator clearly visible' ], additionalGuidelines: [ 'Use descriptive labels, not just "tap" or "click"', 'Ensure sufficient spacing between buttons', 'Provide haptic feedback on supported devices' ] }; case 'navigation': case 'navigation bar': return { ...baseRequirements, minimumTouchTarget: '44pt x 44pt for interactive elements', voiceOverSupport: [ 'Navigation bar trait', 'Clear title announcement', 'Back button with destination context' ], keyboardNavigation: [ 'Tab navigation through interactive elements', 'Escape key for back navigation (macOS)', 'Command+[ for back navigation (macOS)' ], additionalGuidelines: [ 'Keep navigation titles concise and descriptive', 'Ensure back button context is clear', 'Use navigation landmarks for screen readers' ] }; case 'tab': case 'tab bar': return { ...baseRequirements, voiceOverSupport: [ 'Tab bar trait', 'Selected state clearly announced', 'Tab count and position information' ], keyboardNavigation: [ 'Arrow key navigation between tabs', 'Return/Space key for tab selection', 'Control+Tab for tab switching' ], additionalGuidelines: [ 'Use clear, distinct tab labels', 'Ensure selected state is visually obvious', 'Badge numbers should be announced by VoiceOver' ] }; default: return { ...baseRequirements, additionalGuidelines: [ 'Follow platform-specific accessibility guidelines', 'Test with VoiceOver and other assistive technologies', 'Ensure content is accessible in all interface modes' ] }; } } } //# sourceMappingURL=tools.js.map