@navikt/ds-react 8.5.0 → 8.5.1

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.
Files changed (78) hide show
  1. package/cjs/data/table/helpers/table-grid-nav.d.ts +9 -15
  2. package/cjs/data/table/helpers/table-grid-nav.js +18 -25
  3. package/cjs/data/table/helpers/table-grid-nav.js.map +1 -1
  4. package/cjs/data/table/helpers/table-keyboard.d.ts +1 -1
  5. package/cjs/data/table/helpers/table-keyboard.js +1 -6
  6. package/cjs/data/table/helpers/table-keyboard.js.map +1 -1
  7. package/cjs/data/table/root/DataTableRoot.d.ts +14 -4
  8. package/cjs/data/table/root/DataTableRoot.js +4 -6
  9. package/cjs/data/table/root/DataTableRoot.js.map +1 -1
  10. package/cjs/data/table/root/useTableKeyboardNav.d.ts +1 -1
  11. package/cjs/data/table/root/useTableKeyboardNav.js +32 -19
  12. package/cjs/data/table/root/useTableKeyboardNav.js.map +1 -1
  13. package/cjs/data/token-filter/AutoSuggest.d.ts +21 -0
  14. package/cjs/data/token-filter/AutoSuggest.js +129 -0
  15. package/cjs/data/token-filter/AutoSuggest.js.map +1 -0
  16. package/cjs/data/token-filter/TokenFilter.d.ts +11 -0
  17. package/cjs/data/token-filter/TokenFilter.js +91 -0
  18. package/cjs/data/token-filter/TokenFilter.js.map +1 -0
  19. package/cjs/data/token-filter/TokenFilter.types.d.ts +46 -0
  20. package/cjs/data/token-filter/TokenFilter.types.js +3 -0
  21. package/cjs/data/token-filter/TokenFilter.types.js.map +1 -0
  22. package/cjs/data/token-filter/helpers/generate-autocomplete-options.d.ts +70 -0
  23. package/cjs/data/token-filter/helpers/generate-autocomplete-options.js +171 -0
  24. package/cjs/data/token-filter/helpers/generate-autocomplete-options.js.map +1 -0
  25. package/cjs/data/token-filter/helpers/parse-query-text.d.ts +31 -0
  26. package/cjs/data/token-filter/helpers/parse-query-text.js +91 -0
  27. package/cjs/data/token-filter/helpers/parse-query-text.js.map +1 -0
  28. package/cjs/tooltip/Tooltip.js +1 -1
  29. package/cjs/tooltip/Tooltip.js.map +1 -1
  30. package/cjs/utils/i18n/locales/nb.d.ts +75 -154
  31. package/cjs/utils/i18n/locales/nb.js +75 -154
  32. package/cjs/utils/i18n/locales/nb.js.map +1 -1
  33. package/esm/data/table/helpers/table-grid-nav.d.ts +9 -15
  34. package/esm/data/table/helpers/table-grid-nav.js +18 -25
  35. package/esm/data/table/helpers/table-grid-nav.js.map +1 -1
  36. package/esm/data/table/helpers/table-keyboard.d.ts +1 -1
  37. package/esm/data/table/helpers/table-keyboard.js +1 -6
  38. package/esm/data/table/helpers/table-keyboard.js.map +1 -1
  39. package/esm/data/table/root/DataTableRoot.d.ts +14 -4
  40. package/esm/data/table/root/DataTableRoot.js +4 -6
  41. package/esm/data/table/root/DataTableRoot.js.map +1 -1
  42. package/esm/data/table/root/useTableKeyboardNav.d.ts +1 -1
  43. package/esm/data/table/root/useTableKeyboardNav.js +32 -19
  44. package/esm/data/table/root/useTableKeyboardNav.js.map +1 -1
  45. package/esm/data/token-filter/AutoSuggest.d.ts +21 -0
  46. package/esm/data/token-filter/AutoSuggest.js +93 -0
  47. package/esm/data/token-filter/AutoSuggest.js.map +1 -0
  48. package/esm/data/token-filter/TokenFilter.d.ts +11 -0
  49. package/esm/data/token-filter/TokenFilter.js +55 -0
  50. package/esm/data/token-filter/TokenFilter.js.map +1 -0
  51. package/esm/data/token-filter/TokenFilter.types.d.ts +46 -0
  52. package/esm/data/token-filter/TokenFilter.types.js +2 -0
  53. package/esm/data/token-filter/TokenFilter.types.js.map +1 -0
  54. package/esm/data/token-filter/helpers/generate-autocomplete-options.d.ts +70 -0
  55. package/esm/data/token-filter/helpers/generate-autocomplete-options.js +169 -0
  56. package/esm/data/token-filter/helpers/generate-autocomplete-options.js.map +1 -0
  57. package/esm/data/token-filter/helpers/parse-query-text.d.ts +31 -0
  58. package/esm/data/token-filter/helpers/parse-query-text.js +87 -0
  59. package/esm/data/token-filter/helpers/parse-query-text.js.map +1 -0
  60. package/esm/tooltip/Tooltip.js +2 -2
  61. package/esm/tooltip/Tooltip.js.map +1 -1
  62. package/esm/utils/i18n/locales/nb.d.ts +75 -154
  63. package/esm/utils/i18n/locales/nb.js +75 -154
  64. package/esm/utils/i18n/locales/nb.js.map +1 -1
  65. package/package.json +3 -3
  66. package/src/data/table/helpers/table-grid-nav.test.ts +659 -0
  67. package/src/data/table/helpers/table-grid-nav.ts +19 -38
  68. package/src/data/table/helpers/table-keyboard.ts +1 -10
  69. package/src/data/table/root/DataTableRoot.tsx +21 -10
  70. package/src/data/table/root/useTableKeyboardNav.ts +35 -23
  71. package/src/data/token-filter/AutoSuggest.tsx +179 -0
  72. package/src/data/token-filter/TokenFilter.tsx +124 -0
  73. package/src/data/token-filter/TokenFilter.types.ts +79 -0
  74. package/src/data/token-filter/helpers/generate-autocomplete-options.ts +244 -0
  75. package/src/data/token-filter/helpers/parse-query-text.test.ts +410 -0
  76. package/src/data/token-filter/helpers/parse-query-text.ts +148 -0
  77. package/src/tooltip/Tooltip.tsx +3 -3
  78. package/src/utils/i18n/locales/nb.ts +4 -83
