@proveanything/smartlinks-utils-ui 0.9.13 → 0.10.2

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
  );
@@ -5852,67 +5950,92 @@ function SiblingRail({
5852
5950
  selectedItemId,
5853
5951
  isLoading,
5854
5952
  error,
5953
+ onBack,
5855
5954
  onSelect,
5955
+ contextKind,
5956
+ contextSummary,
5856
5957
  dirtyKeys,
5857
5958
  errorKeys,
5858
5959
  i18n
5859
5960
  }) {
5860
5961
  const ruleLabelLookup = useRuleLabelLookup();
5861
- return /* @__PURE__ */ jsx("div", { className: "ra-sibling-rail", children: /* @__PURE__ */ jsxs("div", { className: "ra-sibling-body", children: [
5862
- isLoading && /* @__PURE__ */ jsx(LoadingState, {}),
5863
- !isLoading && error && /* @__PURE__ */ jsx(ErrorState, { error }),
5864
- !isLoading && !error && items.length === 0 && /* @__PURE__ */ jsx(EmptyState, { title: i18n.noItemsTitle, body: i18n.noItemsBody }),
5865
- !isLoading && !error && items.length > 0 && /* @__PURE__ */ jsx("ul", { className: "ra-sibling-list", children: items.map((item, idx) => {
5866
- const id = item.itemId ?? "";
5867
- const key = item.id ?? (id || anchorKey(item.scope) || `pos:${idx}`);
5868
- const akey = anchorKey(item.scope);
5869
- const selected = selectedItemId === id;
5870
- const isDirty = !!(id && dirtyKeys?.has(id) || akey && dirtyKeys?.has(akey));
5871
- const hasError = !!(id && errorKeys?.has(id) || akey && errorKeys?.has(akey));
5872
- const ruleClauses = item.facetRule ? summarizeFacetRule(item.facetRule, ruleLabelLookup) : [];
5873
- const isTargeted = ruleClauses.length > 0;
5874
- const ruleSummary = isTargeted ? ruleClauses.map((c) => c.label).join(" \xB7 ") : null;
5875
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
5962
+ return /* @__PURE__ */ jsxs("div", { className: "ra-sibling-rail", children: [
5963
+ (onBack || contextKind) && /* @__PURE__ */ jsxs("div", { className: "ra-sibling-context", children: [
5964
+ onBack && /* @__PURE__ */ jsx(
5876
5965
  "button",
5877
5966
  {
5878
5967
  type: "button",
5879
- onClick: () => onSelect(id),
5880
- className: "ra-row",
5881
- "data-selected": selected,
5882
- children: [
5883
- /* @__PURE__ */ jsxs("div", { className: "ra-row-body", children: [
5884
- /* @__PURE__ */ jsx("div", { className: "ra-row-title", children: item.label }),
5885
- item.subtitle && /* @__PURE__ */ jsx("div", { className: "ra-row-sub", children: item.subtitle })
5886
- ] }),
5887
- isTargeted && /* @__PURE__ */ jsx(
5888
- "span",
5889
- {
5890
- className: "ra-row-rule-pip",
5891
- title: ruleSummary ? `Targeted: ${ruleSummary}` : "This item has targeting rules",
5892
- "aria-label": ruleSummary ? `Targeted: ${ruleSummary}` : "This item has targeting rules",
5893
- children: /* @__PURE__ */ jsx(SlidersHorizontal, { className: "w-3 h-3", "aria-hidden": "true" })
5894
- }
5895
- ),
5896
- hasError ? /* @__PURE__ */ jsx(
5897
- "span",
5898
- {
5899
- className: "ra-error-pip",
5900
- title: "Save failed",
5901
- "aria-label": "Save failed"
5902
- }
5903
- ) : isDirty ? /* @__PURE__ */ jsx(
5904
- "span",
5905
- {
5906
- className: "ra-dirty-pip",
5907
- title: "Unsaved changes",
5908
- "aria-label": "Unsaved changes"
5909
- }
5910
- ) : null
5911
- ]
5968
+ onClick: onBack,
5969
+ className: "ra-sibling-context-back",
5970
+ "aria-label": i18n.backToList,
5971
+ title: i18n.backToList,
5972
+ children: /* @__PURE__ */ jsx(ArrowLeft, { className: "w-3.5 h-3.5", "aria-hidden": "true" })
5912
5973
  }
5913
- ) }, key);
5914
- }) })
5915
- ] }) });
5974
+ ),
5975
+ contextKind && /* @__PURE__ */ jsxs("div", { className: "ra-sibling-context-label", title: contextSummary ? `${contextKind} \xB7 ${contextSummary}` : contextKind, children: [
5976
+ /* @__PURE__ */ jsx("span", { className: "ra-sibling-context-kind", children: contextKind }),
5977
+ contextSummary && /* @__PURE__ */ jsxs(Fragment, { children: [
5978
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ra-sibling-context-sep", children: "\xB7" }),
5979
+ /* @__PURE__ */ jsx("span", { className: "ra-sibling-context-summary", children: contextSummary })
5980
+ ] })
5981
+ ] })
5982
+ ] }),
5983
+ /* @__PURE__ */ jsxs("div", { className: "ra-sibling-body", children: [
5984
+ isLoading && /* @__PURE__ */ jsx(LoadingState, {}),
5985
+ !isLoading && error && /* @__PURE__ */ jsx(ErrorState, { error }),
5986
+ !isLoading && !error && items.length === 0 && /* @__PURE__ */ jsx(EmptyState, { title: i18n.noItemsTitle, body: i18n.noItemsBody }),
5987
+ !isLoading && !error && items.length > 0 && /* @__PURE__ */ jsx("ul", { className: "ra-sibling-list", children: items.map((item, idx) => {
5988
+ const id = item.itemId ?? "";
5989
+ const key = item.id ?? (id || anchorKey(item.scope) || `pos:${idx}`);
5990
+ const akey = anchorKey(item.scope);
5991
+ const selected = selectedItemId === id;
5992
+ const isDirty = !!(id && dirtyKeys?.has(id) || akey && dirtyKeys?.has(akey));
5993
+ const hasError = !!(id && errorKeys?.has(id) || akey && errorKeys?.has(akey));
5994
+ const ruleClauses = item.facetRule ? summarizeFacetRule(item.facetRule, ruleLabelLookup) : [];
5995
+ const isTargeted = ruleClauses.length > 0;
5996
+ const ruleSummary = isTargeted ? ruleClauses.map((c) => c.label).join(" \xB7 ") : null;
5997
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
5998
+ "button",
5999
+ {
6000
+ type: "button",
6001
+ onClick: () => onSelect(id),
6002
+ className: "ra-row",
6003
+ "data-selected": selected,
6004
+ children: [
6005
+ /* @__PURE__ */ jsxs("div", { className: "ra-row-body", children: [
6006
+ /* @__PURE__ */ jsx("div", { className: "ra-row-title", children: item.label }),
6007
+ item.subtitle && /* @__PURE__ */ jsx("div", { className: "ra-row-sub", children: item.subtitle })
6008
+ ] }),
6009
+ isTargeted && /* @__PURE__ */ jsx(
6010
+ "span",
6011
+ {
6012
+ className: "ra-row-rule-pip",
6013
+ title: ruleSummary ? `Targeted: ${ruleSummary}` : "This item has targeting rules",
6014
+ "aria-label": ruleSummary ? `Targeted: ${ruleSummary}` : "This item has targeting rules",
6015
+ children: /* @__PURE__ */ jsx(Target, { className: "w-3 h-3", "aria-hidden": "true" })
6016
+ }
6017
+ ),
6018
+ hasError ? /* @__PURE__ */ jsx(
6019
+ "span",
6020
+ {
6021
+ className: "ra-error-pip",
6022
+ title: "Save failed",
6023
+ "aria-label": "Save failed"
6024
+ }
6025
+ ) : isDirty ? /* @__PURE__ */ jsx(
6026
+ "span",
6027
+ {
6028
+ className: "ra-dirty-pip",
6029
+ title: "Unsaved changes",
6030
+ "aria-label": "Unsaved changes"
6031
+ }
6032
+ ) : null
6033
+ ]
6034
+ }
6035
+ ) }, key);
6036
+ }) })
6037
+ ] })
6038
+ ] });
5916
6039
  }
