@ai-matrx/rich-content 0.2.41 → 0.2.43

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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.43
4
+
5
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
6
+ `git diff npm/rich-content/v0.2.41..npm/rich-content/v0.2.43 -- apps/shared/rich-content`).
7
+ No source changes intended and no consumer action required.
8
+
9
+ ## 0.2.42
10
+
11
+ - **No second visible Export control.** `ExportPaletteAnchor` loses `trigger`; a host with no ⋯ menu passes `CopySplitButton onExport`, which adds ONE "Export…" row under a divider in the chevron menu (`useExportPaletteKey` + `openExportPalette`). `ActionBar` with `hideOverflow` does this itself. A host with a ⋯ menu keeps exactly the two rows.
12
+ - **A context action takes Comment's slot.** `SELECTION_CONTEXT_PRIMARY` / `selectionPrimaryFor`: on a study guide the reading row is Copy · Highlight · I don't get this · Ask AI · More; Comment is a More row there. Still at most 6.
13
+
14
+ **Consumer action:** replace `<ExportPaletteAnchor trigger …/>` with an anchor keyed by `useExportPaletteKey()` and `onExport={() => openExportPalette(key)}` on the split.
15
+
3
16
  ## 0.2.41
4
17
 
5
18
  - **Copy is two rows.** `CopySplitButton`'s chevron menu is exactly "Copy markdown" and "Copy plain text"; the `mountMore` slot (the Alchemy palette under the chevron) is removed.
@@ -289,3 +302,4 @@ held-back streaming text; the heal tests encode 1.3.1's behavior).
289
302
  - matrx-frontend: call `configureRichContent` at the root (see README) and import from
290
303
  `@ai-matrx/rich-content/<domain>/<module>`.
291
304
 
305
+
@@ -13,10 +13,15 @@ export interface CopySplitButtonProps {
13
13
  * through `className`.
14
14
  */
15
15
  moreOnly?: boolean;
16
+ /**
17
+ * Open the export palette — ONLY for a host with no ⋯ menu of its own: adds one "Export…" row
18
+ * under a divider. A host with a ⋯ menu leaves this out; Export… lives in that menu.
19
+ */
20
+ onExport?: () => void;
16
21
  /** Keep the page's text selection when pressed (the selection toolbar). */
17
22
  preserveSelection?: boolean;
18
23
  disabled?: boolean;
19
24
  className?: string;
20
25
  }
21
- export declare function CopySplitButton({ copy, label, size, moreOnly, preserveSelection, disabled, className, }: CopySplitButtonProps): React.ReactElement;
26
+ export declare function CopySplitButton({ copy, label, size, moreOnly, onExport, preserveSelection, disabled, className, }: CopySplitButtonProps): React.ReactElement;
22
27
  export default CopySplitButton;
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { Check, ChevronDown, ClipboardCopy, Copy, FileCode2, Type } from "lucide-react";
4
+ import { Check, ChevronDown, ClipboardCopy, Copy, FileCode2, Share, Type } from "lucide-react";
5
5
  import { Popover, PopoverContent, PopoverTrigger, cn } from "@ai-matrx/design-system";
6
6
  import { defaultCopyFlavor } from "./copy-commands.js";
