@navikt/ds-react 8.5.0 → 8.5.2
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/cjs/data/table/helpers/table-grid-nav.d.ts +9 -15
- package/cjs/data/table/helpers/table-grid-nav.js +18 -25
- package/cjs/data/table/helpers/table-grid-nav.js.map +1 -1
- package/cjs/data/table/helpers/table-keyboard.d.ts +1 -1
- package/cjs/data/table/helpers/table-keyboard.js +1 -6
- package/cjs/data/table/helpers/table-keyboard.js.map +1 -1
- package/cjs/data/table/root/DataTableRoot.d.ts +41 -4
- package/cjs/data/table/root/DataTableRoot.js +10 -6
- package/cjs/data/table/root/DataTableRoot.js.map +1 -1
- package/cjs/data/table/root/useTableKeyboardNav.d.ts +1 -1
- package/cjs/data/table/root/useTableKeyboardNav.js +32 -19
- package/cjs/data/table/root/useTableKeyboardNav.js.map +1 -1
- package/cjs/data/table/td/DataTableTd.d.ts +5 -4
- package/cjs/data/table/td/DataTableTd.js +2 -2
- package/cjs/data/table/td/DataTableTd.js.map +1 -1
- package/cjs/data/token-filter/AutoSuggest.d.ts +9 -0
- package/cjs/data/token-filter/AutoSuggest.js +56 -0
- package/cjs/data/token-filter/AutoSuggest.js.map +1 -0
- package/cjs/data/token-filter/AutoSuggest.types.d.ts +12 -0
- package/cjs/data/token-filter/AutoSuggest.types.js +3 -0
- package/cjs/data/token-filter/AutoSuggest.types.js.map +1 -0
- package/cjs/data/token-filter/TokenFilter.d.ts +11 -0
- package/cjs/data/token-filter/TokenFilter.js +102 -0
- package/cjs/data/token-filter/TokenFilter.js.map +1 -0
- package/cjs/data/token-filter/TokenFilter.types.d.ts +52 -0
- package/cjs/data/token-filter/TokenFilter.types.js +3 -0
- package/cjs/data/token-filter/TokenFilter.types.js.map +1 -0
- package/cjs/data/token-filter/helpers/generate-autocomplete-options.d.ts +24 -0
- package/cjs/data/token-filter/helpers/generate-autocomplete-options.js +197 -0
- package/cjs/data/token-filter/helpers/generate-autocomplete-options.js.map +1 -0
- package/cjs/data/token-filter/helpers/grouping.d.ts +28 -0
- package/cjs/data/token-filter/helpers/grouping.js +61 -0
- package/cjs/data/token-filter/helpers/grouping.js.map +1 -0
- package/cjs/data/token-filter/helpers/operators.d.ts +22 -0
- package/cjs/data/token-filter/helpers/operators.js +66 -0
- package/cjs/data/token-filter/helpers/operators.js.map +1 -0
- package/cjs/data/token-filter/helpers/parse-query-text.d.ts +25 -0
- package/cjs/data/token-filter/helpers/parse-query-text.js +46 -0
- package/cjs/data/token-filter/helpers/parse-query-text.js.map +1 -0
- package/cjs/data/token-filter/helpers/query-builder.d.ts +20 -0
- package/cjs/data/token-filter/helpers/query-builder.js +38 -0
- package/cjs/data/token-filter/helpers/query-builder.js.map +1 -0
- package/cjs/data/token-filter/helpers/text-matching.d.ts +16 -0
- package/cjs/data/token-filter/helpers/text-matching.js +47 -0
- package/cjs/data/token-filter/helpers/text-matching.js.map +1 -0
- package/cjs/form/combobox/Input/InputController.js +1 -1
- package/cjs/form/combobox/Input/InputController.js.map +1 -1
- package/cjs/form/file-upload/dropzone/FileUploadDropzone.js +1 -1
- package/cjs/form/file-upload/dropzone/FileUploadDropzone.js.map +1 -1
- package/cjs/tooltip/Tooltip.js +1 -1
- package/cjs/tooltip/Tooltip.js.map +1 -1
- package/cjs/utils/i18n/locales/nb.d.ts +75 -154
- package/cjs/utils/i18n/locales/nb.js +75 -154
- package/cjs/utils/i18n/locales/nb.js.map +1 -1
- package/esm/data/table/helpers/table-grid-nav.d.ts +9 -15
- package/esm/data/table/helpers/table-grid-nav.js +18 -25
- package/esm/data/table/helpers/table-grid-nav.js.map +1 -1
- package/esm/data/table/helpers/table-keyboard.d.ts +1 -1
- package/esm/data/table/helpers/table-keyboard.js +1 -6
- package/esm/data/table/helpers/table-keyboard.js.map +1 -1
- package/esm/data/table/root/DataTableRoot.d.ts +41 -4
- package/esm/data/table/root/DataTableRoot.js +10 -6
- package/esm/data/table/root/DataTableRoot.js.map +1 -1
- package/esm/data/table/root/useTableKeyboardNav.d.ts +1 -1
- package/esm/data/table/root/useTableKeyboardNav.js +32 -19
- package/esm/data/table/root/useTableKeyboardNav.js.map +1 -1
- package/esm/data/table/td/DataTableTd.d.ts +5 -4
- package/esm/data/table/td/DataTableTd.js +2 -2
- package/esm/data/table/td/DataTableTd.js.map +1 -1
- package/esm/data/token-filter/AutoSuggest.d.ts +9 -0
- package/esm/data/token-filter/AutoSuggest.js +20 -0
- package/esm/data/token-filter/AutoSuggest.js.map +1 -0
- package/esm/data/token-filter/AutoSuggest.types.d.ts +12 -0
- package/esm/data/token-filter/AutoSuggest.types.js +2 -0
- package/esm/data/token-filter/AutoSuggest.types.js.map +1 -0
- package/esm/data/token-filter/TokenFilter.d.ts +11 -0
- package/esm/data/token-filter/TokenFilter.js +66 -0
- package/esm/data/token-filter/TokenFilter.js.map +1 -0
- package/esm/data/token-filter/TokenFilter.types.d.ts +52 -0
- package/esm/data/token-filter/TokenFilter.types.js +2 -0
- package/esm/data/token-filter/TokenFilter.types.js.map +1 -0
- package/esm/data/token-filter/helpers/generate-autocomplete-options.d.ts +24 -0
- package/esm/data/token-filter/helpers/generate-autocomplete-options.js +195 -0
- package/esm/data/token-filter/helpers/generate-autocomplete-options.js.map +1 -0
- package/esm/data/token-filter/helpers/grouping.d.ts +28 -0
- package/esm/data/token-filter/helpers/grouping.js +59 -0
- package/esm/data/token-filter/helpers/grouping.js.map +1 -0
- package/esm/data/token-filter/helpers/operators.d.ts +22 -0
- package/esm/data/token-filter/helpers/operators.js +60 -0
- package/esm/data/token-filter/helpers/operators.js.map +1 -0
- package/esm/data/token-filter/helpers/parse-query-text.d.ts +25 -0
- package/esm/data/token-filter/helpers/parse-query-text.js +44 -0
- package/esm/data/token-filter/helpers/parse-query-text.js.map +1 -0
- package/esm/data/token-filter/helpers/query-builder.d.ts +20 -0
- package/esm/data/token-filter/helpers/query-builder.js +34 -0
- package/esm/data/token-filter/helpers/query-builder.js.map +1 -0
- package/esm/data/token-filter/helpers/text-matching.d.ts +16 -0
- package/esm/data/token-filter/helpers/text-matching.js +45 -0
- package/esm/data/token-filter/helpers/text-matching.js.map +1 -0
- package/esm/form/combobox/Input/InputController.js +1 -1
- package/esm/form/combobox/Input/InputController.js.map +1 -1
- package/esm/form/file-upload/dropzone/FileUploadDropzone.js +1 -1
- package/esm/form/file-upload/dropzone/FileUploadDropzone.js.map +1 -1
- package/esm/tooltip/Tooltip.js +2 -2
- package/esm/tooltip/Tooltip.js.map +1 -1
- package/esm/utils/i18n/locales/nb.d.ts +75 -154
- package/esm/utils/i18n/locales/nb.js +75 -154
- package/esm/utils/i18n/locales/nb.js.map +1 -1
- package/package.json +3 -3
- package/src/data/table/helpers/table-grid-nav.test.ts +659 -0
- package/src/data/table/helpers/table-grid-nav.ts +19 -38
- package/src/data/table/helpers/table-keyboard.ts +1 -10
- package/src/data/table/root/DataTableRoot.tsx +50 -10
- package/src/data/table/root/useTableKeyboardNav.ts +35 -23
- package/src/data/table/td/DataTableTd.tsx +13 -6
- package/src/data/token-filter/AutoSuggest.tsx +55 -0
- package/src/data/token-filter/AutoSuggest.types.ts +14 -0
- package/src/data/token-filter/TokenFilter.tsx +129 -0
- package/src/data/token-filter/TokenFilter.types.ts +85 -0
- package/src/data/token-filter/helpers/generate-autocomplete-options.test.ts +896 -0
- package/src/data/token-filter/helpers/generate-autocomplete-options.ts +289 -0
- package/src/data/token-filter/helpers/grouping.test.ts +206 -0
- package/src/data/token-filter/helpers/grouping.ts +73 -0
- package/src/data/token-filter/helpers/operators.test.ts +281 -0
- package/src/data/token-filter/helpers/operators.ts +91 -0
- package/src/data/token-filter/helpers/parse-query-text.test.ts +201 -0
- package/src/data/token-filter/helpers/parse-query-text.ts +86 -0
- package/src/data/token-filter/helpers/query-builder.test.ts +126 -0
- package/src/data/token-filter/helpers/query-builder.ts +41 -0
- package/src/data/token-filter/helpers/text-matching.test.ts +125 -0
- package/src/data/token-filter/helpers/text-matching.ts +58 -0
- package/src/form/combobox/Input/InputController.tsx +0 -1
- package/src/form/file-upload/dropzone/FileUploadDropzone.tsx +0 -1
- package/src/tooltip/Tooltip.tsx +3 -3
- package/src/utils/i18n/locales/nb.ts +4 -83
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { buildQueryString } from "./query-builder";
|
|
3
|
+
|
|
4
|
+
describe("buildQueryString", () => {
|
|
5
|
+
describe("basic query building", () => {
|
|
6
|
+
test("builds complete query with all parts", () => {
|
|
7
|
+
expect(buildQueryString("Status", "=", "active")).toBe("Status = active");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("builds query with property and operator only", () => {
|
|
11
|
+
expect(buildQueryString("Status", "=", "")).toBe("Status =");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("builds query with property only", () => {
|
|
15
|
+
expect(buildQueryString("Status", "", "")).toBe("Status");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("returns empty string when all parts are empty", () => {
|
|
19
|
+
expect(buildQueryString("", "", "")).toBe("");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("operator variations", () => {
|
|
24
|
+
test("builds query with contains operator", () => {
|
|
25
|
+
expect(buildQueryString("Name", ":", "test")).toBe("Name : test");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("builds query with not equal operator", () => {
|
|
29
|
+
expect(buildQueryString("Status", "!=", "inactive")).toBe(
|
|
30
|
+
"Status != inactive",
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("builds query with starts with operator", () => {
|
|
35
|
+
expect(buildQueryString("ID", "^", "prefix")).toBe("ID ^ prefix");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("builds query with greater than or equal operator", () => {
|
|
39
|
+
expect(buildQueryString("Count", ">=", "10")).toBe("Count >= 10");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("builds query with all comparison operators", () => {
|
|
43
|
+
expect(buildQueryString("Value", ">", "5")).toBe("Value > 5");
|
|
44
|
+
expect(buildQueryString("Value", "<", "5")).toBe("Value < 5");
|
|
45
|
+
expect(buildQueryString("Value", ">=", "5")).toBe("Value >= 5");
|
|
46
|
+
expect(buildQueryString("Value", "<=", "5")).toBe("Value <= 5");
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("whitespace handling", () => {
|
|
51
|
+
test("joins parts with single space", () => {
|
|
52
|
+
expect(buildQueryString("Property", "=", "value")).toBe(
|
|
53
|
+
"Property = value",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("does not add extra spaces for missing parts", () => {
|
|
58
|
+
expect(buildQueryString("Property", "", "value")).toBe("Property value");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("handles value with spaces", () => {
|
|
62
|
+
expect(buildQueryString("Region", "=", "US East")).toBe(
|
|
63
|
+
"Region = US East",
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("handles property with spaces", () => {
|
|
68
|
+
expect(buildQueryString("Instance ID", "=", "12345")).toBe(
|
|
69
|
+
"Instance ID = 12345",
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("edge cases", () => {
|
|
75
|
+
test("handles numeric values", () => {
|
|
76
|
+
expect(buildQueryString("Count", "=", "123")).toBe("Count = 123");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("handles special characters in value", () => {
|
|
80
|
+
expect(buildQueryString("Path", "=", "/var/log")).toBe("Path = /var/log");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("handles hyphenated values", () => {
|
|
84
|
+
expect(buildQueryString("Region", "=", "us-east-1")).toBe(
|
|
85
|
+
"Region = us-east-1",
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("omits operator when undefined", () => {
|
|
90
|
+
expect(buildQueryString("Status", undefined as any, "active")).toBe(
|
|
91
|
+
"Status active",
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("omits value when null", () => {
|
|
96
|
+
expect(buildQueryString("Status", "=", null as any)).toBe("Status =");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("filters out falsy values correctly", () => {
|
|
100
|
+
// 0 is falsy but should be filtered by Boolean()
|
|
101
|
+
expect(buildQueryString("Count", "=", "0")).toBe("Count = 0");
|
|
102
|
+
// Empty string is falsy and should be filtered
|
|
103
|
+
expect(buildQueryString("Test", "", "")).toBe("Test");
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("real-world examples", () => {
|
|
108
|
+
test("builds property-only queries for operator selection", () => {
|
|
109
|
+
expect(buildQueryString("Status", "!", "")).toBe("Status !");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("builds complete filter queries", () => {
|
|
113
|
+
expect(buildQueryString("Availability Zone", ":", "east")).toBe(
|
|
114
|
+
"Availability Zone : east",
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("builds negation queries", () => {
|
|
119
|
+
expect(buildQueryString("Status", "!=", "terminated")).toBe(
|
|
120
|
+
"Status != terminated",
|
|
121
|
+
);
|
|
122
|
+
expect(buildQueryString("Name", "!:", "test")).toBe("Name !: test");
|
|
123
|
+
expect(buildQueryString("ID", "!^", "prod")).toBe("ID !^ prod");
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { QueryFilterOperator } from "../TokenFilter.types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Human-readable labels for query filter operators.
|
|
5
|
+
* Used for displaying operator descriptions in autocomplete suggestions.
|
|
6
|
+
* TODO: Support i18n
|
|
7
|
+
*/
|
|
8
|
+
const OPERATOR_LABELS: Record<QueryFilterOperator, string> = {
|
|
9
|
+
":": "contains",
|
|
10
|
+
"!:": "does not contain",
|
|
11
|
+
"=": "is",
|
|
12
|
+
"!=": "is not",
|
|
13
|
+
"^": "starts with",
|
|
14
|
+
"!^": "does not start with",
|
|
15
|
+
">=": "is greater than or equal to",
|
|
16
|
+
"<=": "is less than or equal to",
|
|
17
|
+
">": "is greater than",
|
|
18
|
+
"<": "is less than",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Builds a query string from property label, operator, and value.
|
|
23
|
+
* Only includes non-empty parts, joined by spaces.
|
|
24
|
+
* @returns Space-joined query string
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* buildQueryString("Status", "=", "active") // "Status = active"
|
|
28
|
+
* buildQueryString("Status", "=", "") // "Status ="
|
|
29
|
+
* buildQueryString("Status", "", "") // "Status"
|
|
30
|
+
* buildQueryString("", "", "") // ""
|
|
31
|
+
*/
|
|
32
|
+
function buildQueryString(
|
|
33
|
+
propertyLabel: string,
|
|
34
|
+
operator: string,
|
|
35
|
+
value: string,
|
|
36
|
+
): string {
|
|
37
|
+
const parts = [propertyLabel, operator, value].filter(Boolean);
|
|
38
|
+
return parts.join(" ");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { buildQueryString, OPERATOR_LABELS };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { matchesFilterText } from "./text-matching";
|
|
3
|
+
|
|
4
|
+
describe("matchesFilterText", () => {
|
|
5
|
+
describe("basic matching", () => {
|
|
6
|
+
test("returns true for empty filter text", () => {
|
|
7
|
+
expect(matchesFilterText(["hello", "world"], "")).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("returns true for whitespace-only filter text", () => {
|
|
11
|
+
expect(matchesFilterText(["hello", "world"], " ")).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("returns true when single word matches", () => {
|
|
15
|
+
expect(matchesFilterText(["hello", "world"], "hello")).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("returns true when match is case-insensitive", () => {
|
|
19
|
+
expect(matchesFilterText(["Hello World"], "hello")).toBe(true);
|
|
20
|
+
expect(matchesFilterText(["hello world"], "HELLO")).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("returns true when partial match exists", () => {
|
|
24
|
+
expect(matchesFilterText(["testing"], "test")).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("returns false when no match exists", () => {
|
|
28
|
+
expect(matchesFilterText(["hello", "world"], "foo")).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("multi-word matching", () => {
|
|
33
|
+
test("returns true when all words match across fields", () => {
|
|
34
|
+
expect(matchesFilterText(["hello", "world"], "hello world")).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("returns true when all words match in single field", () => {
|
|
38
|
+
expect(matchesFilterText(["hello world"], "hello world")).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("returns true when words match in any order", () => {
|
|
42
|
+
expect(matchesFilterText(["world hello"], "hello world")).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("returns false when not all words match", () => {
|
|
46
|
+
expect(matchesFilterText(["hello"], "hello world")).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("handles multiple spaces in filter text", () => {
|
|
50
|
+
expect(matchesFilterText(["hello world"], "hello world")).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("null/undefined handling", () => {
|
|
55
|
+
test("returns false for null searchFieldValues", () => {
|
|
56
|
+
expect(matchesFilterText(null as any, "test")).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("returns false for undefined searchFieldValues", () => {
|
|
60
|
+
expect(matchesFilterText(undefined as any, "test")).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("returns true for null filterText", () => {
|
|
64
|
+
expect(matchesFilterText(["test"], null as any)).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("returns true for undefined filterText", () => {
|
|
68
|
+
expect(matchesFilterText(["test"], undefined as any)).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("filters out null values in searchFieldValues", () => {
|
|
72
|
+
expect(matchesFilterText(["hello", null as any, "world"], "world")).toBe(
|
|
73
|
+
true,
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("filters out undefined values in searchFieldValues", () => {
|
|
78
|
+
expect(
|
|
79
|
+
matchesFilterText(["hello", undefined as any, "world"], "world"),
|
|
80
|
+
).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("returns false when all searchFieldValues are null/undefined", () => {
|
|
84
|
+
expect(matchesFilterText([null as any, undefined as any], "test")).toBe(
|
|
85
|
+
false,
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("edge cases", () => {
|
|
91
|
+
test("returns true for empty searchFieldValues with empty filter", () => {
|
|
92
|
+
expect(matchesFilterText([], "")).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("returns false for empty searchFieldValues with non-empty filter", () => {
|
|
96
|
+
expect(matchesFilterText([], "test")).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("handles special characters in filter text", () => {
|
|
100
|
+
expect(matchesFilterText(["test-value"], "test-value")).toBe(true);
|
|
101
|
+
expect(matchesFilterText(["test.value"], "test.value")).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("handles numbers in filter text", () => {
|
|
105
|
+
expect(matchesFilterText(["version 123"], "123")).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("handles unicode characters", () => {
|
|
109
|
+
expect(matchesFilterText(["café"], "café")).toBe(true);
|
|
110
|
+
expect(matchesFilterText(["🎉 party"], "party")).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("trims whitespace from filter text", () => {
|
|
114
|
+
expect(matchesFilterText(["hello"], " hello ")).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("handles array with single empty string", () => {
|
|
118
|
+
expect(matchesFilterText([""], "test")).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("matches when filter is substring of field", () => {
|
|
122
|
+
expect(matchesFilterText(["prefix-test-suffix"], "test")).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if search field values match the given filter text.
|
|
3
|
+
*
|
|
4
|
+
* @param searchFieldValues - Array of strings to search within (e.g., labels, tags, descriptions)
|
|
5
|
+
* @param filterText - The search text to match against
|
|
6
|
+
* @returns true if all space-separated parts of filterText are found in at least one searchFieldValue
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* matchesFilterText(['Hello World', 'foo'], 'hello') // true
|
|
10
|
+
* matchesFilterText(['Hello World', 'foo'], 'hello bar') // false
|
|
11
|
+
* matchesFilterText(['Hello World', 'bar'], 'hello bar') // true
|
|
12
|
+
* matchesFilterText([], 'test') // false
|
|
13
|
+
* matchesFilterText(['test'], '') // true (empty filter matches all)
|
|
14
|
+
*/
|
|
15
|
+
function matchesFilterText(
|
|
16
|
+
searchFieldValues: string[],
|
|
17
|
+
filterText: string,
|
|
18
|
+
): boolean {
|
|
19
|
+
/* Guard against null/undefined inputs */
|
|
20
|
+
if (!searchFieldValues || !Array.isArray(searchFieldValues)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (filterText === null || filterText === undefined) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const normalizedFilter = filterText.trim().toLowerCase();
|
|
29
|
+
|
|
30
|
+
/* Empty filter matches everything */
|
|
31
|
+
if (!normalizedFilter) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Split filter into parts ("nord land" -> ["nord", "land"]) */
|
|
36
|
+
const parts = normalizedFilter.split(/\s+/).filter(Boolean);
|
|
37
|
+
|
|
38
|
+
if (parts.length === 0) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Normalize and filter out nullish values */
|
|
43
|
+
const normalizedFields = searchFieldValues
|
|
44
|
+
.map((value) => value?.toLowerCase())
|
|
45
|
+
.filter(Boolean);
|
|
46
|
+
|
|
47
|
+
/* If no valid fields to search, no match */
|
|
48
|
+
if (normalizedFields.length === 0) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Every part of the filter must be found in at least one field */
|
|
53
|
+
return parts.every((part) =>
|
|
54
|
+
normalizedFields.some((field) => field.includes(part)),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { matchesFilterText };
|
package/src/tooltip/Tooltip.tsx
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
useHover,
|
|
12
12
|
useInteractions,
|
|
13
13
|
} from "@floating-ui/react";
|
|
14
|
-
import React, { HTMLAttributes, forwardRef, useRef } from "react";
|
|
14
|
+
import React, { Fragment, HTMLAttributes, forwardRef, useRef } from "react";
|
|
15
15
|
import { useModalContext } from "../modal/Modal.context";
|
|
16
16
|
import { Portal } from "../portal";
|
|
17
17
|
import { HStack } from "../primitives/stack";
|
|
@@ -296,7 +296,7 @@ function TooltipShortcuts({ shortcuts }: { shortcuts: TooltipProps["keys"] }) {
|
|
|
296
296
|
return (
|
|
297
297
|
<span className="aksel-tooltip__keys" aria-hidden>
|
|
298
298
|
{shortcuts.map((key, index) => (
|
|
299
|
-
|
|
299
|
+
<Fragment key={key.join("+")}>
|
|
300
300
|
<HStack gap="space-4">
|
|
301
301
|
{key.map((k, i) => (
|
|
302
302
|
<Detail as="kbd" key={i} className="aksel-tooltip__key">
|
|
@@ -307,7 +307,7 @@ function TooltipShortcuts({ shortcuts }: { shortcuts: TooltipProps["keys"] }) {
|
|
|
307
307
|
{index < shortcuts.length - 1 && (
|
|
308
308
|
<span> {translate("shortcutSeparator")} </span>
|
|
309
309
|
)}
|
|
310
|
-
|
|
310
|
+
</Fragment>
|
|
311
311
|
))}
|
|
312
312
|
</span>
|
|
313
313
|
);
|
|
@@ -7,218 +7,139 @@ interface TranslationMap {
|
|
|
7
7
|
export default {
|
|
8
8
|
global: {
|
|
9
9
|
dateLocale: nb,
|
|
10
|
-
/** @default "Vis mer" */
|
|
11
10
|
showMore: "Vis mer",
|
|
12
|
-
/** @default "Vis mindre" */
|
|
13
11
|
showLess: "Vis mindre",
|
|
14
|
-
/** @default "Skrivebeskyttet" */
|
|
15
12
|
readOnly: "Skrivebeskyttet",
|
|
16
|
-
/** @default "Lukk" */
|
|
17
13
|
close: "Lukk",
|
|
18
|
-
/** @default "Feil" */
|
|
19
14
|
error: "Feil",
|
|
20
|
-
/** @default "Informasjon" */
|
|
21
15
|
info: "Informasjon",
|
|
22
|
-
/** @default "Suksess" */
|
|
23
16
|
success: "Suksess",
|
|
24
|
-
/** @default "Advarsel" */
|
|
25
17
|
warning: "Advarsel",
|
|
26
|
-
/** @default "Kunngjøring" */
|
|
27
18
|
announcement: "Kunngjøring",
|
|
28
19
|
},
|
|
29
20
|
Chips: {
|
|
30
21
|
Removable: {
|
|
31
|
-
/** Will be appended to the accessible name for the button.
|
|
32
|
-
* @default "slett" */
|
|
22
|
+
/** Will be appended to the accessible name for the button. */
|
|
33
23
|
labelSuffix: "slett",
|
|
34
24
|
},
|
|
35
25
|
},
|
|
36
26
|
Combobox: {
|
|
37
|
-
/** The input value will be appended to the end of this text, e.g. `Legg til "input value"`.
|
|
38
|
-
* @default "Legg til" */
|
|
27
|
+
/** The input value will be appended to the end of this text, e.g. `Legg til "input value"`. */
|
|
39
28
|
addOption: "Legg til",
|
|
40
|
-
/** @default "Ingen søketreff" */
|
|
41
29
|
noMatches: "Ingen søketreff",
|
|
42
|
-
/** Loader title
|
|
43
|
-
* @default "Søker…" */
|
|
30
|
+
/** Loader title */
|
|
44
31
|
loading: "Søker…",
|
|
45
|
-
/** @default "{selected} av maks {limit} er valgt." */
|
|
46
32
|
maxSelected: "{selected} av maks {limit} er valgt.",
|
|
47
33
|
},
|
|
48
34
|
CopyButton: {
|
|
49
|
-
/** @default "Kopier" */
|
|
50
35
|
title: "Kopier",
|
|
51
|
-
/** @default "Kopiert!" */
|
|
52
36
|
activeText: "Kopiert!",
|
|
53
37
|
},
|
|
54
38
|
DatePicker: {
|
|
55
|
-
/** @default "Velg dato" */
|
|
56
39
|
chooseDate: "Velg dato",
|
|
57
|
-
/** @default "Velg datoer" */
|
|
58
40
|
chooseDates: "Velg datoer",
|
|
59
|
-
/** @default "Velg start- og sluttdato" */
|
|
60
41
|
chooseDateRange: "Velg start- og sluttdato",
|
|
61
|
-
/** @default "Velg måned" */
|
|
62
42
|
chooseMonth: "Velg måned",
|
|
63
|
-
/** @default "Uke" */
|
|
64
43
|
week: "Uke",
|
|
65
|
-
/** @default "Uke {week}" */
|
|
66
44
|
weekNumber: "Uke {week}",
|
|
67
|
-
/** @default "Velg uke {week}" */
|
|
68
45
|
selectWeekNumber: "Velg uke {week}",
|
|
69
|
-
/** @default "Måned" */
|
|
70
46
|
month: "Måned",
|
|
71
|
-
/** @default "Gå til neste måned" */
|
|
72
47
|
goToNextMonth: "Gå til neste måned",
|
|
73
|
-
/** @default "Gå til forrige måned" */
|
|
74
48
|
goToPreviousMonth: "Gå til forrige måned",
|
|
75
|
-
/** @default "År" */
|
|
76
49
|
year: "År",
|
|
77
|
-
/** @default "Gå til neste år" */
|
|
78
50
|
goToNextYear: "Gå til neste år",
|
|
79
|
-
/** @default "Gå til forrige år" */
|
|
80
51
|
goToPreviousYear: "Gå til forrige år",
|
|
81
|
-
/** @default "Åpne datovelger" */
|
|
82
52
|
openDatePicker: "Åpne datovelger",
|
|
83
|
-
/** @default "Åpne månedsvelger" */
|
|
84
53
|
openMonthPicker: "Åpne månedsvelger",
|
|
85
|
-
/** @default "Lukk datovelger" */
|
|
86
54
|
closeDatePicker: "Lukk datovelger",
|
|
87
|
-
/** @default "Lukk månedsvelger" */
|
|
88
55
|
closeMonthPicker: "Lukk månedsvelger",
|
|
89
56
|
},
|
|
90
57
|
ErrorSummary: {
|
|
91
|
-
/** @default "Du må rette disse feilene før du kan fortsette:" */
|
|
92
58
|
heading: "Du må rette disse feilene før du kan fortsette:",
|
|
93
59
|
},
|
|
94
60
|
FileUpload: {
|
|
95
61
|
dropzone: {
|
|
96
|
-
/** @default "Velg fil" */
|
|
97
62
|
button: "Velg fil",
|
|
98
|
-
/** @default "Velg filer" */
|
|
99
63
|
buttonMultiple: "Velg filer",
|
|
100
|
-
/** @default "Dra og slipp filen her" */
|
|
101
64
|
dragAndDrop: "Dra og slipp filen her",
|
|
102
|
-
/** @default "Dra og slipp filer her" */
|
|
103
65
|
dragAndDropMultiple: "Dra og slipp filer her",
|
|
104
|
-
/** @default "Slipp" */
|
|
105
66
|
drop: "Slipp",
|
|
106
|
-
/** @default "eller" */
|
|
107
67
|
or: "eller",
|
|
108
|
-
/** @default "Filopplasting er deaktivert" */
|
|
109
68
|
disabled: "Filopplasting er deaktivert",
|
|
110
|
-
/** @default "Du kan ikke laste opp flere filer" */
|
|
111
69
|
disabledFilelimit: "Du kan ikke laste opp flere filer",
|
|
112
70
|
},
|
|
113
71
|
item: {
|
|
114
|
-
/** @default "Prøv å laste opp filen på nytt" */
|
|
115
72
|
retryButtonTitle: "Prøv å laste opp filen på nytt",
|
|
116
|
-
/** @default "Slett filen" */
|
|
117
73
|
deleteButtonTitle: "Slett filen",
|
|
118
|
-
/** @default "Laster opp…" */
|
|
119
74
|
uploading: "Laster opp…",
|
|
120
|
-
/** @default "Laster ned…" */
|
|
121
75
|
downloading: "Laster ned…",
|
|
122
76
|
},
|
|
123
77
|
},
|
|
124
78
|
FormProgress: {
|
|
125
|
-
/** @default "Steg {activeStep} av {totalSteps}" */
|
|
126
79
|
step: "Steg {activeStep} av {totalSteps}",
|
|
127
|
-
/** @default "Vis alle steg" */
|
|
128
80
|
showAllSteps: "Vis alle steg",
|
|
129
|
-
/** @default "Skjul alle steg" */
|
|
130
81
|
hideAllSteps: "Skjul alle steg",
|
|
131
82
|
},
|
|
132
83
|
FormSummary: {
|
|
133
|
-
/** @default "Endre svar" */
|
|
134
84
|
editAnswer: "Endre svar",
|
|
135
85
|
},
|
|
136
86
|
GuidePanel: {
|
|
137
|
-
/** @default "Illustrasjon av veileder" */
|
|
138
87
|
illustrationLabel: "Illustrasjon av veileder",
|
|
139
88
|
},
|
|
140
89
|
HelpText: {
|
|
141
|
-
/** @default "Mer informasjon" */
|
|
142
90
|
title: "Mer informasjon",
|
|
143
91
|
},
|
|
144
92
|
Loader: {
|
|
145
|
-
/** @default "Venter…" */
|
|
146
93
|
title: "Venter…",
|
|
147
94
|
},
|
|
148
95
|
Pagination: {
|
|
149
|
-
/** @default "Forrige" */
|
|
150
96
|
previous: "Forrige",
|
|
151
|
-
/** @default "Neste" */
|
|
152
97
|
next: "Neste",
|
|
153
98
|
},
|
|
154
99
|
Process: {
|
|
155
|
-
/** @default "Aktiv" */
|
|
156
100
|
active: "Aktiv",
|
|
157
101
|
},
|
|
158
102
|
ProgressBar: {
|
|
159
|
-
/** @default "{current} av {max}" */
|
|
160
103
|
progress: "{current} av {max}",
|
|
161
|
-
/** @default "Fremdrift kan ikke beregnes, antatt tid er {seconds} sekunder." */
|
|
162
104
|
progressUnknown:
|
|
163
105
|
"Fremdrift kan ikke beregnes, antatt tid er {seconds} sekunder.",
|
|
164
106
|
},
|
|
165
107
|
Search: {
|
|
166
|
-
/** @default "Tøm feltet" */
|
|
167
108
|
clear: "Tøm feltet",
|
|
168
|
-
/** @default "Søk" */
|
|
169
109
|
search: "Søk",
|
|
170
110
|
},
|
|
171
111
|
Textarea: {
|
|
172
|
-
/** Screen readers only
|
|
173
|
-
* @default "Tekstområde med plass til {maxLength} tegn." */
|
|
112
|
+
/** Screen readers only */
|
|
174
113
|
maxLength: "Tekstområde med plass til {maxLength} tegn.",
|
|
175
|
-
/** @default "{chars} tegn for mye" */
|
|
176
114
|
charsTooMany: "{chars} tegn for mye",
|
|
177
|
-
/** @default "{chars} tegn igjen" */
|
|
178
115
|
charsLeft: "{chars} tegn igjen",
|
|
179
116
|
},
|
|
180
117
|
Timeline: {
|
|
181
|
-
/** @default "dd.MM.yyyy" */
|
|
182
118
|
dateFormat: "dd.MM.yyyy",
|
|
183
|
-
/** @default "dd.MM" */
|
|
184
119
|
dayFormat: "dd.MM",
|
|
185
|
-
/** @default "MMM yy" */
|
|
186
120
|
monthFormat: "MMM yy",
|
|
187
|
-
/** @default "yyyy" */
|
|
188
121
|
yearFormat: "yyyy",
|
|
189
122
|
Row: {
|
|
190
|
-
/** @default "Ingen perioder" */
|
|
191
123
|
noPeriods: "Ingen perioder",
|
|
192
|
-
/** @default "{start} til {end}" */
|
|
193
124
|
period: "{start} til {end}",
|
|
194
125
|
},
|
|
195
126
|
Period: {
|
|
196
|
-
/** @default "Suksess" */
|
|
197
127
|
success: "Suksess",
|
|
198
|
-
/** @default "Advarsel" */
|
|
199
128
|
warning: "Advarsel",
|
|
200
|
-
/** @default "Fare" */
|
|
201
129
|
danger: "Fare",
|
|
202
|
-
/** @default "Info" */
|
|
203
130
|
info: "Info",
|
|
204
|
-
/** @default "Nøytral" */
|
|
205
131
|
neutral: "Nøytral",
|
|
206
|
-
/** @default "{status} fra {start} til {end}" */
|
|
207
132
|
period: "{status} fra {start} til {end}",
|
|
208
133
|
},
|
|
209
134
|
Pin: {
|
|
210
|
-
/** @default "Pin: {date}" */
|
|
211
135
|
pin: "Pin: {date}",
|
|
212
136
|
},
|
|
213
137
|
Zoom: {
|
|
214
|
-
/** @default "Zoom tidslinjen {start} til {end}" */
|
|
215
138
|
zoom: "Zoom tidslinjen {start} til {end}",
|
|
216
|
-
/** @default "Tilbakestill tidsperspektiv" */
|
|
217
139
|
reset: "Tilbakestill tidsperspektiv",
|
|
218
140
|
},
|
|
219
141
|
},
|
|
220
142
|
Tooltip: {
|
|
221
|
-
/** @default "eller" */
|
|
222
143
|
shortcutSeparator: "eller",
|
|
223
144
|
},
|
|
224
145
|
} satisfies TranslationMap;
|