feat(writekit): a way out of atoms, and a slash menu that behaves
Publish to NPM / Check version changes and publish (push) Successful in 9m58s
Publish to NPM / Check version changes and publish (push) Successful in 9m58s
Two gaps an author hits within the first minute of using atom blocks: - there was no way to add a paragraph after a non-text block. Enter on a selected atom now starts a paragraph below it (exitAtom, chained before splitBlock), and a click on the root's padding below a trailing atom does the same — ends-in-text just places the caret at the end - the slash menu ignored its own overflow: keyboard navigation walked the highlight out of view (now scrollIntoView nearest), and a background wheel scroll tore the menu off its caret anchor (now prevented outside the menu; scrolling the list itself stays native) - BlockMeta gains `description`; the menu shows a detail pane beside the list with the highlighted item's description, replaceable wholesale via the new #preview slot. Headless stays headless: the pane is unstyled text and appears only when there is a description or a slot. Preset blocks are all described Both floating menus (slash, bubble) are re-layered to the combobox convention: PopperRoot provides the positioning context OUTSIDE a bare Portal, which resolves its target from the ConfigProvider's teleportTarget — the previous hardcoded to="body" was overriding the app's configured target. A Combobox itself is the wrong base here on purpose: its keyboard lives on ComboboxInput, while a suggestion menu must leave focus in the contenteditable — the editor is the input. writekit 0.0.3. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -134,3 +134,57 @@ describe('WritekitRoot (single contenteditable)', () => {
|
||||
expect(hosts[0]!.textContent).toBe('foobar');
|
||||
});
|
||||
});
|
||||
|
||||
describe('writing after a trailing atom', () => {
|
||||
it('a click below the last block starts a paragraph when the doc ends in an atom', async () => {
|
||||
const registry = createDefaultRegistry();
|
||||
const writekit = createWritekit({
|
||||
state: createWritekitState({
|
||||
registry,
|
||||
doc: createDoc([para('a', 'text'), createNode('divider', { id: 'd' })]),
|
||||
}),
|
||||
});
|
||||
render(WritekitRoot, { props: { writekit, platform: 'mac' } });
|
||||
await nextTick();
|
||||
|
||||
const root = document.querySelector('[data-writekit-content]') as HTMLElement;
|
||||
root.style.paddingBottom = '120px';
|
||||
const rect = root.getBoundingClientRect();
|
||||
|
||||
root.dispatchEvent(new PointerEvent('pointerdown', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
clientX: rect.left + 10,
|
||||
clientY: rect.bottom - 10,
|
||||
}));
|
||||
await nextTick();
|
||||
|
||||
expect(writekit.state.doc.content.map(block => block.type)).toEqual(['paragraph', 'divider', 'paragraph']);
|
||||
expect(writekit.state.selection.kind).toBe('text');
|
||||
});
|
||||
|
||||
it('a click below the last block just places the caret when it is text', async () => {
|
||||
const registry = createDefaultRegistry();
|
||||
const writekit = createWritekit({
|
||||
state: createWritekitState({ registry, doc: createDoc([para('a', 'text')]) }),
|
||||
});
|
||||
render(WritekitRoot, { props: { writekit, platform: 'mac' } });
|
||||
await nextTick();
|
||||
|
||||
const root = document.querySelector('[data-writekit-content]') as HTMLElement;
|
||||
root.style.paddingBottom = '120px';
|
||||
const rect = root.getBoundingClientRect();
|
||||
|
||||
root.dispatchEvent(new PointerEvent('pointerdown', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
clientX: rect.left + 10,
|
||||
clientY: rect.bottom - 10,
|
||||
}));
|
||||
await nextTick();
|
||||
|
||||
expect(writekit.state.doc.content).toHaveLength(1);
|
||||
const sel = writekit.state.selection;
|
||||
expect(sel.kind === 'text' && sel.focus.offset).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user