@soleil-se/app-util 5.12.2 → 5.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/client/svelte/5/index.d.ts +8 -8
- package/client/svelte/5/index.js +5 -5
- package/common/index.d.ts +3 -0
- package/common/index.js +4 -1
- package/common/localized-compare/index.d.ts +20 -0
- package/common/localized-compare/index.js +103 -0
- package/package.json +2 -2
- package/server/svelte/5/index.d.ts +4 -4
- package/server/svelte/5/index.js +3 -3
- package/common/server/index.d.ts +0 -6
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ All notable changes to this project will be documented in this file.
|
|
|
7
7
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
8
8
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
9
9
|
|
|
10
|
+
## [5.13.0] - 2026-01-16
|
|
11
|
+
|
|
12
|
+
- Add comparator functions `localizedCompare` and `localizedCompareBy` to sort strings and objects
|
|
13
|
+
by a specific property using localized string comparison since String.localeCompare does not
|
|
14
|
+
work properly with nordic characters in Rhino.
|
|
15
|
+
|
|
16
|
+
## [5.12.3] - 2025-12-03
|
|
17
|
+
|
|
18
|
+
- Even better type definitions for Svelte render functions.
|
|
19
|
+
|
|
10
20
|
## [5.12.2] - 2025-12-03
|
|
11
21
|
|
|
12
22
|
- Better type definitions for Svelte render functions.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/// <reference types="svelte" />
|
|
2
2
|
/**
|
|
3
|
-
* @template {
|
|
3
|
+
* @template {import('svelte').Component<any, any>} TComponent
|
|
4
4
|
* @typedef {object} RenderSettings
|
|
5
5
|
* @property {HTMLElement} [target] Target where app should be mounted.
|
|
6
|
-
* @property {
|
|
6
|
+
* @property {import('svelte').ComponentProps<TComponent>} [props] Root component props.
|
|
7
7
|
* @property {boolean} [hydrate=target.hasChildNodes()] Instructs Svelte to upgrade existing DOM
|
|
8
8
|
* (usually from server-side rendering) rather than creating new elements. By default the app will
|
|
9
9
|
* hydrate when the target has any child nodes.
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
*/
|
|
13
13
|
/**
|
|
14
14
|
* Renders a client side Svelte application.
|
|
15
|
-
* @template {
|
|
16
|
-
* @param {
|
|
17
|
-
* @param {RenderSettings<
|
|
15
|
+
* @template {import('svelte').Component<any, any>} TComponent
|
|
16
|
+
* @param {TComponent} App Svelte app root component.
|
|
17
|
+
* @param {RenderSettings<TComponent>} [settings={}] Settings object.
|
|
18
18
|
*/
|
|
19
|
-
export function render<
|
|
20
|
-
export type RenderSettings<
|
|
19
|
+
export function render<TComponent extends import("svelte").Component<any, any, string>>(App: TComponent, { target, props, hydrate, intro, }?: RenderSettings<TComponent>): void;
|
|
20
|
+
export type RenderSettings<TComponent extends import("svelte").Component<any, any, string>> = {
|
|
21
21
|
/**
|
|
22
22
|
* Target where app should be mounted.
|
|
23
23
|
*/
|
|
@@ -25,7 +25,7 @@ export type RenderSettings<TProps extends Record<string, unknown>> = {
|
|
|
25
25
|
/**
|
|
26
26
|
* Root component props.
|
|
27
27
|
*/
|
|
28
|
-
props?:
|
|
28
|
+
props?: import('svelte').ComponentProps<TComponent>;
|
|
29
29
|
/**
|
|
30
30
|
* Instructs Svelte to upgrade existing DOM
|
|
31
31
|
* (usually from server-side rendering) rather than creating new elements. By default the app will
|
package/client/svelte/5/index.js
CHANGED
|
@@ -4,10 +4,10 @@ import { mount as svelteMount, hydrate as svelteHydrate } from 'svelte';
|
|
|
4
4
|
import { setAppProps } from '../../../common';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* @template {
|
|
7
|
+
* @template {import('svelte').Component<any, any>} TComponent
|
|
8
8
|
* @typedef {object} RenderSettings
|
|
9
9
|
* @property {HTMLElement} [target] Target where app should be mounted.
|
|
10
|
-
* @property {
|
|
10
|
+
* @property {import('svelte').ComponentProps<TComponent>} [props] Root component props.
|
|
11
11
|
* @property {boolean} [hydrate=target.hasChildNodes()] Instructs Svelte to upgrade existing DOM
|
|
12
12
|
* (usually from server-side rendering) rather than creating new elements. By default the app will
|
|
13
13
|
* hydrate when the target has any child nodes.
|
|
@@ -17,9 +17,9 @@ import { setAppProps } from '../../../common';
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Renders a client side Svelte application.
|
|
20
|
-
* @template {
|
|
21
|
-
* @param {
|
|
22
|
-
* @param {RenderSettings<
|
|
20
|
+
* @template {import('svelte').Component<any, any>} TComponent
|
|
21
|
+
* @param {TComponent} App Svelte app root component.
|
|
22
|
+
* @param {RenderSettings<TComponent>} [settings={}] Settings object.
|
|
23
23
|
*/
|
|
24
24
|
export function render(App, {
|
|
25
25
|
target,
|
package/common/index.d.ts
CHANGED
|
@@ -79,3 +79,6 @@ export const isOffline: boolean;
|
|
|
79
79
|
* @constant {boolean}
|
|
80
80
|
*/
|
|
81
81
|
export const isOnline: boolean;
|
|
82
|
+
import { localizedCompare } from "./localized-compare";
|
|
83
|
+
import { localizedCompareBy } from "./localized-compare";
|
|
84
|
+
export { localizedCompare, localizedCompareBy };
|
package/common/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import app from '@sitevision/api/common/app';
|
|
|
3
3
|
|
|
4
4
|
import { nativeRequire } from '../server';
|
|
5
5
|
import getLegacyRouteUri from './legacy/getRouteUri';
|
|
6
|
+
import { localizedCompare, localizedCompareBy } from './localized-compare';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Regex for selecting leading slashes
|
|
@@ -150,7 +151,7 @@ export function getRouteUri(route = '', params = undefined) {
|
|
|
150
151
|
*/
|
|
151
152
|
export function getViewUri(route = '', params = undefined) {
|
|
152
153
|
if (!router?.getUrl) {
|
|
153
|
-
console.warn('[@soleil-
|
|
154
|
+
console.warn('[@soleil-se/app-util] getViewUri requires router.getUrl support.');
|
|
154
155
|
return undefined;
|
|
155
156
|
}
|
|
156
157
|
|
|
@@ -183,3 +184,5 @@ export function setAppProps(data) {
|
|
|
183
184
|
export function getAppProps(key) {
|
|
184
185
|
return key ? appProps[key] : appProps;
|
|
185
186
|
}
|
|
187
|
+
|
|
188
|
+
export { localizedCompare, localizedCompareBy };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compares two strings in a localized manner, taking into account special characters.
|
|
3
|
+
* The order is defined as: a-z å ä æ ö ø (case-insensitive, with lowercase before uppercase).
|
|
4
|
+
* Characters not in this set are compared based on their Unicode code points after removing accents.
|
|
5
|
+
* @param {string} a - The first string to compare.
|
|
6
|
+
* @param {string} b - The second string to compare.
|
|
7
|
+
* @returns {number} Negative if a < b, positive if a > b, zero if equal.
|
|
8
|
+
*/
|
|
9
|
+
export function localizedCompare(a: string, b: string): number;
|
|
10
|
+
/**
|
|
11
|
+
* Creates a comparator function for sorting objects by a specific property or properties.
|
|
12
|
+
* The property values are compared using localized string comparison.
|
|
13
|
+
* When given an array of properties, sorts by the first property, then by the second if equal, etc.
|
|
14
|
+
* @param {string|string[]} prop - The property name(s) to compare by.
|
|
15
|
+
* @returns {(a: Object, b: Object) => number} A comparator function that accepts two objects and returns their sort order.
|
|
16
|
+
* @example
|
|
17
|
+
* arr.sort(localizedCompareBy('title'))
|
|
18
|
+
* arr.sort(localizedCompareBy(['lastName', 'firstName']))
|
|
19
|
+
*/
|
|
20
|
+
export function localizedCompareBy(prop: string | string[]): (a: any, b: any) => number;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const charOrder = 'abcdefghijklmnopqrstuvwxyzåäæöø';
|
|
2
|
+
|
|
3
|
+
function normalizeChar(char) {
|
|
4
|
+
// Remove accents from unknown characters (é → e, ñ → n)
|
|
5
|
+
return char.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function getCharInfo(char) {
|
|
9
|
+
const lowerChar = char.toLowerCase();
|
|
10
|
+
let index = charOrder.indexOf(lowerChar);
|
|
11
|
+
let isAccented = false;
|
|
12
|
+
|
|
13
|
+
// If not found, try normalized version
|
|
14
|
+
if (index === -1) {
|
|
15
|
+
const normalized = normalizeChar(lowerChar);
|
|
16
|
+
index = charOrder.indexOf(normalized);
|
|
17
|
+
|
|
18
|
+
if (index !== -1) {
|
|
19
|
+
isAccented = true;
|
|
20
|
+
} else {
|
|
21
|
+
// Completely unknown character
|
|
22
|
+
index = charOrder.length + char.charCodeAt(0);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
baseIndex: index,
|
|
28
|
+
isAccented,
|
|
29
|
+
isLower: char === char.toLowerCase() && char !== char.toUpperCase(),
|
|
30
|
+
accentCode: isAccented ? char.normalize('NFD').charCodeAt(1) || 0 : 0,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Compares two strings in a localized manner, taking into account special characters.
|
|
36
|
+
* The order is defined as: a-z å ä æ ö ø (case-insensitive, with lowercase before uppercase).
|
|
37
|
+
* Characters not in this set are compared based on their Unicode code points after removing accents.
|
|
38
|
+
* @param {string} a - The first string to compare.
|
|
39
|
+
* @param {string} b - The second string to compare.
|
|
40
|
+
* @returns {number} Negative if a < b, positive if a > b, zero if equal.
|
|
41
|
+
*/
|
|
42
|
+
export function localizedCompare(a, b) {
|
|
43
|
+
// Handle null/undefined
|
|
44
|
+
if (a == null) return b == null ? 0 : -1;
|
|
45
|
+
if (b == null) return 1;
|
|
46
|
+
|
|
47
|
+
// Ensure strings
|
|
48
|
+
const strA = String(a);
|
|
49
|
+
const strB = String(b);
|
|
50
|
+
|
|
51
|
+
const minLength = Math.min(strA.length, strB.length);
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < minLength; i += 1) {
|
|
54
|
+
const infoA = getCharInfo(strA[i]);
|
|
55
|
+
const infoB = getCharInfo(strB[i]);
|
|
56
|
+
|
|
57
|
+
// 1. Compare base character (case and accent insensitive)
|
|
58
|
+
if (infoA.baseIndex !== infoB.baseIndex) {
|
|
59
|
+
return infoA.baseIndex - infoB.baseIndex;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 2. Non-accented before accented (e.g., 'e' < 'é')
|
|
63
|
+
if (infoA.isAccented !== infoB.isAccented) {
|
|
64
|
+
return infoA.isAccented ? 1 : -1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 3. If both accented, compare accent types (é vs è)
|
|
68
|
+
if (infoA.isAccented && infoA.accentCode !== infoB.accentCode) {
|
|
69
|
+
return infoA.accentCode - infoB.accentCode;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 4. Lowercase before uppercase (e.g., 'e' < 'E')
|
|
73
|
+
if (infoA.isLower !== infoB.isLower) {
|
|
74
|
+
return infoA.isLower ? -1 : 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return strA.length - strB.length;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Creates a comparator function for sorting objects by a specific property or properties.
|
|
83
|
+
* The property values are compared using localized string comparison.
|
|
84
|
+
* When given an array of properties, sorts by the first property, then by the second if equal, etc.
|
|
85
|
+
* @param {string|string[]} prop - The property name(s) to compare by.
|
|
86
|
+
* @returns {(a: Object, b: Object) => number} A comparator function that accepts two objects and returns their sort order.
|
|
87
|
+
* @example
|
|
88
|
+
* arr.sort(localizedCompareBy('title'))
|
|
89
|
+
* arr.sort(localizedCompareBy(['lastName', 'firstName']))
|
|
90
|
+
*/
|
|
91
|
+
export function localizedCompareBy(prop) {
|
|
92
|
+
const props = Array.isArray(prop) ? prop : [prop];
|
|
93
|
+
|
|
94
|
+
return (a, b) => {
|
|
95
|
+
for (let i = 0; i < props.length; i += 1) {
|
|
96
|
+
const result = localizedCompare(a[props[i]], b[props[i]]);
|
|
97
|
+
if (result !== 0) {
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return 0;
|
|
102
|
+
};
|
|
103
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soleil-se/app-util",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.13.0",
|
|
4
4
|
"description": "Utility functions for WebApps, RESTApps and Widgets in Sitevision.",
|
|
5
5
|
"main": "./common/index.js",
|
|
6
6
|
"author": "Soleil AB",
|
|
@@ -25,5 +25,5 @@
|
|
|
25
25
|
"scripts": {
|
|
26
26
|
"create-type-definitions": "node ../../utils/createTypeDefinitions.js ./common/index.js ./client/index.js ./client/svelte/index.js ./client/svelte/3/index.js ./client/svelte/4/index.js ./client/svelte/5/index.js ./server/index.js ./server/svelte/index.js ./server/svelte/3/index.js ./server/svelte/4/index.js ./server/svelte/5/index.js ./server/app-data/index.js ./server/global-app-data/index.js"
|
|
27
27
|
},
|
|
28
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "3c3ef92d604eeed89887a94964edf2146e2a746a"
|
|
29
29
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/// <reference types="svelte" />
|
|
2
2
|
/**
|
|
3
3
|
* Returns HTML for a server rendered Svelte app.
|
|
4
|
-
* @template {
|
|
5
|
-
* @param {
|
|
6
|
-
* @param {
|
|
4
|
+
* @template {import('svelte').Component<any, any>} TComponent
|
|
5
|
+
* @param {TComponent} App Svelte component that is root of app.
|
|
6
|
+
* @param {import('svelte').ComponentProps<TComponent>} props Props passed to root component.
|
|
7
7
|
* @return {string} HTML for the server rendered app.
|
|
8
8
|
*/
|
|
9
|
-
export function render<
|
|
9
|
+
export function render<TComponent extends import("svelte").Component<any, any, string>>(App: TComponent, props: import("svelte").ComponentProps<TComponent>): string;
|
package/server/svelte/5/index.js
CHANGED
|
@@ -3,9 +3,9 @@ import { appId, setAppProps } from '../../../common';
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Returns HTML for a server rendered Svelte app.
|
|
6
|
-
* @template {
|
|
7
|
-
* @param {
|
|
8
|
-
* @param {
|
|
6
|
+
* @template {import('svelte').Component<any, any>} TComponent
|
|
7
|
+
* @param {TComponent} App Svelte component that is root of app.
|
|
8
|
+
* @param {import('svelte').ComponentProps<TComponent>} props Props passed to root component.
|
|
9
9
|
* @return {string} HTML for the server rendered app.
|
|
10
10
|
*/
|
|
11
11
|
export function render(App, props) {
|