Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/wet-groups-enter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@qwik.dev/core': minor
---

feat: Add experimental `Show` control-flow component with `when$`, `then$`, and optional `else$` branches.
72 changes: 72 additions & 0 deletions e2e/qwik-e2e/apps/e2e/src/components/show/show.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Show, component$, useSignal } from '@qwik.dev/core';

export const ShowRoot = component$(() => {
const withElse = useSignal(false);
const withoutElse = useSignal(false);
const interactive = useSignal(true);
const branchCount = useSignal(0);
const item = useSignal<string | null>(null);

return (
<>
<h1>Show</h1>

<div id="initial-true">
<Show when$={() => true} then$={() => <span>Initial then</span>} />
</div>

<div id="initial-false">
<Show when$={() => false} then$={() => <span>Then</span>} else$={() => <span>Else</span>} />
</div>

<div id="initial-empty">
<Show when$={() => false} then$={() => <span>Empty then</span>} />
</div>

<button id="toggle-with-else" onClick$={() => (withElse.value = !withElse.value)}>
Toggle with else
</button>
<div id="toggle-with-else-result">
<Show
when$={() => withElse.value}
then$={() => <span>Shown</span>}
else$={() => <span>Hidden</span>}
/>
</div>

<button id="toggle-without-else" onClick$={() => (withoutElse.value = !withoutElse.value)}>
Toggle without else
</button>
<div id="toggle-without-else-result">
<Show when$={() => withoutElse.value} then$={() => <span>Present</span>} />
</div>

<button id="toggle-interactive" onClick$={() => (interactive.value = !interactive.value)}>
Toggle interactive
</button>
<div id="interactive-result">
<Show
when$={() => interactive.value}
then$={() => (
<button id="branch-action" onClick$={() => branchCount.value++}>
Inside {branchCount.value}
</button>
)}
else$={() => <span>Interactive else</span>}
/>
</div>
<div id="branch-count">{branchCount.value}</div>

<button id="set-item" onClick$={() => (item.value = 'hello')}>
Set item
</button>
<div id="item-result">
<Show
when$={() => item.value}
then$={(v) => <span>Got: {v}</span>}
else$={() => <span>No value</span>}
/>
</div>
</>
);
});
2 changes: 2 additions & 0 deletions e2e/qwik-e2e/apps/e2e/src/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { QRL } from './components/qrl/qrl';
import { AsyncRoot } from './components/use-async/use-async';
import { Backpatching } from './components/backpatching/backpatching';
import { EachRoot } from './components/each/each';
import { ShowRoot } from './components/show/show';
import { SuspenseRoot } from './components/suspense/suspense';

const tests: Record<string, FunctionComponent> = {
Expand Down Expand Up @@ -81,6 +82,7 @@ const tests: Record<string, FunctionComponent> = {
'/e2e/async-computed': () => <AsyncRoot />,
'/e2e/backpatching': () => <Backpatching />,
'/e2e/each': () => <EachRoot />,
'/e2e/show': () => <ShowRoot />,
'/e2e/suspense': () => <SuspenseRoot />,
'/e2e/worker': () => <WorkerRoot />,
};
Expand Down
4 changes: 2 additions & 2 deletions e2e/qwik-e2e/dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ export { router }
clientManifest = manifest;
},
},
experimental: ['each', 'suspense', 'preventNavigate', 'enableRequestRewrite'],
experimental: ['each', 'show', 'suspense', 'preventNavigate', 'enableRequestRewrite'],
}),
],
})
Expand All @@ -224,7 +224,7 @@ export { router }
plugins: [
...plugins,
optimizer.qwikVite({
experimental: ['each', 'suspense', 'preventNavigate', 'enableRequestRewrite'],
experimental: ['each', 'show', 'suspense', 'preventNavigate', 'enableRequestRewrite'],
ssr: {
manifestInput: clientManifest,
},
Expand Down
57 changes: 57 additions & 0 deletions e2e/qwik-e2e/tests/show.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { expect, test } from '@playwright/test';

test.describe('show', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/e2e/show');
page.on('pageerror', (err) => expect(err).toEqual(undefined));
page.on('console', (msg) => {
if (msg.type() === 'error') {
expect(msg.text()).toEqual(undefined);
}
});
});

test('should render initial branches during SSR', async ({ page }) => {
await expect(page.locator('#initial-true')).toHaveText('Initial then');
await expect(page.locator('#initial-false')).toHaveText('Else');
await expect(page.locator('#initial-empty')).toHaveText('');
});

test('should update then and else branches after resume', async ({ page }) => {
const result = page.locator('#toggle-with-else-result');

await expect(result).toHaveText('Hidden');
await page.locator('#toggle-with-else').click();
await expect(result).toHaveText('Shown');
await page.locator('#toggle-with-else').click();
await expect(result).toHaveText('Hidden');
});

test('should update empty fallback after resume', async ({ page }) => {
const result = page.locator('#toggle-without-else-result');

await expect(result).toHaveText('');
await page.locator('#toggle-without-else').click();
await expect(result).toHaveText('Present');
await page.locator('#toggle-without-else').click();
await expect(result).toHaveText('');
});

test('should keep event handlers in rendered branches working', async ({ page }) => {
await expect(page.locator('#branch-action')).toHaveText('Inside 0');
await page.locator('#branch-action').click();
await expect(page.locator('#branch-action')).toHaveText('Inside 1');
await expect(page.locator('#branch-count')).toHaveText('1');

await page.locator('#toggle-interactive').click();
await expect(page.locator('#interactive-result')).toHaveText('Interactive else');
await page.locator('#toggle-interactive').click();
await expect(page.locator('#branch-action')).toHaveText('Inside 1');
});

test('should pass the when$ value to the then$ branch', async ({ page }) => {
await expect(page.locator('#item-result')).toHaveText('No value');
await page.locator('#set-item').click();
await expect(page.locator('#item-result')).toHaveText('Got: hello');
});
});
19 changes: 18 additions & 1 deletion packages/docs/src/routes/api/qwik-optimizer/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
}
],
"kind": "Enum",
"content": "Use `__EXPERIMENTAL__.x` to check if feature `x` is enabled. It will be replaced with `true` or `false` via an exact string replacement.\n\nAdd experimental features to this enum definition.\n\n\n```typescript\nexport declare enum ExperimentalFeatures \n```\n\n\n<table><thead><tr><th>\n\nMember\n\n\n</th><th>\n\nValue\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\neach\n\n\n</td><td>\n\n`\"each\"`\n\n\n</td><td>\n\nEnable the Each keyed-list primitive\n\n\n</td></tr>\n<tr><td>\n\nenableRequestRewrite\n\n\n</td><td>\n\n`\"enableRequestRewrite\"`\n\n\n</td><td>\n\nEnable request.rewrite()\n\n\n</td></tr>\n<tr><td>\n\ninsights\n\n\n</td><td>\n\n`\"insights\"`\n\n\n</td><td>\n\nEnable the ability to use the Qwik Insights vite plugin and `<Insights/>` component\n\n\n</td></tr>\n<tr><td>\n\nnoSPA\n\n\n</td><td>\n\n`\"noSPA\"`\n\n\n</td><td>\n\nDisable SPA navigation handler in Qwik Router\n\n\n</td></tr>\n<tr><td>\n\npreventNavigate\n\n\n</td><td>\n\n`\"preventNavigate\"`\n\n\n</td><td>\n\nEnable the usePreventNavigate hook\n\n\n</td></tr>\n<tr><td>\n\nsuspense\n\n\n</td><td>\n\n`\"suspense\"`\n\n\n</td><td>\n\nEnable the Suspense fallback primitive\n\n\n</td></tr>\n<tr><td>\n\nvalibot\n\n\n</td><td>\n\n`\"valibot\"`\n\n\n</td><td>\n\nEnable the Valibot form validation\n\n\n</td></tr>\n</tbody></table>",
"content": "Use `__EXPERIMENTAL__.x` to check if feature `x` is enabled. It will be replaced with `true` or `false` via an exact string replacement.\n\nAdd experimental features to this enum definition.\n\n\n```typescript\nexport declare enum ExperimentalFeatures \n```\n\n\n<table><thead><tr><th>\n\nMember\n\n\n</th><th>\n\nValue\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\neach\n\n\n</td><td>\n\n`\"each\"`\n\n\n</td><td>\n\nEnable the Each keyed-list primitive\n\n\n</td></tr>\n<tr><td>\n\nenableRequestRewrite\n\n\n</td><td>\n\n`\"enableRequestRewrite\"`\n\n\n</td><td>\n\nEnable request.rewrite()\n\n\n</td></tr>\n<tr><td>\n\ninsights\n\n\n</td><td>\n\n`\"insights\"`\n\n\n</td><td>\n\nEnable the ability to use the Qwik Insights vite plugin and `<Insights/>` component\n\n\n</td></tr>\n<tr><td>\n\nnoSPA\n\n\n</td><td>\n\n`\"noSPA\"`\n\n\n</td><td>\n\nDisable SPA navigation handler in Qwik Router\n\n\n</td></tr>\n<tr><td>\n\npreventNavigate\n\n\n</td><td>\n\n`\"preventNavigate\"`\n\n\n</td><td>\n\nEnable the usePreventNavigate hook\n\n\n</td></tr>\n<tr><td>\n\nshow\n\n\n</td><td>\n\n`\"show\"`\n\n\n</td><td>\n\nEnable the Show conditional primitive\n\n\n</td></tr>\n<tr><td>\n\nsuspense\n\n\n</td><td>\n\n`\"suspense\"`\n\n\n</td><td>\n\nEnable the Suspense fallback primitive\n\n\n</td></tr>\n<tr><td>\n\nvalibot\n\n\n</td><td>\n\n`\"valibot\"`\n\n\n</td><td>\n\nEnable the Valibot form validation\n\n\n</td></tr>\n</tbody></table>",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-vite/src/plugins/plugin.ts",
"mdFile": "qwik-vite.experimentalfeatures.md"
},
Expand Down Expand Up @@ -339,6 +339,23 @@
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-vite/src/types.ts",
"mdFile": "qwik-vite.serverqwikmanifest.md"
},
{
"name": "show",
"id": "experimentalfeatures-show",
"hierarchy": [
{
"name": "ExperimentalFeatures",
"id": "experimentalfeatures-show"
},
{
"name": "show",
"id": "experimentalfeatures-show"
}
],
"kind": "EnumMember",
"content": "",
"mdFile": "qwik-vite.experimentalfeatures.show.md"
},
{
"name": "suspense",
"id": "experimentalfeatures-suspense",
Expand Down
15 changes: 15 additions & 0 deletions packages/docs/src/routes/api/qwik-optimizer/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@ Enable the usePreventNavigate hook
</td></tr>
<tr><td>

show

</td><td>

`"show"`

</td><td>

Enable the Show conditional primitive

</td></tr>
<tr><td>

suspense

</td><td>
Expand Down Expand Up @@ -1449,6 +1462,8 @@ export type ServerQwikManifest = Pick<

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-vite/src/types.ts)

