UNPKG

claritykit-svelte

Version:

A comprehensive Svelte component library focused on accessibility, ADHD-optimized design, developer experience, and full SSR compatibility

376 lines (375 loc) 12.8 kB
/** * Apply filters to data */ export function applyFilters(data, filters, customFilter) { if (filters.length === 0) return data; return data.filter(item => { // Group filters by groupLogic const orGroups = []; let currentGroup = []; filters.forEach((filter, index) => { currentGroup.push(filter); if (index === filters.length - 1 || filters[index + 1]?.groupLogic === 'OR') { orGroups.push([...currentGroup]); currentGroup = []; } }); // At least one OR group must pass return orGroups.some(group => // All filters in an AND group must pass group.every(filter => { if (customFilter) { const customResult = customFilter(item, filter); if (customResult !== undefined) return customResult; } return evaluateFilter(item, filter); })); }); } /** * Evaluate a single filter condition */ function evaluateFilter(item, filter) { const value = getNestedValue(item, filter.field); const filterValue = filter.value; const operator = filter.operator; switch (operator) { case 'equals': return value === filterValue; case 'not_equals': return value !== filterValue; case 'contains': return String(value).toLowerCase().includes(String(filterValue).toLowerCase()); case 'not_contains': return !String(value).toLowerCase().includes(String(filterValue).toLowerCase()); case 'starts_with': return String(value).toLowerCase().startsWith(String(filterValue).toLowerCase()); case 'ends_with': return String(value).toLowerCase().endsWith(String(filterValue).toLowerCase()); case 'greater_than': return Number(value) > Number(filterValue); case 'less_than': return Number(value) < Number(filterValue); case 'greater_than_or_equal': return Number(value) >= Number(filterValue); case 'less_than_or_equal': return Number(value) <= Number(filterValue); case 'between': if (Array.isArray(filterValue) && filterValue.length === 2) { const numValue = Number(value); return numValue >= Number(filterValue[0]) && numValue <= Number(filterValue[1]); } return false; case 'in': return Array.isArray(filterValue) ? filterValue.includes(value) : false; case 'not_in': return Array.isArray(filterValue) ? !filterValue.includes(value) : true; case 'is_null': return value === null || value === undefined; case 'is_not_null': return value !== null && value !== undefined; case 'is_empty': return value === '' || (Array.isArray(value) && value.length === 0); case 'is_not_empty': return value !== '' && (!Array.isArray(value) || value.length > 0); case 'before': return new Date(value) < new Date(filterValue); case 'after': return new Date(value) > new Date(filterValue); case 'on': const valueDate = new Date(value); const filterDate = new Date(filterValue); return valueDate.toDateString() === filterDate.toDateString(); default: return true; } } /** * Apply sorting to data */ export function applySorting(data, sort) { return [...data].sort((a, b) => { const aValue = getNestedValue(a, sort.field); const bValue = getNestedValue(b, sort.field); // Handle null values if (aValue === null || aValue === undefined) { return sort.nullsFirst ? -1 : 1; } if (bValue === null || bValue === undefined) { return sort.nullsFirst ? 1 : -1; } // Compare values let comparison = 0; if (typeof aValue === 'string' && typeof bValue === 'string') { comparison = aValue.localeCompare(bValue, undefined, { numeric: true }); } else if (aValue instanceof Date && bValue instanceof Date) { comparison = aValue.getTime() - bValue.getTime(); } else { comparison = aValue < bValue ? -1 : aValue > bValue ? 1 : 0; } // Apply sort direction return sort.direction === 'desc' ? -comparison : comparison; }); } /** * Apply multiple sort configurations */ export function applyMultiSort(data, sorts) { if (sorts.length === 0) return data; // Sort by priority (lower priority first) const sortedConfigs = [...sorts].sort((a, b) => (a.priority || 0) - (b.priority || 0)); return [...data].sort((a, b) => { for (const sort of sortedConfigs) { const aValue = getNestedValue(a, sort.field); const bValue = getNestedValue(b, sort.field); // Handle null values if (aValue === null || aValue === undefined) { if (bValue === null || bValue === undefined) continue; return sort.nullsFirst ? -1 : 1; } if (bValue === null || bValue === undefined) { return sort.nullsFirst ? 1 : -1; } // Compare values let comparison = 0; if (typeof aValue === 'string' && typeof bValue === 'string') { comparison = aValue.localeCompare(bValue, undefined, { numeric: true }); } else if (aValue instanceof Date && bValue instanceof Date) { comparison = aValue.getTime() - bValue.getTime(); } else { comparison = aValue < bValue ? -1 : aValue > bValue ? 1 : 0; } // If values are different, return comparison if (comparison !== 0) { return sort.direction === 'desc' ? -comparison : comparison; } // If values are equal, continue to next sort field } return 0; }); } /** * Get nested value from object using dot notation */ export function getNestedValue(obj, path) { if (!path) return obj; const keys = path.split('.'); let value = obj; for (const key of keys) { if (value === null || value === undefined) return null; // Handle array index notation like items[0] const arrayMatch = key.match(/^(.+)\[(\d+)\]$/); if (arrayMatch) { const [, arrayKey, index] = arrayMatch; value = value[arrayKey]?.[parseInt(index)]; } else { value = value[key]; } } return value; } /** * Set nested value in object using dot notation */ export function setNestedValue(obj, path, value) { const keys = path.split('.'); const lastKey = keys.pop(); if (!lastKey) return; let current = obj; for (const key of keys) { if (!current[key]) { current[key] = {}; } current = current[key]; } current[lastKey] = value; } /** * Group data by a field */ export function groupBy(data, field) { return data.reduce((groups, item) => { const key = String(getNestedValue(item, field) || 'Uncategorized'); if (!groups[key]) { groups[key] = []; } groups[key].push(item); return groups; }, {}); } /** * Calculate aggregate values */ export function aggregate(data, field, operation) { if (data.length === 0) return 0; if (operation === 'count') { return data.length; } const values = data .map(item => getNestedValue(item, field)) .filter(val => val !== null && val !== undefined && !isNaN(Number(val))) .map(Number); if (values.length === 0) return 0; switch (operation) { case 'sum': return values.reduce((sum, val) => sum + val, 0); case 'avg': return values.reduce((sum, val) => sum + val, 0) / values.length; case 'min': return Math.min(...values); case 'max': return Math.max(...values); default: return 0; } } /** * Search data across multiple fields */ export function searchData(data, query, fields) { if (!query || query.trim() === '') return data; const lowerQuery = query.toLowerCase(); return data.filter(item => { return fields.some(field => { const value = getNestedValue(item, field); if (value === null || value === undefined) return false; return String(value).toLowerCase().includes(lowerQuery); }); }); } /** * Paginate data */ export function paginate(data, page, pageSize) { const totalItems = data.length; const totalPages = Math.ceil(totalItems / pageSize); const currentPage = Math.min(Math.max(1, page), totalPages); const startIndex = (currentPage - 1) * pageSize; const endIndex = startIndex + pageSize; return { items: data.slice(startIndex, endIndex), totalItems, totalPages, currentPage }; } /** * Export data to CSV */ export function exportToCSV(data, columns, filename = 'export.csv') { if (data.length === 0) return; // Build CSV content const headers = columns.join(','); const rows = data.map(item => { return columns.map(col => { const value = getNestedValue(item, col); // Escape values containing commas or quotes if (value && (String(value).includes(',') || String(value).includes('"'))) { return `"${String(value).replace(/"/g, '""')}"`; } return value ?? ''; }).join(','); }); const csv = [headers, ...rows].join('\n'); // Create download link const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); link.setAttribute('href', url); link.setAttribute('download', filename); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } /** * Export data to JSON */ export function exportToJSON(data, filename = 'export.json') { const json = JSON.stringify(data, null, 2); const blob = new Blob([json], { type: 'application/json' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); link.setAttribute('href', url); link.setAttribute('download', filename); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } /** * Calculate ADHD-specific metrics */ export function calculateADHDMetrics(task) { let urgency_score = 0; let cognitive_load = 0; let task_complexity = 0; // Calculate urgency based on due date if (task.due_date) { const daysUntilDue = Math.floor((new Date(task.due_date).getTime() - Date.now()) / (1000 * 60 * 60 * 24)); if (daysUntilDue < 0) urgency_score = 100; // Overdue else if (daysUntilDue === 0) urgency_score = 90; // Due today else if (daysUntilDue === 1) urgency_score = 75; // Due tomorrow else if (daysUntilDue <= 3) urgency_score = 60; // Due this week else if (daysUntilDue <= 7) urgency_score = 40; // Due next week else urgency_score = 20; // Due later } // Add priority to urgency if (task.priority) { urgency_score = Math.min(100, urgency_score + (task.priority * 10)); } // Calculate cognitive load based on various factors if (task.subtasks?.length > 0) { cognitive_load += Math.min(30, task.subtasks.length * 5); } if (task.dependencies?.length > 0) { cognitive_load += Math.min(20, task.dependencies.length * 5); } if (task.estimated_hours > 4) { cognitive_load += 20; } else if (task.estimated_hours > 2) { cognitive_load += 10; } // Calculate task complexity if (task.description?.length > 500) { task_complexity += 20; } if (task.tags?.length > 3) { task_complexity += 10; } if (task.attachments?.length > 0) { task_complexity += 10; } if (task.comments?.length > 5) { task_complexity += 15; } return { urgency_score: Math.min(100, urgency_score), cognitive_load: Math.min(100, cognitive_load), task_complexity: Math.min(100, task_complexity) }; }