7
7
  const ROWS = [
@@ -13,6 +13,7 @@ function CopySplitButton({
13
13
  label = "Copy",
14
14
  size = "sm",
15
15
  moreOnly = false,
16
+ onExport,
16
17
  preserveSelection = false,
17
18
  disabled = false,
18
19
  className
@@ -86,33 +87,53 @@ function CopySplitButton({
86
87
  children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3" })
87
88
  }
88
89
  ) }),
89
- /* @__PURE__ */ jsx(
90
+ /* @__PURE__ */ jsxs(
90
91
  PopoverContent,
91
92
  {
92
93
  align: "start",
93
94
  className: "w-auto min-w-60 max-w-[min(24rem,calc(100vw-16px))] p-1",
94
95
  onOpenAutoFocus: preserveSelection ? (event) => event.preventDefault() : void 0,
95
96
  "data-copy-split-menu": "",
96
- children: /* @__PURE__ */ jsx("div", { role: "menu", "aria-label": "Copy as", children: ROWS.map(({ flavor: rowFlavor, label: rowLabel, icon: Icon }) => /* @__PURE__ */ jsxs(
97
- "button",
98
- {
99
- type: "button",
100
- role: "menuitem",
101
- "data-copy-flavor": rowFlavor,
102
- onMouseDown: keep,
103
- onClick: () => {
104
- setOpen(false);
105
- void run(rowFlavor);
97
+ children: [
98
+ /* @__PURE__ */ jsx("div", { role: "menu", "aria-label": "Copy as", children: ROWS.map(({ flavor: rowFlavor, label: rowLabel, icon: Icon }) => /* @__PURE__ */ jsxs(
99
+ "button",
100
+ {
101
+ type: "button",
102
+ role: "menuitem",
103
+ "data-copy-flavor": rowFlavor,
104
+ onMouseDown: keep,
105
+ onClick: () => {
106
+ setOpen(false);
107
+ void run(rowFlavor);
108
+ },
109
+ className: "flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm hover:bg-accent",
110
+ children: [
111
+ /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
112
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: rowLabel }),
113
+ rowFlavor === flavor ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Default" }) : null
114
+ ]
106
115
  },
107
- className: "flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm hover:bg-accent",
108
- children: [
109
- /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
110
- /* @__PURE__ */ jsx("span", { className: "flex-1", children: rowLabel }),
111
- rowFlavor === flavor ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Default" }) : null
112
- ]
113
- },
114
- rowFlavor
115
- )) })
116
+ rowFlavor
117
+ )) }),
118
+ onExport ? /* @__PURE__ */ jsx("div", { className: "mt-1 border-t border-border pt-1", children: /* @__PURE__ */ jsxs(
119
+ "button",
120
+ {
121
+ type: "button",
122
+ role: "menuitem",
123
+ "data-copy-split-export": "",
124
+ onMouseDown: keep,
125
+ onClick: () => {
126
+ setOpen(false);
127
+ onExport();
128
+ },
129
+ className: "flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm hover:bg-accent",
130
+ children: [
131
+ /* @__PURE__ */ jsx(Share, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
132
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: "Export\u2026" })
133
+ ]
134
+ }
135
+ ) }) : null
136
+ ]
116
137
  }
117
138
  )
118
139
  ] })
@@ -9,12 +9,9 @@ export interface ExportPaletteAnchorProps {
9
9
  exportKey?: string;
10
10
  /** The host's hidden ContentTransferMenu — the palette that mounts here. */
11
11
  controllerRef: React.RefObject<ContentTransferController | null>;
12
- /** Render a visible "Export…" icon button (a surface with no ⋯ menu of its own). */
13
- trigger?: boolean;
14
- /** What is exported, for the trigger's name ("Export note"). */
15
- label?: string;
16
- size?: "sm" | "xs";
17
12
  className?: string;
18
13
  }
19
- export declare function ExportPaletteAnchor({ exportKey, controllerRef, trigger, label, size, className, }: ExportPaletteAnchorProps): React.ReactElement;
14
+ export declare function ExportPaletteAnchor({ exportKey, controllerRef, className, }: ExportPaletteAnchorProps): React.ReactElement;
15
+ /** A key for a host that opens its own anchor (the split Copy's "Export…" row). */
16
+ export declare function useExportPaletteKey(): string;
20
17
  export default ExportPaletteAnchor;
@@ -1,8 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { Share } from "lucide-react";
5
- import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger, cn } from "@ai-matrx/design-system";
4
+ import { Popover, PopoverAnchor, PopoverContent, cn } from "@ai-matrx/design-system";
6
5
  const hosts = /* @__PURE__ */ new Map();
