toiljs 0.0.11 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +2 -0
  2. package/build/cli/.tsbuildinfo +1 -1
  3. package/build/cli/configure.js +10 -4
  4. package/build/cli/create.js +58 -30
  5. package/build/cli/diagnostics.d.ts +55 -0
  6. package/build/cli/diagnostics.js +333 -0
  7. package/build/cli/doctor.d.ts +6 -0
  8. package/build/cli/doctor.js +249 -0
  9. package/build/cli/index.js +26 -0
  10. package/build/cli/proc.d.ts +5 -0
  11. package/build/cli/proc.js +20 -0
  12. package/build/cli/ui.d.ts +1 -0
  13. package/build/cli/ui.js +1 -0
  14. package/build/cli/update.d.ts +7 -0
  15. package/build/cli/update.js +117 -0
  16. package/build/cli/updates.d.ts +10 -0
  17. package/build/cli/updates.js +45 -0
  18. package/build/client/.tsbuildinfo +1 -1
  19. package/build/client/dev/error-overlay.js +1 -1
  20. package/build/client/head/metadata.js +3 -1
  21. package/build/client/index.d.ts +5 -1
  22. package/build/client/index.js +2 -0
  23. package/build/client/navigation/navigation.js +1 -1
  24. package/build/client/routing/Router.js +2 -2
  25. package/build/client/search/search.d.ts +26 -0
  26. package/build/client/search/search.js +101 -0
  27. package/build/client/search/use-page-search.d.ts +8 -0
  28. package/build/client/search/use-page-search.js +21 -0
  29. package/build/compiler/.tsbuildinfo +1 -1
  30. package/build/compiler/generate.js +26 -23
  31. package/build/compiler/index.d.ts +2 -0
  32. package/build/compiler/index.js +1 -0
  33. package/build/compiler/pages.d.ts +8 -0
  34. package/build/compiler/pages.js +37 -0
  35. package/build/compiler/plugin.js +3 -1
  36. package/build/compiler/prerender.d.ts +1 -0
  37. package/build/compiler/prerender.js +11 -5
  38. package/build/compiler/seo.js +10 -3
  39. package/build/io/.tsbuildinfo +1 -1
  40. package/examples/basic/client/components/Header.tsx +43 -41
  41. package/examples/basic/client/components/HoneycombBackground.tsx +223 -230
  42. package/examples/basic/client/public/index.html +18 -16
  43. package/examples/basic/client/routes/(legal)/privacy.tsx +18 -19
  44. package/examples/basic/client/routes/(legal)/terms.tsx +15 -16
  45. package/examples/basic/client/routes/about.tsx +21 -22
  46. package/examples/basic/client/routes/blog/[id].tsx +26 -18
  47. package/examples/basic/client/routes/features/actions.tsx +67 -67
  48. package/examples/basic/client/routes/features/error/index.tsx +27 -27
  49. package/examples/basic/client/routes/features/head.tsx +38 -38
  50. package/examples/basic/client/routes/features/index.tsx +83 -75
  51. package/examples/basic/client/routes/features/realtime.tsx +34 -32
  52. package/examples/basic/client/routes/features/script.tsx +31 -31
  53. package/examples/basic/client/routes/features/seo.tsx +39 -39
  54. package/examples/basic/client/routes/features/template/index.tsx +20 -20
  55. package/examples/basic/client/routes/features/template/template.tsx +16 -18
  56. package/examples/basic/client/routes/gallery/@modal/(.)photo/[id].tsx +23 -23
  57. package/examples/basic/client/routes/gallery/index.tsx +42 -42
  58. package/examples/basic/client/routes/gallery/photo/[id].tsx +18 -18
  59. package/examples/basic/client/routes/get-started.tsx +157 -84
  60. package/examples/basic/client/routes/index.tsx +137 -96
  61. package/examples/basic/client/routes/loader-demo/index.tsx +59 -52
  62. package/examples/basic/client/routes/search.tsx +61 -0
  63. package/examples/basic/client/routes/test.tsx +7 -8
  64. package/examples/basic/client/styles/main.css +624 -552
  65. package/package.json +2 -2
  66. package/presets/eslint.js +10 -3
  67. package/src/cli/configure.ts +363 -353
  68. package/src/cli/create.ts +563 -530
  69. package/src/cli/diagnostics.ts +421 -0
  70. package/src/cli/doctor.ts +318 -0
  71. package/src/cli/features.ts +166 -160
  72. package/src/cli/index.ts +242 -211
  73. package/src/cli/proc.ts +30 -0
  74. package/src/cli/ui.ts +111 -103
  75. package/src/cli/update.ts +150 -0
  76. package/src/cli/updates.ts +69 -0
  77. package/src/client/components/Image.tsx +91 -89
  78. package/src/client/dev/error-overlay.tsx +193 -197
  79. package/src/client/head/metadata.ts +94 -92
  80. package/src/client/index.ts +79 -64
  81. package/src/client/navigation/Link.tsx +94 -100
  82. package/src/client/navigation/navigation.ts +215 -218
  83. package/src/client/routing/Router.tsx +210 -193
  84. package/src/client/routing/hooks.ts +110 -114
  85. package/src/client/routing/lazy.ts +77 -81
  86. package/src/client/search/search.ts +189 -0
  87. package/src/client/search/use-page-search.ts +73 -0
  88. package/src/compiler/config.ts +173 -171
  89. package/src/compiler/fonts.ts +89 -87
  90. package/src/compiler/generate.ts +378 -373
  91. package/src/compiler/image-report.ts +88 -85
  92. package/src/compiler/index.ts +2 -0
  93. package/src/compiler/pages.ts +70 -0
  94. package/src/compiler/plugin.ts +51 -47
  95. package/src/compiler/prerender.ts +152 -130
  96. package/src/compiler/routes.ts +132 -131
  97. package/src/compiler/seo.ts +381 -356
  98. package/src/compiler/vite.ts +155 -145
  99. package/src/io/FastSet.ts +99 -96
  100. package/test/configure.test.ts +94 -90
  101. package/test/doctor.test.ts +140 -0
  102. package/test/dom/Image.test.tsx +73 -46
  103. package/test/dom/Script.test.tsx +48 -45
  104. package/test/dom/action.test.tsx +146 -129
  105. package/test/dom/error-overlay.test.tsx +44 -44
  106. package/test/dom/loader.test.tsx +2 -2
  107. package/test/dom/revalidate.test.tsx +1 -1
  108. package/test/dom/route-head.test.tsx +1 -2
  109. package/test/dom/slot.test.tsx +131 -109
  110. package/test/dom/view-transitions.test.tsx +53 -51
  111. package/test/features.test.ts +149 -142
  112. package/test/fonts.test.ts +28 -26
  113. package/test/head.test.ts +45 -35
  114. package/test/metadata.test.ts +42 -41
  115. package/test/pages.test.ts +105 -0
  116. package/test/prerender.test.ts +54 -46
  117. package/test/search.test.ts +114 -0
  118. package/test/seo.test.ts +164 -142
  119. package/test/update.test.ts +44 -0
