diff --git a/.changeset/late-ravens-smoke.md b/.changeset/late-ravens-smoke.md
deleted file mode 100644
index 34c2c7931..000000000
--- a/.changeset/late-ravens-smoke.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@qwik-ui/headless': patch
----
-
-fix: select validates correctly with modular forms
diff --git a/.changeset/twenty-spoons-fail.md b/.changeset/twenty-spoons-fail.md
new file mode 100644
index 000000000..de92b2cae
--- /dev/null
+++ b/.changeset/twenty-spoons-fail.md
@@ -0,0 +1,5 @@
+---
+'@qwik-ui/headless': patch
+---
+
+Chromium 109-113 did not properly support the popover but reported that they did. This led to the polyfill not activating which caused our E2E tests to break. We are dropping down to Chromium 108 to validate the polyfill as users of Chrome would see it before the popover API was supported.
diff --git a/apps/website/src/routes/docs/headless/select/examples/description.tsx b/apps/website/src/routes/docs/headless/select/examples/description.tsx
new file mode 100644
index 000000000..4f4061c9c
--- /dev/null
+++ b/apps/website/src/routes/docs/headless/select/examples/description.tsx
@@ -0,0 +1,33 @@
+import { component$, useStyles$ } from '@builder.io/qwik';
+import { Select } from '@qwik-ui/headless';
+
+export default component$(() => {
+ useStyles$(styles);
+ const users = ['Tim', 'Ryan', 'Jim', 'Jessie', 'Abby'];
+
+ return (
+
+ Logged in users
+
+
+
+ Select a user to see their profile
+
+
+ {users.map((user) => (
+
+ {user}
+
+
+
+
+ ))}
+
+
+
+ );
+});
+
+// internal
+import styles from '../snippets/select.css?inline';
+import { LuCheck } from '@qwikest/icons/lucide';
diff --git a/apps/website/src/routes/docs/headless/select/examples/validation.tsx b/apps/website/src/routes/docs/headless/select/examples/validation.tsx
index 865c4b57f..fd51df1b0 100644
--- a/apps/website/src/routes/docs/headless/select/examples/validation.tsx
+++ b/apps/website/src/routes/docs/headless/select/examples/validation.tsx
@@ -34,7 +34,11 @@ export default component$(() => {
- {field.error &&
{field.error}
}
+ {field.error && (
+
+ {field.error}
+
+ )}
{users.map((user) => (
diff --git a/apps/website/src/routes/docs/headless/select/index.mdx b/apps/website/src/routes/docs/headless/select/index.mdx
index 212ae3b6e..031b58968 100644
--- a/apps/website/src/routes/docs/headless/select/index.mdx
+++ b/apps/website/src/routes/docs/headless/select/index.mdx
@@ -175,7 +175,15 @@ The native select element is created when a form name has been given to `
-Above is an example of submitting a multi-select form.
+The `` component is used to display errors when the select is invalid.
+
+To style based on the invalid state, use the `data-invalid` data attribute.
+
+### Descriptions
+
+Provide more information to assistive technologies by adding a description to the select.
+
+
## Component state
diff --git a/apps/website/src/routes/docs/headless/select/snippets/select.css b/apps/website/src/routes/docs/headless/select/snippets/select.css
index 582740d8a..bd124480e 100644
--- a/apps/website/src/routes/docs/headless/select/snippets/select.css
+++ b/apps/website/src/routes/docs/headless/select/snippets/select.css
@@ -10,19 +10,28 @@
width: 100%;
height: 100%;
border: 2px dotted hsla(var(--primary) / 1);
- border-radius: calc(var (--border-radius) / 2);
min-height: 44px;
max-width: var(--select-width);
padding-block: 0.5rem;
display: flex;
justify-content: center;
align-items: center;
+ margin-top: 0.25rem;
}
.select-trigger:hover {
background-color: hsla(var(--primary) / 0.08);
}
+.select-trigger:focus-visible {
+ outline: 2px solid hsla(var(--primary) / 1);
+ outline-offset: 2px;
+}
+
+.select-trigger[data-invalid] {
+ border: 2px dotted #d2122e;
+}
+
.select-popover {
width: 100%;
max-width: var(--select-width);
@@ -33,7 +42,6 @@
background-color: hsl(var(--background));
padding: 0.5rem;
border: 2px dotted hsla(var(--foreground) / 0.6);
- border-radius: calc(var(--border-radius) / 2);
max-width: var(--select-width);
color: hsl(var(--foreground));
}
@@ -87,7 +95,6 @@
[data-highlighted] {
background-color: hsla(var(--primary) / 0.08);
outline: 2px dotted hsla(var(--primary) / 1);
- border-radius: calc(var(--border-radius) / 2);
}
[data-disabled] {
diff --git a/package.json b/package.json
index 467bdcc1f..48db63cf0 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,7 @@
"test.headless": "nx component-test headless --skip-nx-cache",
"test.visual.headless": "nx visual-test headless",
"test.pw.headless": "nx e2e headless",
- "test.pw.headless-chrome-113": "nx e2e-chrome-113 headless",
+ "test.pw.headless-chrome-108": "nx e2e-chrome-108 headless",
"test.pw.headless.ci": "nx e2e headless",
"test.headless.ci": "nx component-test-ci headless",
"test.utils": "nx test utils"
diff --git a/packages/kit-headless/CHANGELOG.md b/packages/kit-headless/CHANGELOG.md
index a1bae1abe..52acfb150 100644
--- a/packages/kit-headless/CHANGELOG.md
+++ b/packages/kit-headless/CHANGELOG.md
@@ -1,5 +1,25 @@
# Changelog
+## 0.4.4
+
+### Patch Changes
+
+- ✨ new Select.ErrorMessage component (by [@thejackshelton](https://github.com/thejackshelton) in [#825](https://github.com/qwikifiers/qwik-ui/pull/825))
+
+ feat: data-invalid attribute to style when the select is invalid
+
+ feat: new Select.Description component
+
+## 0.4.3
+
+### Patch Changes
+
+- 🐞🩹 select validates correctly with modular forms (by [@thejackshelton](https://github.com/thejackshelton) in [#814](https://github.com/qwikifiers/qwik-ui/pull/814))
+
+- refactor: improved select focus navigation (by [@thejackshelton](https://github.com/thejackshelton) in [#822](https://github.com/qwikifiers/qwik-ui/pull/822))
+
+- 🐞🩹 select can now be reactively disabled (by [@thejackshelton](https://github.com/thejackshelton) in [#823](https://github.com/qwikifiers/qwik-ui/pull/823))
+
## 0.4.2
### Patch Changes
diff --git a/packages/kit-headless/package.json b/packages/kit-headless/package.json
index 81fe8e6b3..8cb78618f 100644
--- a/packages/kit-headless/package.json
+++ b/packages/kit-headless/package.json
@@ -1,6 +1,6 @@
{
"name": "@qwik-ui/headless",
- "version": "0.4.2",
+ "version": "0.4.4",
"description": "Qwik UI headless components library",
"publishConfig": {
"access": "public"
@@ -16,7 +16,7 @@
"types": "./index.d.ts",
"type": "module",
"scripts": {
- "setup.chrome.113": "npx tsx ./scripts/prepare-chrome-113.ts"
+ "setup.chrome.108": "npx tsx ./scripts/prepare-chrome-browser.ts"
},
"exports": {
".": {
diff --git a/packages/kit-headless/playwright.config.ts b/packages/kit-headless/playwright.config.ts
index 33a67eafd..9a03acc7d 100644
--- a/packages/kit-headless/playwright.config.ts
+++ b/packages/kit-headless/playwright.config.ts
@@ -18,16 +18,16 @@ let binaryPath;
switch (currentPlatform) {
case 'darwin':
binaryPath =
- './browsers/chrome/113/chrome-darwin/Chromium.app/Contents/MacOS/Chromium';
+ './browsers/chrome/108/chrome-darwin/Chromium.app/Contents/MacOS/Chromium';
break;
case 'win32':
- binaryPath = './browsers/chrome/113/chrome-win32/chrome.exe';
+ binaryPath = './browsers/chrome/108/chrome-win32/chrome.exe';
break;
case 'linux':
- binaryPath = './browsers/chrome/113/chrome-linux/chrome';
+ binaryPath = './browsers/chrome/108/chrome-linux/chrome';
break;
default:
- throw new Error('Cannot install Chrome 13 on unknown platform');
+ throw new Error('Cannot install Chrome 108 on unknown platform');
}
/**
@@ -65,7 +65,7 @@ export default defineConfig({
},
{
- name: 'popover-chrome-113',
+ name: 'popover-chrome-108',
use: {
launchOptions: {
executablePath: path.resolve(dirName, binaryPath),
diff --git a/packages/kit-headless/project.json b/packages/kit-headless/project.json
index fa35ad86e..86b370168 100644
--- a/packages/kit-headless/project.json
+++ b/packages/kit-headless/project.json
@@ -55,19 +55,19 @@
"project": ["logic"]
}
},
- "setup-chrome-113": {
+ "setup-chrome-108": {
"executor": "nx:run-script",
"options": {
- "script": "setup.chrome.113"
+ "script": "setup.chrome.108"
}
},
- "e2e-chrome-113": {
+ "e2e-chrome-108": {
"executor": "@nx/playwright:playwright",
"outputs": ["{workspaceRoot}/dist/.playwright/packages/kit-headless"],
- "dependsOn": ["setup-chrome-113"],
+ "dependsOn": ["setup-chrome-108"],
"options": {
"config": "packages/kit-headless/playwright.config.ts",
- "project": ["popover-chrome-113"]
+ "project": ["popover-chrome-108"]
}
},
"visual-test": {
diff --git a/packages/kit-headless/scripts/prepare-chrome-113.ts b/packages/kit-headless/scripts/prepare-chrome-browser.ts
similarity index 86%
rename from packages/kit-headless/scripts/prepare-chrome-113.ts
rename to packages/kit-headless/scripts/prepare-chrome-browser.ts
index 0160776e5..ef6040254 100644
--- a/packages/kit-headless/scripts/prepare-chrome-113.ts
+++ b/packages/kit-headless/scripts/prepare-chrome-browser.ts
@@ -12,16 +12,16 @@ function printProgress(progress: number) {
}
async function prepareChrome113() {
- const path = `browsers/chrome/113/chrome-${os.platform()}`;
+ const path = `browsers/chrome/108/chrome-${os.platform()}`;
if (fs.existsSync(path)) {
- console.log('Chrome 113 already exists. Skipping unzip ⏩');
+ console.log('Chrome 108 already exists. Skipping unzip ⏩');
} else {
console.log('Creating directory to save the browser 🌐');
fs.mkdirSync(path, { recursive: true });
const downloadResponse = await fetch(
- `https://github.com/qwikifiers/qwik-ui-polyfill-browsers/raw/main/chrome/113/chrome-${os.platform()}.zip`,
+ `https://github.com/qwikifiers/qwik-ui-polyfill-browsers/raw/main/chrome/108/chrome-${os.platform()}.zip`,
);
const contentLength = downloadResponse.headers.get('content-length');
@@ -33,7 +33,7 @@ async function prepareChrome113() {
async start(controller) {
if (!downloadResponse.body) {
throw new Error(
- 'Invalid response received when trying to download Chrome 113',
+ 'Invalid response received when trying to download Chrome 108',
);
}
@@ -53,7 +53,7 @@ async function prepareChrome113() {
await decompress(
Buffer.from(await res.arrayBuffer()),
- `browsers/chrome/113/chrome-${os.platform()}`,
+ `browsers/chrome/108/chrome-${os.platform()}`,
{ strip: 1 },
);
}
diff --git a/packages/kit-headless/src/components/accordion/accordion-root.tsx b/packages/kit-headless/src/components/accordion/accordion-root.tsx
index 63c48cfb5..32345e4f7 100644
--- a/packages/kit-headless/src/components/accordion/accordion-root.tsx
+++ b/packages/kit-headless/src/components/accordion/accordion-root.tsx
@@ -19,9 +19,12 @@ export const HAccordionRootImpl = component$((props: AccordionRootProps) => {
disabled,
collapsible = true,
animated,
+ itemsMap,
...rest
} = props;
+ itemsMap;
+
const selectedIndexSig = useSignal(initialIndex ?? -1);
const triggerRefsArray = useSignal>([]);
const isAnimatedSig = useSignal(animated === true);
diff --git a/packages/kit-headless/src/components/combobox/combobox.test.ts-snapshots/closed-combobox-visual-win32.png b/packages/kit-headless/src/components/combobox/combobox.test.ts-snapshots/closed-combobox-visual-win32.png
new file mode 100644
index 000000000..eb456d989
Binary files /dev/null and b/packages/kit-headless/src/components/combobox/combobox.test.ts-snapshots/closed-combobox-visual-win32.png differ
diff --git a/packages/kit-headless/src/components/modal/modal.test.ts-snapshots/closed-modal-visual-win32.png b/packages/kit-headless/src/components/modal/modal.test.ts-snapshots/closed-modal-visual-win32.png
new file mode 100644
index 000000000..0d6d9fff7
Binary files /dev/null and b/packages/kit-headless/src/components/modal/modal.test.ts-snapshots/closed-modal-visual-win32.png differ
diff --git a/packages/kit-headless/src/components/modal/modal.test.ts-snapshots/opened-modal-visual-win32.png b/packages/kit-headless/src/components/modal/modal.test.ts-snapshots/opened-modal-visual-win32.png
new file mode 100644
index 000000000..0d6d9fff7
Binary files /dev/null and b/packages/kit-headless/src/components/modal/modal.test.ts-snapshots/opened-modal-visual-win32.png differ
diff --git a/packages/kit-headless/src/components/popover/popover-trigger.tsx b/packages/kit-headless/src/components/popover/popover-trigger.tsx
index da2173699..d2f874888 100644
--- a/packages/kit-headless/src/components/popover/popover-trigger.tsx
+++ b/packages/kit-headless/src/components/popover/popover-trigger.tsx
@@ -23,7 +23,10 @@ export const HPopoverTrigger = component$(
} = usePopover(context.compId);
const handleClick$ = $(async () => {
- if (context.hover) return;
+ if (context.hover && !isSupportedSig.value) {
+ await showPopover();
+ return;
+ }
if (isSupportedSig.value) return;
diff --git a/packages/kit-headless/src/components/popover/popover.driver.ts b/packages/kit-headless/src/components/popover/popover.driver.ts
index 080b3eeca..925804b58 100644
--- a/packages/kit-headless/src/components/popover/popover.driver.ts
+++ b/packages/kit-headless/src/components/popover/popover.driver.ts
@@ -9,6 +9,10 @@ export function createTestDriver(rootLocator: T) {
return rootLocator.locator('[popover]');
};
+ const getPopoverByTextContent = (popoverContent: string) => {
+ return rootLocator.locator('.popover-panel').getByText(popoverContent);
+ };
+
const getTrigger = () => {
return rootLocator.locator('[popovertarget]');
};
@@ -16,7 +20,11 @@ export function createTestDriver(rootLocator: T) {
const openPopover = async (key: PopoverOpenKeys | 'click', index?: number) => {
const action = key === 'click' ? 'click' : 'press';
const trigger = index !== undefined ? getTrigger().nth(index) : getTrigger();
- const popover = index !== undefined ? getPopover().nth(index) : getPopover();
+
+ const popover =
+ index !== undefined
+ ? getPopoverByTextContent(`Popover ${index + 1}`)
+ : getPopover();
if (action === 'click') {
await trigger.click({ position: { x: 1, y: 1 } }); // Modified line
@@ -26,6 +34,8 @@ export function createTestDriver(rootLocator: T) {
// Needed because Playwright doesn't wait for the listbox to be visible
await expect(popover).toBeVisible();
+
+ return { trigger, popover };
};
const getAllPopovers = () => {
@@ -49,5 +59,6 @@ export function createTestDriver(rootLocator: T) {
getAllTriggers,
openPopover,
getProgrammaticButtonTrigger,
+ getPopoverByTextContent,
};
}
diff --git a/packages/kit-headless/src/components/popover/popover.test.ts b/packages/kit-headless/src/components/popover/popover.test.ts
index b32c45300..536c4b16d 100644
--- a/packages/kit-headless/src/components/popover/popover.test.ts
+++ b/packages/kit-headless/src/components/popover/popover.test.ts
@@ -37,11 +37,11 @@ test.describe('Mouse Behavior', () => {
THEN the popover should close`, async ({ page }) => {
const { driver: d } = await setup(page, 'basic');
- await d.openPopover('click');
+ const { popover } = await d.openPopover('click');
await page.mouse.click(0, 0);
- await expect(d.getPopover()).toBeHidden();
+ await expect(popover).toBeHidden();
});
test(`GIVEN a pair of popovers in auto mode
@@ -52,11 +52,8 @@ test.describe('Mouse Behavior', () => {
}) => {
const { driver: d } = await setup(page, 'auto');
- const firstPopover = d.getPopover().nth(0);
- const secondPopover = d.getPopover().nth(1);
-
- await d.openPopover('click', 0);
- await d.openPopover('click', 1);
+ const { popover: firstPopover } = await d.openPopover('click', 0);
+ const { popover: secondPopover } = await d.openPopover('click', 1);
await expect(firstPopover).toBeHidden();
await expect(secondPopover).toBeVisible();
@@ -72,7 +69,7 @@ test.describe('Mouse Behavior', () => {
await page.mouse.click(0, 0);
- await expect(d.getPopover().nth(0)).toBeVisible();
+ await expect(d.getPopoverByTextContent('Popover 1')).toBeVisible();
});
test(`GIVEN a pair of manual popovers
@@ -81,8 +78,11 @@ test.describe('Mouse Behavior', () => {
THEN then both popovers should be opened`, async ({ page }) => {
const { driver: d } = await setup(page, 'manual');
- await d.openPopover('click', 0);
- await d.openPopover('click', 1);
+ const { popover: firstPopover } = await d.openPopover('click', 0);
+ const { popover: secondPopover } = await d.openPopover('click', 1);
+
+ await expect(firstPopover).toBeVisible();
+ await expect(secondPopover).toBeVisible();
});
test(`GIVEN a pair of manual opened popovers
@@ -91,13 +91,14 @@ test.describe('Mouse Behavior', () => {
THEN both popovers should be closed`, async ({ page }) => {
const { driver: d } = await setup(page, 'manual');
- const firstPopover = d.getPopover().nth(0);
- const secondPopover = d.getPopover().nth(1);
- const firstTrigger = d.getTrigger().nth(0);
- const secondTrigger = d.getTrigger().nth(1);
-
- await d.openPopover('click', 0);
- await d.openPopover('click', 1);
+ const { popover: firstPopover, trigger: firstTrigger } = await d.openPopover(
+ 'click',
+ 0,
+ );
+ const { popover: secondPopover, trigger: secondTrigger } = await d.openPopover(
+ 'click',
+ 1,
+ );
// Explicitly specifying click positions due to default behavior targeting the element's center,
// which is obscured by the popover in this scenario.
@@ -152,31 +153,46 @@ test.describe('Mouse Behavior', () => {
expect(gutterSpace).toBe(40);
});
- // test(`GIVEN a combobox with a flip configured
- // WHEN scrolling the page
- // THEN the popover flip to the opposite end once space runs out`, async ({ page }) => {
- // const { driver: d } = await setup(page, 'flip');
+ test(`GIVEN a combobox with a flip configured
+ WHEN scrolling the page
+ THEN the popover flip to the opposite end once space runs out`, async ({
+ page,
+ }) => {
+ const { driver: d } = await setup(page, 'flip');
- // const popover = d.getPopover();
- // const trigger = d.getTrigger();
+ const popover = d.getPopover();
+ const trigger = d.getTrigger();
- // // Introduce artificial spacing
- // await trigger.evaluate((element) => (element.style.top = '800px'));
+ async function calculateYDiff() {
+ const popoverBoundingBox = await popover.boundingBox();
+ const triggerBoundingBox = await trigger.boundingBox();
- // await trigger.click();
+ console.log(triggerBoundingBox, popoverBoundingBox);
- // await expect(popover).toBeVisible();
+ return (popoverBoundingBox?.y ?? 0) - (triggerBoundingBox?.y ?? 0);
+ }
- // const popoverBoundingBox = await popover.boundingBox();
- // const triggerBoundingBox = await trigger.boundingBox();
+ // Introduce artificial spacing
+ await trigger.evaluate((element) => (element.style.marginTop = '2000px'));
+ await trigger.evaluate((element) => (element.style.marginBottom = '1000px'));
- // console.log(triggerBoundingBox, popoverBoundingBox);
+ await trigger.click();
- // const triggerBottomAbsolutePosition =
- // (triggerBoundingBox?.y ?? 0) + (triggerBoundingBox?.height ?? 0);
+ // Should be below the trigger
+ await expect(popover).toBeVisible();
- // expect((popoverBoundingBox?.y ?? 0) - triggerBottomAbsolutePosition).toBe(24);
- // });
+ let yDiff = await calculateYDiff();
+
+ expect(yDiff).toBeGreaterThan(0);
+
+ await page.evaluate(() => window.scrollBy(0, -400));
+
+ await page.waitForTimeout(1000);
+
+ // Should be above the trigger
+ yDiff = await calculateYDiff();
+ expect(yDiff).toBeLessThan(0);
+ });
});
test.describe('Keyboard Behavior', () => {
@@ -245,13 +261,7 @@ test.describe('Keyboard Behavior', () => {
THEN the first popover should close and the second one appear`, async ({ page }) => {
const { driver: d } = await setup(page, 'auto');
- const firstPopover = d.getPopover().nth(0);
- const secondPopover = d.getPopover().nth(1);
-
- await expect(firstPopover).toBeHidden();
- await expect(secondPopover).toBeHidden();
-
- await d.openPopover('Enter', 0);
+ const { popover: firstPopover } = await d.openPopover('Enter', 0);
await d.openPopover('Enter', 1);
await expect(firstPopover).toBeHidden();
@@ -264,13 +274,14 @@ test.describe('Keyboard Behavior', () => {
THEN then both popovers should be closed`, async ({ page }) => {
const { driver: d } = await setup(page, 'manual');
- const firstPopover = d.getPopover().nth(0);
- const secondPopover = d.getPopover().nth(1);
- const firstTrigger = d.getTrigger().nth(0);
- const secondTrigger = d.getTrigger().nth(1);
-
- await d.openPopover('Enter', 0);
- await d.openPopover('Enter', 1);
+ const { popover: firstPopover, trigger: firstTrigger } = await d.openPopover(
+ 'click',
+ 0,
+ );
+ const { popover: secondPopover, trigger: secondTrigger } = await d.openPopover(
+ 'click',
+ 1,
+ );
await secondTrigger.press('Enter');
await expect(secondPopover).toBeHidden();
@@ -319,12 +330,15 @@ test.describe('Keyboard Behavior', () => {
const popover = d.getPopover();
const trigger = d.getTrigger();
+ await trigger.focus();
+ await expect(trigger).toBeFocused();
+
await trigger.press('Enter');
await expect(popover).toBeVisible();
- const popoverBoundingBox = await popover.boundingBox();
const triggerBoundingBox = await trigger.boundingBox();
+ const popoverBoundingBox = await popover.boundingBox();
expect(popoverBoundingBox?.x).toBeGreaterThan(
(triggerBoundingBox?.x ?? Number.MAX_VALUE) +
@@ -332,43 +346,3 @@ test.describe('Keyboard Behavior', () => {
);
});
});
-
-test.describe('Programmatic', () => {
- // test(`GIVEN a programmatic popover
- // WHEN the showPopover function is called
- // THEN the popover should be open`, async ({ page }) => {
- // const { driver: d } = await setup(page, 'show');
- // await expect(d.getPopover()).toBeHidden();
- // const programmaticTrigger = page.getByRole('button', { name: 'show popover' });
- // await programmaticTrigger.click();
- // await expect(d.getPopover()).toBeVisible();
- // });
- // test(`GIVEN an open programmatic popover
- // WHEN the hidePopover function is called
- // THEN the popover should be hidden`, async ({ page }) => {
- // const { driver: d } = await setup(page, 'hide');
- // const programmaticTrigger = page.getByRole('button', { name: 'hide popover' });
- // // initial open
- // await d.openPopover('click');
- // await programmaticTrigger.click({ position: { x: 0, y: 0 } });
- // await expect(d.getPopover()).toBeHidden();
- // });
- // test(`GIVEN a programmatic popover
- // WHEN the togglePopover function is called
- // THEN the popover should be open`, async ({ page }) => {
- // const { driver: d } = await setup(page, 'toggle');
- // const programmaticTrigger = page.getByRole('button', { name: 'toggle popover' });
- // await programmaticTrigger.click();
- // await expect(d.getPopover()).toBeVisible();
- // });
- // test(`GIVEN an open programmatic popover
- // WHEN the togglePopover function is called
- // THEN the popover should be closed`, async ({ page }) => {
- // const { driver: d } = await setup(page, 'toggle');
- // const programmaticTrigger = page.getByRole('button', { name: 'toggle popover' });
- // await programmaticTrigger.click();
- // await expect(d.getPopover()).toBeVisible();
- // await programmaticTrigger.click();
- // await expect(d.getPopover()).toBeHidden();
- // });
-});
diff --git a/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/closed-popover-popover-chrome-108-win32.png b/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/closed-popover-popover-chrome-108-win32.png
new file mode 100644
index 000000000..5d32d0f08
Binary files /dev/null and b/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/closed-popover-popover-chrome-108-win32.png differ
diff --git a/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/closed-popover-popover-chrome-113-win32.png b/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/closed-popover-popover-chrome-113-win32.png
new file mode 100644
index 000000000..5d32d0f08
Binary files /dev/null and b/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/closed-popover-popover-chrome-113-win32.png differ
diff --git a/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/closed-popover-visual-win32.png b/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/closed-popover-visual-win32.png
new file mode 100644
index 000000000..c64e14694
Binary files /dev/null and b/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/closed-popover-visual-win32.png differ
diff --git a/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/opened-popover-popover-chrome-108-win32.png b/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/opened-popover-popover-chrome-108-win32.png
new file mode 100644
index 000000000..5d32d0f08
Binary files /dev/null and b/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/opened-popover-popover-chrome-108-win32.png differ
diff --git a/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/opened-popover-popover-chrome-113-win32.png b/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/opened-popover-popover-chrome-113-win32.png
new file mode 100644
index 000000000..5d32d0f08
Binary files /dev/null and b/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/opened-popover-popover-chrome-113-win32.png differ
diff --git a/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/opened-popover-visual-win32.png b/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/opened-popover-visual-win32.png
new file mode 100644
index 000000000..b3c32b61b
Binary files /dev/null and b/packages/kit-headless/src/components/popover/popover.test.ts-snapshots/opened-popover-visual-win32.png differ
diff --git a/packages/kit-headless/src/components/select/hidden-select.tsx b/packages/kit-headless/src/components/select/hidden-select.tsx
index e82b2e81b..56dd6625c 100644
--- a/packages/kit-headless/src/components/select/hidden-select.tsx
+++ b/packages/kit-headless/src/components/select/hidden-select.tsx
@@ -41,10 +41,14 @@ export const HHiddenNativeSelect = component$(
// @ts-expect-error modular forms ref function
ref?.(element);
}}
+ onFocus$={() => {
+ // override modular forms focus event
+ return;
+ }}
multiple={context.multiple}
tabIndex={-1}
autocomplete={autoComplete}
- disabled={context.disabled}
+ disabled={context.isDisabledSig.value ? true : undefined}
required={context.required}
name={context.name}
// height is determined by its children
diff --git a/packages/kit-headless/src/components/select/index.ts b/packages/kit-headless/src/components/select/index.ts
index d410a7532..a64a3b95d 100644
--- a/packages/kit-headless/src/components/select/index.ts
+++ b/packages/kit-headless/src/components/select/index.ts
@@ -1,7 +1,7 @@
export { HSelectRoot as Root } from './select-inline';
export { HSelectLabel as Label } from './select-label';
export { HSelectTrigger as Trigger } from './select-trigger';
-export { HSelectDisplayText as DisplayValue } from './select-display-text';
+export { HSelectDisplayValue as DisplayValue } from './select-display-value';
export { HSelectPopover as Popover } from './select-popover';
export { HSelectListbox as Listbox } from './select-listbox';
export { HSelectGroup as Group } from './select-group';
@@ -9,4 +9,6 @@ export { HSelectGroupLabel as GroupLabel } from './select-group-label';
export { HSelectItem as Item } from './select-item';
export { HSelectItemLabel as ItemLabel } from './select-item-label';
export { HSelectItemIndicator as ItemIndicator } from './select-item-indicator';
+export { HSelectDescription as Description } from './select-description';
export { HHiddenNativeSelect as HiddenNativeSelect } from './hidden-select';
+export { HSelectErrorMessage as ErrorMessage } from './select-error-message';
diff --git a/packages/kit-headless/src/components/select/select-context.ts b/packages/kit-headless/src/components/select/select-context.ts
index 2e79fdd43..a4187c50b 100644
--- a/packages/kit-headless/src/components/select/select-context.ts
+++ b/packages/kit-headless/src/components/select/select-context.ts
@@ -14,6 +14,7 @@ export type SelectContext = {
listboxRef: Signal;
groupRef: Signal;
labelRef: Signal;
+ highlightedItemRef: Signal;
// core state
itemsMapSig: Readonly>;
@@ -21,6 +22,7 @@ export type SelectContext = {
highlightedIndexSig: Signal;
currDisplayValueSig: Signal;
isListboxOpenSig: Signal;
+ isDisabledSig: Signal;
localId: string;
// user configurable
@@ -38,10 +40,7 @@ export type SelectContext = {
*/
required?: boolean;
- /**
- * If `true`, prevents the user from interacting with the select.
- */
- disabled?: boolean;
+ isInvalidSig?: Signal;
};
export const groupContextId = createContextId('Select-Group');
diff --git a/packages/kit-headless/src/components/select/select-description.tsx b/packages/kit-headless/src/components/select/select-description.tsx
index 490d1165d..c125a0a6c 100644
--- a/packages/kit-headless/src/components/select/select-description.tsx
+++ b/packages/kit-headless/src/components/select/select-description.tsx
@@ -8,7 +8,11 @@ export const HSelectDescription = component$((props: SelectDescriptionProps) =>
const descriptionId = `${context.localId}-description`;
return (
-