From 152f1e51af468ab90b3970df928b386cbf89d106 Mon Sep 17 00:00:00 2001 From: Renato Augusto Gama dos Santos Date: Mon, 15 Jul 2024 17:10:23 -0300 Subject: [PATCH 1/8] AI Proofread: Improve Popover UX --- .../components/breve/features/events.ts | 54 ++++++++++++------- .../components/breve/highlight/index.tsx | 15 ++++-- .../components/breve/highlight/style.scss | 3 +- .../components/breve/store/actions.ts | 15 +----- .../components/breve/store/reducer.ts | 27 +--------- .../components/breve/store/selectors.ts | 9 +--- .../components/breve/types.ts | 13 +++-- 7 files changed, 59 insertions(+), 77 deletions(-) diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts index d4838209f0e8e..89cb09371d470 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts @@ -1,7 +1,7 @@ /** * External dependencies */ -import { dispatch, select } from '@wordpress/data'; +import { dispatch } from '@wordpress/data'; /** * Internal dependencies */ @@ -10,31 +10,47 @@ import features from './index'; /** * Types */ -import type { BreveDispatch, BreveSelect } from '../types'; +import type { BreveDispatch } from '../types'; let highlightTimeout: number; +let anchorTimeout: number; function handleMouseEnter( e: React.MouseEvent ) { - e.stopPropagation(); clearTimeout( highlightTimeout ); - ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).increasePopoverLevel(); - ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setHighlightHover( true ); - ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setPopoverAnchor( e.target ); -} + clearTimeout( anchorTimeout ); -function handleMouseLeave( e: React.MouseEvent ) { - e.stopPropagation(); - ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).decreasePopoverLevel(); + anchorTimeout = setTimeout( () => { + const el = e.target as HTMLElement; + const rect = el.getBoundingClientRect(); + const diff = e.clientY - Math.floor( rect.top ); + const offset = diff < 10 ? 20 : 10; - highlightTimeout = setTimeout( () => { - // If the mouse is still over any highlight, don't hide the popover - const { getPopoverLevel } = select( 'jetpack/ai-breve' ) as BreveSelect; - if ( getPopoverLevel() > 0 ) { - return; - } + ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setHighlightHover( true ); + ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setPopoverAnchor( { + target: e.target as HTMLElement, + virtual: { + getBoundingClientRect() { + return { + top: e.clientY + offset, + left: e.clientX, + bottom: e.clientY, + right: e.clientX, + width: 0, + height: 0, + x: e.clientX, + y: e.clientY, + } as DOMRect; + }, + contextElement: e.target as HTMLElement, + }, + } as unknown as HTMLElement ); + }, 100 ); +} +function handleMouseLeave() { + highlightTimeout = setTimeout( () => { ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setHighlightHover( false ); - }, 50 ); + }, 100 ); } export default function registerEvents( clientId: string ) { @@ -47,8 +63,8 @@ export default function registerEvents( clientId: string ) { if ( items?.length > 0 ) { items.forEach( highlightEl => { - highlightEl?.removeEventListener?.( 'mouseenter', handleMouseEnter ); - highlightEl?.addEventListener?.( 'mouseenter', handleMouseEnter ); + highlightEl?.removeEventListener?.( 'mouseover', handleMouseEnter ); + highlightEl?.addEventListener?.( 'mouseover', handleMouseEnter ); highlightEl?.removeEventListener?.( 'mouseleave', handleMouseLeave ); highlightEl?.addEventListener?.( 'mouseleave', handleMouseLeave ); } ); diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/highlight/index.tsx b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/highlight/index.tsx index 738e648361bcb..07942dc3f2da9 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/highlight/index.tsx +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/highlight/index.tsx @@ -32,11 +32,16 @@ export default function Highlight() { return isHighlightHover || isPopoverHover; }, [] ); - const anchor = useSelect( select => { - return ( select( 'jetpack/ai-breve' ) as BreveSelect ).getPopoverAnchor(); + const { target: anchor, virtual } = useSelect( select => { + return ( + ( select( 'jetpack/ai-breve' ) as BreveSelect ).getPopoverAnchor() ?? { + target: null, + virtual: null, + } + ); }, [] ); - const isPopoverOpen = popoverOpen && anchor; + const isPopoverOpen = popoverOpen && virtual; const selectedFeatured = anchor ? ( anchor as HTMLElement )?.getAttribute?.( 'data-type' ) : null; @@ -60,10 +65,10 @@ export default function Highlight() { <> { isPopoverOpen && ( - ).reduceRight( ( acc, anchor ) => acc ?? anchor, null ); + return state?.popover?.anchor ?? null; } export function getPopoverLevel( state: BreveState ) { diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/types.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/types.ts index 33171a1426311..e314e5f02f4e9 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/types.ts +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/types.ts @@ -1,12 +1,19 @@ export type BreveControls = () => React.JSX.Element; +type Anchor = { + target: HTMLElement; + virtual: { + getBoundingClientRect: () => DOMRect; + contextElement: HTMLElement; + }; +}; + export type BreveState = { popover?: { isHighlightHover?: boolean; isPopoverHover?: boolean; - anchors?: Array< HTMLElement | EventTarget >; + anchor?: Anchor; level?: number; - frozenAnchor?: HTMLElement | EventTarget; }; configuration?: { enabled?: boolean; @@ -17,7 +24,7 @@ export type BreveState = { export type BreveSelect = { isHighlightHover: () => boolean; isPopoverHover: () => boolean; - getPopoverAnchor: () => HTMLElement | EventTarget; + getPopoverAnchor: () => Anchor; getPopoverLevel: () => number; isProofreadEnabled: () => boolean; isFeatureEnabled: ( feature: string ) => boolean; From 77f85a97d4211b49e449c5c7a4d27eff95708a73 Mon Sep 17 00:00:00 2001 From: Renato Augusto Gama dos Santos Date: Mon, 15 Jul 2024 17:11:00 -0300 Subject: [PATCH 2/8] changelog --- .../plugins/jetpack/changelog/update-ai-proofread-popover-ux | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/plugins/jetpack/changelog/update-ai-proofread-popover-ux diff --git a/projects/plugins/jetpack/changelog/update-ai-proofread-popover-ux b/projects/plugins/jetpack/changelog/update-ai-proofread-popover-ux new file mode 100644 index 0000000000000..a321157afcce1 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-ai-proofread-popover-ux @@ -0,0 +1,4 @@ +Significance: minor +Type: other + +Improve Popover UX on AI Proofread From 0c5702f2ae1b8f2c67340ba2563b66c1a2c9c991 Mon Sep 17 00:00:00 2001 From: Renato Augusto Gama dos Santos Date: Mon, 15 Jul 2024 17:14:37 -0300 Subject: [PATCH 3/8] AI Proofread: Improve offset --- .../ai-assistant-plugin/components/breve/features/events.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts index 89cb09371d470..d27c674630505 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts @@ -23,7 +23,7 @@ function handleMouseEnter( e: React.MouseEvent ) { const el = e.target as HTMLElement; const rect = el.getBoundingClientRect(); const diff = e.clientY - Math.floor( rect.top ); - const offset = diff < 10 ? 20 : 10; + const offset = diff === 0 ? 20 : 10; ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setHighlightHover( true ); ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setPopoverAnchor( { From 0dc5897e44a6609bf8714a130dbc404f143d1587 Mon Sep 17 00:00:00 2001 From: Renato Augusto Gama dos Santos Date: Mon, 15 Jul 2024 17:15:55 -0300 Subject: [PATCH 4/8] :AI Proofread: Improve offset --- .../ai-assistant-plugin/components/breve/features/events.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts index d27c674630505..f0d56517e510e 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts @@ -23,7 +23,7 @@ function handleMouseEnter( e: React.MouseEvent ) { const el = e.target as HTMLElement; const rect = el.getBoundingClientRect(); const diff = e.clientY - Math.floor( rect.top ); - const offset = diff === 0 ? 20 : 10; + const offset = diff === 0 ? 20 : 0; ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setHighlightHover( true ); ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setPopoverAnchor( { From 925485eff98766dcf941c008b809fea3d97fba9f Mon Sep 17 00:00:00 2001 From: Renato Augusto Gama dos Santos Date: Mon, 15 Jul 2024 20:04:46 -0300 Subject: [PATCH 5/8] AI Proofread: Improve offset --- .../ai-assistant-plugin/components/breve/features/events.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts index f0d56517e510e..defb6c9572405 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts @@ -23,7 +23,7 @@ function handleMouseEnter( e: React.MouseEvent ) { const el = e.target as HTMLElement; const rect = el.getBoundingClientRect(); const diff = e.clientY - Math.floor( rect.top ); - const offset = diff === 0 ? 20 : 0; + const offset = diff === 0 ? 10 : 0; ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setHighlightHover( true ); ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setPopoverAnchor( { From e34a425b8ff80391a4d5cecfb0844d242701211d Mon Sep 17 00:00:00 2001 From: Renato Augusto Gama dos Santos Date: Mon, 15 Jul 2024 20:20:17 -0300 Subject: [PATCH 6/8] AI Proofread: Only point to cursor if long sentence --- .../components/breve/features/events.ts | 29 ++++++++++++------- .../components/breve/store/selectors.ts | 4 +-- .../components/breve/types.ts | 6 ++-- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts index defb6c9572405..edce1df097ad5 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts @@ -10,7 +10,7 @@ import features from './index'; /** * Types */ -import type { BreveDispatch } from '../types'; +import type { BreveDispatch, Anchor } from '../types'; let highlightTimeout: number; let anchorTimeout: number; @@ -21,14 +21,17 @@ function handleMouseEnter( e: React.MouseEvent ) { anchorTimeout = setTimeout( () => { const el = e.target as HTMLElement; - const rect = el.getBoundingClientRect(); - const diff = e.clientY - Math.floor( rect.top ); - const offset = diff === 0 ? 10 : 0; + let virtual = el; - ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setHighlightHover( true ); - ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setPopoverAnchor( { - target: e.target as HTMLElement, - virtual: { + const words = el?.innerText?.match?.( /\b\w+\b/g ); + const shouldPointToCursor = words?.length > 3; + + if ( shouldPointToCursor ) { + const rect = el.getBoundingClientRect(); + const diff = e.clientY - Math.floor( rect.top ); + const offset = diff === 0 ? 10 : 0; + + virtual = { getBoundingClientRect() { return { top: e.clientY + offset, @@ -42,8 +45,14 @@ function handleMouseEnter( e: React.MouseEvent ) { } as DOMRect; }, contextElement: e.target as HTMLElement, - }, - } as unknown as HTMLElement ); + } as unknown as HTMLElement; + } + + ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setHighlightHover( true ); + ( dispatch( 'jetpack/ai-breve' ) as BreveDispatch ).setPopoverAnchor( { + target: e.target as HTMLElement, + virtual: virtual, + } as Anchor ); }, 100 ); } diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/selectors.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/selectors.ts index 48906d192aa33..87f83a68d7d9b 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/selectors.ts +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/selectors.ts @@ -1,7 +1,7 @@ /** * Types */ -import type { BreveState } from '../types'; +import type { Anchor, BreveState } from '../types'; // POPOVER @@ -13,7 +13,7 @@ export function isPopoverHover( state: BreveState ) { return state.popover?.isPopoverHover; } -export function getPopoverAnchor( state: BreveState ): HTMLElement | EventTarget | null { +export function getPopoverAnchor( state: BreveState ): Anchor { return state?.popover?.anchor ?? null; } diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/types.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/types.ts index e314e5f02f4e9..b9913ea617b96 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/types.ts +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/types.ts @@ -1,10 +1,10 @@ export type BreveControls = () => React.JSX.Element; -type Anchor = { +export type Anchor = { target: HTMLElement; virtual: { getBoundingClientRect: () => DOMRect; - contextElement: HTMLElement; + contextElement?: HTMLElement; }; }; @@ -34,7 +34,7 @@ export type BreveSelect = { export type BreveDispatch = { setHighlightHover: ( isHover: boolean ) => void; setPopoverHover: ( isHover: boolean ) => void; - setPopoverAnchor: ( anchor: HTMLElement | EventTarget ) => void; + setPopoverAnchor: ( anchor: Anchor ) => void; increasePopoverLevel: () => void; decreasePopoverLevel: () => void; toggleProofread: ( force?: boolean ) => void; From 5b04600513ebd8c50e8f146dfb6b6e9164ba7634 Mon Sep 17 00:00:00 2001 From: Douglas Date: Tue, 16 Jul 2024 16:15:47 -0300 Subject: [PATCH 7/8] change sentence check --- .../ai-assistant-plugin/components/breve/features/events.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts index edce1df097ad5..e1a6af13e6ea3 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts @@ -23,8 +23,7 @@ function handleMouseEnter( e: React.MouseEvent ) { const el = e.target as HTMLElement; let virtual = el; - const words = el?.innerText?.match?.( /\b\w+\b/g ); - const shouldPointToCursor = words?.length > 3; + const shouldPointToCursor = el.getAttribute( 'data-type' ) === 'long-sentences'; if ( shouldPointToCursor ) { const rect = el.getBoundingClientRect(); From deff56ff16eb630018c560882ec63715083f4465 Mon Sep 17 00:00:00 2001 From: Douglas Date: Tue, 16 Jul 2024 16:31:50 -0300 Subject: [PATCH 8/8] fix types --- .../components/breve/features/events.ts | 8 +++++--- .../components/breve/store/selectors.ts | 2 +- .../plugins/ai-assistant-plugin/components/breve/types.ts | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts index e1a6af13e6ea3..143891bd085fd 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/features/events.ts @@ -15,7 +15,7 @@ import type { BreveDispatch, Anchor } from '../types'; let highlightTimeout: number; let anchorTimeout: number; -function handleMouseEnter( e: React.MouseEvent ) { +function handleMouseEnter( e: MouseEvent ) { clearTimeout( highlightTimeout ); clearTimeout( anchorTimeout ); @@ -67,9 +67,11 @@ export default function registerEvents( clientId: string ) { const block = container?.querySelector?.( `#${ id }` ); features.forEach( ( { config } ) => { - const items = block?.querySelectorAll?.( `[data-type='${ config.name }']` ) || []; + const items: NodeListOf< HTMLElement > | undefined = block?.querySelectorAll?.( + `[data-type='${ config.name }']` + ); - if ( items?.length > 0 ) { + if ( items && items?.length > 0 ) { items.forEach( highlightEl => { highlightEl?.removeEventListener?.( 'mouseover', handleMouseEnter ); highlightEl?.addEventListener?.( 'mouseover', handleMouseEnter ); diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/selectors.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/selectors.ts index 87f83a68d7d9b..3ca8adb85aba7 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/selectors.ts +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/store/selectors.ts @@ -13,7 +13,7 @@ export function isPopoverHover( state: BreveState ) { return state.popover?.isPopoverHover; } -export function getPopoverAnchor( state: BreveState ): Anchor { +export function getPopoverAnchor( state: BreveState ): Anchor | null { return state?.popover?.anchor ?? null; } diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/types.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/types.ts index b9913ea617b96..63332f02f4007 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/types.ts +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/breve/types.ts @@ -24,7 +24,7 @@ export type BreveState = { export type BreveSelect = { isHighlightHover: () => boolean; isPopoverHover: () => boolean; - getPopoverAnchor: () => Anchor; + getPopoverAnchor: () => Anchor | null; getPopoverLevel: () => number; isProofreadEnabled: () => boolean; isFeatureEnabled: ( feature: string ) => boolean;