0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-01-27 22:19:04 -05:00

feat: support setting timeout for client:idle (#11743)

* feat: support setting timeout for `client:idle`

* tst: add client:idle timeout e2e test

* Update .changeset/clever-emus-roll.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Update .changeset/clever-emus-roll.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* nit: we wait for times, not values!

---------

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
Phil 2024-08-28 13:09:19 +02:00 committed by GitHub
parent 5af8b4f1cb
commit cce0894534
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 128 additions and 4 deletions

View file

@ -0,0 +1,11 @@
---
'astro': minor
---
Adds a new, optional property `timeout` for the `client:idle` directive.
This value allows you to specify a maximum time to wait, in milliseconds, before hydrating a UI framework component, even if the page is not yet done with its initial load. This means you can delay hydration for lower-priority UI elements with more control to ensure your element is interactive within a specified time frame.
```astro
<ShowHideButton client:idle={{timeout: 500}} />
```

View file

@ -0,0 +1,33 @@
import { expect } from '@playwright/test';
import { testFactory, waitForHydrate } from './test-utils.js';
const test = testFactory({ root: './fixtures/client-idle-timeout/' });
let devServer;
test.beforeAll(async ({ astro }) => {
devServer = await astro.startDevServer();
});
test.afterAll(async () => {
await devServer.stop();
});
test.describe('Client idle timeout', () => {
test('React counter', async ({ astro, page }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#react-counter');
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
await waitForHydrate(page, counter);
const inc = counter.locator('.increment');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('1');
});
});

View file

@ -0,0 +1,9 @@
import react from '@astrojs/react';
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
integrations: [
react(),
],
});

View file

@ -0,0 +1,13 @@
{
"name": "@e2e/client-idle-timeout",
"version": "0.0.0",
"private": true,
"devDependencies": {
"@astrojs/react": "workspace:*",
"astro": "workspace:*"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
}
}

View file

@ -0,0 +1,18 @@
import React, { useState } from 'react';
export default function Counter({ children, count: initialCount = 0, id }) {
const [count, setCount] = useState(initialCount);
const add = () => setCount((i) => i + 1);
const subtract = () => setCount((i) => i - 1);
return (
<>
<div id={id} className="counter">
<button className="decrement" onClick={subtract}>-</button>
<pre>{count}</pre>
<button className="increment" onClick={add}>+</button>
</div>
<div className="counter-message">{children}</div>
</>
);
}

View file

@ -0,0 +1,16 @@
---
import Counter from '../components/Counter.jsx';
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
</head>
<body>
<main>
<Counter id="react-counter" client:idle={{timeout: 200}}></Counter>
</main>
</body>
</html>

View file

@ -91,7 +91,7 @@ export type {
export interface AstroBuiltinProps {
'client:load'?: boolean;
'client:idle'?: boolean;
'client:idle'?: IdleRequestOptions | boolean;
'client:media'?: string;
'client:visible'?: ClientVisibleOptions | boolean;
'client:only'?: boolean | string;

View file

@ -1,14 +1,22 @@
import type { ClientDirective } from '../../@types/astro.js';
const idleDirective: ClientDirective = (load) => {
const idleDirective: ClientDirective = (load, options) => {
const cb = async () => {
const hydrate = await load();
await hydrate();
};
const rawOptions =
typeof options.value === 'object' ? (options.value as IdleRequestOptions) : undefined;
const idleOptions: IdleRequestOptions = {
timeout: rawOptions?.timeout,
};
if ('requestIdleCallback' in window) {
(window as any).requestIdleCallback(cb);
(window as any).requestIdleCallback(cb, idleOptions);
} else {
setTimeout(cb, 200);
setTimeout(cb, idleOptions.timeout || 200);
}
};

16
pnpm-lock.yaml generated
View file

@ -1004,6 +1004,22 @@ importers:
specifier: ^3.4.38
version: 3.4.38(typescript@5.5.4)
packages/astro/e2e/fixtures/client-idle-timeout:
dependencies:
react:
specifier: ^18.3.1
version: 18.3.1
react-dom:
specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
devDependencies:
'@astrojs/react':
specifier: workspace:*
version: link:../../../../integrations/react
astro:
specifier: workspace:*
version: link:../../..
packages/astro/e2e/fixtures/client-only:
dependencies:
preact: