Skip to content
Merged
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
4 changes: 4 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@
"fixture:request-time:e2e": "cd packages/adapter-vite/__fixtures__/request-time && deno run -A npm:@playwright/test@1.59.1 test --config e2e/playwright.config.ts --project=chromium",
"fixture:request-time:e2e:browsers": "cd packages/adapter-vite/__fixtures__/request-time && deno run -A npm:@playwright/test@1.59.1 test --config e2e/playwright.config.ts",
"fixture:request-time:gate": "deno task fixture:request-time:build && deno task fixture:request-time:e2e:browsers",
"fixture:ui-dogfood:build": "cd packages/adapter-vite/__fixtures__/ui-dogfood && rm -rf dist && deno run --config ../../../../deno.json --allow-read --allow-write --allow-net --allow-env --allow-ffi --allow-sys --allow-run ../../src/cli/build.ts",
"fixture:ui-dogfood:e2e": "cd packages/adapter-vite/__fixtures__/ui-dogfood && deno run -A npm:@playwright/test@1.59.1 test --config e2e/playwright.config.ts --project=chromium",
"fixture:ui-dogfood:e2e:browsers": "cd packages/adapter-vite/__fixtures__/ui-dogfood && deno run -A npm:@playwright/test@1.59.1 test --config e2e/playwright.config.ts",
"fixture:ui-dogfood:gate": "deno task fixture:ui-dogfood:build && deno task fixture:ui-dogfood:e2e:browsers",
"starter-smoke:setup": "deno run -A e2e/starter-smoke/setup.ts",
"test:starter-smoke": "deno task starter-smoke:setup && deno run -A npm:@playwright/test@1.59.1 test --config e2e/starter-smoke/playwright.config.ts",
"test:starter-smoke:dev": "deno task starter-smoke:setup && deno run -A npm:@playwright/test@1.59.1 test --config e2e/starter-smoke/playwright.dev.config.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Closed-shadow boundary element — consumer-authored, qualifies the
* closed-root contract as an external consumer (#1226): SSR emits a
* shadowrootmode="closed" template whose content renders but stays
* encapsulated (host.shadowRoot === null).
*/
import { element, OpenElement } from '@openelement/element';

@element('dogfood-closed', { root: 'shadow-closed' })
export default class DogfoodClosed extends OpenElement {
render() {
return <p>closed root boundary content</p>;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Light-root boundary element — consumer-authored, qualifies the light-root
* contract as an external consumer (#1226): SSR emits the content inline with
* the generated data-oe-light marker, no shadowroot template.
*/
import { element, OpenElement } from '@openelement/element';

@element('dogfood-light', { root: 'light' })
export default class DogfoodLight extends OpenElement {
render() {
return <p id='light-content'>light root boundary content</p>;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* /boundaries page — qualifies the open/light/closed root contracts side by
* side (#1226): @openelement/ui primitives are shadow-open (observable
* shadowRoot), while the consumer-authored dogfood-light and dogfood-closed
* elements prove the other two root modes through the same compiled path.
*/
import { element, OpenElement } from '@openelement/element';
import '@openelement/ui/open-badge';
import './boundary-closed.tsx';
import './boundary-light.tsx';

@element('boundaries-page', { root: 'shadow-open' })
export default class BoundariesPage extends OpenElement {
render() {
return (
<main>
<h1>ui dogfood — boundaries</h1>
<open-badge id='open-boundary' tone='brand'>open shadow boundary</open-badge>
<dogfood-light></dogfood-light>
<dogfood-closed></dogfood-closed>
</main>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* /dialog page — qualifies open-dialog on the compiled framework (#1226):
* - a trigger-driven modal dialog (focus containment, Escape, focus return);
* - a second dialog SSR-rendered with the `open` attribute, qualifying the
* attribute -> top-layer modal choreography at hydration (#1030).
*/
import { element, OpenElement } from '@openelement/element';
import '@openelement/ui/open-dialog';

@element('dialog-page', { root: 'shadow-open' })
export default class DialogPage extends OpenElement {
render() {
return (
<main>
<h1>ui dogfood — dialog</h1>
<open-dialog label='Dogfood dialog'>
<button slot='trigger' id='dialog-trigger' type='button'>Open dialog</button>
<p id='dialog-text'>Dogfood dialog body</p>
<button id='dialog-inner-action' type='button'>Inner action</button>
<button slot='footer' id='dialog-footer-action' type='button'>Footer action</button>
</open-dialog>
<open-dialog id='ssr-open-dialog' label='SSR open dialog' open>
<p id='ssr-open-text'>SSR-opened dialog body</p>
</open-dialog>
<button id='after-dialog' type='button'>After dialog</button>
</main>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* /dropdown page — qualifies open-dropdown (popover-API dropdown) on the
* compiled framework (#1226): trigger toggle (pointerdown/click guard),
* light dismiss, Escape close, and the per-instance anchor pair.
*/
import { element, OpenElement } from '@openelement/element';
import '@openelement/ui/open-dropdown';

@element('dropdown-page', { root: 'shadow-open' })
export default class DropdownPage extends OpenElement {
render() {
return (
<main>
<h1>ui dogfood — dropdown</h1>
<open-dropdown id='main-dropdown'>
<button slot='trigger' id='dropdown-trigger' type='button'>Menu</button>
<div role='menu' id='dropdown-menu'>
<button role='menuitem' id='menu-item-1' type='button'>First action</button>
<button role='menuitem' id='menu-item-2' type='button'>Second action</button>
</div>
</open-dropdown>
<button id='outside' type='button'>Outside</button>
</main>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* /form page — qualifies open-input + open-button form participation on the
* compiled framework (#1226):
* - open-input is form-associated (ElementInternals.setFormValue), so a real
* <form> submission carries its value;
* - required + empty value maps to valueMissing on the host validity;
* - formResetCallback clears the value; formDisabledCallback mirrors a
* disabled fieldset ancestor;
* - open-button type="submit" runs the composed-submit choreography.
* The form-probe island wires the observable: it writes the submission's
* FormData entries into #form-output.
*/
import { element, OpenElement } from '@openelement/element';
import '@openelement/ui/open-button';
import '@openelement/ui/open-input';

@element('form-page', { root: 'shadow-open' })
export default class FormPage extends OpenElement {
render() {
return (
<main>
<h1>ui dogfood — form</h1>
<form id='dogfood-form'>
<open-input id='username' name='username' label='Username' required></open-input>
<open-input
id='email'
name='email'
type='email'
label='Email'
value='ada@example.com'
>
</open-input>
<fieldset id='locked-group' disabled>
<open-input id='locked' name='locked' label='Locked'></open-input>
</fieldset>
<open-button id='submit-open' type='submit'>Submit via open-button</open-button>
<button id='submit-native' type='submit'>Native submit</button>
<button id='reset-native' type='reset'>Native reset</button>
</form>
<form-probe></form-probe>
<output id='form-output'></output>
</main>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/** ui-dogfood home — static prerendered page (compiled, v0.44). */
import { element, OpenElement } from '@openelement/element';

@element('index-page', { root: 'shadow-open' })
export default class HomePage extends OpenElement {
render() {
return (
<main>
<h1 id='home-marker'>ui dogfood fixture home</h1>
<nav>
<a href='/dialog'>dialog</a> <a href='/tabs'>tabs</a> <a href='/dropdown'>dropdown</a>
{' '}
<a href='/form'>form</a> <a href='/boundaries'>boundaries</a>
</nav>
</main>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* /tabs page — qualifies open-tabs (WAI-ARIA tabs pattern) on the compiled
* framework (#1226): light-DOM tab/panel decoration, roving tabindex,
* ArrowLeft/ArrowRight/Home/End keyboard selection, and the #move-target
* container used by the reconnect/dispose spec.
*/
import { element, OpenElement } from '@openelement/element';
import '@openelement/ui/open-tabs';

@element('tabs-page', { root: 'shadow-open' })
export default class TabsPage extends OpenElement {
render() {
return (
<main>
<h1>ui dogfood — tabs</h1>
<open-tabs id='main-tabs'>
<button slot='tab' type='button'>Alpha</button>
<button slot='tab' type='button'>Beta</button>
<button slot='tab' type='button'>Gamma</button>
<div slot='panel'>Alpha panel content</div>
<div slot='panel'>Beta panel content</div>
<div slot='panel'>Gamma panel content</div>
</open-tabs>
<div id='tabs-move-target'></div>
</main>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* form-probe — island that makes form participation observable (#1226).
*
* The /form page shell is not registered client-side, so the submit/reset
* listeners live here: on activation the probe wires the form (found through
* its own root node — the page shadow root) and echoes FormData entries into
* #form-output with default prevented, so the browser never navigates.
*/
import { element, OpenElement, property } from '@openelement/element';
import { defineIslandConfig } from '@openelement/app';

export const openElement = defineIslandConfig({ hydrate: 'load', ssr: true, dsd: true });

@element('form-probe', { root: 'shadow-open' })
export default class FormProbe extends OpenElement {
@property({ reflect: false, attribute: false })
status = 'probe-idle';

override onDsdHydrated(): void {
this.wire();
}

override onCsrRendered(): void {
this.wire();
}

private wire(): void {
const root = this.getRootNode() as unknown as ParentNode;
const form = root.querySelector<HTMLFormElement>('#dogfood-form');
const output = root.querySelector<HTMLOutputElement>('#form-output');
if (!form || !output) return;
// Reconnect-safe: the listeners outlive a move, so wire at most once.
if (form.dataset.probeWired === 'true') return;
form.dataset.probeWired = 'true';
const echo = (): void => {
const data = new FormData(form);
output.textContent = JSON.stringify(Object.fromEntries(data.entries()));
};
form.addEventListener('submit', (event) => {
event.preventDefault();
echo();
});
form.addEventListener('reset', () => {
// formResetCallback runs as a queued custom-element reaction — after a
// microtask queued from this listener — so the echo waits a macrotask.
setTimeout(echo, 0);
});
this.status = 'probe-wired';
}

render() {
return <p id='probe-status'>{this.status}</p>;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** /boundaries — static page qualifying the open/light/closed root contracts (#1226). */
import { definePage } from '@openelement/app';
import BoundariesPage from '../components/page-boundaries.tsx';

export default definePage(BoundariesPage, {
head: { title: 'ui dogfood fixture — boundaries' },
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** /dialog — static page qualifying open-dialog (#1226). */
import { definePage } from '@openelement/app';
import DialogPage from '../components/page-dialog.tsx';

export default definePage(DialogPage, {
head: { title: 'ui dogfood fixture — dialog' },
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** /dropdown — static page qualifying open-dropdown (#1226). */
import { definePage } from '@openelement/app';
import DropdownPage from '../components/page-dropdown.tsx';

export default definePage(DropdownPage, {
head: { title: 'ui dogfood fixture — dropdown' },
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** /form — static page qualifying open-input/open-button form participation (#1226). */
import { definePage } from '@openelement/app';
import FormPage from '../components/page-form.tsx';

export default definePage(FormPage, {
head: { title: 'ui dogfood fixture — form' },
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** Static home page (default renderIntent mode 'static') — prerendered at build time. */
import { definePage } from '@openelement/app';
import HomePage from '../components/page-home.tsx';

export default definePage(HomePage, {
head: { title: 'ui dogfood fixture — home' },
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** /tabs — static page qualifying open-tabs (#1226). */
import { definePage } from '@openelement/app';
import TabsPage from '../components/page-tabs.tsx';

export default definePage(TabsPage, {
head: { title: 'ui dogfood fixture — tabs' },
});
56 changes: 56 additions & 0 deletions packages/adapter-vite/__fixtures__/ui-dogfood/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"nodeModulesDir": "auto",
"exclude": [
"e2e"
],
"compilerOptions": {
"lib": [
"ES2022",
"DOM",
"DOM.Iterable",
"deno.ns"
],
"strict": true,
"skipLibCheck": true,
"jsx": "react-jsx",
"jsxImportSource": "@openelement/element"
},
"fmt": {
"useTabs": false,
"lineWidth": 100,
"indentWidth": 2,
"semiColons": true,
"singleQuote": true,
"proseWrap": "preserve"
},
"lint": {
"rules": {
"tags": [
"recommended"
],
"exclude": [
"no-sloppy-imports"
]
}
},
"imports": {
"@openelement/app": "../../../app/src/index.ts",
"@openelement/element": "../../../element/src/index.ts",
"@openelement/element/jsx-runtime": "../../../element/src/jsx-runtime.ts",
"@openelement/element/jsx-dev-runtime": "../../../element/src/jsx-dev-runtime.ts",
"@openelement/element/build-utils": "../../../element/src/build-utils.ts",
"@openelement/element/sanitize": "../../../element/src/sanitize.ts",
"@openelement/element/": "../../../element/src/",
"@openelement/ui": "../../../ui/src/index.ts",
"@openelement/ui/open-badge": "../../../ui/src/open-badge.tsx",
"@openelement/ui/open-button": "../../../ui/src/open-button.tsx",
"@openelement/ui/open-callout": "../../../ui/src/open-callout.tsx",
"@openelement/ui/open-card": "../../../ui/src/open-card.tsx",
"@openelement/ui/open-code-block": "../../../ui/src/open-code-block.tsx",
"@openelement/ui/open-dialog": "../../../ui/src/open-dialog.tsx",
"@openelement/ui/open-dropdown": "../../../ui/src/open-dropdown.tsx",
"@openelement/ui/open-input": "../../../ui/src/open-input.tsx",
"@openelement/ui/open-tabs": "../../../ui/src/open-tabs.tsx",
"@openelement/ui/open-theme-toggle": "../../../ui/src/open-theme-toggle.tsx"
}
}
Loading
Loading