@proveanything/smartlinks-utils-ui 0.9.13 → 0.10.0

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.
@@ -6,7 +6,7 @@ import { cn } from '../../chunk-L7FQ52F5.js';
6
6
  import { parsedRefToTarget, parsedRefToScope, matchRecords, scopesEqual, getRecordById, listRecords, upsertRecord, updateRecord, createRecord, removeRecord } from '../../chunk-KA4MKRHL.js';
7
7
  export { bulkDelete, bulkUpsert, createRecord, getRecordById, listRecords, matchRecords, parsedRefToScope, parsedRefToTarget, removeRecord, restoreRecord, scopesEqual, upsertRecord } from '../../chunk-KA4MKRHL.js';
8
8
  import { createContext, useState, useEffect, useCallback, useMemo, useRef, useContext, useSyncExternalStore, useLayoutEffect, createElement, useId } from 'react';
9
- import { ChevronDown, Database, Lightbulb, SearchX, Inbox, LayoutGrid, Eye, MoreHorizontal, Download, Upload, Trash2, Copy, Pencil, Plus, CircleDashed, ArrowDownLeft, CheckCircle2, List, SlidersHorizontal, Globe, Tag, Boxes, Layers, Package, Rows3, ChevronRight, Eraser, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, CornerDownLeft, Circle, AlertCircle, Undo2, Save, Loader2, XCircle, ArrowRight, BookOpen, Globe2, Target, Check, Settings2 } from 'lucide-react';
9
+ import { ChevronDown, Database, Lightbulb, SearchX, Inbox, LayoutGrid, Eye, MoreHorizontal, Download, Upload, Trash2, Copy, Pencil, Plus, CircleDashed, ArrowDownLeft, CheckCircle2, List, SlidersHorizontal, Globe, Tag, Boxes, Layers, Package, Target, Rows3, ChevronRight, Eraser, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, CornerDownLeft, Circle, CopyPlus, AlertCircle, Undo2, Save, Loader2, XCircle, ArrowRight, BookOpen, Globe2, Check, Settings2 } from 'lucide-react';
10
10
  import { useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
11
11
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
12
12
  import { createPortal } from 'react-dom';
@@ -142,6 +142,8 @@ var DEFAULT_I18N = {
142
142
  pasteConfirmCancel: "Cancel",
143
143
  pasteWarnTitle: "Cross-scope paste",
144
144
  pasteWarnContinue: "Continue",
145
+ duplicateAction: "Duplicate",
146
+ duplicateToast: "Duplicated {name}. Review and Save.",
145
147
  itemsAllLabel: "All items",
146
148
  subtitleEmpty: "Not set",
147
149
  subtitleConfigured: "Configured",
@@ -1549,7 +1551,8 @@ function useShellClipboard(args) {
1549
1551
  onTelemetry,
1550
1552
  onCopyOverride,
1551
1553
  onPasteOverride,
1552
- onLeftSelectRef
1554
+ onLeftSelectRef,
1555
+ onCreateItemDraftRef
1553
1556
  } = args;
1554
1557
  const clipboard = useRecordClipboard({
1555
1558
  appId,
@@ -1681,8 +1684,7 @@ function useShellClipboard(args) {
1681
1684
  const rowClipboard = enabled ? (record) => {
1682
1685
  const summaryHasData = record.data != null;
1683
1686
  const sourceParsed = record.scope;
1684
- const compat = clipboard.entry ? checkPasteCompatibility(clipboard.entry.sourceScope, sourceParsed) : null;
1685
- const sameTarget = clipboard.entry ? clipboard.entry.sourceRecordId && record.id ? clipboard.entry.sourceRecordId === record.id : clipboard.entry.sourceScope.raw === anchorKey(record.scope) : false;
1687
+ const canDuplicate = summaryHasData && isCollection;
1686
1688
  return {
1687
1689
  onCopy: summaryHasData ? () => {
1688
1690
  const value = onCopyOverride ? onCopyOverride({ value: record.data, scope: sourceParsed }) : cloneValue(record.data);
@@ -1698,15 +1700,27 @@ function useShellClipboard(args) {
1698
1700
  variant: "copy"
1699
1701
  });
1700
1702
  } : void 0,
1701
- onPaste: () => {
1703
+ onDuplicate: canDuplicate ? () => {
1704
+ const value = onCopyOverride ? onCopyOverride({ value: record.data, scope: sourceParsed }) : cloneValue(record.data);
1705
+ clipboard.set({
1706
+ value,
1707
+ sourceScope: sourceParsed,
1708
+ sourceRecordId: record.id ?? void 0,
1709
+ sourceLabel: record.label
1710
+ });
1711
+ onTelemetry?.({ type: "clipboard.copy", recordType, sourceRef: anchorKey(record.scope) });
1702
1712
  onLeftSelectRef.current?.(record);
1703
- setPendingPasteTarget(
1704
- record.id ? { kind: "record", recordId: record.id } : { kind: "anchor", ref: anchorKey(record.scope) }
1705
- );
1706
- },
1707
- canPaste: !!clipboard.entry && !sameTarget && compat?.status !== "denied",
1708
- pasteWillReplace: record.status === "configured",
1709
- clipboardSourceLabel: clipboard.entry?.sourceLabel
1713
+ const create = onCreateItemDraftRef?.current;
1714
+ if (!create) return;
1715
+ window.setTimeout(() => {
1716
+ const newId = create();
1717
+ if (newId) setPendingPasteTarget({ kind: "record", recordId: newId });
1718
+ }, 0);
1719
+ setNotice({
1720
+ message: i18n.duplicateToast.replace("{name}", record.label),
1721
+ variant: "copy"
1722
+ });
1723
+ } : void 0
1710
1724
  };
1711
1725
  } : void 0;
1712
1726
  const toast = notice ? /* @__PURE__ */ jsx(
@@ -1860,14 +1874,17 @@ function useShellNavigation(args) {
1860
1874
  lastAppliedDLRef
1861
1875
  ]);
1862
1876
  const onItemCreate = useCallback(() => {
1863
- if (!isCollection) return;
1877
+ if (!isCollection) return void 0;
1878
+ let createdId;
1864
1879
  void runWithGuard(() => {
1865
1880
  const id = coerceDraftItemId(generateItemId);
1881
+ createdId = id;
1866
1882
  setSelectedItemId(id);
1867
1883
  onTelemetry?.({ type: "item.create", recordType, scopeRef: baseScopeRef });
1868
1884
  deepLinkState.emit({ recordId: null, scope: baseScopeRef || null }, "record.open");
1869
1885
  stampEcho(null, baseScopeRef || null);
1870
1886
  });
1887
+ return createdId;
1871
1888
  }, [
1872
1889
  isCollection,
1873
1890
  runWithGuard,
@@ -1951,7 +1968,9 @@ function useShellNavigation(args) {
1951
1968
  ]);
1952
1969
  const itemViewCtx = useMemo(() => ({
1953
1970
  onOpen: onItemOpen,
1954
- onCreate: onItemCreate,
1971
+ onCreate: () => {
1972
+ onItemCreate();
1973
+ },
1955
1974
  onDelete: (id) => {
1956
1975
  void onItemDelete(id);
1957
1976
  },
@@ -2108,20 +2127,6 @@ var useShellDeepLink = (args) => {
2108
2127
  const [pendingDeepLinkRecordId, setPendingDeepLinkRecordId] = useState(null);
2109
2128
  const preserveInitialRecordIdRef = useRef(!!deepLinkState.urlState.recordId);
2110
2129
  const warnedMissingRef = useRef(/* @__PURE__ */ new Set());
2111
- const bootLoggedRef = useRef(false);
2112
- if (!bootLoggedRef.current) {
2113
- bootLoggedRef.current = true;
2114
- console.info("[RecordsAdminShell] deep-link boot snapshot", {
2115
- enabled: deepLinkState.enabled,
2116
- urlStateRecordId: deepLinkState.urlState.recordId ?? null,
2117
- urlStateScope: deepLinkState.urlState.scope ?? null,
2118
- urlStateView: deepLinkState.urlState.view ?? null,
2119
- urlStateRaw: JSON.stringify(deepLinkState.urlState),
2120
- paramNames: deepLinkState.paramNames,
2121
- windowHash: typeof window !== "undefined" ? window.location.hash : null,
2122
- windowSearch: typeof window !== "undefined" ? window.location.search : null
2123
- });
2124
- }
2125
2130
  useEffect(() => {
2126
2131
  if (!deepLinkState.enabled) return;
2127
2132
  if (selectedItemId) return;
@@ -2140,14 +2145,6 @@ var useShellDeepLink = (args) => {
2140
2145
  useEffect(() => {
2141
2146
  const { recordId, scope, view } = deepLinkState.urlState;
2142
2147
  const sig = `${recordId ?? ""}|${scope ?? ""}|${view ?? ""}`;
2143
- console.info("[RecordsAdminShell] restore effect tick", {
2144
- enabled: deepLinkState.enabled,
2145
- recordId,
2146
- scope,
2147
- view,
2148
- sig,
2149
- lastApplied: lastAppliedDLRef.current
2150
- });
2151
2148
  if (!deepLinkState.enabled) return;
2152
2149
  if (sig === lastAppliedDLRef.current) return;
2153
2150
  lastAppliedDLRef.current = sig;
@@ -2174,31 +2171,9 @@ var useShellDeepLink = (args) => {
2174
2171
  }
2175
2172
  setPendingDeepLinkRecordId(recordId ?? null);
2176
2173
  if (recordId) preserveInitialRecordIdRef.current = true;
2177
- if (recordId) {
2178
- console.info("[RecordsAdminShell] deep-link restore \u2014 pending recordId set", {
2179
- recordId,
2180
- scope: scope ?? null,
2181
- view: view ?? null
2182
- });
2183
- }
2184
- if (!recordId && selectedItemId !== null) {
2185
- console.info("[RecordsAdminShell] preserving selected item during restore without recordId", {
2186
- selectedItemId,
2187
- scope,
2188
- view
2189
- });
2190
- }
2191
2174
  }, [deepLinkState.enabled, deepLinkState.urlState]);
2192
2175
  useEffect(() => {
2193
2176
  const pending = pendingDeepLinkRecordId;
2194
- console.info("[RecordsAdminShell] resolver tick", {
2195
- pending,
2196
- isCollection,
2197
- collectionItemsCount: collectionItems.items.length,
2198
- collectionItemsLoading: collectionItems.isLoading,
2199
- recordListCount: recordListItems.length,
2200
- recordListLoading
2201
- });
2202
2177
  if (!pending) return;
2203
2178
  if (isCollection) {
2204
2179
  const hit2 = collectionItems.items.find((it) => it.id === pending || it.itemId === pending);
@@ -3282,10 +3257,8 @@ var statusToneLabel = (tone) => {
3282
3257
  };
3283
3258
  var RowContextMenu = ({
3284
3259
  onCopy,
3285
- onPaste,
3286
- canPaste,
3287
- pasteWillReplace,
3288
- pasteSourceLabel,
3260
+ onDuplicate,
3261
+ actions,
3289
3262
  i18n
3290
3263
  }) => {
3291
3264
  const [open, setOpen] = useState(false);
@@ -3334,8 +3307,9 @@ var RowContextMenu = ({
3334
3307
  window.removeEventListener("scroll", update, true);
3335
3308
  };
3336
3309
  }, [open]);
3337
- if (!onCopy && !onPaste) return null;
3338
- const pasteLabel = !canPaste ? i18n.clipboardEmpty : pasteSourceLabel ? i18n.pasteFrom.replace("{name}", pasteSourceLabel) : pasteWillReplace ? i18n.pasteReplace : i18n.paste;
3310
+ const hasActions = (actions?.length ?? 0) > 0;
3311
+ if (!onCopy && !onDuplicate && !hasActions) return null;
3312
+ const showDivider = (onCopy || onDuplicate) && hasActions;
3339
3313
  return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: "ra-row-menu-wrap relative", children: [
3340
3314
  /* @__PURE__ */ jsx(
3341
3315
  "button",
@@ -3381,24 +3355,49 @@ var RowContextMenu = ({
3381
3355
  ]
3382
3356
  }
3383
3357
  ),
3384
- onPaste && /* @__PURE__ */ jsxs(
3358
+ onDuplicate && /* @__PURE__ */ jsxs(
3385
3359
  "button",
3386
3360
  {
3387
3361
  type: "button",
3388
3362
  role: "menuitem",
3389
3363
  className: "ra-row-menu-item",
3390
- disabled: !canPaste,
3391
3364
  onClick: (e) => {
3392
3365
  e.stopPropagation();
3393
3366
  setOpen(false);
3394
- if (canPaste) onPaste();
3367
+ onDuplicate();
3395
3368
  },
3396
3369
  children: [
3397
- /* @__PURE__ */ jsx(ClipboardPaste, { className: "w-3 h-3", "aria-hidden": "true" }),
3398
- /* @__PURE__ */ jsx("span", { className: "truncate", children: pasteLabel })
3370
+ /* @__PURE__ */ jsx(CopyPlus, { className: "w-3 h-3", "aria-hidden": "true" }),
3371
+ /* @__PURE__ */ jsx("span", { children: i18n.duplicateAction })
3399
3372
  ]
3400
3373
  }
3401
- )
3374
+ ),
3375
+ showDivider && /* @__PURE__ */ jsx("div", { className: "ra-row-menu-divider", role: "separator" }),
3376
+ hasActions && actions.map((action) => {
3377
+ const Icon = action.icon;
3378
+ return /* @__PURE__ */ jsxs(
3379
+ "button",
3380
+ {
3381
+ type: "button",
3382
+ role: "menuitem",
3383
+ className: "ra-row-menu-item",
3384
+ "data-tone": action.variant === "danger" ? "danger" : void 0,
3385
+ disabled: action.disabled,
3386
+ title: action.disabled ? action.disabledReason : void 0,
3387
+ onClick: (e) => {
3388
+ e.stopPropagation();
3389
+ if (action.disabled) return;
3390
+ setOpen(false);
3391
+ void action.onAction();
3392
+ },
3393
+ children: [
3394
+ Icon && /* @__PURE__ */ jsx(Icon, { className: "w-3 h-3" }),
3395
+ /* @__PURE__ */ jsx("span", { children: action.label })
3396
+ ]
3397
+ },
3398
+ action.key
3399
+ );
3400
+ })
3402
3401
  ]
3403
3402
  }
3404
3403
  ),
@@ -3447,7 +3446,7 @@ var RuleLabelLookupProvider = ({
3447
3446
  return /* @__PURE__ */ jsx(RuleLabelLookupContext.Provider, { value: v, children });
3448
3447
  };
3449
3448
  var DefaultRecordRow = ({ record, ctx, compact = false }) => {
3450
- const { selected, onSelect, isDirty, hasError, onCopy, onPaste, canPaste, pasteWillReplace, clipboardSourceLabel } = ctx;
3449
+ const { selected, onSelect, isDirty, hasError, onCopy, onDuplicate, actions } = ctx;
3451
3450
  const ruleLabelLookup = useRuleLabelLookup();
3452
3451
  const ScopeIcon = record.scope.kind && record.scope.kind !== "collection" ? DEFAULT_ICONS.scope[record.scope.kind] : DEFAULT_ICONS.scope.product;
3453
3452
  const tone = resolveTone(void 0, record.status);
@@ -3500,7 +3499,7 @@ var DefaultRecordRow = ({ record, ctx, compact = false }) => {
3500
3499
  className: "ra-row-rule-pip",
3501
3500
  title: ruleSummary ? `Targeted: ${ruleSummary}` : "This record has targeting rules",
3502
3501
  "aria-label": ruleSummary ? `Targeted: ${ruleSummary}` : "This record has targeting rules",
3503
- children: /* @__PURE__ */ jsx(SlidersHorizontal, { className: "w-3 h-3", "aria-hidden": "true" })
3502
+ children: /* @__PURE__ */ jsx(Target, { className: "w-3 h-3", "aria-hidden": "true" })
3504
3503
  }
3505
3504
  ),
3506
3505
  hasError ? /* @__PURE__ */ jsx(
@@ -3519,20 +3518,15 @@ var DefaultRecordRow = ({ record, ctx, compact = false }) => {
3519
3518
  }
3520
3519
  )
3521
3520
  ] }),
3522
- (onCopy || onPaste) && /* @__PURE__ */ jsx(
3521
+ (onCopy || onDuplicate || actions && actions.length > 0) && /* @__PURE__ */ jsx(
3523
3522
  RowContextMenu,
3524
3523
  {
3525
3524
  onCopy,
3526
- onPaste,
3527
- canPaste,
3528
- pasteWillReplace,
3529
- pasteSourceLabel: clipboardSourceLabel,
3525
+ onDuplicate,
3526
+ actions,
3530
3527
  i18n: {
3531
3528
  copy: DEFAULT_I18N.copy,
3532
- paste: DEFAULT_I18N.paste,
3533
- pasteFrom: DEFAULT_I18N.pasteFrom,
3534
- pasteReplace: DEFAULT_I18N.pasteReplace,
3535
- clipboardEmpty: DEFAULT_I18N.clipboardEmpty
3529
+ duplicateAction: DEFAULT_I18N.duplicateAction
3536
3530
  }
3537
3531
  }
3538
3532
  )
@@ -3554,10 +3548,37 @@ var RecordList = ({
3554
3548
  groupBy,
3555
3549
  renderGroupActions,
3556
3550
  rowClipboard,
3551
+ rowActions,
3557
3552
  i18n
3558
3553
  }) => {
3554
+ const containerRef = useRef(null);
3555
+ const onKeyDown = useCallback((e) => {
3556
+ const key = e.key;
3557
+ if (key !== "ArrowDown" && key !== "ArrowUp" && key !== "Home" && key !== "End") {
3558
+ return;
3559
+ }
3560
+ const root = containerRef.current;
3561
+ if (!root) return;
3562
+ const buttons = Array.from(
3563
+ root.querySelectorAll("button.ra-row-hit")
3564
+ ).filter((btn) => !btn.hasAttribute("disabled") && btn.offsetParent !== null);
3565
+ if (buttons.length === 0) return;
3566
+ const active = document.activeElement;
3567
+ const currentIdx = active ? buttons.indexOf(active) : -1;
3568
+ let nextIdx = currentIdx;
3569
+ if (key === "ArrowDown") nextIdx = currentIdx < 0 ? 0 : Math.min(currentIdx + 1, buttons.length - 1);
3570
+ else if (key === "ArrowUp") nextIdx = currentIdx <= 0 ? 0 : currentIdx - 1;
3571
+ else if (key === "Home") nextIdx = 0;
3572
+ else if (key === "End") nextIdx = buttons.length - 1;
3573
+ if (nextIdx !== currentIdx && buttons[nextIdx]) {
3574
+ e.preventDefault();
3575
+ buttons[nextIdx].focus();
3576
+ buttons[nextIdx].scrollIntoView({ block: "nearest" });
3577
+ }
3578
+ }, []);
3559
3579
  const buildCtx = (item) => {
3560
3580
  const cb = rowClipboard ? rowClipboard(item) : null;
3581
+ const extraActions = rowActions ? rowActions(item) ?? void 0 : void 0;
3561
3582
  const itemAnchorKey = anchorKey(item.scope);
3562
3583
  const idMatch = selectedId != null && item.id != null && item.id === selectedId;
3563
3584
  const anchorMatch = selectedAnchorKey != null && !!itemAnchorKey && itemAnchorKey === selectedAnchorKey;
@@ -3572,7 +3593,8 @@ var RecordList = ({
3572
3593
  isDirty: dirtyIdMatch || dirtyAnchorMatch || idInStore || anchorInStore,
3573
3594
  hasError: errorInStore,
3574
3595
  i18n,
3575
- ...cb ?? {}
3596
+ ...cb ?? {},
3597
+ actions: extraActions && extraActions.length > 0 ? extraActions : void 0
3576
3598
  };
3577
3599
  };
3578
3600
  const groups = useMemo(() => {
@@ -3599,15 +3621,33 @@ var RecordList = ({
3599
3621
  };
3600
3622
  if (groups) {
3601
3623
  return /* @__PURE__ */ jsx(
3602
- GroupedList,
3624
+ "div",
3603
3625
  {
3604
- groups,
3605
- renderItems,
3606
- renderGroupActions
3626
+ ref: containerRef,
3627
+ onKeyDown,
3628
+ role: "listbox",
3629
+ "aria-label": "Records",
3630
+ children: /* @__PURE__ */ jsx(
3631
+ GroupedList,
3632
+ {
3633
+ groups,
3634
+ renderItems,
3635
+ renderGroupActions
3636
+ }
3637
+ )
3607
3638
  }
3608
3639
  );
3609
3640
  }
3610
- return renderItems(items);
3641
+ return /* @__PURE__ */ jsx(
3642
+ "div",
3643
+ {
3644
+ ref: containerRef,
3645
+ onKeyDown,
3646
+ role: "listbox",
3647
+ "aria-label": "Records",
3648
+ children: renderItems(items)
3649
+ }
3650
+ );
3611
3651
  };
3612
3652
  var GroupedList = ({
3613
3653
  groups,
@@ -5518,6 +5558,8 @@ function DefaultItemTable({
5518
5558
  selectedId,
5519
5559
  onOpen,
5520
5560
  onDelete,
5561
+ rowActions,
5562
+ rowClipboard,
5521
5563
  i18n
5522
5564
  }) {
5523
5565
  const cols = columns ?? [];
@@ -5555,6 +5597,31 @@ function DefaultItemTable({
5555
5597
  /* @__PURE__ */ jsx("td", { className: "ra-item-row-meta", children: formatDate(item.updatedAt) })
5556
5598
  ] }) : cols.map((c) => /* @__PURE__ */ jsx("td", { style: { textAlign: c.align ?? "left" }, children: c.render(item) }, c.key)),
5557
5599
  /* @__PURE__ */ jsxs("td", { className: "ra-item-row-actions", children: [
5600
+ (() => {
5601
+ const extra = rowActions ? rowActions(item) ?? void 0 : void 0;
5602
+ const cb = rowClipboard ? rowClipboard(item) : null;
5603
+ const hasExtras = !!(extra && extra.length > 0);
5604
+ if (!cb?.onCopy && !cb?.onDuplicate && !hasExtras) return null;
5605
+ return /* @__PURE__ */ jsx(
5606
+ "span",
5607
+ {
5608
+ onClick: (e) => e.stopPropagation(),
5609
+ onMouseDown: (e) => e.stopPropagation(),
5610
+ children: /* @__PURE__ */ jsx(
5611
+ RowContextMenu,
5612
+ {
5613
+ onCopy: cb?.onCopy,
5614
+ onDuplicate: cb?.onDuplicate,
5615
+ actions: extra ?? void 0,
5616
+ i18n: {
5617
+ copy: DEFAULT_I18N.copy,
5618
+ duplicateAction: DEFAULT_I18N.duplicateAction
5619
+ }
5620
+ }
5621
+ )
5622
+ }
5623
+ );
5624
+ })(),
5558
5625
  /* @__PURE__ */ jsx(
5559
5626
  "button",
5560
5627
  {
@@ -5598,6 +5665,8 @@ function DefaultItemCards({
5598
5665
  ctx,
5599
5666
  renderCard,
5600
5667
  cardSize = "md",
5668
+ rowActions,
5669
+ rowClipboard,
5601
5670
  i18n
5602
5671
  }) {
5603
5672
  const isGallery = variant === "gallery";
@@ -5610,10 +5679,13 @@ function DefaultItemCards({
5610
5679
  const id = item.itemId ?? "";
5611
5680
  const key = item.id ?? (id || anchorKey(item.scope) || `pos:${idx}`);
5612
5681
  const open = () => ctx.onOpen(id);
5682
+ const extraActions = rowActions ? rowActions(item) ?? void 0 : void 0;
5683
+ const cb = rowClipboard ? rowClipboard(item) : null;
5613
5684
  const slotCtx = {
5614
5685
  ...ctx,
5615
5686
  selected: selectedId === id,
5616
- onSelect: open
5687
+ onSelect: open,
5688
+ actions: extraActions && extraActions.length > 0 ? extraActions : void 0
5617
5689
  };
5618
5690
  if (renderCard) {
5619
5691
  return /* @__PURE__ */ jsx(
@@ -5640,6 +5712,26 @@ function DefaultItemCards({
5640
5712
  /* @__PURE__ */ jsx("div", { className: "ra-item-card-title", children: item.label }),
5641
5713
  item.subtitle && /* @__PURE__ */ jsx("div", { className: "ra-item-card-sub", children: item.subtitle })
5642
5714
  ] }),
5715
+ (cb?.onCopy || cb?.onDuplicate || extraActions && extraActions.length > 0) && /* @__PURE__ */ jsx(
5716
+ "span",
5717
+ {
5718
+ className: "ra-item-card-menu",
5719
+ onClick: (e) => e.stopPropagation(),
5720
+ onMouseDown: (e) => e.stopPropagation(),
5721
+ children: /* @__PURE__ */ jsx(
5722
+ RowContextMenu,
5723
+ {
5724
+ onCopy: cb?.onCopy,
5725
+ onDuplicate: cb?.onDuplicate,
5726
+ actions: extraActions,
5727
+ i18n: {
5728
+ copy: DEFAULT_I18N.copy,
5729
+ duplicateAction: DEFAULT_I18N.duplicateAction
5730
+ }
5731
+ }
5732
+ )
5733
+ }
5734
+ ),
5643
5735
  /* @__PURE__ */ jsx(
5644
5736
  "button",
5645
5737
  {
@@ -5677,6 +5769,8 @@ function ItemListView({
5677
5769
  renderItemEmpty,
5678
5770
  itemColumns,
5679
5771
  cardSize = "md",
5772
+ rowActions,
5773
+ rowClipboard,
5680
5774
  i18n
5681
5775
  }) {
5682
5776
  const newLabel = i18n.newItem.includes("{noun}") ? i18n.newItem.replace("{noun}", itemNoun) : i18n.newItem;
@@ -5747,6 +5841,8 @@ function ItemListView({
5747
5841
  selectedId: ctx.selectedId,
5748
5842
  onOpen: ctx.onOpen,
5749
5843
  onDelete: ctx.onDelete,
5844
+ rowActions,
5845
+ rowClipboard,
5750
5846
  i18n
5751
5847
  }
5752
5848
  );
@@ -5760,6 +5856,8 @@ function ItemListView({
5760
5856
  ctx,
5761
5857
  renderCard: renderItemCard,
5762
5858
  cardSize,
5859
+ rowActions,
5860
+ rowClipboard,
5763
5861
  i18n
5764
5862
  }
5765
5863
  );
@@ -5890,7 +5988,7 @@ function SiblingRail({
5890
5988
  className: "ra-row-rule-pip",
5891
5989
  title: ruleSummary ? `Targeted: ${ruleSummary}` : "This item has targeting rules",
5892
5990
  "aria-label": ruleSummary ? `Targeted: ${ruleSummary}` : "This item has targeting rules",
5893
- children: /* @__PURE__ */ jsx(SlidersHorizontal, { className: "w-3 h-3", "aria-hidden": "true" })
5991
+ children: /* @__PURE__ */ jsx(Target, { className: "w-3 h-3", "aria-hidden": "true" })
5894
5992
  }
5895
5993
  ),
5896
5994
  hasError ? /* @__PURE__ */ jsx(
@@ -7144,6 +7242,7 @@ function RecordsAdminShellInner(props) {
7144
7242
  unsaved,
7145
7243
  clipboard,
7146
7244
  actions,
7245
+ recordActions,
7147
7246
  icons: iconsOverride,
7148
7247
  // Deep linking
7149
7248
  deepLink
@@ -7600,6 +7699,7 @@ function RecordsAdminShellInner(props) {
7600
7699
  i18n
7601
7700
  ]);
7602
7701
  const onLeftSelectRef = useRef(null);
7702
+ const onCreateItemDraftRef = useRef(null);
7603
7703
  const { runWithGuard } = useDirtyNavigation({
7604
7704
  strategy: dirtyStrategy,
7605
7705
  isDirty: editorCtx.isDirty,
@@ -7654,19 +7754,37 @@ function RecordsAdminShellInner(props) {
7654
7754
  onTelemetry,
7655
7755
  onCopyOverride,
7656
7756
  onPasteOverride,
7657
- onLeftSelectRef
7757
+ onLeftSelectRef,
7758
+ onCreateItemDraftRef
7658
7759
  });
7659
7760
  const editorClipboard = shellClipboard.editorClipboard;
7660
7761
  const rowClipboard = shellClipboard.rowClipboard;
7762
+ const wrappedRecordActions = recordActions ? (record) => {
7763
+ const list = recordActions(record, record.scope);
7764
+ if (!list || list.length === 0) return list ?? void 0;
7765
+ return list.map((a) => ({
7766
+ ...a,
7767
+ onAction: () => {
7768
+ onTelemetry?.({
7769
+ type: "recordAction.invoke",
7770
+ recordType,
7771
+ key: a.key,
7772
+ ref: anchorKey(record.scope)
7773
+ });
7774
+ return a.onAction();
7775
+ }
7776
+ }));
7777
+ } : void 0;
7661
7778
  const baseScopeRef = editingScope?.raw ?? "";
7662
7779
  const itemNounLabel = itemNoun || "item";
7663
7780
  const {
7664
7781
  onItemOpen,
7782
+ onItemCreate,
7665
7783
  onItemBack,
7666
7784
  onItemPrev,
7667
7785
  onItemNext,
7668
7786
  itemPosition,
7669
- itemViewCtx} = useShellNavigation({
7787
+ itemViewCtx: baseItemViewCtx} = useShellNavigation({
7670
7788
  isCollection,
7671
7789
  runWithGuard,
7672
7790
  selectedItemId,
@@ -7686,6 +7804,7 @@ function RecordsAdminShellInner(props) {
7686
7804
  hooks: props.hooks,
7687
7805
  lastAppliedDLRef
7688
7806
  });
7807
+ const itemViewCtx = baseItemViewCtx;
7689
7808
  const renderEditorWithPreview = () => {
7690
7809
  if (!editingTargetScope) return null;
7691
7810
  const previewBody = renderPreview && effectivePreviewScope ? renderPreview({ resolved: editorCtx.value, previewScope: effectivePreviewScope }) : null;
@@ -8151,6 +8270,7 @@ function RecordsAdminShellInner(props) {
8151
8270
  });
8152
8271
  };
8153
8272
  onLeftSelectRef.current = onLeftSelect;
8273
+ onCreateItemDraftRef.current = onItemCreate;
8154
8274
  return /* @__PURE__ */ jsx(RuleLabelLookupProvider, { value: ruleLabelLookup, children: /* @__PURE__ */ jsxs(
8155
8275
  "div",
8156
8276
  {
@@ -8179,7 +8299,7 @@ function RecordsAdminShellInner(props) {
8179
8299
  }
8180
8300
  );
8181
8301
  })(),
8182
- /* @__PURE__ */ jsxs("div", { className: "px-4 pt-4 space-y-3", children: [
8302
+ /* @__PURE__ */ jsxs("div", { className: "px-2 pt-2 space-y-2", children: [
8183
8303
  intro && !dismissed && !(headerWillRender && resolvedReopenAffordance === "header") && /* @__PURE__ */ jsx(
8184
8304
  IntroCard,
8185
8305
  {
@@ -8491,6 +8611,7 @@ function RecordsAdminShellInner(props) {
8491
8611
  ),
8492
8612
  renderGroupActions: renderRuleGroupActions,
8493
8613
  rowClipboard,
8614
+ rowActions: wrappedRecordActions,
8494
8615
  i18n
8495
8616
  }
8496
8617
  ),
@@ -8589,6 +8710,8 @@ function RecordsAdminShellInner(props) {
8589
8710
  itemColumns,
8590
8711
  cardSize: itemCardSize,
8591
8712
  ruleSummary: activeRuleSummary,
8713
+ rowActions: wrappedRecordActions,
8714
+ rowClipboard,
8592
8715
  i18n
8593
8716
  }
8594
8717
  ),
@@ -8675,7 +8798,41 @@ var RecordBrowser = ({
8675
8798
  scopesLoading,
8676
8799
  i18n
8677
8800
  }) => {
8678
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", style: { background: "hsl(var(--ra-surface))" }, children: [
8801
+ const rootRef = useRef(null);
8802
+ const searchRef = useRef(null);
8803
+ useEffect(() => {
8804
+ const root = rootRef.current;
8805
+ if (!root) return;
8806
+ const handler = (e) => {
8807
+ if (e.key !== "/" || e.metaKey || e.ctrlKey || e.altKey) return;
8808
+ const target = e.target;
8809
+ if (!target || !root.contains(target)) return;
8810
+ const tag = target.tagName;
8811
+ if (tag === "INPUT" || tag === "TEXTAREA" || target.isContentEditable) return;
8812
+ e.preventDefault();
8813
+ searchRef.current?.focus();
8814
+ searchRef.current?.select();
8815
+ };
8816
+ root.addEventListener("keydown", handler);
8817
+ return () => root.removeEventListener("keydown", handler);
8818
+ }, []);
8819
+ const onSearchKeyDown = (e) => {
8820
+ if (e.key === "Escape") {
8821
+ if (search) {
8822
+ e.preventDefault();
8823
+ onSearchChange("");
8824
+ } else {
8825
+ e.currentTarget.blur();
8826
+ }
8827
+ } else if (e.key === "ArrowDown") {
8828
+ const first = rootRef.current?.querySelector("button.ra-row-hit");
8829
+ if (first) {
8830
+ e.preventDefault();
8831
+ first.focus();
8832
+ }
8833
+ }
8834
+ };
8835
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: "flex flex-col h-full", style: { background: "hsl(var(--ra-surface))" }, children: [
8679
8836
  /* @__PURE__ */ jsx(ScopeTabs, { scopes, active: activeScope, onChange: onActiveScopeChange, loading: scopesLoading }),
8680
8837
  /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-2.5 border-b", style: { borderColor: "hsl(var(--ra-border))" }, children: [
8681
8838
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
@@ -8683,9 +8840,11 @@ var RecordBrowser = ({
8683
8840
  /* @__PURE__ */ jsx(
8684
8841
  "input",
8685
8842
  {
8843
+ ref: searchRef,
8686
8844
  type: "text",
8687
8845
  value: search,
8688
8846
  onChange: (e) => onSearchChange(e.target.value),
8847
+ onKeyDown: onSearchKeyDown,
8689
8848
  placeholder: i18n.searchPlaceholder,
8690
8849
  className: "w-full pl-8 pr-3 py-1.5 text-xs rounded-md border bg-transparent focus:outline-none focus:ring-1",
8691
8850
  style: {
@@ -8720,7 +8879,7 @@ var RecordBrowser = ({
8720
8879
  };
8721
8880
  var initials2 = (s) => s.split(/\s+/).filter(Boolean).slice(0, 2).map((p) => p[0]?.toUpperCase() ?? "").join("") || "?";
8722
8881
  var DefaultRecordCard = ({ record, ctx, variant = "grid" }) => {
8723
- const { selected, onSelect, isDirty, hasError, onCopy, onPaste, canPaste, pasteWillReplace, clipboardSourceLabel } = ctx;
8882
+ const { selected, onSelect, isDirty, hasError, onCopy, onDuplicate, actions } = ctx;
8724
8883
  const aspect = variant === "gallery" ? "aspect-video" : "aspect-square";
8725
8884
  return /* @__PURE__ */ jsxs(
8726
8885
  "button",
@@ -8781,20 +8940,15 @@ var DefaultRecordCard = ({ record, ctx, variant = "grid" }) => {
8781
8940
  /* @__PURE__ */ jsxs("div", { className: "p-2.5 min-w-0", children: [
8782
8941
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5 min-w-0", children: [
8783
8942
  /* @__PURE__ */ jsx("div", { className: "ra-row-title flex-1 min-w-0", children: record.label }),
8784
- (onCopy || onPaste) && /* @__PURE__ */ jsx(
8943
+ (onCopy || onDuplicate || actions && actions.length > 0) && /* @__PURE__ */ jsx(
8785
8944
  RowContextMenu,
8786
8945
  {
8787
8946
  onCopy,
8788
- onPaste,
8789
- canPaste,
8790
- pasteWillReplace,
8791
- pasteSourceLabel: clipboardSourceLabel,
8947
+ onDuplicate,
8948
+ actions,
8792
8949
  i18n: {
8793
8950
  copy: DEFAULT_I18N.copy,
8794
- paste: DEFAULT_I18N.paste,
8795
- pasteFrom: DEFAULT_I18N.pasteFrom,
8796
- pasteReplace: DEFAULT_I18N.pasteReplace,
8797
- clipboardEmpty: DEFAULT_I18N.clipboardEmpty
8951
+ duplicateAction: DEFAULT_I18N.duplicateAction
8798
8952
  }
8799
8953
  }
8800
8954
  )