HEX
Server: LiteSpeed
System: Linux server315.web-hosting.com 4.18.0-553.54.1.lve.el8.x86_64 #1 SMP Wed Jun 4 13:01:13 UTC 2025 x86_64
User: globfdxw (6114)
PHP: 8.1.34
Disabled: NONE
Upload Files
File: /home/globfdxw/diasporameetsafrica.com/wp-content/plugins/extendify/src/QuickEdit/lib/click-rule.js
// === Unified front-end selector — canonical reference ===
//
// Edit mode is opt-out: post-Launch users land on the front end with
// `extendify-quick-edit-on` already on `<html>`. The page must still
// behave like a visitor's page for navigation; selection has to layer on
// top without breaking the click semantics users expect from links and
// form controls. That contract lives in this file and two siblings:
//
//   click-rule.js   (this file)   — what a click does
//   escape-rule.js                — what Esc does
//   ../state/store.js             — unified selection state
//
// Cursor (CSS, in quick-edit.css) telegraphs the click rule before the
// click: crosshair on tagged-block content, pointer on anchors, UA
// default on form controls. The CSS is the user-visible promise the JS
// keeps.
//
// --- Click rule ---
//
// Capture-phase listener on `document` while edit mode is on. Given the
// click target, returns the branch the listener should take. Wiring
// (preventDefault, stopPropagation, hover-bar render/clear, the soft-
// selection carve-out for the staged block) lives in hover-bar.js.
//
// Priority — first matching branch wins:
//   1. anchor          — let the browser navigate (cursor: pointer)
//   2. pill / toolbar  — let the bubble-phase pill handler fire
//   3. form control    — let the input/textarea/select/button focus
//   4. tagged block    — commit selection on the innermost tagged ancestor
//   5. otherwise       — outside-click, clear any open hover bar
//
// Anchor beats tagged-block intentionally: clicking a link in a nav menu
// or a button inside a tagged section navigates. Pills carry
// `data-extendify-quick-edit-pill` so they survive the form-control
// branch (the pill is a <button>). Tagged blocks are detected by any of
// the five data attributes the block-tagging filters emit
// (`data-extendify-agent-block-id`, `-part-block-id`,
// `-quick-edit-product-id`, `-quick-edit-wpform-field-id`,
// `-quick-edit-mediatext-media`).
//
// --- Selection state shape ---
//
// One store (`useQuickEditStore`) carries three slots:
//
//   selected            — Quick Edit canvas-mount target. Set when the
//                         user clicks the Quick Edit pill; drives the
//                         inline editor.
//   agentBlock          — Ask AI staged block. Set by the Ask AI pill or
//                         by an agent workflow; drives DOMHighlighter's
//                         outline + X-close.
//   committedSelection  — Sticky pre-pill-action selection. Set by the
//                         `select` branch below; pins the hover bar +
//                         outline to a clicked block until the user
//                         clicks outside, clicks a different tagged
//                         block, or clicks a pill (which hands off to
//                         `selected` / `agentBlock`).
//
// The three coexist and stay distinct on purpose: a QE canvas opens for
// a modal flow on one block; an Ask AI selection stages a block for a
// multi-turn workflow; a committed selection holds the hover bar steady
// so the user's cursor can travel to a pill without losing the target.
// Same store keeps cross-feature gating cheap (hasAgentBlockSelected,
// committedSelection truthiness) without a bridge.
//
// --- Sticky selection (agentBlock + committedSelection) ---
//
// When either slot is set, hover-driven bar movement is fully
// suppressed — no re-render on any hover, including tagged inner
// children of the held block. The user can't accidentally re-pick a
// neighbour or drift the bar onto a descendant.
//
// `agentBlock` keeps the bar HIDDEN (only DOMHighlighter's X-close
// indicator shows). No pills, ever, while staged — to re-engage Ask
// AI, the user clicks the X-close (clears agentBlock) and re-hovers /
// re-clicks the block. This avoids a broken state where clicking the
// Quick Edit pill while a block is staged for Ask AI opened the
// canvas while leaving the agent's selection outline + X-close on
// top of it.
//
// `committedSelection` keeps the bar PINNED on the committed element.
// The pills stay clickable; no hover moves the bar.
//
// Clicks INSIDE the held block route natively (anchor navigates, form
// control focuses) — EXCEPT when they land on a tagged descendant
// block, in which case the same click swaps the selection onto the
// descendant (drill-in parity with the cross-sibling swap below).
// Clicks OUTSIDE clear the relevant slot; if the same click also lands
// on a different tagged block, the click rule commits it in the same
// gesture so a single click swaps the selection. For `agentBlock` the
// clear is a soft one: the sidebar stays open (closing the sidebar
// still cascades to clearing the block via Agent.jsx, but clearing the
// block here does not close the sidebar).
//
// --- Esc rule ---
//
// One global keydown handler (escape-rule.js + global-escape.js):
//   1. agent block staged → clear it (also cancels in-flight workflow)
//   2. QE selection set   → clear it
//   3. otherwise          → noop (Esc must not flip edit mode off)
//
// Per-surface Esc handlers (BlockTextEditor capture-phase, WP <Modal>,
// image-menu) bubble before the global one and keep working.

const POST_ATTR = 'data-extendify-agent-block-id';
const PART_ATTR = 'data-extendify-part-block-id';
const PRODUCT_ATTR = 'data-extendify-quick-edit-product-id';
const WPFORM_FIELD_ATTR = 'data-extendify-quick-edit-wpform-field-id';
const MEDIATEXT_MEDIA_ATTR = 'data-extendify-quick-edit-mediatext-media';

const PILL_SELECTOR =
	'[data-extendify-quick-edit-pill], [data-extendify-quick-edit-bar]';
const FORM_SELECTOR = 'input, textarea, select, button';
const TAGGED_SELECTOR = [
	`[${POST_ATTR}]`,
	`[${PART_ATTR}]`,
	`[${PRODUCT_ATTR}]`,
	`[${WPFORM_FIELD_ATTR}]`,
	`[${MEDIATEXT_MEDIA_ATTR}]`,
].join(', ');

export const decideClickAction = (target) => {
	if (!target || target.nodeType !== 1) return { action: 'ignore' };
	if (target.closest('a[href]')) return { action: 'navigate' };
	if (target.closest(PILL_SELECTOR)) return { action: 'pill' };
	if (target.closest(FORM_SELECTOR)) return { action: 'focus-control' };
	const tagged = target.closest(TAGGED_SELECTOR);
	if (tagged) return { action: 'select', el: tagged };
	return { action: 'clear' };
};