@@ -1,67 +1,67 @@
1
- // Mutations: a `loader` reads, an action writes, then revalidation refetches the loader so the UI
2
- // reflects the new state with no manual refetch. Here a module-level counter stands in for a server.
3
- let serverCount = 0;
4
- async function wait(ms: number): Promise<void> {
5
- return new Promise((resolve) => setTimeout(resolve, ms));
6
- }
7
-
8
- export const metadata: Toil.Metadata = {
9
- title: 'Actions and forms',
10
- description: 'useAction and <Form> mutations with pending state and revalidation.',
11
- };
12
-
13
- export const loader = async () => {
14
- await wait(150);
15
- return { count: serverCount };
16
- };
17
-
18
- export default function ActionsDemo() {
19
- const { count } = Toil.useLoaderData(loader);
20
-
21
- // useAction: run a mutation, track pending/error, then revalidate this route's loader on success.
22
- const increment = Toil.useAction(
23
- async (by: number) => {
24
- await wait(400);
25
- serverCount += by;
26
- return serverCount;
27
- },
28
- { revalidate: true },
29
- );
30
-
31
- return (
32
- <main>
33
- <h1>Actions and forms</h1>
34
- <p>
35
- Server count (from the loader): <strong>{count}</strong>
36
- </p>
37
-
38
- <p>
39
- <button type="button" disabled={increment.pending} onClick={() => void increment.run(1)}>
40
- {increment.pending ? 'Saving' : 'Increment via useAction'}
41
- </button>
42
- {increment.error ? <span style={{ color: 'crimson' }}> failed</span> : null}
43
- </p>
44
-
45
- {/* The declarative form: submits to an action, revalidates on success, exposes pending. */}
46
- <Toil.Form
47
- action={async (form) => {
48
- await wait(400);
49
- serverCount += Number(form.get('by') || 0);
50
- }}
51
- revalidate>
52
- {({ pending }) => (
53
- <>
54
- <input name="by" type="number" defaultValue={5} disabled={pending} />
55
- <button type="submit" disabled={pending}>
56
- {pending ? 'Saving' : 'Add via <Form>'}
57
- </button>
58
- </>
59
- )}
60
- </Toil.Form>
61
-
62
- <p>
63
- <Toil.Link href="/features">Back to features</Toil.Link>
64
- </p>
65
- </main>
66
- );
67
- }
1
+ // Mutations: a `loader` reads, an action writes, then revalidation refetches the loader so the UI
2
+ // reflects the new state with no manual refetch. Here a module-level counter stands in for a server.
3
+ let serverCount = 0;
4
+ async function wait(ms: number): Promise<void> {
5
+ return new Promise((resolve) => setTimeout(resolve, ms));
6
+ }
7
+
8
+ export const metadata: Toil.Metadata = {
9
+ title: 'Actions and forms',
10
+ description: 'useAction and <Form> mutations with pending state and revalidation.'
11
+ };
12
+
13
+ export const loader = async () => {
14
+ await wait(150);
15
+ return { count: serverCount };
16
+ };
17
+
18
+ export default function ActionsDemo() {
19
+ const { count } = Toil.useLoaderData(loader);
20
+
21
+ // useAction: run a mutation, track pending/error, then revalidate this route's loader on success.
22
+ const increment = Toil.useAction(
23
+ async (by: number) => {
24
+ await wait(400);
25
+ serverCount += by;
26
+ return serverCount;
27
+ },
28
+ { revalidate: true }
29
+ );
30
+
31
+ return (
32
+ <main>
33
+ <h1>Actions and forms</h1>
34
+ <p>
35
+ Server count (from the loader): <strong>{count}</strong>
36
+ </p>
37
+
38
+ <p>
39
+ <button type="button" disabled={increment.pending} onClick={() => void increment.run(1)}>
40
+ {increment.pending ? 'Saving' : 'Increment via useAction'}
41
+ </button>
42
+ {increment.error ? <span style={{ color: 'crimson' }}> failed</span> : null}
43
+ </p>
44
+
45
+ {/* The declarative form: submits to an action, revalidates on success, exposes pending. */}
46
+ <Toil.Form
47
+ action={async (form) => {
48
+ await wait(400);
49
+ serverCount += Number(form.get('by') || 0);
50
+ }}
51
+ revalidate>
52
+ {({ pending }) => (
53
+ <>
54
+ <input name="by" type="number" defaultValue={5} disabled={pending} />
55
+ <button type="submit" disabled={pending}>
56
+ {pending ? 'Saving' : 'Add via <Form>'}
57
+ </button>
58
+ </>
59
+ )}
60
+ </Toil.Form>
61
+
62
+ <p>
63
+ <Toil.Link href="/features">Back to features</Toil.Link>
64
+ </p>
65
+ </main>
66
+ );
67
+ }
@@ -1,27 +1,27 @@
1
- import { useState } from 'react';
2
-
3
- export const metadata: Toil.Metadata = { title: 'Error boundary' };
4
-
5
- // When a route throws during render, the nearest `error.tsx` catches it and renders instead of a
6
- // blank screen. Click the button to flip into a throwing render and watch error.tsx take over.
7
- export default function ErrorDemo() {
8
- const [boom, setBoom] = useState(false);
9
- if (boom) throw new Error('Kaboom from features/error, caught by error.tsx');
10
- return (
11
- <main>
12
- <h1>Error boundary</h1>
13
- <p>
14
- A thrown render is caught by <code>error.tsx</code> in this folder, scoped to this
15
- segment so the rest of the app keeps working.
16
- </p>
17
- <p>
18
- <button type="button" onClick={() => setBoom(true)}>
19
- Throw an error
20
- </button>
21
- </p>
22
- <p>
23
- <Toil.Link href="/features">Back to features</Toil.Link>
24
- </p>
25
- </main>
26
- );
27
- }
1
+ import { useState } from 'react';
2
+
3
+ export const metadata: Toil.Metadata = { title: 'Error boundary' };
4
+
5
+ // When a route throws during render, the nearest `error.tsx` catches it and renders instead of a
6
+ // blank screen. Click the button to flip into a throwing render and watch error.tsx take over.
7
+ export default function ErrorDemo() {
8
+ const [boom, setBoom] = useState(false);
9
+ if (boom) throw new Error('Kaboom from features/error, caught by error.tsx');
10
+ return (
11
+ <main>
12
+ <h1>Error boundary</h1>
13
+ <p>
14
+ A thrown render is caught by <code>error.tsx</code> in this folder, scoped to this segment so the rest
15
+ of the app keeps working.
16
+ </p>
17
+ <p>
18
+ <button type="button" onClick={() => setBoom(true)}>
19
+ Throw an error
20
+ </button>
21
+ </p>
22
+ <p>
23
+ <Toil.Link href="/features">Back to features</Toil.Link>
24
+ </p>
25
+ </main>
26
+ );
27
+ }
@@ -1,38 +1,38 @@
1
- import { useState } from 'react';
2
-
3
- // The imperative head API, for when the title or tags depend on component state rather than a static
4
- // export. `useTitle` / `useHead` apply for the component's lifetime and revert on unmount; `<Toil.Head>`
5
- // is the declarative form. They compose with (and can override) the route `metadata`.
6
- export default function HeadDemo() {
7
- const [count, setCount] = useState(0);
8
-
9
- // Live title: the tab updates every render as `count` changes.
10
- Toil.useTitle(`Clicked ${count} times`);
11
-
12
- // Add a meta tag and a canonical link for this page only.
13
- Toil.useHead({
14
- meta: [{ name: 'description', content: `Imperative head demo, clicked ${count} times.` }],
15
- link: [{ rel: 'canonical', href: 'https://toil.example/features/head' }],
16
- });
17
-
18
- return (
19
- <main>
20
- <h1>Imperative head</h1>
21
- <p>
22
- The tab title is driven by component state via <code>Toil.useTitle</code>. Click the
23
- button and watch it change, then leave the page and the title reverts.
24
- </p>
25
- <p>
26
- <button type="button" onClick={() => setCount((c) => c + 1)}>
27
- Clicked {count} times
28
- </button>
29
- </p>
30
- {/* Declarative equivalent, the same merge rules apply. */}
31
- <Toil.Head meta={[{ property: 'og:title', content: `Clicked ${count} times` }]} />
32
- <p>
33
- <Toil.Link href="/features/seo">Compare with route metadata</Toil.Link>{' '}
34
- <Toil.Link href="/features">Back to features</Toil.Link>
35
- </p>
36
- </main>
37
- );
38
- }
1
+ import { useState } from 'react';
2
+
3
+ // The imperative head API, for when the title or tags depend on component state rather than a static
4
+ // export. `useTitle` / `useHead` apply for the component's lifetime and revert on unmount; `<Toil.Head>`
5
+ // is the declarative form. They compose with (and can override) the route `metadata`.
6
+ export default function HeadDemo() {
7
+ const [count, setCount] = useState(0);
8
+
9
+ // Live title: the tab updates every render as `count` changes.
10
+ Toil.useTitle(`Clicked ${count} times`);
11
+
12
+ // Add a meta tag and a canonical link for this page only.
13
+ Toil.useHead({
14
+ meta: [{ name: 'description', content: `Imperative head demo, clicked ${count} times.` }],
15
+ link: [{ rel: 'canonical', href: 'https://toil.example/features/head' }]
16
+ });
17
+
18
+ return (
19
+ <main>
20
+ <h1>Imperative head</h1>
21
+ <p>
22
+ The tab title is driven by component state via <code>Toil.useTitle</code>. Click the button and watch it
23
+ change, then leave the page and the title reverts.
24
+ </p>
25
+ <p>
26
+ <button type="button" onClick={() => setCount((c) => c + 1)}>
27
+ Clicked {count} times
28
+ </button>
29
+ </p>
30
+ {/* Declarative equivalent, the same merge rules apply. */}
31
+ <Toil.Head meta={[{ property: 'og:title', content: `Clicked ${count} times` }]} />
32
+ <p>
33
+ <Toil.Link href="/features/seo">Compare with route metadata</Toil.Link>{' '}
34
+ <Toil.Link href="/features">Back to features</Toil.Link>
35
+ </p>
36
+ </main>
37
+ );
38
+ }
@@ -1,75 +1,83 @@
1
- // The feature hub: one place that links to a live demo of every ToilJS capability. Its own tab
2
- // title comes from the `metadata` export below (rendered as "Features | ToilJS" via the layout
3
- // template) and is baked into build/client/features/index.html at build time.
4
- export const metadata: Toil.Metadata = {
5
- title: 'Features',
6
- description: 'Live demos of every ToilJS feature: routing, data, head/SEO, components, realtime, and binary IO.',
7
- openGraph: { title: 'Every ToilJS feature, demoed', type: 'website' },
8
- };
9
-
10
- const groups: { heading: string; items: { href: Toil.Href; label: string; note: string }[] }[] = [
11
- {
12
- heading: 'Routing',
13
- items: [
14
- { href: '/blog/42', label: 'Dynamic route', note: 'blog/[id].tsx, /blog/42' },
15
- { href: '/docs/getting/started', label: 'Catch-all', note: 'docs/[...slug].tsx' },
16
- { href: '/files', label: 'Optional catch-all', note: 'files/[[...slug]].tsx, matches /files and /files/a/b' },
17
- { href: '/privacy', label: 'Route group', note: '(legal)/privacy.tsx, no URL segment' },
18
- { href: '/gallery', label: 'Parallel + intercepting', note: '@modal/(.)photo/[id], a real modal route' },
19
- { href: '/features/template', label: 'Templates', note: 'template.tsx remounts on every navigation' },
20
- { href: '/features/error', label: 'Error boundary', note: 'error.tsx catches a thrown route' },
21
- ],
22
- },
23
- {
24
- heading: 'Data',
25
- items: [
26
- { href: '/loader-demo', label: 'Loader + revalidate', note: 'data before render, cached, refetchable' },
27
- { href: '/features/actions', label: 'Actions + Form', note: 'useAction / <Form>, pending state, revalidate' },
28
- ],
29
- },
30
- {
31
- heading: 'Head and SEO',
32
- items: [
33
- { href: '/features/seo', label: 'Route metadata', note: 'export const metadata, title override' },
34
- { href: '/features/head', label: 'Imperative head', note: 'useTitle / useHead / <Head>' },
35
- ],
36
- },
37
- {
38
- heading: 'Components and runtime',
39
- items: [
40
- { href: '/features/script', label: 'Script', note: 'Toil.Script with a load strategy' },
41
- { href: '/features/realtime', label: 'WebSocket channel', note: 'Toil.useChannel against /_toil' },
42
- { href: '/io', label: 'Binary IO', note: 'BinaryWriter / BinaryReader / FastSet, no import' },
43
- ],
44
- },
45
- ];
46
-
47
- export default function Features() {
48
- return (
49
- <main>
50
- <h1>Every feature, live</h1>
51
- <p>
52
- Each link is a working demo served from <code>client/routes/</code>. Watch the tab
53
- title change as you navigate, that is the per-route <code>metadata</code> at work.
54
- </p>
55
- {groups.map((g) => (
56
- <section key={g.heading} style={{ marginTop: 24 }}>
57
- <h2 style={{ fontSize: '1rem', opacity: 0.7 }}>{g.heading}</h2>
58
- <ul style={{ display: 'grid', gap: 8, listStyle: 'none', padding: 0 }}>
59
- {g.items.map((it) => (
60
- <li key={it.href}>
61
- <Toil.Link href={it.href} style={{ fontWeight: 600 }}>
62
- {it.label}
63
- </Toil.Link>
64
- <span style={{ opacity: 0.6 }}> , {it.note}</span>
65
- </li>
66
- ))}
67
- </ul>
68
- </section>
69
- ))}
70
- <p style={{ marginTop: 24 }}>
71
- <Toil.Link href="/">Back home</Toil.Link>
72
- </p>
73
- </main>
74
- );
75
- }
1
+ // The feature hub: one place that links to a live demo of every ToilJS capability. Its own tab
2
+ // title comes from the `metadata` export below (rendered as "Features | ToilJS" via the layout
3
+ // template) and is baked into build/client/features/index.html at build time.
4
+ export const metadata: Toil.Metadata = {
5
+ title: 'Features',
6
+ description: 'Live demos of every ToilJS feature: routing, data, head/SEO, components, realtime, and binary IO.',
7
+ openGraph: { title: 'Every ToilJS feature, demoed', type: 'website' }
8
+ };
9
+
10
+ const groups: { heading: string; items: { href: Toil.Href; label: string; note: string }[] }[] = [
11
+ {
12
+ heading: 'Routing',
13
+ items: [
14
+ { href: '/blog/42', label: 'Dynamic route', note: 'blog/[id].tsx, /blog/42' },
15
+ { href: '/docs/getting/started', label: 'Catch-all', note: 'docs/[...slug].tsx' },
16
+ {
17
+ href: '/files',
18
+ label: 'Optional catch-all',
19
+ note: 'files/[[...slug]].tsx, matches /files and /files/a/b'
20
+ },
21
+ { href: '/privacy', label: 'Route group', note: '(legal)/privacy.tsx, no URL segment' },
22
+ { href: '/gallery', label: 'Parallel + intercepting', note: '@modal/(.)photo/[id], a real modal route' },
23
+ { href: '/features/template', label: 'Templates', note: 'template.tsx remounts on every navigation' },
24
+ { href: '/features/error', label: 'Error boundary', note: 'error.tsx catches a thrown route' }
25
+ ]
26
+ },
27
+ {
28
+ heading: 'Data',
29
+ items: [
30
+ { href: '/loader-demo', label: 'Loader + revalidate', note: 'data before render, cached, refetchable' },
31
+ {
32
+ href: '/features/actions',
33
+ label: 'Actions + Form',
34
+ note: 'useAction / <Form>, pending state, revalidate'
35
+ }
36
+ ]
37
+ },
38
+ {
39
+ heading: 'Head and SEO',
40
+ items: [
41
+ { href: '/features/seo', label: 'Route metadata', note: 'export const metadata, title override' },
42
+ { href: '/features/head', label: 'Imperative head', note: 'useTitle / useHead / <Head>' }
43
+ ]
44
+ },
45
+ {
46
+ heading: 'Components and runtime',
47
+ items: [
48
+ { href: '/features/script', label: 'Script', note: 'Toil.Script with a load strategy' },
49
+ { href: '/features/realtime', label: 'WebSocket channel', note: 'Toil.useChannel against /_toil' },
50
+ { href: '/io', label: 'Binary IO', note: 'BinaryWriter / BinaryReader / FastSet, no import' }
51
+ ]
52
+ }
53
+ ];
54
+
55
+ export default function Features() {
56
+ return (
57
+ <main>
58
+ <h1>Every feature, live</h1>
59
+ <p>
60
+ Each link is a working demo served from <code>client/routes/</code>. Watch the tab title change as you
61
+ navigate, that is the per-route <code>metadata</code> at work.
62
+ </p>
63
+ {groups.map((g) => (
64
+ <section key={g.heading} style={{ marginTop: 24 }}>
65
+ <h2 style={{ fontSize: '1rem', opacity: 0.7 }}>{g.heading}</h2>
66
+ <ul style={{ display: 'grid', gap: 8, listStyle: 'none', padding: 0 }}>
67
+ {g.items.map((it) => (
68
+ <li key={it.href}>
69
+ <Toil.Link href={it.href} style={{ fontWeight: 600 }}>
70
+ {it.label}
71
+ </Toil.Link>
72
+ <span style={{ opacity: 0.6 }}> , {it.note}</span>
73
+ </li>
74
+ ))}
75
+ </ul>
76
+ </section>
77
+ ))}
78
+ <p style={{ marginTop: 24 }}>
79
+ <Toil.Link href="/">Back home</Toil.Link>
80
+ </p>
81
+ </main>
82
+ );
83
+ }
@@ -1,32 +1,34 @@
1
- export const metadata: Toil.Metadata = {
2
- title: 'Realtime',
3
- description: 'A typed WebSocket channel to the server with connect, reconnect, and message decoding.',
4
- };
5
-
6
- // Toil.useChannel opens a WebSocket to the server (default path /_toil), tracks `connected`, collects
7
- // `messages`, and exposes `send`. It reconnects automatically. With no server socket running the demo
8
- // simply shows "disconnected", the API is the same once the server handles the channel.
9
- export default function RealtimeDemo() {
10
- const chat = Toil.useChannel({ path: '/_toil' });
11
- return (
12
- <main>
13
- <h1>Realtime</h1>
14
- <p>
15
- Connection: <strong>{chat.connected ? 'connected' : 'disconnected'}</strong>, messages
16
- received: <strong>{chat.messages.length}</strong>.
17
- </p>
18
- <p>
19
- <button type="button" onClick={() => chat.send('ping')}>
20
- Send ping
21
- </button>
22
- </p>
23
- <p style={{ opacity: 0.6 }}>
24
- <code>const chat = Toil.useChannel({'{'} path: '/_toil' {'}'})</code>, connect, reconnect,
25
- and decoding are handled for you.
26
- </p>
27
- <p>
28
- <Toil.Link href="/features">Back to features</Toil.Link>
29
- </p>
30
- </main>
31
- );
32
- }
1
+ export const metadata: Toil.Metadata = {
2
+ title: 'Realtime',
3
+ description: 'A typed WebSocket channel to the server with connect, reconnect, and message decoding.'
4
+ };
5
+
6
+ // Toil.useChannel opens a WebSocket to the server (default path /_toil), tracks `connected`, collects
7
+ // `messages`, and exposes `send`. It reconnects automatically. With no server socket running the demo
8
+ // simply shows "disconnected", the API is the same once the server handles the channel.
9
+ export default function RealtimeDemo() {
10
+ const chat = Toil.useChannel({ path: '/_toil' });
11
+ return (
12
+ <main>
13
+ <h1>Realtime</h1>
14
+ <p>
15
+ Connection: <strong>{chat.connected ? 'connected' : 'disconnected'}</strong>, messages received:{' '}
16
+ <strong>{chat.messages.length}</strong>.
17
+ </p>
18
+ <p>
19
+ <button type="button" onClick={() => chat.send('ping')}>
20
+ Send ping
21
+ </button>
22
+ </p>
23
+ <p style={{ opacity: 0.6 }}>
24
+ <code>
25
+ const chat = Toil.useChannel({'{'} path: '/_toil' {'}'})
26
+ </code>
27
+ , connect, reconnect, and decoding are handled for you.
28
+ </p>
29
+ <p>
30
+ <Toil.Link href="/features">Back to features</Toil.Link>
31
+ </p>
32
+ </main>
33
+ );
34
+ }
@@ -1,31 +1,31 @@
1
- import { useState } from 'react';
2
-
3
- export const metadata: Toil.Metadata = {
4
- title: 'Script',
5
- description: 'Load third-party scripts with a strategy, deduplicated so they never run twice.',
6
- };
7
-
8
- // Toil.Script injects an external or inline script once (deduped by src/id), with a load strategy.
9
- // `onReady` fires when it has loaded. Here we load a tiny public script and report when it is ready.
10
- export default function ScriptDemo() {
11
- const [ready, setReady] = useState(false);
12
- return (
13
- <main>
14
- <h1>Script</h1>
15
- <p>
16
- <code>Toil.Script</code> loads external scripts with a <code>strategy</code>{' '}
17
- (<code>afterInteractive</code>, <code>lazyOnload</code>, <code>beforeInteractive</code>)
18
- and dedupes them, so the same script never runs twice across navigations.
19
- </p>
20
- <Toil.Script
21
- src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js"
22
- strategy="afterInteractive"
23
- onReady={() => setReady(true)}
24
- />
25
- <p>Status: {ready ? 'script ready' : 'loading'}</p>
26
- <p>
27
- <Toil.Link href="/features">Back to features</Toil.Link>
28
- </p>
29
- </main>
30
- );
31
- }
1
+ import { useState } from 'react';
2
+
3
+ export const metadata: Toil.Metadata = {
4
+ title: 'Script',
5
+ description: 'Load third-party scripts with a strategy, deduplicated so they never run twice.'
6
+ };
7
+
8
+ // Toil.Script injects an external or inline script once (deduped by src/id), with a load strategy.
9
+ // `onReady` fires when it has loaded. Here we load a tiny public script and report when it is ready.
10
+ export default function ScriptDemo() {
11
+ const [ready, setReady] = useState(false);
12
+ return (
13
+ <main>
14
+ <h1>Script</h1>
15
+ <p>
16
+ <code>Toil.Script</code> loads external scripts with a <code>strategy</code> (
17
+ <code>afterInteractive</code>, <code>lazyOnload</code>, <code>beforeInteractive</code>) and dedupes
18
+ them, so the same script never runs twice across navigations.
19
+ </p>
20
+ <Toil.Script
21
+ src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js"
22
+ strategy="afterInteractive"
23
+ onReady={() => setReady(true)}
24
+ />
25
+ <p>Status: {ready ? 'script ready' : 'loading'}</p>
26
+ <p>
27
+ <Toil.Link href="/features">Back to features</Toil.Link>
28
+ </p>
29
+ </main>
30
+ );
31
+ }