<h2 id="experimentalfeatures-show">show</h2>

<h2 id="experimentalfeatures-suspense">suspense</h2>

<h2 id="symbolmapper">SymbolMapper</h2>
Expand Down
42 changes: 42 additions & 0 deletions packages/docs/src/routes/api/qwik/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -2073,6 +2073,48 @@
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/shared/platform/platform.ts",
"mdFile": "core.setplatform.md"
},
{
"name": "Show",
"id": "show",
"hierarchy": [
{
"name": "Show",
"id": "show"
}
],
"kind": "Variable",
"content": "```typescript\nShow: ShowComponent\n```",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/control-flow/show.ts",
"mdFile": "core.show.md"
},
{
"name": "ShowComponent",
"id": "showcomponent",
"hierarchy": [
{
"name": "ShowComponent",
"id": "showcomponent"
}
],
"kind": "TypeAlias",
"content": "```typescript\nexport type ShowComponent = <WHEN = unknown, THEN extends JSXOutput = JSXOutput, ELSE extends JSXOutput = JSXOutput>(props: PublicProps<ShowProps<WHEN, THEN, ELSE>>, key: string | null, flags: number, dev?: DevJSX) => JSXOutput;\n```\n**References:** [JSXOutput](#jsxoutput)<!-- -->, [PublicProps](#publicprops)<!-- -->, [ShowProps](#showprops)<!-- -->, [DevJSX](#devjsx)",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/control-flow/show.ts",
"mdFile": "core.showcomponent.md"
},
{
"name": "ShowProps",
"id": "showprops",
"hierarchy": [
{
"name": "ShowProps",
"id": "showprops"
}
],
"kind": "Interface",
"content": "```typescript\nexport interface ShowProps<WHEN = unknown, THEN extends JSXOutput = JSXOutput, ELSE extends JSXOutput = JSXOutput> \n```\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nelse$?\n\n\n</td><td>\n\n\n</td><td>\n\n[QRL](#qrl-type-alias)<!-- -->&lt;(when: WHEN) =&gt; ELSE&gt;\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\nthen$\n\n\n</td><td>\n\n\n</td><td>\n\n[QRL](#qrl-type-alias)<!-- -->&lt;(when: WHEN) =&gt; THEN&gt;\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\nwhen$\n\n\n</td><td>\n\n\n</td><td>\n\n[QRL](#qrl-type-alias)<!-- -->&lt;() =&gt; WHEN&gt;\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/control-flow/show.ts",
"mdFile": "core.showprops.md"
},
{
"name": "Signal",
"id": "signal",
Expand Down
95 changes: 95 additions & 0 deletions packages/docs/src/routes/api/qwik/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4505,6 +4505,101 @@ plt

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/shared/platform/platform.ts)

