@proveanything/smartlinks-utils-ui 0.3.12 → 0.3.13

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.
@@ -70,17 +70,26 @@ var getRecordByRef = async (ctx, ref) => {
70
70
  };
71
71
  var upsertRecord = async (ctx, write) => {
72
72
  const ref = write.ref ?? deriveRefFromScope(write.scope);
73
- const res = await ctx.SL.app.records.upsert(ctx.collectionId, ctx.appId, {
73
+ const payload = {
74
74
  ref,
75
75
  ...ctx.recordType ? { recordType: ctx.recordType } : {},
76
- scope: write.scope,
77
76
  data: write.data,
78
77
  status: write.status,
79
78
  startsAt: write.startsAt,
80
79
  expiresAt: write.expiresAt,
81
80
  customId: write.customId,
82
81
  sourceSystem: write.sourceSystem
83
- });
82
+ };
83
+ if (write.facetRule && write.facetRule.all && write.facetRule.all.length > 0) {
84
+ payload.facetRule = write.facetRule;
85
+ } else {
86
+ payload.scope = write.scope;
87
+ }
88
+ const res = await ctx.SL.app.records.upsert(
89
+ ctx.collectionId,
90
+ ctx.appId,
91
+ payload
92
+ );
84
93
  return { record: res, isCreate: !!res?.created };
85
94
  };
