UNPKG

@soleil-se/app-util

Version:

Utility functions for WebApps, RESTApps and Widgets in Sitevision.

112 lines (95 loc) 3.9 kB
const charOrder = 'abcdefghijklmnopqrstuvwxyzåäæöø'; function normalizeChar(char) { // Remove accents from unknown characters (é → e, ñ → n) return char.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); } function getCharInfo(char) { const lowerChar = char.toLowerCase(); let index = charOrder.indexOf(lowerChar); let isAccented = false; // If not found, try normalized version if (index === -1) { const normalized = normalizeChar(lowerChar); index = charOrder.indexOf(normalized); if (index !== -1) { isAccented = true; } else { // Completely unknown character index = charOrder.length + char.charCodeAt(0); } } return { baseIndex: index, isAccented, isLower: char === char.toLowerCase() && char !== char.toUpperCase(), accentCode: isAccented ? char.normalize('NFD').charCodeAt(1) || 0 : 0, }; } /** * Compares two strings in a localized manner, taking into account special characters. * The order is defined as: a-z å ä æ ö ø (case-insensitive, with lowercase before uppercase). * Characters not in this set are compared based on their Unicode code points after removing accents. * @param {string} a - The first string to compare. * @param {string} b - The second string to compare. * @returns {number} Negative if a < b, positive if a > b, zero if equal. */ export function localizedCompare(a, b) { // Handle null/undefined if (a == null) return b == null ? 0 : -1; if (b == null) return 1; // Ensure strings const strA = String(a); const strB = String(b); const minLength = Math.min(strA.length, strB.length); for (let i = 0; i < minLength; i += 1) { const infoA = getCharInfo(strA[i]); const infoB = getCharInfo(strB[i]); // 1. Compare base character (case and accent insensitive) if (infoA.baseIndex !== infoB.baseIndex) { return infoA.baseIndex - infoB.baseIndex; } // 2. Non-accented before accented (e.g., 'e' < 'é') if (infoA.isAccented !== infoB.isAccented) { return infoA.isAccented ? 1 : -1; } // 3. If both accented, compare accent types (é vs è) if (infoA.isAccented && infoA.accentCode !== infoB.accentCode) { return infoA.accentCode - infoB.accentCode; } // 4. Lowercase before uppercase (e.g., 'e' < 'E') if (infoA.isLower !== infoB.isLower) { return infoA.isLower ? -1 : 1; } } return strA.length - strB.length; } /** * Creates a comparator function for sorting objects by a specific property or properties. * The property values are compared using localized string comparison. * When given an array of properties, sorts by the first property, then by the second if equal, etc. * Each property can be a string or an object with a `key` and optional `order` ('asc' | 'desc', defaults to 'asc'). * @param {string|{key: string, order?: 'asc'|'desc'}|Array<string|{key: string, order?: 'asc'|'desc'}>} prop - The property name(s) to compare by. * @returns {(a: Object, b: Object) => number} A comparator function that accepts two objects and returns their sort order. * @example * arr.sort(localizedCompareBy('title')) * arr.sort(localizedCompareBy(['lastName', 'firstName'])) * arr.sort(localizedCompareBy({ key: 'firstName', order: 'desc' })) * arr.sort(localizedCompareBy([{ key: 'lastName', order: 'asc' }, { key: 'firstName', order: 'desc' }])) */ export function localizedCompareBy(prop) { const props = (Array.isArray(prop) ? prop : [prop]) .map((p) => (typeof p === 'string' ? { key: p, order: 'asc' } : { key: p.key, order: p.order ?? 'asc' } )); return (a, b) => { for (let i = 0; i < props.length; i += 1) { const { key, order } = props[i]; const result = localizedCompare(a[key], b[key]); if (result !== 0) { return order === 'desc' ? -result : result; } } return 0; }; }