<h2 id="show">Show</h2>

```typescript
Show: ShowComponent;
```

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/control-flow/show.ts)

<h2 id="showcomponent">ShowComponent</h2>

```typescript
export type ShowComponent = <
WHEN = unknown,
THEN extends JSXOutput = JSXOutput,
ELSE extends JSXOutput = JSXOutput,
>(
props: PublicProps<ShowProps<WHEN, THEN, ELSE>>,
key: string | null,
flags: number,
dev?: DevJSX,
) => JSXOutput;
```

**References:** [JSXOutput](#jsxoutput), [PublicProps](#publicprops), [ShowProps](#showprops), [DevJSX](#devjsx)

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/control-flow/show.ts)

<h2 id="showprops">ShowProps</h2>

```typescript
export interface ShowProps<WHEN = unknown, THEN extends JSXOutput = JSXOutput, ELSE extends JSXOutput = JSXOutput>
```

<table><thead><tr><th>

Property

</th><th>

Modifiers

</th><th>

Type

</th><th>

Description

</th></tr></thead>
<tbody><tr><td>

else$?

</td><td>

</td><td>

[QRL](#qrl-type-alias)&lt;(when: WHEN) =&gt; ELSE&gt;

</td><td>

_(Optional)_

</td></tr>
<tr><td>

then$

</td><td>

</td><td>

[QRL](#qrl-type-alias)&lt;(when: WHEN) =&gt; THEN&gt;

</td><td>

</td></tr>
<tr><td>

when$

</td><td>

</td><td>

[QRL](#qrl-type-alias)&lt;() =&gt; WHEN&gt;

</td><td>

</td></tr>
</tbody></table>

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/control-flow/show.ts)

<h2 id="signal">Signal</h2>

A signal is a reactive value which can be read and written. When the signal is written, all tasks which are tracking the signal will be re-run and all components that read the signal will be re-rendered.
Expand Down
Loading
Loading