words: preview visible cue + gutter active-block tint + no-caret command guard

Three issues found while testing the menu bar:

- Preview "did nothing": clicking Preview does switch to read-only
  (contenteditable=false), but a read-only surface looks identical to an
  editable one, so the change was invisible. Now `[data-mode='preview']`
  collapses the bar's formatting groups, leaving just the view controls
  (inspector + Edit/Preview) — the bar visibly simplifies.
- Gutter active block: the dashed outline read poorly. Replace it with a
  faint primary-tinted fill + a 3px accent bar on the left edge (toward the
  grip), so the block the handle acts on is obvious.
- Bold / insert "don't work" with no caret: a selection-dependent command
  fired from the menu bar (outside the editable) no-op'd when the editor
  was never focused — `toggleMark` bailed on a null selection, and
  `insertBlockOfType` silently fell back to the last block. Add a TipTap
  `.focus()`-style guard `ensureCaret()` in the provider: when `selection`
  is null it drops a collapsed caret at the document start, so marks arm
  (stored mark → next typed text) and inserts land predictably after the
  first block. Wired into `runCommandName` (marks / align) and
  `insertBlockOfType`. The provider freezes the selection across blur, so
  this only fires on a never-focused editor.

Verified in-browser: Preview hides the format groups (toggle stays); the
hovered block shows the tinted fill + accent; a fresh editor's Bold now
arms + places a caret. Words tests 477/477 pass; 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 fff17c58dc
commit be15bbe4f6

@ -124,6 +124,13 @@
color: var(--color-primary-solid);
background: color-mix(in srgb, var(--color-primary-solid) 12%, transparent);
}
/* Preview is read-only — the surface alone looks identical to edit, so make
the switch obvious: collapse the formatting groups, leaving the bar as just
the view controls (inspector toggle + Edit/Preview). */
[data-words][data-mode='preview'] [data-words-menubar-group] {
display: none;
}
/* Park the docked inspector panels below the bar so they don't cover it. */
[data-words]:has([data-words-menubar]) [data-words-sidebar],
[data-words]:has([data-words-menubar]) [data-words-drawer-panel] {
@ -172,13 +179,16 @@
transform: translateX(1px) scale(1.18);
}
/* dashed outline emphasising the block the handle acts on */
/* Active-block highlight while the handle acts on a block: a faint tinted
fill + a solid accent bar on the left edge (toward the gutter grip). Reads
as "this is the block" far better than the old dashed outline. */
[data-words-block-outline] {
position: absolute;
z-index: 1;
pointer-events: none;
border: 1px dashed var(--words-border-hover);
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);
}
@keyframes words-outline-in {

@ -26,6 +26,7 @@ import type {
} from './types';
import { comparePath, type WordsPath } from './engine/path';
import {
createCollapsedSelection,
isCollapsedSelection,
normalizeRange,
type WordsPoint,
@ -747,6 +748,9 @@ export class WordsProvider {
return;
}
// A mark / block command from a surface outside the editable (toolbar)
// needs a caret to act on — arm one if the editor was never focused.
this.ensureCaret();
const command = this.commandFromName(name, opts.level);
if (!command) return;
const changed = this.applyCommand(command);
@ -1506,6 +1510,19 @@ export class WordsProvider {
return true;
}
/**
* TipTap's `.chain().focus()` equivalent: a selection-dependent command
* invoked from a surface OUTSIDE the editable (the menu bar, a panel)
* needs a caret to act on. The provider freezes the selection across blur,
* so this only fires on a never-focused editor — it drops a collapsed
* caret at the document start so marks / inserts apply there instead of
* silently no-op'ing. No-op if a selection already exists.
*/
private ensureCaret() {
if (this.selection) return;
this.setSelectionPublic(createCollapsedSelection([0, 0], 0));
}
private isInsideWordsTool(element: HTMLElement): boolean {
// Internal-tool predicate for the focus scope. Any tool that
// the user clicks WITHOUT intending to leave the editor must
@ -1714,6 +1731,9 @@ export class WordsProvider {
* `toggleList`. Caret lands at the start of the new block.
*/
insertBlockOfType(id: WordsSlashCommandId, target?: HTMLElement): boolean {
// With no caret the insert would silently fall back to the last block;
// arm a caret at the doc start so a toolbar insert lands predictably.
this.ensureCaret();
const sel = this.selection;
const doc = this.history.present.document;
const currentIdx =

Loading…
Cancel
Save

Powered by TurnKey Linux.