@ai-matrx/rich-content 0.2.9 → 0.2.15
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 +9 -0
- package/dist/copy/copy-commands.d.ts +42 -0
- package/dist/copy/copy-commands.js +119 -0
- package/dist/copy/markdown-copy-html.d.ts +3 -0
- package/dist/copy/markdown-copy-html.js +8 -0
- package/dist/copy/markdown-readable-text.d.ts +4 -0
- package/dist/copy/markdown-readable-text.js +62 -0
- package/dist/display/blocks/json/json-tabular-utils.d.ts +7 -5
- package/dist/display/blocks/json/json-tabular-utils.js +26 -17
- package/dist/display/blocks/table/StreamingTableRenderer.js +11 -16
- package/dist/display/chat-markdown/BasicMarkdownContent.js +8 -6
- package/dist/display/chat-markdown/ConfigurableMarkdownContent.js +8 -6
- package/dist/display/chat-markdown/block-registry/BlockComponentRegistry.js +2 -0
- package/dist/display/chat-markdown/block-registry/block-dispatch.js +2 -0
- package/dist/display/tables/MarkdownTable.js +11 -16
- package/dist/display/tables/TableWithSeparatedControls.js +11 -16
- package/dist/display/tables/table-csv.d.ts +7 -0
- package/dist/display/tables/table-csv.js +19 -0
- package/dist/host/app-bindings.d.ts +32 -0
- package/dist/host/app-bindings.js +40 -0
- package/dist/host/feedback-subject.d.ts +16 -0
- package/dist/host/feedback-subject.js +0 -0
- package/dist/host/manual-copy.d.ts +7 -0
- package/dist/host/manual-copy.js +11 -0
- package/dist/host/registry.d.ts +2 -0
- package/dist/host/registry.js +10 -0
- package/dist/host/session-knob.d.ts +5 -0
- package/dist/host/session-knob.js +7 -0
- package/dist/host/types.d.ts +24 -0
- package/dist/rich-document/RegistryContextMenu.d.ts +29 -0
- package/dist/rich-document/RegistryContextMenu.js +40 -0
- package/dist/rich-document/RichDocument.d.ts +78 -0
- package/dist/rich-document/RichDocument.js +218 -0
- package/dist/rich-document/RichDocumentActionProvider.d.ts +18 -0
- package/dist/rich-document/RichDocumentActionProvider.js +18 -0
- package/dist/rich-document/RichDocumentActionSurface.d.ts +11 -0
- package/dist/rich-document/RichDocumentActionSurface.js +59 -0
- package/dist/rich-document/RichDocumentActions.d.ts +12 -0
- package/dist/rich-document/RichDocumentActions.js +29 -0
- package/dist/rich-document/actions/hostActions.d.ts +5 -0
- package/dist/rich-document/actions/hostActions.js +31 -0
- package/dist/rich-document/actions/labels.d.ts +9 -0
- package/dist/rich-document/actions/labels.js +8 -0
- package/dist/rich-document/actions/markdownTable.d.ts +9 -0
- package/dist/rich-document/actions/markdownTable.js +39 -0
- package/dist/rich-document/actions/provider.d.ts +58 -0
- package/dist/rich-document/actions/provider.js +245 -0
- package/dist/rich-document/actions/resumePendingAuthAction.d.ts +3 -0
- package/dist/rich-document/actions/resumePendingAuthAction.js +31 -0
- package/dist/rich-document/actions/sources/index.d.ts +9 -0
- package/dist/rich-document/actions/sources/index.js +22 -0
- package/dist/rich-document/actions/sources/raw.d.ts +4 -0
- package/dist/rich-document/actions/sources/raw.js +35 -0
- package/dist/rich-document/actions/useRichDocumentProvider.d.ts +1 -0
- package/dist/rich-document/actions/useRichDocumentProvider.js +13 -0
- package/dist/rich-document/host-types.d.ts +51 -0
- package/dist/rich-document/host-types.js +0 -0
- package/dist/rich-document/registrations.d.ts +1 -0
- package/dist/rich-document/registrations.js +13 -0
- package/dist/rich-document/review/applyTargets.d.ts +16 -0
- package/dist/rich-document/review/applyTargets.js +32 -0
- package/dist/rich-document/review/proposedEdit.d.ts +22 -0
- package/dist/rich-document/review/proposedEdit.js +48 -0
- package/dist/rich-document/review/protectedSpans.d.ts +14 -0
- package/dist/rich-document/review/protectedSpans.js +61 -0
- package/dist/rich-document/runtime/actionSurfacesStore.d.ts +30 -0
- package/dist/rich-document/runtime/actionSurfacesStore.js +79 -0
- package/dist/rich-document/runtime/contextMenuFor.d.ts +6 -0
- package/dist/rich-document/runtime/contextMenuFor.js +7 -0
- package/dist/rich-document/runtime/providerBridge.d.ts +20 -0
- package/dist/rich-document/runtime/providerBridge.js +23 -0
- package/dist/rich-document/runtime/useActionSurfaceProvider.d.ts +33 -0
- package/dist/rich-document/runtime/useActionSurfaceProvider.js +214 -0
- package/dist/rich-document/search/ContentFindControl.d.ts +8 -0
- package/dist/rich-document/search/ContentFindControl.js +46 -0
- package/dist/rich-document/search/RenderedFindBar.d.ts +8 -0
- package/dist/rich-document/search/RenderedFindBar.js +110 -0
- package/dist/rich-document/search/renderedFind.d.ts +1 -0
- package/dist/rich-document/search/renderedFind.js +1 -0
- package/dist/rich-document/types.d.ts +384 -0
- package/dist/rich-document/types.js +0 -0
- package/dist/rich-document/variants/ActionBar.d.ts +17 -0
- package/dist/rich-document/variants/ActionBar.js +48 -0
- package/dist/rich-document/variants/MenuVariant.d.ts +15 -0
- package/dist/rich-document/variants/MenuVariant.js +17 -0
- package/dist/rich-document/variants/MiniActionBar.d.ts +5 -0
- package/dist/rich-document/variants/MiniActionBar.js +11 -0
- package/dist/rich-document/variants/RegistryActionList.d.ts +17 -0
- package/dist/rich-document/variants/RegistryActionList.js +94 -0
- package/dist/rich-document/variants/RegistryActionMenu.d.ts +24 -0
- package/dist/rich-document/variants/RegistryActionMenu.js +74 -0
- package/dist/rich-document/variants/shared/AlchemyDocumentMenu.d.ts +7 -0
- package/dist/rich-document/variants/shared/AlchemyDocumentMenu.js +83 -0
- package/dist/rich-document/variants/shared/OpenOneMenuButton.d.ts +9 -0
- package/dist/rich-document/variants/shared/OpenOneMenuButton.js +70 -0
- package/dist/rich-document/variants/shared/StandaloneOneMenu.d.ts +8 -0
- package/dist/rich-document/variants/shared/StandaloneOneMenu.js +32 -0
- package/dist/rich-document/variants/shared/categories.d.ts +4 -0
- package/dist/rich-document/variants/shared/categories.js +31 -0
- package/dist/rich-document/variants/shared/iconMap.d.ts +2 -0
- package/dist/rich-document/variants/shared/iconMap.js +73 -0
- package/dist/rich-document/variants/shared/menuStructure.d.ts +81 -0
- package/dist/rich-document/variants/shared/menuStructure.js +268 -0
- package/dist/rich-document/variants/shared/runAction.d.ts +17 -0
- package/dist/rich-document/variants/shared/runAction.js +24 -0
- package/dist/selection-toolbar/SelectionToolbarFrame.d.ts +64 -0
- package/dist/selection-toolbar/SelectionToolbarFrame.js +191 -0
- package/dist/selection-toolbar/common-actions.d.ts +2 -0
- package/dist/selection-toolbar/common-actions.js +103 -0
- package/dist/selection-toolbar/selection-actions.d.ts +126 -0
- package/dist/selection-toolbar/selection-actions.js +240 -0
- package/dist/selection-toolbar/selection-copy.d.ts +19 -0
- package/dist/selection-toolbar/selection-copy.js +98 -0
- package/dist/selection-toolbar/selection-shape.d.ts +1 -0
- package/dist/selection-toolbar/selection-shape.js +15 -0
- package/dist/selection-toolbar/selection-zones.d.ts +90 -0
- package/dist/selection-toolbar/selection-zones.js +85 -0
- package/dist/selection-toolbar/textarea-caret.d.ts +8 -0
- package/dist/selection-toolbar/textarea-caret.js +69 -0
- package/dist/utils/alchemy-icon-keys.d.ts +18 -0
- package/dist/utils/alchemy-icon-keys.js +29 -0
- package/dist/utils/rendered-find.d.ts +9 -0
- package/dist/utils/rendered-find.js +68 -0
- package/package.json +21 -5
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import {
|
|
4
|
+
useHostDispatch,
|
|
5
|
+
useHostGetState,
|
|
6
|
+
useIsAuthenticated,
|
|
7
|
+
useIsSuperAdmin,
|
|
8
|
+
useActiveOrganizationId
|
|
9
|
+
} from "../../host/app-bindings.js";
|
|
10
|
+
import {
|
|
11
|
+
registerProvider,
|
|
12
|
+
unregisterProvider,
|
|
13
|
+
updateProviderSpecs
|
|
14
|
+
} from "./actionSurfacesStore.js";
|
|
15
|
+
import { getSourceAdapter } from "../actions/sources/index.js";
|
|
16
|
+
import { shortHash } from "../actions/sources/raw.js";
|
|
17
|
+
import { resolveActions, richDocumentClickTarget } from "../actions/provider.js";
|
|
18
|
+
import { resolveActionLabel } from "../actions/labels.js";
|
|
19
|
+
import {
|
|
20
|
+
registerBridge,
|
|
21
|
+
unregisterBridge,
|
|
22
|
+
updateBridge
|
|
23
|
+
} from "./providerBridge.js";
|
|
24
|
+
function actionsToSpecs(actions, ctx) {
|
|
25
|
+
return actions.map((action) => {
|
|
26
|
+
const disabledResult = action.disabled?.(ctx);
|
|
27
|
+
const isDisabled = disabledResult === true || typeof disabledResult === "object" && disabledResult !== null;
|
|
28
|
+
const disabledReason = typeof disabledResult === "object" && disabledResult !== null ? disabledResult.reason : void 0;
|
|
29
|
+
return {
|
|
30
|
+
id: action.id,
|
|
31
|
+
label: resolveActionLabel(action.label, ctx),
|
|
32
|
+
iconName: action.icon.displayName ?? action.icon.name ?? "Circle",
|
|
33
|
+
category: action.category,
|
|
34
|
+
renderSlot: action.renderSlot ?? "overflow",
|
|
35
|
+
order: action.order ?? 0,
|
|
36
|
+
disabled: isDisabled,
|
|
37
|
+
disabledReason
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function specsKey(specs, contentSourceId) {
|
|
42
|
+
return `${contentSourceId}::${specs.map(
|
|
43
|
+
(s) => `${s.id}|${s.label}|${s.disabled ? 1 : 0}|${s.disabledReason ?? ""}`
|
|
44
|
+
).join(";")}`;
|
|
45
|
+
}
|
|
46
|
+
function buildContext(args) {
|
|
47
|
+
const adapter = getSourceAdapter(args.source.type);
|
|
48
|
+
const prefix = adapter.instanceKeyPrefix(args.source);
|
|
49
|
+
const instancePrefix = args.source.type === "raw" ? `${prefix}-${shortHash(args.content).slice(0, 8)}` : prefix;
|
|
50
|
+
return {
|
|
51
|
+
content: args.content,
|
|
52
|
+
source: args.source,
|
|
53
|
+
metadata: args.host?.metadata ?? null,
|
|
54
|
+
dispatch: args.dispatch,
|
|
55
|
+
getState: args.getState,
|
|
56
|
+
organizationId: args.organizationId,
|
|
57
|
+
isAuthenticated: args.isAuthenticated,
|
|
58
|
+
isAdmin: args.isAdmin,
|
|
59
|
+
isCreator: args.host?.isCreator ?? false,
|
|
60
|
+
surfaceKey: args.host?.surfaceKey ?? null,
|
|
61
|
+
onClose: () => {
|
|
62
|
+
},
|
|
63
|
+
instanceKey: (suffix) => `${instancePrefix}-${suffix}`,
|
|
64
|
+
sourceAdapter: adapter,
|
|
65
|
+
callbacks: args.callbacks,
|
|
66
|
+
extensions: args.extensions
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function useActionSurfaceProvider(args) {
|
|
70
|
+
const {
|
|
71
|
+
content,
|
|
72
|
+
source,
|
|
73
|
+
actions: actionsProp,
|
|
74
|
+
actionsVariant,
|
|
75
|
+
actionsSurfaceId
|
|
76
|
+
} = args;
|
|
77
|
+
const dispatch = useHostDispatch();
|
|
78
|
+
const getState = useHostGetState();
|
|
79
|
+
const isAuthenticated = Boolean(useIsAuthenticated());
|
|
80
|
+
const isAdmin = Boolean(useIsSuperAdmin());
|
|
81
|
+
const organizationId = useActiveOrganizationId();
|
|
82
|
+
const providerId = React.useId();
|
|
83
|
+
const contentRef = React.useRef(content ?? "");
|
|
84
|
+
const sourceRef = React.useRef(source);
|
|
85
|
+
const callbacksRef = React.useRef(actionsProp?.callbacks);
|
|
86
|
+
const extensionsRef = React.useRef(
|
|
87
|
+
actionsProp?.extensions
|
|
88
|
+
);
|
|
89
|
+
const hostRef = React.useRef(actionsProp);
|
|
90
|
+
const dispatchRef = React.useRef(dispatch);
|
|
91
|
+
const isAuthRef = React.useRef(isAuthenticated);
|
|
92
|
+
const isAdminRef = React.useRef(isAdmin);
|
|
93
|
+
React.useLayoutEffect(() => {
|
|
94
|
+
contentRef.current = content ?? "";
|
|
95
|
+
sourceRef.current = source;
|
|
96
|
+
callbacksRef.current = actionsProp?.callbacks;
|
|
97
|
+
extensionsRef.current = actionsProp?.extensions;
|
|
98
|
+
hostRef.current = actionsProp;
|
|
99
|
+
dispatchRef.current = dispatch;
|
|
100
|
+
isAuthRef.current = isAuthenticated;
|
|
101
|
+
isAdminRef.current = isAdmin;
|
|
102
|
+
});
|
|
103
|
+
const getCtx = () => buildContext({
|
|
104
|
+
content: contentRef.current,
|
|
105
|
+
source: sourceRef.current,
|
|
106
|
+
callbacks: callbacksRef.current,
|
|
107
|
+
extensions: extensionsRef.current,
|
|
108
|
+
host: hostRef.current,
|
|
109
|
+
dispatch: dispatchRef.current,
|
|
110
|
+
getState,
|
|
111
|
+
isAuthenticated: isAuthRef.current,
|
|
112
|
+
isAdmin: isAdminRef.current,
|
|
113
|
+
organizationId
|
|
114
|
+
});
|
|
115
|
+
const ctx = buildContext({
|
|
116
|
+
content: content ?? "",
|
|
117
|
+
source,
|
|
118
|
+
callbacks: actionsProp?.callbacks,
|
|
119
|
+
extensions: actionsProp?.extensions,
|
|
120
|
+
host: actionsProp,
|
|
121
|
+
dispatch,
|
|
122
|
+
getState,
|
|
123
|
+
isAuthenticated,
|
|
124
|
+
isAdmin,
|
|
125
|
+
organizationId
|
|
126
|
+
});
|
|
127
|
+
const resolvedActions = resolveActions(ctx, {
|
|
128
|
+
exclude: actionsProp?.exclude,
|
|
129
|
+
extra: actionsProp?.extra
|
|
130
|
+
});
|
|
131
|
+
const specs = actionsToSpecs(resolvedActions, ctx);
|
|
132
|
+
const targetKey = [
|
|
133
|
+
ctx.instanceKey("target"),
|
|
134
|
+
source.readOnly ? 1 : 0,
|
|
135
|
+
isAuthenticated ? 1 : 0,
|
|
136
|
+
isAdmin ? 1 : 0,
|
|
137
|
+
organizationId ?? "",
|
|
138
|
+
actionsProp?.isCreator ? 1 : 0,
|
|
139
|
+
(actionsProp?.exclude ?? []).join(","),
|
|
140
|
+
(actionsProp?.extra ?? []).map((a) => a.id).join(","),
|
|
141
|
+
shortHash(content ?? ""),
|
|
142
|
+
resolvedActions.map((a) => a.id).join(",")
|
|
143
|
+
].join("|");
|
|
144
|
+
const [targetState, setTargetState] = React.useState(() => ({
|
|
145
|
+
key: targetKey,
|
|
146
|
+
target: richDocumentClickTarget(ctx, {
|
|
147
|
+
getCtx,
|
|
148
|
+
exclude: actionsProp?.exclude ?? [],
|
|
149
|
+
extra: actionsProp?.extra ?? []
|
|
150
|
+
})
|
|
151
|
+
}));
|
|
152
|
+
if (targetState.key !== targetKey) {
|
|
153
|
+
setTargetState({
|
|
154
|
+
key: targetKey,
|
|
155
|
+
target: richDocumentClickTarget(ctx, {
|
|
156
|
+
getCtx,
|
|
157
|
+
exclude: actionsProp?.exclude ?? [],
|
|
158
|
+
extra: actionsProp?.extra ?? []
|
|
159
|
+
})
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
const target = targetState.target;
|
|
163
|
+
const contentSourceId = ctx.instanceKey("alchemy");
|
|
164
|
+
const lastSpecsKey = React.useRef("");
|
|
165
|
+
React.useEffect(() => {
|
|
166
|
+
if (actionsVariant !== "remote" || !actionsSurfaceId) return void 0;
|
|
167
|
+
registerProvider({
|
|
168
|
+
surfaceId: actionsSurfaceId,
|
|
169
|
+
registration: {
|
|
170
|
+
providerId,
|
|
171
|
+
computedActionSpecs: specs,
|
|
172
|
+
contentSourceId,
|
|
173
|
+
sourceType: source.type
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
lastSpecsKey.current = specsKey(specs, contentSourceId);
|
|
177
|
+
return () => {
|
|
178
|
+
unregisterProvider({
|
|
179
|
+
surfaceId: actionsSurfaceId,
|
|
180
|
+
providerId
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
}, [actionsVariant, actionsSurfaceId, providerId, dispatch, source.type]);
|
|
184
|
+
React.useEffect(() => {
|
|
185
|
+
if (actionsVariant !== "remote" || !actionsSurfaceId) return;
|
|
186
|
+
const key = specsKey(specs, contentSourceId);
|
|
187
|
+
if (key === lastSpecsKey.current) return;
|
|
188
|
+
lastSpecsKey.current = key;
|
|
189
|
+
updateProviderSpecs({
|
|
190
|
+
surfaceId: actionsSurfaceId,
|
|
191
|
+
providerId,
|
|
192
|
+
computedActionSpecs: specs,
|
|
193
|
+
contentSourceId
|
|
194
|
+
});
|
|
195
|
+
}, [
|
|
196
|
+
actionsVariant,
|
|
197
|
+
actionsSurfaceId,
|
|
198
|
+
providerId,
|
|
199
|
+
dispatch,
|
|
200
|
+
specs,
|
|
201
|
+
contentSourceId
|
|
202
|
+
]);
|
|
203
|
+
React.useEffect(() => {
|
|
204
|
+
registerBridge(providerId, { getCtx, resolvedActions, target });
|
|
205
|
+
return () => unregisterBridge(providerId);
|
|
206
|
+
}, [providerId]);
|
|
207
|
+
React.useEffect(() => {
|
|
208
|
+
updateBridge(providerId, { getCtx, resolvedActions, target });
|
|
209
|
+
});
|
|
210
|
+
return { ctx, getCtx, resolvedActions, target };
|
|
211
|
+
}
|
|
212
|
+
export {
|
|
213
|
+
useActionSurfaceProvider
|
|
214
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type RefObject } from "react";
|
|
2
|
+
/** Places the shared rendered-content finder in a reader's existing header. */
|
|
3
|
+
export declare function ContentFindControl({ rootRef, label, inline, onOpenChange, }: {
|
|
4
|
+
rootRef: RefObject<HTMLElement | null>;
|
|
5
|
+
label?: string;
|
|
6
|
+
inline?: boolean;
|
|
7
|
+
onOpenChange?: (open: boolean) => void;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { Search } from "lucide-react";
|
|
5
|
+
import { Button } from "@ai-matrx/design-system/controls";
|
|
6
|
+
import { RenderedFindBar } from "./RenderedFindBar.js";
|
|
7
|
+
import { surfaceOwnsKey } from "@ai-matrx/kit/keyboard-scope";
|
|
8
|
+
function ContentFindControl({
|
|
9
|
+
rootRef,
|
|
10
|
+
label = "Find in this content",
|
|
11
|
+
inline = false,
|
|
12
|
+
onOpenChange
|
|
13
|
+
}) {
|
|
14
|
+
const [open, setOpen] = useState(false);
|
|
15
|
+
const [focusRequest, setFocusRequest] = useState(0);
|
|
16
|
+
const openFind = () => {
|
|
17
|
+
setOpen(true);
|
|
18
|
+
onOpenChange?.(true);
|
|
19
|
+
setFocusRequest((value) => value + 1);
|
|
20
|
+
};
|
|
21
|
+
const closeFind = () => {
|
|
22
|
+
setOpen(false);
|
|
23
|
+
onOpenChange?.(false);
|
|
24
|
+
};
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const onKeyDown = (event) => {
|
|
27
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "f" && !event.shiftKey && !event.altKey) {
|
|
28
|
+
if (!surfaceOwnsKey(event, rootRef.current)) return;
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
setOpen(true);
|
|
31
|
+
onOpenChange?.(true);
|
|
32
|
+
setFocusRequest((value) => value + 1);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
window.addEventListener("keydown", onKeyDown, true);
|
|
36
|
+
return () => window.removeEventListener("keydown", onKeyDown, true);
|
|
37
|
+
}, [onOpenChange, rootRef]);
|
|
38
|
+
if (inline && open) return /* @__PURE__ */ jsx("div", { className: "min-w-0 w-full sm:w-[360px]", children: /* @__PURE__ */ jsx(RenderedFindBar, { rootRef, label, focusRequest, onClose: closeFind, compact: true }) });
|
|
39
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative shrink-0", children: [
|
|
40
|
+
/* @__PURE__ */ jsx(Button, { icon: /* @__PURE__ */ jsx(Search, { "aria-hidden": true }), type: "button", variant: "quiet", "aria-label": label, title: label, onClick: openFind }),
|
|
41
|
+
open && /* @__PURE__ */ jsx("div", { className: "absolute right-0 top-full z-30 mt-1 w-[min(88vw,520px)]", children: /* @__PURE__ */ jsx(RenderedFindBar, { rootRef, label, focusRequest, onClose: closeFind }) })
|
|
42
|
+
] });
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
ContentFindControl
|
|
46
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** A read-only find bar for any rendered RichDocument. Never mutates content or selection. */
|
|
2
|
+
export declare function RenderedFindBar({ rootRef, onClose, label, focusRequest, compact }: {
|
|
3
|
+
rootRef: React.RefObject<HTMLElement | null>;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
label?: string;
|
|
6
|
+
focusRequest?: number;
|
|
7
|
+
compact?: boolean;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
4
|
+
import { CaseSensitive, ChevronDown, ChevronUp, Regex, Search, WholeWord, X } from "lucide-react";
|
|
5
|
+
import { collectRenderedFindRanges } from "./renderedFind.js";
|
|
6
|
+
function highlightApi() {
|
|
7
|
+
const css = window.CSS;
|
|
8
|
+
const Highlight = window.Highlight;
|
|
9
|
+
return css?.highlights && Highlight ? { registry: css.highlights, Highlight } : null;
|
|
10
|
+
}
|
|
11
|
+
function clearHighlights(all, active) {
|
|
12
|
+
const api = highlightApi();
|
|
13
|
+
api?.registry.delete(all);
|
|
14
|
+
api?.registry.delete(active);
|
|
15
|
+
}
|
|
16
|
+
function RenderedFindBar({ rootRef, onClose, label = "Find in document", focusRequest = 0, compact = false }) {
|
|
17
|
+
const inputRef = useRef(null);
|
|
18
|
+
const [query, setQuery] = useState("");
|
|
19
|
+
const [options, setOptions] = useState({});
|
|
20
|
+
const [ranges, setRanges] = useState([]);
|
|
21
|
+
const [current, setCurrent] = useState(0);
|
|
22
|
+
const [version, setVersion] = useState(0);
|
|
23
|
+
const instance = useId().replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
24
|
+
const allHighlight = `matrx-rendered-find-${instance}`;
|
|
25
|
+
const activeHighlight = `${allHighlight}-active`;
|
|
26
|
+
const scope = `${allHighlight}-scope`;
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const root = rootRef.current;
|
|
29
|
+
if (!root) return;
|
|
30
|
+
root.classList.add(scope);
|
|
31
|
+
return () => root.classList.remove(scope);
|
|
32
|
+
}, [rootRef, scope]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
inputRef.current?.focus();
|
|
35
|
+
inputRef.current?.select();
|
|
36
|
+
}, [focusRequest]);
|
|
37
|
+
useEffect(() => () => clearHighlights(allHighlight, activeHighlight), [allHighlight, activeHighlight]);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const root = rootRef.current;
|
|
40
|
+
if (!root || !query.trim()) return;
|
|
41
|
+
let timer;
|
|
42
|
+
const observer = new MutationObserver(() => {
|
|
43
|
+
if (timer) clearTimeout(timer);
|
|
44
|
+
timer = setTimeout(() => setVersion((value) => value + 1), 150);
|
|
45
|
+
});
|
|
46
|
+
observer.observe(root, { childList: true, subtree: true, characterData: true });
|
|
47
|
+
return () => {
|
|
48
|
+
observer.disconnect();
|
|
49
|
+
if (timer) clearTimeout(timer);
|
|
50
|
+
};
|
|
51
|
+
}, [rootRef, query]);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
const root = rootRef.current;
|
|
54
|
+
const found = root ? collectRenderedFindRanges(root, query, options) : [];
|
|
55
|
+
setRanges(found);
|
|
56
|
+
setCurrent(0);
|
|
57
|
+
}, [rootRef, query, options, version]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const api = highlightApi();
|
|
60
|
+
if (api) {
|
|
61
|
+
if (ranges.length) api.registry.set(allHighlight, new api.Highlight(...ranges));
|
|
62
|
+
else api.registry.delete(allHighlight);
|
|
63
|
+
const active = ranges[current];
|
|
64
|
+
if (active) api.registry.set(activeHighlight, new api.Highlight(active));
|
|
65
|
+
else api.registry.delete(activeHighlight);
|
|
66
|
+
}
|
|
67
|
+
ranges[current]?.startContainer.parentElement?.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
68
|
+
}, [ranges, current, allHighlight, activeHighlight]);
|
|
69
|
+
const step = (direction) => {
|
|
70
|
+
if (ranges.length) setCurrent((value) => (value + direction + ranges.length) % ranges.length);
|
|
71
|
+
};
|
|
72
|
+
const toggle = (key) => setOptions((value) => ({ ...value, [key]: !value[key] }));
|
|
73
|
+
const validRegex = !options.regex || (() => {
|
|
74
|
+
try {
|
|
75
|
+
new RegExp(query);
|
|
76
|
+
return true;
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
})();
|
|
81
|
+
return /* @__PURE__ */ jsxs("div", { role: "search", "aria-label": label, "data-find-ignore": "", className: `flex min-w-0 items-center gap-1 rounded-lg border border-border bg-card px-2 py-1 shadow-sm ${compact ? "flex-nowrap" : "flex-wrap"}`, children: [
|
|
82
|
+
/* @__PURE__ */ jsx("style", { children: `.${scope}::highlight(${allHighlight}), .${scope} ::highlight(${allHighlight}) { background-color: rgb(250 204 21 / .5); color: inherit; } .${scope}::highlight(${activeHighlight}), .${scope} ::highlight(${activeHighlight}) { background-color: rgb(249 115 22 / .85); color: white; }` }),
|
|
83
|
+
/* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
84
|
+
/* @__PURE__ */ jsx("input", { ref: inputRef, type: "search", value: query, onChange: (event) => setQuery(event.target.value), onKeyDown: (event) => {
|
|
85
|
+
if (event.key === "Enter") {
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
step(event.shiftKey ? -1 : 1);
|
|
88
|
+
}
|
|
89
|
+
if (event.key === "Escape") {
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
onClose();
|
|
92
|
+
}
|
|
93
|
+
}, placeholder: label, "aria-label": label, className: "min-w-0 flex-1 bg-transparent text-base outline-none md:text-sm" }),
|
|
94
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-12 text-right text-xs tabular-nums text-muted-foreground", "aria-live": "polite", children: !validRegex ? "Invalid pattern" : !query.trim() ? "" : ranges.length ? `${current + 1} of ${ranges.length}` : "No matches" }),
|
|
95
|
+
!compact && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
96
|
+
/* @__PURE__ */ jsx(OptionButton, { label: "Match case", pressed: !!options.caseSensitive, onClick: () => toggle("caseSensitive"), children: /* @__PURE__ */ jsx(CaseSensitive, { className: "h-4 w-4" }) }),
|
|
97
|
+
/* @__PURE__ */ jsx(OptionButton, { label: "Whole word", pressed: !!options.wholeWord, onClick: () => toggle("wholeWord"), children: /* @__PURE__ */ jsx(WholeWord, { className: "h-4 w-4" }) }),
|
|
98
|
+
/* @__PURE__ */ jsx(OptionButton, { label: "Regular expression", pressed: !!options.regex, onClick: () => toggle("regex"), children: /* @__PURE__ */ jsx(Regex, { className: "h-4 w-4" }) })
|
|
99
|
+
] }),
|
|
100
|
+
/* @__PURE__ */ jsx(OptionButton, { label: "Previous match", disabled: !ranges.length, onClick: () => step(-1), children: /* @__PURE__ */ jsx(ChevronUp, { className: "h-4 w-4" }) }),
|
|
101
|
+
/* @__PURE__ */ jsx(OptionButton, { label: "Next match", disabled: !ranges.length, onClick: () => step(1), children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" }) }),
|
|
102
|
+
/* @__PURE__ */ jsx(OptionButton, { label: "Close search", onClick: onClose, children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) })
|
|
103
|
+
] });
|
|
104
|
+
}
|
|
105
|
+
function OptionButton({ label, pressed, disabled, onClick, children }) {
|
|
106
|
+
return /* @__PURE__ */ jsx("button", { type: "button", "aria-label": label, title: label, "aria-pressed": pressed, disabled, onClick, className: "rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground aria-pressed:bg-primary/10 aria-pressed:text-primary-ink disabled:opacity-40", children });
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
RenderedFindBar
|
|
110
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../../utils/rendered-find.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../../utils/rendered-find.js";
|