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
JavaScript
/**
* 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)
};
}