5917
6040
  var TONE_ICON = {
5918
6041
  info: Lightbulb,
@@ -6850,6 +6973,60 @@ var SaveAllProgress = ({
6850
6973
  }
6851
6974
  );
6852
6975
  };
6976
+ function PreviewReopenPill({ anchorRef, onClick, ariaLabel, title, children }) {
6977
+ const [pos, setPos] = useState(null);
6978
+ const rafRef = useRef(null);
6979
+ useLayoutEffect(() => {
6980
+ const el = anchorRef.current;
6981
+ if (!el || typeof window === "undefined") return;
6982
+ const measure = () => {
6983
+ if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
6984
+ rafRef.current = requestAnimationFrame(() => {
6985
+ const rect = el.getBoundingClientRect();
6986
+ if (rect.width === 0 && rect.height === 0) return;
6987
+ setPos({
6988
+ top: rect.top + rect.height / 2,
6989
+ right: Math.max(0, window.innerWidth - rect.right)
6990
+ });
6991
+ });
6992
+ };
6993
+ measure();
6994
+ const ro = new ResizeObserver(measure);
6995
+ ro.observe(el);
6996
+ ro.observe(document.body);
6997
+ window.addEventListener("resize", measure);
6998
+ window.addEventListener("scroll", measure, true);
6999
+ return () => {
7000
+ ro.disconnect();
7001
+ window.removeEventListener("resize", measure);
7002
+ window.removeEventListener("scroll", measure, true);
7003
+ if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
7004
+ };
7005
+ }, [anchorRef]);
7006
+ if (typeof document === "undefined" || !pos) return null;
7007
+ return createPortal(
7008
+ /* @__PURE__ */ jsx(
7009
+ "button",
7010
+ {
7011
+ type: "button",
7012
+ className: "ra-shell ra-preview-reopen ra-preview-reopen--floating",
7013
+ onClick,
7014
+ "aria-label": ariaLabel,
7015
+ title,
7016
+ style: {
7017
+ position: "fixed",
7018
+ top: pos.top,
7019
+ right: pos.right,
7020
+ // Pull half the pill width out into the gutter so it visually
7021
+ // anchors *to* the editor edge rather than sitting inside it.
7022
+ transform: "translate(50%, -50%)"
7023
+ },
7024
+ children
7025
+ }
7026
+ ),
7027
+ document.body
7028
+ );
7029
+ }
6853
7030
  var EditorMountPool = ({
6854
7031
  renderSlot,
6855
7032
  keepMountedHidden = true,
@@ -7144,6 +7321,7 @@ function RecordsAdminShellInner(props) {
7144
7321
  unsaved,
7145
7322
  clipboard,
7146
7323
  actions,
7324
+ recordActions,
7147
7325
  icons: iconsOverride,
7148
7326
  // Deep linking
7149
7327
  deepLink
@@ -7600,6 +7778,8 @@ function RecordsAdminShellInner(props) {
7600
7778
  i18n
7601
7779
  ]);
7602
7780
  const onLeftSelectRef = useRef(null);
7781
+ const onCreateItemDraftRef = useRef(null);
7782
+ const previewReopenAnchorRef = useRef(null);
7603
7783
  const { runWithGuard } = useDirtyNavigation({
7604
7784
  strategy: dirtyStrategy,
7605
7785
  isDirty: editorCtx.isDirty,
@@ -7654,19 +7834,37 @@ function RecordsAdminShellInner(props) {
7654
7834
  onTelemetry,
7655
7835
  onCopyOverride,
7656
7836
  onPasteOverride,
7657
- onLeftSelectRef
7837
+ onLeftSelectRef,
7838
+ onCreateItemDraftRef
7658
7839
  });
7659
7840
  const editorClipboard = shellClipboard.editorClipboard;
7660
7841
  const rowClipboard = shellClipboard.rowClipboard;
7842
+ const wrappedRecordActions = recordActions ? (record) => {
7843
+ const list = recordActions(record, record.scope);
7844
+ if (!list || list.length === 0) return list ?? void 0;
7845
+ return list.map((a) => ({
7846
+ ...a,
7847
+ onAction: () => {
7848
+ onTelemetry?.({
7849
+ type: "recordAction.invoke",
7850
+ recordType,
7851
+ key: a.key,
7852
+ ref: anchorKey(record.scope)
7853
+ });
7854
+ return a.onAction();
7855
+ }
7856
+ }));
7857
+ } : void 0;
7661
7858
  const baseScopeRef = editingScope?.raw ?? "";
7662
7859
  const itemNounLabel = itemNoun || "item";
7663
7860
  const {
7664
7861
  onItemOpen,
7862
+ onItemCreate,
7665
7863
  onItemBack,
7666
7864
  onItemPrev,
7667
7865
  onItemNext,
7668
7866
  itemPosition,
7669
- itemViewCtx} = useShellNavigation({
7867
+ itemViewCtx: baseItemViewCtx} = useShellNavigation({
7670
7868
  isCollection,
7671
7869
  runWithGuard,
7672
7870
  selectedItemId,
@@ -7686,8 +7884,10 @@ function RecordsAdminShellInner(props) {
7686
7884
  hooks: props.hooks,
7687
7885
  lastAppliedDLRef
7688
7886
  });
7887
+ const itemViewCtx = baseItemViewCtx;
7689
7888
  const renderEditorWithPreview = () => {
7690
7889
  if (!editingTargetScope) return null;
7890
+ const previewAnchorRef = previewReopenAnchorRef;
7691
7891
  const previewBody = renderPreview && effectivePreviewScope ? renderPreview({ resolved: editorCtx.value, previewScope: effectivePreviewScope }) : null;
7692
7892
  const scopePicker = previewScopePicker && effectivePreviewScope ? /* @__PURE__ */ jsx(
7693
7893
  PreviewScopePicker,
@@ -7789,15 +7989,14 @@ function RecordsAdminShellInner(props) {
7789
7989
  if (previewMode === "side") {
7790
7990
  if (!sidePreviewOpen) {
7791
7991
  return withNav(
7792
- /* @__PURE__ */ jsxs("div", { className: "relative h-full", children: [
7992
+ /* @__PURE__ */ jsxs("div", { className: "relative h-full", ref: previewAnchorRef, children: [
7793
7993
  baseEditor(),
7794
7994
  /* @__PURE__ */ jsxs(
7795
- "button",
7995
+ PreviewReopenPill,
7796
7996
  {
7797
- type: "button",
7798
- className: "ra-preview-reopen",
7997
+ anchorRef: previewAnchorRef,
7799
7998
  onClick: () => setSidePreviewOpen(true),
7800
- "aria-label": i18n.openPreview,
7999
+ ariaLabel: i18n.openPreview,
7801
8000
  title: i18n.openPreview,
7802
8001
  children: [
7803
8002
  /* @__PURE__ */ jsx(Eye, { "aria-hidden": "true" }),
@@ -8151,6 +8350,7 @@ function RecordsAdminShellInner(props) {
8151
8350
  });
8152
8351
  };
8153
8352
  onLeftSelectRef.current = onLeftSelect;
8353
+ onCreateItemDraftRef.current = onItemCreate;
8154
8354
  return /* @__PURE__ */ jsx(RuleLabelLookupProvider, { value: ruleLabelLookup, children: /* @__PURE__ */ jsxs(
8155
8355
  "div",
8156
8356
  {
@@ -8179,7 +8379,7 @@ function RecordsAdminShellInner(props) {
8179
8379
  }
8180
8380
  );
8181
8381
  })(),
8182
- /* @__PURE__ */ jsxs("div", { className: "px-4 pt-4 space-y-3", children: [
8382
+ /* @__PURE__ */ jsxs("div", { className: "px-2 pt-2 space-y-2", children: [
8183
8383
  intro && !dismissed && !(headerWillRender && resolvedReopenAffordance === "header") && /* @__PURE__ */ jsx(
8184
8384
  IntroCard,
8185
8385
  {
@@ -8318,6 +8518,8 @@ function RecordsAdminShellInner(props) {
8318
8518
  onSelect: onItemOpen,
8319
8519
  dirtyKeys,
8320
8520
  errorKeys,
8521
+ contextKind: activeScope === "rule" ? "Rule" : activeScope === "product" ? "Product" : activeScope === "collection" ? "Global" : activeScope === "all" ? "All records" : activeScope === "variant" ? "Variant" : activeScope === "batch" ? "Batch" : activeScope === "facet" ? "Facet" : void 0,
8522
+ contextSummary: activeScope === "rule" ? activeRuleSummary : activeScope === "product" ? editorHeaderLabel ?? null : null,
8321
8523
  i18n
8322
8524
  }
8323
8525
  ) : /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -8491,6 +8693,7 @@ function RecordsAdminShellInner(props) {
8491
8693
  ),
8492
8694
  renderGroupActions: renderRuleGroupActions,
8493
8695
  rowClipboard,
8696
+ rowActions: wrappedRecordActions,
8494
8697
  i18n
8495
8698
  }
8496
8699
  ),
@@ -8589,6 +8792,8 @@ function RecordsAdminShellInner(props) {
8589
8792
  itemColumns,
8590
8793
  cardSize: itemCardSize,
8591
8794
  ruleSummary: activeRuleSummary,
8795
+ rowActions: wrappedRecordActions,
8796
+ rowClipboard,
8592
8797
  i18n
8593
8798
  }
8594
8799
  ),
@@ -8675,7 +8880,41 @@ var RecordBrowser = ({
8675
8880
  scopesLoading,
8676
8881
  i18n
8677
8882
  }) => {
8678
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", style: { background: "hsl(var(--ra-surface))" }, children: [
8883
+ const rootRef = useRef(null);
8884
+ const searchRef = useRef(null);
8885
+ useEffect(() => {
8886
+ const root = rootRef.current;
8887
+ if (!root) return;
8888
+ const handler = (e) => {
8889
+ if (e.key !== "/" || e.metaKey || e.ctrlKey || e.altKey) return;
8890
+ const target = e.target;
8891
+ if (!target || !root.contains(target)) return;
8892
+ const tag = target.tagName;
8893
+ if (tag === "INPUT" || tag === "TEXTAREA" || target.isContentEditable) return;
8894
+ e.preventDefault();
8895
+ searchRef.current?.focus();
8896
+ searchRef.current?.select();
8897
+ };
8898
+ root.addEventListener("keydown", handler);
8899
+ return () => root.removeEventListener("keydown", handler);
8900
+ }, []);
8901
+ const onSearchKeyDown = (e) => {
8902
+ if (e.key === "Escape") {
8903
+ if (search) {
8904
+ e.preventDefault();
8905
+ onSearchChange("");
8906
+ } else {
8907
+ e.currentTarget.blur();
8908
+ }
8909
+ } else if (e.key === "ArrowDown") {
8910
+ const first = rootRef.current?.querySelector("button.ra-row-hit");
8911
+ if (first) {
8912
+ e.preventDefault();
8913
+ first.focus();
8914
+ }
8915
+ }
8916
+ };
8917
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: "flex flex-col h-full", style: { background: "hsl(var(--ra-surface))" }, children: [
8679
8918
  /* @__PURE__ */ jsx(ScopeTabs, { scopes, active: activeScope, onChange: onActiveScopeChange, loading: scopesLoading }),
8680
8919
  /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-2.5 border-b", style: { borderColor: "hsl(var(--ra-border))" }, children: [
8681
8920
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
@@ -8683,9 +8922,11 @@ var RecordBrowser = ({
8683
8922
  /* @__PURE__ */ jsx(
8684
8923
  "input",
8685
8924
  {
8925
+ ref: searchRef,
8686
8926
  type: "text",
8687
8927
  value: search,
8688
8928
  onChange: (e) => onSearchChange(e.target.value),
8929
+ onKeyDown: onSearchKeyDown,
8689
8930
  placeholder: i18n.searchPlaceholder,
8690
8931
  className: "w-full pl-8 pr-3 py-1.5 text-xs rounded-md border bg-transparent focus:outline-none focus:ring-1",
8691
8932
  style: {
@@ -8720,7 +8961,7 @@ var RecordBrowser = ({
8720
8961
  };
8721
8962
  var initials2 = (s) => s.split(/\s+/).filter(Boolean).slice(0, 2).map((p) => p[0]?.toUpperCase() ?? "").join("") || "?";
8722
8963
  var DefaultRecordCard = ({ record, ctx, variant = "grid" }) => {
8723
- const { selected, onSelect, isDirty, hasError, onCopy, onPaste, canPaste, pasteWillReplace, clipboardSourceLabel } = ctx;
8964
+ const { selected, onSelect, isDirty, hasError, onCopy, onDuplicate, actions } = ctx;
8724
8965
  const aspect = variant === "gallery" ? "aspect-video" : "aspect-square";
8725
8966
  return /* @__PURE__ */ jsxs(
8726
8967
  "button",
@@ -8781,20 +9022,15 @@ var DefaultRecordCard = ({ record, ctx, variant = "grid" }) => {
8781
9022
  /* @__PURE__ */ jsxs("div", { className: "p-2.5 min-w-0", children: [
8782
9023
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5 min-w-0", children: [
8783
9024
  /* @__PURE__ */ jsx("div", { className: "ra-row-title flex-1 min-w-0", children: record.label }),
8784
- (onCopy || onPaste) && /* @__PURE__ */ jsx(
9025
+ (onCopy || onDuplicate || actions && actions.length > 0) && /* @__PURE__ */ jsx(
8785
9026
  RowContextMenu,
8786
9027
  {
8787
9028
  onCopy,
8788
- onPaste,
8789
- canPaste,
8790
- pasteWillReplace,
8791
- pasteSourceLabel: clipboardSourceLabel,
9029
+ onDuplicate,
9030
+ actions,
8792
9031
  i18n: {
8793
9032
  copy: DEFAULT_I18N.copy,
8794
- paste: DEFAULT_I18N.paste,
8795
- pasteFrom: DEFAULT_I18N.pasteFrom,
8796
- pasteReplace: DEFAULT_I18N.pasteReplace,
8797
- clipboardEmpty: DEFAULT_I18N.clipboardEmpty
9033
+ duplicateAction: DEFAULT_I18N.duplicateAction
8798
9034
  }
8799
9035
  }
8800
9036
  )