Skip to content

Commit d9eaab2

Browse files
ellatrixclaude
andcommitted
Keep the typewriter active regardless of the hasSelectedBlock subscription
The typewriter gated its event listeners on a `hasSelectedBlock` store subscription. When the selected block supports an editable root, the writing flow wrapper holds focus and a transient selection update can leave that subscription stale (reporting no selected block) even though a block is selected, so the typewriter never attached its listeners and stopped maintaining the caret position. Attach the listeners unconditionally; `maintainCaretPosition` already no-ops unless the selection is within an editable element. This reverts the earlier test-side workarounds (waiting a frame and polling the caret position), which only masked the inactive typewriter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent c8c26c8 commit d9eaab2

2 files changed

Lines changed: 35 additions & 45 deletions

File tree

packages/block-editor/src/components/typewriter/index.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@ export function useTypewriter() {
2323

2424
return useRefEffect(
2525
( node ) => {
26-
if ( ! hasSelectedBlock ) {
27-
return;
28-
}
29-
26+
// Attach the listeners unconditionally rather than only while a
27+
// block is selected. `maintainCaretPosition` already no-ops unless
28+
// the selection is within an editable element, and gating on the
29+
// `hasSelectedBlock` subscription is unreliable when the selected
30+
// block supports an editable root: the wrapper holds focus and a
31+
// transient selection update can leave the subscription stale,
32+
// leaving the typewriter inactive.
3033
const { ownerDocument } = node;
3134
const { defaultView } = ownerDocument;
3235

test/e2e/specs/editor/various/typewriter.spec.js

Lines changed: 28 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ test.describe( 'Typewriter', () => {
3232
// The page shouldn't be scrolled when it's being filled.
3333
await page.keyboard.press( 'Enter' );
3434

35-
await expect
36-
.poll( () => typewriterUtils.getCaretPosition() )
37-
.toBeGreaterThanOrEqual( initialPosition );
35+
expect(
36+
await typewriterUtils.getCaretPosition()
37+
).toBeGreaterThanOrEqual( initialPosition );
3838

3939
// Create blocks until the typewriter effect kicks in.
4040
while (
@@ -55,9 +55,9 @@ test.describe( 'Typewriter', () => {
5555
// Now the scroll position should be maintained.
5656
await page.keyboard.press( 'Enter' );
5757

58-
await expect
59-
.poll( () => typewriterUtils.getDiff( newPosition ) )
60-
.toBeLessThanOrEqual( BUFFER );
58+
expect(
59+
await typewriterUtils.getDiff( newPosition )
60+
).toBeLessThanOrEqual( BUFFER );
6161

6262
// Type until the text wraps.
6363
while (
@@ -76,17 +76,17 @@ test.describe( 'Typewriter', () => {
7676
await page.keyboard.type( 'a' );
7777
}
7878

79-
await expect
80-
.poll( () => typewriterUtils.getDiff( newPosition ) )
81-
.toBeLessThanOrEqual( BUFFER );
79+
expect(
80+
await typewriterUtils.getDiff( newPosition )
81+
).toBeLessThanOrEqual( BUFFER );
8282

8383
// Pressing backspace will reposition the caret to the previous line.
8484
// Scroll position should be adjusted again.
8585
await page.keyboard.press( 'Backspace' );
8686

87-
await expect
88-
.poll( () => typewriterUtils.getDiff( newPosition ) )
89-
.toBeLessThanOrEqual( BUFFER );
87+
expect(
88+
await typewriterUtils.getDiff( newPosition )
89+
).toBeLessThanOrEqual( BUFFER );
9090

9191
// Should reset scroll position to maintain.
9292
await page.keyboard.press( 'ArrowUp' );
@@ -98,9 +98,9 @@ test.describe( 'Typewriter', () => {
9898
// Should be scrolled to new position.
9999
await page.keyboard.press( 'Enter' );
100100

101-
await expect
102-
.poll( () => typewriterUtils.getDiff( positionAfterArrowUp ) )
103-
.toBeLessThanOrEqual( BUFFER );
101+
expect(
102+
await typewriterUtils.getDiff( positionAfterArrowUp )
103+
).toBeLessThanOrEqual( BUFFER );
104104
} );
105105

106106
test( 'should maintain caret position after scroll', async ( {
@@ -161,9 +161,7 @@ test.describe( 'Typewriter', () => {
161161
);
162162
} );
163163

164-
await expect
165-
.poll( () => typewriterUtils.getDiff( initialPosition ) )
166-
.toBe( 0 );
164+
expect( await typewriterUtils.getDiff( initialPosition ) ).toBe( 0 );
167165
} );
168166

169167
test( 'should maintain caret position after leaving last editable', async ( {
@@ -181,9 +179,9 @@ test.describe( 'Typewriter', () => {
181179
const initialPosition = await typewriterUtils.getCaretPosition();
182180

183181
// Should maintain scroll position.
184-
await expect
185-
.poll( () => typewriterUtils.getDiff( initialPosition ) )
186-
.toBeLessThanOrEqual( BUFFER );
182+
expect(
183+
await typewriterUtils.getDiff( initialPosition )
184+
).toBeLessThanOrEqual( BUFFER );
187185
} );
188186

189187
test( 'should scroll caret into view from the top', async ( {
@@ -245,9 +243,9 @@ test.describe( 'Typewriter', () => {
245243
// Should maintain new caret position.
246244
await page.keyboard.press( 'Enter' );
247245

248-
await expect
249-
.poll( () => typewriterUtils.getDiff( newBottomPosition ) )
250-
.toBeLessThanOrEqual( BUFFER );
246+
expect(
247+
await typewriterUtils.getDiff( newBottomPosition )
248+
).toBeLessThanOrEqual( BUFFER );
251249

252250
await page.keyboard.press( 'Backspace' );
253251

@@ -277,9 +275,9 @@ test.describe( 'Typewriter', () => {
277275
// Should maintain new caret position.
278276
await page.keyboard.press( 'Enter' );
279277

280-
await expect
281-
.poll( () => typewriterUtils.getDiff( newTopPosition ) )
282-
.toBeLessThanOrEqual( BUFFER );
278+
expect(
279+
await typewriterUtils.getDiff( newTopPosition )
280+
).toBeLessThanOrEqual( BUFFER );
283281
} );
284282
} );
285283

@@ -293,20 +291,9 @@ class TypewriterUtils {
293291

294292
async getCaretPosition() {
295293
return await this.#page.evaluate( () => {
296-
// Wait for one animation frame before measuring. When the selected
297-
// block supports an editable root, the writing flow wrapper holds
298-
// focus instead of the block's editable element, so a keystroke
299-
// does not move the browser's focus to the new caret. The
300-
// typewriter's scroll compensation then lands on the next
301-
// animation frame rather than synchronously, so the caret reaches
302-
// its final position one frame later. Without the wrapper this is
303-
// effectively a no-op.
304-
return new Promise( ( resolve ) => {
305-
const view = document.activeElement?.contentWindow ?? window;
306-
view.requestAnimationFrame( () => {
307-
resolve( window.wp.dom.computeCaretRect( view ).y );
308-
} );
309-
} );
294+
return window.wp.dom.computeCaretRect(
295+
document.activeElement?.contentWindow ?? window
296+
).y;
310297
} );
311298
}
312299

0 commit comments

Comments
 (0)