You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/web/routes/uix/components/virtual-grid/+page.svelte

345 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<script lang="ts">
import {
VirtualGrid,
type VirtualGridSize,
type VirtualGridVariant,
type VirtualGridColor,
type VirtualGridProviderSnippetProps
} from '$uix/eidos/components/virtual-grid';
import { Button } from '$uix/eidos/components/button';
import { compileMorfo } from '$uix/morfo';
import { virtualGridMorfo } from '@/uix/morfo/components/virtual-grid';
import { getActiveUix } from '$active-uix';
const uix = getActiveUix();
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
let tab = $state<Tab>('live');
let rowCount = $state(1000);
let columnCount = $state(50);
let rowSize = $state(40);
let columnSize = $state(120);
let size = $state<VirtualGridSize>('md');
let variant = $state<VirtualGridVariant>('surface');
let color = $state<VirtualGridColor>('primary');
let block = $state(true);
const compiled = compileMorfo(virtualGridMorfo);
const partsList = $derived([...compiled.parts.byKebab.values()]);
const events = $derived([...compiled.actions.byName.values()]);
let providerSnippetProps: VirtualGridProviderSnippetProps | null = $state(null);
function scrollToRandom() {
const r = Math.floor(Math.random() * rowCount);
const c = Math.floor(Math.random() * columnCount);
providerSnippetProps?.scrollToCell(r, c, { rowAlign: 'center', columnAlign: 'center', behavior: 'smooth' });
}
function scrollToCorner() {
providerSnippetProps?.scrollToCell(rowCount - 1, columnCount - 1, { rowAlign: 'end', columnAlign: 'end' });
}
const eidosSnippet = $derived(
[
"<script lang='ts'>",
" import { VirtualGrid } from '$uix/eidos/components/virtual-grid';",
'</' + 'script>',
'',
'<VirtualGrid',
` rowCount={${rowCount}}`,
` columnCount={${columnCount}}`,
` rowSize={${rowSize}}`,
` columnSize={${columnSize}}`,
size !== 'md' && ` size="${size}"`,
variant !== 'surface' && ` variant="${variant}"`,
color !== 'primary' && ` color="${color}"`,
'>',
' {#snippet children({ virtualCells, totalWidth, totalHeight })}',
' <VirtualGrid.Viewport style="height: 24rem;">',
' <div style="position: relative; width: {totalWidth}px; height: {totalHeight}px;">',
' {#each virtualCells as c (c.key)}',
' <VirtualGrid.Cell {...c}>',
' R{c.rowIndex} · C{c.columnIndex}',
' </VirtualGrid.Cell>',
' {/each}',
' </div>',
' </VirtualGrid.Viewport>',
' {/snippet}',
'</VirtualGrid>'
]
.filter(Boolean)
.join('\n')
);
</script>
<div data-uix-canvas-inner>
<header>
<div data-uix-eyebrow>Data · VirtualGrid</div>
<h1 data-uix-page-title>VirtualGrid</h1>
<p data-uix-page-lede>
2D windowed rendering — virtualises rows AND columns. Renders
the cells inside the visible rectangle plus row/column
overscan and keeps DOM size constant regardless of total cell
count. Use for large data tables (without the
<code>Table</code> machinery), thumbnail grids, spreadsheets.
</p>
<div data-uix-page-meta>
<span data-uix-meta-pill>
<span data-uix-meta-key>parts</span>{compiled.parts.order.length}
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>cells</span> {(rowCount * columnCount).toLocaleString()}
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>rows</span> {rowCount.toLocaleString()}
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>cols</span> {columnCount.toLocaleString()}
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>cellSize</span> {rowSize}×{columnSize}px
</span>
</div>
</header>
<div data-uix-stage>
<div data-uix-stage-area style="padding: var(--uix-space-3); display: flex; justify-content: center;">
<div style="inline-size: min(48rem, 100%);">
<VirtualGrid
{rowCount}
{columnCount}
{rowSize}
{columnSize}
{size}
{variant}
{color}
{block}
>
{#snippet children(snippetProps)}
{@const { virtualCells, totalWidth, totalHeight } = snippetProps}
{(providerSnippetProps = snippetProps, '')}
<VirtualGrid.Viewport style="block-size: 24rem;">
<div style="position: relative; inline-size: {totalWidth}px; block-size: {totalHeight}px;">
{#each virtualCells as c (c.key)}
<VirtualGrid.Cell
rowIndex={c.rowIndex}
columnIndex={c.columnIndex}
rowStart={c.rowStart}
columnStart={c.columnStart}
rowSize={c.rowSize}
columnSize={c.columnSize}
>
<div style="display: flex; align-items: center; justify-content: center; block-size: 100%; padding: var(--uix-space-1) var(--uix-space-2); border-block-end: 1px solid var(--color-border-default); border-inline-end: 1px solid var(--color-border-default); font-size: var(--font-size-xs); background: {(c.rowIndex + c.columnIndex) % 2 === 0 ? 'var(--color-surface-default)' : 'var(--color-surface-subtle)'};">
R{c.rowIndex}·C{c.columnIndex}
</div>
</VirtualGrid.Cell>
{/each}
</div>
</VirtualGrid.Viewport>
{/snippet}
</VirtualGrid>
</div>
</div>
<div data-uix-stage-trace>
<span data-uix-stage-trace-key>note</span>
<span>scroll in both axes. <code>scrollToCell</code> in the snippet props lets you jump anywhere.</span>
</div>
</div>
<div data-uix-tabs role="tablist">
<button data-uix-tab data-active={tab === 'live'} onclick={() => (tab = 'live')}>Live</button>
<button data-uix-tab data-active={tab === 'api'} onclick={() => (tab = 'api')}>
API <span data-uix-tab-count>3</span>
</button>
<button data-uix-tab data-active={tab === 'morfo'} onclick={() => (tab = 'morfo')}>
<span data-uix-layer-badge="morfo">morfo</span>
<span data-uix-tab-count>{partsList.length}p · {events.length}e</span>
</button>
<button data-uix-tab data-active={tab === 'sema'} onclick={() => (tab = 'sema')}>
<span data-uix-layer-badge="sema">sema</span>
<span data-uix-tab-count>{events.length}</span>
</button>
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button>
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
</div>
{#if tab === 'live'}
<section data-uix-section>
<h2 data-uix-section-title>Controls</h2>
<p data-uix-section-desc>v1 is fixed-size only (consistent <code>rowSize</code> + <code>columnSize</code>). Dynamic sizes in 2D require attributing measured deltas to row vs column — a fundamentally ambiguous problem most virtualisers sidestep.</p>
<div data-uix-subsection-head>
<span data-uix-layer-badge="soma">soma</span> data + sizing
</div>
<div data-uix-controls>
<label data-uix-control>
<span data-uix-control-label>rowCount</span>
<span data-uix-chips role="radiogroup">
{#each [100, 1_000, 10_000] as n (n)}
<button data-uix-chip data-active={rowCount === n} onclick={() => (rowCount = n)}>{n.toLocaleString()}</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>columnCount</span>
<span data-uix-chips role="radiogroup">
{#each [10, 50, 200] as n (n)}
<button data-uix-chip data-active={columnCount === n} onclick={() => (columnCount = n)}>{n}</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>rowSize</span>
<span data-uix-chips role="radiogroup">
{#each [32, 40, 64] as n (n)}
<button data-uix-chip data-active={rowSize === n} onclick={() => (rowSize = n)}>{n}px</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>columnSize</span>
<span data-uix-chips role="radiogroup">
{#each [80, 120, 200] as n (n)}
<button data-uix-chip data-active={columnSize === n} onclick={() => (columnSize = n)}>{n}px</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>imperative</span>
<span style="display: flex; gap: var(--uix-space-1);">
<Button size="sm" variant="ghost" onclick={scrollToRandom}>Scroll to random</Button>
<Button size="sm" variant="ghost" onclick={scrollToCorner}>Scroll to corner</Button>
</span>
</label>
</div>
<div data-uix-subsection-head>
<span data-uix-layer-badge="eidos">eidos</span> visual props
</div>
<div data-uix-controls>
<label data-uix-control>
<span data-uix-control-label>variant</span>
<span data-uix-chips role="radiogroup">
{#each ['surface', 'outline', 'ghost'] as v (v)}
<button data-uix-chip data-active={variant === v} onclick={() => (variant = v as VirtualGridVariant)}>{v}</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>size</span>
<span data-uix-chips role="radiogroup">
{#each ['xs', 'sm', 'md', 'lg', 'xl'] as s (s)}
<button data-uix-chip data-active={size === s} onclick={() => (size = s as VirtualGridSize)}>{s}</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>color</span>
<span data-uix-chips role="radiogroup">
{#each ['primary', 'secondary', 'neutral', 'affirm', 'fulfill', 'risk', 'threat', 'loss'] as c (c)}
<button data-uix-chip data-active={color === c} onclick={() => (color = c as VirtualGridColor)}>{c}</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>block</span>
<span data-uix-switch>
<input type="checkbox" bind:checked={block} />
<span data-uix-switch-label>{block ? 'on' : 'off'}</span>
</span>
</label>
</div>
<div data-uix-code>
<div data-uix-code-head>
<span data-uix-layer-badge="eidos">eidos</span>
<span>visual · VirtualGrid + Viewport + Cell</span>
<span data-uix-code-lang>svelte</span>
</div>
<pre><code>{eidosSnippet}</code></pre>
</div>
</section>
{/if}
{#if tab === 'api'}
<section data-uix-section>
<h2 data-uix-section-title>API reference</h2>
<p data-uix-section-desc>3 parts: <code>Provider · Viewport · Cell</code>. v1 fixed-size only.</p>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
<tbody>
<tr><td class="name">rowCount</td><td class="type">number</td><td class="default empty">required</td><td>Total rows.</td></tr>
<tr><td class="name">columnCount</td><td class="type">number</td><td class="default empty">required</td><td>Total columns.</td></tr>
<tr><td class="name">rowSize</td><td class="type">number</td><td class="default">40</td><td>Fixed row height (px).</td></tr>
<tr><td class="name">columnSize</td><td class="type">number</td><td class="default">120</td><td>Fixed column width (px).</td></tr>
<tr><td class="name">rowOverscan</td><td class="type">number</td><td class="default">2</td><td>Extra rows rendered above/below.</td></tr>
<tr><td class="name">columnOverscan</td><td class="type">number</td><td class="default">2</td><td>Extra columns rendered left/right.</td></tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Snippet props (children)</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Key</th><th>Type</th><th>Description</th></tr></thead>
<tbody>
<tr><td class="name">virtualCells</td><td class="type">VirtualCell[]</td><td>Cells in the visible rect + overscan.</td></tr>
<tr><td class="name">totalWidth</td><td class="type">number</td><td>Sum of column sizes.</td></tr>
<tr><td class="name">totalHeight</td><td class="type">number</td><td>Sum of row sizes.</td></tr>
<tr><td class="name">scrollToCell</td><td class="type">fn</td><td>Imperative scroll to a (row, col) cell.</td></tr>
<tr><td class="name">isEmpty</td><td class="type">boolean</td><td><code>rowCount === 0 || columnCount === 0</code>.</td></tr>
</tbody>
</table>
</div>
</section>
{/if}
{#if tab === 'morfo'}
<section data-uix-section>
<h2 data-uix-section-title><span data-uix-layer-badge="morfo">morfo</span> · declarative contract</h2>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Part</th><th>Marker</th><th>Element</th><th>Role</th><th>Optional</th></tr></thead>
<tbody>
{#each partsList as part (part.kebab)}
<tr>
<td class="name">{part.kebab}</td>
<td><code data-uix-part-marker>[{part.marker}]</code></td>
<td class="type">&lt;{part.defaultElement}&gt;</td>
<td class="type">{part.role ?? '—'}</td>
<td class="default">{part.optional ? 'yes' : 'no'}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</section>
{/if}
{#if tab === 'sema'}
<section data-uix-section>
<h2 data-uix-section-title><span data-uix-layer-badge="sema">sema</span> · events</h2>
<p data-uix-section-desc>VirtualGrid is a pure performance primitive — no sema events. Compose with selection-aware grids when needed.</p>
</section>
{/if}
{#if tab === 'recipe'}
<section data-uix-section>
<h2 data-uix-section-title>Eidos recipe</h2>
<p data-uix-section-desc>Selectors at <code>src/uix/eidos/components/virtual-grid/virtual-grid.css</code>. Eidos paints wrapper chrome only — Cell positioning comes from soma inline styles.</p>
</section>
{/if}
{#if tab === 'a11y'}
<section data-uix-section>
<h2 data-uix-section-title>Accessibility</h2>
<p data-uix-section-desc>VirtualGrid is a primitive — no implicit role on the container. Use <code>role="grid"</code> + proper aria attrs in the consumer when building tabular UIs, or compose with <code>TreeGrid</code> / <code>Table</code> for ARIA-complete tables.</p>
</section>
{/if}
</div>

Powered by TurnKey Linux.