@ai-matrx/context-menu 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.cjs CHANGED
@@ -427,11 +427,21 @@ var CONTEXT_REGION_TRIGGER_ATTRS = {
427
427
  };
428
428
 
429
429
  // src/engine/utils/table-at-target.ts
430
- var import_table_shape = require("@ai-matrx/records-ui/table-shape");
430
+ var shapeReader = null;
431
+ var shapeLoad = null;
432
+ function preloadTableShape() {
433
+ shapeLoad ??= import("@ai-matrx/records-ui/table-shape").then((m) => {
434
+ shapeReader = m.shapeTextOfNode;
435
+ }).catch((error) => {
436
+ shapeLoad = null;
437
+ console.error("[ContextMenuV3] could not load the table shape reader", error);
438
+ });
439
+ return shapeLoad;
440
+ }
431
441
  function tableTextAtTarget(target) {
432
442
  const table = target?.closest("table");
433
- if (!table) return null;
434
- const text = (0, import_table_shape.shapeTextOfNode)(table.cloneNode(true));
443
+ if (!table || !shapeReader) return null;
444
+ const text = shapeReader(table.cloneNode(true));
435
445
  return text.trim() ? text : null;
436
446
  }
437
447
 
@@ -570,7 +580,6 @@ function useContextMenuHostBindings() {
570
580
 
571
581
  // src/engine/ContextMenuV3.tsx
572
582
  var import_selectors = require("@ai-matrx/chat/agents/redux/agent-shortcuts/selectors");
573
- var import_thunks = require("@ai-matrx/chat/agents/redux/agent-shortcuts/thunks");
574
583
  var import_jsx_runtime = require("react/jsx-runtime");
575
584
  var NON_TEXT_INPUT_TYPES = /* @__PURE__ */ new Set([
576
585
  "button",
@@ -642,17 +651,7 @@ function warmMenuContent() {
642
651
  }
643
652
  return menuContentSlot().warm ?? Promise.resolve();
644
653
  }
645
- function useWarmMenuContentWhenIdle() {
646
- (0, import_react2.useEffect)(() => {
647
- if (menuContentSlot().warm) return;
648
- if (typeof window.requestIdleCallback === "function") {
649
- const id2 = window.requestIdleCallback(() => void warmMenuContent(), { timeout: 4e3 });
650
- return () => window.cancelIdleCallback(id2);
651
- }
652
- const id = window.setTimeout(() => void warmMenuContent(), 1500);
653
- return () => window.clearTimeout(id);
654
- }, []);
655
- }
654
+ var menuEverOpened = false;
656
655
  function ShortcutKeyRunner(props) {
657
656
  const mod = useMenuContentModule();
658
657
  return mod ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(mod.ShortcutKeyRunner, { ...props }) : null;
@@ -767,7 +766,8 @@ function ContextMenuV3({
767
766
  const store = import_react2.default.useContext(import_react_redux.ReactReduxContext)?.store;
768
767
  const [menuPoint, setMenuPoint] = (0, import_react2.useState)({ x: 0, y: 0 });
769
768
  const [openSeq, setOpenSeq] = (0, import_react2.useState)(0);
770
- useWarmMenuContentWhenIdle();
769
+ const [engaged, setEngaged] = (0, import_react2.useState)(false);
770
+ const engage = () => setEngaged(true);
771
771
  const capturedSelection = (0, import_react2.useRef)(null);
772
772
  const selectionLocked = (0, import_react2.useRef)(false);
773
773
  const lastMousePos = (0, import_react2.useRef)(null);
@@ -798,7 +798,7 @@ function ContextMenuV3({
798
798
  onTextInsertAfter,
799
799
  getApplicationScope
800
800
  }) : null;
801
- const widgetHandleId = (0, import_useWidgetHandle.useOptionalWidgetHandle)(widgetHandle);
801
+ const widgetHandleId = (0, import_useWidgetHandle.useOptionalWidgetHandle)(engaged ? widgetHandle : null);
802
802
  const getEffectiveContextData = () => mergeResolvedContextData(
803
803
  contextData,
804
804
  resolvedContext
@@ -806,6 +806,7 @@ function ContextMenuV3({
806
806
  const surfaceSpokeAboutEntity = !!resolvedContext && types_exports.CONTEXT_MENU_ENTITY_KEY in resolvedContext;
807
807
  const effectiveEntity = surfaceSpokeAboutEntity ? resolveEffectiveEntity(entity, resolvedContext) : sniffedEntity ?? entity;
808
808
  (0, import_react2.useEffect)(() => {
809
+ if (!engaged) return;
809
810
  const handleSelection = () => {
810
811
  if (selectionLocked.current) return;
811
812
  const owner = selectionOwnerRef.current;
@@ -822,18 +823,15 @@ function ContextMenuV3({
822
823
  };
823
824
  document.addEventListener("selectionchange", handleSelection);
824
825
  return () => document.removeEventListener("selectionchange", handleSelection);
825
- }, []);
826
- (0, import_react2.useEffect)(() => {
827
- const handleMouseMove = (e) => {
828
- lastMousePos.current = { x: e.clientX, y: e.clientY };
829
- };
830
- document.addEventListener("mousemove", handleMouseMove, { passive: true });
831
- return () => document.removeEventListener("mousemove", handleMouseMove);
832
- }, []);
826
+ }, [engaged]);
827
+ const rememberPointer = (e) => {
828
+ lastMousePos.current = { x: e.clientX, y: e.clientY };
829
+ };
833
830
  const handleMouseDown = (e) => {
834
831
  if (suppressed) return;
835
832
  if (reachedThroughPortal(e)) return;
836
833
  if (e.button !== 2) return;
834
+ void warmMenuContent();
837
835
  if (!isEditable && yieldsToNativeTextMenu(e.target)) return;
838
836
  const target = e.target;
839
837
  resolvePerTargetContext(target);
@@ -849,6 +847,9 @@ function ContextMenuV3({
849
847
  }
850
848
  };
851
849
  const resolvePerTargetContext = (target) => {
850
+ menuEverOpened = true;
851
+ engage();
852
+ if (target?.closest("table")) void preloadTableShape();
852
853
  const rowHit = resolveTableRowItem(target);
853
854
  const rowMenu = rowHit?.menu ?? null;
854
855
  setResolvedItem(rowHit?.item ?? null);
@@ -989,11 +990,14 @@ function ContextMenuV3({
989
990
  setDropdownOpen(true);
990
991
  };
991
992
  const selectionHost = { kind: "context-menu-selection", open: openFromSelection };
992
- (0, import_selection_zones.useSelectionZone)(selectionOwner, {
993
- // A suppressed menu (the text is streaming) keeps the toolbar away until it settles.
994
- suppress: suppressed,
995
- host: { [CONTEXT_MENU_SELECTION_HOST_KEY]: selectionHost }
996
- });
993
+ (0, import_selection_zones.useSelectionZone)(
994
+ selectionOwner,
995
+ engaged ? {
996
+ // A suppressed menu (the text is streaming) keeps the toolbar away until it settles.
997
+ suppress: suppressed,
998
+ host: { [CONTEXT_MENU_SELECTION_HOST_KEY]: selectionHost }
999
+ } : null
1000
+ );
997
1001
  const clearLongPress = () => {
998
1002
  if (longPressTimer.current !== null) {
999
1003
  clearTimeout(longPressTimer.current);
@@ -1129,6 +1133,8 @@ function ContextMenuV3({
1129
1133
  const reg = paletteRegistry();
1130
1134
  const now = Date.now();
1131
1135
  if (e.type === "pointerdown") reg.pointerDown = { element: e.target, at: now };
1136
+ if (e.type === "pointerdown" || e.type === "focusin" || e.type === "focus") engage();
1137
+ if (e.type === "pointerover" || e.type === "pointerdown") rememberPointer(e);
1132
1138
  reg.seq += 1;
1133
1139
  reg[kind] = { id: paletteIdRef.current, element: e.target, seq: reg.seq, at: now };
1134
1140
  };
@@ -1149,6 +1155,7 @@ function ContextMenuV3({
1149
1155
  const native = e.nativeEvent;
1150
1156
  if (native[KEY_RUN_CLAIMED] || e.defaultPrevented || suppressed || e.repeat) return;
1151
1157
  if (reachedThroughPortal(e)) return;
1158
+ if (!menuEverOpened) return;
1152
1159
  if (!store || !e.altKey && !e.ctrlKey && !e.metaKey) return;
1153
1160
  const matchOf = (event) => findComboMatch(
1154
1161
  event,
@@ -1184,7 +1191,7 @@ function ContextMenuV3({
1184
1191
  const loadShortcuts = () => {
1185
1192
  if (!store || !isEditable || shortcutsLoaded.current) return null;
1186
1193
  if (!shortcutsLoad.current) {
1187
- shortcutsLoad.current = store.dispatch((0, import_thunks.fetchUnifiedMenu)({ scope, scopeId })).catch(() => void 0).finally(() => {
1194
+ shortcutsLoad.current = import("@ai-matrx/chat/agents/redux/agent-shortcuts/thunks").then(({ fetchUnifiedMenu: fetchUnifiedMenu2 }) => store.dispatch(fetchUnifiedMenu2({ scope, scopeId }))).catch(() => void 0).finally(() => {
1188
1195
  shortcutsLoaded.current = true;
1189
1196
  });
1190
1197
  }
@@ -1203,6 +1210,7 @@ function ContextMenuV3({
1203
1210
  "data-content-source": contentSource ? contentSourceKey(contentSource) : void 0,
1204
1211
  onContextMenuCapture: (e) => {
1205
1212
  if (reachedThroughPortal(e)) return;
1213
+ void warmMenuContent();
1206
1214
  if (!isEditable && yieldsToNativeTextMenu(e.target))
1207
1215
  NATIVE_TEXT_YIELD.set(e.nativeEvent, e.currentTarget);
1208
1216
  },
@@ -1820,6 +1828,17 @@ function toast2(props) {
1820
1828
  else import_notify2.toast.message(message, options);
1821
1829
  }
1822
1830
 
1831
+ // src/engine/utils/ensure-block-template.ts
1832
+ var import_db = require("@ai-matrx/chat/host/db");
1833
+ var import_ui_slots = require("@ai-matrx/chat/host/ui-slots");
1834
+ async function ensureBlockTemplate(blockId, dispatch) {
1835
+ const { data, error } = await import_db.supabase.schema("skill").from("render_definition").select("template").eq("id", blockId).single();
1836
+ if (error) throw new Error(error.message);
1837
+ const template = data?.template ?? "";
1838
+ dispatch(import_ui_slots.sklActions.renderDefinitionsMerged([{ id: blockId, template }]));
1839
+ return template;
1840
+ }
1841
+
1823
1842
  // src/engine/hooks/useContextMenuActions.ts
1824
1843
  var import_org = require("@ai-matrx/chat/host/org");
1825
1844
  var import_useAgentLauncher = require("@ai-matrx/chat/agents/hooks/useAgentLauncher");
@@ -1951,7 +1970,7 @@ var import_constants2 = require("@ai-matrx/chat/agents/shortcuts/constants");
1951
1970
  // src/engine/hooks/useUnifiedAgentContextMenu.ts
1952
1971
  var import_react4 = require("react");
1953
1972
  var import_hooks = require("@ai-matrx/chat/store/hooks");
1954
- var import_thunks2 = require("@ai-matrx/chat/agents/redux/agent-shortcuts/thunks");
1973
+ var import_thunks = require("@ai-matrx/chat/agents/redux/agent-shortcuts/thunks");
1955
1974
 
1956
1975
  // src/engine/utils/menu-deadline.ts
1957
1976
  var menu_deadline_exports = {};
@@ -2109,7 +2128,7 @@ function useUnifiedAgentContextMenu(args) {
2109
2128
  const categories = (0, import_hooks.useAppSelector)(import_selectors3.selectAllCategoriesArray);
2110
2129
  const contentBlocks = (0, import_hooks.useAppSelector)((state) => getContextMenuHost().selectRenderDefinitions(state));
2111
2130
  const rowsInHand = (0, import_hooks.useAppSelector)(
2112
- (state) => state.agentShortcut.contextLoaded?.[(0, import_thunks2.unifiedMenuLoadedKey)({ scope, scopeId })] ?? false
2131
+ (state) => state.agentShortcut.contextLoaded?.[(0, import_thunks.unifiedMenuLoadedKey)({ scope, scopeId })] ?? false
2113
2132
  );
2114
2133
  const [error, setError] = (0, import_react4.useState)(null);
2115
2134
  const refresh = (0, import_react4.useCallback)(async () => {
@@ -2117,7 +2136,7 @@ function useUnifiedAgentContextMenu(args) {
2117
2136
  try {
2118
2137
  setError(null);
2119
2138
  await (0, menu_deadline_exports.withMenuDeadline)(
2120
- dispatch((0, import_thunks2.fetchUnifiedMenu)({ scope, scopeId })).unwrap(),
2139
+ dispatch((0, import_thunks.fetchUnifiedMenu)({ scope, scopeId })).unwrap(),
2121
2140
  "Loading the menu's AI actions"
2122
2141
  );
2123
2142
  } catch (err) {
@@ -3005,12 +3024,25 @@ function useContextMenuActions(props) {
3005
3024
  };
3006
3025
  const insertTargets = { editorId, getTextarea, insertAtCaret, onTextReplace };
3007
3026
  const handleContentBlockInsert = (entry) => {
3008
- const template = entry.template;
3009
- if (insertIntoEditor(insertTargets, template)) {
3010
- onContentInserted?.();
3011
- return;
3012
- }
3013
- void (0, import_clipboard.copyText)(template).then((copied) => {
3027
+ void (async () => {
3028
+ let template = entry.template;
3029
+ if (!template) {
3030
+ try {
3031
+ template = await ensureBlockTemplate(entry.id, dispatch);
3032
+ } catch (error) {
3033
+ toast2({
3034
+ title: "Couldn't load that block",
3035
+ description: error instanceof Error ? error.message : "Try again.",
3036
+ variant: "destructive"
3037
+ });
3038
+ return;
3039
+ }
3040
+ }
3041
+ if (insertIntoEditor(insertTargets, template)) {
3042
+ onContentInserted?.();
3043
+ return;
3044
+ }
3045
+ const copied = await (0, import_clipboard.copyText)(template);
3014
3046
  if (copied) {
3015
3047
  toast2({
3016
3048
  title: "Copied to clipboard",
@@ -3019,7 +3051,7 @@ function useContextMenuActions(props) {
3019
3051
  } else {
3020
3052
  getContextMenuHost().showManualCopy({ text: template });
3021
3053
  }
3022
- });
3054
+ })();
3023
3055
  };
3024
3056
  const handleEntrySelect = (entry) => {
3025
3057
  if (entry.entryType === "agent_shortcut") void handleShortcutExecute(entry);