diff --git a/.circleci/config.yml b/.circleci/config.yml index ba726b2ebf..7f27a8f823 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ executors: parameters: current_golden_images_hash: type: string - default: 6212f1e2821ec631ba02ddd3784d9c8c03cf6b9a + default: e5761b668fbcb5a97e8f5b297c91b4e2e72c35bd wireit_cache_name: type: string default: wireit diff --git a/packages/calendar/src/Calendar.ts b/packages/calendar/src/Calendar.ts index 2ea80d7ab1..4e5cc85340 100644 --- a/packages/calendar/src/Calendar.ts +++ b/packages/calendar/src/Calendar.ts @@ -24,6 +24,7 @@ import { today, } from '@internationalized/date'; import { NumberFormatter } from '@internationalized/number'; + import { CSSResultArray, html, @@ -45,6 +46,12 @@ import { languageResolverUpdatedSymbol, } from '@spectrum-web-components/reactive-controllers/src/LanguageResolution.js'; +import styles from './calendar.css.js'; + +import '@spectrum-web-components/action-button/sp-action-button.js'; +import '@spectrum-web-components/icons-workflow/icons/sp-icon-chevron-left.js'; +import '@spectrum-web-components/icons-workflow/icons/sp-icon-chevron-right.js'; + import { CalendarValue, CalendarWeekday, @@ -52,12 +59,6 @@ import { DateValue, } from './types.js'; -import styles from './calendar.css.js'; - -import '@spectrum-web-components/action-button/sp-action-button.js'; -import '@spectrum-web-components/icons-workflow/icons/sp-icon-chevron-left.js'; -import '@spectrum-web-components/icons-workflow/icons/sp-icon-chevron-right.js'; - export const DAYS_PER_WEEK = 7; /** * @element sp-calendar @@ -191,16 +192,7 @@ export class Calendar extends SpectrumElement { if (changesDates) { this.convertToCalendarDates(); this.checkDatePropsCompliance(changesMin || changesMax); - if (this.value) this.currentDate = this.value as CalendarDate; - else { - const isTodayNonCompliant = this.isNonCompliantDate(this.today); - - if (isTodayNonCompliant) { - if (this.min) this.currentDate = this.min as CalendarDate; - else if (this.max) - this.currentDate = this.max as CalendarDate; - } else this.currentDate = this.today; - } + this.updateCurrentDate(); } const previousDate = changedProperties.get('currentDate'); @@ -264,6 +256,20 @@ export class Calendar extends SpectrumElement { } } + private updateCurrentDate(): void { + if (this.value) { + this.currentDate = this.value as CalendarDate; + return; + } + + const isTodayNonCompliant = this.isNonCompliantDate(this.today); + + if (isTodayNonCompliant) { + if (this.min) this.currentDate = this.min as CalendarDate; + else if (this.max) this.currentDate = this.max as CalendarDate; + } else this.currentDate = this.today; + } + /** * Whether the date is non-compliant with the min and max constraints */ diff --git a/packages/calendar/stories/calendar.stories.ts b/packages/calendar/stories/calendar.stories.ts index b58b90b5ce..0d9c96c5c6 100644 --- a/packages/calendar/stories/calendar.stories.ts +++ b/packages/calendar/stories/calendar.stories.ts @@ -9,11 +9,15 @@ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTA OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ +import { CalendarDate, DateValue } from '@internationalized/date'; + import { html, type TemplateResult } from '@spectrum-web-components/base'; +import { CalendarValue } from '@spectrum-web-components/calendar'; + import { spreadProps } from '../../../test/lit-helpers.js'; -import { CalendarValue } from '../src/types.js'; -import { CalendarDate, DateValue } from '@internationalized/date'; + import '@spectrum-web-components/calendar/sp-calendar.js'; +import '@spectrum-web-components/theme/sp-theme.js'; type ComponentArgs = { value?: CalendarValue; @@ -71,54 +75,46 @@ export default { }, }; -const dateControlsDisabledArgTypes = { - min: { - table: { - disable: true, - }, - }, - max: { - table: { - disable: true, - }, - }, - value: { - table: { - disable: true, - }, - }, -}; +const computeProps = (args: StoryArgs): ComponentArgs => { + const timestampToValue = (timestamp: number): CalendarValue => { + const date = new Date(); + date.setTime(timestamp); + return new CalendarDate( + date.getFullYear(), + date.getMonth() + 1, // Date months are 0-indexed while CalendarDate months are 1-indexed + date.getDate() + ); + }; -const timestampToValue = (timestamp: number): CalendarValue => { - const date = new Date(); - date.setTime(timestamp); - return new CalendarDate( - date.getFullYear(), - date.getMonth() + 1, // Date months are 0-indexed while CalendarDate months are 1-indexed - date.getDate() - ); + return { + value: args.value + ? timestampToValue(args.value as unknown as number) + : undefined, + min: args.min + ? timestampToValue(args.min as unknown as number) + : undefined, + max: args.max + ? timestampToValue(args.max as unknown as number) + : undefined, + padded: args.padded, + disabled: args.disabled, + }; }; const Template = (args: StoryArgs = {}): TemplateResult => { - args.value = args.value - ? timestampToValue(args.value as unknown as number) - : undefined; - args.min = args.min - ? timestampToValue(args.min as unknown as number) - : undefined; - args.max = args.max - ? timestampToValue(args.max as unknown as number) - : undefined; - return html` `; }; export const Default = (args: StoryArgs): TemplateResult => Template(args); +Default.swc_vrt = { + // Needed because the style on the current day will cause the snapshot to fail every day it runs + skip: true, +}; export const disabled = (args: StoryArgs): TemplateResult => Template(args); disabled.args = { @@ -133,43 +129,50 @@ padded.args = { export const preselectedValue = (args: StoryArgs): TemplateResult => { return html` `; }; -preselectedValue.argTypes = dateControlsDisabledArgTypes; export const minDate = (args: StoryArgs): TemplateResult => { return html` `; }; -minDate.argTypes = dateControlsDisabledArgTypes; export const maxDate = (args: StoryArgs): TemplateResult => { return html` `; }; -maxDate.argTypes = dateControlsDisabledArgTypes; export const minAndMaxDates = (args: StoryArgs): TemplateResult => { return html` `; }; -minAndMaxDates.argTypes = dateControlsDisabledArgTypes; + +export const bengaliIndiaLocale = (args: StoryArgs): TemplateResult => { + return html` + + + + `; +}; diff --git a/packages/calendar/test/calendar.test.ts b/packages/calendar/test/calendar.test.ts index 676c42f2b6..219997118c 100644 --- a/packages/calendar/test/calendar.test.ts +++ b/packages/calendar/test/calendar.test.ts @@ -17,17 +17,21 @@ import { parseDate, today, } from '@internationalized/date'; -import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; + import { ActionButton } from '@spectrum-web-components/action-button'; import { Calendar, DAYS_PER_WEEK } from '@spectrum-web-components/calendar'; + import '@spectrum-web-components/calendar/sp-calendar.js'; import '@spectrum-web-components/theme/sp-theme.js'; + +import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; import { sendKeys, sendMouse } from '@web/test-runner-commands'; import { spy, stub } from 'sinon'; import { testForLitDevWarnings } from '../../../test/testing-helpers.js'; import { expectSameDates, fixtureElement, + getElementCenter, sendKeyMultipleTimes, } from './helpers.js'; @@ -498,13 +502,9 @@ describe('Calendar', () => { await sendKeys({ press: 'Enter' }); await elementUpdated(element); - const rect = unavailableDayElement.getBoundingClientRect(); - const centerX = Math.round(rect.left + rect.width / 2); - const centerY = Math.round(rect.top + rect.height / 2); - await sendMouse({ type: 'click', - position: [centerX, centerY], + position: getElementCenter(unavailableDayElement), }); await elementUpdated(element); @@ -652,13 +652,9 @@ describe('Calendar', () => { }); it('should update value when an available day is clicked', async () => { - const rect = availableDayElement.getBoundingClientRect(); - const centerX = Math.round(rect.left + rect.width / 2); - const centerY = Math.round(rect.top + rect.height / 2); - await sendMouse({ type: 'click', - position: [centerX, centerY], + position: getElementCenter(availableDayElement), }); await elementUpdated(element); @@ -680,6 +676,19 @@ describe('Calendar', () => { expectSameDates(element.value!, availableDateToSelect); }); + + it('should clear the selected value when the clear method is called', async () => { + await sendMouse({ + type: 'click', + position: getElementCenter(availableDayElement), + }); + await elementUpdated(element); + + element.clear(); + await elementUpdated(element); + + expect(element.value).to.be.undefined; + }); }); describe('Dispatched change', () => { @@ -701,13 +710,9 @@ describe('Calendar', () => { }); it("should dispatch 'change' when an available day is selected by clicking", async () => { - const rect = availableDayElement.getBoundingClientRect(); - const centerX = Math.round(rect.left + rect.width / 2); - const centerY = Math.round(rect.top + rect.height / 2); - await sendMouse({ type: 'click', - position: [centerX, centerY], + position: getElementCenter(availableDayElement), }); await elementUpdated(element); @@ -716,7 +721,7 @@ describe('Calendar', () => { changeSpy.resetHistory(); await sendMouse({ type: 'click', - position: [centerX, centerY], + position: getElementCenter(availableDayElement), }); await elementUpdated(element); expect(changeSpy.callCount).to.equal(0); @@ -797,9 +802,48 @@ describe('Calendar', () => { }); describe('Disabled', () => { - it('should disable the next and previous month buttons'); - it("should not focus the calendar's days"); - it("should not select a day when it's clicked"); + beforeEach(async () => { + element = await fixtureElement({ props: { disabled: true } }); + }); + + it('should disable the next and previous month buttons', () => { + const nextButton = element.shadowRoot.querySelector( + NEXT_BUTTON_SELECTOR + ) as ActionButton; + const prevButton = element.shadowRoot.querySelector( + PREV_BUTTON_SELECTOR + ) as ActionButton; + + expect(nextButton.disabled).to.be.true; + expect(prevButton.disabled).to.be.true; + }); + + it('should not have focusable days', () => { + const focusableDay = element.shadowRoot.querySelector( + "td.tableCell[tabindex='0']" + ) as HTMLElement; + + expect(focusableDay).to.be.null; + }); + + it("should not select a day when it's clicked", async () => { + const availableDateToSelect = new CalendarDate( + fixedYear, + fixedMonth, + fixedDay + 1 + ); + const availableDayElement = element.shadowRoot.querySelector( + `[data-value='${availableDateToSelect.toString()}']` + ) as HTMLElement; + + await sendMouse({ + type: 'click', + position: getElementCenter(availableDayElement), + }); + await elementUpdated(element); + + expect(element.value).to.be.undefined; + }); }); describe('Gregorian AD era limits', () => { @@ -903,6 +947,4 @@ describe('Calendar', () => { expectSameDates(element['currentDate'], lastDate); }); }); - - describe('Localized', () => {}); }); diff --git a/packages/calendar/test/helpers.ts b/packages/calendar/test/helpers.ts index 77b2f4fe02..0b0b6c5c82 100644 --- a/packages/calendar/test/helpers.ts +++ b/packages/calendar/test/helpers.ts @@ -39,6 +39,24 @@ export async function fixtureElement({ return el; } +/** + * Computes the x and y coordinates of the center of the given element, rounded to the nearest integer. + * @param element - The element to get the center of + * @returns - The x and y coordinates of the center of the element + */ +export function getElementCenter(element: HTMLElement): [number, number] { + const rect = element.getBoundingClientRect(); + return [ + Math.round(rect.left + rect.width / 2), + Math.round(rect.top + rect.height / 2), + ]; +} + +/** + * Sends the specified key the given number of times. + * @param key - The key to send + * @param times - The number of times to send the key + */ export function sendKeyMultipleTimes( key: string, times: number @@ -48,6 +66,12 @@ export function sendKeyMultipleTimes( ); } +/** + * Asserts that the given date values are the same day. + * @param a - The first date value + * @param b - The second date value + * @param message - The message to display if the assertion fails + */ export function expectSameDates( a: DateValue, b: DateValue, diff --git a/packages/date-time-picker/src/DateTimePicker.ts b/packages/date-time-picker/src/DateTimePicker.ts index bbd75374ce..9566826691 100644 --- a/packages/date-time-picker/src/DateTimePicker.ts +++ b/packages/date-time-picker/src/DateTimePicker.ts @@ -23,6 +23,7 @@ import { ZonedDateTime, } from '@internationalized/date'; import { NumberParser } from '@internationalized/number'; + import { CSSResultArray, html, @@ -53,13 +54,6 @@ import { import { Focusable } from '@spectrum-web-components/shared/src/focusable.js'; import styles from './date-time-picker.css.js'; -import { - DateTimePickerValue, - EditableSegmentType, - Precision, - Precisions, - SegmentTypes, -} from './types.js'; // TODO: Load dependencies lazily when possible import '@spectrum-web-components/calendar/sp-calendar.js'; @@ -69,6 +63,7 @@ import '@spectrum-web-components/icons-workflow/icons/sp-icon-calendar.js'; import '@spectrum-web-components/overlay/sp-overlay.js'; import '@spectrum-web-components/picker-button/sp-picker-button.js'; import '@spectrum-web-components/popover/sp-popover.js'; + import { equalSegmentValues, isCalendarDate, @@ -84,6 +79,13 @@ import { DecrementModifier } from './segments/modifiers/DecrementModifier.js'; import { IncrementModifier } from './segments/modifiers/IncrementModifier.js'; import { InputModifier } from './segments/modifiers/InputModifier.js'; import { type SegmentsModifierParams } from './segments/modifiers/SegmentsModifier.js'; +import { + DateTimePickerValue, + EditableSegmentType, + Precision, + Precisions, + SegmentTypes, +} from './types.js'; /** * @element sp-date-time-picker @@ -197,7 +199,7 @@ export class DateTimePicker extends ManageHelpText( } /** - * Returns whether the component's precision includes time segments (hour, minute, second) + * @return Whether the component's precision includes time segments (hour, minute, second) */ private get includesTime(): boolean { const timePrecisions = [ @@ -260,7 +262,7 @@ export class DateTimePicker extends ManageHelpText( } /** - * Returns the component's most precise date property (min, max or value) or undefined if none is defined. + * Computes the component's most precise date property (min, max or value) or undefined if none is defined. * The order of precedence is: ZonedDateTime, CalendarDateTime, CalendarDate. */ private get mostSpecificDateValue(): DateValue | undefined { @@ -421,9 +423,9 @@ export class DateTimePicker extends ManageHelpText( (this.isCalendarOpen = false)} > @@ -497,13 +499,15 @@ export class DateTimePicker extends ManageHelpText( ${segment.formatted ?? ''} + >${segment.formatted} `; } public renderEditableSegment(segment: EditableSegment): TemplateResult { const isActive = !this.disabled && !this.readonly; const usePlaceholder = segment.value === undefined; + const inputMode = + segment.type === SegmentTypes.DayPeriod ? 'text' : 'numeric'; const segmentClasses: ClassInfo = { 'editable-segment': true, @@ -538,7 +542,7 @@ export class DateTimePicker extends ManageHelpText(
String(this.maxValue).length) - newValue = numberParser.parse(String(newValue).slice(1)); - - const { minValue, maxValue } = this.inputValidationLimits; - - if (newValue < minValue) { - newValue = !this.isInputValueCompliant(typedValue) - ? this.value - : typedValue; - } else if (newValue > maxValue) { - newValue = !this.isInputValueCompliant(typedValue) - ? this.value - : typedValue; + if (this.isInputValueCompliant(newValue)) { + this.value = newValue; + return; } - this.value = newValue; + if (this.isInputValueCompliant(typedValue)) this.value = typedValue; } private isInputValueCompliant(value: number): boolean { diff --git a/packages/date-time-picker/src/segments/SegmentsFactory.ts b/packages/date-time-picker/src/segments/SegmentsFactory.ts index 4e2e619bfe..d95433a16f 100644 --- a/packages/date-time-picker/src/segments/SegmentsFactory.ts +++ b/packages/date-time-picker/src/segments/SegmentsFactory.ts @@ -10,7 +10,6 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -// import { ZonedDateTime } from '@internationalized/date'; import { DateFormatter, ZonedDateTime } from '@internationalized/date'; import { NumberFormatter } from '@internationalized/number'; import { SegmentType, SegmentTypes } from '../types'; diff --git a/packages/date-time-picker/stories/date-time-picker.stories.ts b/packages/date-time-picker/stories/date-time-picker.stories.ts index f5bd99fff8..ffb890a24d 100644 --- a/packages/date-time-picker/stories/date-time-picker.stories.ts +++ b/packages/date-time-picker/stories/date-time-picker.stories.ts @@ -12,15 +12,16 @@ governing permissions and limitations under the License. import { CalendarDate, CalendarDateTime, + DateValue, toZoned, } from '@internationalized/date'; + import { css, html, TemplateResult, unsafeCSS, } from '@spectrum-web-components/base'; -import { DateValue } from '@spectrum-web-components/calendar'; import { DateTimePickerValue, Precision, @@ -115,35 +116,45 @@ const storyMeta = { }, }; -const timestampToValue = (timestamp: number): DateValue => { - const date = new Date(); - date.setTime(timestamp); - return new CalendarDateTime( - date.getFullYear(), - date.getMonth() + 1, // Date months are 0-indexed while CalendarDate months are 1-indexed - date.getDate(), - date.getHours(), - date.getMinutes(), - date.getSeconds() - ); +const computeProps = (args: StoryArgs): ComponentArgs => { + const timestampToValue = (timestamp: number): DateValue => { + const date = new Date(); + date.setTime(timestamp); + return new CalendarDateTime( + date.getFullYear(), + date.getMonth() + 1, // Date months are 0-indexed while CalendarDate months are 1-indexed + date.getDate(), + date.getHours(), + date.getMinutes(), + date.getSeconds() + ); + }; + + return { + value: args.value + ? timestampToValue(args.value as unknown as number) + : undefined, + min: args.min + ? timestampToValue(args.min as unknown as number) + : undefined, + max: args.max + ? timestampToValue(args.max as unknown as number) + : undefined, + disabled: args.disabled, + readonly: args.readonly, + quiet: args.quiet, + invalid: args.invalid, + autofocus: args.autofocus, + precision: args.precision, + }; }; const Template = (args: StoryArgs = {}): TemplateResult => { - args.value = args.value - ? timestampToValue(args.value as unknown as number) - : undefined; - args.min = args.min - ? timestampToValue(args.min as unknown as number) - : undefined; - args.max = args.max - ? timestampToValue(args.max as unknown as number) - : undefined; - return html` `; }; @@ -175,84 +186,65 @@ autofocus.args = { autofocus: true, }; -const dateControlsDisabledArgTypes = { - min: { - table: { - disable: true, - }, - }, - max: { - table: { - disable: true, - }, - }, - value: { - table: { - disable: true, - }, - }, -}; - export const preselectedValue = (args: StoryArgs): TemplateResult => { return html` `; }; -preselectedValue.argTypes = dateControlsDisabledArgTypes; export const minDate = (args: StoryArgs): TemplateResult => { return html` `; }; -minDate.argTypes = dateControlsDisabledArgTypes; export const maxDate = (args: StoryArgs): TemplateResult => { return html` `; }; -maxDate.argTypes = dateControlsDisabledArgTypes; export const minAndMaxDates = (args: StoryArgs): TemplateResult => { return html` `; }; -minAndMaxDates.argTypes = dateControlsDisabledArgTypes; export const secondPrecision = (args: StoryArgs): TemplateResult => { return html` `; }; export const helpText = (args: StoryArgs): TemplateResult => { return html` - + Please select your birthday @@ -263,9 +255,9 @@ export const helpText = (args: StoryArgs): TemplateResult => { export const negativeHelpText = (args: StoryArgs): TemplateResult => { return html` Change state to invalid to see the negative help text @@ -276,11 +268,10 @@ export const negativeHelpText = (args: StoryArgs): TemplateResult => { `; }; -negativeHelpText.argTypes = dateControlsDisabledArgTypes; -export const customIcon = (): TemplateResult => { +export const customIcon = (args: StoryArgs): TemplateResult => { return html` - + `; @@ -303,7 +294,7 @@ export const customWidth = (args: StoryArgs): TemplateResult[] => {
`; diff --git a/packages/date-time-picker/test/date-time-picker.test.ts b/packages/date-time-picker/test/date-time-picker.test.ts index 88be449543..1888296d07 100644 --- a/packages/date-time-picker/test/date-time-picker.test.ts +++ b/packages/date-time-picker/test/date-time-picker.test.ts @@ -18,13 +18,7 @@ import { ZonedDateTime, } from '@internationalized/date'; import { NumberFormatter } from '@internationalized/number'; -import { - elementUpdated, - expect, - fixture, - html, - oneEvent, -} from '@open-wc/testing'; + import { Calendar } from '@spectrum-web-components/calendar'; import { DateTimePicker, @@ -34,17 +28,28 @@ import { SegmentTypes, } from '@spectrum-web-components/date-time-picker'; import { PickerButton } from '@spectrum-web-components/picker-button'; + +import { + elementUpdated, + expect, + fixture, + html, + oneEvent, +} from '@open-wc/testing'; import { sendKeys, sendMouse } from '@web/test-runner-commands'; import { spy, stub } from 'sinon'; import { testForLitDevWarnings } from '../../../test/testing-helpers.js'; import { dispatchCalendarChange, type EditableSegments, + expectFocused, expectPlaceholder, expectPlaceholders, expectSameDates, fixtureElement, getEditableSegments, + getElementCenter, + isCalendarOpen, openCalendar, sendKeyMultipleTimes, } from './helpers.js'; @@ -157,7 +162,9 @@ describe('DateTimePicker', () => { }); it('should update the value as CalendarDate when it is the most specific date value', async () => { - element = await fixtureElement({ props: { value: valueDate } }); + element = await fixtureElement({ + props: { value: valueDate.set({ year: 2222 }) }, + }); editableSegments = getEditableSegments(element); const year = editableSegments.getByType(SegmentTypes.Year); @@ -195,7 +202,9 @@ describe('DateTimePicker', () => { }); it('should update the value as CalendarDateTime when it is the most specific date value', async () => { - element = await fixtureElement({ props: { value: valueDateTime } }); + element = await fixtureElement({ + props: { value: valueDateTime.set({ year: 2222 }) }, + }); editableSegments = getEditableSegments(element); const year = editableSegments.getByType(SegmentTypes.Year); @@ -237,7 +246,9 @@ describe('DateTimePicker', () => { }); it('should update the value as ZonedDateTime when it is the most specific date value', async () => { - element = await fixtureElement({ props: { value: valueZoned } }); + element = await fixtureElement({ + props: { value: valueZoned.set({ year: 2222 }) }, + }); editableSegments = getEditableSegments(element); const year = editableSegments.getByType(SegmentTypes.Year); @@ -580,7 +591,7 @@ describe('DateTimePicker', () => { describe('Calendar', () => { it('should have the calendar closed by default', () => { - expect(element['isCalendarOpen']).to.be.false; + expect(isCalendarOpen(element)).to.be.false; }); it('should open and close the calendar using the keyboard', async () => { @@ -593,13 +604,13 @@ describe('DateTimePicker', () => { await sendKeys({ press: 'Enter' }); await opened; - expect(element['isCalendarOpen']).to.be.true; + expect(isCalendarOpen(element)).to.be.true; const closed = oneEvent(element, 'sp-closed'); await sendKeys({ press: 'Escape' }); await closed; - expect(element['isCalendarOpen']).to.be.false; + expect(isCalendarOpen(element)).to.be.false; }); it('should open and close the calendar using the pointer', async () => { @@ -607,18 +618,14 @@ describe('DateTimePicker', () => { 'sp-picker-button' ) as PickerButton; - const rect = calendarButton.getBoundingClientRect(); - const centerX = Math.round(rect.left + rect.width / 2); - const centerY = Math.round(rect.top + rect.height / 2); - const opened = oneEvent(element, 'sp-opened'); await sendMouse({ type: 'click', - position: [centerX, centerY], + position: getElementCenter(calendarButton), }); await opened; - expect(element['isCalendarOpen']).to.be.true; + expect(isCalendarOpen(element)).to.be.true; const closed = oneEvent(element, 'sp-closed'); await sendMouse({ @@ -627,7 +634,7 @@ describe('DateTimePicker', () => { }); await closed; - expect(element['isCalendarOpen']).to.be.false; + expect(isCalendarOpen(element)).to.be.false; }); it('should pass the value and min/max constraints to the calendar', async () => { @@ -656,10 +663,20 @@ describe('DateTimePicker', () => { ); }); + it("should close the calendar when the component gets disabled and it's open", async () => { + await openCalendar(element); + expect(isCalendarOpen(element)).to.be.true; + + element.disabled = true; + await elementUpdated(element); + + expect(isCalendarOpen(element)).to.be.false; + }); + describe('change event', () => { it('should close the calendar when handling its change event', async () => { await openCalendar(element); - expect(element['isCalendarOpen']).to.be.true; + expect(isCalendarOpen(element)).to.be.true; const closed = oneEvent(element, 'sp-closed'); await dispatchCalendarChange( @@ -668,7 +685,7 @@ describe('DateTimePicker', () => { ); await closed; - expect(element['isCalendarOpen']).to.be.false; + expect(isCalendarOpen(element)).to.be.false; }); it('should update the value as CalendarDate when its the most specific date value', async () => { @@ -897,17 +914,187 @@ describe('DateTimePicker', () => { }); describe('Focus', () => { - it('should focus segments by clicking on them'); - it( - "should change segment focus to right by using the 'Right Arrow' key" - ); - it("should change segment focus to left by using the 'Left Arrow' key"); - it( - 'should change segment focus to left by using the Backspace/Delete key on a placeholder' - ); - // TODO: one TAB press should focus the calendar button, not the next segment - it("should focus the calendar button by using the 'Tab' key"); - it('should focus the calendar button after the Calendar closes'); + it('should focus segments by clicking on them', async () => { + const yearSegment = editableSegments.getByType(SegmentTypes.Year); + + await sendMouse({ + type: 'click', + position: getElementCenter(yearSegment), + }); + await sendKeys({ press: 'ArrowUp' }); + + expectFocused(document, element, 'element not focused'); + expectFocused(element.shadowRoot, yearSegment, 'year not focused'); + expect(yearSegment.innerText).to.equal(`${fixedYear}`); + + const daySegment = editableSegments.getByType(SegmentTypes.Day); + + await sendMouse({ + type: 'click', + position: getElementCenter(daySegment), + }); + await sendKeys({ press: 'ArrowUp' }); + + expectFocused(document, element, 'element not focused'); + expectFocused(element.shadowRoot, daySegment, 'day not focused'); + expect(daySegment.innerText).to.equal('01'); + }); + + it('should focus the first editable segment when the focus method is called', async () => { + element.focus(); + await elementUpdated(element); + + expectFocused(document, element, 'element not focused'); + expectFocused(element.shadowRoot, element.firstEditableSegment); + }); + + it("should change segment focus to right by using the 'Right Arrow' key", async () => { + element = await fixtureElement({ + props: { precision: Precisions.Second }, + }); + editableSegments = getEditableSegments(element); + const dayPeriodSegment = editableSegments.getByType( + SegmentTypes.DayPeriod + ); + + element.focus(); + await elementUpdated(element); + await sendKeyMultipleTimes( + 'ArrowRight', + editableSegments.length - 1 + ); + + expectFocused(element.shadowRoot, dayPeriodSegment); + + await sendKeys({ press: 'ArrowRight' }); + + expectFocused( + element.shadowRoot, + dayPeriodSegment, + 'dayPeriod no longer focused' + ); + }); + + it("should change segment focus to left by using the 'Left Arrow' key", async () => { + element = await fixtureElement({ + props: { precision: Precisions.Second }, + }); + editableSegments = getEditableSegments(element); + const dayPeriodSegment = editableSegments.getByType( + SegmentTypes.DayPeriod + ); + + await sendMouse({ + type: 'click', + position: getElementCenter(dayPeriodSegment), + }); + + expectFocused(document, element, 'element not focused'); + expectFocused( + element.shadowRoot, + dayPeriodSegment, + 'dayPeriod not focused' + ); + + await sendKeyMultipleTimes( + 'ArrowLeft', + editableSegments.length - 1 + ); + + expectFocused(element.shadowRoot, element.firstEditableSegment); + + await sendKeys({ press: 'ArrowLeft' }); + + expectFocused( + element.shadowRoot, + element.firstEditableSegment, + 'firstEditableSegment no longer focused' + ); + }); + + it('should change segment focus to left by using the Backspace/Delete key on a placeholder', async () => { + element = await fixtureElement({ + props: { + precision: Precisions.Hour, + }, + }); + + editableSegments = getEditableSegments(element); + expectPlaceholders(editableSegments); + + const dayPeriod = editableSegments.getByType( + SegmentTypes.DayPeriod + ); + await sendMouse({ + type: 'click', + position: getElementCenter(dayPeriod), + }); + + expectFocused(document, element, 'element not focused'); + expectFocused( + element.shadowRoot, + dayPeriod, + 'dayPeriod not focused' + ); + + await sendKeys({ press: 'Backspace' }); + + const hourSegment = editableSegments.getByType(SegmentTypes.Hour); + expectFocused(element.shadowRoot, hourSegment, 'hour not focused'); + + await sendKeys({ press: 'Backspace' }); + + const yearSegment = editableSegments.getByType(SegmentTypes.Year); + expectFocused(element.shadowRoot, yearSegment, 'year not focused'); + + await sendKeys({ press: 'Delete' }); + + const daySegment = editableSegments.getByType(SegmentTypes.Day); + expectFocused(element.shadowRoot, daySegment, 'day not focused'); + + await sendKeys({ press: 'Delete' }); + + const monthSegment = editableSegments.getByType(SegmentTypes.Month); + expectFocused( + element.shadowRoot, + monthSegment, + 'month not focused' + ); + + await sendKeys({ press: 'Delete' }); + expectFocused( + element.shadowRoot, + monthSegment, + 'month not focused' + ); + }); + + it("should change focus up to the calendar button by using the 'Tab' key", async () => { + element.focus(); + await elementUpdated(element); + const dayPeriodSegment = editableSegments.getByType( + SegmentTypes.DayPeriod + ); + const calendarButton = element.shadowRoot!.querySelector( + 'sp-picker-button' + ) as PickerButton; + + await sendKeyMultipleTimes('Tab', editableSegments.length - 1); + + expectFocused( + element.shadowRoot, + dayPeriodSegment, + 'dayPeriod not focused' + ); + + await sendKeys({ press: 'Tab' }); + + expectFocused( + element.shadowRoot, + calendarButton, + 'calendarButton not focused' + ); + }); }); describe('ArrowUp key', () => { @@ -1995,7 +2182,7 @@ describe('DateTimePicker', () => { await sendKeys({ type: '0' }); expect(segment.innerText).to.equal('2030'); await sendKeys({ type: '5' }); - expect(segment.innerText).to.equal('305'); + expect(segment.innerText).to.equal('5'); expectPlaceholders(editableSegments, [segment]); expect(element.value).to.be.undefined; @@ -2247,7 +2434,7 @@ describe('DateTimePicker', () => { expect(segment.innerText).to.equal('12'); await sendKeys({ type: '3' }); await elementUpdated(element); - expect(segment.innerText).to.equal('23'); + expect(segment.innerText).to.equal('03'); await sendKeys({ type: '5' }); await elementUpdated(element); expect(segment.innerText).to.equal('05'); @@ -2663,7 +2850,7 @@ describe('DateTimePicker', () => { element = await fixtureElement({ props: { precision: Precisions.Second, - value: new CalendarDateTime(1010, 10, 15, 13, 10, 10), + value: new CalendarDateTime(1010, 10, 15, 12, 10, 10), }, }); element.addEventListener('input', inputSpy); @@ -2851,7 +3038,6 @@ describe('DateTimePicker', () => { inputSpy.resetHistory(); await sendKeys({ press: 'Delete' }); - await sendKeys({ press: 'Delete' }); await elementUpdated(element); expect(inputSpy.callCount).to.equal(0); @@ -2884,7 +3070,6 @@ describe('DateTimePicker', () => { inputSpy.resetHistory(); await sendKeys({ press: 'Delete' }); - await sendKeys({ press: 'Delete' }); await elementUpdated(element); expect(inputSpy.callCount).to.equal(0); @@ -2900,7 +3085,6 @@ describe('DateTimePicker', () => { }); element.addEventListener('input', inputSpy); editableSegments = getEditableSegments(element); - const yearSegment = editableSegments.getByType(SegmentTypes.Year); const monthSegment = editableSegments.getByType(SegmentTypes.Month); const hourSegment = editableSegments.getByType(SegmentTypes.Hour); const secondSegment = editableSegments.getByType( @@ -2910,10 +3094,6 @@ describe('DateTimePicker', () => { SegmentTypes.DayPeriod ); - yearSegment.focus(); - await sendKeys({ type: '222' }); - await elementUpdated(element); - monthSegment.focus(); await sendKeys({ type: '5' }); await elementUpdated(element); @@ -4227,10 +4407,44 @@ describe('DateTimePicker', () => { }); describe('Disabled', () => { - it('should not accept focus'); - it('should not accept typed in values'); - it('should not accept arrow key inputs'); - it('should not open the calendar'); + beforeEach(async () => { + element = await fixtureElement({ props: { disabled: true } }); + editableSegments = getEditableSegments(element); + }); + + it('should not accept focus', async () => { + element.focus(); + await elementUpdated(element); + expect(document.activeElement === element).to.be.false; + + const yearSegment = editableSegments.getByType(SegmentTypes.Year); + yearSegment.focus(); + + expect(document.activeElement === element).to.be.false; + + const monthSegment = editableSegments.getByType(SegmentTypes.Month); + await sendMouse({ + type: 'click', + position: getElementCenter(monthSegment), + }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Shift+Tab' }); + + expect(document.activeElement === element).to.be.false; + }); + + it('should not open the calendar', async () => { + const calendarButton = element.shadowRoot!.querySelector( + 'sp-picker-button' + ) as PickerButton; + + await sendMouse({ + type: 'click', + position: getElementCenter(calendarButton), + }); + + expect(isCalendarOpen(element)).to.be.false; + }); }); describe('Localization', () => { diff --git a/packages/date-time-picker/test/helpers.ts b/packages/date-time-picker/test/helpers.ts index 1f05fdb2de..d089ce3ac0 100644 --- a/packages/date-time-picker/test/helpers.ts +++ b/packages/date-time-picker/test/helpers.ts @@ -24,6 +24,7 @@ import { SegmentPlaceholders, } from '@spectrum-web-components/date-time-picker'; import '@spectrum-web-components/date-time-picker/sp-date-time-picker.js'; +import { Overlay } from '@spectrum-web-components/overlay/src/Overlay.js'; import '@spectrum-web-components/theme/sp-theme.js'; import { sendKeys } from '@web/test-runner-commands'; import { spreadProps } from '../../../test/lit-helpers.js'; @@ -56,6 +57,11 @@ export async function fixtureElement({ return el; } +/** + * Returns an array of all editable segments in the DateTimePicker + * with a `getByType` method to find a segment by type. + * @param element - The DateTimePicker to get the segments from + */ export function getEditableSegments(element: DateTimePicker): EditableSegments { const elements = Array.from( element.shadowRoot.querySelectorAll('.editable-segment') @@ -71,6 +77,73 @@ export function getEditableSegments(element: DateTimePicker): EditableSegments { return elements as EditableSegments; } +/** + * Sends the specified key the given number of times. + * @param key - The key to send + * @param times - The number of times to send the key + */ +export function sendKeyMultipleTimes( + key: string, + times: number +): Promise { + return Promise.all( + Array.from({ length: times }).map(() => sendKeys({ press: key })) + ); +} + +/** + * Opens the Calendar by focusing the Calendar button and pressing Enter. + * @param element - The DateTimePicker with the Calendar to open + */ +export async function openCalendar(element: DateTimePicker): Promise { + const calendarButton = element.shadowRoot!.querySelector( + 'sp-picker-button' + ) as HTMLElement; + + const opened = oneEvent(element, 'sp-opened'); + calendarButton.focus(); + await sendKeys({ press: 'Enter' }); + await opened; +} + +/** + * Simulates a date selection in the Calendar by dispatching a change event with the given date. + * @param element - The DateTimePicker with the Calendar to dispatch the event on + * @param date - The date to set the Calendar to + */ +export async function dispatchCalendarChange( + element: DateTimePicker, + date: DateValue +): Promise { + const calendarEl = element.shadowRoot!.querySelector( + 'sp-calendar' + ) as Calendar; + + calendarEl.value = date; + calendarEl.dispatchEvent( + new CustomEvent('change', { bubbles: true, composed: true }) + ); + await elementUpdated(element); +} + +/** + * Computes the x and y coordinates of the center of the given element, rounded to the nearest integer. + * @param element - The element to get the center of + * @returns - The x and y coordinates of the center of the element + */ +export function getElementCenter(element: HTMLElement): [number, number] { + const rect = element.getBoundingClientRect(); + return [ + Math.round(rect.left + rect.width / 2), + Math.round(rect.top + rect.height / 2), + ]; +} + +/** + * Asserts that the given editable segments have only placeholders. + * @param editableSegments - The segments to check + * @param exceptions - Segments that are allowed to have content + */ export function expectPlaceholders( editableSegments: EditableSegments, exceptions: HTMLElement[] = [] @@ -81,6 +154,10 @@ export function expectPlaceholders( expectPlaceholder(segment); } +/** + * Asserts that the given segment is a placeholder and does not have a value. + * @param segment - The segment to check + */ export function expectPlaceholder(segment: HTMLElement): void { expect(isPlaceholderSegment(segment)).to.be.true; } @@ -94,15 +171,12 @@ function isPlaceholderSegment(segment: HTMLElement): boolean { return true; } -export function sendKeyMultipleTimes( - key: string, - times: number -): Promise { - return Promise.all( - Array.from({ length: times }).map(() => sendKeys({ press: key })) - ); -} - +/** + * Asserts that the given date values are the same day. + * @param a - The first date value + * @param b - The second date value + * @param message - The message to display if the assertion fails + */ export function expectSameDates( a: DateValue, b: DateValue, @@ -111,28 +185,22 @@ export function expectSameDates( expect(isSameDay(a, b), message).to.be.true; } -export async function dispatchCalendarChange( - element: DateTimePicker, - date: DateValue -): Promise { - const calendarEl = element.shadowRoot!.querySelector( - 'sp-calendar' - ) as Calendar; - - calendarEl.value = date; - calendarEl.dispatchEvent( - new CustomEvent('change', { bubbles: true, composed: true }) - ); - await elementUpdated(element); +/** + * Asserts that the given element is focused. + * @param rootEl - The document or shadow root to check the active element of + * @param focusedEl - The element that should be focused + * @param message - The message to display if the assertion fails + */ +export function expectFocused( + rootEl: Document | ShadowRoot, + focusedEl: HTMLElement, + message?: string +): void { + expect(rootEl.activeElement === focusedEl, message).to.be.true; } -export async function openCalendar(element: DateTimePicker): Promise { - const calendarButton = element.shadowRoot!.querySelector( - 'sp-picker-button' - ) as HTMLElement; - - const opened = oneEvent(element, 'sp-opened'); - calendarButton.focus(); - await sendKeys({ press: 'Enter' }); - await opened; +export function isCalendarOpen(element: DateTimePicker): boolean { + const calendar = element.shadowRoot.querySelector('sp-calendar'); + const calendarOverlay = calendar?.closest('sp-overlay') as Overlay; + return calendarOverlay.open; } diff --git a/yarn.lock b/yarn.lock index 290538b0e7..7233c96bb0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5365,168 +5365,9 @@ resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-9.0.8.tgz#6af3bcdace903b8461f5fcd4c9aa23e70128a456" integrity sha512-rGfd7jqXOdR69bEjrRP58ynuIeJU0czPfwQvzhtCzg7jKVukV+efNHqrs086sC6xutB3W4TF71K/dZMr3oyTyg== -"@spectrum-web-components/action-button@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/action-button/-/action-button-0.47.2.tgz#8db417bfbb4d9e77b7d7ff6a6ad311764c93f7e8" - integrity sha512-29hiRHxokHzVMqAjX8QeR3iP5MVjJXxPp7AG9x4Sz7yidGeXCHbItdkA/8ADpDVoULEUxQoyp5alqBl5b8ZrMQ== - dependencies: - "@spectrum-web-components/base" "^0.47.2" - "@spectrum-web-components/button" "^0.47.2" - "@spectrum-web-components/icon" "^0.47.2" - "@spectrum-web-components/icons-ui" "^0.47.2" - "@spectrum-web-components/shared" "^0.47.2" - -"@spectrum-web-components/base@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/base/-/base-0.47.2.tgz#83ce0fbd6a5f29757445224f56f34eb85860ca31" - integrity sha512-jWktMJUfFIFkV0Q/rC6kx/O4zuTLKSubuPk2dRlucilBxGCUzBFOtdQLYnr4B7nYkukNvBdBloVr2be0bRT0jg== - dependencies: - lit "^2.5.0 || ^3.1.3" - -"@spectrum-web-components/button@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/button/-/button-0.47.2.tgz#cebf73bbc7edcff68d7966bd3fb52dc011186206" - integrity sha512-y0IV0zaPTC4z24RYaZcXy8apzXWIjKQm9aPQdYmeHWwwT/8PDEC83hSbaQXQcVLXpeQJZCPXYNTX5d/5j4a0cg== - dependencies: - "@spectrum-web-components/base" "^0.47.2" - "@spectrum-web-components/clear-button" "^0.47.2" - "@spectrum-web-components/close-button" "^0.47.2" - "@spectrum-web-components/icon" "^0.47.2" - "@spectrum-web-components/icons-ui" "^0.47.2" - "@spectrum-web-components/progress-circle" "^0.47.2" - "@spectrum-web-components/shared" "^0.47.2" - -"@spectrum-web-components/clear-button@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/clear-button/-/clear-button-0.47.2.tgz#60aa43d8fc1b5b0c9ba79391a233785febde5efc" - integrity sha512-Yhf8aRmHtXnlGM6eUFdq4eoi/P4zuI3NVA8uQLA8m99waYv9/TdKvDe2QSuwI4GyFcKnUTBnvmq3Rs6rrw/YcA== - dependencies: - "@spectrum-web-components/base" "^0.47.2" - -"@spectrum-web-components/close-button@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/close-button/-/close-button-0.47.2.tgz#dfcba19d0d7d40dc277d20f672a6eac85cc7fc91" - integrity sha512-EbaxJmK+RqYPZ3wk46urj9bptUX5UvusVWcWaVV6rTI5x+tvcIIJYaCIo2faBijEqteGN1q+HDGgi09448WCNA== - dependencies: - "@spectrum-web-components/base" "^0.47.2" - "@spectrum-web-components/eslint-plugin@file:./linters/eslint": version "0.49.0" -"@spectrum-web-components/field-label@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/field-label/-/field-label-0.47.2.tgz#52c8b6c85fa03c9fdd7a1ecbbf5b391137afa734" - integrity sha512-oosf4eQ+oisrzO08rB9HR+7zpsqV1EjfUSLLfIPbfe59wY69DzKMidZcHVIR3TzrTs5HnW3JTbXRqycgBCMSug== - dependencies: - "@spectrum-web-components/base" "^0.47.2" - "@spectrum-web-components/icon" "^0.47.2" - "@spectrum-web-components/icons-ui" "^0.47.2" - "@spectrum-web-components/reactive-controllers" "^0.47.2" - "@spectrum-web-components/shared" "^0.47.2" - -"@spectrum-web-components/icon@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/icon/-/icon-0.47.2.tgz#24ab873ecf3a48f5d5f93e5e59362bd75cd5ff9a" - integrity sha512-BSgFtfHhvmfjdEG7ANzG7YpwiRttefRfgeYsa/8xK9QuH7Mu1MA8BPhDlHIIqaMq9/8CaYY6JxhAJxnlvnlxpA== - dependencies: - "@spectrum-web-components/base" "^0.47.2" - "@spectrum-web-components/iconset" "^0.47.2" - -"@spectrum-web-components/icons-ui@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/icons-ui/-/icons-ui-0.47.2.tgz#cb38e173327da890daa87132a44016a4ebacb260" - integrity sha512-LOKUGU/Es6gMq5+/1ci7azVSVv76hqlmap/Vg+vxrFygr+Ne7g0D6jd4ICz7i5/WCYG7j5wvgS7Xs5l0epUxGg== - dependencies: - "@spectrum-web-components/base" "^0.47.2" - "@spectrum-web-components/icon" "^0.47.2" - "@spectrum-web-components/iconset" "^0.47.2" - -"@spectrum-web-components/icons-workflow@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/icons-workflow/-/icons-workflow-0.47.2.tgz#77b6132478182ab7aea790c2db9cfb086966af17" - integrity sha512-jpGcKKHcXrLf7HarBoV/iXYBfT7K8HYgFsYM/qSN0IynsaLMkplGX1bubRCnfACYszTzEXGw2+9yCpzblknG5Q== - dependencies: - "@spectrum-web-components/base" "^0.47.2" - "@spectrum-web-components/icon" "^0.47.2" - -"@spectrum-web-components/iconset@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/iconset/-/iconset-0.47.2.tgz#a4b438f3d37dc303bcb8eb8182ba6d265548295d" - integrity sha512-fLYj5Xb1rhpCqOgLsR4d776F48i2zW0s0j2L78pMKVDqipmFI4rQqX+Zgg7n4nhfTzzYZH1K6mr8/xQvjVd7Fg== - dependencies: - "@spectrum-web-components/base" "^0.47.2" - -"@spectrum-web-components/overlay@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/overlay/-/overlay-0.47.2.tgz#357a35c79fb3936af81ecc84d106d3a0ce79e3d7" - integrity sha512-JSwgRfWOblCC2UB5/QaLvBD0peNJOp4ZVKG8mBHHsdC0U4Pudk67pzbNcJPq77MuoSKvG/MF5JIaUfxnQ7XbPg== - dependencies: - "@floating-ui/dom" "^1.6.1" - "@floating-ui/utils" "^0.2.1" - "@spectrum-web-components/action-button" "^0.47.2" - "@spectrum-web-components/base" "^0.47.2" - "@spectrum-web-components/reactive-controllers" "^0.47.2" - "@spectrum-web-components/shared" "^0.47.2" - "@spectrum-web-components/theme" "^0.47.2" - -"@spectrum-web-components/picker-button@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/picker-button/-/picker-button-0.47.2.tgz#30e1b3f448567f60aa0e7efa67ec39f034071064" - integrity sha512-jzoUW5xbyk5EnHON6gjAZMkYFZ9Du5vezGmfozAOgENXQDV/pcNr3z40Hd2U321zYJaUr3ya98rt76kUuot7+g== - dependencies: - "@spectrum-web-components/base" "^0.47.2" - "@spectrum-web-components/button" "^0.47.2" - "@spectrum-web-components/icon" "^0.47.2" - "@spectrum-web-components/icons-ui" "^0.47.2" - "@spectrum-web-components/shared" "^0.47.2" - -"@spectrum-web-components/popover@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/popover/-/popover-0.47.2.tgz#691530d3edf659cada6a72d02e87c5ee890922af" - integrity sha512-dHxK8rje6NoA0FEDl06vcA/HNpn+le8Qk6l2YFJx+mg0Zup4fYHYPHNXVJMeSEKPBnVr8J8LLg9TXxb8Ukk2aw== - dependencies: - "@spectrum-web-components/base" "^0.47.2" - "@spectrum-web-components/overlay" "^0.47.2" - -"@spectrum-web-components/progress-circle@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/progress-circle/-/progress-circle-0.47.2.tgz#b41446fdb8af91b18b007069886a9ef60e960f25" - integrity sha512-sp5yv7Bt0kTs9Vpip31MKlnJTdfi4A1xOyyjz+haXAVWXSIBp7q+y2/KXLP5sToaTDGRzFOx9c8X7ZNt/OHOTA== - dependencies: - "@spectrum-web-components/base" "^0.47.2" - "@spectrum-web-components/shared" "^0.47.2" - -"@spectrum-web-components/reactive-controllers@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/reactive-controllers/-/reactive-controllers-0.47.2.tgz#87eebd9d08b769bfaee2347823087f74ff41fe38" - integrity sha512-rJ6tWx7LNWmz1vjup3ozzcR+XowIu8HBuD5KiwkHsYuTFA1pK/2iIMaJl4FMNsG2X6EUM2U114atasy01pnJbA== - dependencies: - lit "^3.1.3" - -"@spectrum-web-components/shared@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/shared/-/shared-0.47.2.tgz#b535954898209f35b5cf2454f1168938eb1094e4" - integrity sha512-S7WU7AS+iw4p9TUZgl2JF/KEB5c2FfjNloYdTPP+4kRC0HiZ57Wj7vSPrL4a7Kt8ucIwHqzm23GwaTcwQckMXQ== - dependencies: - "@lit-labs/observers" "^2.0.2" - "@spectrum-web-components/base" "^0.47.2" - focus-visible "^5.1.0" - -"@spectrum-web-components/styles@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/styles/-/styles-0.47.2.tgz#cf6d6cd3fb67711470f77160dd8f82099f68bd1b" - integrity sha512-l3b9srYFQlwKWXTZ9DPDPcVqu2fQVQEdP0flzaLXm0zn5lIpWz0VIzNphV3jFsTFXsZcjZzwFsLWGFzfz6geJw== - dependencies: - "@spectrum-web-components/base" "^0.47.2" - -"@spectrum-web-components/theme@^0.47.2": - version "0.47.2" - resolved "https://registry.yarnpkg.com/@spectrum-web-components/theme/-/theme-0.47.2.tgz#1e82db4dcd6f9601fa3082e64e1dbe15f711f3ed" - integrity sha512-bK+sTtHLxakgDNI3yV0R6lnVTjR3rpa09y+8Wq6qX4+xFOVxWHw+1RP6/3M1JxUM7SK0Qw8jhUR7aIiZ36tR6A== - dependencies: - "@spectrum-web-components/base" "^0.47.2" - "@spectrum-web-components/styles" "^0.47.2" - "@storybook/addon-a11y@^7.5.0": version "7.6.19" resolved "https://registry.yarnpkg.com/@storybook/addon-a11y/-/addon-a11y-7.6.19.tgz#8b684edfb3a387d24398e81517dc734f0b76a1db"