diff --git a/.changeset/lemon-candles-grow.md b/.changeset/lemon-candles-grow.md new file mode 100644 index 00000000..0014f32b --- /dev/null +++ b/.changeset/lemon-candles-grow.md @@ -0,0 +1,6 @@ +--- +"@kobalte/tailwindcss": patch +"@kobalte/core": patch +--- + +v0.9.1 diff --git a/apps/docs/src/VERSIONS.ts b/apps/docs/src/VERSIONS.ts index 5c55244f..94f1f584 100644 --- a/apps/docs/src/VERSIONS.ts +++ b/apps/docs/src/VERSIONS.ts @@ -17,6 +17,7 @@ export const CORE_VERSIONS = [ "0.8.1", "0.8.2", "0.9.0", + "0.9.1", ].reverse(); export const LATEST_CORE_CHANGELOG_URL = `/docs/changelog/${CORE_VERSIONS[0].replaceAll(".", "-")}`; diff --git a/apps/docs/src/routes/docs/changelog/0-9-1.mdx b/apps/docs/src/routes/docs/changelog/0-9-1.mdx new file mode 100644 index 00000000..50b27123 --- /dev/null +++ b/apps/docs/src/routes/docs/changelog/0-9-1.mdx @@ -0,0 +1,9 @@ +# v0.9.1 + +**April 25, 2023**. + +## Bug fixes + +- [#173](https://github.com/kobaltedev/kobalte/pull/173) +- [#180](https://github.com/kobaltedev/kobalte/pull/180) +- [#181](https://github.com/kobaltedev/kobalte/pull/181) diff --git a/apps/docs/src/routes/docs/core/components/combobox.mdx b/apps/docs/src/routes/docs/core/components/combobox.mdx index 1438ff40..313c6d97 100644 --- a/apps/docs/src/routes/docs/core/components/combobox.mdx +++ b/apps/docs/src/routes/docs/core/components/combobox.mdx @@ -327,6 +327,43 @@ The combobox consists of: ## Usage +### Filtering options + +Combobox doesn't apply any filter internally, it's the developer responsibility to provide a filtered list of options, whether by using a fuzzy search library client-side or by making server-side requests to an API. + +For convenience Kobalte exposes the `createFilter` primitive which provides functions for filtering or searching based on substring matches with locale sensitive matching support. It automatically uses the current locale set by the application, either via the default browser language or via the `I18nProvider`. + +The available filtering methods are: + +- **startWith**: Returns whether a string starts with a given substring. +- **endWith**: Returns whether a string ends with a given substring. +- **contains**: Returns whether a string contains a given substring. + +```tsx {6,13,15} +import { Combobox, createFilter } from "@kobalte/core"; +import { createSignal } from "solid-js"; + +const ALL_OPTIONS = ["Apple", "Banana", "Blueberry", "Grapes", "Pineapple"]; + +function DefaultValueExample() { + const filter = createFilter({ sensitivity: "base" }); + + const [options, setOptions] = createSignal(ALL_OPTIONS); + + const onInputChange = (value: string) => { + // or filter.startWith + // or filter.endWith + setOptions(ALL_OPTIONS.filter(option => filter.contains(option, value))); + }; + + return ( + + //...rest of the component. + + ); +} +``` + ### Default value An initial, uncontrolled value can be provided using the `defaultValue` prop, which accepts a value corresponding with the `options`. diff --git a/packages/core/dev/App.tsx b/packages/core/dev/App.tsx index a2e406d5..8a89ee3d 100644 --- a/packages/core/dev/App.tsx +++ b/packages/core/dev/App.tsx @@ -1,75 +1,5 @@ -import { createSignal } from "solid-js"; - -import { Combobox, createFilter, I18nProvider } from "../src"; -import { ComboboxTriggerMode } from "../src/combobox"; - -interface Food { - value: string; - label: string; - disabled: boolean; -} - -const RAW_OPTIONS: Food[] = [ - { value: "apple", label: "Apple", disabled: false }, - { value: "banana", label: "Banana", disabled: false }, - { value: "blueberry", label: "Blueberry", disabled: false }, - { value: "grapes", label: "Grapes", disabled: true }, - { value: "pineapple", label: "Pineapple", disabled: false }, -]; +import { I18nProvider } from "../src"; export default function App() { - const filter = createFilter({ sensitivity: "base" }); - - const [options, setOptions] = createSignal(RAW_OPTIONS); - - const [value, setValue] = createSignal(options()[0]); - - const onOpenChange = (isOpen: boolean, triggerMode?: ComboboxTriggerMode) => { - // Show all options on ArrowDown/ArrowUp and button click. - if (isOpen && triggerMode === "manual") { - setOptions(RAW_OPTIONS); - } - }; - - const onInputChange = (value: string) => { - if (value === "") { - //setValue(undefined); - } - - setOptions(RAW_OPTIONS.filter(option => filter.contains(option.label, value))); - }; - - return ( - - {value()?.label ?? "Select an option"} - - options={options()} - optionValue="value" - optionTextValue="label" - optionLabel="label" - optionDisabled="disabled" - //value={value()} - onChange={setValue} - onInputChange={onInputChange} - onOpenChange={onOpenChange} - placeholder="Select a fruit..." - itemComponent={props => ( - - {props.item.rawValue.label} - X - - )} - > - class="combobox__trigger"> - - - - - - - - - - - ); + return ; } diff --git a/packages/core/src/combobox/combobox-base.tsx b/packages/core/src/combobox/combobox-base.tsx index cffb1a5b..54089cee 100644 --- a/packages/core/src/combobox/combobox-base.tsx +++ b/packages/core/src/combobox/combobox-base.tsx @@ -351,13 +351,6 @@ export function ComboboxBase(props: ComboboxBaseProps< }); const getOptionsFromValues = (values: Set): Option[] => { - const optionValue = local.optionValue; - - if (optionValue == null) { - // If no `optionValue`, the values are the options (ex: string[] of options) - return [...values] as Option[]; - } - return flattenOptions().filter(option => values.has(getOptionValue(option as Option))); }; diff --git a/packages/core/src/combobox/combobox-input.tsx b/packages/core/src/combobox/combobox-input.tsx index da99ff6a..d6e829af 100644 --- a/packages/core/src/combobox/combobox-input.tsx +++ b/packages/core/src/combobox/combobox-input.tsx @@ -178,7 +178,6 @@ export function ComboboxInput(props: ComboboxInputProps) { } context.setIsInputFocused(false); - context.resetInputValue(); }; // If a touch happens on direct center of Combobox input, might be virtual click from iPad so open ComboBox menu diff --git a/packages/core/src/combobox/combobox.test.tsx b/packages/core/src/combobox/combobox.test.tsx index 1f292d29..30858e17 100644 --- a/packages/core/src/combobox/combobox.test.tsx +++ b/packages/core/src/combobox/combobox.test.tsx @@ -88,7 +88,7 @@ describe("Combobox", () => { }); describe("option mapping", () => { - const CUSTOM_DATA_SOURCE = [ + const CUSTOM_DATA_SOURCE_WITH_STRING_KEY = [ { name: "Section 1", items: [ @@ -99,16 +99,17 @@ describe("Combobox", () => { }, ]; - it("supports string based option mapping for object options", async () => { + it("supports string based option mapping for object options with string keys", async () => { render(() => ( - options={CUSTOM_DATA_SOURCE} + options={CUSTOM_DATA_SOURCE_WITH_STRING_KEY} optionValue="id" optionTextValue="valueText" optionLabel="name" optionDisabled="disabled" optionGroupChildren="items" placeholder="Placeholder" + onChange={onValueChange} itemComponent={props => ( {props.item.rawValue.name} )} @@ -131,6 +132,7 @@ describe("Combobox", () => { )); const trigger = screen.getByRole("button"); + const input = screen.getByRole("combobox"); fireEvent(trigger, createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" })); await Promise.resolve(); @@ -157,18 +159,39 @@ describe("Combobox", () => { expect(items[2]).toHaveTextContent("Three"); expect(items[2]).toHaveAttribute("data-key", "3"); expect(items[2]).not.toHaveAttribute("data-disabled"); + + fireEvent( + items[2], + createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" }) + ); + await Promise.resolve(); + + fireEvent(items[2], createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + expect(onValueChange).toHaveBeenCalledTimes(1); + expect(onValueChange.mock.calls[0][0]).toBe(CUSTOM_DATA_SOURCE_WITH_STRING_KEY[0].items[2]); + + expect(listbox).not.toBeVisible(); + + // run restore focus rAF + jest.runAllTimers(); + + expect(input).toHaveValue("Three"); + expect(document.activeElement).toBe(input); }); - it("supports function based option mapping for object options", async () => { + it("supports function based option mapping for object options with string keys", async () => { render(() => ( - options={CUSTOM_DATA_SOURCE} + options={CUSTOM_DATA_SOURCE_WITH_STRING_KEY} optionValue={option => option.id} optionTextValue={option => option.valueText} optionLabel={option => option.name} optionDisabled={option => option.disabled} optionGroupChildren={optGroup => optGroup.items} placeholder="Placeholder" + onChange={onValueChange} itemComponent={props => ( {props.item.rawValue.name} )} @@ -191,6 +214,7 @@ describe("Combobox", () => { )); const trigger = screen.getByRole("button"); + const input = screen.getByRole("combobox"); fireEvent(trigger, createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" })); await Promise.resolve(); @@ -217,6 +241,201 @@ describe("Combobox", () => { expect(items[2]).toHaveTextContent("Three"); expect(items[2]).toHaveAttribute("data-key", "3"); expect(items[2]).not.toHaveAttribute("data-disabled"); + + fireEvent( + items[2], + createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" }) + ); + await Promise.resolve(); + + fireEvent(items[2], createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + expect(onValueChange).toHaveBeenCalledTimes(1); + expect(onValueChange.mock.calls[0][0]).toBe(CUSTOM_DATA_SOURCE_WITH_STRING_KEY[0].items[2]); + + expect(listbox).not.toBeVisible(); + + // run restore focus rAF + jest.runAllTimers(); + + expect(input).toHaveValue("Three"); + expect(document.activeElement).toBe(input); + }); + + const CUSTOM_DATA_SOURCE_WITH_NUMBER_KEY = [ + { + name: "Section 1", + items: [ + { id: 1, name: "One", valueText: "One", disabled: false }, + { id: 2, name: "Two", valueText: "Two", disabled: true }, + { id: 3, name: "Three", valueText: "Three", disabled: false }, + ], + }, + ]; + + it("supports string based option mapping for object options with number keys", async () => { + render(() => ( + + options={CUSTOM_DATA_SOURCE_WITH_NUMBER_KEY} + optionValue="id" + optionTextValue="valueText" + optionLabel="name" + optionDisabled="disabled" + optionGroupChildren="items" + placeholder="Placeholder" + onChange={onValueChange} + itemComponent={props => ( + {props.item.rawValue.name} + )} + sectionComponent={props => ( + {props.section.rawValue.name} + )} + > + + Label + + + + + + + + + + + )); + + const trigger = screen.getByRole("button"); + const input = screen.getByRole("combobox"); + + fireEvent(trigger, createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + fireEvent(trigger, createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + jest.runAllTimers(); + + const listbox = screen.getByRole("listbox"); + + const items = within(listbox).getAllByRole("option"); + + expect(items.length).toBe(3); + + expect(items[0]).toHaveTextContent("One"); + expect(items[0]).toHaveAttribute("data-key", "1"); + expect(items[0]).not.toHaveAttribute("data-disabled"); + + expect(items[1]).toHaveTextContent("Two"); + expect(items[1]).toHaveAttribute("data-key", "2"); + expect(items[1]).toHaveAttribute("data-disabled"); + + expect(items[2]).toHaveTextContent("Three"); + expect(items[2]).toHaveAttribute("data-key", "3"); + expect(items[2]).not.toHaveAttribute("data-disabled"); + + fireEvent( + items[2], + createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" }) + ); + await Promise.resolve(); + + fireEvent(items[2], createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + expect(onValueChange).toHaveBeenCalledTimes(1); + expect(onValueChange.mock.calls[0][0]).toBe(CUSTOM_DATA_SOURCE_WITH_NUMBER_KEY[0].items[2]); + + expect(listbox).not.toBeVisible(); + + // run restore focus rAF + jest.runAllTimers(); + + expect(input).toHaveValue("Three"); + expect(document.activeElement).toBe(input); + }); + + it("supports function based option mapping for object options with number keys", async () => { + render(() => ( + + options={CUSTOM_DATA_SOURCE_WITH_NUMBER_KEY} + optionValue={option => option.id} + optionTextValue={option => option.valueText} + optionLabel={option => option.name} + optionDisabled={option => option.disabled} + optionGroupChildren={optGroup => optGroup.items} + placeholder="Placeholder" + onChange={onValueChange} + itemComponent={props => ( + {props.item.rawValue.name} + )} + sectionComponent={props => ( + {props.section.rawValue.name} + )} + > + + Label + + + + + + + + + + + )); + + const trigger = screen.getByRole("button"); + const input = screen.getByRole("combobox"); + + fireEvent(trigger, createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + fireEvent(trigger, createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + jest.runAllTimers(); + + const listbox = screen.getByRole("listbox"); + + const items = within(listbox).getAllByRole("option"); + + expect(items.length).toBe(3); + + expect(items[0]).toHaveTextContent("One"); + expect(items[0]).toHaveAttribute("data-key", "1"); + expect(items[0]).not.toHaveAttribute("data-disabled"); + + expect(items[1]).toHaveTextContent("Two"); + expect(items[1]).toHaveAttribute("data-key", "2"); + expect(items[1]).toHaveAttribute("data-disabled"); + + expect(items[2]).toHaveTextContent("Three"); + expect(items[2]).toHaveAttribute("data-key", "3"); + expect(items[2]).not.toHaveAttribute("data-disabled"); + + fireEvent( + items[2], + createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" }) + ); + await Promise.resolve(); + + fireEvent(items[2], createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + expect(onValueChange).toHaveBeenCalledTimes(1); + expect(onValueChange.mock.calls[0][0]).toBe(CUSTOM_DATA_SOURCE_WITH_NUMBER_KEY[0].items[2]); + + expect(listbox).not.toBeVisible(); + + // run restore focus rAF + jest.runAllTimers(); + + expect(input).toHaveValue("Three"); + expect(document.activeElement).toBe(input); }); it("supports string options without mapping", async () => { @@ -224,6 +443,7 @@ describe("Combobox", () => { ( {props.item.rawValue} )} @@ -243,6 +463,7 @@ describe("Combobox", () => { )); const trigger = screen.getByRole("button"); + const input = screen.getByRole("combobox"); fireEvent(trigger, createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" })); await Promise.resolve(); @@ -269,6 +490,26 @@ describe("Combobox", () => { expect(items[2]).toHaveTextContent("Three"); expect(items[2]).toHaveAttribute("data-key", "Three"); expect(items[2]).not.toHaveAttribute("data-disabled"); + + fireEvent( + items[2], + createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" }) + ); + await Promise.resolve(); + + fireEvent(items[2], createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + expect(onValueChange).toHaveBeenCalledTimes(1); + expect(onValueChange.mock.calls[0][0]).toBe("Three"); + + expect(listbox).not.toBeVisible(); + + // run restore focus rAF + jest.runAllTimers(); + + expect(input).toHaveValue("Three"); + expect(document.activeElement).toBe(input); }); it("supports function based option mapping for string options", async () => { @@ -280,6 +521,7 @@ describe("Combobox", () => { optionLabel={option => option} optionDisabled={option => option === "Two"} placeholder="Placeholder" + onChange={onValueChange} itemComponent={props => ( {props.item.rawValue} )} @@ -299,6 +541,7 @@ describe("Combobox", () => { )); const trigger = screen.getByRole("button"); + const input = screen.getByRole("combobox"); fireEvent(trigger, createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" })); await Promise.resolve(); @@ -325,6 +568,178 @@ describe("Combobox", () => { expect(items[2]).toHaveTextContent("Three"); expect(items[2]).toHaveAttribute("data-key", "Three"); expect(items[2]).not.toHaveAttribute("data-disabled"); + + fireEvent( + items[2], + createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" }) + ); + await Promise.resolve(); + + fireEvent(items[2], createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + expect(onValueChange).toHaveBeenCalledTimes(1); + expect(onValueChange.mock.calls[0][0]).toBe("Three"); + + expect(listbox).not.toBeVisible(); + + // run restore focus rAF + jest.runAllTimers(); + + expect(input).toHaveValue("Three"); + expect(document.activeElement).toBe(input); + }); + + it("supports number options without mapping", async () => { + render(() => ( + ( + {props.item.rawValue} + )} + > + + Label + + + + + + + + + + + )); + + const trigger = screen.getByRole("button"); + const input = screen.getByRole("combobox"); + + fireEvent(trigger, createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + fireEvent(trigger, createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + jest.runAllTimers(); + + const listbox = screen.getByRole("listbox"); + + const items = within(listbox).getAllByRole("option"); + + expect(items.length).toBe(3); + + expect(items[0]).toHaveTextContent("1"); + expect(items[0]).toHaveAttribute("data-key", "1"); + expect(items[0]).not.toHaveAttribute("data-disabled"); + + expect(items[1]).toHaveTextContent("2"); + expect(items[1]).toHaveAttribute("data-key", "2"); + expect(items[1]).not.toHaveAttribute("data-disabled"); + + expect(items[2]).toHaveTextContent("3"); + expect(items[2]).toHaveAttribute("data-key", "3"); + expect(items[2]).not.toHaveAttribute("data-disabled"); + + fireEvent( + items[2], + createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" }) + ); + await Promise.resolve(); + + fireEvent(items[2], createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + expect(onValueChange).toHaveBeenCalledTimes(1); + expect(onValueChange.mock.calls[0][0]).toBe(3); + + expect(listbox).not.toBeVisible(); + + // run restore focus rAF + jest.runAllTimers(); + + expect(input).toHaveValue("3"); + expect(document.activeElement).toBe(input); + }); + + it("supports function based option mapping for number options", async () => { + render(() => ( + option} + optionTextValue={option => option} + optionLabel={option => option} + optionDisabled={option => option === 2} + placeholder="Placeholder" + onChange={onValueChange} + itemComponent={props => ( + {props.item.rawValue} + )} + > + + Label + + + + + + + + + + + )); + + const trigger = screen.getByRole("button"); + const input = screen.getByRole("combobox"); + + fireEvent(trigger, createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + fireEvent(trigger, createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + jest.runAllTimers(); + + const listbox = screen.getByRole("listbox"); + + const items = within(listbox).getAllByRole("option"); + + expect(items.length).toBe(3); + + expect(items[0]).toHaveTextContent("1"); + expect(items[0]).toHaveAttribute("data-key", "1"); + expect(items[0]).not.toHaveAttribute("data-disabled"); + + expect(items[1]).toHaveTextContent("2"); + expect(items[1]).toHaveAttribute("data-key", "2"); + expect(items[1]).toHaveAttribute("data-disabled"); + + expect(items[2]).toHaveTextContent("3"); + expect(items[2]).toHaveAttribute("data-key", "3"); + expect(items[2]).not.toHaveAttribute("data-disabled"); + + fireEvent( + items[2], + createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" }) + ); + await Promise.resolve(); + + fireEvent(items[2], createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + expect(onValueChange).toHaveBeenCalledTimes(1); + expect(onValueChange.mock.calls[0][0]).toBe(3); + + expect(listbox).not.toBeVisible(); + + // run restore focus rAF + jest.runAllTimers(); + + expect(input).toHaveValue("3"); + expect(document.activeElement).toBe(input); }); }); diff --git a/packages/core/src/primitives/create-collection/utils.ts b/packages/core/src/primitives/create-collection/utils.ts index 60669ac9..d8c98eea 100644 --- a/packages/core/src/primitives/create-collection/utils.ts +++ b/packages/core/src/primitives/create-collection/utils.ts @@ -1,4 +1,4 @@ -import { isString } from "@kobalte/utils"; +import { isObject, isString } from "@kobalte/utils"; import { CollectionNode } from "./types"; @@ -23,17 +23,19 @@ export function buildNodes(params: BuildNodesParams): Array { const getKey = (data: any): string => { const _getKey = params.getKey ?? "key"; - return isString(_getKey) ? data[_getKey] : _getKey(data); + const dataKey = isString(_getKey) ? data[_getKey] : _getKey(data); + return dataKey != null ? String(dataKey) : ""; }; - const getTextValue = (data: any): string | undefined => { + const getTextValue = (data: any): string => { const _getTextValue = params.getTextValue ?? "textValue"; - return isString(_getTextValue) ? data[_getTextValue] : _getTextValue(data); + const dataTextValue = isString(_getTextValue) ? data[_getTextValue] : _getTextValue(data); + return dataTextValue != null ? String(dataTextValue) : ""; }; - const getDisabled = (data: any): boolean | undefined => { + const getDisabled = (data: any): boolean => { const _getDisabled = params.getDisabled ?? "disabled"; - return isString(_getDisabled) ? data[_getDisabled] : _getDisabled(data); + return (isString(_getDisabled) ? data[_getDisabled] : _getDisabled(data)) ?? false; }; const getSectionChildren = (data: any): any[] | undefined => { @@ -45,14 +47,14 @@ export function buildNodes(params: BuildNodesParams): Array { }; for (const data of params.dataSource) { - // If it's just a string assume it's an item. - if (isString(data)) { + // If it's not an object assume it's an item. + if (!isObject(data)) { nodes.push({ type: "item", rawValue: data, - key: data, - textValue: data, - disabled: getDisabled(data) ?? false, + key: String(data), + textValue: String(data), + disabled: getDisabled(data), level, index, }); @@ -98,8 +100,8 @@ export function buildNodes(params: BuildNodesParams): Array { type: "item", rawValue: data, key: getKey(data), - textValue: getTextValue(data) ?? "", - disabled: getDisabled(data) ?? false, + textValue: getTextValue(data), + disabled: getDisabled(data), level, index, }); diff --git a/packages/core/src/select/select-base.tsx b/packages/core/src/select/select-base.tsx index 0789cc11..e410e7ac 100644 --- a/packages/core/src/select/select-base.tsx +++ b/packages/core/src/select/select-base.tsx @@ -292,14 +292,7 @@ export function SelectBase(props: SelectBaseProps) => { - const optionValue = local.optionValue; - - if (optionValue == null) { - // If no `optionValue`, the values are the options (ex: string[] of options) - return [...values] as Option[]; - } - + const getOptionsFromValues = (values: Set): Option[] => { return flattenOptions().filter(option => values.has(getOptionValue(option as Option))); }; diff --git a/packages/core/src/select/select.test.tsx b/packages/core/src/select/select.test.tsx index f94a5894..9608b4e3 100644 --- a/packages/core/src/select/select.test.tsx +++ b/packages/core/src/select/select.test.tsx @@ -84,7 +84,7 @@ describe("Select", () => { }); describe("option mapping", () => { - const CUSTOM_DATA_SOURCE = [ + const CUSTOM_DATA_SOURCE_WITH_STRING_KEY = [ { name: "Section 1", items: [ @@ -95,15 +95,181 @@ describe("Select", () => { }, ]; - it("supports string based option mapping for object options", async () => { + it("supports string based option mapping for object options with string keys", async () => { render(() => ( - options={CUSTOM_DATA_SOURCE} + options={CUSTOM_DATA_SOURCE_WITH_STRING_KEY} optionValue="id" optionTextValue="valueText" optionDisabled="disabled" optionGroupChildren="items" placeholder="Placeholder" + onChange={onValueChange} + itemComponent={props => ( + {props.item.rawValue.name} + )} + sectionComponent={props => {props.section.rawValue.name}} + > + + Label + + >{state => state.selectedOption().name} + + + + + + + + )); + + const trigger = screen.getByRole("button"); + + fireEvent(trigger, createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + fireEvent(trigger, createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + jest.runAllTimers(); + + const listbox = screen.getByRole("listbox"); + + const items = within(listbox).getAllByRole("option"); + + expect(items.length).toBe(3); + + expect(items[0]).toHaveTextContent("One"); + expect(items[0]).toHaveAttribute("data-key", "1"); + expect(items[0]).not.toHaveAttribute("data-disabled"); + + expect(items[1]).toHaveTextContent("Two"); + expect(items[1]).toHaveAttribute("data-key", "2"); + expect(items[1]).toHaveAttribute("data-disabled"); + + expect(items[2]).toHaveTextContent("Three"); + expect(items[2]).toHaveAttribute("data-key", "3"); + expect(items[2]).not.toHaveAttribute("data-disabled"); + + fireEvent( + items[2], + createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" }) + ); + await Promise.resolve(); + + fireEvent(items[2], createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + expect(onValueChange).toHaveBeenCalledTimes(1); + expect(onValueChange.mock.calls[0][0]).toBe(CUSTOM_DATA_SOURCE_WITH_STRING_KEY[0].items[2]); + + expect(listbox).not.toBeVisible(); + + // run restore focus rAF + jest.runAllTimers(); + + expect(document.activeElement).toBe(trigger); + expect(trigger).toHaveTextContent("Three"); + }); + + it("supports function based option mapping for object options with string keys", async () => { + render(() => ( + + options={CUSTOM_DATA_SOURCE_WITH_STRING_KEY} + optionValue={option => option.id} + optionTextValue={option => option.valueText} + optionDisabled={option => option.disabled} + optionGroupChildren={optGroup => optGroup.items} + placeholder="Placeholder" + onChange={onValueChange} + itemComponent={props => ( + {props.item.rawValue.name} + )} + sectionComponent={props => {props.section.rawValue.name}} + > + + Label + + >{state => state.selectedOption().name} + + + + + + + + )); + + const trigger = screen.getByRole("button"); + + fireEvent(trigger, createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + fireEvent(trigger, createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + jest.runAllTimers(); + + const listbox = screen.getByRole("listbox"); + + const items = within(listbox).getAllByRole("option"); + + expect(items.length).toBe(3); + + expect(items[0]).toHaveTextContent("One"); + expect(items[0]).toHaveAttribute("data-key", "1"); + expect(items[0]).not.toHaveAttribute("data-disabled"); + + expect(items[1]).toHaveTextContent("Two"); + expect(items[1]).toHaveAttribute("data-key", "2"); + expect(items[1]).toHaveAttribute("data-disabled"); + + expect(items[2]).toHaveTextContent("Three"); + expect(items[2]).toHaveAttribute("data-key", "3"); + expect(items[2]).not.toHaveAttribute("data-disabled"); + + fireEvent( + items[2], + createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" }) + ); + await Promise.resolve(); + + fireEvent(items[2], createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + expect(onValueChange).toHaveBeenCalledTimes(1); + expect(onValueChange.mock.calls[0][0]).toBe(CUSTOM_DATA_SOURCE_WITH_STRING_KEY[0].items[2]); + + expect(listbox).not.toBeVisible(); + + // run restore focus rAF + jest.runAllTimers(); + + expect(document.activeElement).toBe(trigger); + expect(trigger).toHaveTextContent("Three"); + }); + + const CUSTOM_DATA_SOURCE_WITH_NUMBER_KEY = [ + { + name: "Section 1", + items: [ + { id: 1, name: "One", valueText: "One", disabled: false }, + { id: 2, name: "Two", valueText: "Two", disabled: true }, + { id: 3, name: "Three", valueText: "Three", disabled: false }, + ], + }, + ]; + + it("supports string based option mapping for object options with number keys", async () => { + render(() => ( + + options={CUSTOM_DATA_SOURCE_WITH_NUMBER_KEY} + optionValue="id" + optionTextValue="valueText" + optionDisabled="disabled" + optionGroupChildren="items" + placeholder="Placeholder" + onChange={onValueChange} itemComponent={props => ( {props.item.rawValue.name} )} @@ -149,17 +315,38 @@ describe("Select", () => { expect(items[2]).toHaveTextContent("Three"); expect(items[2]).toHaveAttribute("data-key", "3"); expect(items[2]).not.toHaveAttribute("data-disabled"); + + fireEvent( + items[2], + createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" }) + ); + await Promise.resolve(); + + fireEvent(items[2], createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + expect(onValueChange).toHaveBeenCalledTimes(1); + expect(onValueChange.mock.calls[0][0]).toBe(CUSTOM_DATA_SOURCE_WITH_NUMBER_KEY[0].items[2]); + + expect(listbox).not.toBeVisible(); + + // run restore focus rAF + jest.runAllTimers(); + + expect(document.activeElement).toBe(trigger); + expect(trigger).toHaveTextContent("Three"); }); - it("supports function based option mapping for object options", async () => { + it("supports function based option mapping for object options with number keys", async () => { render(() => ( - options={CUSTOM_DATA_SOURCE} + options={CUSTOM_DATA_SOURCE_WITH_NUMBER_KEY} optionValue={option => option.id} optionTextValue={option => option.valueText} optionDisabled={option => option.disabled} optionGroupChildren={optGroup => optGroup.items} placeholder="Placeholder" + onChange={onValueChange} itemComponent={props => ( {props.item.rawValue.name} )} @@ -205,6 +392,26 @@ describe("Select", () => { expect(items[2]).toHaveTextContent("Three"); expect(items[2]).toHaveAttribute("data-key", "3"); expect(items[2]).not.toHaveAttribute("data-disabled"); + + fireEvent( + items[2], + createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" }) + ); + await Promise.resolve(); + + fireEvent(items[2], createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + expect(onValueChange).toHaveBeenCalledTimes(1); + expect(onValueChange.mock.calls[0][0]).toBe(CUSTOM_DATA_SOURCE_WITH_NUMBER_KEY[0].items[2]); + + expect(listbox).not.toBeVisible(); + + // run restore focus rAF + jest.runAllTimers(); + + expect(document.activeElement).toBe(trigger); + expect(trigger).toHaveTextContent("Three"); }); it("supports string options without mapping", async () => { @@ -212,6 +419,7 @@ describe("Select", () => { ( {props.item.rawValue} )} @@ -256,6 +464,26 @@ describe("Select", () => { expect(items[2]).toHaveTextContent("Three"); expect(items[2]).toHaveAttribute("data-key", "Three"); expect(items[2]).not.toHaveAttribute("data-disabled"); + + fireEvent( + items[2], + createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" }) + ); + await Promise.resolve(); + + fireEvent(items[2], createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + expect(onValueChange).toHaveBeenCalledTimes(1); + expect(onValueChange.mock.calls[0][0]).toBe("Three"); + + expect(listbox).not.toBeVisible(); + + // run restore focus rAF + jest.runAllTimers(); + + expect(document.activeElement).toBe(trigger); + expect(trigger).toHaveTextContent("Three"); }); it("supports function based option mapping for string options", async () => { @@ -266,6 +494,7 @@ describe("Select", () => { optionTextValue={option => option} optionDisabled={option => option === "Two"} placeholder="Placeholder" + onChange={onValueChange} itemComponent={props => ( {props.item.rawValue} )} @@ -310,6 +539,173 @@ describe("Select", () => { expect(items[2]).toHaveTextContent("Three"); expect(items[2]).toHaveAttribute("data-key", "Three"); expect(items[2]).not.toHaveAttribute("data-disabled"); + + fireEvent( + items[2], + createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" }) + ); + await Promise.resolve(); + + fireEvent(items[2], createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + expect(onValueChange).toHaveBeenCalledTimes(1); + expect(onValueChange.mock.calls[0][0]).toBe("Three"); + + expect(listbox).not.toBeVisible(); + + // run restore focus rAF + jest.runAllTimers(); + + expect(document.activeElement).toBe(trigger); + expect(trigger).toHaveTextContent("Three"); + }); + + it("supports number options without mapping", async () => { + render(() => ( + ( + {props.item.rawValue} + )} + > + + Label + + >{state => state.selectedOption()} + + + + + + + + )); + + const trigger = screen.getByRole("button"); + + fireEvent(trigger, createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + fireEvent(trigger, createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + jest.runAllTimers(); + + const listbox = screen.getByRole("listbox"); + + const items = within(listbox).getAllByRole("option"); + + expect(items.length).toBe(3); + + expect(items[0]).toHaveTextContent("1"); + expect(items[0]).toHaveAttribute("data-key", "1"); + expect(items[0]).not.toHaveAttribute("data-disabled"); + + expect(items[1]).toHaveTextContent("2"); + expect(items[1]).toHaveAttribute("data-key", "2"); + expect(items[1]).not.toHaveAttribute("data-disabled"); + + expect(items[2]).toHaveTextContent("3"); + expect(items[2]).toHaveAttribute("data-key", "3"); + expect(items[2]).not.toHaveAttribute("data-disabled"); + + fireEvent( + items[2], + createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" }) + ); + await Promise.resolve(); + + fireEvent(items[2], createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + expect(onValueChange).toHaveBeenCalledTimes(1); + expect(onValueChange.mock.calls[0][0]).toBe(3); + + expect(listbox).not.toBeVisible(); + + // run restore focus rAF + jest.runAllTimers(); + + expect(document.activeElement).toBe(trigger); + expect(trigger).toHaveTextContent("3"); + }); + + it("supports function based option mapping for number options", async () => { + render(() => ( + option} + optionTextValue={option => option.toString()} + optionDisabled={option => option === 2} + placeholder="Placeholder" + onChange={onValueChange} + itemComponent={props => ( + {props.item.rawValue} + )} + > + + Label + + >{state => state.selectedOption()} + + + + + + + + )); + + const trigger = screen.getByRole("button"); + + fireEvent(trigger, createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + fireEvent(trigger, createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + jest.runAllTimers(); + + const listbox = screen.getByRole("listbox"); + + const items = within(listbox).getAllByRole("option"); + + expect(items.length).toBe(3); + + expect(items[0]).toHaveTextContent("1"); + expect(items[0]).toHaveAttribute("data-key", "1"); + expect(items[0]).not.toHaveAttribute("data-disabled"); + + expect(items[1]).toHaveTextContent("2"); + expect(items[1]).toHaveAttribute("data-key", "2"); + expect(items[1]).toHaveAttribute("data-disabled"); + + expect(items[2]).toHaveTextContent("3"); + expect(items[2]).toHaveAttribute("data-key", "3"); + expect(items[2]).not.toHaveAttribute("data-disabled"); + + fireEvent( + items[2], + createPointerEvent("pointerdown", { pointerId: 1, pointerType: "mouse" }) + ); + await Promise.resolve(); + + fireEvent(items[2], createPointerEvent("pointerup", { pointerId: 1, pointerType: "mouse" })); + await Promise.resolve(); + + expect(onValueChange).toHaveBeenCalledTimes(1); + expect(onValueChange.mock.calls[0][0]).toBe(3); + + expect(listbox).not.toBeVisible(); + + // run restore focus rAF + jest.runAllTimers(); + + expect(document.activeElement).toBe(trigger); + expect(trigger).toHaveTextContent("3"); }); }); diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 048f7c13..db18087c 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -19,12 +19,14 @@ const STATES = [ "selected", "pressed", "expanded", + "opened", "closed", "highlighted", "current", ]; - const ORIENTATIONS = ["horizontal", "vertical"]; +const SWIPE_STATES = ["start", "move", "cancel", "end"]; +const SWIPE_DIRECTIONS = ["up", "down", "left", "right"]; export interface KobalteTailwindPluginOptions { /** The prefix of generated classes. */ @@ -52,5 +54,29 @@ export default plugin.withOptions(({ prefix = "ui" `:merge(.peer)[data-orientation='${orientation}'] ~ &` ); } + + for (const state of SWIPE_STATES) { + addVariant(`${prefix}-swipe-${state}`, [`&[data-swipe='${state}']`]); + addVariant(`${prefix}-not-swipe-${state}`, [`&:not([data-swipe='${state}'])`]); + addVariant(`${prefix}-group-swipe-${state}`, `:merge(.group)[data-swipe='${state}'] &`); + addVariant(`${prefix}-peer-swipe-${state}`, `:merge(.peer)[data-swipe='${state}'] ~ &`); + } + + for (const direction of SWIPE_DIRECTIONS) { + addVariant(`${prefix}-swipe-direction-${direction}`, [ + `&[data-swipe-direction='${direction}']`, + ]); + addVariant(`${prefix}-not-swipe-direction-${direction}`, [ + `&:not([data-swipe-direction='${direction}'])`, + ]); + addVariant( + `${prefix}-group-swipe-direction-${direction}`, + `:merge(.group)[data-swipe-direction='${direction}'] &` + ); + addVariant( + `${prefix}-peer-swipe-direction-${direction}`, + `:merge(.peer)[data-swipe-direction='${direction}'] ~ &` + ); + } }; }); diff --git a/packages/utils/src/assertion.ts b/packages/utils/src/assertion.ts index 62c48b20..aed55a9e 100644 --- a/packages/utils/src/assertion.ts +++ b/packages/utils/src/assertion.ts @@ -6,6 +6,11 @@ * https://github.com/chakra-ui/chakra-ui/blob/main/packages/utils/src/assertion.ts */ +// Number assertions +export function isNumber(value: any): value is number { + return typeof value === "number"; +} + // Array assertions export function isArray(value: any): value is Array { return Array.isArray(value); @@ -20,3 +25,9 @@ export function isString(value: any): value is string { export function isFunction(value: any): value is T { return typeof value === "function"; } + +// Object assertions +export function isObject(value: any): value is Record { + const type = typeof value; + return value != null && (type === "object" || type === "function") && !isArray(value); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 267cfe6a..3f5cb63e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3140,12 +3140,12 @@ packages: '@typescript-eslint/visitor-keys': 5.57.0 dev: true - /@typescript-eslint/scope-manager/5.59.0: - resolution: {integrity: sha512-tsoldKaMh7izN6BvkK6zRMINj4Z2d6gGhO2UsI8zGZY3XhLq1DndP3Ycjhi1JwdwPRwtLMW4EFPgpuKhbCGOvQ==} + /@typescript-eslint/scope-manager/5.59.1: + resolution: {integrity: sha512-mau0waO5frJctPuAzcxiNWqJR5Z8V0190FTSqRw1Q4Euop6+zTwHAf8YIXNwDOT29tyUDrQ65jSg9aTU/H0omA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.59.0 - '@typescript-eslint/visitor-keys': 5.59.0 + '@typescript-eslint/types': 5.59.1 + '@typescript-eslint/visitor-keys': 5.59.1 dev: true /@typescript-eslint/type-utils/5.57.0_ip5up2nocltd47wbnuyybe5dxu: @@ -3173,8 +3173,8 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/types/5.59.0: - resolution: {integrity: sha512-yR2h1NotF23xFFYKHZs17QJnB51J/s+ud4PYU4MqdZbzeNxpgUr05+dNeCN/bb6raslHvGdd6BFCkVhpPk/ZeA==} + /@typescript-eslint/types/5.59.1: + resolution: {integrity: sha512-dg0ICB+RZwHlysIy/Dh1SP+gnXNzwd/KS0JprD3Lmgmdq+dJAJnUPe1gNG34p0U19HvRlGX733d/KqscrGC1Pg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -3199,8 +3199,8 @@ packages: - supports-color dev: true - /@typescript-eslint/typescript-estree/5.59.0_typescript@4.9.5: - resolution: {integrity: sha512-sUNnktjmI8DyGzPdZ8dRwW741zopGxltGs/SAPgGL/AAgDpiLsCFLcMNSpbfXfmnNeHmK9h3wGmCkGRGAoUZAg==} + /@typescript-eslint/typescript-estree/5.59.1_typescript@4.9.5: + resolution: {integrity: sha512-lYLBBOCsFltFy7XVqzX0Ju+Lh3WPIAWxYpmH/Q7ZoqzbscLiCW00LeYCdsUnnfnj29/s1WovXKh2gwCoinHNGA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -3208,8 +3208,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.59.0 - '@typescript-eslint/visitor-keys': 5.59.0 + '@typescript-eslint/types': 5.59.1 + '@typescript-eslint/visitor-keys': 5.59.1 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -3240,8 +3240,8 @@ packages: - typescript dev: true - /@typescript-eslint/utils/5.59.0_ip5up2nocltd47wbnuyybe5dxu: - resolution: {integrity: sha512-GGLFd+86drlHSvPgN/el6dRQNYYGOvRSDVydsUaQluwIW3HvbXuxyuD5JETvBt/9qGYe+lOrDk6gRrWOHb/FvA==} + /@typescript-eslint/utils/5.59.1_ip5up2nocltd47wbnuyybe5dxu: + resolution: {integrity: sha512-MkTe7FE+K1/GxZkP5gRj3rCztg45bEhsd8HYjczBuYm+qFHP5vtZmjx3B0yUCDotceQ4sHgTyz60Ycl225njmA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -3249,9 +3249,9 @@ packages: '@eslint-community/eslint-utils': 4.4.0_eslint@8.37.0 '@types/json-schema': 7.0.11 '@types/semver': 7.3.13 - '@typescript-eslint/scope-manager': 5.59.0 - '@typescript-eslint/types': 5.59.0 - '@typescript-eslint/typescript-estree': 5.59.0_typescript@4.9.5 + '@typescript-eslint/scope-manager': 5.59.1 + '@typescript-eslint/types': 5.59.1 + '@typescript-eslint/typescript-estree': 5.59.1_typescript@4.9.5 eslint: 8.37.0 eslint-scope: 5.1.1 semver: 7.5.0 @@ -3268,11 +3268,11 @@ packages: eslint-visitor-keys: 3.4.0 dev: true - /@typescript-eslint/visitor-keys/5.59.0: - resolution: {integrity: sha512-qZ3iXxQhanchCeaExlKPV3gDQFxMUmU35xfd5eCXB6+kUw1TUAbIy2n7QIrwz9s98DQLzNWyHp61fY0da4ZcbA==} + /@typescript-eslint/visitor-keys/5.59.1: + resolution: {integrity: sha512-6waEYwBTCWryx0VJmP7JaM4FpipLsFl9CvYf2foAE8Qh/Y0s+bxWysciwOs0LTBED4JCaNxTZ5rGadB14M6dwA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.59.0 + '@typescript-eslint/types': 5.59.1 eslint-visitor-keys: 3.4.0 dev: true @@ -3817,7 +3817,7 @@ packages: hasBin: true dependencies: caniuse-lite: 1.0.30001481 - electron-to-chromium: 1.4.369 + electron-to-chromium: 1.4.370 node-releases: 2.0.10 update-browserslist-db: 1.0.11_browserslist@4.21.5 @@ -4576,8 +4576,8 @@ packages: /ee-first/1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - /electron-to-chromium/1.4.369: - resolution: {integrity: sha512-LfxbHXdA/S+qyoTEA4EbhxGjrxx7WK2h6yb5K2v0UCOufUKX+VZaHbl3svlzZfv9sGseym/g3Ne4DpsgRULmqg==} + /electron-to-chromium/1.4.370: + resolution: {integrity: sha512-c+wzD4sCYQeNeasbnArwsU3ig6+lR6bwQmxfMjB6bx+XoopVSPFp+7ZLxqa90MTC+Tq9QQ5l7zsMNG9GgMBorg==} /emittery/0.10.2: resolution: {integrity: sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==} @@ -5120,7 +5120,7 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@typescript-eslint/utils': 5.59.0_ip5up2nocltd47wbnuyybe5dxu + '@typescript-eslint/utils': 5.59.1_ip5up2nocltd47wbnuyybe5dxu eslint: 8.37.0 is-html: 2.0.0 jsx-ast-utils: 3.3.3 @@ -7095,7 +7095,7 @@ packages: object-inspect: 1.12.3 pidtree: 0.6.0 string-argv: 0.3.1 - yaml: 2.2.1 + yaml: 2.2.2 transitivePeerDependencies: - enquirer - supports-color @@ -10483,7 +10483,7 @@ packages: dependencies: '@types/node': 18.15.11 esbuild: 0.15.18 - postcss: 8.4.21 + postcss: 8.4.23 resolve: 1.22.2 rollup: 2.79.1 optionalDependencies: @@ -10734,8 +10734,8 @@ packages: engines: {node: '>= 6'} dev: true - /yaml/2.2.1: - resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==} + /yaml/2.2.2: + resolution: {integrity: sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==} engines: {node: '>= 14'} dev: true