@proveanything/smartlinks-utils-ui 0.10.2 → 0.10.4

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.
@@ -336,6 +336,10 @@ interface RecordsAdminI18n {
336
336
  previewAs: string;
337
337
  previewAsDefault: string;
338
338
  confirmDelete: string;
339
+ /** Confirm dialog title shown when the row trash is clicked in the item list. */
340
+ deleteConfirmTitle: string;
341
+ /** Confirm dialog body. `{name}` is replaced with the record's friendly label. */
342
+ deleteConfirmBody: string;
339
343
  unsavedBadge: string;
340
344
  unsavedPromptTitle: string;
341
345
  unsavedPromptBody: string;
@@ -2055,6 +2059,15 @@ declare const useProductBrowse: (args: UseProductBrowseArgs) => {
2055
2059
  refetch: () => Promise<void>;
2056
2060
  };
2057
2061
 
2062
+ interface FacetValueShape {
2063
+ key?: string;
2064
+ name?: string;
2065
+ }
2066
+ interface FacetShape {
2067
+ key?: string;
2068
+ name?: string;
2069
+ values?: FacetValueShape[];
2070
+ }
2058
2071
  interface UseFacetBrowseArgs {
2059
2072
  SL: SmartLinksSDK;
2060
2073
  collectionId: string;
@@ -2075,6 +2088,12 @@ declare const useFacetBrowse: ({ SL, collectionId, existing, search, filter, ena
2075
2088
  isLoading: boolean;
2076
2089
  error: Error | null;
2077
2090
  refetch: () => void;
2091
+ /** Raw canonical facet vocabulary as returned by the SDK. Use this
2092
+ * (NOT `items`) when you need the authoritative list of facets and
2093
+ * their values — `items` is the record-merged list and may include
2094
+ * rows whose scope's `facetId` doesn't match a canonical key, which
2095
+ * would otherwise show up as duplicate facet groups. */
2096
+ vocabulary: FacetShape[];
2078
2097
  };
2079
2098
 
2080
2099
  interface CollectedRecord<T = unknown> {
@@ -92,6 +92,8 @@ var DEFAULT_I18N = {
92
92
  previewAs: "Preview as",
93
93
  previewAsDefault: "Same as edited",
94
94
  confirmDelete: "Confirm delete",
95
+ deleteConfirmTitle: "Delete this record?",
96
+ deleteConfirmBody: "Are you sure you want to delete \u201C{name}\u201D? This cannot be undone.",
95
97
  unsavedBadge: "Unsaved",
96
98
  unsavedPromptTitle: "Unsaved changes",
97
99
  unsavedPromptBody: "You have unsaved changes. What would you like to do?",
@@ -817,7 +819,13 @@ var useFacetBrowse = ({
817
819
  counts,
818
820
  isLoading: query.isLoading,
819
821
  error: query.error ?? null,
820
- refetch
822
+ refetch,
823
+ /** Raw canonical facet vocabulary as returned by the SDK. Use this
824
+ * (NOT `items`) when you need the authoritative list of facets and
825
+ * their values — `items` is the record-merged list and may include
826
+ * rows whose scope's `facetId` doesn't match a canonical key, which
827
+ * would otherwise show up as duplicate facet groups. */
828
+ vocabulary: query.data ?? []
821
829
  };
822
830
  };
823
831
  var QK2 = ["records-admin", "product-browse"];
@@ -2943,9 +2951,9 @@ function useEditorBridge(args) {
2943
2951
  const next = session?.status ?? null;
2944
2952
  prevStatusRef.current = next;
2945
2953
  if (next === "saved" && prev !== "saved") {
2946
- onSaved?.();
2954
+ onSaved?.(session?.recordId);
2947
2955
  }
2948
- }, [session?.status]);
2956
+ }, [session?.status, session?.recordId]);
2949
2957
  const remove = useMemo(() => async () => {
2950
2958
  if (!session) return;
2951
2959
  await session.remove();
@@ -3071,7 +3079,7 @@ function useShellEditorTarget(args) {
3071
3079
  facetRule: resolved.facetRule
3072
3080
  },
3073
3081
  defaultData,
3074
- onSaved: () => onSaved(resolved.source !== "self"),
3082
+ onSaved: (recordId) => onSaved(resolved.source !== "self", recordId),
3075
3083
  onDeleted
3076
3084
  });
3077
3085
  return { editorTargetSpec, editorCtx };
@@ -3697,6 +3705,8 @@ var FacetList = RecordList;
3697
3705
  var VariantList = RecordList;
3698
3706
  var BatchList = RecordList;
3699
3707
  var COLLAPSED_FACET_CAP = 6;
3708
+ var COLLAPSED_VALUE_CAP = 12;
3709
+ var VALUE_SEARCH_THRESHOLD = 12;
3700
3710
  function FacetBrowseFilter({
3701
3711
  facets,
3702
3712
  value,
@@ -3710,12 +3720,33 @@ function FacetBrowseFilter({
3710
3720
  );
3711
3721
  const [openKey, setOpenKey] = useState(value?.facetKey ?? firstUsable ?? null);
3712
3722
  const [allExpanded, setAllExpanded] = useState(false);
3723
+ const [valuesExpanded, setValuesExpanded] = useState({});
3724
+ const [valueSearch, setValueSearch] = useState({});
3713
3725
  const effectiveOpen = value?.facetKey ?? openKey ?? firstUsable ?? null;
3714
3726
  if (isLoading) {
3715
3727
  return /* @__PURE__ */ jsx("div", { className: "ra-rule-filters", "aria-busy": "true", children: /* @__PURE__ */ jsx("div", { className: "ra-rule-filters-row", children: /* @__PURE__ */ jsx("span", { className: "ra-rule-filter-chip", "data-active": "false", children: "Loading facets\u2026" }) }) });
3716
3728
  }
3717
3729
  if (!facets.length) return null;
3718
3730
  const openFacet = facets.find((f) => f.key === effectiveOpen) ?? null;
3731
+ const openValuesExpanded = openFacet ? !!valuesExpanded[openFacet.key] : false;
3732
+ const openValueSearch = openFacet ? valueSearch[openFacet.key] ?? "" : "";
3733
+ const openValuesFiltered = useMemo(() => {
3734
+ if (!openFacet) return [];
3735
+ const q = openValueSearch.trim().toLowerCase();
3736
+ if (!q) return openFacet.values;
3737
+ return openFacet.values.filter(
3738
+ (v) => `${v.label ?? ""} ${v.key}`.toLowerCase().includes(q)
3739
+ );
3740
+ }, [openFacet, openValueSearch]);
3741
+ const valueOverflow = openFacet ? openValuesFiltered.length - COLLAPSED_VALUE_CAP : 0;
3742
+ const visibleValues = openFacet ? openValuesExpanded || valueOverflow <= 0 ? openValuesFiltered : (() => {
3743
+ const head = openValuesFiltered.slice(0, COLLAPSED_VALUE_CAP);
3744
+ const activeVal = value && value.facetKey === openFacet.key ? openFacet.values.find((v) => v.key === value.facetValue) : null;
3745
+ if (activeVal && !head.includes(activeVal)) {
3746
+ return [...head.slice(0, COLLAPSED_VALUE_CAP - 1), activeVal];
3747
+ }
3748
+ return head;
3749
+ })() : [];
3719
3750
  const overflow = facets.length - COLLAPSED_FACET_CAP;
3720
3751
  let visibleFacets = facets;
3721
3752
  if (!allExpanded && overflow > 0) {
@@ -3764,30 +3795,57 @@ function FacetBrowseFilter({
3764
3795
  }
3765
3796
  )
3766
3797
  ] }),
3767
- openFacet && openFacet.values.length > 0 && /* @__PURE__ */ jsx(
3798
+ openFacet && openFacet.values.length > 0 && /* @__PURE__ */ jsxs(
3768
3799
  "div",
3769
3800
  {
3770
3801
  className: "ra-rule-filters-row",
3771
3802
  role: "group",
3772
3803
  "aria-label": `Pick a ${openFacet.label ?? openFacet.key} value`,
3773
- style: { paddingLeft: "0.25rem" },
3774
- children: openFacet.values.map((v) => {
3775
- const active = value?.facetKey === openFacet.key && value?.facetValue === v.key;
3776
- return /* @__PURE__ */ jsx(
3804
+ style: { paddingLeft: "0.25rem", maxHeight: "8.5rem", overflowY: "auto" },
3805
+ children: [
3806
+ openFacet.values.length > VALUE_SEARCH_THRESHOLD && /* @__PURE__ */ jsx(
3807
+ "input",
3808
+ {
3809
+ type: "search",
3810
+ value: openValueSearch,
3811
+ onChange: (e) => setValueSearch((s) => ({ ...s, [openFacet.key]: e.target.value })),
3812
+ placeholder: `Search ${openFacet.label ?? openFacet.key}\u2026`,
3813
+ "aria-label": `Search ${openFacet.label ?? openFacet.key} values`,
3814
+ className: "ra-rule-filter-chip",
3815
+ style: { minWidth: "8rem", writingMode: "horizontal-tb" }
3816
+ }
3817
+ ),
3818
+ visibleValues.map((v) => {
3819
+ const active = value?.facetKey === openFacet.key && value?.facetValue === v.key;
3820
+ return /* @__PURE__ */ jsx(
3821
+ "button",
3822
+ {
3823
+ type: "button",
3824
+ className: "ra-rule-filter-chip",
3825
+ "data-tone": "complexity",
3826
+ "data-active": active ? "true" : "false",
3827
+ "aria-pressed": active,
3828
+ onClick: () => onChange(active ? null : { facetKey: openFacet.key, facetValue: v.key }),
3829
+ title: `Filter to ${openFacet.label ?? openFacet.key} = ${v.label ?? v.key}`,
3830
+ children: /* @__PURE__ */ jsx("span", { className: "ra-rule-filter-chip-label", children: v.label ?? v.key })
3831
+ },
3832
+ v.key
3833
+ );
3834
+ }),
3835
+ valueOverflow > 0 && /* @__PURE__ */ jsx(
3777
3836
  "button",
3778
3837
  {
3779
3838
  type: "button",
3839
+ onClick: () => setValuesExpanded((s) => ({ ...s, [openFacet.key]: !openValuesExpanded })),
3780
3840
  className: "ra-rule-filter-chip",
3781
- "data-tone": "complexity",
3782
- "data-active": active ? "true" : "false",
3783
- "aria-pressed": active,
3784
- onClick: () => onChange(active ? null : { facetKey: openFacet.key, facetValue: v.key }),
3785
- title: `Filter to ${openFacet.label ?? openFacet.key} = ${v.label ?? v.key}`,
3786
- children: /* @__PURE__ */ jsx("span", { className: "ra-rule-filter-chip-label", children: v.label ?? v.key })
3787
- },
3788
- v.key
3789
- );
3790
- })
3841
+ "data-active": "false",
3842
+ "aria-expanded": openValuesExpanded,
3843
+ title: openValuesExpanded ? "Show fewer values" : `Show all ${openValuesFiltered.length} values`,
3844
+ children: /* @__PURE__ */ jsx("span", { className: "ra-rule-filter-chip-label", children: openValuesExpanded ? "Show fewer" : `Show all (${openValuesFiltered.length})` })
3845
+ }
3846
+ ),
3847
+ openValuesFiltered.length === 0 && /* @__PURE__ */ jsx("span", { className: "ra-rule-filter-clear", style: { cursor: "default", textDecoration: "none" }, children: "No values match." })
3848
+ ]
3791
3849
  }
3792
3850
  ),
3793
3851
  value && /* @__PURE__ */ jsx(
@@ -5774,6 +5832,17 @@ function ItemListView({
5774
5832
  i18n
5775
5833
  }) {
5776
5834
  const newLabel = i18n.newItem.includes("{noun}") ? i18n.newItem.replace("{noun}", itemNoun) : i18n.newItem;
5835
+ const [pendingDeleteId, setPendingDeleteId] = useState(null);
5836
+ const pendingRecord = useMemo(
5837
+ () => pendingDeleteId ? items.find((it) => (it.itemId ?? "") === pendingDeleteId) ?? null : null,
5838
+ [pendingDeleteId, items]
5839
+ );
5840
+ const guardedCtx = useMemo(() => ({
5841
+ ...ctx,
5842
+ onDelete: (id) => setPendingDeleteId(id)
5843
+ }), [ctx]);
5844
+ const confirmName = pendingRecord?.label ?? itemNoun;
5845
+ const confirmBody = i18n.deleteConfirmBody.includes("{name}") ? i18n.deleteConfirmBody.replace("{name}", confirmName) : i18n.deleteConfirmBody;
5777
5846
  const toolbar = /* @__PURE__ */ jsxs("div", { className: "ra-item-toolbar", children: [
5778
5847
  /* @__PURE__ */ jsxs("div", { className: "ra-item-toolbar-title", children: [
5779
5848
  /* @__PURE__ */ jsx("h2", { className: "ra-display", style: { fontSize: "0.95rem", margin: 0 }, children: i18n.itemListTitle }),
@@ -5831,16 +5900,16 @@ function ItemListView({
5831
5900
  }
5832
5901
  );
5833
5902
  } else if (renderItemList) {
5834
- body = renderItemList(items, ctx);
5903
+ body = renderItemList(items, guardedCtx);
5835
5904
  } else if (view === "table") {
5836
5905
  body = /* @__PURE__ */ jsx(
5837
5906
  DefaultItemTable,
5838
5907
  {
5839
5908
  items,
5840
5909
  columns: itemColumns,
5841
- selectedId: ctx.selectedId,
5842
- onOpen: ctx.onOpen,
5843
- onDelete: ctx.onDelete,
5910
+ selectedId: guardedCtx.selectedId,
5911
+ onOpen: guardedCtx.onOpen,
5912
+ onDelete: guardedCtx.onDelete,
5844
5913
  rowActions,
5845
5914
  rowClipboard,
5846
5915
  i18n
@@ -5852,8 +5921,8 @@ function ItemListView({
5852
5921
  {
5853
5922
  items,
5854
5923
  variant: view,
5855
- selectedId: ctx.selectedId,
5856
- ctx,
5924
+ selectedId: guardedCtx.selectedId,
5925
+ ctx: guardedCtx,
5857
5926
  renderCard: renderItemCard,
5858
5927
  cardSize,
5859
5928
  rowActions,
@@ -5885,7 +5954,23 @@ function ItemListView({
5885
5954
  ]
5886
5955
  }
5887
5956
  ) : null,
5888
- /* @__PURE__ */ jsx("div", { className: "ra-item-list-body", children: body })
5957
+ /* @__PURE__ */ jsx("div", { className: "ra-item-list-body", children: body }),
5958
+ /* @__PURE__ */ jsx(
5959
+ ConfirmDialog,
5960
+ {
5961
+ open: pendingDeleteId !== null,
5962
+ title: i18n.deleteConfirmTitle,
5963
+ body: confirmBody,
5964
+ saveLabel: "",
5965
+ discardLabel: i18n.delete,
5966
+ cancelLabel: i18n.pasteConfirmCancel,
5967
+ onChoice: (choice) => {
5968
+ const id = pendingDeleteId;
5969
+ setPendingDeleteId(null);
5970
+ if (choice === "discard" && id !== null) ctx.onDelete(id);
5971
+ }
5972
+ }
5973
+ )
5889
5974
  ] });
5890
5975
  }
5891
5976
  var EditorItemNav = ({
@@ -7684,7 +7769,7 @@ function RecordsAdminShellInner(props) {
7684
7769
  },
7685
7770
  defaultData,
7686
7771
  deriveDraftLabel,
7687
- onSaved: (isCreate) => {
7772
+ onSaved: (isCreate, savedRecordId) => {
7688
7773
  onTelemetry?.({ type: "record.save", recordType, ref: editingTargetScope?.raw ?? "", isCreate });
7689
7774
  if (ruleWizardStep !== null) {
7690
7775
  setRuleWizardStep(null);
@@ -7695,6 +7780,9 @@ function RecordsAdminShellInner(props) {
7695
7780
  setSelectedRecordId(null);
7696
7781
  setDraftKind(null);
7697
7782
  }
7783
+ if (isCreate && isCollection && savedRecordId && isDraftId3(selectedItemId)) {
7784
+ setSelectedItemId(savedRecordId);
7785
+ }
7698
7786
  refetchAll();
7699
7787
  },
7700
7788
  onDeleted: () => {
@@ -8292,25 +8380,18 @@ function RecordsAdminShellInner(props) {
8292
8380
  [facetBrowseFilter]
8293
8381
  );
8294
8382
  const facetBrowseFacets = useMemo(() => {
8295
- if (!facetBrowse.items.length) return [];
8296
- const byKey = /* @__PURE__ */ new Map();
8297
- for (const it of facetBrowse.items) {
8298
- const key = it.scope.facetId;
8299
- const value = it.scope.facetValue;
8300
- if (!key || !value) continue;
8301
- const facetLabel = it.subtitle ?? key;
8302
- const valueLabel = it.label ?? value;
8303
- const existing = byKey.get(key);
8304
- if (existing) {
8305
- if (!existing.values.some((v) => v.key === value)) {
8306
- existing.values.push({ key: value, label: valueLabel });
8307
- }
8308
- } else {
8309
- byKey.set(key, { key, label: facetLabel, values: [{ key: value, label: valueLabel }] });
8310
- }
8383
+ const vocab = facetBrowse.vocabulary ?? [];
8384
+ if (!vocab.length) return [];
8385
+ const out = [];
8386
+ for (const facet of vocab) {
8387
+ const key = facet.key;
8388
+ if (!key) continue;
8389
+ const values = (facet.values ?? []).filter((v) => !!v.key).map((v) => ({ key: v.key, label: v.name ?? v.key }));
8390
+ if (!values.length) continue;
8391
+ out.push({ key, label: facet.name ?? key, values });
8311
8392
  }
8312
- return Array.from(byKey.values());
8313
- }, [facetBrowse.items]);
8393
+ return out;
8394
+ }, [facetBrowse.vocabulary]);
8314
8395
  const collectionGlobalAllRow = useMemo(
8315
8396
  () => ({
8316
8397
  id: null,