summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHiDeoo2024-04-30 17:21:19 +0200
committerGitHub2024-04-30 17:21:19 +0200
commit7dc503ea7993123a4aeff453d08de41cac887353 (patch)
tree10dadc3fa0d24e902810b56d84ddffadb1b6d834
parent61493e55f1a80362af13f98d665018376e987439 (diff)
downloadIT.starlight-7dc503ea7993123a4aeff453d08de41cac887353.tar.gz
IT.starlight-7dc503ea7993123a4aeff453d08de41cac887353.tar.bz2
IT.starlight-7dc503ea7993123a4aeff453d08de41cac887353.zip
Add support for synced tabs (#640)
Co-authored-by: Chris Swithinbank <357379+delucis@users.noreply.github.com> Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com> Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
-rw-r--r--.changeset/config.json2
-rw-r--r--.changeset/eight-pens-end.md5
-rw-r--r--.changeset/slimy-swans-teach.md13
-rw-r--r--.github/workflows/ci.yml14
-rw-r--r--CONTRIBUTING.md31
-rw-r--r--docs/src/content/docs/getting-started.mdx6
-rw-r--r--docs/src/content/docs/guides/components.mdx44
-rw-r--r--docs/src/content/docs/guides/css-and-tailwind.mdx6
-rw-r--r--docs/src/content/docs/guides/customization.mdx4
-rw-r--r--package.json2
-rw-r--r--packages/starlight/__e2e__/.gitignore3
-rw-r--r--packages/starlight/__e2e__/fixtures/basics/astro.config.mjs10
-rw-r--r--packages/starlight/__e2e__/fixtures/basics/package.json9
-rw-r--r--packages/starlight/__e2e__/fixtures/basics/src/content/config.ts6
-rw-r--r--packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs-variable-height.mdx151
-rw-r--r--packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs.mdx37
-rw-r--r--packages/starlight/__e2e__/fixtures/basics/src/env.d.ts2
-rw-r--r--packages/starlight/__e2e__/tabs.test.ts122
-rw-r--r--packages/starlight/__e2e__/test-utils.ts53
-rw-r--r--packages/starlight/package.json4
-rw-r--r--packages/starlight/playwright.config.ts15
-rw-r--r--packages/starlight/style/props.css2
-rw-r--r--packages/starlight/user-components/Tabs.astro47
-rw-r--r--packages/starlight/vitest.config.ts3
-rw-r--r--pnpm-lock.yaml283
25 files changed, 627 insertions, 247 deletions
diff --git a/.changeset/config.json b/.changeset/config.json
index 1a3e4d0d..f50e3128 100644
--- a/.changeset/config.json
+++ b/.changeset/config.json
@@ -9,7 +9,7 @@
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
- "ignore": ["starlight-docs", "@example/*", "starlight-file-icons-generator"],
+ "ignore": ["starlight-docs", "@example/*", "starlight-file-icons-generator", "@e2e/*"],
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true
}
diff --git a/.changeset/eight-pens-end.md b/.changeset/eight-pens-end.md
new file mode 100644
index 00000000..4aaee094
--- /dev/null
+++ b/.changeset/eight-pens-end.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/starlight': minor
+---
+
+Adds support for syncing multiple sets of tabs on the same page.
diff --git a/.changeset/slimy-swans-teach.md b/.changeset/slimy-swans-teach.md
new file mode 100644
index 00000000..ae115067
--- /dev/null
+++ b/.changeset/slimy-swans-teach.md
@@ -0,0 +1,13 @@
+---
+'@astrojs/starlight': minor
+---
+
+Updates the default `line-height` from `1.8` to `1.75`. This change avoids having a line height with a fractional part which can cause scripts accessing dimensions involving the line height to get an inconsistent rounded value in various browsers.
+
+If you want to preserve the previous `line-height`, you can add the following custom CSS to your site:
+
+```css
+:root {
+ --sl-line-height: 1.8;
+}
+```
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fbde0027..beb12cf4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -30,6 +30,20 @@ jobs:
- name: Test packages
run: pnpm -r test:coverage
+ e2e-test:
+ name: Run E2E tests
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/checkout@v4
+ - uses: pnpm/action-setup@v3
+ - uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: 'pnpm'
+ - run: pnpm i
+ - name: Test packages
+ run: pnpm -r test:e2e
+
pa11y:
name: Check for accessibility issues
runs-on: ubuntu-20.04
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 275ead40..973d01ef 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -187,6 +187,36 @@ pnpm test:coverage
This will print a table to your terminal and also generate an HTML report you can load in a web browser by opening [`packages/starlight/__coverage__/index.html`](./packages/starlight/__coverage__/index.html).
+### End-to-end (E2E) tests
+
+Starlight also includes E2E tests in [`packages/starlight/__e2e__/`](./packages/starlight/__e2e__/), which are run using [Playwright][playwright].
+
+To run these tests, move into the Starlight package and then run `pnpm test:e2e`:
+
+```sh
+cd packages/starlight
+pnpm test:e2e
+```
+
+#### Test fixtures
+
+Each subdirectory of `packages/starlight/__e2e__/fixtures` should contain the basic files needed to run Starlight (`package.json`, `astro.config.mjs`, a content collection configuration in `src/content/config.ts` and some content to render in `src/content/docs/`).
+
+The `testFactory()` helper can be used in a test file to define the fixture which will be built and loaded in a preview server during a set of tests.
+
+```ts
+// packages/starlight/__e2e__/feature.test.ts
+import { testFactory } from './test-utils';
+
+const test = await testFactory('./fixtures/basics/');
+```
+
+This allows you to run tests against different combinations of Astro and Starlight configuration options for various content.
+
+#### When to add E2E tests?
+
+E2E are most useful for testing what happens on a page after it has been loaded by a browser. They run slower than unit tests so they should be used sparingly when unit tests aren’t sufficient.
+
## Translations
Translations help make Starlight accessible to more people.
@@ -254,6 +284,7 @@ To add a language, you will need its BCP-47 tag and a label. See [“Adding a ne
[gfi]: https://github.com/withastro/starlight/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22+
[api-docs]: https://docs.astro.build/en/reference/integrations-reference/
[vitest]: https://vitest.dev/
+[playwright]: https://playwright.dev/
## Showcase
diff --git a/docs/src/content/docs/getting-started.mdx b/docs/src/content/docs/getting-started.mdx
index eb3a8f3e..810b17f8 100644
--- a/docs/src/content/docs/getting-started.mdx
+++ b/docs/src/content/docs/getting-started.mdx
@@ -15,7 +15,7 @@ See the [manual setup instructions](/manual-setup/) to add Starlight to an exist
Create a new Astro + Starlight project by running the following command in your terminal:
-<Tabs>
+<Tabs syncKey="pkg">
<TabItem label="npm">
```sh
@@ -52,7 +52,7 @@ When working locally, [Astro’s development server](https://docs.astro.build/en
Inside your project directory, run the following command to start the development server:
-<Tabs>
+<Tabs syncKey="pkg">
<TabItem label="npm">
```sh
@@ -103,7 +103,7 @@ Be sure to update Starlight regularly!
Starlight is an Astro integration. You can update it and other Astro packages by running the following command in your terminal:
-<Tabs>
+<Tabs syncKey="pkg">
<TabItem label="npm">
```sh
diff --git a/docs/src/content/docs/guides/components.mdx b/docs/src/content/docs/guides/components.mdx
index a193175b..b01050f0 100644
--- a/docs/src/content/docs/guides/components.mdx
+++ b/docs/src/content/docs/guides/components.mdx
@@ -87,6 +87,50 @@ The code above generates the following tabs on the page:
</TabItem>
</Tabs>
+#### Synced tabs
+
+Keep multiple tab groups synchronized by adding the `syncKey` attribute.
+
+All `<Tabs>` on a page with the same `syncKey` value will display the same active label. This allows your reader to choose once (e.g. their operating system or package manager), and see their choice reflected throughout the page.
+
+To synchronize related tabs, add an identical `syncKey` property to each `<Tabs>` component and ensure that they all use the same `<TabItem>` labels:
+
+```mdx 'syncKey="constellations"'
+# src/content/docs/example.mdx
+
+import { Tabs, TabItem } from '@astrojs/starlight/components';
+
+_Some stars:_
+
+<Tabs syncKey="constellations">
+ <TabItem label="Orion">Bellatrix, Rigel, Betelgeuse</TabItem>
+ <TabItem label="Gemini">Pollux, Castor A, Castor B</TabItem>
+</Tabs>
+
+_Some exoplanets:_
+
+<Tabs syncKey="constellations">
+ <TabItem label="Orion">HD 34445 b, Gliese 179 b, Wasp-82 b</TabItem>
+ <TabItem label="Gemini">Pollux b, HAT-P-24b, HD 50554 b</TabItem>
+</Tabs>
+```
+
+The code above generates the following on the page:
+
+_Some stars:_
+
+<Tabs syncKey="constellations">
+ <TabItem label="Orion">Bellatrix, Rigel, Betelgeuse</TabItem>
+ <TabItem label="Gemini">Pollux, Castor A, Castor B</TabItem>
+</Tabs>
+
+_Some exoplanets:_
+
+<Tabs syncKey="constellations">
+ <TabItem label="Orion">HD 34445 b, Gliese 179 b, Wasp-82 b</TabItem>
+ <TabItem label="Gemini">Pollux b, HAT-P-24b, HD 50554 b</TabItem>
+</Tabs>
+
### Cards
import { Card, CardGrid } from '@astrojs/starlight/components';
diff --git a/docs/src/content/docs/guides/css-and-tailwind.mdx b/docs/src/content/docs/guides/css-and-tailwind.mdx
index 2ef37b60..45c88e65 100644
--- a/docs/src/content/docs/guides/css-and-tailwind.mdx
+++ b/docs/src/content/docs/guides/css-and-tailwind.mdx
@@ -63,7 +63,7 @@ The Starlight Tailwind plugin applies the following configuration:
Start a new Starlight project with Tailwind CSS pre-configured using `create astro`:
-<Tabs>
+<Tabs syncKey="pkg">
<TabItem label="npm">
```sh
@@ -95,7 +95,7 @@ If you already have a Starlight site and want to add Tailwind CSS, follow these
1. Add Astro’s Tailwind integration:
- <Tabs>
+ <Tabs syncKey="pkg">
<TabItem label="npm">
@@ -125,7 +125,7 @@ If you already have a Starlight site and want to add Tailwind CSS, follow these
2. Install the Starlight Tailwind plugin:
- <Tabs>
+ <Tabs syncKey="pkg">
<TabItem label="npm">
diff --git a/docs/src/content/docs/guides/customization.mdx b/docs/src/content/docs/guides/customization.mdx
index c2eaa328..1bb35d21 100644
--- a/docs/src/content/docs/guides/customization.mdx
+++ b/docs/src/content/docs/guides/customization.mdx
@@ -130,7 +130,7 @@ You can customize — or even disable — the table of contents globally in the
By default, `<h2>` and `<h3>` headings are included in the table of contents. Change which headings levels to include site-wide using the `minHeadingLevel` and `maxHeadingLevel` options in your [global `tableOfContents`](/reference/configuration/#tableofcontents). Override these defaults on an individual page by adding the corresponding [frontmatter `tableOfContents`](/reference/frontmatter/#tableofcontents) properties:
-<Tabs>
+<Tabs syncKey="config-type">
<TabItem label="Frontmatter">
```md {4-6}
@@ -164,7 +164,7 @@ defineConfig({
Disable the table of contents entirely by setting the `tableOfContents` option to `false`:
-<Tabs>
+<Tabs syncKey="config-type">
<TabItem label="Frontmatter">
```md {4}
diff --git a/package.json b/package.json
index e0609e2a..8e0fd9e6 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,7 @@
{
"name": "/_astro/*.js",
"path": "examples/basics/dist/_astro/*.js",
- "limit": "22.5 kB"
+ "limit": "23 kB"
},
{
"name": "/_astro/*.css",
diff --git a/packages/starlight/__e2e__/.gitignore b/packages/starlight/__e2e__/.gitignore
new file mode 100644
index 00000000..bde2931f
--- /dev/null
+++ b/packages/starlight/__e2e__/.gitignore
@@ -0,0 +1,3 @@
+# generated types
+.astro/
+dist/
diff --git a/packages/starlight/__e2e__/fixtures/basics/astro.config.mjs b/packages/starlight/__e2e__/fixtures/basics/astro.config.mjs
new file mode 100644
index 00000000..9e96cbad
--- /dev/null
+++ b/packages/starlight/__e2e__/fixtures/basics/astro.config.mjs
@@ -0,0 +1,10 @@
+import starlight from '@astrojs/starlight';
+import { defineConfig } from 'astro/config';
+
+export default defineConfig({
+ integrations: [
+ starlight({
+ title: 'Basics',
+ }),
+ ],
+});
diff --git a/packages/starlight/__e2e__/fixtures/basics/package.json b/packages/starlight/__e2e__/fixtures/basics/package.json
new file mode 100644
index 00000000..bbdb1613
--- /dev/null
+++ b/packages/starlight/__e2e__/fixtures/basics/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@e2e/basics",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "@astrojs/starlight": "workspace:*",
+ "astro": "^4.3.5"
+ }
+}
diff --git a/packages/starlight/__e2e__/fixtures/basics/src/content/config.ts b/packages/starlight/__e2e__/fixtures/basics/src/content/config.ts
new file mode 100644
index 00000000..45f60b01
--- /dev/null
+++ b/packages/starlight/__e2e__/fixtures/basics/src/content/config.ts
@@ -0,0 +1,6 @@
+import { defineCollection } from 'astro:content';
+import { docsSchema } from '@astrojs/starlight/schema';
+
+export const collections = {
+ docs: defineCollection({ schema: docsSchema() }),
+};
diff --git a/packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs-variable-height.mdx b/packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs-variable-height.mdx
new file mode 100644
index 00000000..7c7a0485
--- /dev/null
+++ b/packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs-variable-height.mdx
@@ -0,0 +1,151 @@
+---
+title: Tabs with varying content heights
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components';
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+**A set of tabs using a sync key with content of different heights between tabs.**
+
+<Tabs syncKey="things">
+<TabItem label="A">
+
+A little bit of text.
+
+</TabItem>
+<TabItem label="B">
+
+A lot more text.
+
+A lot more text.
+
+A lot more text.
+
+A lot more text.
+
+A lot more text.
+
+A lot more text.
+
+A lot more text.
+
+A lot more text.
+
+A lot more text.
+
+A lot more text.
+
+</TabItem>
+</Tabs>
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+**Another set of tabs using the same sync key with content of different heights between tabs.**
+
+<Tabs syncKey="things">
+<TabItem label="A">
+
+A little bit of text.
+
+</TabItem>
+<TabItem label="B">
+
+A lot more text.
+
+A lot more text.
+
+A lot more text.
+
+A lot more text.
+
+A lot more text.
+
+A lot more text.
+
+A lot more text.
+
+A lot more text.
+
+A lot more text.
+
+A lot more text.
+
+</TabItem>
+</Tabs>
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
diff --git a/packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs.mdx b/packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs.mdx
new file mode 100644
index 00000000..17e643a6
--- /dev/null
+++ b/packages/starlight/__e2e__/fixtures/basics/src/content/docs/tabs.mdx
@@ -0,0 +1,37 @@
+---
+title: Tabs
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components';
+
+A set of tabs using the `pkg` sync key.
+
+<Tabs syncKey="pkg">
+ <TabItem label="npm">npm command</TabItem>
+ <TabItem label="pnpm">pnpm command</TabItem>
+ <TabItem label="yarn">yarn command</TabItem>
+</Tabs>
+
+A basic set of tabs.
+
+<Tabs>
+ <TabItem label="one">tab 1</TabItem>
+ <TabItem label="two">tab 2</TabItem>
+ <TabItem label="three">tab 3</TabItem>
+</Tabs>
+
+Another set of tabs using the `pkg` sync key and an extra tab.
+
+<Tabs syncKey="pkg">
+ <TabItem label="npm">another npm command</TabItem>
+ <TabItem label="pnpm">another pnpm command</TabItem>
+ <TabItem label="bun">another bun command</TabItem>
+ <TabItem label="yarn">another yarn command</TabItem>
+</Tabs>
+
+A set of tabs using the `style` sync key.
+
+<Tabs syncKey="style">
+ <TabItem label="css">css code</TabItem>
+ <TabItem label="tailwind">tailwind code</TabItem>
+</Tabs>
diff --git a/packages/starlight/__e2e__/fixtures/basics/src/env.d.ts b/packages/starlight/__e2e__/fixtures/basics/src/env.d.ts
new file mode 100644
index 00000000..c13bd73c
--- /dev/null
+++ b/packages/starlight/__e2e__/fixtures/basics/src/env.d.ts
@@ -0,0 +1,2 @@
+/// <reference path="../.astro/types.d.ts" />
+/// <reference types="astro/client" /> \ No newline at end of file
diff --git a/packages/starlight/__e2e__/tabs.test.ts b/packages/starlight/__e2e__/tabs.test.ts
new file mode 100644
index 00000000..e0a36ed5
--- /dev/null
+++ b/packages/starlight/__e2e__/tabs.test.ts
@@ -0,0 +1,122 @@
+import { expect, testFactory, type Locator } from './test-utils';
+
+const test = await testFactory('./fixtures/basics/');
+
+test('syncs tabs with a click event', async ({ page, starlight }) => {
+ await starlight.goto('/tabs');
+
+ const tabs = page.locator('starlight-tabs');
+ const pkgTabsA = tabs.nth(0);
+ const pkgTabsB = tabs.nth(2);
+
+ // Select the pnpm tab in the first set of synced tabs.
+ await pkgTabsA.getByRole('tab').filter({ hasText: 'pnpm' }).click();
+
+ await expectSelectedTab(pkgTabsA, 'pnpm', 'pnpm command');
+ await expectSelectedTab(pkgTabsB, 'pnpm', 'another pnpm command');
+
+ // Select the yarn tab in the second set of synced tabs.
+ await pkgTabsB.getByRole('tab').filter({ hasText: 'yarn' }).click();
+
+ await expectSelectedTab(pkgTabsB, 'yarn', 'another yarn command');
+ await expectSelectedTab(pkgTabsA, 'yarn', 'yarn command');
+});
+
+test('syncs tabs with a keyboard event', async ({ page, starlight }) => {
+ await starlight.goto('/tabs');
+
+ const tabs = page.locator('starlight-tabs');
+ const pkgTabsA = tabs.nth(0);
+ const pkgTabsB = tabs.nth(2);
+
+ // Select the pnpm tab in the first set of synced tabs with the keyboard.
+ await pkgTabsA.getByRole('tab', { selected: true }).press('ArrowRight');
+
+ await expectSelectedTab(pkgTabsA, 'pnpm', 'pnpm command');
+ await expectSelectedTab(pkgTabsB, 'pnpm', 'another pnpm command');
+
+ // Select back the npm tab in the second set of synced tabs with the keyboard.
+ const selectedTabB = pkgTabsB.getByRole('tab', { selected: true });
+ await selectedTabB.press('ArrowRight');
+ await selectedTabB.press('ArrowLeft');
+ await selectedTabB.press('ArrowLeft');
+
+ await expectSelectedTab(pkgTabsA, 'npm', 'npm command');
+ await expectSelectedTab(pkgTabsB, 'npm', 'another npm command');
+});
+
+test('syncs only tabs using the same sync key', async ({ page, starlight }) => {
+ await starlight.goto('/tabs');
+
+ const tabs = page.locator('starlight-tabs');
+ const pkgTabsA = tabs.nth(0);
+ const unsyncedTabs = tabs.nth(1);
+ const styleTabs = tabs.nth(3);
+
+ // Select the pnpm tab in the set of tabs synced with the 'pkg' key.
+ await pkgTabsA.getByRole('tab').filter({ hasText: 'pnpm' }).click();
+
+ await expectSelectedTab(unsyncedTabs, 'one', 'tab 1');
+ await expectSelectedTab(styleTabs, 'css', 'css code');
+});
+
+test('supports synced tabs with different tab items', async ({ page, starlight }) => {
+ await starlight.goto('/tabs');
+
+ const tabs = page.locator('starlight-tabs');
+ const pkgTabsA = tabs.nth(0);
+ const pkgTabsB = tabs.nth(2); // This set contains an extra tab item.
+
+ // Select the bun tab in the second set of synced tabs.
+ await pkgTabsB.getByRole('tab').filter({ hasText: 'bun' }).click();
+
+ await expectSelectedTab(pkgTabsA, 'npm', 'npm command');
+ await expectSelectedTab(pkgTabsB, 'bun', 'another bun command');
+});
+
+test('persists the focus when syncing tabs', async ({ page, starlight }) => {
+ await starlight.goto('/tabs');
+
+ const pkgTabsA = page.locator('starlight-tabs').nth(0);
+
+ // Focus the selected tab in the set of tabs synced with the 'pkg' key.
+ await pkgTabsA.getByRole('tab', { selected: true }).focus();
+ // Select the pnpm tab in the set of tabs synced with the 'pkg' key using the keyboard.
+ await page.keyboard.press('ArrowRight');
+
+ expect(
+ await pkgTabsA
+ .getByRole('tab', { selected: true })
+ .evaluate((node) => document.activeElement === node)
+ ).toBe(true);
+});
+
+test('preserves tabs position when alternating between tabs with different content heights', async ({
+ page,
+ starlight,
+}) => {
+ await starlight.goto('/tabs-variable-height');
+
+ const tabs = page.locator('starlight-tabs').nth(1);
+ const selectedTab = tabs.getByRole('tab', { selected: true });
+
+ // Scroll to the second set of synced tabs and focus the selected tab.
+ await tabs.scrollIntoViewIfNeeded();
+ await selectedTab.focus();
+
+ // Get the bounding box of the tabs.
+ const initialBoundingBox = await tabs.boundingBox();
+
+ // Select the second tab which has a different height.
+ await selectedTab.press('ArrowRight');
+
+ // Ensure the tabs vertical position is exactly the same after selecting the second tab.
+ // Note that a small difference could be the result of the base line-height having a fractional part which can cause a
+ // sub-pixel difference in some browsers like Chrome or Firefox.
+ expect((await tabs.boundingBox())?.y).toBe(initialBoundingBox?.y);
+});
+
+async function expectSelectedTab(tabs: Locator, label: string, panel: string) {
+ expect((await tabs.getByRole('tab', { selected: true }).textContent())?.trim()).toBe(label);
+ expect((await tabs.getByRole('tabpanel').textContent())?.trim()).toBe(panel);
+}
diff --git a/packages/starlight/__e2e__/test-utils.ts b/packages/starlight/__e2e__/test-utils.ts
new file mode 100644
index 00000000..e6047323
--- /dev/null
+++ b/packages/starlight/__e2e__/test-utils.ts
@@ -0,0 +1,53 @@
+import { fileURLToPath } from 'node:url';
+import { test as baseTest, type Page } from '@playwright/test';
+import { build, preview } from 'astro';
+
+export { expect, type Locator } from '@playwright/test';
+
+// Setup a test environment that will build and start a preview server for a given fixture path and
+// provide a Starlight Playwright fixture accessible from within all tests.
+export async function testFactory(fixturePath: string) {
+ let previewServer: PreviewServer | undefined;
+
+ const test = baseTest.extend<{ starlight: StarlightPage }>({
+ starlight: async ({ page }, use) => {
+ if (!previewServer) {
+ throw new Error('Could not find a preview server to run tests against.');
+ }
+
+ await use(new StarlightPage(previewServer, page));
+ },
+ });
+
+ test.beforeAll(async () => {
+ const root = fileURLToPath(new URL(fixturePath, import.meta.url));
+ await build({ logLevel: 'error', root });
+ previewServer = await preview({ logLevel: 'error', root });
+ });
+
+ test.afterAll(async () => {
+ await previewServer?.stop();
+ });
+
+ return test;
+}
+
+// A Playwright test fixture accessible from within all tests.
+class StarlightPage {
+ constructor(
+ private readonly previewServer: PreviewServer,
+ private readonly page: Page
+ ) {}
+
+ // Navigate to a URL relative to the server used during a test run and return the resource response.
+ goto(url: string) {
+ return this.page.goto(this.resolveUrl(url));
+ }
+
+ // Resolve a URL relative to the server used during a test run.
+ resolveUrl(url: string) {
+ return `http://localhost:${this.previewServer.port}${url.replace(/^\/?/, '/')}`;
+ }
+}
+
+type PreviewServer = Awaited<ReturnType<typeof preview>>;
diff --git a/packages/starlight/package.json b/packages/starlight/package.json
index 4991eec2..7f9c4f92 100644
--- a/packages/starlight/package.json
+++ b/packages/starlight/package.json
@@ -4,7 +4,8 @@
"description": "Build beautiful, high-performance documentation websites with Astro",
"scripts": {
"test": "vitest",
- "test:coverage": "vitest run --coverage"
+ "test:coverage": "vitest run --coverage",
+ "test:e2e": "playwright install --with-deps chromium && playwright test"
},
"keywords": [
"docs",
@@ -183,6 +184,7 @@
},
"devDependencies": {
"@astrojs/markdown-remark": "^4.2.1",
+ "@playwright/test": "^1.43.1",
"@types/node": "^18.16.19",
"@vitest/coverage-v8": "^1.3.1",
"astro": "^4.3.5",
diff --git a/packages/starlight/playwright.config.ts b/packages/starlight/playwright.config.ts
new file mode 100644
index 00000000..01098ea8
--- /dev/null
+++ b/packages/starlight/playwright.config.ts
@@ -0,0 +1,15 @@
+import { defineConfig, devices } from '@playwright/test';
+
+export default defineConfig({
+ forbidOnly: !!process.env['CI'],
+ projects: [
+ {
+ name: 'Chrome Stable',
+ use: {
+ ...devices['Desktop Chrome'],
+ headless: true,
+ },
+ },
+ ],
+ testMatch: '__e2e__/*.test.ts',
+});
diff --git a/packages/starlight/style/props.css b/packages/starlight/style/props.css
index be9bbb9d..d8ef97ea 100644
--- a/packages/starlight/style/props.css
+++ b/packages/starlight/style/props.css
@@ -79,7 +79,7 @@
--sl-text-h4: var(--sl-text-xl);
--sl-text-h5: var(--sl-text-lg);
- --sl-line-height: 1.8;
+ --sl-line-height: 1.75;
--sl-line-height-headings: 1.2;
--sl-font-system: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
diff --git a/packages/starlight/user-components/Tabs.astro b/packages/starlight/user-components/Tabs.astro
index 2f58103d..376fb19c 100644
--- a/packages/starlight/user-components/Tabs.astro
+++ b/packages/starlight/user-components/Tabs.astro
@@ -2,11 +2,16 @@
import Icon from './Icon.astro';
import { processPanels } from './rehype-tabs';
+interface Props {
+ syncKey?: string;
+}
+
+const { syncKey } = Astro.props;
const panelHtml = await Astro.slots.render('default');
const { html, panels } = processPanels(panelHtml);
---
-<starlight-tabs>
+<starlight-tabs data-sync-key={syncKey}>
{
panels && (
<div class="tablist-wrapper not-content">
@@ -74,14 +79,25 @@ const { html, panels } = processPanels(panelHtml);
<script>
class StarlightTabs extends HTMLElement {
+ // A map of sync keys to all tabs that are synced to that key.
+ static #syncedTabs = new Map<string, StarlightTabs[]>();
+
tabs: HTMLAnchorElement[];
panels: HTMLElement[];
+ #syncKey: string | undefined;
constructor() {
super();
const tablist = this.querySelector<HTMLUListElement>('[role="tablist"]')!;
this.tabs = [...tablist.querySelectorAll<HTMLAnchorElement>('[role="tab"]')];
this.panels = [...this.querySelectorAll<HTMLElement>(':scope > [role="tabpanel"]')];
+ this.#syncKey = this.dataset.syncKey;
+
+ if (this.#syncKey) {
+ const syncedTabs = StarlightTabs.#syncedTabs.get(this.#syncKey) ?? [];
+ syncedTabs.push(this);
+ StarlightTabs.#syncedTabs.set(this.#syncKey, syncedTabs);
+ }
this.tabs.forEach((tab, i) => {
// Handle clicks for mouse users
@@ -117,9 +133,14 @@ const { html, panels } = processPanels(panelHtml);
});
}
- switchTab(newTab: HTMLAnchorElement | null | undefined, index: number) {
+ switchTab(newTab: HTMLAnchorElement | null | undefined, index: number, shouldSync = true) {
if (!newTab) return;
+ // If tabs should be synced, we store the current position so we can restore it after
+ // switching tabs to prevent the page from jumping when the new tab content is of a different
+ // height than the previous tab.
+ const previousTabsOffset = shouldSync ? this.getBoundingClientRect().top : 0;
+
// Mark all tabs as unselected and hide all tab panels.
this.tabs.forEach((tab) => {
tab.setAttribute('aria-selected', 'false');
@@ -135,7 +156,27 @@ const { html, panels } = processPanels(panelHtml);
// Restore active tab to the default tab order.
newTab.removeAttribute('tabindex');
newTab.setAttribute('aria-selected', 'true');
- newTab.focus();
+ if (shouldSync) {
+ newTab.focus();
+ StarlightTabs.#syncTabs(this, newTab.textContent);
+ window.scrollTo({
+ top: window.scrollY + (this.getBoundingClientRect().top - previousTabsOffset),
+ });
+ }
+ }
+
+ static #syncTabs(emitter: StarlightTabs, label: string | null) {
+ const syncKey = emitter.#syncKey;
+ if (!syncKey || !label) return;
+ const syncedTabs = StarlightTabs.#syncedTabs.get(syncKey);
+ if (!syncedTabs) return;
+
+ for (const receiver of syncedTabs) {
+ if (receiver === emitter) continue;
+ const labelIndex = receiver.tabs.findIndex((tab) => tab.textContent === label);
+ if (labelIndex === -1) continue;
+ receiver.switchTab(receiver.tabs[labelIndex], labelIndex, false);
+ }
}
}
diff --git a/packages/starlight/vitest.config.ts b/packages/starlight/vitest.config.ts
index 89e62f69..5440eaa3 100644
--- a/packages/starlight/vitest.config.ts
+++ b/packages/starlight/vitest.config.ts
@@ -11,7 +11,8 @@ const defaultCoverageExcludes = [
'test?(-*).?(c|m)[jt]s?(x)',
'**/*{.,-}{test,spec}.?(c|m)[jt]s?(x)',
'**/__tests__/**',
- '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
+ '**/__e2e__/**',
+ '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,playwright}.config.*',
'**/.{eslint,mocha,prettier}rc.{?(c|m)js,yml}',
];
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3536c01a..9c1dca04 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -185,6 +185,9 @@ importers:
'@astrojs/markdown-remark':
specifier: ^4.2.1
version: 4.2.1
+ '@playwright/test':
+ specifier: ^1.43.1
+ version: 1.43.1
'@types/node':
specifier: ^18.16.19
version: 18.16.19
@@ -198,6 +201,15 @@ importers:
specifier: ^1.3.1
version: 1.3.1(@types/node@18.16.19)
+ packages/starlight/__e2e__/fixtures/basics:
+ dependencies:
+ '@astrojs/starlight':
+ specifier: workspace:*
+ version: link:../../..
+ astro:
+ specifier: ^4.3.5
+ version: 4.3.5(@types/node@18.16.19)
+
packages/tailwind:
dependencies:
'@astrojs/starlight':
@@ -952,7 +964,6 @@ packages:
cpu: [ppc64]
os: [aix]
requiresBuild: true
- dev: false
optional: true
/@esbuild/android-arm64@0.19.12:
@@ -961,15 +972,6 @@ packages:
cpu: [arm64]
os: [android]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/android-arm64@0.19.8:
- resolution: {integrity: sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [android]
- requiresBuild: true
optional: true
/@esbuild/android-arm@0.19.12:
@@ -978,15 +980,6 @@ packages:
cpu: [arm]
os: [android]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/android-arm@0.19.8:
- resolution: {integrity: sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==}
- engines: {node: '>=12'}
- cpu: [arm]
- os: [android]
- requiresBuild: true
optional: true
/@esbuild/android-x64@0.19.12:
@@ -995,15 +988,6 @@ packages:
cpu: [x64]
os: [android]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/android-x64@0.19.8:
- resolution: {integrity: sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [android]
- requiresBuild: true
optional: true
/@esbuild/darwin-arm64@0.19.12:
@@ -1012,15 +996,6 @@ packages:
cpu: [arm64]
os: [darwin]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/darwin-arm64@0.19.8:
- resolution: {integrity: sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
optional: true
/@esbuild/darwin-x64@0.19.12:
@@ -1029,15 +1004,6 @@ packages:
cpu: [x64]
os: [darwin]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/darwin-x64@0.19.8:
- resolution: {integrity: sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
optional: true
/@esbuild/freebsd-arm64@0.19.12:
@@ -1046,15 +1012,6 @@ packages:
cpu: [arm64]
os: [freebsd]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/freebsd-arm64@0.19.8:
- resolution: {integrity: sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [freebsd]
- requiresBuild: true
optional: true
/@esbuild/freebsd-x64@0.19.12:
@@ -1063,15 +1020,6 @@ packages:
cpu: [x64]
os: [freebsd]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/freebsd-x64@0.19.8:
- resolution: {integrity: sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [freebsd]
- requiresBuild: true
optional: true
/@esbuild/linux-arm64@0.19.12:
@@ -1080,15 +1028,6 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/linux-arm64@0.19.8:
- resolution: {integrity: sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
optional: true
/@esbuild/linux-arm@0.19.12:
@@ -1097,15 +1036,6 @@ packages:
cpu: [arm]
os: [linux]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/linux-arm@0.19.8:
- resolution: {integrity: sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==}
- engines: {node: '>=12'}
- cpu: [arm]
- os: [linux]
- requiresBuild: true
optional: true
/@esbuild/linux-ia32@0.19.12:
@@ -1114,15 +1044,6 @@ packages:
cpu: [ia32]
os: [linux]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/linux-ia32@0.19.8:
- resolution: {integrity: sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [linux]
- requiresBuild: true
optional: true
/@esbuild/linux-loong64@0.19.12:
@@ -1131,15 +1052,6 @@ packages:
cpu: [loong64]
os: [linux]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/linux-loong64@0.19.8:
- resolution: {integrity: sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==}
- engines: {node: '>=12'}
- cpu: [loong64]
- os: [linux]
- requiresBuild: true
optional: true
/@esbuild/linux-mips64el@0.19.12:
@@ -1148,15 +1060,6 @@ packages:
cpu: [mips64el]
os: [linux]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/linux-mips64el@0.19.8:
- resolution: {integrity: sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==}
- engines: {node: '>=12'}
- cpu: [mips64el]
- os: [linux]
- requiresBuild: true
optional: true
/@esbuild/linux-ppc64@0.19.12:
@@ -1165,15 +1068,6 @@ packages:
cpu: [ppc64]
os: [linux]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/linux-ppc64@0.19.8:
- resolution: {integrity: sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==}
- engines: {node: '>=12'}
- cpu: [ppc64]
- os: [linux]
- requiresBuild: true
optional: true
/@esbuild/linux-riscv64@0.19.12:
@@ -1182,15 +1076,6 @@ packages:
cpu: [riscv64]
os: [linux]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/linux-riscv64@0.19.8:
- resolution: {integrity: sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==}
- engines: {node: '>=12'}
- cpu: [riscv64]
- os: [linux]
- requiresBuild: true
optional: true
/@esbuild/linux-s390x@0.19.12:
@@ -1199,15 +1084,6 @@ packages:
cpu: [s390x]
os: [linux]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/linux-s390x@0.19.8:
- resolution: {integrity: sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==}
- engines: {node: '>=12'}
- cpu: [s390x]
- os: [linux]
- requiresBuild: true
optional: true
/@esbuild/linux-x64@0.19.12:
@@ -1216,15 +1092,6 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/linux-x64@0.19.8:
- resolution: {integrity: sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
optional: true
/@esbuild/netbsd-x64@0.19.12:
@@ -1233,15 +1100,6 @@ packages:
cpu: [x64]
os: [netbsd]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/netbsd-x64@0.19.8:
- resolution: {integrity: sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [netbsd]
- requiresBuild: true
optional: true
/@esbuild/openbsd-x64@0.19.12:
@@ -1250,15 +1108,6 @@ packages:
cpu: [x64]
os: [openbsd]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/openbsd-x64@0.19.8:
- resolution: {integrity: sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [openbsd]
- requiresBuild: true
optional: true
/@esbuild/sunos-x64@0.19.12:
@@ -1267,15 +1116,6 @@ packages:
cpu: [x64]
os: [sunos]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/sunos-x64@0.19.8:
- resolution: {integrity: sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [sunos]
- requiresBuild: true
optional: true
/@esbuild/win32-arm64@0.19.12:
@@ -1284,15 +1124,6 @@ packages:
cpu: [arm64]
os: [win32]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/win32-arm64@0.19.8:
- resolution: {integrity: sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [win32]
- requiresBuild: true
optional: true
/@esbuild/win32-ia32@0.19.12:
@@ -1301,15 +1132,6 @@ packages:
cpu: [ia32]
os: [win32]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/win32-ia32@0.19.8:
- resolution: {integrity: sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [win32]
- requiresBuild: true
optional: true
/@esbuild/win32-x64@0.19.12:
@@ -1318,15 +1140,6 @@ packages:
cpu: [x64]
os: [win32]
requiresBuild: true
- dev: false
- optional: true
-
- /@esbuild/win32-x64@0.19.8:
- resolution: {integrity: sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
optional: true
/@expressive-code/core@0.35.2:
@@ -1552,6 +1365,20 @@ packages:
dev: false
optional: true
+ /@parse5/tools@0.3.0:
+ resolution: {integrity: sha512-zxRyTHkqb7WQMV8kTNBKWb1BeOFUKXBXTBWuxg9H9hfvQB3IwP6Iw2U75Ia5eyRxPNltmY7E8YAlz6zWwUnjKg==}
+ dependencies:
+ parse5: 7.1.2
+ dev: false
+
+ /@playwright/test@1.43.1:
+ resolution: {integrity: sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==}
+ engines: {node: '>=16'}
+ hasBin: true
+ dependencies:
+ playwright: 1.43.1
+ dev: true
+
/@rollup/rollup-android-arm-eabi@4.6.1:
resolution: {integrity: sha512-0WQ0ouLejaUCRsL93GD4uft3rOmB8qoQMU05Kb8CmMtMBe7XUDLAltxVZI1q6byNqEtU7N1ZX1Vw5lIpgulLQA==}
cpu: [arm]
@@ -2072,7 +1899,7 @@ packages:
dlv: 1.1.3
dset: 3.1.3
es-module-lexer: 1.4.1
- esbuild: 0.19.8
+ esbuild: 0.19.12
estree-walker: 3.0.3
execa: 8.0.1
fast-glob: 3.3.2
@@ -2963,36 +2790,6 @@ packages:
'@esbuild/win32-arm64': 0.19.12
'@esbuild/win32-ia32': 0.19.12
'@esbuild/win32-x64': 0.19.12
- dev: false
-
- /esbuild@0.19.8:
- resolution: {integrity: sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==}
- engines: {node: '>=12'}
- hasBin: true
- requiresBuild: true
- optionalDependencies:
- '@esbuild/android-arm': 0.19.8
- '@esbuild/android-arm64': 0.19.8
- '@esbuild/android-x64': 0.19.8
- '@esbuild/darwin-arm64': 0.19.8
- '@esbuild/darwin-x64': 0.19.8
- '@esbuild/freebsd-arm64': 0.19.8
- '@esbuild/freebsd-x64': 0.19.8
- '@esbuild/linux-arm': 0.19.8
- '@esbuild/linux-arm64': 0.19.8
- '@esbuild/linux-ia32': 0.19.8
- '@esbuild/linux-loong64': 0.19.8
- '@esbuild/linux-mips64el': 0.19.8
- '@esbuild/linux-ppc64': 0.19.8
- '@esbuild/linux-riscv64': 0.19.8
- '@esbuild/linux-s390x': 0.19.8
- '@esbuild/linux-x64': 0.19.8
- '@esbuild/netbsd-x64': 0.19.8
- '@esbuild/openbsd-x64': 0.19.8
- '@esbuild/sunos-x64': 0.19.8
- '@esbuild/win32-arm64': 0.19.8
- '@esbuild/win32-ia32': 0.19.8
- '@esbuild/win32-x64': 0.19.8
/escalade@3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
@@ -3261,6 +3058,14 @@ packages:
/fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+ /fsevents@2.3.2:
+ resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -5381,6 +5186,22 @@ packages:
pathe: 1.1.2
dev: true
+ /playwright-core@1.43.1:
+ resolution: {integrity: sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==}
+ engines: {node: '>=16'}
+ hasBin: true
+ dev: true
+
+ /playwright@1.43.1:
+ resolution: {integrity: sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==}
+ engines: {node: '>=16'}
+ hasBin: true
+ dependencies:
+ playwright-core: 1.43.1
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
/postcss-import@15.1.0(postcss@8.4.33):
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
engines: {node: '>=14.0.0'}
@@ -6903,7 +6724,7 @@ packages:
optional: true
dependencies:
'@types/node': 18.16.19
- esbuild: 0.19.8
+ esbuild: 0.19.12
postcss: 8.4.33
rollup: 4.6.1
optionalDependencies: