Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bundlewatch.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
},
{
"path": "./dist/js/bootstrap.bundle.min.js",
"maxSize": "55.5 kB"
"maxSize": "62.0 kB"
},
{
"path": "./dist/js/bootstrap.js",
Expand Down
2 changes: 1 addition & 1 deletion config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ cdn:
js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@6.0.0-alpha1/dist/js/bootstrap.bundle.min.js"
js_bundle_hash: "sha384-I2J4jlw924JZXHU9un9Mcuixq/rKhd5A8/B1NQ6ifPAiBFacZjwNcec8d6L38jQv"
floating_ui_esm: "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.7.6/dist/floating-ui.dom.esm.min.js"
vanilla_calendar_pro_esm: "https://cdn.jsdelivr.net/npm/vanilla-calendar-pro@3.1.0/index.mjs"
vanilla_calendar_pro_esm: "https://cdn.jsdelivr.net/npm/vanilla-calendar-pro@3.3.1/index.mjs"

anchors:
min: 2
Expand Down
14 changes: 12 additions & 2 deletions js/src/datepicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ class Datepicker extends BaseComponent {
protected declare _displayElement: HTMLElement | false | null
protected declare _themeObserver: MutationObserver | null
protected declare _onFocusIn: (event: Event) => void
protected declare _themeAncestor: Element | null | undefined

constructor(element?: string | Element | null, config?: Partial<DatepickerConfig> | null) {
super(element, config)
Expand Down Expand Up @@ -304,7 +305,14 @@ class Datepicker extends BaseComponent {
}

protected _getThemeAncestor(): Element | null {
return this._element.closest('[data-bs-theme]')
// Cache on first lookup: for an inline calendar, `_syncThemeAttribute` writes
// `data-bs-theme` onto this same element (it doubles as VCP's main element), which
// would make a later `closest()` match itself instead of the real ancestor.
if (this._themeAncestor === undefined) {
this._themeAncestor = this._element.closest('[data-bs-theme]')
}

return this._themeAncestor
}

protected _getEffectiveTheme(): string | null {
Expand Down Expand Up @@ -391,7 +399,9 @@ class Datepicker extends BaseComponent {
selectionDatesMode: this._config.selectionMode,
selectedDates: this._config.selectedDates,
displayMonthsCount: this._config.displayMonthsCount,
type: this._config.displayMonthsCount > 1 ? 'multiple' : 'default',
// Multiple months require VCP's 'multiple' type; otherwise let vcpOptions.type
// through (e.g. 'week') instead of always forcing the single-month default.
type: this._config.displayMonthsCount > 1 ? 'multiple' : (this._config.vcpOptions.type ?? 'default'),
selectedTheme: vcpTheme,
themeAttrDetect: '[data-bs-theme]',
onClickDate: (self, event) => this._handleDateClick(self, event),
Expand Down
92 changes: 92 additions & 0 deletions js/tests/unit/datepicker.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,62 @@ describe('Datepicker', () => {
})
})

// These assertions pin the Vanilla Calendar Pro markup that our Sass targets.
// VCP 3.2.0 added the row and cell wrappers for ARIA, which moved the grid
// columns off the container and broke the month and year layout silently.
describe('calendar markup', () => {
const createInlineCalendar = () => {
fixtureEl.innerHTML = '<div data-bs-toggle="datepicker" data-bs-inline="true"></div>'

const divEl = fixtureEl.querySelector('div')
Datepicker.getOrCreateInstance(divEl)

return divEl
}

it('should wrap months in rows and cells', () => {
const divEl = createInlineCalendar()

divEl.querySelector('[data-vc="month"]').click()

const monthsEl = divEl.querySelector('[data-vc="months"]')

expect(monthsEl.querySelectorAll('[data-vc-months="row"]')).toHaveSize(3)
expect(monthsEl.querySelectorAll('[data-vc-months="row"] > [data-vc-months="cell"] > [data-vc-months-month]')).toHaveSize(12)
})

it('should wrap years in rows and cells', () => {
const divEl = createInlineCalendar()

divEl.querySelector('[data-vc="year"]').click()

const yearsEl = divEl.querySelector('[data-vc="years"]')

expect(yearsEl.querySelectorAll('[data-vc-years="row"]')).toHaveSize(3)
expect(yearsEl.querySelectorAll('[data-vc-years="row"] > [data-vc-years="cell"] > [data-vc-years-year]')).toHaveSize(15)
})

it('should not mark inline calendars with data-vc-input', () => {
const divEl = createInlineCalendar()

expect(divEl.getAttribute('data-vc')).toEqual('calendar')
expect(divEl.hasAttribute('data-vc-input')).toBeFalse()
})

it('should mark popup calendars with data-vc-input', () => {
fixtureEl.innerHTML = '<input type="text" data-bs-toggle="datepicker">'

const inputEl = fixtureEl.querySelector('input')
const datepicker = new Datepicker(inputEl)

return datepicker.show().then(() => {
const calendarEl = datepicker._calendar.context.mainElement

expect(calendarEl.hasAttribute('data-vc-input')).toBeTrue()
})
})
})

describe('data-api', () => {
it('should toggle on click for buttons', () => {
return new Promise(resolve => {
Expand Down Expand Up @@ -1076,6 +1132,42 @@ describe('Datepicker', () => {

expect(datepicker._config.vcpOptions.jumpMonths).toEqual(2)
})

it('should default to the single-month calendar type', () => {
fixtureEl.innerHTML = '<input type="text" data-bs-toggle="datepicker">'

const inputEl = fixtureEl.querySelector('input')
const datepicker = new Datepicker(inputEl)

expect(datepicker._buildCalendarOptions().type).toEqual('default')
})

it('should let vcpOptions.type override the default calendar type', () => {
fixtureEl.innerHTML = '<input type="text" data-bs-toggle="datepicker">'

const inputEl = fixtureEl.querySelector('input')
const datepicker = new Datepicker(inputEl, {
vcpOptions: {
type: 'week'
}
})

expect(datepicker._buildCalendarOptions().type).toEqual('week')
})

it('should force the multiple calendar type when displayMonthsCount is greater than 1, ignoring vcpOptions.type', () => {
fixtureEl.innerHTML = '<input type="text" data-bs-toggle="datepicker">'

const inputEl = fixtureEl.querySelector('input')
const datepicker = new Datepicker(inputEl, {
displayMonthsCount: 2,
vcpOptions: {
type: 'week'
}
})

expect(datepicker._buildCalendarOptions().type).toEqual('multiple')
})
})

describe('date selection handling', () => {
Expand Down
18 changes: 18 additions & 0 deletions js/tests/visual/datepicker.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,24 @@ <h2>Sunday First</h2>

<hr>

<h2>Inline</h2>
<div class="row mb-4">
<div class="md:col-6">
<p class="fg-2">No popup surface. The calendar inherits the page background.</p>
<div id="inlineDatepicker" data-bs-toggle="datepicker" data-bs-inline="true"></div>
</div>
<div class="md:col-6">
<p class="fg-2">Inline inside a card, range selection.</p>
<div class="card">
<div class="card-body">
<div id="inlineCardDatepicker" data-bs-toggle="datepicker" data-bs-inline="true" data-bs-selection-mode="multiple-ranged"></div>
</div>
</div>
</div>
</div>

<hr>

<h2>Dark Mode</h2>
<div class="row mb-4" data-bs-theme="dark">
<div class="md:col-6">
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@
},
"peerDependencies": {
"@floating-ui/dom": "^1.7.6",
"vanilla-calendar-pro": "^3.1.0"
"vanilla-calendar-pro": "^3.3.1"
},
"devDependencies": {
"@astrojs/check": "^0.9.10",
Expand Down
Loading