@agent-native/core 0.22.34 → 0.22.36

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.
Files changed (53) hide show
  1. package/dist/action.d.ts +10 -0
  2. package/dist/action.d.ts.map +1 -1
  3. package/dist/action.js.map +1 -1
  4. package/dist/client/AgentPanel.d.ts +5 -4
  5. package/dist/client/AgentPanel.d.ts.map +1 -1
  6. package/dist/client/AgentPanel.js.map +1 -1
  7. package/dist/client/MultiTabAssistantChat.d.ts +4 -5
  8. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  9. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  10. package/dist/client/index.d.ts +1 -1
  11. package/dist/client/index.d.ts.map +1 -1
  12. package/dist/client/index.js +1 -1
  13. package/dist/client/index.js.map +1 -1
  14. package/dist/client/mcp-app-host.d.ts.map +1 -1
  15. package/dist/client/mcp-app-host.js +9 -5
  16. package/dist/client/mcp-app-host.js.map +1 -1
  17. package/dist/client/sharing/ShareButton.d.ts +7 -0
  18. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  19. package/dist/client/sharing/ShareButton.js +261 -38
  20. package/dist/client/sharing/ShareButton.js.map +1 -1
  21. package/dist/client/sharing/ShareButton.spec.js +77 -0
  22. package/dist/client/sharing/ShareButton.spec.js.map +1 -1
  23. package/dist/client/use-chat-threads.d.ts.map +1 -1
  24. package/dist/client/use-chat-threads.js +72 -26
  25. package/dist/client/use-chat-threads.js.map +1 -1
  26. package/dist/client/use-chat-threads.spec.js +101 -0
  27. package/dist/client/use-chat-threads.spec.js.map +1 -1
  28. package/dist/index.browser.d.ts +1 -1
  29. package/dist/index.browser.d.ts.map +1 -1
  30. package/dist/index.browser.js +1 -1
  31. package/dist/index.browser.js.map +1 -1
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +1 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/mcp/build-server.d.ts +2 -0
  37. package/dist/mcp/build-server.d.ts.map +1 -1
  38. package/dist/mcp/build-server.js +55 -21
  39. package/dist/mcp/build-server.js.map +1 -1
  40. package/dist/mcp/embed-app.d.ts.map +1 -1
  41. package/dist/mcp/embed-app.js +21 -16
  42. package/dist/mcp/embed-app.js.map +1 -1
  43. package/dist/mcp/server.js +3 -3
  44. package/dist/mcp/server.js.map +1 -1
  45. package/dist/org/handlers.d.ts +2 -0
  46. package/dist/org/handlers.d.ts.map +1 -1
  47. package/dist/org/handlers.js +46 -5
  48. package/dist/org/handlers.js.map +1 -1
  49. package/docs/content/actions.md +6 -2
  50. package/docs/content/client.md +2 -1
  51. package/docs/content/external-agents.md +16 -5
  52. package/docs/content/mcp-protocol.md +18 -6
  53. 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 useOrgMembers() {
139
+ function useOrgMemberSearch(query, enabled) {
140
+ const search = query.trim();
138
141
  const [members, setMembers] = useState([]);
139
- useEffect(() => {
140
- let cancelled = false;
141
- fetch(agentNativePath("/_agent-native/org/members"))
142
- .then((r) => (r.ok ? r.json() : null))
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 (cancelled || !data)
181
+ if (controller.signal.aborted || requestId !== requestIdRef.current)
145
182
  return;
146
- const list = Array.isArray(data?.members) ? data.members : [];
147
- setMembers(list
148
- .map((m) => ({
149
- email: typeof m?.email === "string" ? m.email : "",
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
- cancelled = true;
222
+ clearTimeout(timeout);
223
+ abortRef.current?.abort();
157
224
  };
158
- }, []);
159
- return members;
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: "People with access" }), _jsx("div", { className: "mb-4 h-7 rounded-md bg-muted animate-pulse" }), _jsx("div", { className: "mb-2 text-sm font-semibold", children: "General access" }), _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" }) })] }));
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("input", { type: "email", placeholder: policy.requireOrgMemberForUserShares
358
- ? "Add people from your organization"
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
- }, onKeyDown: (e) => {
364
- if (e.key === "Enter")
365
- handleAdd();
366
- }, list: orgMembers.length > 0 ? datalistId : undefined, autoComplete: "off", className: "flex-1 min-w-0 h-9 rounded-md border border-input bg-background px-3 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" }), orgMembers.length > 0 ? (_jsx("datalist", { id: datalistId, children: orgMembers
367
- .filter((m) => m.email !== sharesQuery.data?.ownerEmail &&
368
- !(sharesQuery.data?.shares ?? []).some((s) => s.principalType === "user" &&
369
- s.principalId === m.email))
370
- .map((m) => (_jsx("option", { value: m.email, label: m.name ?? undefined }, m.email))) })) : null, _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: "People with access" }), _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, orgMembers) }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: displayName(data.ownerEmail, orgMembers) }), _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"
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, orgMembers), org: s.principalType === "org" }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: s.principalType === "org"
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, orgMembers) }), 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: "General access" }), _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 &&
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, 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, _jsx("div", { className: "mt-2 flex justify-end", children: _jsx("button", { type: "button", onClick: handleDone, className: BUTTON_PRIMARY_SM, children: "Done" }) })] }));
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);