86
95
  var upsertRecordForScope = async (ctx, scope, data, extra = {}) => upsertRecord(ctx, {
@@ -168,5 +177,5 @@ var deriveRefFromScope = (scope) => {
168
177
  };
169
178
 
170
179
  export { bulkDelete, bulkUpsert, deleteRecord, getRecordByRef, listRecords, matchRecords, parsedRefToScope, parsedRefToTarget, restoreRecord, scopesEqual, upsertRecord, upsertRecordForScope };
171
- //# sourceMappingURL=chunk-TY2UIZ24.js.map
172
- //# sourceMappingURL=chunk-TY2UIZ24.js.map
180
+ //# sourceMappingURL=chunk-76Y4UTYQ.js.map
181
+ //# sourceMappingURL=chunk-76Y4UTYQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/RecordsAdmin/data/scopeBridge.ts","../src/components/RecordsAdmin/data/records.ts"],"names":[],"mappings":"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","file":"chunk-76Y4UTYQ.js","sourcesContent":["// =============================================================================\r\n// scopeBridge — Convert between our UI `ParsedRef` and the SDK `RecordScope`.\r\n//\r\n// Our `ParsedRef` is a UI-friendly flat object that round-trips with the\r\n// `kind:id/kind:id` URL refs. The SDK's `RecordScope` is the structured\r\n// canonical form used for server-side specificity & `match()`. Use these\r\n// helpers at every boundary so we never write a record without `scope`.\r\n// =============================================================================\r\nimport type { RecordScope, RecordTarget } from './sdkTypes';\r\nimport type { ParsedRef } from '../types';\r\n\r\nexport const parsedRefToScope = (ref: ParsedRef): RecordScope => {\r\n const scope: RecordScope = {};\r\n if (ref.productId) scope.productId = ref.productId;\r\n if (ref.variantId) scope.variantId = ref.variantId;\r\n if (ref.batchId) scope.batchId = ref.batchId;\r\n if (ref.proofId) scope.proofId = ref.proofId;\r\n if (ref.facetId) {\r\n scope.facets = [{\r\n key: ref.facetId,\r\n valueKeys: ref.facetValue ? [ref.facetValue] : [],\r\n }];\r\n }\r\n return scope;\r\n};\r\n\r\nexport const parsedRefToTarget = (ref: ParsedRef): RecordTarget => {\r\n const target: RecordTarget = {};\r\n if (ref.productId) target.productId = ref.productId;\r\n if (ref.variantId) target.variantId = ref.variantId;\r\n if (ref.batchId) target.batchId = ref.batchId;\r\n if (ref.proofId) target.proofId = ref.proofId;\r\n if (ref.facetId && ref.facetValue) {\r\n target.facets = { [ref.facetId]: [ref.facetValue] };\r\n }\r\n return target;\r\n};\r\n\r\n/**\r\n * Compare a winning record's scope against the editing scope.\r\n * Returns true when they refer to the same node — used to decide\r\n * \"self\" vs \"inherited\" when consuming `match()` results.\r\n */\r\nexport const scopesEqual = (a: RecordScope, b: RecordScope): boolean => {\r\n if ((a.productId ?? null) !== (b.productId ?? null)) return false;\r\n if ((a.variantId ?? null) !== (b.variantId ?? null)) return false;\r\n if ((a.batchId ?? null) !== (b.batchId ?? null)) return false;\r\n if ((a.proofId ?? null) !== (b.proofId ?? null)) return false;\r\n const af = a.facets ?? [];\r\n const bf = b.facets ?? [];\r\n if (af.length !== bf.length) return false;\r\n const norm = (xs: typeof af) => xs\r\n .map((f) => `${f.key}:${[...f.valueKeys].sort().join(',')}`)\r\n .sort()\r\n .join('|');\r\n return norm(af) === norm(bf);\r\n};","// =============================================================================\r\n// Thin wrappers over SL.app.records.* — always admin: true here.\r\n//\r\n// As of `@proveanything/smartlinks` 1.9.0 the records surface ships a proper\r\n// `upsert()`, server-side `match()`, and atomic `bulkUpsert` / `bulkDelete`.\r\n// We delegate to those rather than rolling our own list+update sequences,\r\n// which were racy and ignored facet AND-of-OR semantics.\r\n// =============================================================================\r\nimport type {\r\n AppRecord, RecordScope, RecordTarget, BulkUpsertItem, BulkDeleteResult,\r\n MatchResult, FacetRule,\r\n} from './sdkTypes';\r\nimport type { ParsedRef, SmartLinksSDK } from '../types';\r\nimport { parsedRefToScope } from './scopeBridge';\r\n\r\nexport interface RecordsCtx {\r\n SL: SmartLinksSDK;\r\n collectionId: string;\r\n appId: string;\r\n /** Optional — when omitted, records are stored against the app id alone\r\n * (no type qualifier). Set this only when an app uses multiple record types\r\n * and needs to query/scope by type. */\r\n recordType?: string;\r\n}\r\n\r\n/** Shape of a single record write — uses the SDK's structured `scope`. */\r\nexport interface RecordWrite<T = unknown> {\r\n /** Logical key. The server canonicalises this from `scope` if omitted. */\r\n ref?: string;\r\n /** Structured scope; required for specificity / `match()` to work. */\r\n scope: RecordScope;\r\n data: T;\r\n visibility?: 'public' | 'owner' | 'admin';\r\n productId?: string;\r\n status?: string;\r\n startsAt?: string | null;\r\n expiresAt?: string | null;\r\n customId?: string;\r\n sourceSystem?: string;\r\n /**\r\n * Optional facet rule. When set, the record is targeted via this rule and\r\n * `scope` should be empty (the SDK enforces mutual exclusion). Used by\r\n * the rule editor / Targeting section.\r\n */\r\n facetRule?: FacetRule | null;\r\n}\r\n\r\nexport const listRecords = async (\r\n ctx: RecordsCtx,\r\n params: { ref?: string; refPrefix?: string; q?: string; limit?: number; offset?: number; sort?: string } = {},\r\n): Promise<{ data: AppRecord[]; total: number; hasMore: boolean }> => {\r\n const { limit = 100, offset, ref, refPrefix, q, sort } = params;\r\n const res = await ctx.SL.app.records.list(\r\n ctx.collectionId,\r\n ctx.appId,\r\n {\r\n ...(ctx.recordType ? { recordType: ctx.recordType } : {}),\r\n limit, offset, ref, refPrefix, q, sort,\r\n },\r\n true,\r\n );\r\n return {\r\n data: res?.data ?? [],\r\n total: res?.pagination?.total ?? (res?.data?.length ?? 0),\r\n hasMore: res?.pagination?.hasMore ?? false,\r\n };\r\n};\r\n\r\n/** Look up a single record by its canonical ref. */\r\nexport const getRecordByRef = async (\r\n ctx: RecordsCtx,\r\n ref: string,\r\n): Promise<AppRecord | null> => {\r\n const res = await ctx.SL.app.records.list(\r\n ctx.collectionId,\r\n ctx.appId,\r\n { ...(ctx.recordType ? { recordType: ctx.recordType } : {}), ref, limit: 1 },\r\n true,\r\n );\r\n return res?.data?.[0] ?? null;\r\n};\r\n\r\n/**\r\n * Atomic upsert via the SDK. Always sends `scope` so the server can compute\r\n * `specificity` and derive `ref` deterministically.\r\n */\r\nexport const upsertRecord = async <T,>(\r\n ctx: RecordsCtx,\r\n write: RecordWrite<T>,\r\n): Promise<{ record: AppRecord; isCreate: boolean }> => {\r\n const ref = write.ref ?? deriveRefFromScope(write.scope);\r\n const payload: Record<string, unknown> = {\r\n ref,\r\n ...(ctx.recordType ? { recordType: ctx.recordType } : {}),\r\n data: write.data as Record<string, unknown>,\r\n status: write.status,\r\n startsAt: write.startsAt,\r\n expiresAt: write.expiresAt,\r\n customId: write.customId,\r\n sourceSystem: write.sourceSystem,\r\n };\r\n // SDK 1.10 enforces mutual exclusion between `scope` and `facetRule`. Send\r\n // exactly one. When a rule is supplied the server resolves matches against\r\n // it; otherwise the structured scope drives specificity.\r\n if (write.facetRule && write.facetRule.all && write.facetRule.all.length > 0) {\r\n payload.facetRule = write.facetRule;\r\n } else {\r\n payload.scope = write.scope;\r\n }\r\n const res = await ctx.SL.app.records.upsert(\r\n ctx.collectionId, ctx.appId, payload as Parameters<typeof ctx.SL.app.records.upsert>[2],\r\n );\r\n return { record: res, isCreate: !!res?.created };\r\n};\r\n\r\n/**\r\n * Convenience wrapper for callers that only have a `ParsedRef` and a payload —\r\n * matches the previous (`ref`, `data`) call shape used by the editor hook.\r\n */\r\nexport const upsertRecordForScope = async <T,>(\r\n ctx: RecordsCtx,\r\n scope: ParsedRef,\r\n data: T,\r\n extra: { visibility?: 'public' | 'owner' | 'admin' } = {},\r\n) => upsertRecord<T>(ctx, {\r\n ref: scope.raw || undefined,\r\n scope: parsedRefToScope(scope),\r\n data,\r\n visibility: extra.visibility,\r\n});\r\n\r\nexport const deleteRecord = async (\r\n ctx: RecordsCtx,\r\n ref: string,\r\n): Promise<boolean> => {\r\n const existing = await getRecordByRef(ctx, ref).catch(() => null);\r\n if (!existing) return false;\r\n await ctx.SL.app.records.remove(ctx.collectionId, ctx.appId, existing.id, true);\r\n return true;\r\n};\r\n\r\n/** Restore a soft-deleted record by ID (admin only). */\r\nexport const restoreRecord = async (\r\n ctx: RecordsCtx,\r\n recordId: string,\r\n): Promise<AppRecord> => ctx.SL.app.records.restore(ctx.collectionId, ctx.appId, recordId);\r\n\r\n/**\r\n * Server-side match — single round trip that returns every record whose\r\n * `scope` is satisfied by `target`, ordered by descending `specificity`.\r\n * Replaces the per-scope N+1 chain walk we used pre-1.9.\r\n */\r\nexport const matchRecords = async (\r\n ctx: RecordsCtx,\r\n target: RecordTarget,\r\n opts: {\r\n strategy?: 'all' | 'best';\r\n at?: string;\r\n includeScheduled?: boolean;\r\n includeExpired?: boolean;\r\n limit?: number;\r\n } = {},\r\n): Promise<MatchResult> => ctx.SL.app.records.match(\r\n ctx.collectionId,\r\n ctx.appId,\r\n {\r\n target,\r\n ...(ctx.recordType ? { recordType: ctx.recordType } : {}),\r\n strategy: opts.strategy ?? 'all',\r\n at: opts.at,\r\n includeScheduled: opts.includeScheduled,\r\n includeExpired: opts.includeExpired,\r\n limit: opts.limit,\r\n },\r\n true,\r\n);\r\n\r\n/**\r\n * Server-side bulk upsert. Up to 500 rows per call; any leftover is chunked.\r\n * Each row is error-isolated by the server.\r\n */\r\nexport const bulkUpsert = async <T,>(\r\n ctx: RecordsCtx,\r\n entries: Array<{ ref?: string; scope: RecordScope; data: T; status?: string }>,\r\n): Promise<{ saved: number; failed: number }> => {\r\n const CHUNK = 500;\r\n let saved = 0;\r\n let failed = 0;\r\n for (let i = 0; i < entries.length; i += CHUNK) {\r\n const slice = entries.slice(i, i + CHUNK);\r\n const items: BulkUpsertItem[] = slice.map((e) => ({\r\n ref: e.ref ?? deriveRefFromScope(e.scope),\r\n ...(ctx.recordType ? { recordType: ctx.recordType } : {}),\r\n scope: e.scope,\r\n data: e.data as Record<string, unknown>,\r\n status: e.status,\r\n }));\r\n const res = await ctx.SL.app.records.bulkUpsert(ctx.collectionId, ctx.appId, items)\r\n .catch((): { saved: number; failed: number } => ({ saved: 0, failed: items.length }));\r\n saved += res.saved ?? 0;\r\n failed += res.failed ?? 0;\r\n }\r\n return { saved, failed };\r\n};\r\n\r\n/**\r\n * Server-side bulk delete. Accepts an explicit ref list (max 1000 per call,\r\n * chunked here) or a scope anchor (single call).\r\n */\r\nexport const bulkDelete = async (\r\n ctx: RecordsCtx,\r\n input: { refs: string[] } | { scope: Omit<RecordScope, 'facets'> },\r\n): Promise<{ removed: number }> => {\r\n if ('scope' in input) {\r\n const res: BulkDeleteResult = await ctx.SL.app.records.bulkDelete(\r\n ctx.collectionId, ctx.appId,\r\n { scope: input.scope, ...(ctx.recordType ? { recordType: ctx.recordType } : {}) },\r\n );\r\n return { removed: res.deleted ?? 0 };\r\n }\r\n\r\n const CHUNK = 1000;\r\n let removed = 0;\r\n for (let i = 0; i < input.refs.length; i += CHUNK) {\r\n const slice = input.refs.slice(i, i + CHUNK);\r\n const res: BulkDeleteResult = await ctx.SL.app.records.bulkDelete(\r\n ctx.collectionId, ctx.appId,\r\n { refs: slice, ...(ctx.recordType ? { recordType: ctx.recordType } : {}) },\r\n );\r\n removed += res.deleted ?? 0;\r\n }\r\n return { removed };\r\n};\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internal — derive a stable ref string from a structured scope. The server\r\n// re-derives this canonically on write; we send our best guess so callers\r\n// (and our own list/lookup helpers) can find the record by ref afterwards.\r\n// ---------------------------------------------------------------------------\r\nconst deriveRefFromScope = (scope: RecordScope): string => {\r\n const parts: string[] = [];\r\n if (scope.facets && scope.facets.length > 0) {\r\n // Sort for determinism.\r\n const sorted = [...scope.facets].sort((a, b) => a.key.localeCompare(b.key));\r\n for (const f of sorted) {\r\n const vals = [...f.valueKeys].sort().join(',');\r\n parts.push(vals ? `facet:${f.key}=${vals}` : `facet:${f.key}`);\r\n }\r\n }\r\n if (scope.productId) parts.push(`product:${scope.productId}`);\r\n if (scope.variantId) parts.push(`variant:${scope.variantId}`);\r\n if (scope.batchId) parts.push(`batch:${scope.batchId}`);\r\n if (scope.proofId) parts.push(`proof:${scope.proofId}`);\r\n return parts.join('/');\r\n};"]}
@@ -243,5 +243,5 @@ var FacetRuleEditor = ({
243
243
  };
244
244
 
245
245
  export { FacetRuleEditor };
246
- //# sourceMappingURL=chunk-MZ6JSCXO.js.map
247
- //# sourceMappingURL=chunk-MZ6JSCXO.js.map
246
+ //# sourceMappingURL=chunk-JMCV6FOW.js.map
247
+ //# sourceMappingURL=chunk-JMCV6FOW.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/FacetRuleEditor/FacetRuleEditor.tsx"],"names":[],"mappings":"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","file":"chunk-MZ6JSCXO.js","sourcesContent":["// =============================================================================\r\n// FacetRuleEditor — Friendly AND-of-OR rule builder for facet-targeted records.\r\n//\r\n// Model: `{ all: [{ facetKey, anyOf: [valueKey, ...] }, ...] }`.\r\n// - Each clause picks ONE facet key and one or more values (OR within).\r\n// - Multiple clauses are ANDed.\r\n// - Facet keys cannot repeat across clauses (server enforces; we hide already-\r\n// chosen keys from the picker).\r\n// - No NOTs, no nesting, no free-text — by design. This is the public-side\r\n// rule shape; richer logic belongs in `ConditionsEditor` (client-side only).\r\n//\r\n// UX notes:\r\n// - Empty state: a single inviting \"Pick a facet to start\" card.\r\n// - Each clause renders as a labelled row of value chips that toggle on click.\r\n// - Bottom bar shows live \"matches N products\" feedback when `preview` is set.\r\n// =============================================================================\r\nimport React, { useMemo } from 'react';\r\nimport { Plus, X, Trash2, Loader2, Check, AlertCircle } from 'lucide-react';\r\nimport { cn } from '../../utils/cn';\r\nimport { useFacets } from '../ConditionsEditor/useFacets';\r\nimport type { FacetRuleEditorProps, FacetRule, FacetRuleClause, FacetOption } from './types';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Helpers\r\n// ---------------------------------------------------------------------------\r\nconst cloneRule = (rule: FacetRule | null): FacetRule => ({\r\n all: (rule?.all ?? []).map((c: FacetRuleClause) => ({ facetKey: c.facetKey, anyOf: [...c.anyOf] })),\r\n});\r\n\r\nconst updateClause = (\r\n rule: FacetRule | null,\r\n index: number,\r\n next: FacetRuleClause | null,\r\n): FacetRule | null => {\r\n const draft = cloneRule(rule);\r\n if (next === null) draft.all.splice(index, 1);\r\n else draft.all[index] = next;\r\n return draft.all.length === 0 ? null : draft;\r\n};\r\n\r\nconst findFacet = (facets: FacetOption[], key: string) =>\r\n facets.find((f) => f.key === key);\r\n\r\n// ---------------------------------------------------------------------------\r\n// Single-clause editor: facet header + chip-grid of values.\r\n// ---------------------------------------------------------------------------\r\ninterface ClauseEditorProps {\r\n clause: FacetRuleClause;\r\n facet: FacetOption | undefined;\r\n onChange: (next: FacetRuleClause | null) => void;\r\n readOnly?: boolean;\r\n}\r\n\r\nconst ClauseEditor: React.FC<ClauseEditorProps> = ({ clause, facet, onChange, readOnly }) => {\r\n const selected = useMemo(() => new Set(clause.anyOf), [clause.anyOf]);\r\n const toggle = (valueKey: string) => {\r\n if (readOnly) return;\r\n const next = new Set(selected);\r\n if (next.has(valueKey)) next.delete(valueKey);\r\n else next.add(valueKey);\r\n onChange({ facetKey: clause.facetKey, anyOf: Array.from(next) });\r\n };\r\n\r\n const facetName = facet?.name ?? clause.facetKey;\r\n const values = facet?.values ?? [];\r\n\r\n return (\r\n <div className=\"rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 overflow-hidden\">\r\n {/* Header */}\r\n <div className=\"flex items-center gap-2 px-4 py-2.5 bg-gray-50 dark:bg-gray-800/50 border-b border-gray-200 dark:border-gray-700\">\r\n <div className=\"flex flex-col flex-1 min-w-0\">\r\n <span className=\"text-[11px] font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400\">\r\n Where\r\n </span>\r\n <span className=\"text-sm font-semibold text-gray-900 dark:text-gray-100 truncate\">\r\n {facetName}\r\n </span>\r\n </div>\r\n <span className=\"text-xs text-gray-500 dark:text-gray-400 px-2 py-0.5 rounded-full bg-gray-100 dark:bg-gray-700\">\r\n {clause.anyOf.length} selected\r\n </span>\r\n {!readOnly && (\r\n <button\r\n type=\"button\"\r\n onClick={() => onChange(null)}\r\n className=\"p-1.5 rounded-md text-gray-400 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors\"\r\n aria-label={`Remove ${facetName} clause`}\r\n >\r\n <Trash2 className=\"w-3.5 h-3.5\" />\r\n </button>\r\n )}\r\n </div>\r\n\r\n {/* Body */}\r\n <div className=\"p-3\">\r\n {!facet ? (\r\n <div className=\"flex items-center gap-2 text-xs text-amber-600 dark:text-amber-400 px-2 py-1.5\">\r\n <AlertCircle className=\"w-3.5 h-3.5\" />\r\n <span>Facet \"{clause.facetKey}\" no longer exists in this collection.</span>\r\n </div>\r\n ) : values.length === 0 ? (\r\n <div className=\"text-xs text-gray-500 dark:text-gray-400 px-2 py-1.5\">\r\n No values defined for this facet yet.\r\n </div>\r\n ) : (\r\n <div className=\"flex flex-wrap gap-1.5\">\r\n {values.map((v) => {\r\n const isSel = selected.has(v.key);\r\n return (\r\n <button\r\n key={v.key}\r\n type=\"button\"\r\n disabled={readOnly}\r\n onClick={() => toggle(v.key)}\r\n className={cn(\r\n 'inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium transition-all',\r\n 'border',\r\n isSel\r\n ? 'bg-blue-600 text-white border-blue-600 hover:bg-blue-700'\r\n : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-200 dark:border-gray-700 hover:border-blue-400 hover:text-blue-600 dark:hover:text-blue-400',\r\n readOnly && 'cursor-not-allowed opacity-60'\r\n )}\r\n aria-pressed={isSel}\r\n >\r\n {v.color && (\r\n <span\r\n className=\"inline-block w-2 h-2 rounded-full\"\r\n style={{ backgroundColor: v.color }}\r\n />\r\n )}\r\n {v.name}\r\n {isSel && <Check className=\"w-3 h-3\" />}\r\n </button>\r\n );\r\n })}\r\n </div>\r\n )}\r\n {clause.anyOf.length > 1 && (\r\n <p className=\"mt-2 text-[11px] text-gray-500 dark:text-gray-400 px-1\">\r\n Matches when any of the selected values is present.\r\n </p>\r\n )}\r\n </div>\r\n </div>\r\n );\r\n};\r\n\r\n// ---------------------------------------------------------------------------\r\n// \"Add facet\" picker — lists facets not already used by another clause.\r\n// ---------------------------------------------------------------------------\r\ninterface AddFacetPickerProps {\r\n available: FacetOption[];\r\n onPick: (facetKey: string) => void;\r\n isFirst: boolean;\r\n}\r\n\r\nconst AddFacetPicker: React.FC<AddFacetPickerProps> = ({ available, onPick, isFirst }) => {\r\n const [open, setOpen] = React.useState(false);\r\n\r\n if (available.length === 0 && !isFirst) {\r\n return (\r\n <p className=\"text-xs text-gray-500 dark:text-gray-400 px-2 py-1\">\r\n All available facets are in use.\r\n </p>\r\n );\r\n }\r\n\r\n if (isFirst) {\r\n // Empty state — friendly inviting card\r\n return (\r\n <div className=\"rounded-xl border-2 border-dashed border-gray-300 dark:border-gray-700 p-6 text-center\">\r\n <p className=\"text-sm font-medium text-gray-700 dark:text-gray-200 mb-1\">\r\n Pick a facet to target\r\n </p>\r\n <p className=\"text-xs text-gray-500 dark:text-gray-400 mb-4\">\r\n Records matching this rule will be applied to every product whose facets satisfy it.\r\n </p>\r\n <div className=\"flex flex-wrap gap-1.5 justify-center\">\r\n {available.map((f) => (\r\n <button\r\n key={f.key}\r\n type=\"button\"\r\n onClick={() => onPick(f.key)}\r\n className=\"inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-xs font-medium bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 hover:bg-blue-100 dark:hover:bg-blue-900/50 transition-colors\"\r\n >\r\n <Plus className=\"w-3 h-3\" />\r\n {f.name}\r\n </button>\r\n ))}\r\n {available.length === 0 && (\r\n <span className=\"text-xs text-gray-500 dark:text-gray-400\">\r\n No facets defined for this collection yet.\r\n </span>\r\n )}\r\n </div>\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div className=\"relative\">\r\n <button\r\n type=\"button\"\r\n onClick={() => setOpen((o) => !o)}\r\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors\"\r\n >\r\n <Plus className=\"w-3.5 h-3.5\" />\r\n AND another facet\r\n </button>\r\n {open && (\r\n <>\r\n <div className=\"fixed inset-0 z-40\" onClick={() => setOpen(false)} />\r\n <div className=\"absolute z-50 mt-1 left-0 min-w-[200px] rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 shadow-lg overflow-hidden\">\r\n {available.map((f) => (\r\n <button\r\n key={f.key}\r\n type=\"button\"\r\n onClick={() => { onPick(f.key); setOpen(false); }}\r\n className=\"w-full text-left px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors\"\r\n >\r\n {f.name}\r\n </button>\r\n ))}\r\n </div>\r\n </>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\n// ---------------------------------------------------------------------------\r\n// Live preview chip — wired through the optional `preview` prop.\r\n// ---------------------------------------------------------------------------\r\nconst PreviewChip: React.FC<{ preview: NonNullable<FacetRuleEditorProps['preview']> }> = ({ preview }) => {\r\n const { totalMatches, isLoading, isStale, error } = preview;\r\n\r\n if (error) {\r\n return (\r\n <div className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300\">\r\n <AlertCircle className=\"w-3 h-3\" />\r\n Preview failed\r\n </div>\r\n );\r\n }\r\n\r\n if (isLoading || isStale || totalMatches === null) {\r\n return (\r\n <div className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400\">\r\n <Loader2 className=\"w-3 h-3 animate-spin\" />\r\n Counting…\r\n </div>\r\n );\r\n }\r\n\r\n const tone = totalMatches === 0\r\n ? 'bg-amber-50 dark:bg-amber-900/30 text-amber-700 dark:text-amber-300'\r\n : 'bg-emerald-50 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-300';\r\n\r\n return (\r\n <div className={cn('inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium', tone)}>\r\n <Check className=\"w-3 h-3\" />\r\n {totalMatches === 0\r\n ? 'Matches no products yet'\r\n : `Matches ${totalMatches} product${totalMatches === 1 ? '' : 's'}`}\r\n </div>\r\n );\r\n};\r\n\r\n// ---------------------------------------------------------------------------\r\n// Top-level component\r\n// ---------------------------------------------------------------------------\r\nexport const FacetRuleEditor: React.FC<FacetRuleEditorProps> = ({\r\n value, onChange,\r\n facets: providedFacets, collectionId, getFacets,\r\n preview, readOnly,\r\n onClear,\r\n title,\r\n description,\r\n className,\r\n}) => {\r\n const facets = useFacets(collectionId, providedFacets, getFacets);\r\n\r\n const usedKeys = useMemo(\r\n () => new Set((value?.all ?? []).map((c: FacetRuleClause) => c.facetKey)),\r\n [value],\r\n );\r\n const availableFacets = useMemo(\r\n () => facets.filter((f) => !usedKeys.has(f.key)),\r\n [facets, usedKeys],\r\n );\r\n\r\n const addClause = (facetKey: string) => {\r\n const next: FacetRule = cloneRule(value);\r\n next.all.push({ facetKey, anyOf: [] });\r\n onChange(next);\r\n };\r\n\r\n const updateAt = (i: number, next: FacetRuleClause | null) => {\r\n onChange(updateClause(value, i, next));\r\n };\r\n\r\n const hasClauses = (value?.all?.length ?? 0) > 0;\r\n\r\n return (\r\n <div className={cn('smartlinks-ui-facet-rule-editor space-y-3', className)}>\r\n {(title || description || (hasClauses && onClear) || preview) && (\r\n <div className=\"flex items-start gap-2\">\r\n <div className=\"flex-1 min-w-0\">\r\n {title && (\r\n <h3 className=\"text-sm font-semibold text-gray-900 dark:text-gray-100\">{title}</h3>\r\n )}\r\n {description && (\r\n <p className=\"mt-0.5 text-xs text-gray-500 dark:text-gray-400\">{description}</p>\r\n )}\r\n </div>\r\n {preview && hasClauses && <PreviewChip preview={preview} />}\r\n {onClear && hasClauses && !readOnly && (\r\n <button\r\n type=\"button\"\r\n onClick={onClear}\r\n className=\"inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs text-gray-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors\"\r\n >\r\n <X className=\"w-3 h-3\" />\r\n Remove rule\r\n </button>\r\n )}\r\n </div>\r\n )}\r\n\r\n {!hasClauses ? (\r\n <AddFacetPicker available={facets} onPick={addClause} isFirst />\r\n ) : (\r\n <>\r\n {value!.all.map((clause: FacetRuleClause, i: number) => (\r\n <React.Fragment key={`${clause.facetKey}-${i}`}>\r\n {i > 0 && (\r\n <div className=\"flex items-center justify-center\">\r\n <span className=\"px-2 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-wider bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400\">\r\n AND\r\n </span>\r\n </div>\r\n )}\r\n <ClauseEditor\r\n clause={clause}\r\n facet={findFacet(facets, clause.facetKey)}\r\n onChange={(next) => updateAt(i, next)}\r\n readOnly={readOnly}\r\n />\r\n </React.Fragment>\r\n ))}\r\n {!readOnly && (\r\n <div className=\"pt-1\">\r\n <AddFacetPicker\r\n available={availableFacets}\r\n onPick={addClause}\r\n isFirst={false}\r\n />\r\n </div>\r\n )}\r\n </>\r\n )}\r\n </div>\r\n );\r\n};"]}
1
+ {"version":3,"sources":["../src/components/FacetRuleEditor/FacetRuleEditor.tsx"],"names":[],"mappings":"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","file":"chunk-JMCV6FOW.js","sourcesContent":["// =============================================================================\r\n// FacetRuleEditor — Friendly AND-of-OR rule builder for facet-targeted records.\r\n//\r\n// Model: `{ all: [{ facetKey, anyOf: [valueKey, ...] }, ...] }`.\r\n// - Each clause picks ONE facet key and one or more values (OR within).\r\n// - Multiple clauses are ANDed.\r\n// - Facet keys cannot repeat across clauses (server enforces; we hide already-\r\n// chosen keys from the picker).\r\n// - No NOTs, no nesting, no free-text — by design. This is the public-side\r\n// rule shape; richer logic belongs in `ConditionsEditor` (client-side only).\r\n//\r\n// UX notes:\r\n// - Empty state: a single inviting \"Pick a facet to start\" card.\r\n// - Each clause renders as a labelled row of value chips that toggle on click.\r\n// - Bottom bar shows live \"matches N products\" feedback when `preview` is set.\r\n// =============================================================================\r\nimport React, { useMemo } from 'react';\r\nimport { Plus, X, Trash2, Loader2, Check, AlertCircle } from 'lucide-react';\r\nimport { cn } from '../../utils/cn';\r\nimport { useFacets } from '../ConditionsEditor/useFacets';\r\nimport type { FacetRuleEditorProps, FacetRule, FacetRuleClause, FacetOption } from './types';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Helpers\r\n// ---------------------------------------------------------------------------\r\nconst cloneRule = (rule: FacetRule | null): FacetRule => ({\r\n all: (rule?.all ?? []).map((c: FacetRuleClause) => ({ facetKey: c.facetKey, anyOf: [...c.anyOf] })),\r\n});\r\n\r\nconst updateClause = (\r\n rule: FacetRule | null,\r\n index: number,\r\n next: FacetRuleClause | null,\r\n): FacetRule | null => {\r\n const draft = cloneRule(rule);\r\n if (next === null) draft.all.splice(index, 1);\r\n else draft.all[index] = next;\r\n return draft.all.length === 0 ? null : draft;\r\n};\r\n\r\nconst findFacet = (facets: FacetOption[], key: string) =>\r\n facets.find((f) => f.key === key);\r\n\r\n// ---------------------------------------------------------------------------\r\n// Single-clause editor: facet header + chip-grid of values.\r\n// ---------------------------------------------------------------------------\r\ninterface ClauseEditorProps {\r\n clause: FacetRuleClause;\r\n facet: FacetOption | undefined;\r\n onChange: (next: FacetRuleClause | null) => void;\r\n readOnly?: boolean;\r\n}\r\n\r\nconst ClauseEditor: React.FC<ClauseEditorProps> = ({ clause, facet, onChange, readOnly }) => {\r\n const selected = useMemo(() => new Set(clause.anyOf), [clause.anyOf]);\r\n const toggle = (valueKey: string) => {\r\n if (readOnly) return;\r\n const next = new Set(selected);\r\n if (next.has(valueKey)) next.delete(valueKey);\r\n else next.add(valueKey);\r\n onChange({ facetKey: clause.facetKey, anyOf: Array.from(next) });\r\n };\r\n\r\n const facetName = facet?.name ?? clause.facetKey;\r\n const values = facet?.values ?? [];\r\n\r\n return (\r\n <div className=\"rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 overflow-hidden\">\r\n {/* Header */}\r\n <div className=\"flex items-center gap-2 px-4 py-2.5 bg-gray-50 dark:bg-gray-800/50 border-b border-gray-200 dark:border-gray-700\">\r\n <div className=\"flex flex-col flex-1 min-w-0\">\r\n <span className=\"text-[11px] font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400\">\r\n Where\r\n </span>\r\n <span className=\"text-sm font-semibold text-gray-900 dark:text-gray-100 truncate\">\r\n {facetName}\r\n </span>\r\n </div>\r\n <span className=\"text-xs text-gray-500 dark:text-gray-400 px-2 py-0.5 rounded-full bg-gray-100 dark:bg-gray-700\">\r\n {clause.anyOf.length} selected\r\n </span>\r\n {!readOnly && (\r\n <button\r\n type=\"button\"\r\n onClick={() => onChange(null)}\r\n className=\"p-1.5 rounded-md text-gray-400 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors\"\r\n aria-label={`Remove ${facetName} clause`}\r\n >\r\n <Trash2 className=\"w-3.5 h-3.5\" />\r\n </button>\r\n )}\r\n </div>\r\n\r\n {/* Body */}\r\n <div className=\"p-3\">\r\n {!facet ? (\r\n <div className=\"flex items-center gap-2 text-xs text-amber-600 dark:text-amber-400 px-2 py-1.5\">\r\n <AlertCircle className=\"w-3.5 h-3.5\" />\r\n <span>Facet \"{clause.facetKey}\" no longer exists in this collection.</span>\r\n </div>\r\n ) : values.length === 0 ? (\r\n <div className=\"text-xs text-gray-500 dark:text-gray-400 px-2 py-1.5\">\r\n No values defined for this facet yet.\r\n </div>\r\n ) : (\r\n <div className=\"flex flex-wrap gap-1.5\">\r\n {values.map((v) => {\r\n const isSel = selected.has(v.key);\r\n return (\r\n <button\r\n key={v.key}\r\n type=\"button\"\r\n disabled={readOnly}\r\n onClick={() => toggle(v.key)}\r\n className={cn(\r\n 'inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium transition-all',\r\n 'border',\r\n isSel\r\n ? 'bg-blue-600 text-white border-blue-600 hover:bg-blue-700'\r\n : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-200 dark:border-gray-700 hover:border-blue-400 hover:text-blue-600 dark:hover:text-blue-400',\r\n readOnly && 'cursor-not-allowed opacity-60'\r\n )}\r\n aria-pressed={isSel}\r\n >\r\n {v.color && (\r\n <span\r\n className=\"inline-block w-2 h-2 rounded-full\"\r\n style={{ backgroundColor: v.color }}\r\n />\r\n )}\r\n {v.name}\r\n {isSel && <Check className=\"w-3 h-3\" />}\r\n </button>\r\n );\r\n })}\r\n </div>\r\n )}\r\n {clause.anyOf.length > 1 && (\r\n <p className=\"mt-2 text-[11px] text-gray-500 dark:text-gray-400 px-1\">\r\n Matches when any of the selected values is present.\r\n </p>\r\n )}\r\n </div>\r\n </div>\r\n );\r\n};\r\n\r\n// ---------------------------------------------------------------------------\r\n// \"Add facet\" picker — lists facets not already used by another clause.\r\n// ---------------------------------------------------------------------------\r\ninterface AddFacetPickerProps {\r\n available: FacetOption[];\r\n onPick: (facetKey: string) => void;\r\n isFirst: boolean;\r\n}\r\n\r\nconst AddFacetPicker: React.FC<AddFacetPickerProps> = ({ available, onPick, isFirst }) => {\r\n const [open, setOpen] = React.useState(false);\r\n\r\n if (available.length === 0 && !isFirst) {\r\n return (\r\n <p className=\"text-xs text-gray-500 dark:text-gray-400 px-2 py-1\">\r\n All available facets are in use.\r\n </p>\r\n );\r\n }\r\n\r\n if (isFirst) {\r\n // Empty state — friendly inviting card\r\n return (\r\n <div className=\"rounded-xl border-2 border-dashed border-gray-300 dark:border-gray-700 p-6 text-center\">\r\n <p className=\"text-sm font-medium text-gray-700 dark:text-gray-200 mb-1\">\r\n Pick a facet to target\r\n </p>\r\n <p className=\"text-xs text-gray-500 dark:text-gray-400 mb-4\">\r\n Records matching this rule will be applied to every product whose facets satisfy it.\r\n </p>\r\n <div className=\"flex flex-wrap gap-1.5 justify-center\">\r\n {available.map((f) => (\r\n <button\r\n key={f.key}\r\n type=\"button\"\r\n onClick={() => onPick(f.key)}\r\n className=\"inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-xs font-medium bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 hover:bg-blue-100 dark:hover:bg-blue-900/50 transition-colors\"\r\n >\r\n <Plus className=\"w-3 h-3\" />\r\n {f.name}\r\n </button>\r\n ))}\r\n {available.length === 0 && (\r\n <span className=\"text-xs text-gray-500 dark:text-gray-400\">\r\n No facets defined for this collection yet.\r\n </span>\r\n )}\r\n </div>\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div className=\"relative\">\r\n <button\r\n type=\"button\"\r\n onClick={() => setOpen((o) => !o)}\r\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors\"\r\n >\r\n <Plus className=\"w-3.5 h-3.5\" />\r\n AND another facet\r\n </button>\r\n {open && (\r\n <>\r\n <div className=\"fixed inset-0 z-40\" onClick={() => setOpen(false)} />\r\n <div className=\"absolute z-50 mt-1 left-0 min-w-[200px] rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 shadow-lg overflow-hidden\">\r\n {available.map((f) => (\r\n <button\r\n key={f.key}\r\n type=\"button\"\r\n onClick={() => { onPick(f.key); setOpen(false); }}\r\n className=\"w-full text-left px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors\"\r\n >\r\n {f.name}\r\n </button>\r\n ))}\r\n </div>\r\n </>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\n// ---------------------------------------------------------------------------\r\n// Live preview chip — wired through the optional `preview` prop.\r\n// ---------------------------------------------------------------------------\r\nconst PreviewChip: React.FC<{ preview: NonNullable<FacetRuleEditorProps['preview']> }> = ({ preview }) => {\r\n const { totalMatches, isLoading, isStale, error } = preview;\r\n\r\n if (error) {\r\n return (\r\n <div className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300\">\r\n <AlertCircle className=\"w-3 h-3\" />\r\n Preview failed\r\n </div>\r\n );\r\n }\r\n\r\n if (isLoading || isStale || totalMatches === null) {\r\n return (\r\n <div className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400\">\r\n <Loader2 className=\"w-3 h-3 animate-spin\" />\r\n Counting…\r\n </div>\r\n );\r\n }\r\n\r\n const tone = totalMatches === 0\r\n ? 'bg-amber-50 dark:bg-amber-900/30 text-amber-700 dark:text-amber-300'\r\n : 'bg-emerald-50 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-300';\r\n\r\n return (\r\n <div className={cn('inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium', tone)}>\r\n <Check className=\"w-3 h-3\" />\r\n {totalMatches === 0\r\n ? 'Matches no products yet'\r\n : `Matches ${totalMatches} product${totalMatches === 1 ? '' : 's'}`}\r\n </div>\r\n );\r\n};\r\n\r\n// ---------------------------------------------------------------------------\r\n// Top-level component\r\n// ---------------------------------------------------------------------------\r\nexport const FacetRuleEditor: React.FC<FacetRuleEditorProps> = ({\r\n value, onChange,\r\n facets: providedFacets, collectionId, getFacets,\r\n preview, readOnly,\r\n onClear,\r\n title,\r\n description,\r\n className,\r\n}) => {\r\n const facets = useFacets(collectionId, providedFacets, getFacets);\r\n\r\n const usedKeys = useMemo(\r\n () => new Set((value?.all ?? []).map((c: FacetRuleClause) => c.facetKey)),\r\n [value],\r\n );\r\n const availableFacets = useMemo(\r\n () => facets.filter((f) => !usedKeys.has(f.key)),\r\n [facets, usedKeys],\r\n );\r\n\r\n const addClause = (facetKey: string) => {\r\n const next: FacetRule = cloneRule(value);\r\n next.all.push({ facetKey, anyOf: [] });\r\n onChange(next);\r\n };\r\n\r\n const updateAt = (i: number, next: FacetRuleClause | null) => {\r\n onChange(updateClause(value, i, next));\r\n };\r\n\r\n const hasClauses = (value?.all?.length ?? 0) > 0;\r\n\r\n return (\r\n <div className={cn('smartlinks-ui-facet-rule-editor space-y-3', className)}>\r\n {(title || description || (hasClauses && onClear) || preview) && (\r\n <div className=\"flex items-start gap-2\">\r\n <div className=\"flex-1 min-w-0\">\r\n {title && (\r\n <h3 className=\"text-sm font-semibold text-gray-900 dark:text-gray-100\">{title}</h3>\r\n )}\r\n {description && (\r\n <p className=\"mt-0.5 text-xs text-gray-500 dark:text-gray-400\">{description}</p>\r\n )}\r\n </div>\r\n {preview && hasClauses && <PreviewChip preview={preview} />}\r\n {onClear && hasClauses && !readOnly && (\r\n <button\r\n type=\"button\"\r\n onClick={onClear}\r\n className=\"inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs text-gray-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors\"\r\n >\r\n <X className=\"w-3 h-3\" />\r\n Remove rule\r\n </button>\r\n )}\r\n </div>\r\n )}\r\n\r\n {!hasClauses ? (\r\n <AddFacetPicker available={facets} onPick={addClause} isFirst />\r\n ) : (\r\n <>\r\n {value!.all.map((clause: FacetRuleClause, i: number) => (\r\n <React.Fragment key={`${clause.facetKey}-${i}`}>\r\n {i > 0 && (\r\n <div className=\"flex items-center justify-center\">\r\n <span className=\"px-2 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-wider bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400\">\r\n AND\r\n </span>\r\n </div>\r\n )}\r\n <ClauseEditor\r\n clause={clause}\r\n facet={findFacet(facets, clause.facetKey)}\r\n onChange={(next) => updateAt(i, next)}\r\n readOnly={readOnly}\r\n />\r\n </React.Fragment>\r\n ))}\r\n {!readOnly && (\r\n <div className=\"pt-1\">\r\n <AddFacetPicker\r\n available={availableFacets}\r\n onPick={addClause}\r\n isFirst={false}\r\n />\r\n </div>\r\n )}\r\n </>\r\n )}\r\n </div>\r\n );\r\n};"]}
@@ -0,0 +1,3 @@
1
+
2
+ //# sourceMappingURL=chunk-S27GIYV7.js.map
3
+ //# sourceMappingURL=chunk-S27GIYV7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-S27GIYV7.js"}
@@ -1,4 +1,5 @@
1
- export { FacetRuleEditor } from '../../chunk-MZ6JSCXO.js';
1
+ import '../../chunk-S27GIYV7.js';
2
+ export { FacetRuleEditor } from '../../chunk-JMCV6FOW.js';
2
3
  import '../../chunk-4LHF5JB7.js';
3
4
  import '../../chunk-L7FQ52F5.js';
4
5
  //# sourceMappingURL=index.js.map
@@ -1,9 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode, ComponentType } from 'react';
3
- import * as _proveanything_smartlinks_dist_types_appObjects from '@proveanything/smartlinks/dist/types/appObjects';
4
3
  import { FacetRule, MatchedAt, RecordScope, AppRecord, RecordTarget, MatchResult, MatchEntry } from '@proveanything/smartlinks/dist/types/appObjects';
5
4
  import { LucideIcon } from 'lucide-react';
6
5
  import * as _tanstack_query_core from '@tanstack/query-core';
6
+ import * as _proveanything_smartlinks from '@proveanything/smartlinks';
7
7
  import { InfiniteData } from '@tanstack/react-query';
8
8
 
9
9
  /**
@@ -104,6 +104,13 @@ interface ResolvedRecord<TData = unknown> {
104
104
  matchedAt?: MatchedAt;
105
105
  /** The rule that fired. Present only when `matchedAt === 'rule'`. */
106
106
  matchedRule?: FacetRule;
107
+ /**
108
+ * The `facetRule` attached to the winning record itself, if any. Distinct
109
+ * from `matchedRule` (which is set when the resolver classified the match
110
+ * as rule-based). For rule-targeted records (`source: 'self'` on a
111
+ * `rule:{id}` ref) this is the rule the editor authors against.
112
+ */
113
+ facetRule?: FacetRule | null;
107
114
  }
108
115
 
109
116
  interface EditorContext<TData = unknown> {
@@ -124,6 +131,15 @@ interface EditorContext<TData = unknown> {
124
131
  isSaving?: boolean;
125
132
  /** Last save error, if any. Cleared when the next save attempt starts. */
126
133
  saveError?: unknown | null;
134
+ /**
135
+ * Current facet rule attached to this record, if any. Always defined for
136
+ * rule-targeted scopes (`scope.kind === 'rule'`) — starts as `{ all: [] }`
137
+ * for a freshly created rule and is updated via `onFacetRuleChange`.
138
+ * Mutually exclusive with the structured `scope` on save.
139
+ */
140
+ facetRule?: FacetRule | null;
141
+ /** Update the in-progress facet rule. Marks the editor dirty. */
142
+ onFacetRuleChange?: (next: FacetRule | null) => void;
127
143
  }
128
144
 
129
145
  interface CsvSchemaColumn<TData> {
@@ -1044,6 +1060,13 @@ interface Props$a<T> {
1044
1060
  i18n: RecordsAdminI18n;
1045
1061
  children: ReactNode;
1046
1062
  preview?: ReactNode;
1063
+ /**
1064
+ * Optional Targeting section rendered above `children`. The shell mounts
1065
+ * this for rule-targeted records (`scope.kind === 'rule'`) so admins can
1066
+ * author the facet rule alongside the record's data. Plain record types
1067
+ * leave it `undefined` and nothing renders.
1068
+ */
1069
+ targeting?: ReactNode;
1047
1070
  bulkActions?: React.ComponentProps<typeof BulkActionsMenu>;
1048
1071
  /** Extra slot rendered in the footer between the danger actions and Save. */
1049
1072
  footerExtra?: ReactNode;
@@ -1082,7 +1105,7 @@ interface Props$a<T> {
1082
1105
  /** Host-provided icons rendered before save / discard / delete labels. */
1083
1106
  actionIcons?: Partial<Record<RecordsAdminActionKey, RecordsAdminActionIcon>>;
1084
1107
  }
1085
- declare function RecordEditor<T>({ ctx, i18n, children, preview, bulkActions, footerExtra, onBeforeDelete, headerLabel, headerSubtitle, headerMeta, clipboard, actionLabels, actionIcons, }: Props$a<T>): react_jsx_runtime.JSX.Element;
1108
+ declare function RecordEditor<T>({ ctx, i18n, children, preview, targeting, bulkActions, footerExtra, onBeforeDelete, headerLabel, headerSubtitle, headerMeta, clipboard, actionLabels, actionIcons, }: Props$a<T>): react_jsx_runtime.JSX.Element;
1086
1109
 
1087
1110
  interface InheritanceCtx {
1088
1111
  parentValue?: Record<string, unknown> | null;
@@ -1269,6 +1292,12 @@ interface RecordWrite<T = unknown> {
1269
1292
  expiresAt?: string | null;
1270
1293
  customId?: string;
1271
1294
  sourceSystem?: string;
1295
+ /**
1296
+ * Optional facet rule. When set, the record is targeted via this rule and
1297
+ * `scope` should be empty (the SDK enforces mutual exclusion). Used by
1298
+ * the rule editor / Targeting section.
1299
+ */
1300
+ facetRule?: FacetRule | null;
1272
1301
  }
1273
1302
  declare const listRecords: (ctx: RecordsCtx, params?: {
1274
1303
  ref?: string;
@@ -1391,6 +1420,13 @@ interface UseRecordEditorArgs<T> {
1391
1420
  * (used by the shell when `dirtyStrategy === 'keep'`).
1392
1421
  */
1393
1422
  reseed?: 'always' | 'preserve-dirty';
1423
+ /**
1424
+ * Initial facet rule for the record. Required for rule-targeted scopes —
1425
+ * pass `null` (or `{ all: [] }`) for a freshly minted `rule:{id}` ref so
1426
+ * the Targeting section has somewhere to author into. Ignored for pinned
1427
+ * scopes unless the host explicitly opts a record into rule targeting.
1428
+ */
1429
+ initialFacetRule?: FacetRule | null;
1394
1430
  }
1395
1431
  declare function useRecordEditor<T>(args: UseRecordEditorArgs<T>): EditorContext<T>;
1396
1432
 
@@ -1406,6 +1442,13 @@ interface UseResolvedRecordArgs {
1406
1442
  facetId?: string;
1407
1443
  facetValue?: string;
1408
1444
  proofId?: string;
1445
+ /**
1446
+ * Optional raw ref. Required for rule-targeted records (`rule:{id}`) so the
1447
+ * resolver can look them up by ref instead of running an inheritance chain.
1448
+ * For pinned scopes this can be left empty — the structured scope ids are
1449
+ * sufficient.
1450
+ */
1451
+ ref?: string;
1409
1452
  supportedScopes?: ScopeKind[];
1410
1453
  enabled?: boolean;
1411
1454
  withParent?: boolean;
@@ -1417,8 +1460,9 @@ declare function useResolvedRecord<T = unknown>(args: UseResolvedRecordArgs): {
1417
1460
  source: RecordSource;
1418
1461
  sourceRef?: string;
1419
1462
  parentValue?: T | null | undefined;
1420
- matchedAt?: _proveanything_smartlinks_dist_types_appObjects.MatchedAt;
1421
- matchedRule?: _proveanything_smartlinks_dist_types_appObjects.FacetRule;
1463
+ matchedAt?: _proveanything_smartlinks.MatchedAt;
1464
+ matchedRule?: _proveanything_smartlinks.FacetRule;
1465
+ facetRule?: _proveanything_smartlinks.FacetRule | null;
1422
1466
  };
1423
1467
 
1424
1468
  interface UseCollectionItemsArgs {