7
6
  function hasExportPalette(key) {
8
7
  return hosts.has(key);
@@ -16,9 +15,6 @@ function openExportPalette(key) {
16
15
  function ExportPaletteAnchor({
17
16
  exportKey,
18
17
  controllerRef,
19
- trigger = false,
20
- label,
21
- size = "sm",
22
18
  className
23
19
  }) {
24
20
  const [open, setOpen] = React.useState(false);
@@ -33,27 +29,8 @@ function ExportPaletteAnchor({
33
29
  };
34
30
  }, [exportKey]);
35
31
  const close = React.useCallback(() => setOpen(false), []);
36
- const name = `Export${label ? ` ${label}` : ""}\u2026`;
37
- const box = size === "xs" ? "h-7 min-w-7" : "h-8 min-w-8";
38
32
  return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
39
- trigger ? /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
40
- "button",
41
- {
42
- type: "button",
43
- "aria-label": name,
44
- title: "Export\u2026",
45
- "data-export-palette-trigger": "",
46
- "data-matrx-button": "",
47
- "data-tap-floor": "",
48
- "data-tap-floor-square": "",
49
- className: cn(
50
- "inline-flex shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground pointer-coarse:h-11 pointer-coarse:min-w-11",
51
- box,
52
- className
53
- ),
54
- children: /* @__PURE__ */ jsx(Share, { className: size === "xs" ? "h-3.5 w-3.5" : "h-4 w-4" })
55
- }
56
- ) }) : /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsx("span", { "aria-hidden": true, "data-export-palette-anchor": "", className: cn("inline-block h-0 w-0", className) }) }),
33
+ /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsx("span", { "aria-hidden": true, "data-export-palette-anchor": "", className: cn("inline-block h-0 w-0", className) }) }),
57
34
  /* @__PURE__ */ jsx(
58
35
  PopoverContent,
59
36
  {
@@ -88,10 +65,14 @@ function PaletteSlot({
88
65
  }, [controllerRef, close]);
89
66
  return /* @__PURE__ */ jsx("div", { ref, "data-export-palette-slot": "", children: failed ? /* @__PURE__ */ jsx("p", { className: "px-2 py-1.5 text-sm text-muted-foreground", children: failed }) : null });
90
67
  }
68
+ function useExportPaletteKey() {
69
+ return `export-palette-${React.useId()}`;
70
+ }
91
71
  var ExportPalette_default = ExportPaletteAnchor;
92
72
  export {
93
73
  ExportPaletteAnchor,
94
74
  ExportPalette_default as default,
95
75
  hasExportPalette,
96
- openExportPalette
76
+ openExportPalette,
77
+ useExportPaletteKey
97
78
  };
@@ -7,7 +7,7 @@ import { cn } from "@ai-matrx/design-system";
7
7
  import { AlchemyDocumentMenu } from "./shared/AlchemyDocumentMenu.js";
8
8
  import { CopySplitButton } from "../../copy/CopySplitButton.js";
9
9
  import { copyRichContent } from "../../copy/copy-commands.js";
10
- import { ExportPaletteAnchor } from "../../copy/ExportPalette.js";
10
+ import { ExportPaletteAnchor, openExportPalette } from "../../copy/ExportPalette.js";
11
11
  import { ensureExportPaletteAction } from "../actions/export-palette-action.js";
12
12
  import { OpenOneMenuButton, useOneMenuFor } from "./shared/OpenOneMenuButton.js";
13
13
  import { StandaloneOneMenu } from "./shared/StandaloneOneMenu.js";
@@ -43,7 +43,14 @@ function ActionBar(props) {
43
43
  ),
44
44
  children: [
45
45
  hasCopy ? /* @__PURE__ */ jsxs(Fragment, { children: [
46
- /* @__PURE__ */ jsx(CopySplitButton, { copy, size: mini ? "xs" : "sm" }),
46
+ /* @__PURE__ */ jsx(
47
+ CopySplitButton,
48
+ {
49
+ copy,
50
+ size: mini ? "xs" : "sm",
51
+ ...hideOverflow ? { onExport: () => void openExportPalette(sourceId) } : {}
52
+ }
53
+ ),
47
54
  /* @__PURE__ */ jsx(
48
55
  AlchemyDocumentMenu,
49
56
  {
@@ -67,7 +74,7 @@ function ActionBar(props) {
67
74
  hideOverflow: true
68
75
  }
69
76
  ),
70
- hasCopy ? /* @__PURE__ */ jsx(ExportPaletteAnchor, { exportKey: sourceId, controllerRef: palette, trigger: hideOverflow, size: mini ? "xs" : "sm" }) : null,
77
+ hasCopy ? /* @__PURE__ */ jsx(ExportPaletteAnchor, { exportKey: sourceId, controllerRef: palette }) : null,
71
78
  hideOverflow ? null : oneMenu ? /* @__PURE__ */ jsx(OpenOneMenuButton, { source: getCtx().source }) : /* @__PURE__ */ jsx(StandaloneOneMenu, { getCtx, target })
72
79
  ]
73
80
  }
@@ -107,6 +107,14 @@ export interface SelectionCommonHost {
107
107
  * editing: Copy · Bold · Italic · Link · Ask AI · More (headings, lists, comment…)
108
108
  */
109
109
  export declare const SELECTION_PRIMARY: Readonly<Record<SelectionMode, readonly string[]>>;
110
+ /**
111
+ * A surface's OWN passage action takes the reading row's middle slot when present (Arman,
112
+ * 2026-10-07): the study guide reads Copy · Highlight · I don't get this · Ask AI · More, and
113
+ * Comment moves behind More there. Listed in priority order; the first present one wins.
114
+ */
115
+ export declare const SELECTION_CONTEXT_PRIMARY: readonly string[];
116
+ /** The buttons for this target: the mode's set, with a present context action in Comment's slot. */
117
+ export declare function selectionPrimaryFor(target: ClickTarget, mode: SelectionMode): readonly string[];
110
118
  /** The most controls the strip ever shows: the split Copy, the buttons, and More. */
111
119
  export declare const SELECTION_MAX_VISIBLE = 6;
112
120
  /**
@@ -85,6 +85,13 @@ const SELECTION_PRIMARY = {
85
85
  edit: ["selection:format-bold", "selection:format-italic", "selection:format-link", "selection:ai"],
86
86
  read: ["selection:highlight", "selection:comment", "selection:ai"]
87
87
  };
88
+ const SELECTION_CONTEXT_PRIMARY = ["selection:tutor-explain"];
89
+ function selectionPrimaryFor(target, mode) {
90
+ const base = SELECTION_PRIMARY[mode];
91
+ if (mode !== "read") return base;
92
+ const context = SELECTION_CONTEXT_PRIMARY.find((id) => presentAt(id, target));
93
+ return context ? base.map((id) => id === "selection:comment" ? context : id) : base;
94
+ }
88
95
  const SELECTION_MAX_VISIBLE = 6;
89
96
  const SELECTION_PRIORITY = {
90
97
  edit: [
@@ -159,9 +166,10 @@ function presentAt(id, target) {
159
166
  function selectionPlacement(id, target) {
160
167
  const toolbar = selectionToolbarHostOf(target);
161
168
  if (!toolbar) return "primary";
162
- if (!SELECTION_PRIMARY[toolbar.mode].includes(id)) return "overflow";
169
+ const primary = selectionPrimaryFor(target, toolbar.mode);
170
+ if (!primary.includes(id)) return "overflow";
163
171
  if (toolbar.slots === null) return "primary";
164
- const shown = SELECTION_PRIMARY[toolbar.mode].filter((x) => presentAt(x, target));
172
+ const shown = primary.filter((x) => presentAt(x, target));
165
173
  if (shown.length <= toolbar.slots) return "primary";
166
174
  const rank = shown.indexOf(id);
167
175
  return rank >= 0 && rank < Math.max(1, toolbar.slots - 1) ? "primary" : "overflow";
@@ -220,6 +228,7 @@ export {
220
228
  PASSAGE_ACTIONS_HOST_KEY,
221
229
  SELECTION_ACTION_MODES,
222
230
  SELECTION_COMMON_HOST_KEY,
231
+ SELECTION_CONTEXT_PRIMARY,
223
232
  SELECTION_HOST_KINDS,
224
233
  SELECTION_MAX_VISIBLE,
225
234
  SELECTION_PRIMARY,
@@ -232,6 +241,7 @@ export {
232
241
  passageActionsProvider,
233
242
  placeSelectionActions,
234
243
  selectionPlacement,
244
+ selectionPrimaryFor,
235
245
  selectionToolbarHostOf,
236
246
  shownInSelectionMode,
237
247
  subscribeDeclaredSelectionProviders
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/rich-content",
3
- "version": "0.2.41",
3
+ "version": "0.2.43",
4
4
  "description": "The AI Matrx formatted-text engine: the one markdown renderer, streaming markdown, the block splitter and renderer, the generic blocks, <RichContent> levels, the rich-document action registry and menus, copy, and the selection toolbar — as portable React.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -133,17 +133,17 @@
133
133
  "@ai-matrx/agents": "latest",
134
134
  "@ai-matrx/alchemy": "latest",
135
135
  "@ai-matrx/canvas": "latest",
136
+ "@ai-matrx/content-ir": "latest",
137
+ "@ai-matrx/content-ir-react": "latest",
136
138
  "@ai-matrx/data": "latest",
137
139
  "@ai-matrx/design-system": "latest",
138
140
  "@ai-matrx/diff": "latest",
139
- "@ai-matrx/content-ir": "latest",
140
141
  "@ai-matrx/icons": "latest",
141
- "@ai-matrx/content-ir-react": "latest",
142
142
  "@ai-matrx/kit": "latest",
143
+ "@ai-matrx/media": "latest",
143
144
  "@ai-matrx/print": "latest",
144
145
  "@ai-matrx/records": "latest",
145
146
  "@ai-matrx/records-ui": "latest",
146
- "@ai-matrx/media": "latest",
147
147
  "@ai-matrx/tap-target": "latest"
148
148
  },
149
149
  "peerDependencies": {