Skip to content

Commit

Permalink
feat: initialFocus prop for calendar components (#207)
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte authored Dec 5, 2023
1 parent 970c5ff commit 1044dda
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/thick-guests-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"bits-ui": patch
---

Calendar & Range Calendar: add `initialFocus` prop to autofocus dates on mount
6 changes: 6 additions & 0 deletions src/content/api-reference/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ export const root: APISchema<Calendar.Props> = {
description: "Whether or not the calendar is readonly.",
default: C.FALSE
},
initialFocus: {
type: C.BOOLEAN,
description:
"If `true`, the calendar will focus the selected day, today, or the first day of the month in that order depending on what is visible when the calendar is mounted.",
default: C.FALSE
},
asChild
},
slotProps: {
Expand Down
6 changes: 6 additions & 0 deletions src/content/api-reference/range-calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ export const root: APISchema<RangeCalendar.Props> = {
description: "Whether or not the calendar is readonly.",
default: C.FALSE
},
initialFocus: {
type: C.BOOLEAN,
description:
"If `true`, the calendar will focus the selected day, today, or the first day of the month in that order depending on what is visible when the calendar is mounted.",
default: C.FALSE
},
asChild
},
slotProps: {
Expand Down
9 changes: 9 additions & 0 deletions src/lib/bits/calendar/_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ type Props<Multiple extends boolean = false> = Expand<
* A callback function called when the placeholder changes.
*/
onPlaceholderChange?: OnChangeFn<DateValue>;

/**
* If `true`, the calendar will focus the selected day,
* today, or the first day of the month in that order depending
* on what is visible when the calendar is mounted.
*
* @default false
*/
initialFocus?: boolean;
} & AsChild
>;

Expand Down
18 changes: 16 additions & 2 deletions src/lib/bits/calendar/components/calendar.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script lang="ts">
import { handleCalendarInitialFocus } from "$lib/internal/focus.js";
import { createDispatcher } from "$lib/internal/events.js";
import { melt } from "@melt-ui/svelte";
import { onMount } from "svelte";
import { setCtx, getAttrs } from "../ctx.js";
import type { Props } from "../types.js";
Expand Down Expand Up @@ -29,6 +30,14 @@
export let asChild: $$Props["asChild"] = false;
export let id: $$Props["id"] = undefined;
export let numberOfMonths: $$Props["numberOfMonths"] = undefined;
export let initialFocus: $$Props["initialFocus"] = false;
let el: HTMLElement | undefined = undefined;
onMount(() => {
if (!initialFocus || !el) return;
handleCalendarInitialFocus(el);
});
const {
elements: { calendar },
Expand Down Expand Up @@ -112,7 +121,12 @@
{#if asChild}
<slot {...slotProps} />
{:else}
<div use:melt={builder} {...$$restProps} on:m-keydown={dispatch}>
<div
use:melt={builder}
{...$$restProps}
on:m-keydown={dispatch}
bind:this={el}
>
<slot {...slotProps} />
</div>
{/if}
36 changes: 34 additions & 2 deletions src/lib/bits/range-calendar/_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,42 @@ type Props = Expand<
| "onValueChange"
| "ids"
> & {
placeholder?: DateValue;
/**
* The selected date range. This updates as the user selects
* date ranges in the calendar.
*
* You can bind this to a value to programmatically control the
* value state.
*/
value?: DateRange;
onPlaceholderChange?: OnChangeFn<DateValue>;

/**
* A callback function called when the value changes.
*/
onValueChange?: OnChangeFn<DateRange>;

/**
* The placeholder date, used to display the calendar when no
* date is selected. This updates as the user navigates
* the calendar.
*
* You can bind this to a value to programmatically control the
* placeholder state.
*/
placeholder?: DateValue;

/**
* A callback function called when the placeholder changes.
*/
onPlaceholderChange?: OnChangeFn<DateValue>;
/**
* If `true`, the calendar will focus the selected day,
* today, or the first day of the month in that order depending
* on what is visible when the calendar is mounted.
*
* @default false
*/
initialFocus?: boolean;
} & AsChild
>;

Expand Down
17 changes: 16 additions & 1 deletion src/lib/bits/range-calendar/components/range-calendar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import { setCtx, getAttrs } from "../ctx.js";
import type { Events, Props } from "../types.js";
import { createDispatcher } from "$lib/internal/events.js";
import { onMount } from "svelte";
import { handleCalendarInitialFocus } from "$lib/internal/focus.js";
type $$Props = Props;
type $$Events = Events;
Expand All @@ -26,6 +28,14 @@
export let asChild: $$Props["asChild"] = false;
export let id: $$Props["id"] = undefined;
export let weekdayFormat: $$Props["weekdayFormat"] = undefined;
export let initialFocus: $$Props["initialFocus"] = false;
let el: HTMLElement | undefined = undefined;
onMount(() => {
if (!initialFocus || !el) return;
handleCalendarInitialFocus(el);
});
const {
elements: { calendar },
Expand Down Expand Up @@ -106,7 +116,12 @@
{#if asChild}
<slot {...slotProps} />
{:else}
<div use:melt={builder} {...$$restProps} on:m-keydown={dispatch}>
<div
use:melt={builder}
{...$$restProps}
on:m-keydown={dispatch}
bind:this={el}
>
<slot {...slotProps} />
</div>
{/if}
26 changes: 26 additions & 0 deletions src/lib/internal/focus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { isBrowser } from ".";

/**
* Handles `initialFocus` prop behavior for the
* Calendar & RangeCalendar components.
*/
export function handleCalendarInitialFocus(calendar: HTMLElement) {
if (!isBrowser) return;
const selectedDay = calendar.querySelector<HTMLElement>("[data-selected]");
if (selectedDay) return focusWithoutScroll(selectedDay);

const today = calendar.querySelector<HTMLElement>("[data-today]");
if (today) return focusWithoutScroll(today);

const firstDay = calendar.querySelector<HTMLElement>("[data-calendar-date]");
if (firstDay) return focusWithoutScroll(firstDay);
}

export function focusWithoutScroll(element: HTMLElement) {
const scrollPosition = {
x: window.pageXOffset || document.documentElement.scrollLeft,
y: window.pageYOffset || document.documentElement.scrollTop
};
element.focus();
window.scrollTo(scrollPosition.x, scrollPosition.y);
}
1 change: 1 addition & 0 deletions src/lib/internal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from "./sleep.js";
export * from "./style.js";
export * from "./types.js";
export * from "./updater.js";
export * from "./focus.js";

0 comments on commit 1044dda

Please sign in to comment.