Add experiments folder. Add usage.md and SKILL.md. Fix Sheet interactions and add dynamic sheet mode.
This commit is contained in:
@@ -1,37 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { NativeDismissGesture, useNativeRouter } from '@native-vue-router/core'
|
||||
import AppAvatar from '../components/AppAvatar.vue'
|
||||
import { useDemoStore } from '../data'
|
||||
import { computed, ref } from "vue";
|
||||
import { NativeSheet, useNativeRouter } from "@native-vue-router/core";
|
||||
import AppAvatar from "../components/AppAvatar.vue";
|
||||
import { useDemoStore } from "../data";
|
||||
|
||||
const native = useNativeRouter()
|
||||
const store = useDemoStore()
|
||||
const query = ref('')
|
||||
const native = useNativeRouter();
|
||||
const store = useDemoStore();
|
||||
const query = ref("");
|
||||
const sheetBreakpoint = ref(0.62);
|
||||
const snapEnabled = ref(true);
|
||||
const sheetBreakpoints = computed(() =>
|
||||
snapEnabled.value ? [0.38, 0.62, 1] : [],
|
||||
);
|
||||
|
||||
async function choose(id: string) {
|
||||
await native.cancelInteractive()
|
||||
await native.replace(`/chat/${id}`, { presentation: 'push' })
|
||||
await native.cancelInteractive();
|
||||
await native.replace(`/chat/${id}`, { presentation: "push" });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NativeDismissGesture as="main" class="screen sheet-screen">
|
||||
<div class="sheet-handle" aria-hidden="true" />
|
||||
<header class="sheet-header">
|
||||
<button type="button" @click="native.dismiss()">Cancel</button>
|
||||
<h1>New message</h1>
|
||||
<span />
|
||||
</header>
|
||||
<label class="search-field compose-search">
|
||||
<span>To:</span>
|
||||
<input v-model="query" autofocus placeholder="Search people" />
|
||||
</label>
|
||||
<div class="conversation-list">
|
||||
<button v-for="person in store.people.value.filter((item) => item.name.toLowerCase().includes(query.toLowerCase()))" :key="person.id" class="conversation-row" @click="choose(person.id)">
|
||||
<AppAvatar :person="person" />
|
||||
<div class="conversation-copy"><strong>{{ person.name }}</strong><p>{{ person.handle }}</p></div>
|
||||
<span class="chevron">›</span>
|
||||
</button>
|
||||
</div>
|
||||
</NativeDismissGesture>
|
||||
<NativeSheet
|
||||
v-model="sheetBreakpoint"
|
||||
class="compose-sheet"
|
||||
aria-label="New message"
|
||||
:breakpoints="sheetBreakpoints"
|
||||
:initial-breakpoint="0.62"
|
||||
>
|
||||
<main class="compose-sheet-content">
|
||||
<header class="sheet-header">
|
||||
<button type="button" @click="native.dismiss()">Cancel</button>
|
||||
<div>
|
||||
<h1>New message</h1>
|
||||
<small data-testid="sheet-size">
|
||||
{{ snapEnabled ? `${Math.round(sheetBreakpoint * 100)}%` : "Auto" }}
|
||||
</small>
|
||||
</div>
|
||||
<button type="button" @click="snapEnabled = !snapEnabled">
|
||||
{{ snapEnabled ? "Fit content" : "Use snap points" }}
|
||||
</button>
|
||||
</header>
|
||||
<label class="search-field compose-search">
|
||||
<span>To:</span>
|
||||
<input v-model="query" autofocus placeholder="Search people" />
|
||||
</label>
|
||||
<div class="conversation-list">
|
||||
<button
|
||||
v-for="person in store.people.value.filter((item) =>
|
||||
item.name.toLowerCase().includes(query.toLowerCase()),
|
||||
)"
|
||||
:key="person.id"
|
||||
class="conversation-row"
|
||||
@click="choose(person.id)"
|
||||
>
|
||||
<AppAvatar :person="person" />
|
||||
<div class="conversation-copy">
|
||||
<strong>{{ person.name }}</strong>
|
||||
<p>{{ person.handle }}</p>
|
||||
</div>
|
||||
<span class="chevron">›</span>
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
</NativeSheet>
|
||||
</template>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { NativeLink, useNativeRouter } from '@native-vue-router/core'
|
||||
import AppHeader from '../components/AppHeader.vue'
|
||||
import { setStoryEntryBlocked, storyEntryGuard } from '../guard-state'
|
||||
import { NativeLink, useNativeRouter } from "@native-vue-router/core";
|
||||
import AppHeader from "../components/AppHeader.vue";
|
||||
import { setStoryEntryBlocked, storyEntryGuard } from "../guard-state";
|
||||
|
||||
const native = useNativeRouter()
|
||||
const native = useNativeRouter();
|
||||
|
||||
function toggleStoryGuard() {
|
||||
setStoryEntryBlocked(!storyEntryGuard.blockEntry)
|
||||
setStoryEntryBlocked(!storyEntryGuard.blockEntry);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -24,13 +24,35 @@ function toggleStoryGuard() {
|
||||
</div>
|
||||
</section>
|
||||
<section class="settings-list">
|
||||
<a href="/profile/runtime-lab" @click.prevent="native.sibling('/profile/runtime-lab')"><span>⌁</span><strong>Runtime stress lab</strong><i>›</i></a>
|
||||
<button type="button" aria-label="Block Stories re-entry" :aria-pressed="storyEntryGuard.blockEntry" @click="toggleStoryGuard">
|
||||
<span>⌽</span><strong>Block cached Stories re-entry</strong><i data-testid="story-guard-status">{{ storyEntryGuard.blockEntry ? storyEntryGuard.status : 'Off' }}</i>
|
||||
<a
|
||||
href="/profile/runtime-lab"
|
||||
@click.prevent="native.sibling('/profile/runtime-lab')"
|
||||
><span>⌁</span><strong>Runtime stress lab</strong><i>›</i></a
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Block Stories re-entry"
|
||||
:aria-pressed="storyEntryGuard.blockEntry"
|
||||
@click="toggleStoryGuard"
|
||||
>
|
||||
<span>⌽</span><strong>Block cached Stories re-entry</strong
|
||||
><i data-testid="story-guard-status">{{
|
||||
storyEntryGuard.blockEntry ? storyEntryGuard.status : "Off"
|
||||
}}</i>
|
||||
</button>
|
||||
<NativeLink to="/settings"
|
||||
><span>⚙︎</span><strong>Navigation lab</strong><i>›</i></NativeLink
|
||||
>
|
||||
<NativeLink to="/profile/vue-lab/alpha?mode=manual#route-state"
|
||||
><span>Vue</span><strong>Vue compatibility lab</strong
|
||||
><i>›</i></NativeLink
|
||||
>
|
||||
<a href="https://github.com" target="_blank" rel="noreferrer"
|
||||
><span>⌘</span><strong>Project source</strong><i>↗</i></a
|
||||
>
|
||||
<button type="button">
|
||||
<span>◐</span><strong>Appearance</strong><i>System</i>
|
||||
</button>
|
||||
<NativeLink to="/settings"><span>⚙︎</span><strong>Navigation lab</strong><i>›</i></NativeLink>
|
||||
<a href="https://github.com" target="_blank" rel="noreferrer"><span>⌘</span><strong>Project source</strong><i>↗</i></a>
|
||||
<button type="button"><span>◐</span><strong>Appearance</strong><i>System</i></button>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { useNativeRouter } from "@native-vue-router/core";
|
||||
import { useRoute } from "vue-router";
|
||||
import AppHeader from "../components/AppHeader.vue";
|
||||
import {
|
||||
compatibilityLab,
|
||||
recordCompatibilityEvent,
|
||||
} from "../compatibility-lab";
|
||||
|
||||
const route = useRoute();
|
||||
const native = useNativeRouter();
|
||||
const unloadStatus = ref("Lab remains cached");
|
||||
const labLocation = computed(() => ({
|
||||
name: "vue-compatibility",
|
||||
params: { sample: route.params.sample },
|
||||
query: route.query,
|
||||
hash: route.hash,
|
||||
}));
|
||||
|
||||
function unloadLab() {
|
||||
const count = native.unload(labLocation.value);
|
||||
unloadStatus.value = count
|
||||
? "Lab view evicted; Back will create a new instance"
|
||||
: "No matching inactive lab view was mounted";
|
||||
recordCompatibilityEvent("Away screen", "native.unload", `${count} view(s)`);
|
||||
}
|
||||
|
||||
async function unloadAndReturn() {
|
||||
unloadLab();
|
||||
await native.pop();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main
|
||||
class="screen compatibility-screen"
|
||||
data-testid="vue-compatibility-away"
|
||||
>
|
||||
<AppHeader
|
||||
title="Cached-route checkpoint"
|
||||
subtitle="The compatibility lab is behind this view"
|
||||
back
|
||||
/>
|
||||
|
||||
<section class="lab-intro compat-intro">
|
||||
<span>↩</span>
|
||||
<div>
|
||||
<strong>Inspect native cache behavior</strong>
|
||||
<p>
|
||||
The previous lab instance is inactive but still mounted until you
|
||||
explicitly unload it.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settings-group">
|
||||
<h2>Inactive route controls</h2>
|
||||
<div>
|
||||
<span
|
||||
><strong>Compatibility lab</strong
|
||||
><small>{{ unloadStatus }}</small></span
|
||||
><b>behind</b>
|
||||
</div>
|
||||
<p>
|
||||
Use normal Back to observe native activate/show without Vue remount
|
||||
hooks. Use “Unload and return” to observe beforeUnmount/unmounted
|
||||
followed by a fresh component instance.
|
||||
</p>
|
||||
</section>
|
||||
<div class="compat-controls">
|
||||
<button type="button" data-testid="compat-unload-lab" @click="unloadLab">
|
||||
Unload cached lab
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="compat-unload-return"
|
||||
@click="unloadAndReturn"
|
||||
>
|
||||
Unload and return
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section class="settings-group compat-event-section">
|
||||
<div class="compat-event-heading">
|
||||
<span
|
||||
><strong>Shared lifecycle journal</strong
|
||||
><small>Events survive route eviction</small></span
|
||||
>
|
||||
</div>
|
||||
<ol class="compat-event-log" data-testid="compat-away-event-log">
|
||||
<li v-for="event in compatibilityLab.events" :key="event.id">
|
||||
<time>{{ event.timestamp }}</time>
|
||||
<span
|
||||
><strong>{{ event.source }}</strong
|
||||
><code>{{ event.hook }}</code></span
|
||||
>
|
||||
<small>{{ event.detail }}</small>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
@@ -0,0 +1,427 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
computed,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
provide,
|
||||
ref,
|
||||
} from "vue";
|
||||
import {
|
||||
onNativeViewActivate,
|
||||
onNativeViewDeactivate,
|
||||
onNativeViewEvict,
|
||||
onNativeViewHide,
|
||||
onNativeViewShow,
|
||||
useNativeRouter,
|
||||
useNativeViewLifecycle,
|
||||
} from "@native-vue-router/core";
|
||||
import { useRoute } from "vue-router";
|
||||
import AppHeader from "../components/AppHeader.vue";
|
||||
import CompatibilityAsyncProbe from "../components/CompatibilityAsyncProbe.vue";
|
||||
import CompatibilityCompositionProbe from "../components/CompatibilityCompositionProbe.vue";
|
||||
import CompatibilityInjectProbe from "../components/CompatibilityInjectProbe.vue";
|
||||
import CompatibilityKeepAliveProbe from "../components/CompatibilityKeepAliveProbe.vue";
|
||||
import CompatibilityOptionsProbe from "../components/CompatibilityOptionsProbe.vue";
|
||||
import {
|
||||
clearCompatibilityEvents,
|
||||
compatibilityLab,
|
||||
compatibilityLabContextKey,
|
||||
recordCompatibilityEvent,
|
||||
} from "../compatibility-lab";
|
||||
|
||||
const route = useRoute();
|
||||
const native = useNativeRouter();
|
||||
const nativeLifecycle = useNativeViewLifecycle();
|
||||
const instanceId = Math.random().toString(36).slice(2, 7);
|
||||
const revision = ref(0);
|
||||
const probesMounted = ref(true);
|
||||
const keepAliveVariant = ref<"A" | "B">("A");
|
||||
const transitionVisible = ref(true);
|
||||
const teleportOpen = ref(false);
|
||||
const suspenseRequest = ref(1);
|
||||
const routeLabel = computed(() => route.fullPath);
|
||||
const nativeVisible = nativeLifecycle.isVisible;
|
||||
const nativeActive = nativeLifecycle.isActive;
|
||||
const eventCount = computed(() => compatibilityLab.events.length);
|
||||
|
||||
provide(compatibilityLabContextKey, {
|
||||
source: `Vue compatibility lab ${instanceId}`,
|
||||
routeLabel,
|
||||
});
|
||||
|
||||
const recordPageEvent = (hook: string, detail?: string) =>
|
||||
recordCompatibilityEvent(`Lab page ${instanceId}`, hook, detail);
|
||||
|
||||
onMounted(() => recordPageEvent("onMounted", route.fullPath));
|
||||
onBeforeUnmount(() => recordPageEvent("onBeforeUnmount"));
|
||||
onUnmounted(() => recordPageEvent("onUnmounted"));
|
||||
onNativeViewActivate(() => recordPageEvent("native activate"));
|
||||
onNativeViewDeactivate(() => recordPageEvent("native deactivate"));
|
||||
onNativeViewShow(() => recordPageEvent("native show"));
|
||||
onNativeViewHide(() => recordPageEvent("native hide"));
|
||||
onNativeViewEvict((reason) => recordPageEvent("native evict", String(reason)));
|
||||
|
||||
function updateProbes() {
|
||||
revision.value += 1;
|
||||
recordPageEvent("revision changed", String(revision.value));
|
||||
}
|
||||
|
||||
function toggleProbeMount() {
|
||||
probesMounted.value = !probesMounted.value;
|
||||
recordPageEvent(probesMounted.value ? "probes inserted" : "probes removed");
|
||||
}
|
||||
|
||||
function switchKeptInstance() {
|
||||
keepAliveVariant.value = keepAliveVariant.value === "A" ? "B" : "A";
|
||||
}
|
||||
|
||||
function recordTransition(hook: string) {
|
||||
recordCompatibilityEvent("Transition", hook);
|
||||
}
|
||||
|
||||
function reloadSuspense() {
|
||||
suspenseRequest.value += 1;
|
||||
}
|
||||
|
||||
function pushAlternateRoute() {
|
||||
const sample = route.params.sample === "alpha" ? "beta" : "alpha";
|
||||
void native.push({
|
||||
name: "vue-compatibility",
|
||||
params: { sample },
|
||||
query: { mode: "parameter", revision: revision.value },
|
||||
hash: "#route-state",
|
||||
});
|
||||
}
|
||||
|
||||
function replaceQueryAndHash() {
|
||||
void native.replace(
|
||||
{
|
||||
name: "vue-compatibility",
|
||||
params: { sample: route.params.sample },
|
||||
query: { mode: "replaced", tick: Date.now().toString().slice(-5) },
|
||||
hash: "#event-log",
|
||||
},
|
||||
{ presentation: "fade" },
|
||||
);
|
||||
}
|
||||
|
||||
function openAwayRoute() {
|
||||
void native.push({
|
||||
name: "vue-compatibility-away",
|
||||
params: { sample: route.params.sample },
|
||||
query: route.query,
|
||||
hash: route.hash,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main
|
||||
class="screen compatibility-screen"
|
||||
data-testid="vue-compatibility-view"
|
||||
:data-instance-id="instanceId"
|
||||
>
|
||||
<AppHeader
|
||||
title="Vue compatibility"
|
||||
subtitle="Native component laboratory"
|
||||
back
|
||||
>
|
||||
<span class="compat-header-state" :class="{ active: nativeActive }">{{
|
||||
nativeActive ? "active" : nativeVisible ? "transitioning" : "cached"
|
||||
}}</span>
|
||||
</AppHeader>
|
||||
|
||||
<section class="lab-intro compat-intro">
|
||||
<span>Vue</span>
|
||||
<div>
|
||||
<strong>Exercise real framework behavior</strong>
|
||||
<p>
|
||||
Every control below runs inside a routed, cached NativeRouterView
|
||||
entry.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="route-state"
|
||||
class="settings-group compat-route-state"
|
||||
data-testid="compat-route-state"
|
||||
>
|
||||
<h2>Scoped route state</h2>
|
||||
<div>
|
||||
<span
|
||||
><strong>Full path</strong
|
||||
><small>useRoute() and Options API $route</small></span
|
||||
><code data-testid="compat-full-path">{{ route.fullPath }}</code>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
><strong>Named route</strong
|
||||
><small>Matched record identity</small></span
|
||||
><code>{{ String(route.name) }}</code>
|
||||
</div>
|
||||
<div>
|
||||
<span><strong>Param</strong><small>route.params.sample</small></span
|
||||
><code data-testid="compat-param">{{ route.params.sample }}</code>
|
||||
</div>
|
||||
<div>
|
||||
<span><strong>Query</strong><small>route.query</small></span
|
||||
><code data-testid="compat-query">{{
|
||||
JSON.stringify(route.query)
|
||||
}}</code>
|
||||
</div>
|
||||
<div>
|
||||
<span><strong>Hash</strong><small>route.hash</small></span
|
||||
><code data-testid="compat-hash">{{ route.hash || "(empty)" }}</code>
|
||||
</div>
|
||||
<div>
|
||||
<span><strong>Matched</strong><small>route.matched</small></span
|
||||
><code>{{
|
||||
route.matched.map((record) => String(record.name)).join(" → ")
|
||||
}}</code>
|
||||
</div>
|
||||
</section>
|
||||
<div class="compat-controls compat-controls--three">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="compat-change-param"
|
||||
@click="pushAlternateRoute"
|
||||
>
|
||||
Push alternate param
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="compat-change-query"
|
||||
@click="replaceQueryAndHash"
|
||||
>
|
||||
Replace query + hash
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="compat-open-away"
|
||||
@click="openAwayRoute"
|
||||
>
|
||||
Cache this view
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section class="settings-group">
|
||||
<h2>provide() / inject()</h2>
|
||||
<CompatibilityInjectProbe location="route tree" />
|
||||
<p>
|
||||
The same probe is rendered inside the Teleport below to verify
|
||||
logical-tree injection and scoped routing.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="settings-group">
|
||||
<h2>Options and Composition lifecycle</h2>
|
||||
<div>
|
||||
<span
|
||||
><strong>Shared revision</strong
|
||||
><small>Changing it triggers beforeUpdate / updated</small></span
|
||||
><b data-testid="compat-revision">{{ revision }}</b>
|
||||
</div>
|
||||
<div class="compat-inline-actions">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="compat-update-probes"
|
||||
@click="updateProbes"
|
||||
>
|
||||
Update props
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="compat-toggle-probes"
|
||||
@click="toggleProbeMount"
|
||||
>
|
||||
{{ probesMounted ? "Unmount probes" : "Mount probes" }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="probesMounted" class="compat-probe-grid">
|
||||
<CompatibilityOptionsProbe
|
||||
:instance-name="`Options ${instanceId}`"
|
||||
:revision="revision"
|
||||
/>
|
||||
<CompatibilityCompositionProbe
|
||||
:instance-name="`Composition ${instanceId}`"
|
||||
:revision="revision"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settings-group">
|
||||
<h2>KeepAlive</h2>
|
||||
<div>
|
||||
<span
|
||||
><strong>Current cached child</strong
|
||||
><small>Switch away and back after incrementing</small></span
|
||||
><b>{{ keepAliveVariant }}</b>
|
||||
</div>
|
||||
<div class="compat-inline-actions">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="compat-switch-keepalive"
|
||||
@click="switchKeptInstance"
|
||||
>
|
||||
Switch to {{ keepAliveVariant === "A" ? "B" : "A" }}
|
||||
</button>
|
||||
</div>
|
||||
<KeepAlive>
|
||||
<CompatibilityKeepAliveProbe
|
||||
:key="keepAliveVariant"
|
||||
:name="keepAliveVariant"
|
||||
/>
|
||||
</KeepAlive>
|
||||
</section>
|
||||
|
||||
<section class="settings-group">
|
||||
<h2>Transition</h2>
|
||||
<div>
|
||||
<span
|
||||
><strong>CSS transition target</strong
|
||||
><small>Hooks are recorded in the event journal</small></span
|
||||
><b>{{ transitionVisible ? "shown" : "removed" }}</b>
|
||||
</div>
|
||||
<div class="compat-inline-actions">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="compat-toggle-transition"
|
||||
@click="transitionVisible = !transitionVisible"
|
||||
>
|
||||
Toggle transition
|
||||
</button>
|
||||
</div>
|
||||
<Transition
|
||||
name="compat-fade"
|
||||
@before-enter="recordTransition('before-enter')"
|
||||
@after-enter="recordTransition('after-enter')"
|
||||
@before-leave="recordTransition('before-leave')"
|
||||
@after-leave="recordTransition('after-leave')"
|
||||
>
|
||||
<article
|
||||
v-if="transitionVisible"
|
||||
class="compat-transition-card"
|
||||
data-testid="compat-transition-card"
|
||||
>
|
||||
Transition child is mounted
|
||||
</article>
|
||||
</Transition>
|
||||
</section>
|
||||
|
||||
<section class="settings-group">
|
||||
<h2>Teleport</h2>
|
||||
<div>
|
||||
<span
|
||||
><strong>Body-level overlay</strong
|
||||
><small
|
||||
>Automatically hidden when this native view is inactive</small
|
||||
></span
|
||||
><b>{{ teleportOpen ? "armed" : "closed" }}</b>
|
||||
</div>
|
||||
<div class="compat-inline-actions">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="compat-open-teleport"
|
||||
@click="teleportOpen = true"
|
||||
>
|
||||
Open teleported overlay
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Teleport to="body">
|
||||
<Transition name="compat-fade">
|
||||
<div
|
||||
v-if="teleportOpen && nativeVisible"
|
||||
class="compat-teleport"
|
||||
data-testid="compat-teleport-overlay"
|
||||
@click.self="teleportOpen = false"
|
||||
>
|
||||
<section
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="compat-teleport-title"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Close teleported overlay"
|
||||
@click="teleportOpen = false"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<h2 id="compat-teleport-title">Teleported route content</h2>
|
||||
<CompatibilityInjectProbe location="teleport" />
|
||||
</section>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
|
||||
<section class="settings-group">
|
||||
<h2>Suspense</h2>
|
||||
<div>
|
||||
<span
|
||||
><strong>Async setup request</strong
|
||||
><small>Fallback remains for 750 ms</small></span
|
||||
><b>#{{ suspenseRequest }}</b>
|
||||
</div>
|
||||
<div class="compat-inline-actions">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="compat-reload-suspense"
|
||||
@click="reloadSuspense"
|
||||
>
|
||||
Reload async child
|
||||
</button>
|
||||
</div>
|
||||
<Suspense
|
||||
:key="suspenseRequest"
|
||||
:timeout="0"
|
||||
@pending="recordCompatibilityEvent('Suspense', 'pending')"
|
||||
@fallback="recordCompatibilityEvent('Suspense', 'fallback')"
|
||||
@resolve="recordCompatibilityEvent('Suspense', 'resolve')"
|
||||
>
|
||||
<CompatibilityAsyncProbe :request-id="suspenseRequest" />
|
||||
<template #fallback>
|
||||
<article
|
||||
class="compat-probe compat-probe--loading"
|
||||
data-testid="compat-suspense-fallback"
|
||||
>
|
||||
<span class="lab-spinner" />
|
||||
<div>
|
||||
<strong>Suspense fallback</strong>
|
||||
<p>Waiting for async setup…</p>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
</Suspense>
|
||||
</section>
|
||||
|
||||
<section id="event-log" class="settings-group compat-event-section">
|
||||
<div class="compat-event-heading">
|
||||
<span
|
||||
><strong>Lifecycle event journal</strong
|
||||
><small>{{ eventCount }} retained events · newest first</small></span
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="compat-clear-events"
|
||||
@click="clearCompatibilityEvents"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
<ol class="compat-event-log" data-testid="compat-event-log">
|
||||
<li v-for="event in compatibilityLab.events" :key="event.id">
|
||||
<time>{{ event.timestamp }}</time>
|
||||
<span
|
||||
><strong>{{ event.source }}</strong
|
||||
><code>{{ event.hook }}</code></span
|
||||
>
|
||||
<small>{{ event.detail }}</small>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
Reference in New Issue
Block a user