Reworks the active-block marker per feedback (it changed colour by depth, shifted right for nested blocks, and showed nothing on load): - New `words-active-rail.svelte`: a single vertical bar in the gutter whose top/height track the CLICKED block, re-measured on activation / render / scroll. It is FRAME-relative at a fixed gutter column (CSS `--_words-content-px - 1.4rem`), so it never shifts right for nested blocks, and uses ONE fixed colour (`--color-primary-solid`). - Removed the block-anchored `[data-words-active]::before` rail (block-relative → shifted; depth-coloured → changed colour). - `words-activate.svelte`: there is now ALWAYS an active block — on first load it seeds the first top-level block, so the inspector + rail have a target instead of "nothing selected". Verified: on load the first block is active and the rail shows at a fixed 27px gutter column, 3px, single colour, height = active block. Check clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>active-uix
parent
986d61bde6
commit
dd7d67d2a1
@ -0,0 +1,55 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* Active-block rail — ONE vertical bar in the left gutter that tracks the
|
||||||
|
* CLICKED (active) block. Frame-relative + a fixed gutter column (CSS), so
|
||||||
|
* it never shifts right for nested blocks, and a single fixed colour. Its
|
||||||
|
* top/height follow the active block, re-measured on activation / render /
|
||||||
|
* scroll. Hidden when the active block is scrolled out of view.
|
||||||
|
*/
|
||||||
|
import type { ActiveDom } from '$adom';
|
||||||
|
import type { ProviderSnippetProps } from '$soma/components/words';
|
||||||
|
|
||||||
|
let {
|
||||||
|
api,
|
||||||
|
content,
|
||||||
|
dom
|
||||||
|
}: {
|
||||||
|
api: ProviderSnippetProps;
|
||||||
|
content: HTMLElement;
|
||||||
|
dom: ActiveDom;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
const frame = $derived(content.closest('[data-words]') as HTMLElement | null);
|
||||||
|
let rail = $state<{ top: number; height: number } | null>(null);
|
||||||
|
|
||||||
|
function measure() {
|
||||||
|
const el = frame ? content.querySelector('[data-words-active]') : null;
|
||||||
|
if (!frame || !(el instanceof HTMLElement)) {
|
||||||
|
rail = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const br = el.getBoundingClientRect();
|
||||||
|
const fr = frame.getBoundingClientRect();
|
||||||
|
const cr = content.getBoundingClientRect();
|
||||||
|
// Hide when the active block is scrolled out of the content viewport.
|
||||||
|
if (br.bottom < cr.top + 2 || br.top > cr.bottom - 2) {
|
||||||
|
rail = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rail = { top: br.top - fr.top, height: br.height };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-measure when the active block changes or the doc re-renders (typing
|
||||||
|
// resizes a block; `{@html}` recreates its element).
|
||||||
|
$effect(() => {
|
||||||
|
void api.activeBlockId;
|
||||||
|
void api.html;
|
||||||
|
dom.raf(measure, content);
|
||||||
|
});
|
||||||
|
// Re-measure on internal content scroll (the rail is frame-relative).
|
||||||
|
$effect(() => dom.listen(content, 'scroll', measure, { capture: true }));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if rail}
|
||||||
|
<div data-words-active-rail style="top: {rail.top}px; height: {rail.height}px;"></div>
|
||||||
|
{/if}
|
||||||
Loading…
Reference in new issue