diff --git a/CHANGELOG.md b/CHANGELOG.md index fac98d7..b07d11b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - **Modal**, **Slideover**, **Drawer**, **Popover** — with `portal={false}`, a Select, DatePicker, DropdownMenu, Tooltip or other floating layer opened inside the container no longer renders behind it when an ancestor sets a `z-index` above 50. Nested layers now render in the container's stacking context, above its panel. Portalled containers are unchanged. ([#217](https://github.com/ndlabdev/sv5ui/issues/217)) +- **DatePicker**, **DateRangePicker** — the calendar is now anchored to the whole field instead of the calendar icon at its end, so with the default `align="start"` it opens flush under the field's left edge rather than floating to the right over neighbouring content. `align="center"` now centres on the field; `align="end"` is unchanged. ([#218](https://github.com/ndlabdev/sv5ui/issues/218)) ## [2.7.0] - 2026-09-09 diff --git a/src/lib/components/DatePicker/DatePicker.svelte b/src/lib/components/DatePicker/DatePicker.svelte index d8e45cf..a8ccc39 100644 --- a/src/lib/components/DatePicker/DatePicker.svelte +++ b/src/lib/components/DatePicker/DatePicker.svelte @@ -241,6 +241,7 @@ {align} {alignOffset} collisionPadding={8} + customAnchor={rootEl} bind:ref={contentEl} class={classes.content} > diff --git a/src/lib/components/DatePicker/DatePicker.svelte.spec.ts b/src/lib/components/DatePicker/DatePicker.svelte.spec.ts index 0282bc5..033abfd 100644 --- a/src/lib/components/DatePicker/DatePicker.svelte.spec.ts +++ b/src/lib/components/DatePicker/DatePicker.svelte.spec.ts @@ -1,3 +1,4 @@ +import '../../../routes/layout.css' import { describe, expect, it, vi } from 'vitest' import { render } from 'vitest-browser-svelte' import DatePicker from './DatePicker.svelte' @@ -865,4 +866,20 @@ describe('DatePicker', () => { }) }) }) + + describe('popover anchor', () => { + it('aligns the calendar with the start of the field instead of the trigger icon', async () => { + const { container } = render(DatePicker) + container.style.paddingLeft = '48px' + await vi.waitFor(() => expect(getTrigger()).not.toBeNull()) + getTrigger()!.click() + await vi.waitFor(() => { + const field = getTrigger()!.parentElement!.getBoundingClientRect() + const trigger = getTrigger()!.getBoundingClientRect() + const content = getContent()!.getBoundingClientRect() + expect(trigger.left - field.left).toBeGreaterThan(100) + expect(Math.abs(content.left - field.left)).toBeLessThanOrEqual(1) + }) + }) + }) }) diff --git a/src/lib/components/DateRangePicker/DateRangePicker.svelte b/src/lib/components/DateRangePicker/DateRangePicker.svelte index c1531a6..2cf48f7 100644 --- a/src/lib/components/DateRangePicker/DateRangePicker.svelte +++ b/src/lib/components/DateRangePicker/DateRangePicker.svelte @@ -318,6 +318,7 @@ {align} {alignOffset} collisionPadding={8} + customAnchor={rootEl} bind:ref={contentEl} class={classes.content} > diff --git a/src/lib/components/DateRangePicker/DateRangePicker.svelte.spec.ts b/src/lib/components/DateRangePicker/DateRangePicker.svelte.spec.ts index 1df0095..583e3f4 100644 --- a/src/lib/components/DateRangePicker/DateRangePicker.svelte.spec.ts +++ b/src/lib/components/DateRangePicker/DateRangePicker.svelte.spec.ts @@ -1,3 +1,4 @@ +import '../../../routes/layout.css' import { describe, expect, it, vi } from 'vitest' import { render } from 'vitest-browser-svelte' import DateRangePicker from './DateRangePicker.svelte' @@ -850,4 +851,20 @@ describe('DateRangePicker', () => { }) }) }) + + describe('popover anchor', () => { + it('aligns the calendar with the start of the field instead of the trigger icon', async () => { + const { container } = render(DateRangePicker) + container.style.paddingLeft = '48px' + await vi.waitFor(() => expect(getTrigger()).not.toBeNull()) + getTrigger()!.click() + await vi.waitFor(() => { + const field = getTrigger()!.parentElement!.getBoundingClientRect() + const trigger = getTrigger()!.getBoundingClientRect() + const content = getContent()!.getBoundingClientRect() + expect(trigger.left - field.left).toBeGreaterThan(100) + expect(Math.abs(content.left - field.left)).toBeLessThanOrEqual(1) + }) + }) + }) })