@@ -0,0 +1,148 @@
1
+ import type { ParsedProperty, QueryFilterOperator } from "../TokenFilter.types";
2
+
3
+ type ParsedText =
4
+ | {
5
+ /** User has typed property + complete operator + value (e.g., "Status != active") */
6
+ step: "property";
7
+ property: ParsedProperty;
8
+ operator: QueryFilterOperator;
9
+ value: string;
10
+ }
11
+ | {
12
+ /** User is typing the operator after property (e.g., "Status !") */
13
+ step: "operator";
14
+ property: ParsedProperty;
15
+ operatorPrefix: string;
16
+ }
17
+ | {
18
+ /** No property match; treat as free-text search */
19
+ step: "free-text";
20
+ value: string;
21
+ operator?: QueryFilterOperator;
22
+ };
23
+
24
+ /**
25
+ * Parse user input text to extract property, operator, and value components.
26
+ * Handles partial input (e.g., user typing "Status !" to complete the operator).
27
+ */
28
+ function parseQueryText(
29
+ filteringText: string,
30
+ filteringProperties: ParsedProperty[],
31
+ ): ParsedText {
32
+ const property = matchFilteringProperty(filteringProperties, filteringText);
33
+ if (!property) {
34
+ const freeTextOperator = matchOperator(QUERY_OPERATORS, filteringText);
35
+ if (freeTextOperator) {
36
+ return {
37
+ step: "free-text",
38
+ operator: freeTextOperator,
39
+ value: filteringText.substring(freeTextOperator.length).trimStart(),
40
+ };
41
+ }
42
+
43
+ return {
44
+ step: "free-text",
45
+ value: filteringText,
46
+ };
47
+ }
48
+
49
+ const textWithoutProperty = filteringText
50
+ .substring(property.propertyLabel.length)
51
+ .trimStart();
52
+
53
+ const operator = matchOperator(QUERY_OPERATORS, textWithoutProperty);
54
+
55
+ if (operator) {
56
+ return {
57
+ step: "property",
58
+ property,
59
+ operator,
60
+ value: textWithoutProperty.substring(operator.length).trimStart(),
61
+ };
62
+ }
63
+
64
+ const operatorPrefix = matchOperatorPrefix(
65
+ QUERY_OPERATORS,
66
+ textWithoutProperty,
67
+ );
68
+
69
+ if (operatorPrefix !== null) {
70
+ return { step: "operator", property, operatorPrefix };
71
+ }
72
+
73
+ return {
74
+ step: "free-text",
75
+ value: filteringText,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Operators ordered by specificity (longest/most specific first)
81
+ * This ensures longer operators like ">=" and "<=" are matched
82
+ * before shorter ones like ">" and "<"
83
+ */
84
+ const QUERY_OPERATORS: QueryFilterOperator[] = [
85
+ ">=",
86
+ "<=",
87
+ "!=",
88
+ "!:",
89
+ "!^",
90
+ "=",
91
+ ":",
92
+ "^",
93
+ ">",
94
+ "<",
95
+ ];
96
+
97
+ /**
98
+ * Match a property from the input text by longest property label.
99
+ * Case-insensitive matching.
100
+ */
101
+ function matchFilteringProperty(
102
+ filteringProperties: ParsedProperty[],
103
+ text: string,
104
+ ): ParsedProperty | undefined {
105
+ const sortedProperties = [...filteringProperties].sort(
106
+ (a, b) => b.propertyLabel.length - a.propertyLabel.length,
107
+ );
108
+ return sortedProperties.find((prop) =>
109
+ text.toLowerCase().startsWith(prop.propertyLabel.toLowerCase()),
110
+ );
111
+ }
112
+
113
+ /**
114
+ * Check if the input text is a valid prefix of any allowed operator.
115
+ * Returns the prefix if valid, null otherwise.
116
+ */
117
+ function matchOperatorPrefix(
118
+ allowedOperators: QueryFilterOperator[],
119
+ filteringText: string,
120
+ ): string | null {
121
+ const trimmedText = filteringText.trim();
122
+
123
+ if (trimmedText.length === 0) {
124
+ return "";
125
+ }
126
+
127
+ const isValidPrefix = allowedOperators.some((operator) =>
128
+ operator.toLowerCase().startsWith(trimmedText.toLowerCase()),
129
+ );
130
+
131
+ return isValidPrefix ? trimmedText : null;
132
+ }
133
+
134
+ /**
135
+ * Match an operator from the input text.
136
+ * Operators are already sorted by specificity, so no re-sorting needed.
137
+ */
138
+ function matchOperator(
139
+ allowedOperators: QueryFilterOperator[],
140
+ text: string,
141
+ ): QueryFilterOperator | undefined {
142
+ return allowedOperators.find((operator) =>
143
+ text.toLowerCase().startsWith(operator.toLowerCase()),
144
+ );
145
+ }
146
+
147
+ export { QUERY_OPERATORS, parseQueryText };
148
+ export type { ParsedText };
@@ -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;