| @@ -1,13 +1,8 @@ | ||
| 1 | 1 | import { __, sprintf } from '@wordpress/i18n'; |
| 2 | +import { useQuickEditStore } from '../state/store'; | |
| 2 | 3 | import { resolveTarget } from './dom'; |
| 3 | -import { | |
| 4 | - askAiTarget, | |
| 5 | - editTarget, | |
| 6 | - hideBar, | |
| 7 | - pillContextFor, | |
| 8 | - showBar, | |
| 9 | -} from './hover-bar'; | |
| 4 | +import { hideBar, isAgentWorking, pinTarget, showBar } from './hover-bar'; | |
| 10 | 5 | |
| 11 | 6 | const SELECTOR = [ |
| 12 | 7 | '[data-extendify-agent-block-id]', |
| 13 | 8 | '[data-extendify-part-block-id]', |
| @@ -34,34 +29,19 @@ | ||
| 34 | 29 | } |
| 35 | 30 | return null; |
| 36 | 31 | }; |
| 37 | 32 | |
| 38 | -const buildAriaLabel = (el, target, { quickEditable, aiAvailable }) => { | |
| 33 | +// Enter only reveals the pills, so announcing "Edit" promises too much. | |
| 34 | +const buildAriaLabel = (el) => { | |
| 39 | 35 | const text = (el.textContent || '').trim().replace(/\s+/g, ' '); |
| 40 | 36 | const snippet = text.length > 60 ? `${text.substring(0, 60)}…` : text; |
| 41 | - // Ask-AI-only blocks (group / columns / media-text) route Enter to the | |
| 42 | - // agent, so announce that action rather than "Edit". | |
| 43 | - if (aiAvailable && !quickEditable) { | |
| 44 | - return snippet | |
| 45 | - ? sprintf( | |
| 46 | - // translators: %s is a snippet of the block's text content. | |
| 47 | - __('Ask AI about "%s"', 'extendify-local'), | |
| 48 | - snippet, | |
| 49 | - ) | |
| 50 | - : __('Ask AI', 'extendify-local'); | |
| 51 | - } | |
| 52 | - const verb = | |
| 53 | - target?.blockType === 'core/image' || target?.blockType === 'core/cover' | |
| 54 | - ? __('Replace image', 'extendify-local') | |
| 55 | - : __('Edit', 'extendify-local'); | |
| 56 | 37 | return snippet |
| 57 | 38 | ? sprintf( |
| 58 | - // translators: %1$s is the action verb (Edit / Replace image), %2$s is a snippet of the block's text content. | |
| 59 | - __('%1$s "%2$s"', 'extendify-local'), | |
| 60 | - verb, | |
| 39 | + // translators: %s is a snippet of the block's text content. | |
| 40 | + __('Editing options for "%s"', 'extendify-local'), | |
| 61 | 41 | snippet, |
| 62 | 42 | ) |
| 63 | - : verb; | |
| 43 | + : __('Editing options', 'extendify-local'); | |
| 64 | 44 | }; |
| 65 | 45 | |
| 66 | 46 | // Skips role="button" on headings/links/landmarks so SR users |
| 67 | 47 | // retain heading/link/landmark navigation. Enter/Space still |
| @@ -98,13 +78,9 @@ | ||
| 98 | 78 | if (!isHeading && !isLink && !isLandmark) { |
| 99 | 79 | el.setAttribute('role', 'button'); |
| 100 | 80 | } |
| 101 | 81 | |
| 102 | - const target = resolveTarget(el); | |
| 103 | - el.setAttribute( | |
| 104 | - 'aria-label', | |
| 105 | - buildAriaLabel(el, target, pillContextFor(target)), | |
| 106 | - ); | |
| 82 | + el.setAttribute('aria-label', buildAriaLabel(el)); | |
| 107 | 83 | el.setAttribute(KB_READY_ATTR, '1'); |
| 108 | 84 | } |
| 109 | 85 | }; |
| 110 | 86 | |
| @@ -133,8 +109,9 @@ | ||
| 133 | 109 | decorate(); |
| 134 | 110 | |
| 135 | 111 | onFocusIn = (e) => { |
| 136 | 112 | if (getSession?.()) return; |
| 113 | + if (isAgentWorking()) return; | |
| 137 | 114 | const el = findTagged(e.target); |
| 138 | 115 | if (!el) return; |
| 139 | 116 | hideBar(); |
| 140 | 117 | showBar(el); |
| @@ -160,13 +137,17 @@ | ||
| 160 | 137 | ) { |
| 161 | 138 | return; |
| 162 | 139 | } |
| 163 | 140 | if (target && !document.body.contains(target)) return; |
| 141 | + // A pinned bar outlives focus; only an outside click or Esc drops it. | |
| 142 | + if (useQuickEditStore.getState().committedSelection) return; | |
| 164 | 143 | const active = document.activeElement; |
| 165 | 144 | if (!active || active === document.body) { |
| 166 | 145 | hideBar(); |
| 167 | 146 | return; |
| 168 | 147 | } |
| 148 | + // Pinning moves focus onto a pill, which lives outside the block. | |
| 149 | + if (active.closest?.('.extendify-quick-edit-bar')) return; | |
| 169 | 150 | if (findTagged(active)) return; |
| 170 | 151 | hideBar(); |
| 171 | 152 | }, 0); |
| 172 | 153 | }; |
| @@ -173,31 +154,17 @@ | ||
| 173 | 154 | |
| 174 | 155 | onActivate = (e) => { |
| 175 | 156 | if (e.key !== 'Enter' && e.key !== ' ') return; |
| 176 | 157 | if (getSession?.()) return; |
| 158 | + if (isAgentWorking()) return; | |
| 177 | 159 | const el = findTagged(e.target); |
| 178 | 160 | if (!el) return; |
| 179 | 161 | // Skip activation when the keystroke came from a child with |
| 180 | 162 | // its own action (e.g. a link inside a nav item). |
| 181 | 163 | if (e.target !== el) return; |
| 182 | - const target = resolveTarget(el); | |
| 183 | - if (!target?.blockType) return; | |
| 164 | + if (!resolveTarget(el)?.blockType) return; | |
| 184 | 165 | e.preventDefault(); |
| 185 | - const { quickEditable, aiAvailable } = pillContextFor(target); | |
| 186 | - // Quick-editable blocks open the inline editor (picker types need the | |
| 187 | - // bar mounted first so the dropdown can anchor to it). | |
| 188 | - if (quickEditable) { | |
| 189 | - showBar(el); | |
| 190 | - editTarget(target); | |
| 191 | - return; | |
| 192 | - } | |
| 193 | - // Ask-AI-only blocks (group / columns / media-text) have no editor to | |
| 194 | - // open; route Enter straight to the agent like the Ask AI pill does. | |
| 195 | - if (aiAvailable) { | |
| 196 | - askAiTarget(el); | |
| 197 | - return; | |
| 198 | - } | |
| 199 | - showBar(el); | |
| 166 | + pinTarget(el); | |
| 200 | 167 | }; |
| 201 | 168 | |
| 202 | 169 | document.addEventListener('focusin', onFocusIn, true); |
| 203 | 170 | document.addEventListener('focusout', onFocusOut, true); |