@agent-native/core 0.22.35 → 0.22.37
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/action.d.ts +10 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +5 -4
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts +4 -5
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/mcp-app-host.d.ts.map +1 -1
- package/dist/client/mcp-app-host.js +9 -5
- package/dist/client/mcp-app-host.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +7 -0
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +261 -38
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/sharing/ShareButton.spec.js +77 -0
- package/dist/client/sharing/ShareButton.spec.js.map +1 -1
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js +72 -26
- package/dist/client/use-chat-threads.js.map +1 -1
- package/dist/client/use-chat-threads.spec.js +101 -0
- package/dist/client/use-chat-threads.spec.js.map +1 -1
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/build-server.d.ts +2 -0
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +62 -23
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/embed-app.d.ts.map +1 -1
- package/dist/mcp/embed-app.js +30 -22
- package/dist/mcp/embed-app.js.map +1 -1
- package/dist/mcp/server.js +3 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/org/handlers.d.ts +2 -0
- package/dist/org/handlers.d.ts.map +1 -1
- package/dist/org/handlers.js +46 -5
- package/dist/org/handlers.js.map +1 -1
- package/dist/server/embed-session.d.ts.map +1 -1
- package/dist/server/embed-session.js +4 -0
- package/dist/server/embed-session.js.map +1 -1
- package/docs/content/actions.md +6 -2
- package/docs/content/client.md +2 -1
- package/docs/content/external-agents.md +16 -5
- package/docs/content/mcp-protocol.md +18 -6
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useMemo, useRef, useState, } from "react";
|
|
2
|
+
import { useCallback, useEffect, useId, useMemo, useRef, useState, } from "react";
|
|
3
3
|
import { useQueryClient } from "@tanstack/react-query";
|
|
4
|
-
import { IconLock, IconBuilding, IconWorld, IconTrash, IconCheck, IconChevronDown, IconCopy, IconSearchOff, } from "@tabler/icons-react";
|
|
4
|
+
import { IconLock, IconBuilding, IconWorld, IconTrash, IconCheck, IconChevronDown, IconCopy, IconLoader2, IconSearch, IconSearchOff, } from "@tabler/icons-react";
|
|
5
5
|
import * as Select from "@radix-ui/react-select";
|
|
6
|
-
import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
|
|
6
|
+
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
|
|
7
7
|
import { useActionQuery, useActionMutation } from "../use-action.js";
|
|
8
8
|
import { cn } from "../utils.js";
|
|
9
9
|
import { agentNativePath } from "../api-path.js";
|
|
@@ -14,6 +14,8 @@ const BUTTON_OUTLINE_SM = cn(BUTTON_BASE, "h-9 px-3 border border-[hsl(var(--sid
|
|
|
14
14
|
const BUTTON_PRIMARY_SM = cn(BUTTON_BASE, "h-9 px-4 bg-primary text-primary-foreground hover:bg-primary/90");
|
|
15
15
|
const BUTTON_GHOST_ICON = cn(BUTTON_BASE, "h-7 w-7 p-0 text-muted-foreground hover:bg-accent hover:text-accent-foreground");
|
|
16
16
|
const SHARE_POPOVER_SURFACE = "border-[hsl(var(--sidebar-border,var(--border)))] bg-[hsl(var(--sidebar-background,var(--popover)))]";
|
|
17
|
+
const MEMBER_SUGGESTION_LIMIT = 25;
|
|
18
|
+
const MEMBER_SEARCH_DEBOUNCE_MS = 140;
|
|
17
19
|
const VIS_META = {
|
|
18
20
|
private: {
|
|
19
21
|
label: "Private",
|
|
@@ -134,29 +136,136 @@ export function ShareButton(props) {
|
|
|
134
136
|
: IconLock;
|
|
135
137
|
return (_jsxs(Popover, { open: open, onOpenChange: handleOpenChange, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: BUTTON_OUTLINE_SM, children: [loaded ? (_jsx(TriggerIcon, { size: 16, strokeWidth: 1.75 })) : (_jsx("span", { "aria-hidden": true, className: "inline-block h-4 w-4 rounded-sm bg-muted animate-pulse" })), _jsx("span", { children: "Share" })] }) }), _jsx(PopoverContent, { align: "end", sideOffset: 6, className: cn("z-[2000] w-[min(460px,92vw)] rounded-lg p-4 shadow-lg", SHARE_POPOVER_SURFACE), onOpenAutoFocus: (e) => e.preventDefault(), children: _jsx(SharePanel, { ...props, sharesQuery: sharesQuery, visibilityOverride: pendingVisibility, onVisibilityChange: handleVisibilityChange, onClose: () => handleOpenChange(false) }) })] }));
|
|
136
138
|
}
|
|
137
|
-
function
|
|
139
|
+
function useOrgMemberSearch(query, enabled) {
|
|
140
|
+
const search = query.trim();
|
|
138
141
|
const [members, setMembers] = useState([]);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
const [nextOffset, setNextOffset] = useState(null);
|
|
143
|
+
const [hasMore, setHasMore] = useState(false);
|
|
144
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
145
|
+
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
|
146
|
+
const [error, setError] = useState(false);
|
|
147
|
+
const requestIdRef = useRef(0);
|
|
148
|
+
const abortRef = useRef(null);
|
|
149
|
+
const fetchPage = useCallback((offset, append) => {
|
|
150
|
+
if (!enabled)
|
|
151
|
+
return;
|
|
152
|
+
const requestId = ++requestIdRef.current;
|
|
153
|
+
abortRef.current?.abort();
|
|
154
|
+
const controller = new AbortController();
|
|
155
|
+
abortRef.current = controller;
|
|
156
|
+
if (append) {
|
|
157
|
+
setIsLoadingMore(true);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
setIsLoading(true);
|
|
161
|
+
setMembers([]);
|
|
162
|
+
setNextOffset(null);
|
|
163
|
+
setHasMore(false);
|
|
164
|
+
}
|
|
165
|
+
setError(false);
|
|
166
|
+
const params = new URLSearchParams();
|
|
167
|
+
if (search)
|
|
168
|
+
params.set("search", search);
|
|
169
|
+
params.set("limit", String(MEMBER_SUGGESTION_LIMIT));
|
|
170
|
+
params.set("offset", String(offset));
|
|
171
|
+
fetch(`${agentNativePath("/_agent-native/org/members")}?${params}`, {
|
|
172
|
+
credentials: "include",
|
|
173
|
+
signal: controller.signal,
|
|
174
|
+
})
|
|
175
|
+
.then((response) => {
|
|
176
|
+
if (!response.ok)
|
|
177
|
+
throw new Error("Could not load people");
|
|
178
|
+
return response.json();
|
|
179
|
+
})
|
|
143
180
|
.then((data) => {
|
|
144
|
-
if (
|
|
181
|
+
if (controller.signal.aborted || requestId !== requestIdRef.current)
|
|
145
182
|
return;
|
|
146
|
-
const
|
|
147
|
-
setMembers(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
name: typeof m?.name === "string" ? m.name : null,
|
|
151
|
-
}))
|
|
152
|
-
.filter((m) => m.email));
|
|
183
|
+
const nextMembers = normalizeMembers(data?.members);
|
|
184
|
+
setMembers((prev) => append ? mergeMembers(prev, nextMembers) : nextMembers);
|
|
185
|
+
setHasMore(data?.hasMore === true);
|
|
186
|
+
setNextOffset(typeof data?.nextOffset === "number" ? data.nextOffset : null);
|
|
153
187
|
})
|
|
154
|
-
.catch(() => {
|
|
188
|
+
.catch((err) => {
|
|
189
|
+
if (controller.signal.aborted || requestId !== requestIdRef.current)
|
|
190
|
+
return;
|
|
191
|
+
setError(true);
|
|
192
|
+
setHasMore(false);
|
|
193
|
+
setNextOffset(null);
|
|
194
|
+
if (!append)
|
|
195
|
+
setMembers([]);
|
|
196
|
+
if (process.env.NODE_ENV === "development") {
|
|
197
|
+
console.warn("[ShareButton] org member search failed", err);
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
.finally(() => {
|
|
201
|
+
if (controller.signal.aborted || requestId !== requestIdRef.current)
|
|
202
|
+
return;
|
|
203
|
+
if (append)
|
|
204
|
+
setIsLoadingMore(false);
|
|
205
|
+
else
|
|
206
|
+
setIsLoading(false);
|
|
207
|
+
});
|
|
208
|
+
}, [enabled, search]);
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
if (!enabled) {
|
|
211
|
+
abortRef.current?.abort();
|
|
212
|
+
setMembers([]);
|
|
213
|
+
setNextOffset(null);
|
|
214
|
+
setHasMore(false);
|
|
215
|
+
setIsLoading(false);
|
|
216
|
+
setIsLoadingMore(false);
|
|
217
|
+
setError(false);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const timeout = setTimeout(() => fetchPage(0, false), search ? MEMBER_SEARCH_DEBOUNCE_MS : 0);
|
|
155
221
|
return () => {
|
|
156
|
-
|
|
222
|
+
clearTimeout(timeout);
|
|
223
|
+
abortRef.current?.abort();
|
|
157
224
|
};
|
|
158
|
-
}, []);
|
|
159
|
-
|
|
225
|
+
}, [enabled, fetchPage, search]);
|
|
226
|
+
const loadMore = useCallback(() => {
|
|
227
|
+
if (!enabled || !hasMore || nextOffset === null)
|
|
228
|
+
return;
|
|
229
|
+
if (isLoading || isLoadingMore)
|
|
230
|
+
return;
|
|
231
|
+
fetchPage(nextOffset, true);
|
|
232
|
+
}, [enabled, fetchPage, hasMore, isLoading, isLoadingMore, nextOffset]);
|
|
233
|
+
return {
|
|
234
|
+
members,
|
|
235
|
+
isLoading,
|
|
236
|
+
isLoadingMore,
|
|
237
|
+
hasMore,
|
|
238
|
+
error,
|
|
239
|
+
loadMore,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function normalizeMembers(value) {
|
|
243
|
+
if (!Array.isArray(value))
|
|
244
|
+
return [];
|
|
245
|
+
return value
|
|
246
|
+
.map((m) => ({
|
|
247
|
+
email: typeof m?.email === "string" ? m.email : "",
|
|
248
|
+
name: typeof m?.name === "string" ? m.name : null,
|
|
249
|
+
role: typeof m?.role === "string" ? m.role : null,
|
|
250
|
+
joinedAt: typeof m?.joinedAt === "number"
|
|
251
|
+
? m.joinedAt
|
|
252
|
+
: typeof m?.joined_at === "number"
|
|
253
|
+
? m.joined_at
|
|
254
|
+
: null,
|
|
255
|
+
}))
|
|
256
|
+
.filter((m) => m.email);
|
|
257
|
+
}
|
|
258
|
+
function mergeMembers(existing, next) {
|
|
259
|
+
const seen = new Set(existing.map((m) => m.email.toLowerCase()));
|
|
260
|
+
const merged = [...existing];
|
|
261
|
+
for (const member of next) {
|
|
262
|
+
const key = member.email.toLowerCase();
|
|
263
|
+
if (seen.has(key))
|
|
264
|
+
continue;
|
|
265
|
+
seen.add(key);
|
|
266
|
+
merged.push(member);
|
|
267
|
+
}
|
|
268
|
+
return merged;
|
|
160
269
|
}
|
|
161
270
|
function SharePanel(props) {
|
|
162
271
|
const { resourceType, resourceId, resourceTitle, sharesQuery, visibilityOverride, onVisibilityChange, onClose, } = props;
|
|
@@ -166,9 +275,8 @@ function SharePanel(props) {
|
|
|
166
275
|
const [role, setRole] = useState("viewer");
|
|
167
276
|
const [notifyPeople, setNotifyPeople] = useState(true);
|
|
168
277
|
const [shareError, setShareError] = useState(null);
|
|
278
|
+
const [suggestionsOpen, setSuggestionsOpen] = useState(false);
|
|
169
279
|
const hasInviteEmail = email.trim().length > 0;
|
|
170
|
-
const orgMembers = useOrgMembers();
|
|
171
|
-
const datalistId = `share-autocomplete-${resourceType}-${resourceId}`;
|
|
172
280
|
// Optimistic overlays so clicks feel instant.
|
|
173
281
|
const [pendingAdds, setPendingAdds] = useState([]);
|
|
174
282
|
const [pendingRemoves, setPendingRemoves] = useState(new Set());
|
|
@@ -198,6 +306,13 @@ function SharePanel(props) {
|
|
|
198
306
|
const visibility = visibilityOverride ?? data?.visibility ?? "private";
|
|
199
307
|
const canManage = data?.role === "owner" || data?.role === "admin";
|
|
200
308
|
const meta = visibilityMeta(visibility, props.visibilityCopy);
|
|
309
|
+
const peopleAccessLabel = props.peopleAccessLabel ?? "People with access";
|
|
310
|
+
const generalAccessLabel = props.generalAccessLabel ?? "General access";
|
|
311
|
+
const shareLinks = (_jsxs(_Fragment, { children: [props.shareUrl ? (_jsx(CopyLinkField, { value: props.shareUrl, label: props.shareUrlLabel, description: props.shareUrlDescription })) : props.shareUrlPlaceholder ? (_jsxs("div", { className: "mb-4 rounded-md border border-dashed border-border bg-muted/20 px-3 py-2.5 text-xs text-muted-foreground", children: [props.shareUrlLabel ? (_jsx("div", { className: "mb-0.5 font-medium text-foreground", children: props.shareUrlLabel })) : null, props.shareUrlPlaceholder] })) : null, props.secondaryShareUrl ? (_jsx(CopyLinkField, { value: props.secondaryShareUrl, label: props.secondaryShareUrlLabel, description: props.secondaryShareUrlDescription })) : null] }));
|
|
312
|
+
const showShareLinks = Boolean(props.shareUrl) ||
|
|
313
|
+
Boolean(props.shareUrlPlaceholder) ||
|
|
314
|
+
Boolean(props.secondaryShareUrl);
|
|
315
|
+
const shareUrlPlacement = props.shareUrlPlacement ?? "bottom";
|
|
201
316
|
const serverShares = data?.shares ?? [];
|
|
202
317
|
const shares = [
|
|
203
318
|
...serverShares
|
|
@@ -205,6 +320,17 @@ function SharePanel(props) {
|
|
|
205
320
|
.map((s) => ({ ...s, role: roleOverrides[keyOf(s)] ?? s.role })),
|
|
206
321
|
...pendingAdds,
|
|
207
322
|
];
|
|
323
|
+
const memberSearch = useOrgMemberSearch(email, canManage && suggestionsOpen);
|
|
324
|
+
const excludedMemberEmails = new Set();
|
|
325
|
+
if (data?.ownerEmail)
|
|
326
|
+
excludedMemberEmails.add(data.ownerEmail.toLowerCase());
|
|
327
|
+
for (const s of shares) {
|
|
328
|
+
if (s.principalType === "user") {
|
|
329
|
+
excludedMemberEmails.add(s.principalId.toLowerCase());
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const memberSuggestions = memberSearch.members.filter((m) => !excludedMemberEmails.has(m.email.toLowerCase()));
|
|
333
|
+
const knownMembers = memberSearch.members;
|
|
208
334
|
const handleVisibility = (next) => {
|
|
209
335
|
if (next === visibility)
|
|
210
336
|
return;
|
|
@@ -244,6 +370,7 @@ function SharePanel(props) {
|
|
|
244
370
|
setShareError(null);
|
|
245
371
|
setPendingAdds((p) => [...p, optimistic]);
|
|
246
372
|
setEmail("");
|
|
373
|
+
setSuggestionsOpen(false);
|
|
247
374
|
addInFlight(k);
|
|
248
375
|
share.mutate({
|
|
249
376
|
resourceType,
|
|
@@ -352,29 +479,125 @@ function SharePanel(props) {
|
|
|
352
479
|
? `Share "${resourceTitle}"`
|
|
353
480
|
: `Share ${resourceType}`;
|
|
354
481
|
if (isLoading) {
|
|
355
|
-
return (_jsxs("div", { children: [_jsx("div", { className: "mb-3 truncate text-base font-semibold", title: titleText, children: titleText }), _jsx("div", { className: "mb-4 h-9 rounded-md bg-muted animate-pulse" }), _jsx("div", { className: "mb-2 text-sm font-semibold", children:
|
|
482
|
+
return (_jsxs("div", { children: [_jsx("div", { className: "mb-3 truncate text-base font-semibold", title: titleText, children: titleText }), _jsx("div", { className: "mb-4 h-9 rounded-md bg-muted animate-pulse" }), _jsx("div", { className: "mb-2 text-sm font-semibold", children: peopleAccessLabel }), _jsx("div", { className: "mb-4 h-7 rounded-md bg-muted animate-pulse" }), _jsx("div", { className: "mb-2 text-sm font-semibold", children: generalAccessLabel }), _jsx("div", { className: "mb-4 h-9 rounded-md bg-muted animate-pulse" }), _jsx("div", { className: "mt-2 flex justify-end", children: _jsx("button", { type: "button", onClick: onClose, className: BUTTON_PRIMARY_SM, children: "Done" }) })] }));
|
|
356
483
|
}
|
|
357
|
-
return (_jsxs("div", { children: [_jsx("div", { className: "mb-3 truncate text-base font-semibold", title: titleText, children: titleText }), canManage ? (_jsxs("div", { className: "mb-4 space-y-2", children: [_jsxs("div", { className: "flex items-stretch gap-2", children: [_jsx(
|
|
358
|
-
|
|
359
|
-
: "Add people by email", value: email, onChange: (e) => {
|
|
360
|
-
setEmail(e.target.value);
|
|
484
|
+
return (_jsxs("div", { children: [_jsx("div", { className: "mb-3 truncate text-base font-semibold", title: titleText, children: titleText }), showShareLinks && shareUrlPlacement === "top" ? shareLinks : null, canManage ? (_jsxs("div", { className: "mb-4 space-y-2", children: [_jsxs("div", { className: "flex items-stretch gap-2", children: [_jsx(MemberAutocomplete, { value: email, open: suggestionsOpen, onOpenChange: setSuggestionsOpen, onValueChange: (next) => {
|
|
485
|
+
setEmail(next);
|
|
361
486
|
if (shareError)
|
|
362
487
|
setShareError(null);
|
|
363
|
-
},
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
488
|
+
}, onSelectMember: (member) => {
|
|
489
|
+
setEmail(member.email);
|
|
490
|
+
setSuggestionsOpen(false);
|
|
491
|
+
if (shareError)
|
|
492
|
+
setShareError(null);
|
|
493
|
+
}, onSubmit: handleAdd, placeholder: policy.requireOrgMemberForUserShares
|
|
494
|
+
? "Add people from your organization"
|
|
495
|
+
: "Add people by email", suggestions: memberSuggestions, search: memberSearch }), _jsx(RoleSelect, { value: role, onChange: setRole })] }), shareError ? (_jsx("div", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: shareError })) : null, hasInviteEmail ? (_jsxs("label", { className: "inline-flex items-center gap-2 text-xs text-muted-foreground", children: [_jsx("input", { type: "checkbox", checked: notifyPeople, onChange: (e) => setNotifyPeople(e.target.checked), className: "h-4 w-4 rounded border-input accent-primary" }), "Notify people"] })) : null] })) : null, _jsx("div", { className: "mb-2 text-sm font-semibold", children: peopleAccessLabel }), _jsxs("ul", { className: "mb-4 flex flex-col gap-1 list-none p-0 m-0", children: [data?.ownerEmail ? (_jsxs("li", { className: "flex items-center gap-3 px-1 py-1.5 text-sm", children: [_jsx(Avatar, { label: displayName(data.ownerEmail, knownMembers) }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: displayName(data.ownerEmail, knownMembers) }), _jsx("span", { className: "text-xs text-muted-foreground", children: "Owner" })] })) : null, shares.map((s) => (_jsxs("li", { className: cn("flex items-center gap-3 px-1 py-1.5 text-sm", inFlight.has(keyOf(s)) && "opacity-60"), children: [_jsx(Avatar, { label: s.principalType === "org"
|
|
371
496
|
? s.principalId
|
|
372
|
-
: displayName(s.principalId,
|
|
497
|
+
: displayName(s.principalId, knownMembers), org: s.principalType === "org" }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: s.principalType === "org"
|
|
373
498
|
? s.principalId
|
|
374
|
-
: displayName(s.principalId,
|
|
499
|
+
: displayName(s.principalId, knownMembers) }), canManage ? (_jsx(RoleSelect, { value: s.role, onChange: (r) => handleChangeRole(s, r), disabled: inFlight.has(keyOf(s)), plain: true })) : (_jsx("span", { className: "text-xs text-muted-foreground", children: cap(s.role) })), canManage ? (_jsx("button", { type: "button", "aria-label": "Remove", onClick: () => handleRemove(s), disabled: inFlight.has(keyOf(s)), className: BUTTON_GHOST_ICON, children: _jsx(IconTrash, { size: 14 }) })) : null] }, keyOf(s)))), !shares.length && !data?.ownerEmail ? (_jsx("li", { className: "px-1 py-1.5 text-sm text-muted-foreground", children: "No one has access yet." })) : null] }), _jsx("div", { className: "mb-2 text-sm font-semibold", children: generalAccessLabel }), _jsxs("div", { className: "mb-4 flex items-center gap-3", children: [_jsx("span", { "aria-hidden": true, className: "inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground", children: _jsx(meta.Icon, { size: 16, strokeWidth: 1.75 }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(VisibilitySelect, { value: visibility, onChange: handleVisibility, disabled: !canManage, visibilityCopy: props.visibilityCopy, allowPublic: policy.allowPublic }), _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground", children: meta.description })] })] }), visibility === "org" && props.hideInSearchControl ? (_jsxs("button", { type: "button", role: "switch", "aria-checked": props.hideInSearchControl.checked, disabled: !canManage || props.hideInSearchControl.pending, onClick: handleHideInSearch, className: cn("mb-4 flex w-full items-center gap-3 rounded-md border border-border/70 bg-muted/25 px-3 py-2.5 text-left transition-colors hover:bg-accent/45 disabled:cursor-not-allowed disabled:opacity-60", props.hideInSearchControl.checked &&
|
|
375
500
|
"border-border bg-accent/35 text-foreground"), children: [_jsx("span", { "aria-hidden": true, className: cn("relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-border bg-muted-foreground/25 transition-colors", props.hideInSearchControl.checked &&
|
|
376
501
|
"border-primary/70 bg-primary"), children: _jsx("span", { className: cn("ml-0.5 size-4 rounded-full bg-background shadow-sm transition-transform", props.hideInSearchControl.checked && "translate-x-4") }) }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsxs("span", { className: "flex items-center gap-1.5 text-sm font-medium text-foreground", children: [_jsx(IconSearchOff, { size: 14, strokeWidth: 1.8 }), props.hideInSearchControl.label ?? "Hide in search"] }), _jsx("span", { className: "mt-0.5 block text-xs leading-5 text-muted-foreground", children: props.hideInSearchControl.description ??
|
|
377
|
-
"People with the link can still open this." })] })] })) : null, props.accessNote ? (_jsx("div", { className: "mb-4 rounded-md border border-border bg-muted/35 p-3 text-xs text-muted-foreground", children: props.accessNote })) : null,
|
|
502
|
+
"People with the link can still open this." })] })] })) : null, props.accessNote ? (_jsx("div", { className: "mb-4 rounded-md border border-border bg-muted/35 p-3 text-xs text-muted-foreground", children: props.accessNote })) : null, showShareLinks && shareUrlPlacement === "bottom" ? shareLinks : null, _jsx("div", { className: "mt-2 flex justify-end", children: _jsx("button", { type: "button", onClick: handleDone, className: BUTTON_PRIMARY_SM, children: "Done" }) })] }));
|
|
503
|
+
}
|
|
504
|
+
function MemberAutocomplete({ value, open, onOpenChange, onValueChange, onSelectMember, onSubmit, placeholder, suggestions, search, }) {
|
|
505
|
+
const rawListboxId = useId();
|
|
506
|
+
const listboxId = rawListboxId.replace(/:/g, "");
|
|
507
|
+
const inputRef = useRef(null);
|
|
508
|
+
const [activeIndex, setActiveIndex] = useState(-1);
|
|
509
|
+
const activeMember = activeIndex >= 0 && activeIndex < suggestions.length
|
|
510
|
+
? suggestions[activeIndex]
|
|
511
|
+
: null;
|
|
512
|
+
useEffect(() => {
|
|
513
|
+
setActiveIndex(-1);
|
|
514
|
+
}, [value]);
|
|
515
|
+
useEffect(() => {
|
|
516
|
+
if (activeIndex >= suggestions.length) {
|
|
517
|
+
setActiveIndex(suggestions.length > 0 ? suggestions.length - 1 : -1);
|
|
518
|
+
}
|
|
519
|
+
}, [activeIndex, suggestions.length]);
|
|
520
|
+
useEffect(() => {
|
|
521
|
+
if (activeIndex < 0)
|
|
522
|
+
return;
|
|
523
|
+
document
|
|
524
|
+
.getElementById(optionId(listboxId, activeIndex))
|
|
525
|
+
?.scrollIntoView({ block: "nearest" });
|
|
526
|
+
}, [activeIndex, listboxId]);
|
|
527
|
+
const chooseMember = (member) => {
|
|
528
|
+
onSelectMember(member);
|
|
529
|
+
onOpenChange(false);
|
|
530
|
+
inputRef.current?.focus();
|
|
531
|
+
};
|
|
532
|
+
const handleKeyDown = (event) => {
|
|
533
|
+
if (event.key === "ArrowDown") {
|
|
534
|
+
event.preventDefault();
|
|
535
|
+
onOpenChange(true);
|
|
536
|
+
if (suggestions.length === 0)
|
|
537
|
+
return;
|
|
538
|
+
setActiveIndex((prev) => {
|
|
539
|
+
if (prev >= suggestions.length - 1) {
|
|
540
|
+
if (search.hasMore && !search.isLoadingMore)
|
|
541
|
+
search.loadMore();
|
|
542
|
+
return suggestions.length - 1;
|
|
543
|
+
}
|
|
544
|
+
return prev + 1;
|
|
545
|
+
});
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
if (event.key === "ArrowUp") {
|
|
549
|
+
event.preventDefault();
|
|
550
|
+
onOpenChange(true);
|
|
551
|
+
if (suggestions.length === 0)
|
|
552
|
+
return;
|
|
553
|
+
setActiveIndex((prev) => (prev <= 0 ? suggestions.length - 1 : prev - 1));
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
if (event.key === "Enter") {
|
|
557
|
+
if (open && activeMember) {
|
|
558
|
+
event.preventDefault();
|
|
559
|
+
chooseMember(activeMember);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (value.trim()) {
|
|
563
|
+
event.preventDefault();
|
|
564
|
+
onSubmit();
|
|
565
|
+
}
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (event.key === "Escape" && open) {
|
|
569
|
+
event.preventDefault();
|
|
570
|
+
event.stopPropagation();
|
|
571
|
+
onOpenChange(false);
|
|
572
|
+
setActiveIndex(-1);
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
const handleScroll = (event) => {
|
|
576
|
+
const target = event.currentTarget;
|
|
577
|
+
if (search.hasMore &&
|
|
578
|
+
!search.isLoadingMore &&
|
|
579
|
+
target.scrollTop + target.clientHeight >= target.scrollHeight - 24) {
|
|
580
|
+
search.loadMore();
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, children: [_jsx(PopoverAnchor, { asChild: true, children: _jsxs("div", { className: "relative flex-1 min-w-0", children: [_jsx(IconSearch, { "aria-hidden": true, size: 15, strokeWidth: 1.8, className: "pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground" }), _jsx("input", { ref: inputRef, type: "email", role: "combobox", "aria-autocomplete": "list", "aria-expanded": open, "aria-controls": open ? listboxId : undefined, "aria-activedescendant": activeIndex >= 0 ? optionId(listboxId, activeIndex) : undefined, placeholder: placeholder, value: value, onChange: (event) => {
|
|
584
|
+
onValueChange(event.target.value);
|
|
585
|
+
onOpenChange(true);
|
|
586
|
+
}, onFocus: () => onOpenChange(true), onBlur: () => {
|
|
587
|
+
setTimeout(() => {
|
|
588
|
+
if (document.activeElement !== inputRef.current) {
|
|
589
|
+
onOpenChange(false);
|
|
590
|
+
}
|
|
591
|
+
}, 0);
|
|
592
|
+
}, onKeyDown: handleKeyDown, autoComplete: "off", className: "h-9 w-full min-w-0 rounded-md border border-input bg-background pl-8 pr-8 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background" }), search.isLoading ? (_jsx(IconLoader2, { "aria-hidden": true, size: 15, strokeWidth: 1.8, className: "pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 animate-spin text-muted-foreground" })) : null] }) }), _jsx(PopoverContent, { align: "start", sideOffset: 4, onOpenAutoFocus: (event) => event.preventDefault(), className: cn("z-[2200] w-[var(--radix-popper-anchor-width)] min-w-[18rem] rounded-md p-1 shadow-lg", SHARE_POPOVER_SURFACE), children: _jsxs("div", { id: listboxId, role: "listbox", className: "max-h-56 overflow-y-auto overflow-x-hidden", onScroll: handleScroll, children: [suggestions.map((member, index) => {
|
|
593
|
+
const active = index === activeIndex;
|
|
594
|
+
return (_jsxs("div", { id: optionId(listboxId, index), role: "option", "aria-selected": active, onMouseDown: (event) => event.preventDefault(), onMouseEnter: () => setActiveIndex(index), onClick: () => chooseMember(member), className: cn("flex cursor-pointer select-none flex-col rounded-sm px-3 py-2 text-sm outline-none", active
|
|
595
|
+
? "bg-accent text-accent-foreground"
|
|
596
|
+
: "text-foreground hover:bg-accent hover:text-accent-foreground"), children: [_jsx("span", { className: "truncate font-medium", children: member.name?.trim() || member.email }), member.name?.trim() ? (_jsx("span", { className: "truncate text-xs text-muted-foreground", children: member.email })) : null] }, member.email));
|
|
597
|
+
}), search.isLoading && suggestions.length === 0 ? (_jsx("div", { className: "px-3 py-3 text-sm text-muted-foreground", children: "Searching..." })) : null, search.error ? (_jsx("div", { className: "px-3 py-3 text-sm text-muted-foreground", children: "Could not load people." })) : null, !search.isLoading && !search.error && suggestions.length === 0 ? (_jsx("div", { className: "px-3 py-3 text-sm text-muted-foreground", children: value.trim() ? "No matches." : "No people found." })) : null, search.isLoadingMore ? (_jsxs("div", { className: "flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground", children: [_jsx(IconLoader2, { "aria-hidden": true, size: 14, strokeWidth: 1.8, className: "animate-spin" }), "Loading..."] })) : null, search.hasMore && !search.isLoadingMore ? (_jsx("button", { type: "button", onMouseDown: (event) => event.preventDefault(), onClick: search.loadMore, className: "mt-1 flex w-full items-center justify-center rounded-sm px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-accent-foreground", children: "Load more" })) : null] }) })] }));
|
|
598
|
+
}
|
|
599
|
+
function optionId(baseId, index) {
|
|
600
|
+
return `${baseId}-option-${index}`;
|
|
378
601
|
}
|
|
379
602
|
function CopyLinkField({ value, label = "Share link", description, }) {
|
|
380
603
|
const [copied, setCopied] = useState(false);
|