diff --git a/components/calendar/package.json b/components/calendar/package.json
index 54128c44e..4b1d2bfe5 100644
--- a/components/calendar/package.json
+++ b/components/calendar/package.json
@@ -38,7 +38,7 @@
"@dhis2-ui/input": "10.16.4",
"@dhis2-ui/layer": "10.16.4",
"@dhis2-ui/popper": "10.16.4",
- "@dhis2/multi-calendar-dates": "2.1.2",
+ "@dhis2/multi-calendar-dates": "3.0.0-alpha.1",
"@dhis2/prop-types": "^3.1.2",
"@dhis2/ui-constants": "10.16.4",
"@dhis2/ui-icons": "10.16.4",
diff --git a/components/calendar/src/calendar-input/CLAUDE.md b/components/calendar/src/calendar-input/CLAUDE.md
new file mode 100644
index 000000000..bf430fd96
--- /dev/null
+++ b/components/calendar/src/calendar-input/CLAUDE.md
@@ -0,0 +1,24 @@
+# CalendarInput
+
+`CalendarInput` wraps the `Calendar` widget (`../calendar/`) with a text input, validation, and a clear button. Both get their date logic — parsing, localization, navigation — from `@dhis2/multi-calendar-dates`'s `useDatePicker`/`useResolvedDirection`/`validateDateString`; this directory has no date-math of its own.
+
+## Data flow
+
+Dates cross the public API as calendar-native `YYYY-MM-DD` strings (in whatever `calendar` prop is set — not necessarily Gregorian ISO), never as `Temporal` objects. Treat any change that would leak a `Temporal`/date-engine object through `onDateSelect`, `date`, or similar props as a breaking change. `CalendarInput`'s `onDateSelect` payload also carries a `validation` object that `CalendarInput` computes itself via `validateDateString` — it is *not* part of `multi-calendar-dates`'s types, so it's declared separately in `../../types/index.d.ts` (`CalendarInputValidation` / `CalendarInputOnDateSelectPayload`). If you change how validation is computed or shaped, update that file too.
+
+## Testing
+
+Two surfaces, used for different things:
+- **Jest** (`__tests__/calendar-input.test.js`, run via `yarn test`) — component behavior and logic: prop handling, validation, callbacks.
+- **Cypress + Cucumber e2e** (`../features/*.feature` + step defs, driven off `../__e2e__/calendar-input.e2e.stories.js`, run via `yarn cy:test` at the repo root) — real rendered output and localization across calendars. Heavier; not run by `yarn test`.
+
+When adding a prop or behavior, add a Jest test. If it's about rendering or localized text for a specific calendar/locale, check whether a `.feature` scenario should exist too.
+
+Coverage decisions in this component are scoped to the three calendars the project actually supports in practice: **Gregorian, Ethiopic, Nepali**. Ethiopic and Nepali behave differently enough from Gregorian (Ethiopic is a native-ICU calendar with its own era handling; Nepali is a custom, non-ICU calendar implemented in `multi-calendar-dates` via a lookup table, not `Intl`) that "tested for Gregorian" should not be assumed to mean "tested for the other two" — verify explicitly rather than assuming parity.
+
+## Gotchas
+
+- **Day-cell tests must assert on visible text, not just the `data-test` attribute.** `data-test` on a day cell is always the full date string (e.g. `2024-10-17`); the *rendered label* is a separate value from `multi-calendar-dates` and can silently regress into something else (it has, historically) without any `data-test`-based assertion catching it. Assert `within(cell).getByText('17')` (or the calendar-appropriate bare day number) in addition to clicking by `data-test`.
+- **`useDatePicker` recomputes on every render of `CalendarInput`, regardless of whether the calendar dropdown is open.** Its output must stay properly memoized inside `multi-calendar-dates` — if you find yourself adding expensive work anywhere in this render path (here or upstream), verify it doesn't run on every keystroke. The `performance (LIBS-763)` describe block in the test file guards against this regressing again; keep it green.
+- **`inputWidth` is deliberately untested.** It's a passthrough to a `styled-jsx` CSS width — purely cosmetic, and `styled-jsx` output is fragile to assert on in jsdom. Don't assume this is an oversight.
+- **`minDate`/`maxDate`/`format`/`strictValidation` get passed to two different functions with different expectations**: `validateDateString` (via `validationOptions`, keyed `minDateString`/`maxDateString`) drives the visible error text, while `useDatePicker` takes the same values under its own key names (`minDate`/`maxDate`) to decide whether to fall back to today internally in strict mode. These used to silently disagree (the `validationOptions` object, keyed for `validateDateString`, was spread straight into `useDatePicker` too) — the widget would keep an out-of-range date selected in strict mode even though the input correctly rejected it. Pass `useDatePicker` its own correctly-named fields explicitly; don't assume an options object built for one of these functions is safe to reuse for the other.
diff --git a/components/calendar/src/calendar-input/__tests__/calendar-input.test.js b/components/calendar/src/calendar-input/__tests__/calendar-input.test.js
index d4d06469b..b63046684 100644
--- a/components/calendar/src/calendar-input/__tests__/calendar-input.test.js
+++ b/components/calendar/src/calendar-input/__tests__/calendar-input.test.js
@@ -40,6 +40,86 @@ describe('Calendar Input', () => {
jest.useRealTimers()
})
+ it('allow selection of a date through the calendar widget (ethiopic)', async () => {
+ jest.useFakeTimers()
+ jest.setSystemTime(new Date('2024-10-22T09:05:00.000Z'))
+
+ const onDateSelectMock = jest.fn()
+ const screen = render(
+
+ )
+
+ const dateInput = within(
+ screen.getByTestId('dhis2-uicore-input')
+ ).getByRole('textbox')
+
+ fireEvent.focus(dateInput)
+
+ const calendar = await screen.findByTestId('calendar')
+ expect(calendar).toBeInTheDocument()
+
+ const todayString = '2017-02-12'
+ const today = within(calendar).getByTestId(todayString)
+
+ // the visible day-cell text must be the bare day number, not the
+ // full date string used for data-test - regressed silently during
+ // the multi-calendar-dates 2.2.0-alpha.1 upgrade (see CLAUDE.md)
+ expect(within(today).getByText('12')).toBeInTheDocument()
+
+ fireEvent.click(today)
+
+ await waitFor(() => {
+ expect(calendar).not.toBeInTheDocument()
+ })
+ expect(onDateSelectMock).toHaveBeenCalledWith(
+ expect.objectContaining({
+ calendarDateString: todayString,
+ })
+ )
+
+ jest.useRealTimers()
+ })
+
+ it('allow selection of a date through the calendar widget (nepali)', async () => {
+ jest.useFakeTimers()
+ jest.setSystemTime(new Date('2024-10-22T09:05:00.000Z'))
+
+ const onDateSelectMock = jest.fn()
+ const screen = render(
+
+ )
+
+ const dateInput = within(
+ screen.getByTestId('dhis2-uicore-input')
+ ).getByRole('textbox')
+
+ fireEvent.focus(dateInput)
+
+ const calendar = await screen.findByTestId('calendar')
+ expect(calendar).toBeInTheDocument()
+
+ const todayString = '2081-07-06'
+ const today = within(calendar).getByTestId(todayString)
+
+ // the visible day-cell text must be the bare day number, not the
+ // full date string used for data-test - regressed silently during
+ // the multi-calendar-dates 2.2.0-alpha.1 upgrade (see CLAUDE.md)
+ expect(within(today).getByText('6')).toBeInTheDocument()
+
+ fireEvent.click(today)
+
+ await waitFor(() => {
+ expect(calendar).not.toBeInTheDocument()
+ })
+ expect(onDateSelectMock).toHaveBeenCalledWith(
+ expect.objectContaining({
+ calendarDateString: todayString,
+ })
+ )
+
+ jest.useRealTimers()
+ })
+
it('allow selection of a date through the input', async () => {
const onDateSelectMock = jest.fn()
const screen = render(
@@ -61,7 +141,291 @@ describe('Calendar Input', () => {
)
})
+ it('rejects input whose detected shape does not match the format prop', () => {
+ // "2024-12-25" is unambiguously YYYY-MM-DD shaped (4-digit group
+ // first). With format="DD-MM-YYYY" that mismatch is rejected
+ // rather than reinterpreted.
+ const screen = render(
+
+ )
+
+ const dateInput = within(
+ screen.getByTestId('dhis2-uicore-input')
+ ).getByRole('textbox')
+
+ fireEvent.change(dateInput, { target: { value: '2024-12-25' } })
+ fireEvent.blur(dateInput)
+
+ expect(
+ screen.getByText(
+ 'Date string format does not match the specified format. Expected DD-MM-YYYY but got YYYY-MM-DD.'
+ )
+ ).toBeInTheDocument()
+ })
+
+ it('accepts the same typed input without the format prop', () => {
+ const screen = render()
+
+ const dateInput = within(
+ screen.getByTestId('dhis2-uicore-input')
+ ).getByRole('textbox')
+
+ fireEvent.change(dateInput, { target: { value: '2024-12-25' } })
+ fireEvent.blur(dateInput)
+
+ expect(
+ screen.queryByText(/does not match the specified format/)
+ ).not.toBeInTheDocument()
+ })
+
+ it('limits the year selector to the current year when pastOnly is set', async () => {
+ jest.useFakeTimers()
+ jest.setSystemTime(new Date('2024-10-22T09:05:00.000Z'))
+
+ const screen = render(
+
+ )
+
+ const dateInput = within(
+ screen.getByTestId('dhis2-uicore-input')
+ ).getByRole('textbox')
+ fireEvent.focus(dateInput)
+
+ const yearSelect = await screen.findByTestId('calendar-year-select')
+ const years = within(yearSelect)
+ .getAllByRole('option')
+ .map((option) => Number(option.value))
+
+ expect(Math.max(...years)).toBe(2024)
+
+ jest.useRealTimers()
+ })
+
+ it('includes future years in the year selector without pastOnly', async () => {
+ jest.useFakeTimers()
+ jest.setSystemTime(new Date('2024-10-22T09:05:00.000Z'))
+
+ const screen = render(
+
+ )
+
+ const dateInput = within(
+ screen.getByTestId('dhis2-uicore-input')
+ ).getByRole('textbox')
+ fireEvent.focus(dateInput)
+
+ const yearSelect = await screen.findByTestId('calendar-year-select')
+ const years = within(yearSelect)
+ .getAllByRole('option')
+ .map((option) => Number(option.value))
+
+ expect(Math.max(...years)).toBeGreaterThan(2024)
+
+ jest.useRealTimers()
+ })
+
+ it('sets the calendar wrapper direction from the dir prop', async () => {
+ const screen = render(
+
+ )
+
+ const dateInput = within(
+ screen.getByTestId('dhis2-uicore-input')
+ ).getByRole('textbox')
+ fireEvent.focus(dateInput)
+
+ const calendar = await screen.findByTestId('calendar')
+ expect(calendar).toHaveAttribute('dir', 'rtl')
+ })
+
+ it('renders day labels using the numberingSystem prop', async () => {
+ jest.useFakeTimers()
+ jest.setSystemTime(new Date('2024-10-22T09:05:00.000Z'))
+
+ const screen = render(
+
+ )
+
+ const dateInput = within(
+ screen.getByTestId('dhis2-uicore-input')
+ ).getByRole('textbox')
+ fireEvent.focus(dateInput)
+
+ const calendar = await screen.findByTestId('calendar')
+ const today = within(calendar).getByTestId('2024-10-22')
+
+ expect(today).toHaveTextContent('٢٢')
+
+ jest.useRealTimers()
+ })
+
+ it('renders weekday headers using the weekDayFormat prop', async () => {
+ const screen = render(
+
+ )
+
+ const dateInput = within(
+ screen.getByTestId('dhis2-uicore-input')
+ ).getByRole('textbox')
+ fireEvent.focus(dateInput)
+
+ const calendar = await screen.findByTestId('calendar')
+ expect(within(calendar).getByText('Monday')).toBeInTheDocument()
+ })
+
+ // Regression coverage for LIBS-763: useDatePicker (in @dhis2/multi-calendar-dates)
+ // used to rebuild the year/month navigation lists and the day-cell grid on every
+ // render - including every keystroke while typing here, with the calendar widget
+ // open - regardless of whether the visible month/calendar/locale had actually
+ // changed. Fixed upstream in @dhis2/multi-calendar-dates@3.0.0-alpha.1.
+ describe('performance (LIBS-763)', () => {
+ // For gregory/ethiopic (native-ICU calendars) the broken memoization showed
+ // up as ~500 redundant `new Intl.DateTimeFormat(...)` constructions for a
+ // single keystroke; count is a deterministic, machine-speed-independent
+ // signal, unlike timing it directly.
+ const countDateTimeFormatConstructions = (fn) => {
+ const realResolvedOptions =
+ Intl.DateTimeFormat.prototype.resolvedOptions
+ let count = 0
+ Intl.DateTimeFormat.prototype.resolvedOptions = function (
+ ...args
+ ) {
+ count++
+ return realResolvedOptions.apply(this, args)
+ }
+ try {
+ fn()
+ } finally {
+ Intl.DateTimeFormat.prototype.resolvedOptions =
+ realResolvedOptions
+ }
+ return count
+ }
+
+ it('does not reconstruct Intl.DateTimeFormat instances while typing (gregory)', async () => {
+ const screen = render(
+
+ )
+ const dateInput = within(
+ screen.getByTestId('dhis2-uicore-input')
+ ).getByRole('textbox')
+ fireEvent.focus(dateInput)
+ await screen.findByTestId('calendar')
+
+ const constructions = countDateTimeFormatConstructions(() => {
+ fireEvent.change(dateInput, {
+ target: { value: '2024-10-1' },
+ })
+ })
+
+ expect(constructions).toBeLessThan(10)
+ })
+
+ it('does not reconstruct Intl.DateTimeFormat instances while typing (ethiopic)', async () => {
+ const screen = render(
+
+ )
+ const dateInput = within(
+ screen.getByTestId('dhis2-uicore-input')
+ ).getByRole('textbox')
+ fireEvent.focus(dateInput)
+ await screen.findByTestId('calendar')
+
+ const constructions = countDateTimeFormatConstructions(() => {
+ fireEvent.change(dateInput, {
+ target: { value: '2017-02-0' },
+ })
+ })
+
+ expect(constructions).toBeLessThan(10)
+ })
+
+ // Nepali is a custom (non-ICU) calendar, so it never goes through
+ // Intl.DateTimeFormat - the same broken memoization instead showed up as
+ // redundant custom-calendar date-conversion work, so time it directly.
+ // 300ms for 10 keystrokes is ~25x the fixed baseline (~13ms) and ~2.5x
+ // below the broken one (~760ms), leaving headroom in both directions.
+ // Skipped in CI: unlike the count-based tests above, this asserts on
+ // wall-clock time, which can be flaky on slower/shared CI runners.
+ // Uncomment to run locally when touching useDatePicker/useNavigation.
+ it.skip('keeps typing fast (nepali)', async () => {
+ const screen = render(
+
+ )
+ const dateInput = within(
+ screen.getByTestId('dhis2-uicore-input')
+ ).getByRole('textbox')
+ fireEvent.focus(dateInput)
+ await screen.findByTestId('calendar')
+
+ const chars = '2081-07-01'.split('')
+ const progressive = chars.map((_, i) =>
+ chars.slice(0, i + 1).join('')
+ )
+
+ const start = process.hrtime.bigint()
+ progressive.forEach((value) =>
+ fireEvent.change(dateInput, { target: { value } })
+ )
+ const elapsedMs = Number(process.hrtime.bigint() - start) / 1e6
+
+ expect(elapsedMs).toBeLessThan(300)
+ })
+ })
+
describe('validation', () => {
+ it('should show a warning instead of an error when strictValidation is false', () => {
+ const onDateSelectMock = jest.fn()
+ const screen = render(
+
+ )
+
+ const dateInputString = '2023-10-12'
+ const dateInput = within(
+ screen.getByTestId('dhis2-uicore-input')
+ ).getByRole('textbox')
+
+ fireEvent.change(dateInput, { target: { value: dateInputString } })
+ fireEvent.blur(dateInput)
+
+ expect(
+ screen.getByText(
+ 'Date 2023-10-12 is less than the minimum allowed date 2024-01-01.'
+ )
+ ).toBeInTheDocument()
+ expect(onDateSelectMock).toHaveBeenCalledWith(
+ expect.objectContaining({
+ validation: expect.objectContaining({
+ warning: true,
+ valid: true,
+ }),
+ })
+ )
+ })
it('should validate minimum date', () => {
const onDateSelectMock = jest.fn()
const screen = render(
@@ -87,6 +451,83 @@ describe('Calendar Input', () => {
).toBeInTheDocument()
expect(onDateSelectMock).toHaveBeenCalledTimes(1)
})
+
+ // Regression test: minDate/maxDate used to reach validateDateString
+ // (as minDateString/maxDateString) but not useDatePicker itself, due to a
+ // key mismatch - so in strict mode the input rejected an out-of-range
+ // date while the widget still selected it. Warning mode is unaffected
+ // (see the test below) since useDatePicker only falls back to today when
+ // strictValidation produces an `error`.
+ it('calendar widget falls back to today for a date the input has flagged as below the minimum (strict mode)', async () => {
+ jest.useFakeTimers()
+ jest.setSystemTime(new Date('2024-10-22T09:05:00.000Z'))
+
+ const screen = render(
+
+ )
+
+ const dateInput = within(
+ screen.getByTestId('dhis2-uicore-input')
+ ).getByRole('textbox')
+
+ fireEvent.change(dateInput, { target: { value: '2020-01-15' } })
+ fireEvent.blur(dateInput)
+
+ expect(
+ screen.getByText(
+ 'Date 2020-01-15 is less than the minimum allowed date 2024-01-01.'
+ )
+ ).toBeInTheDocument()
+
+ fireEvent.focus(dateInput)
+ const calendar = await screen.findByTestId('calendar')
+
+ // expected: the widget falls back to today, like useDatePicker's own
+ // getInvalidDateResult does for a date it recognizes as invalid
+ expect(
+ within(calendar).getByTestId('2024-10-22').querySelector('button')
+ ).toHaveClass('isToday')
+
+ jest.useRealTimers()
+ })
+
+ it('calendar widget correctly still selects an out-of-range date in warning mode', async () => {
+ jest.useFakeTimers()
+ jest.setSystemTime(new Date('2024-10-22T09:05:00.000Z'))
+
+ const screen = render(
+
+ )
+
+ const dateInput = within(
+ screen.getByTestId('dhis2-uicore-input')
+ ).getByRole('textbox')
+
+ fireEvent.change(dateInput, { target: { value: '2020-01-15' } })
+ fireEvent.blur(dateInput)
+
+ expect(
+ screen.getByText(
+ 'Date 2020-01-15 is less than the minimum allowed date 2024-01-01.'
+ )
+ ).toBeInTheDocument()
+
+ fireEvent.focus(dateInput)
+ const calendar = await screen.findByTestId('calendar')
+
+ // warning mode means "flag it, but still let them pick it" - the
+ // widget showing the out-of-range date as selected is intentional
+ expect(
+ within(calendar).getByTestId('2020-01-15').querySelector('button')
+ ).toHaveClass('isSelected')
+
+ jest.useRealTimers()
+ })
+
it('should validate maximum date', () => {
const { getByTestId, getByText } = render(
{
)
).toBeInTheDocument()
})
- // Temporarily disabled
- it.skip('should validate date in ethiopic calendar', () => {
+ it('should validate date in ethiopic calendar', () => {
const onDateSelectMock = jest.fn()
const { getByTestId, getByText, queryByText } = render(
{
getByText('Invalid date in specified calendar')
).toBeInTheDocument()
})
- // ToDo: these scenarios seem to work but they timeout on CI sporadically - ticket: https://dhis2.atlassian.net/browse/LIBS-763
it('should validate date in nepali calendar', () => {
const onDateSelectMock = jest.fn()
const { getByTestId, getByText, queryByText } = render(
diff --git a/components/calendar/src/calendar-input/calendar-input.js b/components/calendar/src/calendar-input/calendar-input.js
index fd722cf43..90f38bb4e 100644
--- a/components/calendar/src/calendar-input/calendar-input.js
+++ b/components/calendar/src/calendar-input/calendar-input.js
@@ -92,7 +92,10 @@ export const CalendarInput = ({
setOpen(false)
},
date,
- ...validationOptions,
+ minDate,
+ maxDate,
+ format,
+ strictValidation,
options: useDatePickerOptions,
})
diff --git a/components/calendar/types/index.d.ts b/components/calendar/types/index.d.ts
index 9fc7c65d7..8f505cbac 100644
--- a/components/calendar/types/index.d.ts
+++ b/components/calendar/types/index.d.ts
@@ -1,5 +1,5 @@
import * as React from 'react'
-import { useDatePicker } from '@dhis2/multi-calendar-dates'
+import { useDatePicker, validateDateString } from '@dhis2/multi-calendar-dates'
import { InputFieldProps } from '@dhis2-ui/input'
export type CalendarDir = 'ltr' | 'rtl'
@@ -7,6 +7,25 @@ export type CalendarDir = 'ltr' | 'rtl'
type CalendarPickerParam = Parameters[0]
type CalendarPickerOptions = CalendarPickerParam['options']
+/**
+ * The `validation` object CalendarInput attaches to its `onDateSelect`
+ * payload. Not produced by the bare `Calendar` widget - CalendarInput
+ * computes it itself via `validateDateString` (whose `error`/`warning`/
+ * `valid` combination depends on the `strictValidation` prop), except
+ * when the date is cleared, in which case it's just `{ valid: true }`.
+ */
+export type CalendarInputValidation = ReturnType
+
+export type CalendarInputOnDateSelectPayload =
+ | {
+ calendarDateString: string
+ validation: CalendarInputValidation
+ }
+ | {
+ calendarDateString: null
+ validation: { valid: true }
+ }
+
export interface CalendarProps {
/**
* the calendar to use such gregory, ethiopic, nepali - full supported list here: https://github.com/dhis2/multi-calendar-dates/blob/main/src/constants/calendars.ts
@@ -62,7 +81,14 @@ export interface CalendarProps {
export const Calendar: React.FC
export type CalendarInputProps = Omit &
- CalendarProps & {
+ Omit & {
+ /**
+ * Called with `{ calendarDateString: null, validation: { valid: true } }`
+ * when the date is cleared, otherwise with `{ calendarDateString: string, validation }`
+ * where `validation` reflects the result of validating against `minDate`/`maxDate`/
+ * `format` (see `CalendarInputValidation`), influenced by `strictValidation`.
+ */
+ onDateSelect: (payload: CalendarInputOnDateSelectPayload) => void
/**
* Optional format for the date. Determines how the date is displayed
* or processed. If not provided it supports both formats
diff --git a/yarn.lock b/yarn.lock
index 2ba6210c5..79d487c40 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -188,7 +188,16 @@
dependencies:
"@babel/highlight" "^7.8.3"
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.29.7", "@babel/code-frame@^7.8.3":
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.8.3":
+ version "7.26.2"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
+ integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.25.9"
+ js-tokens "^4.0.0"
+ picocolors "^1.0.0"
+
+"@babel/code-frame@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.7.tgz#f2fbbfea87c44a21590ec515b778b2c26d8866e7"
integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==
@@ -254,7 +263,18 @@
eslint-visitor-keys "^2.1.0"
semver "^6.3.1"
-"@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.18.7", "@babel/generator@^7.29.7", "@babel/generator@^7.7.2":
+"@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.18.7", "@babel/generator@^7.7.2":
+ version "7.26.2"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.2.tgz#87b75813bec87916210e5e01939a4c823d6bb74f"
+ integrity sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==
+ dependencies:
+ "@babel/parser" "^7.26.2"
+ "@babel/types" "^7.26.0"
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.25"
+ jsesc "^3.0.2"
+
+"@babel/generator@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.7.tgz#cca0b8827e6bcf3ba176788e7f3b180ad6db2fa3"
integrity sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==
@@ -394,12 +414,22 @@
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz#7f0871d99824d23137d60f86fcf6130fd5a1b51f"
integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==
-"@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.24.7", "@babel/helper-validator-identifier@^7.29.7":
+"@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.24.7":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7"
+ integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
+
+"@babel/helper-validator-identifier@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2"
integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==
-"@babel/helper-validator-option@^7.24.7", "@babel/helper-validator-option@^7.29.7":
+"@babel/helper-validator-option@^7.24.7":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72"
+ integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==
+
+"@babel/helper-validator-option@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz#cf315be940213b354eb4abcc0bd01ebe3f73bc2a"
integrity sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==
@@ -413,7 +443,15 @@
"@babel/traverse" "^7.29.7"
"@babel/types" "^7.29.7"
-"@babel/helpers@^7.12.5", "@babel/helpers@^7.29.7":
+"@babel/helpers@^7.12.5":
+ version "7.26.0"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.0.tgz#30e621f1eba5aa45fe6f4868d2e9154d884119a4"
+ integrity sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==
+ dependencies:
+ "@babel/template" "^7.25.9"
+ "@babel/types" "^7.26.0"
+
+"@babel/helpers@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.7.tgz#45abfde7548997e34376c3e69feb475cffb4a607"
integrity sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==
@@ -431,7 +469,14 @@
js-tokens "^4.0.0"
picocolors "^1.0.0"
-"@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.17.0", "@babel/parser@^7.18.8", "@babel/parser@^7.20.7", "@babel/parser@^7.29.7", "@babel/parser@^7.7.0", "@babel/parser@^7.9.4":
+"@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.17.0", "@babel/parser@^7.18.8", "@babel/parser@^7.20.7", "@babel/parser@^7.7.0", "@babel/parser@^7.9.4":
+ version "7.26.2"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.2.tgz#fd7b6f487cfea09889557ef5d4eeb9ff9a5abd11"
+ integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==
+ dependencies:
+ "@babel/types" "^7.26.0"
+
+"@babel/parser@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334"
integrity sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==
@@ -1347,7 +1392,7 @@
dependencies:
regenerator-runtime "^0.14.0"
-"@babel/template@^7.12.7", "@babel/template@^7.29.7", "@babel/template@^7.3.3":
+"@babel/template@^7.12.7", "@babel/template@^7.25.9", "@babel/template@^7.29.7", "@babel/template@^7.3.3":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.29.7.tgz#4d9d4004f645cdd304de958c725162784ecac700"
integrity sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==
@@ -1356,7 +1401,20 @@
"@babel/parser" "^7.29.7"
"@babel/types" "^7.29.7"
-"@babel/traverse@^7.1.6", "@babel/traverse@^7.12.9", "@babel/traverse@^7.18.8", "@babel/traverse@^7.18.9", "@babel/traverse@^7.29.7", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2":
+"@babel/traverse@^7.1.6", "@babel/traverse@^7.12.9", "@babel/traverse@^7.18.8", "@babel/traverse@^7.18.9", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2":
+ version "7.25.9"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84"
+ integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==
+ dependencies:
+ "@babel/code-frame" "^7.25.9"
+ "@babel/generator" "^7.25.9"
+ "@babel/parser" "^7.25.9"
+ "@babel/template" "^7.25.9"
+ "@babel/types" "^7.25.9"
+ debug "^4.3.1"
+ globals "^11.1.0"
+
+"@babel/traverse@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.7.tgz#c47b07a41b95da0907d026b5dd894d98de7d2f2d"
integrity sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==
@@ -1377,7 +1435,7 @@
"@babel/helper-validator-identifier" "^7.14.9"
to-fast-properties "^2.0.0"
-"@babel/types@^7.0.0", "@babel/types@^7.12.6", "@babel/types@^7.12.7", "@babel/types@^7.18.9", "@babel/types@^7.2.0", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.25.2", "@babel/types@^7.29.7", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0":
+"@babel/types@^7.0.0", "@babel/types@^7.12.6", "@babel/types@^7.12.7", "@babel/types@^7.18.9", "@babel/types@^7.2.0", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.25.2", "@babel/types@^7.26.0", "@babel/types@^7.29.7", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92"
integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==
@@ -2132,13 +2190,13 @@
resolved "https://registry.yarnpkg.com/@dhis2/data-engine/-/data-engine-3.17.3.tgz#0347416e9919efbf4d9739c4141fa543f89669ad"
integrity sha512-hLXt7LFrFitR7QgKfGQ3ComTLrY5IAdtERonhdo/SIrsRYWoeVaMiCOkUUzC48pEaeo1/BL5qwA7Tw7jZgROQw==
-"@dhis2/multi-calendar-dates@2.1.2":
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/@dhis2/multi-calendar-dates/-/multi-calendar-dates-2.1.2.tgz#c3dd01e358c3dda4354c6722bb72e077b3ddd9b8"
- integrity sha512-7/EuYZFi266QwyRpK+s79iEiwdOUVBwM+QjKwNUHN3zdYMDmQcWLTo5TujJFg1XnnQ8UhdiRqESq5rgoJkM2fQ==
+"@dhis2/multi-calendar-dates@3.0.0-alpha.1":
+ version "3.0.0-alpha.1"
+ resolved "https://registry.yarnpkg.com/@dhis2/multi-calendar-dates/-/multi-calendar-dates-3.0.0-alpha.1.tgz#2e43e2d00e59ce8b3b7a1d93e5ad8077bea6267b"
+ integrity sha512-Q2L9UnB04jZLRKH8uBjUG8gDi36orixvgaGZJzAIbFXhZ2FVW98yT1lJ05IKbQDZR0rYT+MY6bSo1vE7ARg+yg==
dependencies:
"@dhis2/d2-i18n" "^1.1.3"
- "@js-temporal/polyfill" "0.4.3"
+ "@js-temporal/polyfill" "0.5.1"
classnames "^2.3.2"
"@dhis2/prop-types@^3.1.2":
@@ -3338,13 +3396,12 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
-"@js-temporal/polyfill@0.4.3":
- version "0.4.3"
- resolved "https://registry.npmjs.org/@js-temporal/polyfill/-/polyfill-0.4.3.tgz"
- integrity sha512-6Fmjo/HlkyVCmJzAPnvtEWlcbQUSRhi8qlN9EtJA/wP7FqXsevLLrlojR44kzNzrRkpf7eDJ+z7b4xQD/Ycypw==
+"@js-temporal/polyfill@0.5.1":
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/@js-temporal/polyfill/-/polyfill-0.5.1.tgz#c9726fdb7fbdbc6419292ba94294b10a08852c32"
+ integrity sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ==
dependencies:
- jsbi "^4.1.0"
- tslib "^2.3.1"
+ jsbi "^4.3.0"
"@juggle/resize-observer@^3.3.1":
version "3.4.0"
@@ -12613,10 +12670,10 @@ js2xmlparser@^4.0.2:
dependencies:
xmlcreate "^2.0.4"
-jsbi@^4.1.0:
- version "4.3.0"
- resolved "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz"
- integrity sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==
+jsbi@^4.3.0:
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-4.3.2.tgz#8a4d05d4e09907d73042135b6aa55a6d161ee955"
+ integrity sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew==
jsbn@~0.1.0:
version "0.1.1"
@@ -18467,7 +18524,7 @@ tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
-tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0:
+tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0:
version "2.6.3"
resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz"
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==