words: active-block marker → the gutter (not the block) + no select in preview

Two fixes from feedback:

- The active/hovered-block marker belonged in the GUTTER, not as an overlay
  on the block. `[data-words-block-outline]` is now a thin (3px) vertical
  accent rail pinned to the gutter column (left margin, just left of where
  the text begins) spanning the block's height — only top/height track the
  block. Replaces the tinted fill that landed on the block content.
- Preview is read-only, but `WordsActivate` (click-to-select → the dotted
  active outline) still ran, so blocks could be selected in preview. Add an
  `enabled` prop (false in preview): no click-activation and the active stamp
  is cleared. `<Words>` passes `enabled={mode === 'edit'}`.

Verified in-browser: the gutter rail sits 8px left of the block text (not
over it), spanning the block height; clicking a block in preview no longer
stamps `data-words-active`. Check clean for the touched files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
active-uix
dev 4 months ago
parent be15bbe4f6
commit 91bab32bc2

@ -28,11 +28,15 @@
let { let {
api, api,
content, content,
dom dom,
enabled = true
}: { }: {
api: ProviderSnippetProps; api: ProviderSnippetProps;
content: HTMLElement; content: HTMLElement;
dom: ActiveDom; dom: ActiveDom;
/** When false (preview / read-only) block activation is off: no
* click-to-select and the active-block outline is cleared. */
enabled?: boolean;
} = $props(); } = $props();
// Build the MATERIALIZED-PATH id of the clicked block: walk the DOM up // Build the MATERIALIZED-PATH id of the clicked block: walk the DOM up
@ -52,7 +56,7 @@
} }
$effect(() => { $effect(() => {
if (!content) return; if (!content || !enabled) return;
const onPointerDown = (e: Event) => { const onPointerDown = (e: Event) => {
const target = e.target as HTMLElement | null; const target = e.target as HTMLElement | null;
if (!target) return; if (!target) return;
@ -76,6 +80,7 @@
// are unique, so we match the LAST segment. The segment count is the // are unique, so we match the LAST segment. The segment count is the
// nesting depth → `data-words-depth`. // nesting depth → `data-words-depth`.
$effect(() => { $effect(() => {
const on = enabled; // re-run (and clear) when activation is turned off
const idPath = api.activeBlockId; const idPath = api.activeBlockId;
// Touch `html` so this effect re-runs after each re-render and // Touch `html` so this effect re-runs after each re-render and
// re-stamps the (freshly recreated) element. // re-stamps the (freshly recreated) element.
@ -88,7 +93,8 @@
prev.removeAttribute('data-words-active'); prev.removeAttribute('data-words-active');
prev.removeAttribute('data-words-depth'); prev.removeAttribute('data-words-depth');
} }
if (!idPath) return; // Preview / read-only: stamps cleared above, never re-applied.
if (!on || !idPath) return;
const segments = idPath.split('/').filter(Boolean); const segments = idPath.split('/').filter(Boolean);
const localId = segments[segments.length - 1]; const localId = segments[segments.length - 1];
if (!localId) return; if (!localId) return;

@ -314,11 +314,11 @@
{#if (show || menuOpen) && rect} {#if (show || menuOpen) && rect}
{#if inGutter || menuOpen} {#if inGutter || menuOpen}
<div <!-- Active-block marker: a vertical accent BAR in the gutter (left
data-words-block-outline margin), spanning the block's height. NOT an overlay on the block
style="top: {rect.top - 3}px; left: {rect.left - 3}px; width: {rect.width + itself — the CSS pins it to the gutter column; only top/height
6}px; height: {rect.height + 6}px;" track the block. -->
></div> <div data-words-block-outline style="top: {rect.top}px; height: {rect.height}px;"></div>
{/if} {/if}
<!-- Center the handle vertically against the block's height: position <!-- Center the handle vertically against the block's height: position
it at the block midpoint and let `transform: translateY(-50%)` it at the block midpoint and let `transform: translateY(-50%)`

@ -179,16 +179,17 @@
transform: translateX(1px) scale(1.18); transform: translateX(1px) scale(1.18);
} }
/* Active-block highlight while the handle acts on a block: a faint tinted /* Active-block marker — a vertical accent rail in the GUTTER (left margin),
fill + a solid accent bar on the left edge (toward the gutter grip). Reads NOT on the block. Only top/height come from the block (set inline); the
as "this is the block" far better than the old dashed outline. */ bar is pinned to the gutter column, just inside where the text begins. */
[data-words-block-outline] { [data-words-block-outline] {
position: absolute; position: absolute;
inset-inline-start: calc(var(--_words-content-px) - 0.7rem);
z-index: 1; z-index: 1;
inline-size: 3px;
border-radius: 2px;
background: color-mix(in srgb, var(--color-primary-solid) 55%, transparent);
pointer-events: none; pointer-events: none;
background: color-mix(in srgb, var(--color-primary-solid) 5%, transparent);
border-radius: var(--words-radius-sm);
box-shadow: inset 3px 0 0 color-mix(in srgb, var(--color-primary-solid) 40%, transparent);
animation: words-outline-in var(--words-transition-duration) var(--words-transition-ease); animation: words-outline-in var(--words-transition-duration) var(--words-transition-ease);
} }
@keyframes words-outline-in { @keyframes words-outline-in {

@ -109,7 +109,7 @@
<WordsBubble {api} /> <WordsBubble {api} />
{/if} {/if}
{#if contentEl} {#if contentEl}
<WordsActivate {api} content={contentEl} {dom} /> <WordsActivate {api} content={contentEl} {dom} enabled={mode === 'edit'} />
{#if mode === 'edit'} {#if mode === 'edit'}
<WordsBlockGutter {api} content={contentEl} {dom} /> <WordsBlockGutter {api} content={contentEl} {dom} />
<WordsBlockDrag {api} content={contentEl} {dom} /> <WordsBlockDrag {api} content={contentEl} {dom} />

Loading…
Cancel
Save

Powered by TurnKey Linux.