Compare commits

...
13 Commits
Author SHA1 Message Date
dependabot[bot] ca65ffba89 ⬆️ Bump @hono/node-server from 2.1.1 to 2.1.5 (#5488)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-02 15:45:18 +01:00
dependabot[bot] f5d9c288ad ⬆️ Bump follow-redirects from 1.16.1 to 1.16.2 (#5487)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-02 15:44:49 +01:00
dependabot[bot] 009cab1bc1 ⬆️ Bump @github-ui/storybook-addon-performance-panel from 1.3.0 to 1.3.1 in the storybook group (#5481)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-02 15:20:30 +01:00
dependabot[bot] 1d55e2481e ⬆️ Bump @google-cloud/bigquery from 9.0.2 to 9.5.0 (#5483)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-02 15:20:02 +01:00
Ken 3e039503fb perf(date): Import date utilities from sub-modules (#5476) 2026-11-02 15:19:25 +01:00
dependabot[bot] a47cbaff83 ⬆️ Bump the next group with 3 updates (#5479)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-02 15:01:22 +01:00
Halvor Haugan e30a1edd98 fix(Combobox2): fix issue with VO on Safari 2026-11-02 11:48:47 +01:00
Halvor Haugan 32c578184e fix(Combobox2): fix test 2026-10-30 17:54:38 +01:00
Halvor Haugan cd8fadf444 fix(Combobox2): add forwardRef and squash a bunch of bugs 2026-10-30 17:36:05 +01:00
Ken b69c5fca30 perf: Mark esm builds as side-effect free (#5472) 2026-10-30 13:56:02 +01:00
Ken 7a9530a252 perf(website): Optimize fuse indexing (#5468) 2026-10-30 13:03:08 +01:00
Halvor Haugan 3a858027b8 chore(deps): bump body-parser to 2.4.0 2026-10-30 12:42:31 +01:00
dependabot[bot] ab4ab6f488 ⬆️ Bump body-parser from 1.20.8 to 1.20.9 (#5473)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-30 11:46:46 +01:00
66 changed files with 680 additions and 452 deletions

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
---
"@navikt/ds-react": patch
"@navikt/aksel-icons": patch
---
ESM: Mark ESM output as side-effect free, so bundlers like webpack, Turbopack and esbuild can tree-shake unused components and icons.
+5
View File
@@ -0,0 +1,5 @@
---
"@navikt/ds-react": patch
---
Date utilities: Import date-fns functions and locales from their own paths, so unbundled Node imports (SSR, Vitest/Jest) no longer load every date-fns locale.
+1 -1
View File
@@ -64,7 +64,7 @@
"parse-svg": "node -e 'require(\"./config/parse-svg.js\").main()'",
"copy": "copyfiles util/* src",
"update-metadata": "node config/metadata.js",
"write-packagejson": "echo '{\"type\": \"module\"}' > dist/react/esm/package.json",
"write-packagejson": "echo '{\"type\": \"module\", \"sideEffects\": false}' > dist/react/esm/package.json",
"build": "yarn copy && yarn create-icons && concurrently \"tsc\" \"tsc -p tsconfig.esm.json && tsc-alias -p tsconfig.esm.json && yarn write-packagejson\" && node config/cleanTypes.js && yarn update-metadata && yarn parse-svg",
"fetch-new:icons": "tsx --env-file=.env config/figma/index.ts",
"zip:icons": "node config/zip.js",
+16 -30
View File
@@ -142,7 +142,7 @@
flex-direction: column;
overflow: hidden;
.aksel-combobox2:has(:not(.aksel-combobox2__modal)) & {
.aksel-combobox2:not(:has(.aksel-combobox2__modal)) & {
outline: 1px solid var(--ax-border-neutral-subtleA);
outline-offset: -1px;
border-radius: var(--ax-radius-8);
@@ -158,26 +158,21 @@
margin: -1px;
}
.aksel-combobox2__filter .aksel-search__wrapper {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
.aksel-combobox2__filter .aksel-search__wrapper:has(.aksel-search__input:focus-visible) {
outline: none;
position: relative;
&:has(.aksel-search__input:focus-visible) {
outline: none;
position: relative;
&::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
border: 2px solid var(--ax-border-focus);
}
&::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
border: 2px solid var(--ax-border-focus);
}
.aksel-combobox2:has(:not(.aksel-combobox2__modal)) &::after {
border-top-left-radius: var(--ax-radius-8);
border-top-right-radius: var(--ax-radius-8);
}
.aksel-combobox2:not(:has(.aksel-combobox2__modal)) &::after {
border-top-left-radius: var(--ax-radius-8);
border-top-right-radius: var(--ax-radius-8);
}
}
@@ -189,7 +184,7 @@
border-color: var(--ax-border-neutral-subtleA);
}
.aksel-combobox2[data-size="medium"] & {
.aksel-combobox2__popup[data-size="medium"] & {
min-height: 0;
}
@@ -221,15 +216,6 @@
text-align: center;
}
/************************************************
* Group *
************************************************/
.aksel-combobox2__group-label {
padding: var(--ax-space-8) var(--ax-space-8);
font-weight: bold;
}
/************************************************
* Option *
************************************************/
@@ -253,7 +239,7 @@
margin-top: var(--ax-space-2);
float: left;
.aksel-combobox2[data-size="small"] & {
.aksel-combobox2__popup[data-size="small"] & {
--__axc-combobox2-option-checkmark-size: 1rem;
}
+1 -1
View File
@@ -775,7 +775,7 @@
"docgen:meta": "yarn metadata:generate-names && tsx ./scripts/extract-metadata.ts",
"metadata:generate-names": "tsx ./scripts/extract-component-names.ts",
"i18n-jsdoc": "tsx ./scripts/addJsdocToLocales.ts",
"write-packagejson": "echo '{\"type\": \"module\"}' > esm/package.json",
"write-packagejson": "echo '{\"type\": \"module\", \"sideEffects\": false}' > esm/package.json",
"clean": "rimraf cjs esm",
"build": "yarn i18n-jsdoc && yarn metadata:generate-names && concurrently \"tsc -p tsconfig.build.json --module commonjs --outDir cjs --tsBuildInfoFile cjs/.tsbuildinfo\" \"tsc -p tsconfig.build.json --module esnext --outDir esm --tsBuildInfoFile esm/.tsbuildinfo && tsc-alias -p tsconfig.build.json --dir esm -f && yarn write-packagejson\" && yarn i18n-jsdoc --cleanup && yarn copy-types",
"watch": "tsc --watch -p tsconfig.build.json --module esnext --outDir esm --tsBuildInfoFile esm/.tsbuildinfo",
@@ -1,4 +1,5 @@
import { type Locale, format } from "date-fns";
import type { Locale } from "date-fns";
import { format } from "date-fns/format";
import React, { useEffect, useRef } from "react";
import type { CalendarDay, Modifiers } from "react-day-picker";
import { cl } from "../../utils/helpers";
@@ -1,12 +1,10 @@
import {
type Locale,
format,
getMonth,
getYear,
setMonth,
setYear,
startOfMonth,
} from "date-fns";
import type { Locale } from "date-fns";
import { format } from "date-fns/format";
import { getMonth } from "date-fns/getMonth";
import { getYear } from "date-fns/getYear";
import { setMonth } from "date-fns/setMonth";
import { setYear } from "date-fns/setYear";
import { startOfMonth } from "date-fns/startOfMonth";
import React, { type ChangeEvent, useCallback } from "react";
import { type CalendarMonth, useDayPicker } from "react-day-picker";
import { ArrowLeftIcon, ArrowRightIcon } from "@navikt/aksel-icons";
@@ -1,4 +1,4 @@
import { isWeekend } from "date-fns";
import { isWeekend } from "date-fns/isWeekend";
import React, { useCallback, useState } from "react";
import {
type ClassNames,
@@ -1,4 +1,4 @@
import { isSameDay } from "date-fns";
import { isSameDay } from "date-fns/isSameDay";
import React, { forwardRef, useRef, useState } from "react";
import type { DateRange } from "react-day-picker";
import { useId } from "../../utils-external";
@@ -1,4 +1,5 @@
import { differenceInCalendarDays, isWeekend } from "date-fns";
import { differenceInCalendarDays } from "date-fns/differenceInCalendarDays";
import { isWeekend } from "date-fns/isWeekend";
import React, { useCallback, useState } from "react";
import { type DayEventHandler, dateMatchModifiers } from "react-day-picker";
import { getLocaleFromString } from "../../utils/components/date/Date.locale";
@@ -1,4 +1,6 @@
import { differenceInCalendarDays, isSameDay, isWeekend } from "date-fns";
import { differenceInCalendarDays } from "date-fns/differenceInCalendarDays";
import { isSameDay } from "date-fns/isSameDay";
import { isWeekend } from "date-fns/isWeekend";
import React, { useState } from "react";
import { dateMatchModifiers } from "react-day-picker";
import { getLocaleFromString } from "../../utils/components/date/Date.locale";
+149 -121
View File
@@ -1,4 +1,4 @@
import React from "react";
import React, { forwardRef } from "react";
import { Dialog } from "../../dialog";
import { BodyShort, ErrorMessage } from "../../typography";
import { cl } from "../../utils/helpers";
@@ -36,109 +36,114 @@ interface ComboboxProps<
name?: string;
}
function Combobox<
T extends ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>,
>({
label,
hideLabel,
name,
size: sizeProp,
error,
errorId: errorIdProp,
disabled: disabledProp,
description,
id,
readOnly: readOnlyProp,
multiple,
...rest
}: ComboboxProps<T>) {
const {
inputProps,
errorId,
showErrorMsg,
hasError,
size,
inputDescriptionId,
readOnly,
} = useFormField(
const Combobox = forwardRef(
<T extends ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>>(
{
description,
disabled: disabledProp,
label,
hideLabel,
name,
size: sizeProp,
error,
errorId: errorIdProp,
disabled: disabledProp,
description,
id,
readOnly: readOnlyProp,
size: sizeProp,
},
"combobox",
);
multiple,
...rest
}: ComboboxProps<T>,
ref: React.ForwardedRef<HTMLButtonElement>,
) => {
const {
inputProps,
errorId,
showErrorMsg,
hasError,
size,
inputDescriptionId,
readOnly,
} = useFormField(
{
description,
disabled: disabledProp,
error,
errorId: errorIdProp,
id,
readOnly: readOnlyProp,
size: sizeProp,
},
"combobox",
);
return (
<ComboboxRoot
size={size}
disabled={inputProps.disabled}
multiple={multiple}
{...rest}
>
<ComboboxLabel
htmlFor={inputProps.id}
hide={hideLabel}
readOnly={readOnly}
return (
<ComboboxRoot
size={size}
disabled={inputProps.disabled}
multiple={multiple}
{...rest}
>
{label}
</ComboboxLabel>
{!!description && (
<BodyShort
className={cl(
"aksel-form-field__description aksel-combobox2__description",
{ "aksel-sr-only": hideLabel },
)}
id={inputDescriptionId}
size={size}
as="div"
<ComboboxLabel
htmlFor={inputProps.id}
hide={hideLabel}
readOnly={readOnly}
>
{description}
</BodyShort>
)}
<ComboboxTrigger readOnly={readOnly} {...inputProps}>
<ComboboxField hasError={hasError} />
</ComboboxTrigger>
<div
className="aksel-form-field__error aksel-combobox2__error"
id={errorId}
aria-relevant="additions removals"
aria-live="polite"
>
{showErrorMsg && (
<ErrorMessage size={size} showIcon>
{error}
</ErrorMessage>
)}
</div>
{name &&
rest.value.map((val) => (
<input key={val} type="hidden" name={name} value={val} />
))}
<ComboboxOverlay
mobileHeader={
<Dialog.Header>
<Dialog.Title>{label}</Dialog.Title>
{!!description && (
<Dialog.Description>{description}</Dialog.Description>
{label}
</ComboboxLabel>
{!!description && (
<BodyShort
className={cl(
"aksel-form-field__description aksel-combobox2__description",
{ "aksel-sr-only": hideLabel },
)}
</Dialog.Header>
}
>
<ComboboxPopup>
<ComboboxFilter />
<ComboboxList />
</ComboboxPopup>
</ComboboxOverlay>
</ComboboxRoot>
);
}
id={inputDescriptionId}
size={size}
as="div"
>
{description}
</BodyShort>
)}
<ComboboxTrigger readOnly={readOnly} {...inputProps}>
<ComboboxField ref={ref} hasError={hasError} />
</ComboboxTrigger>
<div
className="aksel-form-field__error aksel-combobox2__error"
id={errorId}
aria-relevant="additions removals"
aria-live="polite"
>
{showErrorMsg && (
<ErrorMessage size={size} showIcon>
{error}
</ErrorMessage>
)}
</div>
{name &&
rest.value.map((val) => (
<input key={val} type="hidden" name={name} value={val} />
))}
<ComboboxOverlay
mobileHeader={
<Dialog.Header>
<Dialog.Title>{label}</Dialog.Title>
{!!description && (
<Dialog.Description>{description}</Dialog.Description>
)}
</Dialog.Header>
}
>
<ComboboxPopup>
<ComboboxFilter />
<ComboboxList />
</ComboboxPopup>
</ComboboxOverlay>
</ComboboxRoot>
);
},
) as <T extends ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>>(
props: ComboboxProps<T> & React.RefAttributes<HTMLButtonElement>,
) => React.ReactElement;
// ----------- ComboboxSingle ----------- //
@@ -149,44 +154,67 @@ interface ComboboxSingleProps<
* Called when selected option changes.
*/
onChange?: (
newValue: NonNullable<ComboboxProps<T>["value"]>[number] | undefined,
newValue: NonNullable<ComboboxProps<T>["value"]>[number] | null,
) => void;
/**
* Value of the selected option.
*/
value?: NonNullable<ComboboxProps<T>["value"]>[number];
value?: NonNullable<ComboboxProps<T>["value"]>[number] | null;
/**
* Initial value when uncontrolled.
*/
defaultValue?: NonNullable<ComboboxProps<T>["value"]>[number];
}
function ComboboxSingle<
T extends ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>,
>({ onChange, value, defaultValue, ...rest }: ComboboxSingleProps<T>) {
const [localValue, setLocalValue] = useControllableState({
defaultValue,
value,
onChange,
});
const onToggleOption: ComboboxProps<T>["onToggleOption"] = (
option,
isSelected,
/**
* Component for selecting a value from a list of options that can be filtered.
*
* **WARNING: This component is in active development and may receive breaking changes outside major releases!**
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/TODO)
* @see 🏷️ {@link ComboboxSingleProps}
*
* @example
* ```jsx
* <ComboboxSingle
* label="Select country"
* name="country"
* options={countries}
* />
* ```
*/
const ComboboxSingle = forwardRef(
<T extends ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>>(
{ onChange, value, defaultValue, ...rest }: ComboboxSingleProps<T>,
ref: React.ForwardedRef<HTMLButtonElement>,
) => {
setLocalValue(isSelected ? option.value : undefined);
}; // TODO: Dumt at ikke får hele option-objektet... Legge til i onChange? Hvis vi tilbyr onToggle så blir det så likt onChange...
const [localValue, setLocalValue] = useControllableState({
defaultValue,
value,
onChange,
});
return (
<Combobox
{...rest}
value={localValue ? [localValue] : []}
defaultValue={defaultValue ? [defaultValue] : []}
multiple={false}
onToggleOption={onToggleOption}
/>
);
}
const onToggleOption: ComboboxProps<T>["onToggleOption"] = (
option,
isSelected,
) => {
setLocalValue(isSelected ? option.value : null);
}; // TODO: Dumt at ikke får hele option-objektet... Legge til i onChange? Hvis vi tilbyr onToggle så blir det så likt onChange...
return (
<Combobox
{...rest}
ref={ref}
value={localValue ? [localValue] : []}
defaultValue={defaultValue ? [defaultValue] : []}
multiple={false}
onToggleOption={onToggleOption}
/>
);
},
) as <T extends ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>>(
props: ComboboxSingleProps<T> & React.RefAttributes<HTMLButtonElement>,
) => React.ReactElement;
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace ComboboxSingle {
@@ -157,7 +157,7 @@ export const Name = () => (
onSubmit={(event) => {
event.preventDefault();
const formData = new FormData(event.target);
const land = formData.getAll("land");
const land = formData.get("land");
console.log(land);
}}
>
@@ -168,7 +168,7 @@ export const Name = () => (
);
export const Controlled = () => {
const [value, setValue] = useState<MyOption["value"] | undefined>("opt-1");
const [value, setValue] = useState<MyOption["value"] | null>("opt-1");
return (
<VStack gap="space-8" minWidth="250px">
@@ -180,7 +180,7 @@ export const Controlled = () => {
/>
Selected: {value}
<div>
<button type="button" onClick={() => setValue(undefined)}>
<button type="button" onClick={() => setValue(null)}>
Clear
</button>
</div>
@@ -1,5 +1,5 @@
import type { Meta } from "@storybook/react-vite";
import React, { useState } from "react";
import React, { useRef, useState } from "react";
import { expect, userEvent, within } from "storybook/test";
import { ComboboxSingle } from "./Combobox";
import { ComboboxFilter } from "./filter/ComboboxFilter";
@@ -60,7 +60,7 @@ const groupedOptions = [
function BaseCombobox<
T extends ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>,
>(props: Partial<ComboboxSingle.Props<T>>) {
const [value, setValue] = useState<string | undefined>("option-1");
const [value, setValue] = useState<string | null>("opt-1");
return (
<ComboboxSingle
@@ -81,6 +81,27 @@ AccessibleNameAndDesc.play = async ({ canvasElement }) => {
await expect(trigger).toHaveAccessibleDescription("Landet hvor du er født.");
};
export const FocusWithRef = () => {
const ref = useRef<HTMLButtonElement>(null);
return (
<div>
<button type="button" onClick={() => ref.current?.focus()}>
Fokuser combobox
</button>
<ComboboxSingle ref={ref} options={countries} label="Velg land" />
</div>
);
};
FocusWithRef.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const combobox = await canvas.findByRole("combobox", { name: "Velg land" });
await expect(combobox).not.toHaveFocus();
await userEvent.click(
canvas.getByRole("button", { name: "Fokuser combobox" }),
);
await expect(combobox).toHaveFocus();
};
let optionMemoTestRenderCnt = 0;
const optionMemoTestRenderFn = (
optOrGroup: (typeof groupedOptions)[number],
@@ -93,7 +114,7 @@ const optionMemoTestRenderFn = (
return optOrGroup.label;
};
export const OptionMemoization = () => {
const [value, setValue] = useState(["option-1"]);
const [value, setValue] = useState(["opt-1"]);
return (
<div style={{ minHeight: "450px" }}>
@@ -1,6 +1,7 @@
import React, { forwardRef } from "react";
import React, { forwardRef, useEffect, useState } from "react";
import { ChevronDownIcon } from "@navikt/aksel-icons";
import { cl } from "../../../utils/helpers";
import { useI18n } from "../../../utils/i18n/i18n.hooks";
import {
type ComboboxRootProps,
useComboboxRootContext,
@@ -14,10 +15,23 @@ interface ComboboxFieldProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
const ComboboxField = forwardRef<HTMLButtonElement, ComboboxFieldProps>(
({ children, hasError, ...rest }, ref) => {
const { options, value, size } = useComboboxRootContext();
const translate = useI18n("Combobox2");
const labels = getLabels(value, options);
const label =
children ?? (labels.length > 1 ? `${labels.length} valgt` : labels[0]);
children ??
(labels.length > 1
? translate("selectedCount", { count: labels.length })
: labels[0]);
// Workaround for issue where VO on Safari announces the old label when focusing this button right after selecting a new option
const [ariaLabel, setAriaLabel] = useState<string>();
useEffect(() => {
if (label) {
setAriaLabel(label);
setTimeout(() => setAriaLabel(undefined), 100);
}
}, [label]);
return (
<div className="aksel-combobox2__field-container">
@@ -30,8 +44,7 @@ const ComboboxField = forwardRef<HTMLButtonElement, ComboboxFieldProps>(
"aksel-body-short",
`aksel-body-short--${size ?? "medium"}`,
)}
// TODO: This results in other problems. Find another fix!
//aria-label={label} // Without this, VO on Safari will announce the old label when focusing this button right after selecting a new option
aria-label={ariaLabel}
{...rest}
>
{label}
@@ -42,6 +42,7 @@ export const ComboboxFilter = (props: ComboboxFilterProps) => {
}
}}
aria-autocomplete="list"
aria-expanded="true"
inputMode="text"
/>
</Listbox.InputSlot>
@@ -55,7 +55,7 @@ function ComboboxListContent<
useComboboxRootContext();
const memoizedOnToggleOption = useEventCallback(onToggleOption);
const deferredFilterString = useDeferredValue(filterString);
const maybeAnnouceHits = useHitsAnnouncer(deferredFilterString, filterString);
const maybeAnnouceHits = useHitsAnnouncer(deferredFilterString);
const translate = useI18n("Combobox2");
const localOnToggleOption = useCallback(
@@ -80,6 +80,7 @@ function ComboboxListContent<
maybeAnnouceHits(filteredOptions);
if (filteredOptions.length === 0) {
// TODO: Consider moving outside of role=listbox
return (
<div className="aksel-combobox2__no-results">{translate("noHits")}</div>
);
@@ -18,9 +18,9 @@ function getNumberOfOptions<T extends ComboboxOptionData>(
}, 0);
}
function useHitsAnnouncer(deferredFilterString: string, filterString: string) {
function useHitsAnnouncer(deferredFilterString: string) {
const translate = useI18n("Combobox2");
const shouldAnnounceRef = useRef(false);
const oldFilterStringRef = useRef(deferredFilterString);
const announce = useAnnounce();
const announceHits = useMemo(() => {
@@ -50,20 +50,20 @@ function useHitsAnnouncer(deferredFilterString: string, filterString: string) {
ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>
)[],
) => {
// The options are filtered based on deferredFilterString, so we need to wait for it to settle.
if (deferredFilterString !== filterString) {
shouldAnnounceRef.current = true;
} else if (shouldAnnounceRef.current) {
if (filteredOptions.length === 0) {
announceHits.clear();
announceNoHits();
} else {
const hits = getNumberOfOptions(filteredOptions);
announceNoHits.clear();
announceHits(hits);
}
shouldAnnounceRef.current = false;
// Announce number of hits when filter string changes.
// We can't do this in a useEffect b.c. of how ComboboxList is structured. Consider refactoring.
if (deferredFilterString === oldFilterStringRef.current) {
return;
}
if (filteredOptions.length === 0) {
announceHits.clear();
announceNoHits();
} else {
const hits = getNumberOfOptions(filteredOptions);
announceNoHits.clear();
announceHits(hits);
}
oldFilterStringRef.current = deferredFilterString;
};
}
@@ -3,6 +3,7 @@ import { CheckmarkHeavyIcon } from "@navikt/aksel-icons";
import { Listbox } from "../../../utils/components/Listbox/root/ListboxRoot";
import type { ComboboxListProps } from "../list/ComboboxList";
import type { ComboboxOptionData } from "../root/ComboboxRoot";
import { highlightSubstring } from "./highlightSubstring";
export interface ComboboxOptionProps<T extends ComboboxOptionData> {
option: T;
@@ -49,31 +50,6 @@ function ComboboxOptionComponent<T extends ComboboxOptionData>({
);
}
const highlightSubstring = (text: string, substring: string) => {
const indexOfHighlightedText = text
.toLocaleLowerCase()
.indexOf(substring.toLocaleLowerCase());
if (indexOfHighlightedText === -1) {
// This can happen if the consumer has implemented their own filtering logic
return text;
}
const start = text.substring(0, indexOfHighlightedText);
const highlight = text.substring(
indexOfHighlightedText,
indexOfHighlightedText + substring.length,
);
const end = text.substring(indexOfHighlightedText + substring.length);
return (
// Aria-label is used to fix testing-library wrongly evaluating the accessible name of the option when highlighting text
// biome-ignore lint/a11y/useAriaPropsSupportedByRole: Doesn't matter if aria-label doesn't work
<span aria-label={text}>
{start}
{highlight && <mark>{highlight}</mark>}
{end}
</span>
);
};
export const ComboboxOption = React.memo(
ComboboxOptionComponent,
) as typeof ComboboxOptionComponent;
@@ -0,0 +1,27 @@
import { describe, expect, test } from "vitest";
import { getHighlightRange } from "./highlightSubstring";
const highlighted = (text: string, substring: string) => {
const range = getHighlightRange(text, substring);
return range && text.substring(...range);
};
describe("getHighlightRange", () => {
test("matches case-insensitively", () => {
expect(getHighlightRange("Norge", "OR")).toEqual([1, 3]);
});
test("returns null when there is no match", () => {
expect(getHighlightRange("Norge", "x")).toBeUndefined();
});
test("handles characters that get longer when lowercased", () => {
expect(highlighted("İstanbul", "an")).toBe("an");
expect(highlighted("İzmir og Antalya", "an")).toBe("An");
expect(highlighted("İzmir", "İz")).toBe("İz");
});
test("includes the whole original character when only part of its lowercase form matches", () => {
expect(highlighted("İstanbul", "i")).toBe("İ");
});
});
@@ -0,0 +1,50 @@
import React from "react";
function highlightSubstring(text: string, substring: string) {
const range = getHighlightRange(text, substring);
if (!range) {
// This can happen if the consumer has implemented their own filtering logic
return text;
}
const [rangeStart, rangeEnd] = range;
return (
// Aria-label is used to fix testing-library wrongly evaluating the accessible name of the option when highlighting text
// biome-ignore lint/a11y/useAriaPropsSupportedByRole: Doesn't matter if it doesn't work in normal browsers
<span aria-label={text}>
{text.substring(0, rangeStart)}
<mark>{text.substring(rangeStart, rangeEnd)}</mark>
{text.substring(rangeEnd)}
</span>
);
}
/**
* Finds the first case-insensitive occurrence of `substring` in `text`.
* Handles characters whose lowercase form has a different length than the original (e.g. "İ").
* @returns The `[start, end]` range of the match in `text`, or `undefined` if there is no match.
*/
function getHighlightRange(text: string, substring: string) {
const lowerSubstring = substring.toLocaleLowerCase();
const matchIndex = text.toLocaleLowerCase().indexOf(lowerSubstring);
if (matchIndex === -1) {
return;
}
const matchEnd = matchIndex + lowerSubstring.length;
let start = 0;
let origPos = 0;
let lowerPos = 0;
for (const char of text) {
if (lowerPos <= matchIndex) {
start = origPos;
}
origPos += char.length;
lowerPos += char.toLocaleLowerCase().length;
if (lowerPos >= matchEnd) {
return [start, origPos] as const;
}
}
}
export { highlightSubstring, getHighlightRange };
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from "react";
import React, { useLayoutEffect, useRef, useState } from "react";
import { Listbox } from "../../../utils/components/Listbox/root/ListboxRoot";
import { createStrictContext } from "../../../utils/helpers";
import { useComboboxRootContext } from "../root/ComboboxRoot";
@@ -29,7 +29,7 @@ export const ComboboxPopup = ({ children }: ComboboxPopupProps) => {
const rootContext = useComboboxRootContext();
const ref = useRef<HTMLDivElement | null>(null);
useEffect(
useLayoutEffect(
function onOpen() {
const canHover = window.matchMedia("(hover: hover)").matches;
@@ -1,10 +1,8 @@
import {
compareAsc,
compareDesc,
format,
isSameMonth,
setYear,
} from "date-fns";
import { compareAsc } from "date-fns/compareAsc";
import { compareDesc } from "date-fns/compareDesc";
import { format } from "date-fns/format";
import { isSameMonth } from "date-fns/isSameMonth";
import { setYear } from "date-fns/setYear";
import React, { useEffect, useRef } from "react";
import { dateIsInCurrentMonth, isMatch } from "../../utils/date";
import { cl } from "../../utils/helpers";
@@ -1,11 +1,9 @@
import {
format,
isAfter,
isBefore,
setYear,
startOfMonth,
startOfYear,
} from "date-fns";
import { format } from "date-fns/format";
import { isAfter } from "date-fns/isAfter";
import { isBefore } from "date-fns/isBefore";
import { setYear } from "date-fns/setYear";
import { startOfMonth } from "date-fns/startOfMonth";
import { startOfYear } from "date-fns/startOfYear";
import React from "react";
import { ArrowLeftIcon, ArrowRightIcon } from "@navikt/aksel-icons";
import { Button } from "../../button";
@@ -1,4 +1,5 @@
import { type Locale, isSameYear } from "date-fns";
import type { Locale } from "date-fns";
import { isSameYear } from "date-fns/isSameYear";
import React from "react";
import { clampDisplayYear } from "../../utils/date";
import { createStrictContext } from "../../utils/helpers";
@@ -1,4 +1,4 @@
import { setYear } from "date-fns";
import { setYear } from "date-fns/setYear";
import type { Matcher } from "../../utils/components/date/Date.typeutils";
import { isMatch } from "../../utils/date";
@@ -1,4 +1,7 @@
import { isSameMonth, setMonth, setYear, startOfMonth } from "date-fns";
import { isSameMonth } from "date-fns/isSameMonth";
import { setMonth } from "date-fns/setMonth";
import { setYear } from "date-fns/setYear";
import { startOfMonth } from "date-fns/startOfMonth";
import React, { useState } from "react";
import { BodyShort } from "../../typography";
import { isMatch } from "../../utils/date";
@@ -1,19 +1,17 @@
import {
type Locale,
addDays,
addMonths,
addYears,
differenceInDays,
differenceInMonths,
differenceInYears,
endOfMonth,
endOfYear,
format,
startOfDay,
startOfMonth,
startOfYear,
subDays,
} from "date-fns";
import type { Locale } from "date-fns";
import { addDays } from "date-fns/addDays";
import { addMonths } from "date-fns/addMonths";
import { addYears } from "date-fns/addYears";
import { differenceInDays } from "date-fns/differenceInDays";
import { differenceInMonths } from "date-fns/differenceInMonths";
import { differenceInYears } from "date-fns/differenceInYears";
import { endOfMonth } from "date-fns/endOfMonth";
import { endOfYear } from "date-fns/endOfYear";
import { format } from "date-fns/format";
import { startOfDay } from "date-fns/startOfDay";
import { startOfMonth } from "date-fns/startOfMonth";
import { startOfYear } from "date-fns/startOfYear";
import { subDays } from "date-fns/subDays";
import React, { useCallback, useMemo } from "react";
import { Detail } from "../../typography/Detail";
import { useDateLocale, useI18n } from "../../utils/i18n/i18n.hooks";
@@ -1,4 +1,4 @@
import { isBefore } from "date-fns";
import { isBefore } from "date-fns/isBefore";
import React, { forwardRef } from "react";
import { useTimelineContext } from "../root/TimelineRoot.context";
import {
@@ -12,7 +12,7 @@ import {
useHover,
useInteractions,
} from "@floating-ui/react";
import { format } from "date-fns";
import { format } from "date-fns/format";
import React, { forwardRef, useMemo, useState } from "react";
import { useMergeRefs } from "../../utils/hooks";
import { useI18n } from "../../utils/i18n/i18n.hooks";
@@ -1,4 +1,6 @@
import { endOfDay, isSameDay, startOfDay } from "date-fns";
import { endOfDay } from "date-fns/endOfDay";
import { isSameDay } from "date-fns/isSameDay";
import { startOfDay } from "date-fns/startOfDay";
import React, { forwardRef, useMemo, useState } from "react";
import { consoleWarning } from "../../utils/helpers/consoleWarning";
import { TimelineAxisLabelsInternal } from "../axis-labels/TimelineAxisLabelsInternal";
@@ -1,5 +1,6 @@
import { renderHook } from "@testing-library/react";
import { addDays, isSameDay } from "date-fns";
import { addDays } from "date-fns/addDays";
import { isSameDay } from "date-fns/isSameDay";
import { describe, expect, test } from "vitest";
import {
useEarliestDate,
@@ -1,4 +1,8 @@
import { addDays, endOfDay, isAfter, startOfDay, subDays } from "date-fns";
import { addDays } from "date-fns/addDays";
import { endOfDay } from "date-fns/endOfDay";
import { isAfter } from "date-fns/isAfter";
import { startOfDay } from "date-fns/startOfDay";
import { subDays } from "date-fns/subDays";
import { useMemo } from "react";
import { horizontalPositionAndWidth } from "../utils/calc";
import { invisiblePeriods, withinADay } from "../utils/filter";
@@ -1,4 +1,4 @@
import { format } from "date-fns";
import { format } from "date-fns/format";
import React, { forwardRef } from "react";
import { BodyShort } from "../../typography/BodyShort";
import { cl } from "../../utils/helpers";
@@ -1,4 +1,4 @@
import { differenceInMilliseconds } from "date-fns";
import { differenceInMilliseconds } from "date-fns/differenceInMilliseconds";
const position = (date: Date, start: Date, endInclusive: Date) => {
const diff = differenceInMilliseconds(endInclusive, start);
@@ -1,4 +1,4 @@
import { differenceInDays } from "date-fns";
import { differenceInDays } from "date-fns/differenceInDays";
import type { PeriodProps } from "../Timeline.types";
import type { Spatial } from "./types.internal";
@@ -1,4 +1,4 @@
import { format } from "date-fns";
import { format } from "date-fns/format";
import { cl } from "../../utils/helpers";
import type { TFunction } from "../../utils/i18n/i18n.types";
import type { PeriodProps } from "../Timeline.types";
@@ -1,4 +1,4 @@
import { differenceInMilliseconds } from "date-fns";
import { differenceInMilliseconds } from "date-fns/differenceInMilliseconds";
import type { Period, PositionedPeriod } from "./types.internal";
const lastDate = (a: Date, b: Date): number => differenceInMilliseconds(a, b);
@@ -1,11 +1,9 @@
import {
addMonths,
addYears,
format,
isSameDay,
subMonths,
subYears,
} from "date-fns";
import { addMonths } from "date-fns/addMonths";
import { addYears } from "date-fns/addYears";
import { format } from "date-fns/format";
import { isSameDay } from "date-fns/isSameDay";
import { subMonths } from "date-fns/subMonths";
import { subYears } from "date-fns/subYears";
import React, { forwardRef } from "react";
import { Detail } from "../../typography/Detail";
import { useI18n } from "../../utils/i18n/i18n.hooks";
@@ -39,4 +39,12 @@ function ListboxOptions({ children, ...rest }: ListboxOptionsProps) {
);
}
/**
* TODO: Things that should be fixed before we allow focus directly
* on the listbox (i.e. when there's no filter input/combobox elm.)
* - Set aria-activedescendant
* - Consider aria-multiselectable
* - Probably aria-label(ledby)?
*/
export { ListboxOptions };
@@ -1,4 +1,6 @@
import { enGB, nb, nn } from "date-fns/locale";
import { enGB } from "date-fns/locale/en-GB";
import { nb } from "date-fns/locale/nb";
import { nn } from "date-fns/locale/nn";
import { createStrictContext } from "../../helpers";
import type { TFunction } from "../../i18n/i18n.types";
import en_translations from "../../i18n/locales/en";
@@ -1,11 +1,9 @@
import {
addYears,
endOfMonth,
endOfYear,
startOfDay,
startOfMonth,
startOfYear,
} from "date-fns";
import { addYears } from "date-fns/addYears";
import { endOfMonth } from "date-fns/endOfMonth";
import { endOfYear } from "date-fns/endOfYear";
import { startOfDay } from "date-fns/startOfDay";
import { startOfMonth } from "date-fns/startOfMonth";
import { startOfYear } from "date-fns/startOfYear";
import { describe, expect, test } from "vitest";
import { calendarRange } from "./calendar-range";
@@ -1,11 +1,9 @@
import {
addYears,
endOfMonth,
endOfYear,
startOfDay,
startOfMonth,
startOfYear,
} from "date-fns";
import { addYears } from "date-fns/addYears";
import { endOfMonth } from "date-fns/endOfMonth";
import { endOfYear } from "date-fns/endOfYear";
import { startOfDay } from "date-fns/startOfDay";
import { startOfMonth } from "date-fns/startOfMonth";
import { startOfYear } from "date-fns/startOfYear";
/**
* Generates the min and max-dates possible to show and navigate to in the calendar.
@@ -1,4 +1,4 @@
import { setYear } from "date-fns";
import { setYear } from "date-fns/setYear";
import { describe, expect, test } from "vitest";
import { dateIsInCurrentMonth, isValidDate } from "./check-dates";
@@ -1,9 +1,7 @@
import {
differenceInCalendarDays,
isThisMonth,
setYear,
startOfDay,
} from "date-fns";
import { differenceInCalendarDays } from "date-fns/differenceInCalendarDays";
import { isThisMonth } from "date-fns/isThisMonth";
import { setYear } from "date-fns/setYear";
import { startOfDay } from "date-fns/startOfDay";
export const dateIsInCurrentMonth = (
date: Date,
@@ -1,4 +1,5 @@
import { startOfMonth, startOfYear } from "date-fns";
import { startOfMonth } from "date-fns/startOfMonth";
import { startOfYear } from "date-fns/startOfYear";
import { describe, expect, test } from "vitest";
import { isDateOutsideRange } from "./check-dates";
import { clampDisplayMonth, clampDisplayYear } from "./clamp-dates";
@@ -1,4 +1,7 @@
import { isAfter, isBefore, startOfMonth, startOfYear } from "date-fns";
import { isAfter } from "date-fns/isAfter";
import { isBefore } from "date-fns/isBefore";
import { startOfMonth } from "date-fns/startOfMonth";
import { startOfYear } from "date-fns/startOfYear";
/**
* Makes sure the month is within the min and max daterange to avoid showing disabled months
@@ -1,4 +1,4 @@
import { isSameDay } from "date-fns";
import { isSameDay } from "date-fns/isSameDay";
import { isDateRange } from "../components/date/Date.typeutils";
export const disableDate = (
@@ -1,4 +1,4 @@
import { nb } from "date-fns/locale";
import { nb } from "date-fns/locale/nb";
import { describe, expect, test } from "vitest";
import { getMonthOptions, getYearOptions } from "./dropdown-options";
@@ -1,16 +1,14 @@
import {
type Locale,
addYears,
eachMonthOfInterval,
endOfYear,
format,
getMonth,
getYear,
isBefore,
isSameYear,
startOfMonth,
startOfYear,
} from "date-fns";
import type { Locale } from "date-fns";
import { addYears } from "date-fns/addYears";
import { eachMonthOfInterval } from "date-fns/eachMonthOfInterval";
import { endOfYear } from "date-fns/endOfYear";
import { format } from "date-fns/format";
import { getMonth } from "date-fns/getMonth";
import { getYear } from "date-fns/getYear";
import { isBefore } from "date-fns/isBefore";
import { isSameYear } from "date-fns/isSameYear";
import { startOfMonth } from "date-fns/startOfMonth";
import { startOfYear } from "date-fns/startOfYear";
/** Return the months to show in the dropdown. */
export function getMonthOptions({
@@ -1,4 +1,5 @@
import { type Locale, format } from "date-fns";
import type { Locale } from "date-fns";
import { format } from "date-fns/format";
import {
INPUT_DATE_STRING_FORMAT_DATE,
INPUT_DATE_STRING_FORMAT_MONTH,
@@ -1,4 +1,4 @@
import { nb } from "date-fns/locale";
import { nb } from "date-fns/locale/nb";
import { describe, expect, test } from "vitest";
import { formatDateForInput } from "./format-date";
import { parseDate } from "./parse-date";
@@ -1,9 +1,7 @@
import {
differenceInCalendarDays,
isDate,
isSameDay,
isSameMonth,
} from "date-fns";
import { differenceInCalendarDays } from "date-fns/differenceInCalendarDays";
import { isDate } from "date-fns/isDate";
import { isSameDay } from "date-fns/isSameDay";
import { isSameMonth } from "date-fns/isSameMonth";
import {
type DateRange,
type Matcher,
@@ -1,4 +1,7 @@
import { type Locale, isBefore, parse, sub } from "date-fns";
import type { Locale } from "date-fns";
import { isBefore } from "date-fns/isBefore";
import { parse } from "date-fns/parse";
import { sub } from "date-fns/sub";
import { isValidDate } from "./check-dates";
export const INPUT_DATE_STRING_FORMAT_DATE = "dd.MM.yyyy";
@@ -1,5 +1,5 @@
import { getMonth } from "date-fns";
import { nb } from "date-fns/locale";
import { getMonth } from "date-fns/getMonth";
import { nb } from "date-fns/locale/nb";
import { describe, expect, test } from "vitest";
import { isValidDate } from "./check-dates";
import { parseDate } from "./parse-date";
@@ -1,4 +1,5 @@
import { isBefore, isSameDay } from "date-fns";
import { isBefore } from "date-fns/isBefore";
import { isSameDay } from "date-fns/isSameDay";
import type { DateRange } from "../components/date/Date.typeutils";
/**
@@ -5,6 +5,8 @@ const internalTranslations = {
selected: "{count} rows selected",
},
Combobox2: {
/** The "value" that is shown when multiple options are selected. */
selectedCount: "{count} valgt",
/** Label for filter input. Only visible to screen readers. */
filter: "Filtrer",
noHits: "Ingen treff",
@@ -1,4 +1,4 @@
import { enGB } from "date-fns/locale";
import { enGB } from "date-fns/locale/en-GB";
import type { Translations } from "../i18n.types";
export default {
@@ -1,4 +1,5 @@
import { type Locale, nb } from "date-fns/locale";
import type { Locale } from "date-fns";
import { nb } from "date-fns/locale/nb";
interface TranslationMap {
[component: string]: Record<string, string | Record<string, string> | Locale>;
@@ -1,4 +1,4 @@
import { nn } from "date-fns/locale";
import { nn } from "date-fns/locale/nn";
import type { Translations } from "../i18n.types";
export default {
@@ -1,27 +1,33 @@
import Fuse from "fuse.js";
import { type TokenDocT, tokens } from "@navikt/ds-tokens/token_docs";
function tokenIndex(_tokens: TokenDocT[]) {
return new Fuse(_tokens, {
threshold: 0.25,
keys: [
{ name: "name", weight: 0.9 },
{ name: "group", weight: 0.3 },
{ name: "category", weight: 0.7 },
{ name: "categoryTitle", weight: 0.7 },
{ name: "comment", weight: 0.3 },
{ name: "type", weight: 0.7 },
{ name: "role", weight: 0.7 },
{ name: "modifier", weight: 0.7 },
],
//shouldSort: false,
ignoreLocation: true,
});
let tokenSearchIndex: Fuse<TokenDocT> | undefined;
function getTokenSearchIndex() {
if (!tokenSearchIndex) {
tokenSearchIndex = new Fuse(tokens, {
threshold: 0.25,
keys: [
{ name: "name", weight: 0.9 },
{ name: "group", weight: 0.3 },
{ name: "category", weight: 0.7 },
{ name: "categoryTitle", weight: 0.7 },
{ name: "comment", weight: 0.3 },
{ name: "type", weight: 0.7 },
{ name: "role", weight: 0.7 },
{ name: "modifier", weight: 0.7 },
],
//shouldSort: false,
ignoreLocation: true,
});
}
return tokenSearchIndex;
}
function searchTokens(query: string): TokenDocT[] {
return query
? tokenIndex(tokens)
? getTokenSearchIndex()
.search(query)
.map((t) => t.item)
: tokens;
@@ -36,32 +36,41 @@ async function globalSearch(
}
const fuse = await getSearchIndex();
const fuseResults = fuse
.search(query)
.filter((x) => x.score !== undefined && x.score < 0.3)
.sort((a, b) => Number(isOverrideHit(b)) - Number(isOverrideHit(a)));
const overrideResults: FuseResult<SearchPageT>[] = [];
const regularResults: FuseResult<SearchPageT>[] = [];
let totalHits = 0;
const topResults =
fuseResults.length > MAX_TOP_RESULTS
? fuseResults
.filter((x) => x.score !== undefined && x.score < 0.1)
.slice(0, MAX_TOP_RESULTS)
: [];
const topResultSet = new Set(topResults);
const groups = new Map<SearchResultPageTypesT, SearchHitGroupT>();
for (const result of fuseResults) {
if (topResultSet.has(result)) {
for (const result of fuse.search(query)) {
if (result.score === undefined || result.score >= 0.3) {
continue;
}
const type = result.item._type;
const group = groups.get(type) ?? { type, total: 0, hits: [] };
groups.set(type, group);
totalHits++;
(isOverrideHit(result) ? overrideResults : regularResults).push(result);
}
group.total++;
if (group.hits.length < MAX_HITS_PER_TYPE) {
group.hits.push(toSearchHit(result));
const groups = new Map<SearchResultPageTypesT, SearchHitGroupT>();
const topResults: SearchHitT[] = [];
for (const rankedResults of [overrideResults, regularResults]) {
for (const result of rankedResults) {
if (
totalHits > MAX_TOP_RESULTS &&
topResults.length < MAX_TOP_RESULTS &&
result.score !== undefined &&
result.score < 0.1
) {
topResults.push(toSearchHit(result));
continue;
}
const type = result.item._type;
const group = groups.get(type) ?? { type, total: 0, hits: [] };
groups.set(type, group);
group.total++;
if (group.hits.length < MAX_HITS_PER_TYPE) {
group.hits.push(toSearchHit(result));
}
}
}
@@ -72,8 +81,8 @@ async function globalSearch(
return {
result: {
totalHits: fuseResults.length,
topResults: topResults.map(toSearchHit),
totalHits,
topResults,
groupedHits,
},
query,
+4 -4
View File
@@ -44,7 +44,7 @@
"jscodeshift": "17.4.0",
"lodash": "4.18.1",
"lz-string": "^1.5.0",
"next": "16.4.2",
"next": "16.4.3",
"next-logger": "^5.0.2",
"next-sanity": "13.2.3",
"next-themes": "^0.4.6",
@@ -71,9 +71,9 @@
"@babel/core": "^7.19.0",
"@babel/parser": "^7.27.5",
"@babel/traverse": "^7.27.4",
"@google-cloud/bigquery": "^9.0.2",
"@next/bundle-analyzer": "16.4.2",
"@next/eslint-plugin-next": "16.4.2",
"@google-cloud/bigquery": "^9.5.0",
"@next/bundle-analyzer": "16.4.3",
"@next/eslint-plugin-next": "16.4.3",
"@playwright/test": "1.61.1",
"@portabletext/block-tools": "^6.0.0",
"@portabletext/markdown": "^2.5.1",
+25
View File
@@ -167,6 +167,31 @@ module.exports = defineConfig([
"import/no-named-export": "error",
},
},
{
files: ["@navikt/core/react/src/**/*.{ts,tsx}"],
ignores: ["**/*.stories.tsx"],
rules: {
"@typescript-eslint/no-restricted-imports": [
"error",
{
paths: [
{
name: "date-fns",
allowTypeImports: true,
message:
'Import functions from their own path, e.g. `import { format } from "date-fns/format"`. The root barrel makes unbundled Node imports load all of date-fns.',
},
{
name: "date-fns/locale",
allowTypeImports: true,
message:
'Import locales from their own path, e.g. `import { nb } from "date-fns/locale/nb"`. The barrel makes unbundled Node imports load all locales.',
},
],
},
],
},
},
{
files: ["@navikt/**"],
ignores: ["**/*.stories.tsx"],
+1 -1
View File
@@ -80,7 +80,7 @@
"@biomejs/biome": "2.5.14",
"@changesets/cli": "2.31.1",
"@eslint/js": "^10.0.1",
"@github-ui/storybook-addon-performance-panel": "^1.3.0",
"@github-ui/storybook-addon-performance-panel": "^1.3.1",
"@storybook/addon-a11y": "^10.6.3",
"@storybook/addon-docs": "^10.6.3",
"@storybook/addon-themes": "^10.6.3",
+136 -79
View File
@@ -3026,9 +3026,9 @@ __metadata:
languageName: node
linkType: hard
"@github-ui/storybook-addon-performance-panel@npm:^1.3.0":
version: 1.3.0
resolution: "@github-ui/storybook-addon-performance-panel@npm:1.3.0"
"@github-ui/storybook-addon-performance-panel@npm:^1.3.1":
version: 1.3.1
resolution: "@github-ui/storybook-addon-performance-panel@npm:1.3.1"
peerDependencies:
"@storybook/icons": ^2.0.0
"@storybook/react": ^10.0.0 || >=11.0.0-0 <12.0.0-0
@@ -3039,25 +3039,31 @@ __metadata:
optional: true
react:
optional: true
checksum: 10/25c956d23a99ea49f11b5dc7dadcaf7084bc7df0ce4644c97db66d13f10a5c76d777f1ae1259cb1a5d6cf60afcb180413429e9c56638281b6e28521db53f750a
checksum: 10/f9f1eca61b071d1f1e3eadc9302f6aa17557732225bff88580e259ddcbe33469c045b273daa095095fcc044b63635ce86e36d5adb2c0a5dbeba5eb8ca20f3e3b
languageName: node
linkType: hard
"@google-cloud/bigquery@npm:^9.0.2":
version: 9.0.2
resolution: "@google-cloud/bigquery@npm:9.0.2"
"@google-cloud/bigquery@npm:^9.5.0":
version: 9.5.0
resolution: "@google-cloud/bigquery@npm:9.5.0"
dependencies:
"@google-cloud/common": "npm:^8.0.0"
"@google-cloud/paginator": "npm:^7.0.0"
"@google-cloud/precise-date": "npm:^6.0.0"
"@google-cloud/promisify": "npm:^6.0.0"
apache-arrow: "npm:^21.0.0"
arrify: "npm:^3.0.0"
big.js: "npm:^7.0.0"
duplexify: "npm:^4.1.3"
extend: "npm:^3.0.2"
stream-events: "npm:^1.0.5"
teeny-request: "npm:^11.0.0"
checksum: 10/4db3af38f51b80bffada28a6dbe2cec02c1f7ccee82a37dbf1b4790584e1bd0606eb34e0eef675571289dd31dfff41931f049d6e4012e09498f5c357266cd266
peerDependencies:
"@google-cloud/bigquery-storage": ^6.3.0
peerDependenciesMeta:
"@google-cloud/bigquery-storage":
optional: true
checksum: 10/b068c895bec6f0fa64cb65e05590f470edec9409725fe6471d476eb7d053036091d754ec25b024318335cbe813438fea56da2082c3353afe9801a59fcb640775
languageName: node
linkType: hard
@@ -3109,11 +3115,11 @@ __metadata:
linkType: hard
"@hono/node-server@npm:^1.19.9 || ^2.0.5, @hono/node-server@npm:^2.0.12":
version: 2.1.1
resolution: "@hono/node-server@npm:2.1.1"
version: 2.1.5
resolution: "@hono/node-server@npm:2.1.5"
peerDependencies:
hono: ^4
checksum: 10/af031ec46684c7a201cf70ce2e14f17dcd64624b42bbbf5a84dc54d7a2a9fe77a96d3d0280a16affcbc99a2de682f8687ee63cf6f6cc3de2cd5bee22ebe1f7fe
hono: ^4 || ^5.0.0-0
checksum: 10/4b6f21fe37f956a61856b6f517f91543962f32466a88477d362e8cff77272e541ea6388b8026275e1a2a1976d098c9bf4c3e1d8f8087505e8eac777f0f872901
languageName: node
linkType: hard
@@ -5086,84 +5092,91 @@ __metadata:
languageName: node
linkType: hard
"@next/bundle-analyzer@npm:16.4.2":
version: 16.4.2
resolution: "@next/bundle-analyzer@npm:16.4.2"
"@next/bundle-analyzer@npm:16.4.3":
version: 16.4.3
resolution: "@next/bundle-analyzer@npm:16.4.3"
dependencies:
webpack-bundle-analyzer: "npm:4.10.1"
checksum: 10/88aff93dac5666bda4dae47c3c28463470abcc2361d3397e37ed20074b58c0a017305f986097cceac5520052caf7fd35b079f1b45b8e3cb19b1438a3564dfe12
checksum: 10/cc29a60472950a8c178af34f7e164f8b903455811aeb01b12ae3f2721124485cc20dcf4e941b509c2dbb82e8023174ea01a69d4a90735fd6c7657f35fb245a60
languageName: node
linkType: hard
"@next/env@npm:16.4.2, @next/env@npm:^15.0.0 || ^16.0.0":
"@next/env@npm:16.4.3":
version: 16.4.3
resolution: "@next/env@npm:16.4.3"
checksum: 10/61fc8844d0432a0030694486d3a2dc1af58770f81d0f47b7b8b2ce8b8c236f56b83da0d16ba16fef9f2b7d4ce5de6af06e5ca73b722a3d733821b4858b71ddb3
languageName: node
linkType: hard
"@next/env@npm:^15.0.0 || ^16.0.0":
version: 16.4.2
resolution: "@next/env@npm:16.4.2"
checksum: 10/6ac734ca99aa1fb31d1130fd6cffdec711ef460cb2b87fa880fa00201c06f61de62bc5d5b7835aa2861087e9587aa1a535ba85dbf37a40979075bc50dac3588f
languageName: node
linkType: hard
"@next/eslint-plugin-next@npm:16.4.2":
version: 16.4.2
resolution: "@next/eslint-plugin-next@npm:16.4.2"
"@next/eslint-plugin-next@npm:16.4.3":
version: 16.4.3
resolution: "@next/eslint-plugin-next@npm:16.4.3"
dependencies:
"@eslint-community/eslint-utils": "npm:4.9.1"
fast-glob: "npm:3.3.1"
checksum: 10/961ba902b831ddae05a3519862fc43e969be2cd47e0bdc75a070ddfe7136d22c0b7428bfe1fc7863e44c0af913a43317060e318329bb47907d9fd1a1fbc02420
checksum: 10/b00bad73eedf18224e9c7700a6f8e990ace898190e2fc004b4dfc5f592d968da2b47b9cffea163b524bddba2aebf9817245bcd6f5259074fb435cb2a1ecb9994
languageName: node
linkType: hard
"@next/swc-darwin-arm64@npm:16.4.2":
version: 16.4.2
resolution: "@next/swc-darwin-arm64@npm:16.4.2"
"@next/swc-darwin-arm64@npm:16.4.3":
version: 16.4.3
resolution: "@next/swc-darwin-arm64@npm:16.4.3"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@next/swc-darwin-x64@npm:16.4.2":
version: 16.4.2
resolution: "@next/swc-darwin-x64@npm:16.4.2"
"@next/swc-darwin-x64@npm:16.4.3":
version: 16.4.3
resolution: "@next/swc-darwin-x64@npm:16.4.3"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@next/swc-linux-arm64-gnu@npm:16.4.2":
version: 16.4.2
resolution: "@next/swc-linux-arm64-gnu@npm:16.4.2"
"@next/swc-linux-arm64-gnu@npm:16.4.3":
version: 16.4.3
resolution: "@next/swc-linux-arm64-gnu@npm:16.4.3"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@next/swc-linux-arm64-musl@npm:16.4.2":
version: 16.4.2
resolution: "@next/swc-linux-arm64-musl@npm:16.4.2"
"@next/swc-linux-arm64-musl@npm:16.4.3":
version: 16.4.3
resolution: "@next/swc-linux-arm64-musl@npm:16.4.3"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@next/swc-linux-x64-gnu@npm:16.4.2":
version: 16.4.2
resolution: "@next/swc-linux-x64-gnu@npm:16.4.2"
"@next/swc-linux-x64-gnu@npm:16.4.3":
version: 16.4.3
resolution: "@next/swc-linux-x64-gnu@npm:16.4.3"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@next/swc-linux-x64-musl@npm:16.4.2":
version: 16.4.2
resolution: "@next/swc-linux-x64-musl@npm:16.4.2"
"@next/swc-linux-x64-musl@npm:16.4.3":
version: 16.4.3
resolution: "@next/swc-linux-x64-musl@npm:16.4.3"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@next/swc-win32-arm64-msvc@npm:16.4.2":
version: 16.4.2
resolution: "@next/swc-win32-arm64-msvc@npm:16.4.2"
"@next/swc-win32-arm64-msvc@npm:16.4.3":
version: 16.4.3
resolution: "@next/swc-win32-arm64-msvc@npm:16.4.3"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@next/swc-win32-x64-msvc@npm:16.4.2":
version: 16.4.2
resolution: "@next/swc-win32-x64-msvc@npm:16.4.2"
"@next/swc-win32-x64-msvc@npm:16.4.3":
version: 16.4.3
resolution: "@next/swc-win32-x64-msvc@npm:16.4.3"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
@@ -8509,6 +8522,15 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:^25.2.0":
version: 25.9.9
resolution: "@types/node@npm:25.9.9"
dependencies:
undici-types: "npm:>=7.24.0 <7.24.7"
checksum: 10/8accbdd15bc035db3b20bf9353dabce847a5d3dd1f3976c25e67907f4fd287b00b8163f661fe48cd6d644e30a11e1dffd67e2742233209c7b7cbc500d14d394c
languageName: node
linkType: hard
"@types/parse-json@npm:^4.0.0":
version: 4.0.2
resolution: "@types/parse-json@npm:4.0.2"
@@ -9890,7 +9912,7 @@ __metadata:
"@biomejs/biome": "npm:2.5.14"
"@changesets/cli": "npm:2.31.1"
"@eslint/js": "npm:^10.0.1"
"@github-ui/storybook-addon-performance-panel": "npm:^1.3.0"
"@github-ui/storybook-addon-performance-panel": "npm:^1.3.1"
"@storybook/addon-a11y": "npm:^10.6.3"
"@storybook/addon-docs": "npm:^10.6.3"
"@storybook/addon-themes": "npm:^10.6.3"
@@ -10056,6 +10078,20 @@ __metadata:
languageName: node
linkType: hard
"apache-arrow@npm:^21.0.0":
version: 21.2.0
resolution: "apache-arrow@npm:21.2.0"
dependencies:
"@types/node": "npm:^25.2.0"
flatbuffers: "npm:^25.1.24"
json-with-bigint: "npm:^3.5.3"
tslib: "npm:^2.6.2"
bin:
arrow2csv: bin/arrow2csv.js
checksum: 10/209850cda893c54e87a65d12548120ee1b0ed071682c6113d75aba8a662a2eddfa3d507e8441658842478d56759de2078da3e6a42b57914d37fd53eed2e977b0
languageName: node
linkType: hard
"arg@npm:^5.0.2":
version: 5.0.2
resolution: "arg@npm:5.0.2"
@@ -10696,25 +10732,25 @@ __metadata:
linkType: hard
"body-parser@npm:^2.2.1":
version: 2.3.0
resolution: "body-parser@npm:2.3.0"
version: 2.4.0
resolution: "body-parser@npm:2.4.0"
dependencies:
bytes: "npm:^3.1.2"
content-type: "npm:^2.0.0"
content-type: "npm:^2.1.0"
debug: "npm:^4.4.3"
http-errors: "npm:^2.0.1"
iconv-lite: "npm:^0.7.2"
iconv-lite: "npm:^0.7.3"
on-finished: "npm:^2.4.1"
qs: "npm:^6.15.2"
qs: "npm:^6.16.1"
raw-body: "npm:^3.0.2"
type-is: "npm:^2.1.0"
checksum: 10/94f741ff525358dd0d47f0a042936dc61b9d2e348ff1228c5ffd79f1902c673a317bbb9495b3f7d1db4483befe6ffed1c42bc4cdd8da8f41690530810cfe4dad
checksum: 10/e34971abf7051529d4fae37d3714382e7fa8198bc969f1ec4932af48aef53bba71523545c034ce04fa808d3b15e0d63849e6e93b4f1131d65227e1f414437f68
languageName: node
linkType: hard
"body-parser@npm:~1.20.5":
version: 1.20.8
resolution: "body-parser@npm:1.20.8"
version: 1.20.9
resolution: "body-parser@npm:1.20.9"
dependencies:
bytes: "npm:~3.1.2"
content-type: "npm:~1.0.5"
@@ -10728,7 +10764,7 @@ __metadata:
raw-body: "npm:~2.5.3"
type-is: "npm:~1.6.18"
unpipe: "npm:~1.0.0"
checksum: 10/e5a13ff68066a49823403f93ffe056611d9696182e6de194cd1a3c5b2159457494d4133b4666bad591d7a5a6eb924dd34dd15c83aa2d11c63800ed4d2395b5eb
checksum: 10/e9ccf7a920205d674a83cdfae0e83ed3cfe32af668706034e9832a8bf3b80f06fbc18a7c26759611f72d334311683a4b1ff73661690038a352e8c3b4a424b709
languageName: node
linkType: hard
@@ -14160,6 +14196,13 @@ __metadata:
languageName: node
linkType: hard
"flatbuffers@npm:^25.1.24":
version: 25.9.23
resolution: "flatbuffers@npm:25.9.23"
checksum: 10/e2086e7f9b04f7932a93f6935347d8cdca0cbdcb582cd23b2b7c55aa1f831c6e33ba595c49bebe25767a945fc4698984840692baa470d61db4484e06aac4df15
languageName: node
linkType: hard
"flatted@npm:^3.2.9, flatted@npm:^3.4.2":
version: 3.4.4
resolution: "flatted@npm:3.4.4"
@@ -14193,12 +14236,12 @@ __metadata:
linkType: hard
"follow-redirects@npm:^1.0.0":
version: 1.16.1
resolution: "follow-redirects@npm:1.16.1"
version: 1.16.2
resolution: "follow-redirects@npm:1.16.2"
peerDependenciesMeta:
debug:
optional: true
checksum: 10/269c9f38e4bb413d7ab224fb5aa733118288a18e84d4d0a22821a850574624a55d45f2c6735fc09fc1cceedb6343a5539daf6d2dd70bce8980c8336d230a2f50
checksum: 10/3303addfa9aaf145022c2faa76ecba8052fe325bce00f3992ca2b13f3295590d6a64828c128cfcbc9678569f2f98fbd190d6ca5f505857b07eea9867008a38a3
languageName: node
linkType: hard
@@ -15440,7 +15483,7 @@ __metadata:
languageName: node
linkType: hard
"iconv-lite@npm:^0.7.0, iconv-lite@npm:^0.7.2, iconv-lite@npm:~0.7.0":
"iconv-lite@npm:^0.7.0, iconv-lite@npm:^0.7.2, iconv-lite@npm:^0.7.3, iconv-lite@npm:~0.7.0":
version: 0.7.3
resolution: "iconv-lite@npm:0.7.3"
dependencies:
@@ -16701,6 +16744,13 @@ __metadata:
languageName: node
linkType: hard
"json-with-bigint@npm:^3.5.3":
version: 3.5.12
resolution: "json-with-bigint@npm:3.5.12"
checksum: 10/096a98e435311ab7cdc645769b949cda0cc723b2f1fd0dd5e86839432a703e804a99136da658536e86b876a85d193a87066352e6723dbcc498439fc6c3c34d5c
languageName: node
linkType: hard
"json5@npm:2.2.3, json5@npm:^2.2.2, json5@npm:^2.2.3":
version: 2.2.3
resolution: "json5@npm:2.2.3"
@@ -18538,19 +18588,19 @@ __metadata:
languageName: node
linkType: hard
"next@npm:16.4.2":
version: 16.4.2
resolution: "next@npm:16.4.2"
"next@npm:16.4.3":
version: 16.4.3
resolution: "next@npm:16.4.3"
dependencies:
"@next/env": "npm:16.4.2"
"@next/swc-darwin-arm64": "npm:16.4.2"
"@next/swc-darwin-x64": "npm:16.4.2"
"@next/swc-linux-arm64-gnu": "npm:16.4.2"
"@next/swc-linux-arm64-musl": "npm:16.4.2"
"@next/swc-linux-x64-gnu": "npm:16.4.2"
"@next/swc-linux-x64-musl": "npm:16.4.2"
"@next/swc-win32-arm64-msvc": "npm:16.4.2"
"@next/swc-win32-x64-msvc": "npm:16.4.2"
"@next/env": "npm:16.4.3"
"@next/swc-darwin-arm64": "npm:16.4.3"
"@next/swc-darwin-x64": "npm:16.4.3"
"@next/swc-linux-arm64-gnu": "npm:16.4.3"
"@next/swc-linux-arm64-musl": "npm:16.4.3"
"@next/swc-linux-x64-gnu": "npm:16.4.3"
"@next/swc-linux-x64-musl": "npm:16.4.3"
"@next/swc-win32-arm64-msvc": "npm:16.4.3"
"@next/swc-win32-x64-msvc": "npm:16.4.3"
"@swc/helpers": "npm:0.5.23"
baseline-browser-mapping: "npm:^2.9.19"
caniuse-lite: "npm:^1.0.30001579"
@@ -18594,7 +18644,7 @@ __metadata:
optional: true
bin:
next: dist/bin/next
checksum: 10/f72af6bfb2d469d3ee29551c221a8fc48f5b4f4a7b685b6e1897458657607186fac34867f42ec66a67e9d5dc72e7e97abf08db875ba0ce921876b19c63617dc4
checksum: 10/4473274bcb1da4921d33b81f8e6c25ac2a9ee959fee49b538bd186550c641111a74b42e85ad406842c1ae471ead49ed98ce1eae699d64f8fa55dc67c14557ee7
languageName: node
linkType: hard
@@ -20419,7 +20469,7 @@ __metadata:
languageName: node
linkType: hard
"qs@npm:^6.12.3, qs@npm:^6.14.0, qs@npm:^6.15.2, qs@npm:~6.15.1":
"qs@npm:^6.12.3, qs@npm:^6.14.0, qs@npm:~6.15.1":
version: 6.15.3
resolution: "qs@npm:6.15.3"
dependencies:
@@ -20429,7 +20479,7 @@ __metadata:
languageName: node
linkType: hard
"qs@npm:~6.16.0":
"qs@npm:^6.16.1, qs@npm:~6.16.0":
version: 6.16.1
resolution: "qs@npm:6.16.1"
dependencies:
@@ -23925,7 +23975,7 @@ __metadata:
languageName: node
linkType: hard
"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.8.0, tslib@npm:^2.8.1":
"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2, tslib@npm:^2.8.0, tslib@npm:^2.8.1":
version: 2.8.1
resolution: "tslib@npm:2.8.1"
checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7
@@ -24214,6 +24264,13 @@ __metadata:
languageName: node
linkType: hard
"undici-types@npm:>=7.24.0 <7.24.7":
version: 7.24.6
resolution: "undici-types@npm:7.24.6"
checksum: 10/defc9538b952e3c15b8526596c591f7c1f0c7605ad27a2b7feddbea7ef2e3003f3eda2cdb051a3cb1a2185e3893100fd9cb925c799db99d48131ea63b5233d10
languageName: node
linkType: hard
"undici-types@npm:~7.18.0":
version: 7.18.2
resolution: "undici-types@npm:7.18.2"
@@ -25135,12 +25192,12 @@ __metadata:
"@babel/core": "npm:^7.19.0"
"@babel/parser": "npm:^7.27.5"
"@babel/traverse": "npm:^7.27.4"
"@google-cloud/bigquery": "npm:^9.0.2"
"@google-cloud/bigquery": "npm:^9.5.0"
"@navikt/analytics-types": "npm:0.0.9"
"@navikt/next-logger": "npm:5.0.1"
"@navikt/oasis": "npm:4.1.4"
"@next/bundle-analyzer": "npm:16.4.2"
"@next/eslint-plugin-next": "npm:16.4.2"
"@next/bundle-analyzer": "npm:16.4.3"
"@next/eslint-plugin-next": "npm:16.4.3"
"@playwright/test": "npm:1.61.1"
"@portabletext/block-tools": "npm:^6.0.0"
"@portabletext/markdown": "npm:^2.5.1"
@@ -25171,7 +25228,7 @@ __metadata:
json5: "npm:2.2.3"
lodash: "npm:4.18.1"
lz-string: "npm:^1.5.0"
next: "npm:16.4.2"
next: "npm:16.4.3"
next-logger: "npm:^5.0.2"
next-sanity: "npm:13.2.3"
next-themes: "npm:^0.4.6"