@proveanything/smartlinks-utils-ui 0.12.4 → 0.12.5

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.
@@ -455,6 +455,14 @@ interface RecordsAdminI18n {
455
455
  duplicateAction: string;
456
456
  /** Toast shown after a successful Duplicate. `{name}` = source label. */
457
457
  duplicateToast: string;
458
+ /** Duplicate-to picker labels. */
459
+ duplicateDialogTitle: string;
460
+ duplicateTargetGlobal: string;
461
+ duplicateTargetSameRule: string;
462
+ duplicateTargetExistingRules: string;
463
+ duplicateTargetNewRule: string;
464
+ duplicateConfirm: string;
465
+ duplicateCancel: string;
458
466
  /**
459
467
  * Row-menu label for the "Copy and start new rule" action — shown on
460
468
  * the rules tab in singleton mode. One-click flow: copies the row to
@@ -7,7 +7,7 @@ import { cn } from '../../chunk-L7FQ52F5.js';
7
7
  import { parsedRefToTarget, parsedRefToScope, matchRecords, scopesEqual, getRecordById, listRecords, upsertRecord, updateRecord, createRecord, removeRecord } from '../../chunk-KA4MKRHL.js';
8
8
  export { bulkDelete, bulkUpsert, createRecord, getRecordById, listRecords, matchRecords, parsedRefToScope, parsedRefToTarget, removeRecord, restoreRecord, scopesEqual, upsertRecord } from '../../chunk-KA4MKRHL.js';
9
9
  import { createContext, useMemo, useState, useEffect, useCallback, useRef, isValidElement, useLayoutEffect, useContext, useSyncExternalStore, createElement } from 'react';
10
- 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, Check, Rows3, ChevronRight, Eraser, FilePlus2, CopyPlus, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, CornerDownLeft, Circle, ArrowUpDown, ArrowUp, ArrowDown, MinusCircle, XCircle, AlertCircle, Undo2, Save, Loader2, Archive, ArrowRight, Globe2, Settings2 } from 'lucide-react';
10
+ 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, Check, Rows3, ChevronRight, Eraser, FilePlus2, CopyPlus, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, CornerDownLeft, Circle, ArrowUpDown, ArrowUp, ArrowDown, MinusCircle, XCircle, AlertCircle, Undo2, Save, Loader2, Archive, ArrowRight, Globe2, Sparkles, Settings2 } from 'lucide-react';
11
11
  import { useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
12
12
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
13
13
  import { createPortal } from 'react-dom';
@@ -240,6 +240,13 @@ var DEFAULT_I18N = {
240
240
  pasteWarnContinue: "Continue",
241
241
  duplicateAction: "Duplicate",
242
242
  duplicateToast: "Duplicated {name}. Review and Save.",
243
+ duplicateDialogTitle: 'Duplicate "{name}" to:',
244
+ duplicateTargetGlobal: "Global",
245
+ duplicateTargetSameRule: "Same rule as source",
246
+ duplicateTargetExistingRules: "Existing rules",
247
+ duplicateTargetNewRule: "New rule\u2026",
248
+ duplicateConfirm: "Duplicate",
249
+ duplicateCancel: "Cancel",
243
250
  copyAndNewRuleAction: "Copy and start new rule",
244
251
  itemsAllLabel: "All items",
245
252
  subtitleEmpty: "Not set",
@@ -1621,6 +1628,199 @@ var ClipboardToast = ({ message, variant = "copy", onDismiss }) => {
1621
1628
  }
1622
1629
  );
1623
1630
  };
1631
+ var DEFAULT_I18N2 = {
1632
+ title: 'Duplicate "{name}" to:',
1633
+ global: "Global",
1634
+ sameRule: "Same rule as source",
1635
+ existingRules: "Existing rules",
1636
+ newRule: "New rule\u2026",
1637
+ confirm: "Duplicate",
1638
+ cancel: "Cancel"
1639
+ };
1640
+ function DuplicateTargetPicker({
1641
+ open,
1642
+ sourceLabel,
1643
+ sourceHasRule,
1644
+ rules,
1645
+ supportsRules,
1646
+ supportsGlobal = true,
1647
+ onCancel,
1648
+ onConfirm,
1649
+ i18n
1650
+ }) {
1651
+ const t = { ...DEFAULT_I18N2, ...i18n ?? {} };
1652
+ const [picked, setPicked] = useState(null);
1653
+ const containerRef = useRef(null);
1654
+ useEffect(() => {
1655
+ if (!open) {
1656
+ setPicked(null);
1657
+ return;
1658
+ }
1659
+ setPicked(sourceHasRule && supportsRules ? { kind: "sameRule" } : supportsGlobal ? { kind: "global" } : null);
1660
+ }, [open, sourceHasRule, supportsRules, supportsGlobal]);
1661
+ useEffect(() => {
1662
+ if (!open) return;
1663
+ const onKey = (e) => {
1664
+ if (e.key === "Escape") {
1665
+ e.preventDefault();
1666
+ onCancel();
1667
+ }
1668
+ };
1669
+ window.addEventListener("keydown", onKey);
1670
+ return () => window.removeEventListener("keydown", onKey);
1671
+ }, [open, onCancel]);
1672
+ const sortedRules = useMemo(() => rules.slice().sort((a, b) => {
1673
+ if (b.count !== a.count) return b.count - a.count;
1674
+ return a.summary.localeCompare(b.summary);
1675
+ }), [rules]);
1676
+ if (!open || typeof document === "undefined") return null;
1677
+ const stop = (e) => {
1678
+ e.stopPropagation();
1679
+ };
1680
+ const isSame = (a, b) => {
1681
+ if (!a || a.kind !== b.kind) return false;
1682
+ if (a.kind === "existingRule" && b.kind === "existingRule") {
1683
+ return a.ruleHash === b.ruleHash;
1684
+ }
1685
+ return true;
1686
+ };
1687
+ const RadioRow = ({
1688
+ target,
1689
+ label,
1690
+ sublabel,
1691
+ icon: Icon
1692
+ }) => {
1693
+ const checked = isSame(picked, target);
1694
+ return /* @__PURE__ */ jsxs(
1695
+ "button",
1696
+ {
1697
+ type: "button",
1698
+ onClick: () => setPicked(target),
1699
+ className: "w-full text-left px-3 py-2 rounded-md border transition-colors flex items-start gap-2.5",
1700
+ style: {
1701
+ borderColor: checked ? "hsl(var(--ra-accent))" : "hsl(var(--ra-border))",
1702
+ background: checked ? "hsl(var(--ra-accent) / 0.08)" : "transparent",
1703
+ color: "hsl(var(--ra-text))"
1704
+ },
1705
+ children: [
1706
+ /* @__PURE__ */ jsx(
1707
+ "span",
1708
+ {
1709
+ className: "mt-0.5 inline-flex items-center justify-center h-4 w-4 rounded-full shrink-0",
1710
+ style: {
1711
+ border: `2px solid ${checked ? "hsl(var(--ra-accent))" : "hsl(var(--ra-border))"}`
1712
+ },
1713
+ children: checked && /* @__PURE__ */ jsx(
1714
+ "span",
1715
+ {
1716
+ className: "h-2 w-2 rounded-full",
1717
+ style: { background: "hsl(var(--ra-accent))" }
1718
+ }
1719
+ )
1720
+ }
1721
+ ),
1722
+ Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 mt-0.5 shrink-0" }),
1723
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
1724
+ /* @__PURE__ */ jsx("span", { className: "block text-sm", children: label }),
1725
+ sublabel && /* @__PURE__ */ jsx("span", { className: "block text-xs truncate", style: { color: "hsl(var(--ra-muted-text))" }, children: sublabel })
1726
+ ] })
1727
+ ]
1728
+ }
1729
+ );
1730
+ };
1731
+ const showRuleSection = supportsRules && (sortedRules.length > 0 || sourceHasRule);
1732
+ return createPortal(
1733
+ /* @__PURE__ */ jsx(
1734
+ "div",
1735
+ {
1736
+ className: "fixed inset-0 z-[1000] flex items-center justify-center",
1737
+ style: { background: "hsl(0 0% 0% / 0.4)" },
1738
+ onClick: onCancel,
1739
+ onMouseDown: stop,
1740
+ onTouchStart: stop,
1741
+ children: /* @__PURE__ */ jsxs(
1742
+ "div",
1743
+ {
1744
+ ref: containerRef,
1745
+ role: "dialog",
1746
+ "aria-modal": "true",
1747
+ className: "w-[min(28rem,calc(100vw-2rem))] max-h-[min(36rem,calc(100vh-2rem))] flex flex-col rounded-lg shadow-xl",
1748
+ style: { background: "hsl(var(--ra-surface))", border: "1px solid hsl(var(--ra-border))" },
1749
+ onClick: stop,
1750
+ onMouseDown: stop,
1751
+ onTouchStart: stop,
1752
+ children: [
1753
+ /* @__PURE__ */ jsx("header", { className: "px-4 py-3 border-b", style: { borderColor: "hsl(var(--ra-border))" }, children: /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", style: { color: "hsl(var(--ra-text))" }, children: t.title.replace("{name}", sourceLabel) }) }),
1754
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 overflow-y-auto px-4 py-3 space-y-3", children: [
1755
+ supportsGlobal && /* @__PURE__ */ jsx(RadioRow, { target: { kind: "global" }, label: t.global, icon: Globe2 }),
1756
+ showRuleSection && /* @__PURE__ */ jsxs(Fragment, { children: [
1757
+ sourceHasRule && /* @__PURE__ */ jsx(RadioRow, { target: { kind: "sameRule" }, label: t.sameRule, icon: Target }),
1758
+ sortedRules.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
1759
+ /* @__PURE__ */ jsx(
1760
+ "div",
1761
+ {
1762
+ className: "text-[10px] uppercase tracking-wide mb-1.5 px-1",
1763
+ style: { color: "hsl(var(--ra-muted-text))" },
1764
+ children: t.existingRules
1765
+ }
1766
+ ),
1767
+ /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: sortedRules.map((r) => /* @__PURE__ */ jsx(
1768
+ RadioRow,
1769
+ {
1770
+ target: { kind: "existingRule", ruleHash: r.hash },
1771
+ label: r.summary,
1772
+ sublabel: `${r.count} record${r.count === 1 ? "" : "s"}`,
1773
+ icon: Target
1774
+ },
1775
+ r.hash
1776
+ )) })
1777
+ ] }),
1778
+ /* @__PURE__ */ jsx(RadioRow, { target: { kind: "newRule" }, label: t.newRule, icon: Sparkles })
1779
+ ] })
1780
+ ] }),
1781
+ /* @__PURE__ */ jsxs(
1782
+ "footer",
1783
+ {
1784
+ className: "px-4 py-3 border-t flex items-center justify-end gap-2",
1785
+ style: { borderColor: "hsl(var(--ra-border))" },
1786
+ children: [
1787
+ /* @__PURE__ */ jsx(
1788
+ "button",
1789
+ {
1790
+ type: "button",
1791
+ onClick: onCancel,
1792
+ className: "text-xs px-3 py-1.5 rounded-md border hover:bg-[hsl(var(--ra-muted))]",
1793
+ style: { borderColor: "hsl(var(--ra-border))", color: "hsl(var(--ra-text))" },
1794
+ children: t.cancel
1795
+ }
1796
+ ),
1797
+ /* @__PURE__ */ jsxs(
1798
+ "button",
1799
+ {
1800
+ type: "button",
1801
+ disabled: !picked,
1802
+ onClick: () => {
1803
+ if (picked) onConfirm(picked);
1804
+ },
1805
+ className: "text-xs px-3 py-1.5 rounded-md font-medium transition-opacity disabled:opacity-40 inline-flex items-center gap-1.5",
1806
+ style: { background: "hsl(var(--ra-accent))", color: "hsl(var(--ra-surface))" },
1807
+ children: [
1808
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
1809
+ t.confirm
1810
+ ]
1811
+ }
1812
+ )
1813
+ ]
1814
+ }
1815
+ )
1816
+ ]
1817
+ }
1818
+ )
1819
+ }
1820
+ ),
1821
+ document.body
1822
+ );
1823
+ }
1624
1824
  function useShellClipboard(args) {
1625
1825
  const {
1626
1826
  enabled,
@@ -1640,7 +1840,11 @@ function useShellClipboard(args) {
1640
1840
  onLeftSelectRef,
1641
1841
  onCreateItemDraftRef,
1642
1842
  onCreateRuleFromClipboardRef,
1643
- isRuleTab
1843
+ isRuleTab,
1844
+ ruleCatalogue,
1845
+ ruleCatalogueRef,
1846
+ supportsRules = true,
1847
+ onCreateItemDraftAtScopeRef
1644
1848
  } = args;
1645
1849
  const clipboard = useRecordClipboard({
1646
1850
  appId,
@@ -1770,17 +1974,98 @@ function useShellClipboard(args) {
1770
1974
  // wired item-draft creation. Pushes the editor value to the clipboard
1771
1975
  // (so the existing pendingPasteTarget effect lands it on the new
1772
1976
  // draft) and mints a fresh draft.
1773
- onDuplicate: isCollection && !!onCreateItemDraftRef ? () => {
1774
- copyCurrent();
1775
- window.setTimeout(() => {
1776
- const create = onCreateItemDraftRef?.current;
1777
- if (!create) return;
1778
- const newId = create();
1779
- if (newId) setPendingPasteTarget({ kind: "record", recordId: newId });
1780
- }, 0);
1977
+ // Collection-cardinality clone affordance opens the Duplicate-To
1978
+ // picker so the admin chooses the destination scope (Global, an
1979
+ // existing rule, a new rule, or the source's own rule). We only show
1980
+ // this when the host wired the at-scope draft creator, since without
1981
+ // it we have nowhere to mint the duplicate.
1982
+ onDuplicate: isCollection && !!onCreateItemDraftAtScopeRef ? () => {
1983
+ if (!editingScope) return;
1984
+ openDuplicatePicker({
1985
+ value: editorCtx.value,
1986
+ scope: editingScope,
1987
+ label: editorHeaderLabel ?? editingScope.raw,
1988
+ facetRule: resolved.facetRule ?? null,
1989
+ recordId: resolved.recordId
1990
+ });
1781
1991
  } : void 0
1782
1992
  } : void 0;
1783
1993
  const [pendingPasteTarget, setPendingPasteTarget] = useState(null);
1994
+ const [pendingSeed, setPendingSeed] = useState(null);
1995
+ const [pickerOpen, setPickerOpen] = useState(false);
1996
+ const pickerSourceRef = useRef(null);
1997
+ const openDuplicatePicker = useCallback((source) => {
1998
+ pickerSourceRef.current = {
1999
+ value: source.value,
2000
+ scope: source.scope,
2001
+ label: source.label,
2002
+ facetRule: source.facetRule ?? null,
2003
+ recordId: source.recordId
2004
+ };
2005
+ setPickerOpen(true);
2006
+ }, []);
2007
+ const onDuplicatePickerCancel = useCallback(() => {
2008
+ setPickerOpen(false);
2009
+ pickerSourceRef.current = null;
2010
+ }, []);
2011
+ const onDuplicatePickerConfirm = useCallback((target) => {
2012
+ const source = pickerSourceRef.current;
2013
+ setPickerOpen(false);
2014
+ pickerSourceRef.current = null;
2015
+ if (!source) return;
2016
+ const transformed = onCopyOverride ? onCopyOverride({ value: source.value, scope: source.scope }) : cloneValue(source.value);
2017
+ if (target.kind === "newRule") {
2018
+ clipboard.set({
2019
+ value: transformed,
2020
+ sourceScope: source.scope,
2021
+ sourceRecordId: source.recordId,
2022
+ sourceLabel: source.label
2023
+ });
2024
+ onTelemetry?.({
2025
+ type: "clipboard.copy",
2026
+ recordType,
2027
+ sourceRef: anchorKey(source.scope)
2028
+ });
2029
+ window.setTimeout(() => onCreateRuleFromClipboardRef?.current?.(), 0);
2030
+ return;
2031
+ }
2032
+ const create = onCreateItemDraftAtScopeRef?.current;
2033
+ if (!create) return;
2034
+ let scopeArg;
2035
+ if (target.kind === "global") {
2036
+ scopeArg = { kind: "global" };
2037
+ } else if (target.kind === "sameRule") {
2038
+ if (!source.facetRule) return;
2039
+ scopeArg = { kind: "rule", rule: source.facetRule };
2040
+ } else {
2041
+ const list = ruleCatalogueRef?.current ?? ruleCatalogue ?? [];
2042
+ const hit = list.find((r) => r.hash === target.ruleHash);
2043
+ if (!hit) return;
2044
+ scopeArg = { kind: "rule", rule: hit.rule };
2045
+ }
2046
+ const newId = create(scopeArg);
2047
+ if (!newId) return;
2048
+ setPendingPasteTarget({ kind: "record", recordId: newId });
2049
+ setPendingSeed({ value: transformed, sourceLabel: source.label });
2050
+ onTelemetry?.({
2051
+ type: "clipboard.copy",
2052
+ recordType,
2053
+ sourceRef: anchorKey(source.scope)
2054
+ });
2055
+ setNotice({
2056
+ message: i18n.duplicateToast.replace("{name}", source.label),
2057
+ variant: "copy"
2058
+ });
2059
+ }, [
2060
+ onCopyOverride,
2061
+ clipboard,
2062
+ onTelemetry,
2063
+ recordType,
2064
+ onCreateRuleFromClipboardRef,
2065
+ onCreateItemDraftAtScopeRef,
2066
+ ruleCatalogue,
2067
+ i18n.duplicateToast
2068
+ ]);
1784
2069
  useEffect(() => {
1785
2070
  if (!pendingPasteTarget) return;
1786
2071
  if (!editingScope) return;
@@ -1788,14 +2073,27 @@ function useShellClipboard(args) {
1788
2073
  if (!matched) return;
1789
2074
  const t = window.setTimeout(() => {
1790
2075
  setPendingPasteTarget(null);
2076
+ if (pendingSeed) {
2077
+ const seed = pendingSeed;
2078
+ setPendingSeed(null);
2079
+ editorCtx.onChange(seed.value);
2080
+ onTelemetry?.({
2081
+ type: "clipboard.paste",
2082
+ recordType,
2083
+ sourceRef: anchorKey(editingScope),
2084
+ destinationRef: editingScope.raw,
2085
+ replaced: false
2086
+ });
2087
+ return;
2088
+ }
1791
2089
  void pasteCurrent();
1792
2090
  }, 0);
1793
2091
  return () => window.clearTimeout(t);
1794
- }, [pendingPasteTarget, editingScope, isCollection, selectedItemId, selectedRecordId, pasteCurrent]);
2092
+ }, [pendingPasteTarget, editingScope, isCollection, selectedItemId, selectedRecordId, pasteCurrent, pendingSeed, editorCtx, onTelemetry, recordType]);
1795
2093
  const rowClipboard = enabled ? (record) => {
1796
2094
  const summaryHasData = record.data != null;
1797
2095
  const sourceParsed = record.scope;
1798
- const canDuplicate = summaryHasData && isCollection;
2096
+ const canDuplicate = summaryHasData && isCollection && !!onCreateItemDraftAtScopeRef;
1799
2097
  const canCopyAndNewRule = summaryHasData && !isCollection && !!isRuleTab && !!onCreateRuleFromClipboardRef;
1800
2098
  return {
1801
2099
  onCopy: summaryHasData ? () => {
@@ -1826,24 +2124,12 @@ function useShellClipboard(args) {
1826
2124
  }, 0);
1827
2125
  } : void 0,
1828
2126
  onDuplicate: canDuplicate ? () => {
1829
- const value = onCopyOverride ? onCopyOverride({ value: record.data, scope: sourceParsed }) : cloneValue(record.data);
1830
- clipboard.set({
1831
- value,
1832
- sourceScope: sourceParsed,
1833
- sourceRecordId: record.id ?? void 0,
1834
- sourceLabel: record.label
1835
- });
1836
- onTelemetry?.({ type: "clipboard.copy", recordType, sourceRef: anchorKey(record.scope) });
1837
- onLeftSelectRef.current?.(record);
1838
- const create = onCreateItemDraftRef?.current;
1839
- if (!create) return;
1840
- window.setTimeout(() => {
1841
- const newId = create();
1842
- if (newId) setPendingPasteTarget({ kind: "record", recordId: newId });
1843
- }, 0);
1844
- setNotice({
1845
- message: i18n.duplicateToast.replace("{name}", record.label),
1846
- variant: "copy"
2127
+ openDuplicatePicker({
2128
+ value: record.data,
2129
+ scope: sourceParsed,
2130
+ label: record.label,
2131
+ facetRule: record.facetRule ?? null,
2132
+ recordId: record.id ?? void 0
1847
2133
  });
1848
2134
  } : void 0
1849
2135
  };
@@ -1856,10 +2142,36 @@ function useShellClipboard(args) {
1856
2142
  onDismiss: () => setNotice(null)
1857
2143
  }
1858
2144
  ) : null;
2145
+ const duplicatePicker = enabled ? /* @__PURE__ */ jsx(
2146
+ DuplicateTargetPicker,
2147
+ {
2148
+ open: pickerOpen,
2149
+ sourceLabel: pickerSourceRef.current?.label ?? "",
2150
+ sourceHasRule: !!pickerSourceRef.current?.facetRule,
2151
+ rules: (ruleCatalogueRef?.current ?? ruleCatalogue ?? []).map((r) => ({
2152
+ hash: r.hash,
2153
+ summary: r.summary,
2154
+ count: r.count
2155
+ })),
2156
+ supportsRules,
2157
+ onCancel: onDuplicatePickerCancel,
2158
+ onConfirm: onDuplicatePickerConfirm,
2159
+ i18n: {
2160
+ title: i18n.duplicateDialogTitle,
2161
+ global: i18n.duplicateTargetGlobal,
2162
+ sameRule: i18n.duplicateTargetSameRule,
2163
+ existingRules: i18n.duplicateTargetExistingRules,
2164
+ newRule: i18n.duplicateTargetNewRule,
2165
+ confirm: i18n.duplicateConfirm,
2166
+ cancel: i18n.duplicateCancel
2167
+ }
2168
+ }
2169
+ ) : null;
1859
2170
  return {
1860
2171
  editorClipboard,
1861
2172
  rowClipboard,
1862
2173
  confirmDialog: pasteConfirm.dialog,
2174
+ duplicatePicker,
1863
2175
  toast,
1864
2176
  store: clipboard
1865
2177
  };
@@ -9178,6 +9490,8 @@ function RecordsAdminShellInner(props) {
9178
9490
  ]);
9179
9491
  const onLeftSelectRef = useRef(null);
9180
9492
  const onCreateItemDraftRef = useRef(null);
9493
+ const onCreateItemDraftAtScopeRef = useRef(null);
9494
+ const ruleCatalogueRef = useRef([]);
9181
9495
  const onCreateRuleFromClipboardRef = useRef(null);
9182
9496
  const previewReopenAnchorRef = useRef(null);
9183
9497
  const { runWithGuard } = useDirtyNavigation({
@@ -9230,14 +9544,17 @@ function RecordsAdminShellInner(props) {
9230
9544
  isCollection,
9231
9545
  selectedItemId,
9232
9546
  selectedRecordId,
9233
- resolved: { source: resolved.source, recordId: resolved.recordId },
9547
+ resolved: { source: resolved.source, recordId: resolved.recordId, facetRule: resolved.facetRule ?? null },
9234
9548
  onTelemetry,
9235
9549
  onCopyOverride,
9236
9550
  onPasteOverride,
9237
9551
  onLeftSelectRef,
9238
9552
  onCreateItemDraftRef,
9239
9553
  onCreateRuleFromClipboardRef,
9240
- isRuleTab: activeScope === "rule" || activeScope === "collection"
9554
+ isRuleTab: activeScope === "rule" || activeScope === "collection",
9555
+ ruleCatalogueRef,
9556
+ supportsRules: effectiveTopLevelScopes.includes("rule"),
9557
+ onCreateItemDraftAtScopeRef
9241
9558
  });
9242
9559
  const editorClipboard = shellClipboard.editorClipboard;
9243
9560
  const rowClipboard = shellClipboard.rowClipboard;
@@ -10019,6 +10336,26 @@ function RecordsAdminShellInner(props) {
10019
10336
  onLeftSelectRef.current = onLeftSelect;
10020
10337
  onCreateItemDraftRef.current = onItemCreate;
10021
10338
  onCreateRuleFromClipboardRef.current = () => onCreateRule("paste");
10339
+ ruleCatalogueRef.current = ruleCatalogue;
10340
+ onCreateItemDraftAtScopeRef.current = (target) => {
10341
+ if (!isCollection) return null;
10342
+ if (target.kind === "global") {
10343
+ if (activeScope !== "collection") setActiveScope("collection");
10344
+ setSelectedRecordId(null);
10345
+ setDraftKind(null);
10346
+ const id2 = onItemCreate();
10347
+ return id2 ?? null;
10348
+ }
10349
+ if (activeScope !== "rule") setActiveScope("rule");
10350
+ setRuleWizardRule(target.rule);
10351
+ setRuleWizardSeedMode(null);
10352
+ setRuleWizardDraftKey(null);
10353
+ setRuleWizardStep(2);
10354
+ setDraftKind("rule");
10355
+ setSelectedRecordId(null);
10356
+ const id = onItemCreate();
10357
+ return id ?? null;
10358
+ };
10022
10359
  return /* @__PURE__ */ jsx(RuleLabelLookupProvider, { value: ruleLabelLookup, children: /* @__PURE__ */ jsxs(
10023
10360
  "div",
10024
10361
  {
@@ -10027,6 +10364,7 @@ function RecordsAdminShellInner(props) {
10027
10364
  children: [
10028
10365
  dirtyConfirm.dialog,
10029
10366
  shellClipboard.confirmDialog,
10367
+ shellClipboard.duplicatePicker,
10030
10368
  shellClipboard.toast,
10031
10369
  (() => {
10032
10370
  const showFloatHelp = !!intro && dismissed && resolvedReopenAffordance === "footer" && !headerWillRender;