@proveanything/smartlinks-utils-ui 0.7.8 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-B34MMOND.js → chunk-F2YH3NVP.js} +3 -3
- package/dist/{chunk-B34MMOND.js.map → chunk-F2YH3NVP.js.map} +1 -1
- package/dist/components/AssetPicker/index.js +1 -1
- package/dist/components/ConditionsEditor/index.js +1 -1
- package/dist/components/FontPicker/index.js +1 -1
- package/dist/components/IconPicker/index.js +1 -1
- package/dist/components/RecordsAdmin/index.d.ts +13 -1
- package/dist/components/RecordsAdmin/index.js +2398 -1262
- package/dist/components/RecordsAdmin/index.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { styleInject } from '../../chunk-
|
|
1
|
+
import { styleInject } from '../../chunk-F2YH3NVP.js';
|
|
2
2
|
import { FacetRuleEditor } from '../../chunk-JMCV6FOW.js';
|
|
3
3
|
import { useFacets } from '../../chunk-4LHF5JB7.js';
|
|
4
4
|
import { cn } from '../../chunk-L7FQ52F5.js';
|
|
5
|
-
import {
|
|
5
|
+
import { parsedRefToTarget, parsedRefToScope, matchRecords, scopesEqual, getRecordById, listRecords, upsertRecord, updateRecord, createRecord, removeRecord } from '../../chunk-KFKVGUUP.js';
|
|
6
6
|
export { bulkDelete, bulkUpsert, createRecord, getRecordById, listRecords, matchRecords, parsedRefToScope, parsedRefToTarget, removeRecord, restoreRecord, scopesEqual, upsertRecord } from '../../chunk-KFKVGUUP.js';
|
|
7
|
-
import { createContext,
|
|
7
|
+
import { createContext, useState, useEffect, useCallback, useMemo, useRef, useContext, useSyncExternalStore, createElement, useId } from 'react';
|
|
8
8
|
import { ChevronDown, Database, Lightbulb, SearchX, Inbox, LayoutGrid, Eye, MoreHorizontal, Download, Upload, Trash2, Copy, Pencil, Plus, CircleDashed, ArrowDownLeft, CheckCircle2, SlidersHorizontal, Globe, Tag, Boxes, Layers, Package, Rows3, List, ChevronRight, Eraser, ClipboardPaste, Box, X, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, Search, CornerDownLeft, Circle, AlertCircle, Undo2, Save, Loader2, XCircle, ArrowRight, BookOpen, Target, Settings2 } from 'lucide-react';
|
|
9
|
-
import { useQueryClient, useInfiniteQuery
|
|
9
|
+
import { useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
|
|
10
10
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
11
11
|
import { createPortal } from 'react-dom';
|
|
12
12
|
|
|
@@ -223,155 +223,6 @@ var resolutionChain = (target, supportedScopes) => {
|
|
|
223
223
|
}
|
|
224
224
|
return Array.from(new Set(chain));
|
|
225
225
|
};
|
|
226
|
-
var defaultClassify = (r) => {
|
|
227
|
-
if (!r.data) return "empty";
|
|
228
|
-
const keys = Object.keys(r.data);
|
|
229
|
-
if (keys.length === 0) return "empty";
|
|
230
|
-
return "configured";
|
|
231
|
-
};
|
|
232
|
-
var matchesScope = (kind, rec, _p) => {
|
|
233
|
-
const hasRule = !!rec.facetRule;
|
|
234
|
-
if (kind === "rule") return hasRule;
|
|
235
|
-
if (hasRule) return false;
|
|
236
|
-
const productId = rec.productId ?? void 0;
|
|
237
|
-
const variantId = rec.variantId ?? void 0;
|
|
238
|
-
const batchId = rec.batchId ?? void 0;
|
|
239
|
-
const proofId = rec.proofId ?? void 0;
|
|
240
|
-
if (kind === "collection") {
|
|
241
|
-
return !productId && !variantId && !batchId && !proofId;
|
|
242
|
-
}
|
|
243
|
-
if (kind === "product") return !!productId && !variantId && !batchId;
|
|
244
|
-
if (kind === "variant") return !!variantId;
|
|
245
|
-
if (kind === "batch") return !!batchId;
|
|
246
|
-
return false;
|
|
247
|
-
};
|
|
248
|
-
var matchesContext = (p, ctx) => {
|
|
249
|
-
if (!ctx) return true;
|
|
250
|
-
if (ctx.productId && p.productId !== ctx.productId) return false;
|
|
251
|
-
if (ctx.variantId && p.variantId !== ctx.variantId) return false;
|
|
252
|
-
if (ctx.batchId && p.batchId !== ctx.batchId) return false;
|
|
253
|
-
return true;
|
|
254
|
-
};
|
|
255
|
-
var toSummary = (rec) => {
|
|
256
|
-
const ref = rec.ref ?? "";
|
|
257
|
-
const productId = rec.productId ?? void 0;
|
|
258
|
-
const variantId = rec.variantId ?? void 0;
|
|
259
|
-
const batchId = rec.batchId ?? void 0;
|
|
260
|
-
const proofId = rec.proofId ?? void 0;
|
|
261
|
-
const scope = {
|
|
262
|
-
kind: batchId ? "batch" : variantId ? "variant" : productId ? "product" : "collection",
|
|
263
|
-
raw: ref,
|
|
264
|
-
productId,
|
|
265
|
-
variantId,
|
|
266
|
-
batchId,
|
|
267
|
-
proofId
|
|
268
|
-
};
|
|
269
|
-
const facetRule = rec.facetRule ?? null;
|
|
270
|
-
if (facetRule) scope.kind = "rule";
|
|
271
|
-
const ruleLabel = facetRule && facetRule.all && facetRule.all.length > 0 ? facetRule.all.length === 1 ? "Rule \xB7 1 facet" : `Rule \xB7 ${facetRule.all.length} facets` : null;
|
|
272
|
-
const fallbackLabel = scope.batchId ?? scope.variantId ?? scope.productId ?? ruleLabel ?? (ref || "Global record");
|
|
273
|
-
return {
|
|
274
|
-
id: rec.id,
|
|
275
|
-
ref,
|
|
276
|
-
scope,
|
|
277
|
-
data: rec.data ?? null,
|
|
278
|
-
status: "configured",
|
|
279
|
-
label: fallbackLabel,
|
|
280
|
-
updatedAt: rec.updatedAt,
|
|
281
|
-
facetRule
|
|
282
|
-
};
|
|
283
|
-
};
|
|
284
|
-
var QK_BASE = ["records-admin", "list"];
|
|
285
|
-
var useRecordList = (args) => {
|
|
286
|
-
const {
|
|
287
|
-
ctx,
|
|
288
|
-
scopeKind,
|
|
289
|
-
search = "",
|
|
290
|
-
filter = "all",
|
|
291
|
-
classify: classify2,
|
|
292
|
-
enabled = true,
|
|
293
|
-
scaffolder,
|
|
294
|
-
contextScope,
|
|
295
|
-
pageSize = 100
|
|
296
|
-
} = args;
|
|
297
|
-
const queryClient = useQueryClient();
|
|
298
|
-
const queryKey = useMemo(
|
|
299
|
-
() => [
|
|
300
|
-
...QK_BASE,
|
|
301
|
-
ctx.collectionId,
|
|
302
|
-
ctx.appId,
|
|
303
|
-
ctx.recordType,
|
|
304
|
-
scopeKind,
|
|
305
|
-
contextScope?.productId ?? null,
|
|
306
|
-
contextScope?.variantId ?? null,
|
|
307
|
-
contextScope?.batchId ?? null
|
|
308
|
-
],
|
|
309
|
-
[ctx.collectionId, ctx.appId, ctx.recordType, scopeKind, contextScope?.productId, contextScope?.variantId, contextScope?.batchId]
|
|
310
|
-
);
|
|
311
|
-
const query = useInfiniteQuery({
|
|
312
|
-
queryKey,
|
|
313
|
-
enabled,
|
|
314
|
-
initialPageParam: 0,
|
|
315
|
-
queryFn: async ({ pageParam }) => {
|
|
316
|
-
const offset = pageParam;
|
|
317
|
-
const { data, total: total2, hasMore } = await listRecords(ctx, { limit: pageSize, offset });
|
|
318
|
-
return { data, total: total2, hasMore, nextOffset: offset + data.length };
|
|
319
|
-
},
|
|
320
|
-
getNextPageParam: (last) => last.hasMore ? last.nextOffset : void 0,
|
|
321
|
-
staleTime: 3e4
|
|
322
|
-
});
|
|
323
|
-
const [scaffolded, setScaffolded] = useState(null);
|
|
324
|
-
const rawItems = useMemo(() => {
|
|
325
|
-
const all = query.data?.pages.flatMap((p) => p.data) ?? [];
|
|
326
|
-
const cls = classify2 ?? defaultClassify;
|
|
327
|
-
return all.map((rec) => ({ rec, summary: toSummary(rec) })).filter(({ rec, summary }) => matchesScope(scopeKind, rec, summary.scope)).filter(({ summary }) => matchesContext(summary.scope, contextScope)).map(({ summary }) => ({ ...summary, status: cls(summary) }));
|
|
328
|
-
}, [query.data, scopeKind, classify2, contextScope]);
|
|
329
|
-
useEffect(() => {
|
|
330
|
-
if (!scaffolder) {
|
|
331
|
-
setScaffolded(null);
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
let cancelled = false;
|
|
335
|
-
Promise.resolve(scaffolder(rawItems)).then((next) => {
|
|
336
|
-
if (!cancelled) setScaffolded(next);
|
|
337
|
-
});
|
|
338
|
-
return () => {
|
|
339
|
-
cancelled = true;
|
|
340
|
-
};
|
|
341
|
-
}, [rawItems, scaffolder]);
|
|
342
|
-
const items = scaffolded ?? rawItems;
|
|
343
|
-
const filtered = useMemo(() => {
|
|
344
|
-
let out = items;
|
|
345
|
-
if (filter !== "all") out = out.filter((r) => r.status === filter);
|
|
346
|
-
if (search.trim()) {
|
|
347
|
-
const q = search.trim().toLowerCase();
|
|
348
|
-
out = out.filter((r) => `${r.label} ${r.subtitle ?? ""} ${r.ref}`.toLowerCase().includes(q));
|
|
349
|
-
}
|
|
350
|
-
return out;
|
|
351
|
-
}, [items, filter, search]);
|
|
352
|
-
const counts = useMemo(() => ({
|
|
353
|
-
all: items.length,
|
|
354
|
-
configured: items.filter((r) => r.status === "configured").length,
|
|
355
|
-
partial: items.filter((r) => r.status === "partial").length,
|
|
356
|
-
empty: items.filter((r) => r.status === "empty").length
|
|
357
|
-
}), [items]);
|
|
358
|
-
const refetch = useCallback(() => {
|
|
359
|
-
queryClient.invalidateQueries({ queryKey: [...QK_BASE, ctx.collectionId, ctx.appId, ctx.recordType] });
|
|
360
|
-
}, [queryClient, ctx.collectionId, ctx.appId, ctx.recordType]);
|
|
361
|
-
const total = query.data?.pages[query.data.pages.length - 1]?.total ?? items.length;
|
|
362
|
-
return {
|
|
363
|
-
allItems: items,
|
|
364
|
-
items: filtered,
|
|
365
|
-
total,
|
|
366
|
-
counts,
|
|
367
|
-
isLoading: query.isLoading,
|
|
368
|
-
error: query.error ?? null,
|
|
369
|
-
refetch,
|
|
370
|
-
hasNextPage: query.hasNextPage,
|
|
371
|
-
isFetchingNextPage: query.isFetchingNextPage,
|
|
372
|
-
fetchNextPage: query.fetchNextPage
|
|
373
|
-
};
|
|
374
|
-
};
|
|
375
226
|
|
|
376
227
|
// src/components/RecordsAdmin/data/resolveRecord.ts
|
|
377
228
|
var resolveRecord = async (args) => {
|
|
@@ -525,563 +376,303 @@ function useResolvedRecord(args) {
|
|
|
525
376
|
error: query.error ?? null
|
|
526
377
|
};
|
|
527
378
|
}
|
|
528
|
-
var
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
appId,
|
|
539
|
-
rule,
|
|
540
|
-
limit = 20,
|
|
541
|
-
debounceMs = 350,
|
|
542
|
-
enabled = true
|
|
543
|
-
} = args;
|
|
544
|
-
const [debouncedRule, setDebouncedRule] = useState(rule);
|
|
545
|
-
const timer = useRef(null);
|
|
379
|
+
var RT_KEY = (recordType) => recordType ?? "_default";
|
|
380
|
+
var lsKey = (appId, recordType) => `ra:intro:${appId}:${RT_KEY(recordType)}`;
|
|
381
|
+
var useIntroDismissed = (SL, collectionId, appId, recordType) => {
|
|
382
|
+
const [dismissed, setDismissed] = useState(() => {
|
|
383
|
+
try {
|
|
384
|
+
return localStorage.getItem(lsKey(appId, recordType)) === "1";
|
|
385
|
+
} catch {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
});
|
|
546
389
|
useEffect(() => {
|
|
547
|
-
|
|
548
|
-
|
|
390
|
+
let cancelled = false;
|
|
391
|
+
(async () => {
|
|
392
|
+
try {
|
|
393
|
+
const cfg = await SL?.appConfiguration?.getConfig?.({ collectionId, appId, admin: true });
|
|
394
|
+
if (cancelled) return;
|
|
395
|
+
const flag = cfg?._meta?.introDismissed?.[RT_KEY(recordType)];
|
|
396
|
+
if (flag) setDismissed(true);
|
|
397
|
+
} catch {
|
|
398
|
+
}
|
|
399
|
+
})();
|
|
549
400
|
return () => {
|
|
550
|
-
|
|
401
|
+
cancelled = true;
|
|
551
402
|
};
|
|
552
|
-
}, [
|
|
553
|
-
const
|
|
403
|
+
}, [SL, collectionId, appId, recordType]);
|
|
404
|
+
const dismiss = useCallback(async () => {
|
|
405
|
+
setDismissed(true);
|
|
406
|
+
try {
|
|
407
|
+
localStorage.setItem(lsKey(appId, recordType), "1");
|
|
408
|
+
} catch {
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
const cfg = await SL?.appConfiguration?.getConfig?.({ collectionId, appId, admin: true }).catch(() => ({}));
|
|
412
|
+
const next = {
|
|
413
|
+
...cfg ?? {},
|
|
414
|
+
_meta: {
|
|
415
|
+
...cfg?._meta ?? {},
|
|
416
|
+
introDismissed: { ...cfg?._meta?.introDismissed ?? {}, [RT_KEY(recordType)]: true }
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
await SL?.appConfiguration?.setConfig?.({ collectionId, appId, admin: true, config: next });
|
|
420
|
+
} catch {
|
|
421
|
+
}
|
|
422
|
+
}, [SL, collectionId, appId, recordType]);
|
|
423
|
+
const undismiss = useCallback(() => {
|
|
424
|
+
setDismissed(false);
|
|
425
|
+
try {
|
|
426
|
+
localStorage.removeItem(lsKey(appId, recordType));
|
|
427
|
+
} catch {
|
|
428
|
+
}
|
|
429
|
+
}, [appId, recordType]);
|
|
430
|
+
return { dismissed, dismiss, undismiss };
|
|
431
|
+
};
|
|
432
|
+
var useScopeProbe = ({ SL, collectionId, admin = true, enabled = true }) => {
|
|
554
433
|
const query = useQuery({
|
|
555
|
-
queryKey: ["records-admin", "
|
|
556
|
-
enabled: enabled && !!collectionId && !!
|
|
557
|
-
staleTime:
|
|
558
|
-
queryFn: () =>
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
434
|
+
queryKey: ["records-admin", "scope-probe", collectionId, admin],
|
|
435
|
+
enabled: enabled && !!collectionId && !!SL?.collection?.get,
|
|
436
|
+
staleTime: 5 * 6e4,
|
|
437
|
+
queryFn: async () => {
|
|
438
|
+
const c = await SL.collection.get(collectionId, admin);
|
|
439
|
+
return {
|
|
440
|
+
hasVariants: !!c?.variants,
|
|
441
|
+
hasBatches: !!c?.batches
|
|
442
|
+
};
|
|
443
|
+
}
|
|
562
444
|
});
|
|
563
445
|
return {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
isLoading: query.
|
|
567
|
-
isStale: rule !== debouncedRule,
|
|
446
|
+
hasVariants: query.data?.hasVariants ?? false,
|
|
447
|
+
hasBatches: query.data?.hasBatches ?? false,
|
|
448
|
+
isLoading: query.isLoading,
|
|
568
449
|
error: query.error ?? null
|
|
569
450
|
};
|
|
570
|
-
}
|
|
571
|
-
var createStore = () => {
|
|
572
|
-
const map = /* @__PURE__ */ new Map();
|
|
573
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
574
|
-
let nextOrder = 0;
|
|
575
|
-
let cachedList = [];
|
|
576
|
-
const recompute = () => {
|
|
577
|
-
cachedList = Array.from(map.values()).sort((a, b) => a.order - b.order);
|
|
578
|
-
};
|
|
579
|
-
const emit = () => {
|
|
580
|
-
recompute();
|
|
581
|
-
listeners.forEach((l) => l());
|
|
582
|
-
};
|
|
583
|
-
return {
|
|
584
|
-
list: () => cachedList,
|
|
585
|
-
get: (key) => map.get(key),
|
|
586
|
-
has: (key) => map.has(key),
|
|
587
|
-
upsertDraft(input) {
|
|
588
|
-
const existing = map.get(input.key);
|
|
589
|
-
const order = existing?.order ?? input.order ?? nextOrder++;
|
|
590
|
-
const status = input.status ?? existing?.status ?? "dirty";
|
|
591
|
-
map.set(input.key, {
|
|
592
|
-
...input,
|
|
593
|
-
order,
|
|
594
|
-
status
|
|
595
|
-
});
|
|
596
|
-
emit();
|
|
597
|
-
},
|
|
598
|
-
setStatus(key, status, error) {
|
|
599
|
-
const existing = map.get(key);
|
|
600
|
-
if (!existing) return;
|
|
601
|
-
map.set(key, { ...existing, status, error });
|
|
602
|
-
emit();
|
|
603
|
-
},
|
|
604
|
-
clearDraft(key) {
|
|
605
|
-
if (map.delete(key)) emit();
|
|
606
|
-
},
|
|
607
|
-
clearAll() {
|
|
608
|
-
if (map.size === 0) return;
|
|
609
|
-
map.clear();
|
|
610
|
-
emit();
|
|
611
|
-
},
|
|
612
|
-
subscribe(listener) {
|
|
613
|
-
listeners.add(listener);
|
|
614
|
-
return () => {
|
|
615
|
-
listeners.delete(listener);
|
|
616
|
-
};
|
|
617
|
-
}
|
|
618
|
-
};
|
|
619
|
-
};
|
|
620
|
-
var DirtyDraftContext = createContext(null);
|
|
621
|
-
var DirtyDraftProvider = ({ children }) => {
|
|
622
|
-
const storeRef = useRef(null);
|
|
623
|
-
if (!storeRef.current) storeRef.current = createStore();
|
|
624
|
-
return /* @__PURE__ */ jsx(DirtyDraftContext.Provider, { value: storeRef.current, children });
|
|
625
|
-
};
|
|
626
|
-
var useDirtyDraftStore = () => {
|
|
627
|
-
const ctx = useContext(DirtyDraftContext);
|
|
628
|
-
const fallbackRef = useRef(null);
|
|
629
|
-
if (!ctx && !fallbackRef.current) fallbackRef.current = createStore();
|
|
630
|
-
return ctx ?? fallbackRef.current;
|
|
631
|
-
};
|
|
632
|
-
var useDirtyDrafts = () => {
|
|
633
|
-
const store = useDirtyDraftStore();
|
|
634
|
-
return useSyncExternalStore(
|
|
635
|
-
useCallback((cb) => store.subscribe(cb), [store]),
|
|
636
|
-
useCallback(() => store.list(), [store]),
|
|
637
|
-
useCallback(() => store.list(), [store])
|
|
638
|
-
);
|
|
639
|
-
};
|
|
640
|
-
var useDirtyDraft = (key) => {
|
|
641
|
-
const store = useDirtyDraftStore();
|
|
642
|
-
const getSnapshot = useCallback(
|
|
643
|
-
() => key ? store.get(key) : void 0,
|
|
644
|
-
[store, key]
|
|
645
|
-
);
|
|
646
|
-
return useSyncExternalStore(
|
|
647
|
-
useCallback((cb) => store.subscribe(cb), [store]),
|
|
648
|
-
getSnapshot,
|
|
649
|
-
getSnapshot
|
|
650
|
-
);
|
|
651
451
|
};
|
|
652
|
-
var
|
|
653
|
-
if (
|
|
654
|
-
const
|
|
655
|
-
return
|
|
656
|
-
|
|
657
|
-
var useDirtyDraftActions = () => {
|
|
658
|
-
const store = useDirtyDraftStore();
|
|
659
|
-
const drafts = useDirtyDrafts();
|
|
660
|
-
const count = useMemo(() => drafts.filter((d) => d.status !== "saved").length, [drafts]);
|
|
661
|
-
const errorCount = useMemo(() => drafts.filter((d) => d.status === "error").length, [drafts]);
|
|
662
|
-
return { drafts, count, errorCount, store };
|
|
452
|
+
var defaultClassify = (r) => {
|
|
453
|
+
if (!r.data) return "empty";
|
|
454
|
+
const keys = Object.keys(r.data);
|
|
455
|
+
if (keys.length === 0) return "empty";
|
|
456
|
+
return "configured";
|
|
663
457
|
};
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
458
|
+
var matchesScope = (kind, rec, _p) => {
|
|
459
|
+
const hasRule = !!rec.facetRule;
|
|
460
|
+
if (kind === "rule") return hasRule;
|
|
461
|
+
if (hasRule) return false;
|
|
462
|
+
const productId = rec.productId ?? void 0;
|
|
463
|
+
const variantId = rec.variantId ?? void 0;
|
|
464
|
+
const batchId = rec.batchId ?? void 0;
|
|
465
|
+
const proofId = rec.proofId ?? void 0;
|
|
466
|
+
if (kind === "collection") {
|
|
467
|
+
return !productId && !variantId && !batchId && !proofId;
|
|
671
468
|
}
|
|
469
|
+
if (kind === "product") return !!productId && !variantId && !batchId;
|
|
470
|
+
if (kind === "variant") return !!variantId;
|
|
471
|
+
if (kind === "batch") return !!batchId;
|
|
472
|
+
return false;
|
|
672
473
|
};
|
|
673
|
-
var
|
|
674
|
-
if (
|
|
675
|
-
if (
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
return JSON.parse(JSON.stringify(resolved.data));
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
return defaultData?.() ?? {};
|
|
474
|
+
var matchesContext = (p, ctx) => {
|
|
475
|
+
if (!ctx) return true;
|
|
476
|
+
if (ctx.productId && p.productId !== ctx.productId) return false;
|
|
477
|
+
if (ctx.variantId && p.variantId !== ctx.variantId) return false;
|
|
478
|
+
if (ctx.batchId && p.batchId !== ctx.batchId) return false;
|
|
479
|
+
return true;
|
|
683
480
|
};
|
|
684
|
-
|
|
481
|
+
var toSummary = (rec) => {
|
|
482
|
+
const ref = rec.ref ?? "";
|
|
483
|
+
const productId = rec.productId ?? void 0;
|
|
484
|
+
const variantId = rec.variantId ?? void 0;
|
|
485
|
+
const batchId = rec.batchId ?? void 0;
|
|
486
|
+
const proofId = rec.proofId ?? void 0;
|
|
487
|
+
const scope = {
|
|
488
|
+
kind: batchId ? "batch" : variantId ? "variant" : productId ? "product" : "collection",
|
|
489
|
+
raw: ref,
|
|
490
|
+
productId,
|
|
491
|
+
variantId,
|
|
492
|
+
batchId,
|
|
493
|
+
proofId
|
|
494
|
+
};
|
|
495
|
+
const facetRule = rec.facetRule ?? null;
|
|
496
|
+
if (facetRule) scope.kind = "rule";
|
|
497
|
+
const ruleLabel = facetRule && facetRule.all && facetRule.all.length > 0 ? facetRule.all.length === 1 ? "Rule \xB7 1 facet" : `Rule \xB7 ${facetRule.all.length} facets` : null;
|
|
498
|
+
const fallbackLabel = scope.batchId ?? scope.variantId ?? scope.productId ?? ruleLabel ?? (ref || "Global record");
|
|
499
|
+
return {
|
|
500
|
+
id: rec.id,
|
|
501
|
+
ref,
|
|
502
|
+
scope,
|
|
503
|
+
data: rec.data ?? null,
|
|
504
|
+
status: "configured",
|
|
505
|
+
label: fallbackLabel,
|
|
506
|
+
updatedAt: rec.updatedAt,
|
|
507
|
+
facetRule
|
|
508
|
+
};
|
|
509
|
+
};
|
|
510
|
+
var QK_BASE = ["records-admin", "list"];
|
|
511
|
+
var useRecordList = (args) => {
|
|
685
512
|
const {
|
|
686
513
|
ctx,
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
createMode = false,
|
|
696
|
-
deriveDraftLabel
|
|
514
|
+
scopeKind,
|
|
515
|
+
search = "",
|
|
516
|
+
filter = "all",
|
|
517
|
+
classify: classify2,
|
|
518
|
+
enabled = true,
|
|
519
|
+
scaffolder,
|
|
520
|
+
contextScope,
|
|
521
|
+
pageSize = 100
|
|
697
522
|
} = args;
|
|
698
523
|
const queryClient = useQueryClient();
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
initialDraft ? initialDraft.baseline : cleanSeed
|
|
712
|
-
);
|
|
713
|
-
const [facetRule, setFacetRule] = useState(seedFacetRule);
|
|
714
|
-
const [savedFacetRule, setSavedFacetRule] = useState(
|
|
715
|
-
initialDraft ? initialDraft.baselineFacetRule : initialFacetRule
|
|
524
|
+
const queryKey = useMemo(
|
|
525
|
+
() => [
|
|
526
|
+
...QK_BASE,
|
|
527
|
+
ctx.collectionId,
|
|
528
|
+
ctx.appId,
|
|
529
|
+
ctx.recordType,
|
|
530
|
+
scopeKind,
|
|
531
|
+
contextScope?.productId ?? null,
|
|
532
|
+
contextScope?.variantId ?? null,
|
|
533
|
+
contextScope?.batchId ?? null
|
|
534
|
+
],
|
|
535
|
+
[ctx.collectionId, ctx.appId, ctx.recordType, scopeKind, contextScope?.productId, contextScope?.variantId, contextScope?.batchId]
|
|
716
536
|
);
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
537
|
+
const query = useInfiniteQuery({
|
|
538
|
+
queryKey,
|
|
539
|
+
enabled,
|
|
540
|
+
initialPageParam: 0,
|
|
541
|
+
queryFn: async ({ pageParam }) => {
|
|
542
|
+
const offset = pageParam;
|
|
543
|
+
const { data, total: total2, hasMore } = await listRecords(ctx, { limit: pageSize, offset });
|
|
544
|
+
return { data, total: total2, hasMore, nextOffset: offset + data.length };
|
|
545
|
+
},
|
|
546
|
+
getNextPageParam: (last) => last.hasMore ? last.nextOffset : void 0,
|
|
547
|
+
staleTime: 3e4
|
|
548
|
+
});
|
|
549
|
+
const [scaffolded, setScaffolded] = useState(null);
|
|
550
|
+
const rawItems = useMemo(() => {
|
|
551
|
+
const all = query.data?.pages.flatMap((p) => p.data) ?? [];
|
|
552
|
+
const cls = classify2 ?? defaultClassify;
|
|
553
|
+
return all.map((rec) => ({ rec, summary: toSummary(rec) })).filter(({ rec, summary }) => matchesScope(scopeKind, rec, summary.scope)).filter(({ summary }) => matchesContext(summary.scope, contextScope)).map(({ summary }) => ({ ...summary, status: cls(summary) }));
|
|
554
|
+
}, [query.data, scopeKind, classify2, contextScope]);
|
|
725
555
|
useEffect(() => {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
const hasUnsaved = !isEqual(valueRef.current, savedSnapshot);
|
|
729
|
-
if (!hasUnsaved) setValue(fresh);
|
|
730
|
-
} else {
|
|
731
|
-
setValue(fresh);
|
|
732
|
-
}
|
|
733
|
-
setSavedSnapshot(fresh);
|
|
734
|
-
setFacetRule(initialFacetRule);
|
|
735
|
-
setSavedFacetRule(initialFacetRule);
|
|
736
|
-
setOptimisticSource(null);
|
|
737
|
-
setUserInteracted(false);
|
|
738
|
-
}, [scope.raw, resolved.source, resolved.sourceRef]);
|
|
739
|
-
const valueDiff = !isEqual(value, savedSnapshot) || !isEqual(facetRule, savedFacetRule);
|
|
740
|
-
const isDirty = userInteracted && valueDiff;
|
|
741
|
-
const handleChange = useCallback((next) => {
|
|
742
|
-
setUserInteracted(true);
|
|
743
|
-
setValue(next);
|
|
744
|
-
}, []);
|
|
745
|
-
const handleFacetRuleChange = useCallback((next) => {
|
|
746
|
-
setUserInteracted(true);
|
|
747
|
-
setFacetRule(next);
|
|
748
|
-
}, []);
|
|
749
|
-
const save = useCallback(async () => {
|
|
750
|
-
const anchors = parsedRefToScope(scope);
|
|
751
|
-
const hasAnchors = !!(anchors.productId || anchors.variantId || anchors.batchId || anchors.proofId);
|
|
752
|
-
const hasRule = !!(facetRule && facetRule.all && facetRule.all.length > 0);
|
|
753
|
-
const isRuleScope2 = scope.kind === "rule";
|
|
754
|
-
if (isRuleScope2 && !hasAnchors && !hasRule && !resolved.recordId && !createMode) {
|
|
755
|
-
console.warn("[useRecordEditor] save() bailed \u2014 rule scope with no clauses, no recordId, not in createMode");
|
|
556
|
+
if (!scaffolder) {
|
|
557
|
+
setScaffolded(null);
|
|
756
558
|
return;
|
|
757
559
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
const cacheKey = resolvedRecordQueryKey({
|
|
762
|
-
collectionId: ctx.collectionId,
|
|
763
|
-
appId: ctx.appId,
|
|
764
|
-
recordType: ctx.recordType,
|
|
765
|
-
productId: scope.productId,
|
|
766
|
-
variantId: scope.variantId,
|
|
767
|
-
batchId: scope.batchId,
|
|
768
|
-
facetId: scope.facetId,
|
|
769
|
-
facetValue: scope.facetValue,
|
|
770
|
-
proofId: scope.proofId,
|
|
771
|
-
recordId: resolved.recordId,
|
|
772
|
-
withParent: true
|
|
773
|
-
});
|
|
774
|
-
const previousCache = queryClient.getQueryData(cacheKey);
|
|
775
|
-
setSaveError(null);
|
|
776
|
-
setIsSaving(true);
|
|
777
|
-
setSavedSnapshot(value);
|
|
778
|
-
setSavedFacetRule(facetRule);
|
|
779
|
-
setOptimisticSource("self");
|
|
780
|
-
queryClient.setQueryData(cacheKey, {
|
|
781
|
-
data: value,
|
|
782
|
-
source: "self",
|
|
783
|
-
sourceRef: scope.raw,
|
|
784
|
-
recordId: resolved.recordId,
|
|
785
|
-
parentValue: previousCache?.parentValue ?? resolved.parentValue
|
|
560
|
+
let cancelled = false;
|
|
561
|
+
Promise.resolve(scaffolder(rawItems)).then((next) => {
|
|
562
|
+
if (!cancelled) setScaffolded(next);
|
|
786
563
|
});
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
564
|
+
return () => {
|
|
565
|
+
cancelled = true;
|
|
566
|
+
};
|
|
567
|
+
}, [rawItems, scaffolder]);
|
|
568
|
+
const items = scaffolded ?? rawItems;
|
|
569
|
+
const filtered = useMemo(() => {
|
|
570
|
+
let out = items;
|
|
571
|
+
if (filter !== "all") out = out.filter((r) => r.status === filter);
|
|
572
|
+
if (search.trim()) {
|
|
573
|
+
const q = search.trim().toLowerCase();
|
|
574
|
+
out = out.filter((r) => `${r.label} ${r.subtitle ?? ""} ${r.ref}`.toLowerCase().includes(q));
|
|
575
|
+
}
|
|
576
|
+
return out;
|
|
577
|
+
}, [items, filter, search]);
|
|
578
|
+
const counts = useMemo(() => ({
|
|
579
|
+
all: items.length,
|
|
580
|
+
configured: items.filter((r) => r.status === "configured").length,
|
|
581
|
+
partial: items.filter((r) => r.status === "partial").length,
|
|
582
|
+
empty: items.filter((r) => r.status === "empty").length
|
|
583
|
+
}), [items]);
|
|
584
|
+
const refetch = useCallback(() => {
|
|
585
|
+
queryClient.invalidateQueries({ queryKey: [...QK_BASE, ctx.collectionId, ctx.appId, ctx.recordType] });
|
|
586
|
+
}, [queryClient, ctx.collectionId, ctx.appId, ctx.recordType]);
|
|
587
|
+
const total = query.data?.pages[query.data.pages.length - 1]?.total ?? items.length;
|
|
588
|
+
return {
|
|
589
|
+
allItems: items,
|
|
590
|
+
items: filtered,
|
|
591
|
+
total,
|
|
592
|
+
counts,
|
|
593
|
+
isLoading: query.isLoading,
|
|
594
|
+
error: query.error ?? null,
|
|
595
|
+
refetch,
|
|
596
|
+
hasNextPage: query.hasNextPage,
|
|
597
|
+
isFetchingNextPage: query.isFetchingNextPage,
|
|
598
|
+
fetchNextPage: query.fetchNextPage
|
|
599
|
+
};
|
|
600
|
+
};
|
|
601
|
+
var LOG = "[RecordsAdmin/useFacetBrowse]";
|
|
602
|
+
var QK = ["records-admin", "facet-browse"];
|
|
603
|
+
var toScaffoldSummary = (facet, value) => {
|
|
604
|
+
const facetKey = facet.key ?? "";
|
|
605
|
+
const valueKey = value.key ?? "";
|
|
606
|
+
const ref = buildRef({ facetId: facetKey, facetValue: valueKey });
|
|
607
|
+
return {
|
|
608
|
+
id: null,
|
|
609
|
+
ref,
|
|
610
|
+
scope: parseRef(ref),
|
|
611
|
+
data: null,
|
|
612
|
+
status: "empty",
|
|
613
|
+
label: value.name ?? valueKey ?? "Untitled value",
|
|
614
|
+
subtitle: facet.name ?? facetKey ?? void 0
|
|
615
|
+
};
|
|
616
|
+
};
|
|
617
|
+
var useFacetBrowse = ({
|
|
618
|
+
SL,
|
|
619
|
+
collectionId,
|
|
620
|
+
existing,
|
|
621
|
+
search = "",
|
|
622
|
+
filter = "all",
|
|
623
|
+
enabled = true
|
|
624
|
+
}) => {
|
|
625
|
+
const queryClient = useQueryClient();
|
|
626
|
+
const hasAdminList = !!SL?.facets?.list;
|
|
627
|
+
const hasPublicList = !!SL?.facets?.publicList;
|
|
628
|
+
const hasAnyList = hasAdminList || hasPublicList;
|
|
629
|
+
const queryEnabled = enabled && !!collectionId && hasAnyList;
|
|
630
|
+
const query = useQuery({
|
|
631
|
+
queryKey: [...QK, collectionId],
|
|
632
|
+
enabled: queryEnabled,
|
|
633
|
+
staleTime: 3e4,
|
|
634
|
+
queryFn: async () => {
|
|
635
|
+
const t0 = performance.now();
|
|
636
|
+
try {
|
|
637
|
+
if (SL?.facets?.list) {
|
|
638
|
+
console.info(`${LOG} \u2192 SL.facets.list("${collectionId}", { includeValues: true })`);
|
|
639
|
+
const res = await SL.facets.list(collectionId, { includeValues: true });
|
|
640
|
+
const items = res?.items ?? [];
|
|
641
|
+
console.info(
|
|
642
|
+
`${LOG} \u2190 SL.facets.list ok in ${Math.round(performance.now() - t0)}ms \u2014 ${items.length} facet(s)`,
|
|
643
|
+
items
|
|
644
|
+
);
|
|
645
|
+
return items;
|
|
646
|
+
}
|
|
647
|
+
if (SL?.facets?.publicList) {
|
|
648
|
+
console.info(`${LOG} \u2192 SL.facets.publicList("${collectionId}", { includeValues: true })`);
|
|
649
|
+
const res = await SL.facets.publicList(collectionId, { includeValues: true });
|
|
650
|
+
const items = res?.items ?? [];
|
|
651
|
+
console.info(
|
|
652
|
+
`${LOG} \u2190 SL.facets.publicList ok in ${Math.round(performance.now() - t0)}ms \u2014 ${items.length} facet(s)`,
|
|
653
|
+
items
|
|
654
|
+
);
|
|
655
|
+
return items;
|
|
656
|
+
}
|
|
657
|
+
console.warn(`${LOG} queryFn ran but no facets API is available on SL`);
|
|
658
|
+
return [];
|
|
659
|
+
} catch (err) {
|
|
660
|
+
console.error(`${LOG} \u2716 facets fetch failed`, err);
|
|
661
|
+
throw err;
|
|
821
662
|
}
|
|
822
|
-
setSaveError(err);
|
|
823
|
-
onSaveError?.(err);
|
|
824
|
-
throw err;
|
|
825
|
-
} finally {
|
|
826
|
-
setIsSaving(false);
|
|
827
663
|
}
|
|
828
|
-
}
|
|
829
|
-
const
|
|
830
|
-
setValue(savedSnapshot);
|
|
831
|
-
setFacetRule(savedFacetRule);
|
|
832
|
-
setUserInteracted(false);
|
|
833
|
-
draftStore.clearDraft(draftKey);
|
|
834
|
-
}, [savedSnapshot, savedFacetRule]);
|
|
835
|
-
const remove = useCallback(async () => {
|
|
836
|
-
if (resolved.source !== "self") return;
|
|
837
|
-
if (!resolved.recordId) return;
|
|
838
|
-
await removeRecord(ctx, resolved.recordId);
|
|
839
|
-
draftStore.clearDraft(draftKey);
|
|
840
|
-
onDeleted?.();
|
|
841
|
-
}, [resolved.source, resolved.recordId]);
|
|
842
|
-
const prevDraftKeyRef = useRef(draftKey);
|
|
843
|
-
const prevScopeRawRef = useRef(scope.raw);
|
|
664
|
+
});
|
|
665
|
+
const lastLoggedRef = useRef("");
|
|
844
666
|
useEffect(() => {
|
|
845
|
-
const
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
667
|
+
const signature = `${enabled}|${collectionId}|${hasAdminList}|${hasPublicList}`;
|
|
668
|
+
if (signature === lastLoggedRef.current) return;
|
|
669
|
+
lastLoggedRef.current = signature;
|
|
670
|
+
if (!enabled) {
|
|
671
|
+
console.info(`${LOG} skipped \u2014 enabled=false (shell not on facet tab yet)`);
|
|
672
|
+
return;
|
|
850
673
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
}, [draftKey, scope.raw]);
|
|
854
|
-
useEffect(() => {
|
|
855
|
-
if (!isDirty) {
|
|
856
|
-
return;
|
|
857
|
-
}
|
|
858
|
-
const anchors = parsedRefToScope(scope);
|
|
859
|
-
const saveKind = resolved.recordId && resolved.source === "self" ? "update" : createMode ? "create" : "upsert";
|
|
860
|
-
const deriveLabel = () => {
|
|
861
|
-
if (deriveDraftLabel) {
|
|
862
|
-
try {
|
|
863
|
-
const custom = deriveDraftLabel(value, scope);
|
|
864
|
-
if (typeof custom === "string" && custom.trim()) return custom.trim();
|
|
865
|
-
} catch {
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
const KEYS = ["title", "name", "label", "heading", "question", "slug"];
|
|
869
|
-
const pickString = (obj) => {
|
|
870
|
-
if (!obj || typeof obj !== "object") return void 0;
|
|
871
|
-
for (const key of KEYS) {
|
|
872
|
-
const raw = obj[key];
|
|
873
|
-
if (typeof raw === "string" && raw.trim()) return raw.trim();
|
|
874
|
-
}
|
|
875
|
-
return void 0;
|
|
876
|
-
};
|
|
877
|
-
const v = value;
|
|
878
|
-
const top = pickString(v);
|
|
879
|
-
if (top) return top;
|
|
880
|
-
if (v && typeof v === "object") {
|
|
881
|
-
for (const wrapper of ["display", "content", "meta", "data"]) {
|
|
882
|
-
const nested = pickString(v[wrapper]);
|
|
883
|
-
if (nested) return nested;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
if (scope.raw?.startsWith("item:")) return "Untitled item";
|
|
887
|
-
if (scope.kind === "rule") return "Rule";
|
|
888
|
-
if (scope.kind && scope.kind !== "collection") {
|
|
889
|
-
return scope.kind.charAt(0).toUpperCase() + scope.kind.slice(1);
|
|
890
|
-
}
|
|
891
|
-
return "Default";
|
|
892
|
-
};
|
|
893
|
-
draftStore.upsertDraft({
|
|
894
|
-
key: draftKey,
|
|
895
|
-
label: deriveLabel(),
|
|
896
|
-
context: scope.kind,
|
|
897
|
-
scopeRaw: scope.raw,
|
|
898
|
-
recordId: resolved.recordId,
|
|
899
|
-
value,
|
|
900
|
-
facetRule,
|
|
901
|
-
baseline: savedSnapshot,
|
|
902
|
-
baselineFacetRule: savedFacetRule,
|
|
903
|
-
createMode,
|
|
904
|
-
scopeAnchors: anchors,
|
|
905
|
-
ref: scope.kind === "rule" && scope.raw ? scope.raw : void 0,
|
|
906
|
-
saveKind,
|
|
907
|
-
save: async () => {
|
|
908
|
-
await save();
|
|
909
|
-
}
|
|
910
|
-
});
|
|
911
|
-
}, [isDirty, value, facetRule, savedSnapshot, savedFacetRule, scope.raw, resolved.recordId, resolved.source, createMode, save]);
|
|
912
|
-
const effectiveSource = optimisticSource ?? resolved.source;
|
|
913
|
-
const isRuleScope = scope.kind === "rule";
|
|
914
|
-
const ruleValid = !isRuleScope || isFacetRuleValid(facetRule);
|
|
915
|
-
const canSave = ruleValid;
|
|
916
|
-
const cannotSaveReason = !ruleValid ? "Pick at least one value for every facet in the rule before saving." : void 0;
|
|
917
|
-
return {
|
|
918
|
-
value,
|
|
919
|
-
onChange: handleChange,
|
|
920
|
-
source: effectiveSource,
|
|
921
|
-
recordId: resolved.recordId,
|
|
922
|
-
parentValue: resolved.parentValue,
|
|
923
|
-
scope,
|
|
924
|
-
isDirty,
|
|
925
|
-
save,
|
|
926
|
-
reset,
|
|
927
|
-
remove,
|
|
928
|
-
canRemove: effectiveSource === "self",
|
|
929
|
-
canSave,
|
|
930
|
-
cannotSaveReason,
|
|
931
|
-
isSaving,
|
|
932
|
-
saveError,
|
|
933
|
-
facetRule,
|
|
934
|
-
onFacetRuleChange: handleFacetRuleChange
|
|
935
|
-
};
|
|
936
|
-
}
|
|
937
|
-
var RT_KEY = (recordType) => recordType ?? "_default";
|
|
938
|
-
var lsKey = (appId, recordType) => `ra:intro:${appId}:${RT_KEY(recordType)}`;
|
|
939
|
-
var useIntroDismissed = (SL, collectionId, appId, recordType) => {
|
|
940
|
-
const [dismissed, setDismissed] = useState(() => {
|
|
941
|
-
try {
|
|
942
|
-
return localStorage.getItem(lsKey(appId, recordType)) === "1";
|
|
943
|
-
} catch {
|
|
944
|
-
return false;
|
|
945
|
-
}
|
|
946
|
-
});
|
|
947
|
-
useEffect(() => {
|
|
948
|
-
let cancelled = false;
|
|
949
|
-
(async () => {
|
|
950
|
-
try {
|
|
951
|
-
const cfg = await SL?.appConfiguration?.getConfig?.({ collectionId, appId, admin: true });
|
|
952
|
-
if (cancelled) return;
|
|
953
|
-
const flag = cfg?._meta?.introDismissed?.[RT_KEY(recordType)];
|
|
954
|
-
if (flag) setDismissed(true);
|
|
955
|
-
} catch {
|
|
956
|
-
}
|
|
957
|
-
})();
|
|
958
|
-
return () => {
|
|
959
|
-
cancelled = true;
|
|
960
|
-
};
|
|
961
|
-
}, [SL, collectionId, appId, recordType]);
|
|
962
|
-
const dismiss = useCallback(async () => {
|
|
963
|
-
setDismissed(true);
|
|
964
|
-
try {
|
|
965
|
-
localStorage.setItem(lsKey(appId, recordType), "1");
|
|
966
|
-
} catch {
|
|
967
|
-
}
|
|
968
|
-
try {
|
|
969
|
-
const cfg = await SL?.appConfiguration?.getConfig?.({ collectionId, appId, admin: true }).catch(() => ({}));
|
|
970
|
-
const next = {
|
|
971
|
-
...cfg ?? {},
|
|
972
|
-
_meta: {
|
|
973
|
-
...cfg?._meta ?? {},
|
|
974
|
-
introDismissed: { ...cfg?._meta?.introDismissed ?? {}, [RT_KEY(recordType)]: true }
|
|
975
|
-
}
|
|
976
|
-
};
|
|
977
|
-
await SL?.appConfiguration?.setConfig?.({ collectionId, appId, admin: true, config: next });
|
|
978
|
-
} catch {
|
|
979
|
-
}
|
|
980
|
-
}, [SL, collectionId, appId, recordType]);
|
|
981
|
-
const undismiss = useCallback(() => {
|
|
982
|
-
setDismissed(false);
|
|
983
|
-
try {
|
|
984
|
-
localStorage.removeItem(lsKey(appId, recordType));
|
|
985
|
-
} catch {
|
|
986
|
-
}
|
|
987
|
-
}, [appId, recordType]);
|
|
988
|
-
return { dismissed, dismiss, undismiss };
|
|
989
|
-
};
|
|
990
|
-
var useScopeProbe = ({ SL, collectionId, admin = true, enabled = true }) => {
|
|
991
|
-
const query = useQuery({
|
|
992
|
-
queryKey: ["records-admin", "scope-probe", collectionId, admin],
|
|
993
|
-
enabled: enabled && !!collectionId && !!SL?.collection?.get,
|
|
994
|
-
staleTime: 5 * 6e4,
|
|
995
|
-
queryFn: async () => {
|
|
996
|
-
const c = await SL.collection.get(collectionId, admin);
|
|
997
|
-
return {
|
|
998
|
-
hasVariants: !!c?.variants,
|
|
999
|
-
hasBatches: !!c?.batches
|
|
1000
|
-
};
|
|
1001
|
-
}
|
|
1002
|
-
});
|
|
1003
|
-
return {
|
|
1004
|
-
hasVariants: query.data?.hasVariants ?? false,
|
|
1005
|
-
hasBatches: query.data?.hasBatches ?? false,
|
|
1006
|
-
isLoading: query.isLoading,
|
|
1007
|
-
error: query.error ?? null
|
|
1008
|
-
};
|
|
1009
|
-
};
|
|
1010
|
-
var LOG = "[RecordsAdmin/useFacetBrowse]";
|
|
1011
|
-
var QK = ["records-admin", "facet-browse"];
|
|
1012
|
-
var toScaffoldSummary = (facet, value) => {
|
|
1013
|
-
const facetKey = facet.key ?? "";
|
|
1014
|
-
const valueKey = value.key ?? "";
|
|
1015
|
-
const ref = buildRef({ facetId: facetKey, facetValue: valueKey });
|
|
1016
|
-
return {
|
|
1017
|
-
id: null,
|
|
1018
|
-
ref,
|
|
1019
|
-
scope: parseRef(ref),
|
|
1020
|
-
data: null,
|
|
1021
|
-
status: "empty",
|
|
1022
|
-
label: value.name ?? valueKey ?? "Untitled value",
|
|
1023
|
-
subtitle: facet.name ?? facetKey ?? void 0
|
|
1024
|
-
};
|
|
1025
|
-
};
|
|
1026
|
-
var useFacetBrowse = ({
|
|
1027
|
-
SL,
|
|
1028
|
-
collectionId,
|
|
1029
|
-
existing,
|
|
1030
|
-
search = "",
|
|
1031
|
-
filter = "all",
|
|
1032
|
-
enabled = true
|
|
1033
|
-
}) => {
|
|
1034
|
-
const queryClient = useQueryClient();
|
|
1035
|
-
const hasAdminList = !!SL?.facets?.list;
|
|
1036
|
-
const hasPublicList = !!SL?.facets?.publicList;
|
|
1037
|
-
const hasAnyList = hasAdminList || hasPublicList;
|
|
1038
|
-
const queryEnabled = enabled && !!collectionId && hasAnyList;
|
|
1039
|
-
const query = useQuery({
|
|
1040
|
-
queryKey: [...QK, collectionId],
|
|
1041
|
-
enabled: queryEnabled,
|
|
1042
|
-
staleTime: 3e4,
|
|
1043
|
-
queryFn: async () => {
|
|
1044
|
-
const t0 = performance.now();
|
|
1045
|
-
try {
|
|
1046
|
-
if (SL?.facets?.list) {
|
|
1047
|
-
console.info(`${LOG} \u2192 SL.facets.list("${collectionId}", { includeValues: true })`);
|
|
1048
|
-
const res = await SL.facets.list(collectionId, { includeValues: true });
|
|
1049
|
-
const items = res?.items ?? [];
|
|
1050
|
-
console.info(
|
|
1051
|
-
`${LOG} \u2190 SL.facets.list ok in ${Math.round(performance.now() - t0)}ms \u2014 ${items.length} facet(s)`,
|
|
1052
|
-
items
|
|
1053
|
-
);
|
|
1054
|
-
return items;
|
|
1055
|
-
}
|
|
1056
|
-
if (SL?.facets?.publicList) {
|
|
1057
|
-
console.info(`${LOG} \u2192 SL.facets.publicList("${collectionId}", { includeValues: true })`);
|
|
1058
|
-
const res = await SL.facets.publicList(collectionId, { includeValues: true });
|
|
1059
|
-
const items = res?.items ?? [];
|
|
1060
|
-
console.info(
|
|
1061
|
-
`${LOG} \u2190 SL.facets.publicList ok in ${Math.round(performance.now() - t0)}ms \u2014 ${items.length} facet(s)`,
|
|
1062
|
-
items
|
|
1063
|
-
);
|
|
1064
|
-
return items;
|
|
1065
|
-
}
|
|
1066
|
-
console.warn(`${LOG} queryFn ran but no facets API is available on SL`);
|
|
1067
|
-
return [];
|
|
1068
|
-
} catch (err) {
|
|
1069
|
-
console.error(`${LOG} \u2716 facets fetch failed`, err);
|
|
1070
|
-
throw err;
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
});
|
|
1074
|
-
const lastLoggedRef = useRef("");
|
|
1075
|
-
useEffect(() => {
|
|
1076
|
-
const signature = `${enabled}|${collectionId}|${hasAdminList}|${hasPublicList}`;
|
|
1077
|
-
if (signature === lastLoggedRef.current) return;
|
|
1078
|
-
lastLoggedRef.current = signature;
|
|
1079
|
-
if (!enabled) {
|
|
1080
|
-
console.info(`${LOG} skipped \u2014 enabled=false (shell not on facet tab yet)`);
|
|
1081
|
-
return;
|
|
1082
|
-
}
|
|
1083
|
-
if (!collectionId) {
|
|
1084
|
-
console.warn(`${LOG} skipped \u2014 no collectionId provided`);
|
|
674
|
+
if (!collectionId) {
|
|
675
|
+
console.warn(`${LOG} skipped \u2014 no collectionId provided`);
|
|
1085
676
|
return;
|
|
1086
677
|
}
|
|
1087
678
|
if (!hasAnyList) {
|
|
@@ -1249,81 +840,261 @@ var useProductChildren = (args) => {
|
|
|
1249
840
|
refetch
|
|
1250
841
|
};
|
|
1251
842
|
};
|
|
1252
|
-
var
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
e.preventDefault();
|
|
1264
|
-
e.returnValue = "";
|
|
1265
|
-
return "";
|
|
1266
|
-
};
|
|
1267
|
-
window.addEventListener("beforeunload", handler);
|
|
1268
|
-
return () => window.removeEventListener("beforeunload", handler);
|
|
1269
|
-
}, [isDirty, disableBeforeUnload]);
|
|
1270
|
-
useEffect(() => {
|
|
1271
|
-
if (disableParentMessage || typeof window === "undefined") return;
|
|
1272
|
-
if (window.parent === window) return;
|
|
1273
|
-
try {
|
|
1274
|
-
window.parent.postMessage({
|
|
1275
|
-
type: MESSAGE_TYPE,
|
|
1276
|
-
isDirty,
|
|
1277
|
-
label: label ?? null
|
|
1278
|
-
}, "*");
|
|
1279
|
-
} catch {
|
|
1280
|
-
}
|
|
1281
|
-
}, [isDirty, label, disableParentMessage]);
|
|
1282
|
-
useEffect(() => {
|
|
1283
|
-
if (disableParentMessage || typeof window === "undefined") return;
|
|
1284
|
-
if (window.parent === window) return;
|
|
1285
|
-
return () => {
|
|
1286
|
-
try {
|
|
1287
|
-
window.parent.postMessage({ type: MESSAGE_TYPE, isDirty: false, label: label ?? null }, "*");
|
|
1288
|
-
} catch {
|
|
1289
|
-
}
|
|
1290
|
-
};
|
|
1291
|
-
}, [disableParentMessage, label]);
|
|
1292
|
-
const confirmDiscard = useCallback(
|
|
1293
|
-
async (message) => {
|
|
1294
|
-
if (!isDirty) return true;
|
|
1295
|
-
const msg = message ?? "You have unsaved changes. Discard them?";
|
|
1296
|
-
if (confirm) {
|
|
1297
|
-
try {
|
|
1298
|
-
return !!await confirm(msg);
|
|
1299
|
-
} catch {
|
|
1300
|
-
return false;
|
|
1301
|
-
}
|
|
843
|
+
var EMPTY_RULE_FILTERS = { facetKeys: [], minClauses: null };
|
|
844
|
+
var COMPLEXITY_THRESHOLDS = [3, 5, 10];
|
|
845
|
+
var ruleOf = (r) => r.facetRule ?? null;
|
|
846
|
+
function RuleFilterChips({ source, value, onChange }) {
|
|
847
|
+
const facetKeyEntries = useMemo(() => {
|
|
848
|
+
const counts = /* @__PURE__ */ new Map();
|
|
849
|
+
for (const r of source) {
|
|
850
|
+
const rule = ruleOf(r);
|
|
851
|
+
if (!rule) continue;
|
|
852
|
+
for (const c of rule.all ?? []) {
|
|
853
|
+
counts.set(c.facetKey, (counts.get(c.facetKey) ?? 0) + 1);
|
|
1302
854
|
}
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
855
|
+
}
|
|
856
|
+
return Array.from(counts.entries()).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
857
|
+
}, [source]);
|
|
858
|
+
const maxClauses = useMemo(() => {
|
|
859
|
+
let max = 0;
|
|
860
|
+
for (const r of source) {
|
|
861
|
+
const rule = ruleOf(r);
|
|
862
|
+
if (!rule) continue;
|
|
863
|
+
max = Math.max(max, rule.all?.length ?? 0);
|
|
864
|
+
}
|
|
865
|
+
return max;
|
|
866
|
+
}, [source]);
|
|
867
|
+
if (facetKeyEntries.length === 0 && maxClauses < 2) return null;
|
|
868
|
+
const toggleKey = (key) => {
|
|
869
|
+
const has = value.facetKeys.includes(key);
|
|
870
|
+
const next = has ? value.facetKeys.filter((k) => k !== key) : [...value.facetKeys, key];
|
|
871
|
+
onChange({ ...value, facetKeys: next });
|
|
872
|
+
};
|
|
873
|
+
const setMin = (n) => onChange({ ...value, minClauses: n });
|
|
874
|
+
const hasAny = value.facetKeys.length > 0 || value.minClauses != null;
|
|
875
|
+
return /* @__PURE__ */ jsxs("div", { className: "ra-rule-filters", children: [
|
|
876
|
+
/* @__PURE__ */ jsx("div", { className: "ra-rule-filters-row", role: "group", "aria-label": "Filter rules by facet", children: facetKeyEntries.map(([key, count]) => {
|
|
877
|
+
const active = value.facetKeys.includes(key);
|
|
878
|
+
return /* @__PURE__ */ jsxs(
|
|
879
|
+
"button",
|
|
880
|
+
{
|
|
881
|
+
type: "button",
|
|
882
|
+
onClick: () => toggleKey(key),
|
|
883
|
+
className: "ra-rule-filter-chip",
|
|
884
|
+
"data-active": active ? "true" : "false",
|
|
885
|
+
"aria-pressed": active,
|
|
886
|
+
title: `Show rules using ${key}`,
|
|
887
|
+
children: [
|
|
888
|
+
/* @__PURE__ */ jsx("span", { className: "ra-rule-filter-chip-label", children: key }),
|
|
889
|
+
/* @__PURE__ */ jsx("span", { className: "ra-rule-filter-chip-count", children: count })
|
|
890
|
+
]
|
|
891
|
+
},
|
|
892
|
+
key
|
|
893
|
+
);
|
|
894
|
+
}) }),
|
|
895
|
+
maxClauses >= 2 && /* @__PURE__ */ jsx("div", { className: "ra-rule-filters-row", role: "group", "aria-label": "Filter by clause count", children: COMPLEXITY_THRESHOLDS.filter((n) => maxClauses >= n).map((n) => {
|
|
896
|
+
const active = value.minClauses === n;
|
|
897
|
+
return /* @__PURE__ */ jsxs(
|
|
898
|
+
"button",
|
|
899
|
+
{
|
|
900
|
+
type: "button",
|
|
901
|
+
onClick: () => setMin(active ? null : n),
|
|
902
|
+
className: "ra-rule-filter-chip",
|
|
903
|
+
"data-tone": "complexity",
|
|
904
|
+
"data-active": active ? "true" : "false",
|
|
905
|
+
"aria-pressed": active,
|
|
906
|
+
title: `Only rules with \u2265 ${n} facets`,
|
|
907
|
+
children: [
|
|
908
|
+
"\u2265 ",
|
|
909
|
+
n,
|
|
910
|
+
" facets"
|
|
911
|
+
]
|
|
912
|
+
},
|
|
913
|
+
n
|
|
914
|
+
);
|
|
915
|
+
}) }),
|
|
916
|
+
hasAny && /* @__PURE__ */ jsx(
|
|
917
|
+
"button",
|
|
918
|
+
{
|
|
919
|
+
type: "button",
|
|
920
|
+
onClick: () => onChange(EMPTY_RULE_FILTERS),
|
|
921
|
+
className: "ra-rule-filter-clear",
|
|
922
|
+
"aria-label": "Clear rule filters",
|
|
923
|
+
children: "Clear filters"
|
|
924
|
+
}
|
|
925
|
+
)
|
|
926
|
+
] });
|
|
927
|
+
}
|
|
928
|
+
function applyRuleFilters(items, filters) {
|
|
929
|
+
if (filters.facetKeys.length === 0 && filters.minClauses == null) return items;
|
|
930
|
+
return items.filter((r) => {
|
|
931
|
+
const rule = ruleOf(r);
|
|
932
|
+
if (!rule) return false;
|
|
933
|
+
if (filters.minClauses != null && (rule.all?.length ?? 0) < filters.minClauses) return false;
|
|
934
|
+
if (filters.facetKeys.length > 0) {
|
|
935
|
+
const keys = new Set((rule.all ?? []).map((c) => c.facetKey));
|
|
936
|
+
if (!filters.facetKeys.some((k) => keys.has(k))) return false;
|
|
937
|
+
}
|
|
938
|
+
return true;
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// src/components/RecordsAdmin/hooks/useShellBrowser.ts
|
|
943
|
+
function useShellBrowser(opts) {
|
|
944
|
+
const {
|
|
945
|
+
ctx,
|
|
946
|
+
SL,
|
|
947
|
+
collectionId,
|
|
948
|
+
activeScope,
|
|
949
|
+
contextScope,
|
|
950
|
+
probeIsLoading,
|
|
951
|
+
selectedProductId,
|
|
952
|
+
drillTab,
|
|
953
|
+
classify: classify2
|
|
954
|
+
} = opts;
|
|
955
|
+
const [search, setSearch] = useState("");
|
|
956
|
+
const [filter, setFilter] = useState("all");
|
|
957
|
+
const [ruleFilters, setRuleFilters] = useState(EMPTY_RULE_FILTERS);
|
|
958
|
+
const [facetBrowseFilter, setFacetBrowseFilter] = useState(null);
|
|
959
|
+
useEffect(() => {
|
|
960
|
+
setSearch("");
|
|
961
|
+
setFilter("all");
|
|
962
|
+
setRuleFilters(EMPTY_RULE_FILTERS);
|
|
963
|
+
setFacetBrowseFilter(null);
|
|
964
|
+
}, [activeScope]);
|
|
965
|
+
const productBrowse = useProductBrowse({
|
|
966
|
+
SL,
|
|
967
|
+
collectionId,
|
|
968
|
+
search: activeScope === "product" ? search : "",
|
|
969
|
+
enabled: activeScope === "product" && !contextScope?.productId
|
|
970
|
+
});
|
|
971
|
+
const recordListEnabled = (activeScope === "rule" || activeScope === "collection") && !probeIsLoading;
|
|
972
|
+
const recordList = useRecordList({
|
|
973
|
+
ctx,
|
|
974
|
+
scopeKind: activeScope,
|
|
975
|
+
search,
|
|
976
|
+
filter,
|
|
977
|
+
classify: classify2,
|
|
978
|
+
contextScope,
|
|
979
|
+
enabled: recordListEnabled
|
|
980
|
+
});
|
|
981
|
+
const facetBrowse = useFacetBrowse({
|
|
982
|
+
SL,
|
|
983
|
+
collectionId,
|
|
984
|
+
existing: [],
|
|
985
|
+
enabled: (activeScope === "rule" || activeScope === "collection") && !probeIsLoading
|
|
986
|
+
});
|
|
987
|
+
const variantChildren = useProductChildren({
|
|
988
|
+
SL,
|
|
989
|
+
collectionId,
|
|
990
|
+
productId: selectedProductId,
|
|
991
|
+
kind: drillTab === "variant" ? "variant" : null
|
|
992
|
+
});
|
|
993
|
+
const batchChildren = useProductChildren({
|
|
994
|
+
SL,
|
|
995
|
+
collectionId,
|
|
996
|
+
productId: selectedProductId,
|
|
997
|
+
kind: drillTab === "batch" ? "batch" : null
|
|
998
|
+
});
|
|
999
|
+
const refetchAll = useCallback(() => {
|
|
1000
|
+
productBrowse.refetch();
|
|
1001
|
+
recordList.refetch();
|
|
1002
|
+
facetBrowse.refetch();
|
|
1003
|
+
variantChildren.refetch?.();
|
|
1004
|
+
batchChildren.refetch?.();
|
|
1005
|
+
}, [productBrowse, recordList, facetBrowse, variantChildren, batchChildren]);
|
|
1006
|
+
return {
|
|
1007
|
+
search,
|
|
1008
|
+
setSearch,
|
|
1009
|
+
filter,
|
|
1010
|
+
setFilter,
|
|
1011
|
+
ruleFilters,
|
|
1012
|
+
setRuleFilters,
|
|
1013
|
+
facetBrowseFilter,
|
|
1014
|
+
setFacetBrowseFilter,
|
|
1015
|
+
productBrowse,
|
|
1016
|
+
recordList,
|
|
1017
|
+
facetBrowse,
|
|
1018
|
+
variantChildren,
|
|
1019
|
+
batchChildren,
|
|
1020
|
+
refetchAll
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
var MESSAGE_TYPE = "smartlinks:unsaved-changes";
|
|
1024
|
+
function useUnsavedGuard({
|
|
1025
|
+
isDirty,
|
|
1026
|
+
label,
|
|
1027
|
+
confirm,
|
|
1028
|
+
disableBeforeUnload = false,
|
|
1029
|
+
disableParentMessage = false
|
|
1030
|
+
}) {
|
|
1031
|
+
useEffect(() => {
|
|
1032
|
+
if (disableBeforeUnload || !isDirty || typeof window === "undefined") return;
|
|
1033
|
+
const handler = (e) => {
|
|
1034
|
+
e.preventDefault();
|
|
1035
|
+
e.returnValue = "";
|
|
1036
|
+
return "";
|
|
1037
|
+
};
|
|
1038
|
+
window.addEventListener("beforeunload", handler);
|
|
1039
|
+
return () => window.removeEventListener("beforeunload", handler);
|
|
1040
|
+
}, [isDirty, disableBeforeUnload]);
|
|
1041
|
+
useEffect(() => {
|
|
1042
|
+
if (disableParentMessage || typeof window === "undefined") return;
|
|
1043
|
+
if (window.parent === window) return;
|
|
1044
|
+
try {
|
|
1045
|
+
window.parent.postMessage({
|
|
1046
|
+
type: MESSAGE_TYPE,
|
|
1047
|
+
isDirty,
|
|
1048
|
+
label: label ?? null
|
|
1049
|
+
}, "*");
|
|
1050
|
+
} catch {
|
|
1051
|
+
}
|
|
1052
|
+
}, [isDirty, label, disableParentMessage]);
|
|
1053
|
+
useEffect(() => {
|
|
1054
|
+
if (disableParentMessage || typeof window === "undefined") return;
|
|
1055
|
+
if (window.parent === window) return;
|
|
1056
|
+
return () => {
|
|
1057
|
+
try {
|
|
1058
|
+
window.parent.postMessage({ type: MESSAGE_TYPE, isDirty: false, label: label ?? null }, "*");
|
|
1059
|
+
} catch {
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
}, [disableParentMessage, label]);
|
|
1063
|
+
const confirmDiscard = useCallback(
|
|
1064
|
+
async (message) => {
|
|
1065
|
+
if (!isDirty) return true;
|
|
1066
|
+
const msg = message ?? "You have unsaved changes. Discard them?";
|
|
1067
|
+
if (confirm) {
|
|
1068
|
+
try {
|
|
1069
|
+
return !!await confirm(msg);
|
|
1070
|
+
} catch {
|
|
1071
|
+
return false;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
if (typeof window === "undefined") return true;
|
|
1075
|
+
return window.confirm(msg);
|
|
1076
|
+
},
|
|
1077
|
+
[isDirty, confirm]
|
|
1078
|
+
);
|
|
1079
|
+
return { confirmDiscard };
|
|
1080
|
+
}
|
|
1081
|
+
var fallbackConfirm = async (i18n) => {
|
|
1082
|
+
if (typeof window === "undefined") return "discard";
|
|
1083
|
+
const msg = `${i18n?.title ?? "Unsaved changes"}
|
|
1084
|
+
|
|
1085
|
+
${i18n?.body ?? "OK to save & continue, Cancel to discard."}`;
|
|
1086
|
+
return window.confirm(msg) ? "save" : "discard";
|
|
1087
|
+
};
|
|
1088
|
+
var useDirtyNavigation = ({
|
|
1089
|
+
strategy,
|
|
1090
|
+
isDirty,
|
|
1091
|
+
save,
|
|
1092
|
+
reset,
|
|
1093
|
+
confirm,
|
|
1094
|
+
i18n
|
|
1095
|
+
}) => {
|
|
1096
|
+
const runWithGuard = useCallback(
|
|
1097
|
+
async (action) => {
|
|
1327
1098
|
if (!isDirty || strategy === "keep") {
|
|
1328
1099
|
action();
|
|
1329
1100
|
return true;
|
|
@@ -1495,61 +1266,22 @@ var useConfirmDialog = () => {
|
|
|
1495
1266
|
)
|
|
1496
1267
|
};
|
|
1497
1268
|
};
|
|
1498
|
-
var
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
};
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
}, []);
|
|
1515
|
-
const handleChoice = useCallback((choice) => {
|
|
1516
|
-
setOpen(false);
|
|
1517
|
-
const r = resolverRef.current;
|
|
1518
|
-
resolverRef.current = null;
|
|
1519
|
-
r?.(choice === "discard");
|
|
1520
|
-
}, []);
|
|
1521
|
-
return {
|
|
1522
|
-
confirm,
|
|
1523
|
-
dialog: /* @__PURE__ */ jsx(
|
|
1524
|
-
ConfirmDialog,
|
|
1525
|
-
{
|
|
1526
|
-
open,
|
|
1527
|
-
title: state.title,
|
|
1528
|
-
body: state.body,
|
|
1529
|
-
saveLabel: "",
|
|
1530
|
-
discardLabel: state.confirmLabel,
|
|
1531
|
-
cancelLabel: state.cancelLabel,
|
|
1532
|
-
onChoice: handleChoice
|
|
1533
|
-
}
|
|
1534
|
-
)
|
|
1535
|
-
};
|
|
1536
|
-
};
|
|
1537
|
-
var stores = /* @__PURE__ */ new Map();
|
|
1538
|
-
var storageKey = (key) => `ra:clipboard:${key}`;
|
|
1539
|
-
var getStore = (key) => {
|
|
1540
|
-
let s = stores.get(key);
|
|
1541
|
-
if (!s) {
|
|
1542
|
-
s = { entry: null, listeners: /* @__PURE__ */ new Set() };
|
|
1543
|
-
if (typeof window !== "undefined") {
|
|
1544
|
-
try {
|
|
1545
|
-
const raw = window.sessionStorage.getItem(storageKey(key));
|
|
1546
|
-
if (raw) s.entry = JSON.parse(raw);
|
|
1547
|
-
} catch {
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
stores.set(key, s);
|
|
1551
|
-
}
|
|
1552
|
-
return s;
|
|
1269
|
+
var stores = /* @__PURE__ */ new Map();
|
|
1270
|
+
var storageKey = (key) => `ra:clipboard:${key}`;
|
|
1271
|
+
var getStore = (key) => {
|
|
1272
|
+
let s = stores.get(key);
|
|
1273
|
+
if (!s) {
|
|
1274
|
+
s = { entry: null, listeners: /* @__PURE__ */ new Set() };
|
|
1275
|
+
if (typeof window !== "undefined") {
|
|
1276
|
+
try {
|
|
1277
|
+
const raw = window.sessionStorage.getItem(storageKey(key));
|
|
1278
|
+
if (raw) s.entry = JSON.parse(raw);
|
|
1279
|
+
} catch {
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
stores.set(key, s);
|
|
1283
|
+
}
|
|
1284
|
+
return s;
|
|
1553
1285
|
};
|
|
1554
1286
|
var persist = (key, entry) => {
|
|
1555
1287
|
if (typeof window === "undefined") return;
|
|
@@ -1611,6 +1343,256 @@ function cloneValue(value) {
|
|
|
1611
1343
|
}
|
|
1612
1344
|
}
|
|
1613
1345
|
}
|
|
1346
|
+
var DEFAULTS2 = {
|
|
1347
|
+
title: "Replace existing values?",
|
|
1348
|
+
body: "This destination already has saved values. Replace them?",
|
|
1349
|
+
confirmLabel: "Replace",
|
|
1350
|
+
cancelLabel: "Cancel"
|
|
1351
|
+
};
|
|
1352
|
+
var usePasteConfirm = () => {
|
|
1353
|
+
const [open, setOpen] = useState(false);
|
|
1354
|
+
const [state, setState] = useState(DEFAULTS2);
|
|
1355
|
+
const resolverRef = useRef(null);
|
|
1356
|
+
const confirm = useCallback((i18n) => {
|
|
1357
|
+
setState({ ...DEFAULTS2, ...i18n ?? {} });
|
|
1358
|
+
setOpen(true);
|
|
1359
|
+
return new Promise((resolve) => {
|
|
1360
|
+
resolverRef.current = resolve;
|
|
1361
|
+
});
|
|
1362
|
+
}, []);
|
|
1363
|
+
const handleChoice = useCallback((choice) => {
|
|
1364
|
+
setOpen(false);
|
|
1365
|
+
const r = resolverRef.current;
|
|
1366
|
+
resolverRef.current = null;
|
|
1367
|
+
r?.(choice === "discard");
|
|
1368
|
+
}, []);
|
|
1369
|
+
return {
|
|
1370
|
+
confirm,
|
|
1371
|
+
dialog: /* @__PURE__ */ jsx(
|
|
1372
|
+
ConfirmDialog,
|
|
1373
|
+
{
|
|
1374
|
+
open,
|
|
1375
|
+
title: state.title,
|
|
1376
|
+
body: state.body,
|
|
1377
|
+
saveLabel: "",
|
|
1378
|
+
discardLabel: state.confirmLabel,
|
|
1379
|
+
cancelLabel: state.cancelLabel,
|
|
1380
|
+
onChoice: handleChoice
|
|
1381
|
+
}
|
|
1382
|
+
)
|
|
1383
|
+
};
|
|
1384
|
+
};
|
|
1385
|
+
var ClipboardToast = ({ message, variant = "copy", onDismiss }) => {
|
|
1386
|
+
useEffect(() => {
|
|
1387
|
+
const t = window.setTimeout(onDismiss, 2500);
|
|
1388
|
+
return () => window.clearTimeout(t);
|
|
1389
|
+
}, [message, onDismiss]);
|
|
1390
|
+
const Icon = variant === "paste" ? ClipboardPaste : Copy;
|
|
1391
|
+
return /* @__PURE__ */ jsxs(
|
|
1392
|
+
"div",
|
|
1393
|
+
{
|
|
1394
|
+
role: "status",
|
|
1395
|
+
"aria-live": "polite",
|
|
1396
|
+
className: "ra-clipboard-toast",
|
|
1397
|
+
children: [
|
|
1398
|
+
/* @__PURE__ */ jsx(Icon, { className: "w-3.5 h-3.5 shrink-0", "aria-hidden": "true" }),
|
|
1399
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: message })
|
|
1400
|
+
]
|
|
1401
|
+
}
|
|
1402
|
+
);
|
|
1403
|
+
};
|
|
1404
|
+
function useShellClipboard(args) {
|
|
1405
|
+
const {
|
|
1406
|
+
enabled,
|
|
1407
|
+
appId,
|
|
1408
|
+
recordType,
|
|
1409
|
+
i18n,
|
|
1410
|
+
editorCtx,
|
|
1411
|
+
editingScope,
|
|
1412
|
+
editorHeaderLabel,
|
|
1413
|
+
isCollection,
|
|
1414
|
+
selectedItemId,
|
|
1415
|
+
selectedRecordId,
|
|
1416
|
+
resolved,
|
|
1417
|
+
onTelemetry,
|
|
1418
|
+
onCopyOverride,
|
|
1419
|
+
onPasteOverride,
|
|
1420
|
+
onLeftSelectRef
|
|
1421
|
+
} = args;
|
|
1422
|
+
const clipboard = useRecordClipboard({
|
|
1423
|
+
appId,
|
|
1424
|
+
recordType: recordType ?? "__default"
|
|
1425
|
+
});
|
|
1426
|
+
const pasteConfirm = usePasteConfirm();
|
|
1427
|
+
const [notice, setNotice] = useState(null);
|
|
1428
|
+
const copyCurrent = useCallback(() => {
|
|
1429
|
+
if (!enabled || !editingScope) return;
|
|
1430
|
+
const sourceValue = onCopyOverride ? onCopyOverride({ value: editorCtx.value, scope: editingScope }) : cloneValue(editorCtx.value);
|
|
1431
|
+
clipboard.set({
|
|
1432
|
+
value: sourceValue,
|
|
1433
|
+
sourceScope: editingScope,
|
|
1434
|
+
sourceRecordId: resolved.recordId,
|
|
1435
|
+
sourceLabel: editorHeaderLabel
|
|
1436
|
+
});
|
|
1437
|
+
onTelemetry?.({ type: "clipboard.copy", recordType, sourceRef: editingScope.raw });
|
|
1438
|
+
setNotice({
|
|
1439
|
+
message: i18n.copyToast.replace("{name}", editorHeaderLabel ?? editingScope.raw),
|
|
1440
|
+
variant: "copy"
|
|
1441
|
+
});
|
|
1442
|
+
}, [
|
|
1443
|
+
enabled,
|
|
1444
|
+
editingScope,
|
|
1445
|
+
onCopyOverride,
|
|
1446
|
+
editorCtx.value,
|
|
1447
|
+
clipboard,
|
|
1448
|
+
editorHeaderLabel,
|
|
1449
|
+
onTelemetry,
|
|
1450
|
+
recordType,
|
|
1451
|
+
i18n.copyToast,
|
|
1452
|
+
resolved.recordId
|
|
1453
|
+
]);
|
|
1454
|
+
const pasteCurrent = useCallback(async () => {
|
|
1455
|
+
if (!enabled || !editingScope || !clipboard.entry) return;
|
|
1456
|
+
const compat = checkPasteCompatibility(clipboard.entry.sourceScope, editingScope);
|
|
1457
|
+
if (compat.status === "denied") {
|
|
1458
|
+
setNotice({ message: compat.reason ?? "Paste not allowed here", variant: "paste" });
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
if (compat.status === "warn") {
|
|
1462
|
+
const ok = await pasteConfirm.confirm({
|
|
1463
|
+
title: i18n.pasteWarnTitle,
|
|
1464
|
+
body: compat.reason ?? "",
|
|
1465
|
+
confirmLabel: i18n.pasteWarnContinue,
|
|
1466
|
+
cancelLabel: i18n.pasteConfirmCancel
|
|
1467
|
+
});
|
|
1468
|
+
if (!ok) return;
|
|
1469
|
+
}
|
|
1470
|
+
const willReplace = resolved.source === "self";
|
|
1471
|
+
if (willReplace) {
|
|
1472
|
+
const ok = await pasteConfirm.confirm({
|
|
1473
|
+
title: i18n.pasteConfirmTitle,
|
|
1474
|
+
body: i18n.pasteConfirmBody.replace(
|
|
1475
|
+
"{destination}",
|
|
1476
|
+
editorHeaderLabel ?? editingScope.raw
|
|
1477
|
+
),
|
|
1478
|
+
confirmLabel: i18n.pasteConfirmReplace,
|
|
1479
|
+
cancelLabel: i18n.pasteConfirmCancel
|
|
1480
|
+
});
|
|
1481
|
+
if (!ok) return;
|
|
1482
|
+
}
|
|
1483
|
+
const sourceParsed = clipboard.entry.sourceScope;
|
|
1484
|
+
const transformed = onPasteOverride ? onPasteOverride(
|
|
1485
|
+
{ value: clipboard.entry.value, sourceScope: sourceParsed },
|
|
1486
|
+
{ scope: editingScope, currentValue: editorCtx.value ?? null }
|
|
1487
|
+
) : cloneValue(clipboard.entry.value);
|
|
1488
|
+
if (transformed === null) return;
|
|
1489
|
+
editorCtx.onChange(transformed);
|
|
1490
|
+
onTelemetry?.({
|
|
1491
|
+
type: "clipboard.paste",
|
|
1492
|
+
recordType,
|
|
1493
|
+
sourceRef: clipboard.entry.sourceScope.raw,
|
|
1494
|
+
destinationRef: editingScope.raw,
|
|
1495
|
+
replaced: willReplace
|
|
1496
|
+
});
|
|
1497
|
+
setNotice({
|
|
1498
|
+
message: i18n.pasteToast.replace("{name}", clipboard.entry.sourceLabel ?? clipboard.entry.sourceScope.raw),
|
|
1499
|
+
variant: "paste"
|
|
1500
|
+
});
|
|
1501
|
+
}, [
|
|
1502
|
+
enabled,
|
|
1503
|
+
editingScope,
|
|
1504
|
+
clipboard.entry,
|
|
1505
|
+
pasteConfirm,
|
|
1506
|
+
resolved.source,
|
|
1507
|
+
onPasteOverride,
|
|
1508
|
+
editorCtx,
|
|
1509
|
+
onTelemetry,
|
|
1510
|
+
recordType,
|
|
1511
|
+
editorHeaderLabel,
|
|
1512
|
+
i18n.pasteWarnTitle,
|
|
1513
|
+
i18n.pasteWarnContinue,
|
|
1514
|
+
i18n.pasteConfirmCancel,
|
|
1515
|
+
i18n.pasteConfirmTitle,
|
|
1516
|
+
i18n.pasteConfirmBody,
|
|
1517
|
+
i18n.pasteConfirmReplace,
|
|
1518
|
+
i18n.pasteToast
|
|
1519
|
+
]);
|
|
1520
|
+
const editorPasteCompat = useMemo(() => {
|
|
1521
|
+
if (!enabled || !clipboard.entry || !editingScope) return null;
|
|
1522
|
+
const sameRecord = clipboard.entry.sourceRecordId && resolved.recordId && clipboard.entry.sourceRecordId === resolved.recordId;
|
|
1523
|
+
const sameAnchor = !clipboard.entry.sourceRecordId && clipboard.entry.sourceScope.raw === editingScope.raw;
|
|
1524
|
+
if (sameRecord || sameAnchor) return { status: "denied" };
|
|
1525
|
+
return checkPasteCompatibility(clipboard.entry.sourceScope, editingScope);
|
|
1526
|
+
}, [enabled, clipboard.entry, editingScope, resolved.recordId]);
|
|
1527
|
+
const editorClipboard = enabled && editingScope ? {
|
|
1528
|
+
onCopy: copyCurrent,
|
|
1529
|
+
onPaste: () => {
|
|
1530
|
+
void pasteCurrent();
|
|
1531
|
+
},
|
|
1532
|
+
canCopy: true,
|
|
1533
|
+
canPaste: !!clipboard.entry && editorPasteCompat?.status !== "denied",
|
|
1534
|
+
pasteSourceLabel: clipboard.entry?.sourceLabel,
|
|
1535
|
+
pasteWillReplace: resolved.source === "self" && !!clipboard.entry
|
|
1536
|
+
} : void 0;
|
|
1537
|
+
const [pendingPasteTarget, setPendingPasteTarget] = useState(null);
|
|
1538
|
+
useEffect(() => {
|
|
1539
|
+
if (!pendingPasteTarget) return;
|
|
1540
|
+
if (!editingScope) return;
|
|
1541
|
+
const matched = pendingPasteTarget.kind === "record" ? (isCollection ? selectedItemId : selectedRecordId) === pendingPasteTarget.recordId : editingScope.raw === pendingPasteTarget.ref;
|
|
1542
|
+
if (!matched) return;
|
|
1543
|
+
const t = window.setTimeout(() => {
|
|
1544
|
+
setPendingPasteTarget(null);
|
|
1545
|
+
void pasteCurrent();
|
|
1546
|
+
}, 0);
|
|
1547
|
+
return () => window.clearTimeout(t);
|
|
1548
|
+
}, [pendingPasteTarget, editingScope, isCollection, selectedItemId, selectedRecordId, pasteCurrent]);
|
|
1549
|
+
const rowClipboard = enabled ? (record) => {
|
|
1550
|
+
const summaryHasData = record.data != null;
|
|
1551
|
+
const sourceParsed = record.scope;
|
|
1552
|
+
const compat = clipboard.entry ? checkPasteCompatibility(clipboard.entry.sourceScope, sourceParsed) : null;
|
|
1553
|
+
const sameTarget = clipboard.entry ? clipboard.entry.sourceRecordId && record.id ? clipboard.entry.sourceRecordId === record.id : clipboard.entry.sourceScope.raw === record.ref : false;
|
|
1554
|
+
return {
|
|
1555
|
+
onCopy: summaryHasData ? () => {
|
|
1556
|
+
const value = onCopyOverride ? onCopyOverride({ value: record.data, scope: sourceParsed }) : cloneValue(record.data);
|
|
1557
|
+
clipboard.set({
|
|
1558
|
+
value,
|
|
1559
|
+
sourceScope: sourceParsed,
|
|
1560
|
+
sourceRecordId: record.id ?? void 0,
|
|
1561
|
+
sourceLabel: record.label
|
|
1562
|
+
});
|
|
1563
|
+
onTelemetry?.({ type: "clipboard.copy", recordType, sourceRef: record.ref });
|
|
1564
|
+
setNotice({
|
|
1565
|
+
message: i18n.copyToast.replace("{name}", record.label),
|
|
1566
|
+
variant: "copy"
|
|
1567
|
+
});
|
|
1568
|
+
} : void 0,
|
|
1569
|
+
onPaste: () => {
|
|
1570
|
+
onLeftSelectRef.current?.(record);
|
|
1571
|
+
setPendingPasteTarget(
|
|
1572
|
+
record.id ? { kind: "record", recordId: record.id } : { kind: "anchor", ref: record.ref }
|
|
1573
|
+
);
|
|
1574
|
+
},
|
|
1575
|
+
canPaste: !!clipboard.entry && !sameTarget && compat?.status !== "denied",
|
|
1576
|
+
pasteWillReplace: record.status === "configured",
|
|
1577
|
+
clipboardSourceLabel: clipboard.entry?.sourceLabel
|
|
1578
|
+
};
|
|
1579
|
+
} : void 0;
|
|
1580
|
+
const toast = notice ? /* @__PURE__ */ jsx(
|
|
1581
|
+
ClipboardToast,
|
|
1582
|
+
{
|
|
1583
|
+
message: notice.message,
|
|
1584
|
+
variant: notice.variant,
|
|
1585
|
+
onDismiss: () => setNotice(null)
|
|
1586
|
+
}
|
|
1587
|
+
) : null;
|
|
1588
|
+
return {
|
|
1589
|
+
editorClipboard,
|
|
1590
|
+
rowClipboard,
|
|
1591
|
+
confirmDialog: pasteConfirm.dialog,
|
|
1592
|
+
toast,
|
|
1593
|
+
store: clipboard
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1614
1596
|
var LABELS = {
|
|
1615
1597
|
collection: "Global",
|
|
1616
1598
|
product: "Products",
|
|
@@ -2002,104 +1984,6 @@ var ProductList = RecordList;
|
|
|
2002
1984
|
var FacetList = RecordList;
|
|
2003
1985
|
var VariantList = RecordList;
|
|
2004
1986
|
var BatchList = RecordList;
|
|
2005
|
-
var EMPTY_RULE_FILTERS = { facetKeys: [], minClauses: null };
|
|
2006
|
-
var COMPLEXITY_THRESHOLDS = [3, 5, 10];
|
|
2007
|
-
var ruleOf = (r) => r.facetRule ?? null;
|
|
2008
|
-
function RuleFilterChips({ source, value, onChange }) {
|
|
2009
|
-
const facetKeyEntries = useMemo(() => {
|
|
2010
|
-
const counts = /* @__PURE__ */ new Map();
|
|
2011
|
-
for (const r of source) {
|
|
2012
|
-
const rule = ruleOf(r);
|
|
2013
|
-
if (!rule) continue;
|
|
2014
|
-
for (const c of rule.all ?? []) {
|
|
2015
|
-
counts.set(c.facetKey, (counts.get(c.facetKey) ?? 0) + 1);
|
|
2016
|
-
}
|
|
2017
|
-
}
|
|
2018
|
-
return Array.from(counts.entries()).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
2019
|
-
}, [source]);
|
|
2020
|
-
const maxClauses = useMemo(() => {
|
|
2021
|
-
let max = 0;
|
|
2022
|
-
for (const r of source) {
|
|
2023
|
-
const rule = ruleOf(r);
|
|
2024
|
-
if (!rule) continue;
|
|
2025
|
-
max = Math.max(max, rule.all?.length ?? 0);
|
|
2026
|
-
}
|
|
2027
|
-
return max;
|
|
2028
|
-
}, [source]);
|
|
2029
|
-
if (facetKeyEntries.length === 0 && maxClauses < 2) return null;
|
|
2030
|
-
const toggleKey = (key) => {
|
|
2031
|
-
const has = value.facetKeys.includes(key);
|
|
2032
|
-
const next = has ? value.facetKeys.filter((k) => k !== key) : [...value.facetKeys, key];
|
|
2033
|
-
onChange({ ...value, facetKeys: next });
|
|
2034
|
-
};
|
|
2035
|
-
const setMin = (n) => onChange({ ...value, minClauses: n });
|
|
2036
|
-
const hasAny = value.facetKeys.length > 0 || value.minClauses != null;
|
|
2037
|
-
return /* @__PURE__ */ jsxs("div", { className: "ra-rule-filters", children: [
|
|
2038
|
-
/* @__PURE__ */ jsx("div", { className: "ra-rule-filters-row", role: "group", "aria-label": "Filter rules by facet", children: facetKeyEntries.map(([key, count]) => {
|
|
2039
|
-
const active = value.facetKeys.includes(key);
|
|
2040
|
-
return /* @__PURE__ */ jsxs(
|
|
2041
|
-
"button",
|
|
2042
|
-
{
|
|
2043
|
-
type: "button",
|
|
2044
|
-
onClick: () => toggleKey(key),
|
|
2045
|
-
className: "ra-rule-filter-chip",
|
|
2046
|
-
"data-active": active ? "true" : "false",
|
|
2047
|
-
"aria-pressed": active,
|
|
2048
|
-
title: `Show rules using ${key}`,
|
|
2049
|
-
children: [
|
|
2050
|
-
/* @__PURE__ */ jsx("span", { className: "ra-rule-filter-chip-label", children: key }),
|
|
2051
|
-
/* @__PURE__ */ jsx("span", { className: "ra-rule-filter-chip-count", children: count })
|
|
2052
|
-
]
|
|
2053
|
-
},
|
|
2054
|
-
key
|
|
2055
|
-
);
|
|
2056
|
-
}) }),
|
|
2057
|
-
maxClauses >= 2 && /* @__PURE__ */ jsx("div", { className: "ra-rule-filters-row", role: "group", "aria-label": "Filter by clause count", children: COMPLEXITY_THRESHOLDS.filter((n) => maxClauses >= n).map((n) => {
|
|
2058
|
-
const active = value.minClauses === n;
|
|
2059
|
-
return /* @__PURE__ */ jsxs(
|
|
2060
|
-
"button",
|
|
2061
|
-
{
|
|
2062
|
-
type: "button",
|
|
2063
|
-
onClick: () => setMin(active ? null : n),
|
|
2064
|
-
className: "ra-rule-filter-chip",
|
|
2065
|
-
"data-tone": "complexity",
|
|
2066
|
-
"data-active": active ? "true" : "false",
|
|
2067
|
-
"aria-pressed": active,
|
|
2068
|
-
title: `Only rules with \u2265 ${n} facets`,
|
|
2069
|
-
children: [
|
|
2070
|
-
"\u2265 ",
|
|
2071
|
-
n,
|
|
2072
|
-
" facets"
|
|
2073
|
-
]
|
|
2074
|
-
},
|
|
2075
|
-
n
|
|
2076
|
-
);
|
|
2077
|
-
}) }),
|
|
2078
|
-
hasAny && /* @__PURE__ */ jsx(
|
|
2079
|
-
"button",
|
|
2080
|
-
{
|
|
2081
|
-
type: "button",
|
|
2082
|
-
onClick: () => onChange(EMPTY_RULE_FILTERS),
|
|
2083
|
-
className: "ra-rule-filter-clear",
|
|
2084
|
-
"aria-label": "Clear rule filters",
|
|
2085
|
-
children: "Clear filters"
|
|
2086
|
-
}
|
|
2087
|
-
)
|
|
2088
|
-
] });
|
|
2089
|
-
}
|
|
2090
|
-
function applyRuleFilters(items, filters) {
|
|
2091
|
-
if (filters.facetKeys.length === 0 && filters.minClauses == null) return items;
|
|
2092
|
-
return items.filter((r) => {
|
|
2093
|
-
const rule = ruleOf(r);
|
|
2094
|
-
if (!rule) return false;
|
|
2095
|
-
if (filters.minClauses != null && (rule.all?.length ?? 0) < filters.minClauses) return false;
|
|
2096
|
-
if (filters.facetKeys.length > 0) {
|
|
2097
|
-
const keys = new Set((rule.all ?? []).map((c) => c.facetKey));
|
|
2098
|
-
if (!filters.facetKeys.some((k) => keys.has(k))) return false;
|
|
2099
|
-
}
|
|
2100
|
-
return true;
|
|
2101
|
-
});
|
|
2102
|
-
}
|
|
2103
1987
|
var COLLAPSED_FACET_CAP = 6;
|
|
2104
1988
|
function FacetBrowseFilter({
|
|
2105
1989
|
facets,
|
|
@@ -2481,6 +2365,103 @@ var createDefaultDeepLinkAdapter = (paramNames) => ({
|
|
|
2481
2365
|
}
|
|
2482
2366
|
});
|
|
2483
2367
|
|
|
2368
|
+
// src/components/RecordsAdmin/data/postMessageDeepLinkAdapter.ts
|
|
2369
|
+
var CONTEXT_KEYS = [
|
|
2370
|
+
"dark",
|
|
2371
|
+
"appId",
|
|
2372
|
+
"collectionId",
|
|
2373
|
+
"productId",
|
|
2374
|
+
"proofId",
|
|
2375
|
+
"lang",
|
|
2376
|
+
"theme"
|
|
2377
|
+
];
|
|
2378
|
+
var findQueryString = (loc) => {
|
|
2379
|
+
if (loc.search && loc.search.length > 1) {
|
|
2380
|
+
return loc.search.startsWith("?") ? loc.search.slice(1) : loc.search;
|
|
2381
|
+
}
|
|
2382
|
+
if (loc.hash && loc.hash.includes("?")) {
|
|
2383
|
+
return loc.hash.slice(loc.hash.indexOf("?") + 1);
|
|
2384
|
+
}
|
|
2385
|
+
return "";
|
|
2386
|
+
};
|
|
2387
|
+
var findPath = (loc) => {
|
|
2388
|
+
if (loc.hash && loc.hash.startsWith("#")) {
|
|
2389
|
+
const hash = loc.hash.slice(1);
|
|
2390
|
+
const qIdx = hash.indexOf("?");
|
|
2391
|
+
const hashPath = qIdx >= 0 ? hash.slice(0, qIdx) : hash;
|
|
2392
|
+
if (hashPath) return hashPath;
|
|
2393
|
+
}
|
|
2394
|
+
return loc.pathname || "/";
|
|
2395
|
+
};
|
|
2396
|
+
var isInSmartLinksIframe = () => {
|
|
2397
|
+
if (typeof window === "undefined") return false;
|
|
2398
|
+
try {
|
|
2399
|
+
return window.parent != null && window.parent !== window;
|
|
2400
|
+
} catch {
|
|
2401
|
+
return true;
|
|
2402
|
+
}
|
|
2403
|
+
};
|
|
2404
|
+
var createPostMessageDeepLinkAdapter = (paramNames) => {
|
|
2405
|
+
const lastShellState = {};
|
|
2406
|
+
const post = (path) => {
|
|
2407
|
+
if (typeof window === "undefined") return;
|
|
2408
|
+
const params = new URLSearchParams(findQueryString(window.location));
|
|
2409
|
+
const context = {};
|
|
2410
|
+
const state = {};
|
|
2411
|
+
params.forEach((value, key) => {
|
|
2412
|
+
if (key === "theme") return;
|
|
2413
|
+
if (CONTEXT_KEYS.includes(key)) context[key] = value;
|
|
2414
|
+
else state[key] = value;
|
|
2415
|
+
});
|
|
2416
|
+
const overlay = (key, paramKey) => {
|
|
2417
|
+
const v = lastShellState[key];
|
|
2418
|
+
if (v == null || v === "") delete state[paramKey];
|
|
2419
|
+
else state[paramKey] = v;
|
|
2420
|
+
};
|
|
2421
|
+
overlay("recordId", paramNames.recordId);
|
|
2422
|
+
overlay("scope", paramNames.scope);
|
|
2423
|
+
overlay("view", paramNames.view);
|
|
2424
|
+
try {
|
|
2425
|
+
window.parent.postMessage({
|
|
2426
|
+
type: "smartlinks-route-change",
|
|
2427
|
+
path,
|
|
2428
|
+
context,
|
|
2429
|
+
state,
|
|
2430
|
+
appId: context.appId
|
|
2431
|
+
}, "*");
|
|
2432
|
+
} catch {
|
|
2433
|
+
}
|
|
2434
|
+
};
|
|
2435
|
+
return {
|
|
2436
|
+
read() {
|
|
2437
|
+
if (typeof window === "undefined") return {};
|
|
2438
|
+
const params = new URLSearchParams(findQueryString(window.location));
|
|
2439
|
+
return {
|
|
2440
|
+
recordId: params.get(paramNames.recordId),
|
|
2441
|
+
scope: params.get(paramNames.scope),
|
|
2442
|
+
view: params.get(paramNames.view)
|
|
2443
|
+
};
|
|
2444
|
+
},
|
|
2445
|
+
write(partial) {
|
|
2446
|
+
if (typeof window === "undefined") return;
|
|
2447
|
+
if ("recordId" in partial) lastShellState.recordId = partial.recordId ?? null;
|
|
2448
|
+
if ("scope" in partial) lastShellState.scope = partial.scope ?? null;
|
|
2449
|
+
if ("view" in partial) lastShellState.view = partial.view ?? null;
|
|
2450
|
+
post(findPath(window.location));
|
|
2451
|
+
},
|
|
2452
|
+
subscribe(listener) {
|
|
2453
|
+
if (typeof window === "undefined") return () => {
|
|
2454
|
+
};
|
|
2455
|
+
window.addEventListener("popstate", listener);
|
|
2456
|
+
window.addEventListener("hashchange", listener);
|
|
2457
|
+
return () => {
|
|
2458
|
+
window.removeEventListener("popstate", listener);
|
|
2459
|
+
window.removeEventListener("hashchange", listener);
|
|
2460
|
+
};
|
|
2461
|
+
}
|
|
2462
|
+
};
|
|
2463
|
+
};
|
|
2464
|
+
|
|
2484
2465
|
// src/components/RecordsAdmin/hooks/useDeepLinkState.ts
|
|
2485
2466
|
var SMART_PUSH = ["record.open", "record.close", "scope"];
|
|
2486
2467
|
var classify = (mode, kind) => {
|
|
@@ -2500,7 +2481,7 @@ function useDeepLinkState(options) {
|
|
|
2500
2481
|
if (!enabled) return null;
|
|
2501
2482
|
if (options?.adapter) return options.adapter;
|
|
2502
2483
|
if (!defaultAdapterRef.current) {
|
|
2503
|
-
defaultAdapterRef.current = createDefaultDeepLinkAdapter(paramNames);
|
|
2484
|
+
defaultAdapterRef.current = isInSmartLinksIframe() ? createPostMessageDeepLinkAdapter(paramNames) : createDefaultDeepLinkAdapter(paramNames);
|
|
2504
2485
|
}
|
|
2505
2486
|
return defaultAdapterRef.current;
|
|
2506
2487
|
}, [enabled, options?.adapter, paramNames]);
|
|
@@ -2874,6 +2855,49 @@ function RecordEditor({
|
|
|
2874
2855
|
)
|
|
2875
2856
|
] });
|
|
2876
2857
|
}
|
|
2858
|
+
var isFacetRuleValid = (rule) => {
|
|
2859
|
+
if (!rule || !Array.isArray(rule.all) || rule.all.length === 0) return false;
|
|
2860
|
+
return rule.all.every(
|
|
2861
|
+
(c) => !!c.facetKey && Array.isArray(c.anyOf) && c.anyOf.length > 0
|
|
2862
|
+
);
|
|
2863
|
+
};
|
|
2864
|
+
function useRulePreview(args) {
|
|
2865
|
+
const {
|
|
2866
|
+
SL,
|
|
2867
|
+
collectionId,
|
|
2868
|
+
appId,
|
|
2869
|
+
rule,
|
|
2870
|
+
limit = 20,
|
|
2871
|
+
debounceMs = 350,
|
|
2872
|
+
enabled = true
|
|
2873
|
+
} = args;
|
|
2874
|
+
const [debouncedRule, setDebouncedRule] = useState(rule);
|
|
2875
|
+
const timer = useRef(null);
|
|
2876
|
+
useEffect(() => {
|
|
2877
|
+
if (timer.current) clearTimeout(timer.current);
|
|
2878
|
+
timer.current = setTimeout(() => setDebouncedRule(rule), debounceMs);
|
|
2879
|
+
return () => {
|
|
2880
|
+
if (timer.current) clearTimeout(timer.current);
|
|
2881
|
+
};
|
|
2882
|
+
}, [rule, debounceMs]);
|
|
2883
|
+
const valid = isFacetRuleValid(debouncedRule);
|
|
2884
|
+
const query = useQuery({
|
|
2885
|
+
queryKey: ["records-admin", "preview-rule", collectionId, appId, debouncedRule, limit],
|
|
2886
|
+
enabled: enabled && !!collectionId && !!appId && valid,
|
|
2887
|
+
staleTime: 3e4,
|
|
2888
|
+
queryFn: () => SL.app.records.previewRule(collectionId, appId, {
|
|
2889
|
+
facetRule: debouncedRule,
|
|
2890
|
+
limit
|
|
2891
|
+
})
|
|
2892
|
+
});
|
|
2893
|
+
return {
|
|
2894
|
+
totalMatches: query.data?.total ?? null,
|
|
2895
|
+
sampleProductIds: (query.data?.matchingProducts ?? []).map((p) => p.productId),
|
|
2896
|
+
isLoading: query.isFetching,
|
|
2897
|
+
isStale: rule !== debouncedRule,
|
|
2898
|
+
error: query.error ?? null
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2877
2901
|
function summariseRule(clauses, facets) {
|
|
2878
2902
|
let valueCount = 0;
|
|
2879
2903
|
const parts = clauses.map((c) => {
|
|
@@ -4270,11 +4294,11 @@ var statusDot = (status) => {
|
|
|
4270
4294
|
}
|
|
4271
4295
|
};
|
|
4272
4296
|
var UnsavedTray = ({
|
|
4273
|
-
|
|
4297
|
+
items,
|
|
4274
4298
|
isSaving,
|
|
4275
4299
|
onSaveAll,
|
|
4276
4300
|
onDiscardAll,
|
|
4277
|
-
|
|
4301
|
+
onOpenItem,
|
|
4278
4302
|
onJumpToError,
|
|
4279
4303
|
saveLabel,
|
|
4280
4304
|
discardLabel,
|
|
@@ -4290,27 +4314,8 @@ var UnsavedTray = ({
|
|
|
4290
4314
|
const wrapRef = useRef(null);
|
|
4291
4315
|
const SI = SaveIcon ?? Save;
|
|
4292
4316
|
const DI = DiscardIcon ?? Undo2;
|
|
4293
|
-
const
|
|
4294
|
-
|
|
4295
|
-
for (const d of drafts) {
|
|
4296
|
-
const bucket = d.recordId || d.scopeRaw || d.key;
|
|
4297
|
-
const existing = byBucket.get(bucket);
|
|
4298
|
-
if (!existing) {
|
|
4299
|
-
byBucket.set(bucket, d);
|
|
4300
|
-
continue;
|
|
4301
|
-
}
|
|
4302
|
-
const existingSynthetic = existing.key.startsWith("draft:");
|
|
4303
|
-
const incomingSynthetic = d.key.startsWith("draft:");
|
|
4304
|
-
if (existingSynthetic && !incomingSynthetic) byBucket.set(bucket, d);
|
|
4305
|
-
}
|
|
4306
|
-
return Array.from(byBucket.values()).sort((a, b) => a.order - b.order);
|
|
4307
|
-
}, [drafts]);
|
|
4308
|
-
const liveDrafts = useMemo(
|
|
4309
|
-
() => uniqueDrafts.filter((d) => d.status !== "saved"),
|
|
4310
|
-
[uniqueDrafts]
|
|
4311
|
-
);
|
|
4312
|
-
const total = liveDrafts.length;
|
|
4313
|
-
const errors = liveDrafts.filter((d) => d.status === "error").length;
|
|
4317
|
+
const total = items.length;
|
|
4318
|
+
const errors = items.filter((i) => i.status === "error").length;
|
|
4314
4319
|
const isSingle = total === 1;
|
|
4315
4320
|
useEffect(() => {
|
|
4316
4321
|
if (!open) return;
|
|
@@ -4321,7 +4326,7 @@ var UnsavedTray = ({
|
|
|
4321
4326
|
return () => window.removeEventListener("mousedown", onDoc);
|
|
4322
4327
|
}, [open]);
|
|
4323
4328
|
if (total === 0) return null;
|
|
4324
|
-
const countLabel = isSingle ?
|
|
4329
|
+
const countLabel = isSingle ? items[0].label || "this record" : countTemplate.replace("{n}", String(total));
|
|
4325
4330
|
return /* @__PURE__ */ jsxs(
|
|
4326
4331
|
"div",
|
|
4327
4332
|
{
|
|
@@ -4391,14 +4396,14 @@ var UnsavedTray = ({
|
|
|
4391
4396
|
}
|
|
4392
4397
|
)
|
|
4393
4398
|
] }),
|
|
4394
|
-
open && !isSingle && /* @__PURE__ */ jsx("div", { className: "ra-unsaved-popover", role: "menu", children:
|
|
4399
|
+
open && !isSingle && /* @__PURE__ */ jsx("div", { className: "ra-unsaved-popover", role: "menu", children: items.map((it) => /* @__PURE__ */ jsxs(
|
|
4395
4400
|
"button",
|
|
4396
4401
|
{
|
|
4397
4402
|
type: "button",
|
|
4398
4403
|
className: "ra-unsaved-popover-row",
|
|
4399
4404
|
onClick: () => {
|
|
4400
4405
|
setOpen(false);
|
|
4401
|
-
|
|
4406
|
+
onOpenItem?.(it);
|
|
4402
4407
|
},
|
|
4403
4408
|
role: "menuitem",
|
|
4404
4409
|
children: [
|
|
@@ -4406,25 +4411,93 @@ var UnsavedTray = ({
|
|
|
4406
4411
|
"span",
|
|
4407
4412
|
{
|
|
4408
4413
|
className: "ra-unsaved-popover-dot",
|
|
4409
|
-
style: { background: statusDot(
|
|
4414
|
+
style: { background: statusDot(it.status) },
|
|
4410
4415
|
"aria-hidden": "true"
|
|
4411
4416
|
}
|
|
4412
4417
|
),
|
|
4413
|
-
/* @__PURE__ */ jsx("span", { className: "ra-unsaved-popover-label", children:
|
|
4414
|
-
|
|
4415
|
-
d.status === "error" && /* @__PURE__ */ jsx("span", { className: "ra-unsaved-popover-err", children: "failed" })
|
|
4418
|
+
/* @__PURE__ */ jsx("span", { className: "ra-unsaved-popover-label", children: it.label || "Default" }),
|
|
4419
|
+
it.status === "error" && /* @__PURE__ */ jsx("span", { className: "ra-unsaved-popover-err", children: "failed" })
|
|
4416
4420
|
]
|
|
4417
4421
|
},
|
|
4418
|
-
|
|
4422
|
+
it.editorId
|
|
4419
4423
|
)) })
|
|
4420
4424
|
]
|
|
4421
4425
|
}
|
|
4422
4426
|
);
|
|
4423
4427
|
};
|
|
4428
|
+
var HEADER_PILL_OVERFLOW_THRESHOLD = 5;
|
|
4429
|
+
function canRenderInHeader(items) {
|
|
4430
|
+
if (items.length === 0) return false;
|
|
4431
|
+
if (items.length >= HEADER_PILL_OVERFLOW_THRESHOLD) return false;
|
|
4432
|
+
if (items.some((i) => i.status === "error")) return false;
|
|
4433
|
+
return true;
|
|
4434
|
+
}
|
|
4435
|
+
var HeaderUnsavedPill = ({
|
|
4436
|
+
items,
|
|
4437
|
+
isSaving,
|
|
4438
|
+
onSaveAll,
|
|
4439
|
+
onDiscardAll,
|
|
4440
|
+
saveLabel,
|
|
4441
|
+
discardLabel,
|
|
4442
|
+
saveAllLabel,
|
|
4443
|
+
discardAllLabel,
|
|
4444
|
+
countTemplate,
|
|
4445
|
+
SaveIcon,
|
|
4446
|
+
DiscardIcon
|
|
4447
|
+
}) => {
|
|
4448
|
+
const SI = SaveIcon ?? Save;
|
|
4449
|
+
const DI = DiscardIcon ?? Undo2;
|
|
4450
|
+
if (items.length === 0) return null;
|
|
4451
|
+
const total = items.length;
|
|
4452
|
+
const isSingle = total === 1;
|
|
4453
|
+
return /* @__PURE__ */ jsxs(
|
|
4454
|
+
"div",
|
|
4455
|
+
{
|
|
4456
|
+
className: "ra-unsaved-pill",
|
|
4457
|
+
role: "status",
|
|
4458
|
+
"aria-live": "polite",
|
|
4459
|
+
onClick: (e) => e.stopPropagation(),
|
|
4460
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
4461
|
+
onTouchStart: (e) => e.stopPropagation(),
|
|
4462
|
+
children: [
|
|
4463
|
+
!isSingle && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4464
|
+
/* @__PURE__ */ jsx("span", { className: "ra-unsaved-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(AlertCircle, { className: "w-3.5 h-3.5" }) }),
|
|
4465
|
+
/* @__PURE__ */ jsx("span", { className: "ra-unsaved-pill-text", children: countTemplate.replace("{n}", String(total)) })
|
|
4466
|
+
] }),
|
|
4467
|
+
/* @__PURE__ */ jsxs(
|
|
4468
|
+
"button",
|
|
4469
|
+
{
|
|
4470
|
+
type: "button",
|
|
4471
|
+
className: "ra-unsaved-btn ra-unsaved-btn-ghost",
|
|
4472
|
+
onClick: onDiscardAll,
|
|
4473
|
+
disabled: isSaving,
|
|
4474
|
+
children: [
|
|
4475
|
+
/* @__PURE__ */ jsx(DI, { className: "w-3 h-3" }),
|
|
4476
|
+
isSingle ? discardLabel : discardAllLabel
|
|
4477
|
+
]
|
|
4478
|
+
}
|
|
4479
|
+
),
|
|
4480
|
+
/* @__PURE__ */ jsxs(
|
|
4481
|
+
"button",
|
|
4482
|
+
{
|
|
4483
|
+
type: "button",
|
|
4484
|
+
className: "ra-unsaved-btn ra-unsaved-btn-primary",
|
|
4485
|
+
onClick: onSaveAll,
|
|
4486
|
+
disabled: isSaving,
|
|
4487
|
+
children: [
|
|
4488
|
+
/* @__PURE__ */ jsx(SI, { className: "w-3 h-3" }),
|
|
4489
|
+
isSingle ? saveLabel : saveAllLabel
|
|
4490
|
+
]
|
|
4491
|
+
}
|
|
4492
|
+
)
|
|
4493
|
+
]
|
|
4494
|
+
}
|
|
4495
|
+
);
|
|
4496
|
+
};
|
|
4424
4497
|
var SaveAllProgress = ({
|
|
4425
4498
|
open,
|
|
4426
|
-
|
|
4427
|
-
|
|
4499
|
+
items,
|
|
4500
|
+
saveOne,
|
|
4428
4501
|
onClose,
|
|
4429
4502
|
onJumpToError,
|
|
4430
4503
|
i18n
|
|
@@ -4432,42 +4505,44 @@ var SaveAllProgress = ({
|
|
|
4432
4505
|
const [running, setRunning] = useState(false);
|
|
4433
4506
|
const [done, setDone] = useState(false);
|
|
4434
4507
|
const stopRef = useRef(false);
|
|
4435
|
-
const [
|
|
4508
|
+
const [batchIds, setBatchIds] = useState([]);
|
|
4436
4509
|
useEffect(() => {
|
|
4437
4510
|
if (!open) {
|
|
4438
4511
|
setRunning(false);
|
|
4439
4512
|
setDone(false);
|
|
4440
4513
|
stopRef.current = false;
|
|
4441
|
-
|
|
4514
|
+
setBatchIds([]);
|
|
4442
4515
|
return;
|
|
4443
4516
|
}
|
|
4444
|
-
const initial =
|
|
4445
|
-
|
|
4517
|
+
const initial = items.map((i) => i.editorId);
|
|
4518
|
+
setBatchIds(initial);
|
|
4446
4519
|
void runBatch(initial);
|
|
4447
4520
|
}, [open]);
|
|
4448
|
-
const runBatch = async (
|
|
4521
|
+
const runBatch = async (ids) => {
|
|
4449
4522
|
setRunning(true);
|
|
4450
4523
|
setDone(false);
|
|
4451
4524
|
stopRef.current = false;
|
|
4452
|
-
for (const
|
|
4525
|
+
for (const id of ids) {
|
|
4453
4526
|
if (stopRef.current) break;
|
|
4454
|
-
const live = store.get(item.key);
|
|
4455
|
-
if (!live) continue;
|
|
4456
|
-
if (live.status === "saved") continue;
|
|
4457
|
-
store.setStatus(live.key, "saving");
|
|
4458
4527
|
try {
|
|
4459
|
-
await
|
|
4460
|
-
|
|
4461
|
-
} catch (err) {
|
|
4462
|
-
store.setStatus(live.key, "error", err);
|
|
4528
|
+
await saveOne(id);
|
|
4529
|
+
} catch {
|
|
4463
4530
|
}
|
|
4464
4531
|
}
|
|
4465
4532
|
setRunning(false);
|
|
4466
4533
|
setDone(true);
|
|
4467
4534
|
};
|
|
4468
|
-
const visible =
|
|
4535
|
+
const visible = useMemo(() => batchIds.map((id) => {
|
|
4536
|
+
const live = items.find((i) => i.editorId === id);
|
|
4537
|
+
return live ?? {
|
|
4538
|
+
editorId: id,
|
|
4539
|
+
label: "",
|
|
4540
|
+
status: "saved",
|
|
4541
|
+
scope: { kind: "collection", raw: "" }
|
|
4542
|
+
};
|
|
4543
|
+
}), [batchIds, items]);
|
|
4469
4544
|
const total = visible.length;
|
|
4470
|
-
const completed = visible.filter((d) => d.status
|
|
4545
|
+
const completed = visible.filter((d) => d.status !== "dirty" && d.status !== "saving").length;
|
|
4471
4546
|
const errors = visible.filter((d) => d.status === "error");
|
|
4472
4547
|
const successAll = done && errors.length === 0 && completed === total;
|
|
4473
4548
|
useEffect(() => {
|
|
@@ -4516,7 +4591,7 @@ var SaveAllProgress = ({
|
|
|
4516
4591
|
] }),
|
|
4517
4592
|
/* @__PURE__ */ jsx("span", { className: "ra-saveall-label", children: d.label || "Default" }),
|
|
4518
4593
|
d.status === "error" && /* @__PURE__ */ jsx("span", { className: "ra-saveall-err", title: String(d.error?.message ?? d.error ?? ""), children: d.error?.message ?? "Save failed" })
|
|
4519
|
-
] }, d.
|
|
4594
|
+
] }, d.editorId)) }),
|
|
4520
4595
|
/* @__PURE__ */ jsxs("div", { className: "ra-saveall-actions", children: [
|
|
4521
4596
|
running && /* @__PURE__ */ jsx(
|
|
4522
4597
|
"button",
|
|
@@ -4547,7 +4622,7 @@ var SaveAllProgress = ({
|
|
|
4547
4622
|
{
|
|
4548
4623
|
type: "button",
|
|
4549
4624
|
className: "ra-unsaved-btn ra-unsaved-btn-primary",
|
|
4550
|
-
onClick: () => void runBatch(errors),
|
|
4625
|
+
onClick: () => void runBatch(errors.map((e) => e.editorId)),
|
|
4551
4626
|
children: i18n.retryFailed
|
|
4552
4627
|
}
|
|
4553
4628
|
)
|
|
@@ -4568,30 +4643,683 @@ var SaveAllProgress = ({
|
|
|
4568
4643
|
}
|
|
4569
4644
|
);
|
|
4570
4645
|
};
|
|
4571
|
-
var ClipboardToast = ({ message, variant = "copy", onDismiss }) => {
|
|
4572
|
-
useEffect(() => {
|
|
4573
|
-
const t = window.setTimeout(onDismiss, 2500);
|
|
4574
|
-
return () => window.clearTimeout(t);
|
|
4575
|
-
}, [message, onDismiss]);
|
|
4576
|
-
const Icon = variant === "paste" ? ClipboardPaste : Copy;
|
|
4577
|
-
return /* @__PURE__ */ jsxs(
|
|
4578
|
-
"div",
|
|
4579
|
-
{
|
|
4580
|
-
role: "status",
|
|
4581
|
-
"aria-live": "polite",
|
|
4582
|
-
className: "ra-clipboard-toast",
|
|
4583
|
-
children: [
|
|
4584
|
-
/* @__PURE__ */ jsx(Icon, { className: "w-3.5 h-3.5 shrink-0", "aria-hidden": "true" }),
|
|
4585
|
-
/* @__PURE__ */ jsx("span", { className: "truncate", children: message })
|
|
4586
|
-
]
|
|
4587
|
-
}
|
|
4588
|
-
);
|
|
4589
|
-
};
|
|
4590
4646
|
|
|
4591
|
-
// src/components/RecordsAdmin/
|
|
4592
|
-
var
|
|
4593
|
-
if (
|
|
4594
|
-
|
|
4647
|
+
// src/components/RecordsAdmin/editor-session/editorStore.ts
|
|
4648
|
+
var isEqual = (a, b) => {
|
|
4649
|
+
if (a === b) return true;
|
|
4650
|
+
try {
|
|
4651
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
4652
|
+
} catch {
|
|
4653
|
+
return false;
|
|
4654
|
+
}
|
|
4655
|
+
};
|
|
4656
|
+
var cloneDeep = (v) => {
|
|
4657
|
+
if (v == null) return v;
|
|
4658
|
+
try {
|
|
4659
|
+
return structuredClone(v);
|
|
4660
|
+
} catch {
|
|
4661
|
+
return JSON.parse(JSON.stringify(v));
|
|
4662
|
+
}
|
|
4663
|
+
};
|
|
4664
|
+
var TITLE_KEYS = ["title", "name", "label", "heading", "question", "slug"];
|
|
4665
|
+
var TITLE_WRAPPERS = ["display", "content", "meta", "data"];
|
|
4666
|
+
var deriveDefaultLabel = (value, spec) => {
|
|
4667
|
+
const pickString = (obj) => {
|
|
4668
|
+
if (!obj || typeof obj !== "object") return void 0;
|
|
4669
|
+
for (const key of TITLE_KEYS) {
|
|
4670
|
+
const raw = obj[key];
|
|
4671
|
+
if (typeof raw === "string" && raw.trim()) return raw.trim();
|
|
4672
|
+
}
|
|
4673
|
+
return void 0;
|
|
4674
|
+
};
|
|
4675
|
+
const v = value;
|
|
4676
|
+
const top = pickString(v);
|
|
4677
|
+
if (top) return top;
|
|
4678
|
+
if (v && typeof v === "object") {
|
|
4679
|
+
for (const wrapper of TITLE_WRAPPERS) {
|
|
4680
|
+
const nested = pickString(v[wrapper]);
|
|
4681
|
+
if (nested) return nested;
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
if (spec.scope.raw?.startsWith("item:")) return "Untitled item";
|
|
4685
|
+
if (spec.scope.kind === "rule") return "Rule";
|
|
4686
|
+
if (spec.scope.kind && spec.scope.kind !== "collection") {
|
|
4687
|
+
return spec.scope.kind.charAt(0).toUpperCase() + spec.scope.kind.slice(1);
|
|
4688
|
+
}
|
|
4689
|
+
return "Default";
|
|
4690
|
+
};
|
|
4691
|
+
var editorIdCounter = 0;
|
|
4692
|
+
var mintEditorId = () => {
|
|
4693
|
+
editorIdCounter += 1;
|
|
4694
|
+
return `ed_${Date.now().toString(36)}_${editorIdCounter.toString(36)}`;
|
|
4695
|
+
};
|
|
4696
|
+
var recomputePinned = (e) => {
|
|
4697
|
+
if (e.status === "saving" || e.status === "error") return true;
|
|
4698
|
+
if (e.status === "dirty") return true;
|
|
4699
|
+
return false;
|
|
4700
|
+
};
|
|
4701
|
+
var recomputeStatus = (e) => {
|
|
4702
|
+
if (e.status === "saving") return "saving";
|
|
4703
|
+
if (e.status === "error") return "error";
|
|
4704
|
+
const valueDiff = !isEqual(e.value, e.baseline);
|
|
4705
|
+
const ruleDiff = !isEqual(e.facetRule, e.baselineFacetRule);
|
|
4706
|
+
if (valueDiff || ruleDiff) return "dirty";
|
|
4707
|
+
return e.status === "saved" ? "saved" : "idle";
|
|
4708
|
+
};
|
|
4709
|
+
var createEditorStore = () => {
|
|
4710
|
+
const map = /* @__PURE__ */ new Map();
|
|
4711
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
4712
|
+
let cachedList = [];
|
|
4713
|
+
let nextOrder = 0;
|
|
4714
|
+
const recompute = () => {
|
|
4715
|
+
cachedList = Array.from(map.values()).sort((a, b) => a.order - b.order);
|
|
4716
|
+
};
|
|
4717
|
+
const emit = () => {
|
|
4718
|
+
recompute();
|
|
4719
|
+
listeners.forEach((l) => l());
|
|
4720
|
+
};
|
|
4721
|
+
const update = (editorId, mut) => {
|
|
4722
|
+
const prev = map.get(editorId);
|
|
4723
|
+
if (!prev) return false;
|
|
4724
|
+
const draft = { ...prev };
|
|
4725
|
+
const result = mut(draft);
|
|
4726
|
+
const next = result ?? draft;
|
|
4727
|
+
next.status = recomputeStatus(next);
|
|
4728
|
+
next.pinned = recomputePinned(next);
|
|
4729
|
+
map.set(editorId, next);
|
|
4730
|
+
emit();
|
|
4731
|
+
return true;
|
|
4732
|
+
};
|
|
4733
|
+
return {
|
|
4734
|
+
list: () => cachedList,
|
|
4735
|
+
get: (editorId) => map.get(editorId),
|
|
4736
|
+
findExistingEditorIdFor(spec) {
|
|
4737
|
+
if (spec.recordId) {
|
|
4738
|
+
for (const entry of map.values()) {
|
|
4739
|
+
if (entry.recordId === spec.recordId) return entry.editorId;
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4742
|
+
const wantCreate = !!spec.createMode;
|
|
4743
|
+
for (const entry of map.values()) {
|
|
4744
|
+
if (entry.recordId) continue;
|
|
4745
|
+
if (entry.spec.scope.raw !== spec.scope.raw) continue;
|
|
4746
|
+
if (!!entry.spec.createMode !== wantCreate) continue;
|
|
4747
|
+
return entry.editorId;
|
|
4748
|
+
}
|
|
4749
|
+
return void 0;
|
|
4750
|
+
},
|
|
4751
|
+
open(input) {
|
|
4752
|
+
const editorId = input.editorId ?? mintEditorId();
|
|
4753
|
+
if (map.has(editorId)) return editorId;
|
|
4754
|
+
const seedValue = input.seed?.value ?? input.defaultValue;
|
|
4755
|
+
const seedFacetRule = input.seed?.facetRule ?? input.spec.initialFacetRule ?? null;
|
|
4756
|
+
const source = input.seed?.source ?? "empty";
|
|
4757
|
+
const value = cloneDeep(seedValue);
|
|
4758
|
+
const baseline = cloneDeep(seedValue);
|
|
4759
|
+
const entry = {
|
|
4760
|
+
editorId,
|
|
4761
|
+
spec: input.spec,
|
|
4762
|
+
saveSpec: input.saveSpec,
|
|
4763
|
+
value,
|
|
4764
|
+
baseline,
|
|
4765
|
+
facetRule: seedFacetRule,
|
|
4766
|
+
baselineFacetRule: seedFacetRule,
|
|
4767
|
+
status: "idle",
|
|
4768
|
+
source,
|
|
4769
|
+
recordId: input.seed?.recordId ?? input.spec.recordId,
|
|
4770
|
+
parentValue: input.seed?.parentValue ?? null,
|
|
4771
|
+
label: input.label ?? input.spec.label ?? deriveDefaultLabel(value, input.spec),
|
|
4772
|
+
order: nextOrder++,
|
|
4773
|
+
lastActiveAt: Date.now(),
|
|
4774
|
+
pinned: false,
|
|
4775
|
+
mounted: false
|
|
4776
|
+
};
|
|
4777
|
+
map.set(editorId, entry);
|
|
4778
|
+
emit();
|
|
4779
|
+
return editorId;
|
|
4780
|
+
},
|
|
4781
|
+
close(editorId) {
|
|
4782
|
+
const entry = map.get(editorId);
|
|
4783
|
+
if (!entry) return false;
|
|
4784
|
+
if (entry.pinned) return false;
|
|
4785
|
+
map.delete(editorId);
|
|
4786
|
+
emit();
|
|
4787
|
+
return true;
|
|
4788
|
+
},
|
|
4789
|
+
clearUnpinned() {
|
|
4790
|
+
let changed = false;
|
|
4791
|
+
for (const [id, entry] of map.entries()) {
|
|
4792
|
+
if (!entry.pinned) {
|
|
4793
|
+
map.delete(id);
|
|
4794
|
+
changed = true;
|
|
4795
|
+
}
|
|
4796
|
+
}
|
|
4797
|
+
if (changed) emit();
|
|
4798
|
+
},
|
|
4799
|
+
setValue(editorId, next) {
|
|
4800
|
+
update(editorId, (e) => {
|
|
4801
|
+
const prev = e.value;
|
|
4802
|
+
const resolved = typeof next === "function" ? next(prev) : next;
|
|
4803
|
+
e.value = resolved;
|
|
4804
|
+
if (e.status === "saved" || e.status === "error") {
|
|
4805
|
+
e.status = "idle";
|
|
4806
|
+
e.error = void 0;
|
|
4807
|
+
}
|
|
4808
|
+
if (!e.spec.label) {
|
|
4809
|
+
e.label = deriveDefaultLabel(e.value, e.spec);
|
|
4810
|
+
}
|
|
4811
|
+
});
|
|
4812
|
+
},
|
|
4813
|
+
setFacetRule(editorId, next) {
|
|
4814
|
+
update(editorId, (e) => {
|
|
4815
|
+
e.facetRule = next;
|
|
4816
|
+
if (e.status === "saved" || e.status === "error") {
|
|
4817
|
+
e.status = "idle";
|
|
4818
|
+
e.error = void 0;
|
|
4819
|
+
}
|
|
4820
|
+
});
|
|
4821
|
+
},
|
|
4822
|
+
hydrateFromResolver(editorId, resolved) {
|
|
4823
|
+
const entry = map.get(editorId);
|
|
4824
|
+
if (!entry) return;
|
|
4825
|
+
if (entry.status === "dirty" || entry.status === "saving" || entry.status === "error") return;
|
|
4826
|
+
if (entry.status === "saved") {
|
|
4827
|
+
update(editorId, (e) => {
|
|
4828
|
+
e.source = resolved.source;
|
|
4829
|
+
if (resolved.recordId) e.recordId = resolved.recordId;
|
|
4830
|
+
e.parentValue = resolved.parentValue ?? null;
|
|
4831
|
+
});
|
|
4832
|
+
return;
|
|
4833
|
+
}
|
|
4834
|
+
update(editorId, (e) => {
|
|
4835
|
+
const fresh = resolved.data == null ? e.value : cloneDeep(resolved.data);
|
|
4836
|
+
e.value = fresh;
|
|
4837
|
+
e.baseline = cloneDeep(fresh);
|
|
4838
|
+
const rule = resolved.facetRule ?? e.facetRule;
|
|
4839
|
+
e.facetRule = rule;
|
|
4840
|
+
e.baselineFacetRule = rule;
|
|
4841
|
+
e.source = resolved.source;
|
|
4842
|
+
if (resolved.recordId) e.recordId = resolved.recordId;
|
|
4843
|
+
e.parentValue = resolved.parentValue ?? null;
|
|
4844
|
+
if (!e.spec.label) e.label = deriveDefaultLabel(fresh, e.spec);
|
|
4845
|
+
});
|
|
4846
|
+
},
|
|
4847
|
+
markMounted(editorId) {
|
|
4848
|
+
update(editorId, (e) => {
|
|
4849
|
+
e.mounted = true;
|
|
4850
|
+
});
|
|
4851
|
+
},
|
|
4852
|
+
markActive(editorId) {
|
|
4853
|
+
update(editorId, (e) => {
|
|
4854
|
+
e.lastActiveAt = Date.now();
|
|
4855
|
+
});
|
|
4856
|
+
},
|
|
4857
|
+
async save(editorId) {
|
|
4858
|
+
const entry = map.get(editorId);
|
|
4859
|
+
if (!entry) return;
|
|
4860
|
+
if (entry.status !== "dirty" && entry.status !== "error") return;
|
|
4861
|
+
const persistedValue = entry.value;
|
|
4862
|
+
const persistedFacetRule = entry.facetRule;
|
|
4863
|
+
const { ctx, anchors } = entry.saveSpec;
|
|
4864
|
+
const spec = entry.spec;
|
|
4865
|
+
update(editorId, (e) => {
|
|
4866
|
+
e.status = "saving";
|
|
4867
|
+
e.error = void 0;
|
|
4868
|
+
});
|
|
4869
|
+
try {
|
|
4870
|
+
let nextRecordId = entry.recordId;
|
|
4871
|
+
if (entry.recordId && entry.source === "self") {
|
|
4872
|
+
await updateRecord(ctx, entry.recordId, {
|
|
4873
|
+
data: persistedValue,
|
|
4874
|
+
facetRule: persistedFacetRule
|
|
4875
|
+
});
|
|
4876
|
+
} else if (spec.createMode) {
|
|
4877
|
+
const created = await createRecord(ctx, {
|
|
4878
|
+
ref: spec.scope.kind === "rule" && spec.scope.raw ? spec.scope.raw : spec.ref,
|
|
4879
|
+
scope: anchors,
|
|
4880
|
+
data: persistedValue,
|
|
4881
|
+
facetRule: persistedFacetRule
|
|
4882
|
+
});
|
|
4883
|
+
nextRecordId = created?.id ?? nextRecordId;
|
|
4884
|
+
} else {
|
|
4885
|
+
const upserted = await upsertRecord(ctx, {
|
|
4886
|
+
ref: spec.scope.kind === "rule" && spec.scope.raw ? spec.scope.raw : spec.ref,
|
|
4887
|
+
scope: anchors,
|
|
4888
|
+
data: persistedValue,
|
|
4889
|
+
facetRule: persistedFacetRule
|
|
4890
|
+
});
|
|
4891
|
+
nextRecordId = upserted.record?.id ?? nextRecordId;
|
|
4892
|
+
}
|
|
4893
|
+
update(editorId, (e) => {
|
|
4894
|
+
e.baseline = cloneDeep(persistedValue);
|
|
4895
|
+
e.baselineFacetRule = persistedFacetRule;
|
|
4896
|
+
e.recordId = nextRecordId;
|
|
4897
|
+
e.source = "self";
|
|
4898
|
+
e.status = "saved";
|
|
4899
|
+
e.error = void 0;
|
|
4900
|
+
});
|
|
4901
|
+
} catch (err) {
|
|
4902
|
+
update(editorId, (e) => {
|
|
4903
|
+
e.status = "error";
|
|
4904
|
+
e.error = err;
|
|
4905
|
+
});
|
|
4906
|
+
throw err;
|
|
4907
|
+
}
|
|
4908
|
+
},
|
|
4909
|
+
reset(editorId) {
|
|
4910
|
+
update(editorId, (e) => {
|
|
4911
|
+
e.value = cloneDeep(e.baseline);
|
|
4912
|
+
e.facetRule = e.baselineFacetRule;
|
|
4913
|
+
e.status = "idle";
|
|
4914
|
+
e.error = void 0;
|
|
4915
|
+
});
|
|
4916
|
+
},
|
|
4917
|
+
async remove(editorId) {
|
|
4918
|
+
const entry = map.get(editorId);
|
|
4919
|
+
if (!entry) return;
|
|
4920
|
+
if (entry.source !== "self") return;
|
|
4921
|
+
if (!entry.recordId) return;
|
|
4922
|
+
await removeRecord(entry.saveSpec.ctx, entry.recordId);
|
|
4923
|
+
map.delete(editorId);
|
|
4924
|
+
emit();
|
|
4925
|
+
},
|
|
4926
|
+
enforcePoolLimit(max, exceptEditorId) {
|
|
4927
|
+
const alive = Array.from(map.values());
|
|
4928
|
+
if (alive.length <= max) return;
|
|
4929
|
+
const evictable = alive.filter((e) => !e.pinned && e.editorId !== exceptEditorId).sort((a, b) => a.lastActiveAt - b.lastActiveAt);
|
|
4930
|
+
let toEvict = alive.length - max;
|
|
4931
|
+
let changed = false;
|
|
4932
|
+
for (const entry of evictable) {
|
|
4933
|
+
if (toEvict <= 0) break;
|
|
4934
|
+
map.delete(entry.editorId);
|
|
4935
|
+
toEvict -= 1;
|
|
4936
|
+
changed = true;
|
|
4937
|
+
}
|
|
4938
|
+
if (changed) emit();
|
|
4939
|
+
},
|
|
4940
|
+
subscribe(listener) {
|
|
4941
|
+
listeners.add(listener);
|
|
4942
|
+
return () => {
|
|
4943
|
+
listeners.delete(listener);
|
|
4944
|
+
};
|
|
4945
|
+
}
|
|
4946
|
+
};
|
|
4947
|
+
};
|
|
4948
|
+
var buildSaveSpec = (ctx, spec) => ({
|
|
4949
|
+
ctx,
|
|
4950
|
+
anchors: parsedRefToScope(spec.scope)
|
|
4951
|
+
});
|
|
4952
|
+
var EditorSessionContext = createContext(null);
|
|
4953
|
+
var useEditorSessionContext = () => {
|
|
4954
|
+
const ctx = useContext(EditorSessionContext);
|
|
4955
|
+
if (!ctx) {
|
|
4956
|
+
throw new Error(
|
|
4957
|
+
"[smartlinks-ui] useEditorSession / useEditorSelection / useDirtyOverview must be used inside <EditorSessionProvider> (RecordsAdminShell mounts one for you)."
|
|
4958
|
+
);
|
|
4959
|
+
}
|
|
4960
|
+
return ctx;
|
|
4961
|
+
};
|
|
4962
|
+
var EditorSessionProvider = ({
|
|
4963
|
+
ctx,
|
|
4964
|
+
children,
|
|
4965
|
+
maxOpenEditors = 8,
|
|
4966
|
+
defaultValueFactory
|
|
4967
|
+
}) => {
|
|
4968
|
+
const storeRef = useRef(null);
|
|
4969
|
+
if (!storeRef.current) storeRef.current = createEditorStore();
|
|
4970
|
+
const currentRef = useRef(void 0);
|
|
4971
|
+
const listenersRef = useRef(/* @__PURE__ */ new Set());
|
|
4972
|
+
const subscribeCurrent = useCallback((cb) => {
|
|
4973
|
+
listenersRef.current.add(cb);
|
|
4974
|
+
return () => {
|
|
4975
|
+
listenersRef.current.delete(cb);
|
|
4976
|
+
};
|
|
4977
|
+
}, []);
|
|
4978
|
+
const getCurrent = useCallback(() => currentRef.current, []);
|
|
4979
|
+
const setCurrentEditorId = useCallback((id) => {
|
|
4980
|
+
if (currentRef.current === id) return;
|
|
4981
|
+
currentRef.current = id;
|
|
4982
|
+
listenersRef.current.forEach((l) => l());
|
|
4983
|
+
}, []);
|
|
4984
|
+
const currentEditorId = useSyncExternalStore(subscribeCurrent, getCurrent, getCurrent);
|
|
4985
|
+
const value = useMemo(() => ({
|
|
4986
|
+
store: storeRef.current,
|
|
4987
|
+
ctx,
|
|
4988
|
+
currentEditorId,
|
|
4989
|
+
setCurrentEditorId,
|
|
4990
|
+
maxOpenEditors,
|
|
4991
|
+
defaultValueFactory
|
|
4992
|
+
}), [ctx, currentEditorId, setCurrentEditorId, maxOpenEditors, defaultValueFactory]);
|
|
4993
|
+
return /* @__PURE__ */ jsx(EditorSessionContext.Provider, { value, children });
|
|
4994
|
+
};
|
|
4995
|
+
var useEntry = (editorId) => {
|
|
4996
|
+
const { store } = useEditorSessionContext();
|
|
4997
|
+
const subscribe = useCallback((cb) => store.subscribe(cb), [store]);
|
|
4998
|
+
const getSnapshot = useCallback(
|
|
4999
|
+
() => editorId ? store.get(editorId) : void 0,
|
|
5000
|
+
[store, editorId]
|
|
5001
|
+
);
|
|
5002
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
5003
|
+
};
|
|
5004
|
+
var useStoreList = () => {
|
|
5005
|
+
const { store } = useEditorSessionContext();
|
|
5006
|
+
const subscribe = useCallback((cb) => store.subscribe(cb), [store]);
|
|
5007
|
+
const getSnapshot = useCallback(() => store.list(), [store]);
|
|
5008
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
5009
|
+
};
|
|
5010
|
+
function useEditorSession(editorId) {
|
|
5011
|
+
const { store, currentEditorId, ctx } = useEditorSessionContext();
|
|
5012
|
+
const queryClient = useQueryClient();
|
|
5013
|
+
const entry = useEntry(editorId);
|
|
5014
|
+
const onChange = useCallback((next) => {
|
|
5015
|
+
if (!editorId) return;
|
|
5016
|
+
store.setValue(editorId, next);
|
|
5017
|
+
}, [store, editorId]);
|
|
5018
|
+
const onFacetRuleChange = useCallback((next) => {
|
|
5019
|
+
if (!editorId) return;
|
|
5020
|
+
store.setFacetRule(editorId, next);
|
|
5021
|
+
}, [store, editorId]);
|
|
5022
|
+
const save = useCallback(async () => {
|
|
5023
|
+
if (!editorId) return;
|
|
5024
|
+
try {
|
|
5025
|
+
await store.save(editorId);
|
|
5026
|
+
const e = store.get(editorId);
|
|
5027
|
+
if (e) {
|
|
5028
|
+
const key = resolvedRecordQueryKey({
|
|
5029
|
+
collectionId: ctx.collectionId,
|
|
5030
|
+
appId: ctx.appId,
|
|
5031
|
+
recordType: ctx.recordType,
|
|
5032
|
+
productId: e.spec.scope.productId,
|
|
5033
|
+
variantId: e.spec.scope.variantId,
|
|
5034
|
+
batchId: e.spec.scope.batchId,
|
|
5035
|
+
facetId: e.spec.scope.facetId,
|
|
5036
|
+
facetValue: e.spec.scope.facetValue,
|
|
5037
|
+
proofId: e.spec.scope.proofId,
|
|
5038
|
+
recordId: e.recordId,
|
|
5039
|
+
withParent: true
|
|
5040
|
+
});
|
|
5041
|
+
queryClient.invalidateQueries({ queryKey: key });
|
|
5042
|
+
}
|
|
5043
|
+
} catch {
|
|
5044
|
+
}
|
|
5045
|
+
}, [store, editorId, ctx, queryClient]);
|
|
5046
|
+
const reset = useCallback(() => {
|
|
5047
|
+
if (!editorId) return;
|
|
5048
|
+
store.reset(editorId);
|
|
5049
|
+
}, [store, editorId]);
|
|
5050
|
+
const remove = useCallback(async () => {
|
|
5051
|
+
if (!editorId) return;
|
|
5052
|
+
await store.remove(editorId);
|
|
5053
|
+
}, [store, editorId]);
|
|
5054
|
+
if (!editorId || !entry) return null;
|
|
5055
|
+
const isRuleScope = entry.spec.scope.kind === "rule";
|
|
5056
|
+
const ruleValid = !isRuleScope || isFacetRuleValid(entry.facetRule);
|
|
5057
|
+
const canSave = ruleValid;
|
|
5058
|
+
const cannotSaveReason = !ruleValid ? "Pick at least one value for every facet in the rule before saving." : void 0;
|
|
5059
|
+
return {
|
|
5060
|
+
editorId,
|
|
5061
|
+
value: entry.value,
|
|
5062
|
+
onChange,
|
|
5063
|
+
source: entry.source,
|
|
5064
|
+
recordId: entry.recordId,
|
|
5065
|
+
parentValue: entry.parentValue,
|
|
5066
|
+
scope: entry.spec.scope,
|
|
5067
|
+
isDirty: entry.status === "dirty" || entry.status === "saving" || entry.status === "error",
|
|
5068
|
+
status: entry.status,
|
|
5069
|
+
error: entry.error ?? null,
|
|
5070
|
+
save,
|
|
5071
|
+
reset,
|
|
5072
|
+
remove,
|
|
5073
|
+
canRemove: entry.source === "self" && !!entry.recordId,
|
|
5074
|
+
canSave,
|
|
5075
|
+
cannotSaveReason,
|
|
5076
|
+
facetRule: entry.facetRule,
|
|
5077
|
+
onFacetRuleChange,
|
|
5078
|
+
isActive: editorId === currentEditorId
|
|
5079
|
+
};
|
|
5080
|
+
}
|
|
5081
|
+
var useEditorSelection = () => {
|
|
5082
|
+
const { store, ctx, currentEditorId, setCurrentEditorId, maxOpenEditors, defaultValueFactory } = useEditorSessionContext();
|
|
5083
|
+
const openEditors = useStoreList();
|
|
5084
|
+
const selectTarget = useCallback((input) => {
|
|
5085
|
+
const existing = store.findExistingEditorIdFor(input.spec);
|
|
5086
|
+
if (existing) {
|
|
5087
|
+
store.markActive(existing);
|
|
5088
|
+
setCurrentEditorId(existing);
|
|
5089
|
+
store.enforcePoolLimit(maxOpenEditors, existing);
|
|
5090
|
+
return existing;
|
|
5091
|
+
}
|
|
5092
|
+
const editorId = store.open({
|
|
5093
|
+
spec: input.spec,
|
|
5094
|
+
saveSpec: buildSaveSpec(ctx, input.spec),
|
|
5095
|
+
seed: input.seed ?? null,
|
|
5096
|
+
defaultValue: input.defaultValue ?? defaultValueFactory?.() ?? {},
|
|
5097
|
+
label: input.label
|
|
5098
|
+
});
|
|
5099
|
+
setCurrentEditorId(editorId);
|
|
5100
|
+
store.enforcePoolLimit(maxOpenEditors, editorId);
|
|
5101
|
+
return editorId;
|
|
5102
|
+
}, [store, ctx, setCurrentEditorId, maxOpenEditors, defaultValueFactory]);
|
|
5103
|
+
const focus = useCallback((editorId) => {
|
|
5104
|
+
store.markActive(editorId);
|
|
5105
|
+
setCurrentEditorId(editorId);
|
|
5106
|
+
}, [store, setCurrentEditorId]);
|
|
5107
|
+
const blur = useCallback(() => setCurrentEditorId(void 0), [setCurrentEditorId]);
|
|
5108
|
+
const close = useCallback((editorId) => {
|
|
5109
|
+
const closed = store.close(editorId);
|
|
5110
|
+
if (closed && currentEditorId === editorId) setCurrentEditorId(void 0);
|
|
5111
|
+
return closed;
|
|
5112
|
+
}, [store, currentEditorId, setCurrentEditorId]);
|
|
5113
|
+
const hydrate = useCallback((editorId, resolved) => {
|
|
5114
|
+
store.hydrateFromResolver(editorId, resolved);
|
|
5115
|
+
}, [store]);
|
|
5116
|
+
return { currentEditorId, openEditors, selectTarget, focus, blur, close, hydrate };
|
|
5117
|
+
};
|
|
5118
|
+
var overviewItemFromEntry = (e) => ({
|
|
5119
|
+
editorId: e.editorId,
|
|
5120
|
+
label: e.label,
|
|
5121
|
+
status: e.status,
|
|
5122
|
+
error: e.error,
|
|
5123
|
+
scope: e.spec.scope,
|
|
5124
|
+
recordId: e.recordId
|
|
5125
|
+
});
|
|
5126
|
+
var useDirtyOverview = () => {
|
|
5127
|
+
const { store } = useEditorSessionContext();
|
|
5128
|
+
const list = useStoreList();
|
|
5129
|
+
const items = useMemo(
|
|
5130
|
+
() => list.filter((e) => e.status === "dirty" || e.status === "saving" || e.status === "error").map(overviewItemFromEntry),
|
|
5131
|
+
[list]
|
|
5132
|
+
);
|
|
5133
|
+
const count = items.length;
|
|
5134
|
+
const errorCount = useMemo(() => items.filter((i) => i.status === "error").length, [items]);
|
|
5135
|
+
const saveAll = useCallback(async () => {
|
|
5136
|
+
const ids = list.filter((e) => e.status === "dirty" || e.status === "error").map((e) => e.editorId);
|
|
5137
|
+
for (const id of ids) {
|
|
5138
|
+
try {
|
|
5139
|
+
await store.save(id);
|
|
5140
|
+
} catch {
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
}, [store, list]);
|
|
5144
|
+
const discardAll = useCallback(() => {
|
|
5145
|
+
const ids = list.filter((e) => e.status === "dirty" || e.status === "error").map((e) => e.editorId);
|
|
5146
|
+
ids.forEach((id) => store.reset(id));
|
|
5147
|
+
}, [store, list]);
|
|
5148
|
+
const saveOne = useCallback(async (editorId) => {
|
|
5149
|
+
await store.save(editorId);
|
|
5150
|
+
}, [store]);
|
|
5151
|
+
return { items, count, errorCount, saveAll, discardAll, saveOne };
|
|
5152
|
+
};
|
|
5153
|
+
var EditorMountPool = ({
|
|
5154
|
+
renderSlot,
|
|
5155
|
+
keepMountedHidden = true,
|
|
5156
|
+
fallback = null,
|
|
5157
|
+
className
|
|
5158
|
+
}) => {
|
|
5159
|
+
const { openEditors, currentEditorId } = useEditorSelection();
|
|
5160
|
+
const ids = useMemo(
|
|
5161
|
+
() => openEditors.map((e) => e.editorId).sort(),
|
|
5162
|
+
[openEditors]
|
|
5163
|
+
);
|
|
5164
|
+
if (!currentEditorId && ids.length === 0) {
|
|
5165
|
+
return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
5166
|
+
}
|
|
5167
|
+
const visibleIds = keepMountedHidden ? ids : currentEditorId ? [currentEditorId] : [];
|
|
5168
|
+
return /* @__PURE__ */ jsx("div", { className, style: { display: "contents" }, children: visibleIds.map((id) => {
|
|
5169
|
+
const isActive = id === currentEditorId;
|
|
5170
|
+
return /* @__PURE__ */ jsx(EditorPoolSlot, { editorId: id, isActive, children: renderSlot(id) }, id);
|
|
5171
|
+
}) });
|
|
5172
|
+
};
|
|
5173
|
+
var EditorPoolSlot = ({ editorId, isActive, children }) => {
|
|
5174
|
+
const inertProps = isActive ? {} : { inert: "" };
|
|
5175
|
+
return /* @__PURE__ */ jsx(
|
|
5176
|
+
"div",
|
|
5177
|
+
{
|
|
5178
|
+
"data-editor-slot": editorId,
|
|
5179
|
+
"data-active": isActive ? "true" : "false",
|
|
5180
|
+
"aria-hidden": isActive ? void 0 : true,
|
|
5181
|
+
style: {
|
|
5182
|
+
display: isActive ? "contents" : "none"
|
|
5183
|
+
},
|
|
5184
|
+
...inertProps,
|
|
5185
|
+
children
|
|
5186
|
+
}
|
|
5187
|
+
);
|
|
5188
|
+
};
|
|
5189
|
+
function useEditorSlotContext(editorId) {
|
|
5190
|
+
const session = useEditorSession(editorId);
|
|
5191
|
+
return useMemo(() => {
|
|
5192
|
+
if (!session) return null;
|
|
5193
|
+
return {
|
|
5194
|
+
value: session.value,
|
|
5195
|
+
onChange: session.onChange,
|
|
5196
|
+
source: session.source,
|
|
5197
|
+
recordId: session.recordId,
|
|
5198
|
+
parentValue: session.parentValue,
|
|
5199
|
+
scope: session.scope,
|
|
5200
|
+
isDirty: session.isDirty,
|
|
5201
|
+
save: session.save,
|
|
5202
|
+
reset: session.reset,
|
|
5203
|
+
remove: session.remove,
|
|
5204
|
+
canRemove: session.canRemove,
|
|
5205
|
+
canSave: session.canSave,
|
|
5206
|
+
cannotSaveReason: session.cannotSaveReason,
|
|
5207
|
+
isSaving: session.status === "saving",
|
|
5208
|
+
saveError: session.error,
|
|
5209
|
+
facetRule: session.facetRule,
|
|
5210
|
+
onFacetRuleChange: session.onFacetRuleChange
|
|
5211
|
+
};
|
|
5212
|
+
}, [session]);
|
|
5213
|
+
}
|
|
5214
|
+
var isEqualSpec = (a, b) => {
|
|
5215
|
+
if (a === b) return true;
|
|
5216
|
+
if (!a || !b) return false;
|
|
5217
|
+
if (a.scope.raw !== b.scope.raw) return false;
|
|
5218
|
+
if ((a.recordId ?? null) !== (b.recordId ?? null)) return false;
|
|
5219
|
+
if (!!a.createMode !== !!b.createMode) return false;
|
|
5220
|
+
return true;
|
|
5221
|
+
};
|
|
5222
|
+
function useEditorBridge(args) {
|
|
5223
|
+
const { target, resolved, defaultData, onSaved, onDeleted } = args;
|
|
5224
|
+
const selection = useEditorSelection();
|
|
5225
|
+
const lastTargetRef = useRef(null);
|
|
5226
|
+
const editorIdRef = useRef(void 0);
|
|
5227
|
+
useEffect(() => {
|
|
5228
|
+
if (!target) {
|
|
5229
|
+
selection.blur();
|
|
5230
|
+
lastTargetRef.current = null;
|
|
5231
|
+
editorIdRef.current = void 0;
|
|
5232
|
+
return;
|
|
5233
|
+
}
|
|
5234
|
+
if (isEqualSpec(target, lastTargetRef.current)) return;
|
|
5235
|
+
lastTargetRef.current = target;
|
|
5236
|
+
const id = selection.selectTarget({
|
|
5237
|
+
spec: target,
|
|
5238
|
+
seed: resolved.source === "empty" ? void 0 : {
|
|
5239
|
+
value: resolved.data ?? (defaultData?.() ?? null),
|
|
5240
|
+
facetRule: resolved.facetRule ?? null,
|
|
5241
|
+
source: resolved.source,
|
|
5242
|
+
parentValue: resolved.parentValue ?? null,
|
|
5243
|
+
recordId: resolved.recordId
|
|
5244
|
+
},
|
|
5245
|
+
defaultValue: defaultData?.() ?? {}
|
|
5246
|
+
});
|
|
5247
|
+
editorIdRef.current = id;
|
|
5248
|
+
}, [target?.scope.raw, target?.recordId, target?.createMode]);
|
|
5249
|
+
useEffect(() => {
|
|
5250
|
+
const id = editorIdRef.current;
|
|
5251
|
+
if (!id) return;
|
|
5252
|
+
if (resolved.source === "empty" && !resolved.recordId) return;
|
|
5253
|
+
selection.hydrate(id, resolved);
|
|
5254
|
+
}, [resolved.source, resolved.recordId, resolved.sourceRef, resolved.data, resolved.facetRule]);
|
|
5255
|
+
const editorId = selection.currentEditorId ?? editorIdRef.current;
|
|
5256
|
+
const session = useEditorSession(editorId);
|
|
5257
|
+
const prevStatusRef = useRef(null);
|
|
5258
|
+
useEffect(() => {
|
|
5259
|
+
const prev = prevStatusRef.current;
|
|
5260
|
+
const next = session?.status ?? null;
|
|
5261
|
+
prevStatusRef.current = next;
|
|
5262
|
+
if (next === "saved" && prev !== "saved") {
|
|
5263
|
+
onSaved?.();
|
|
5264
|
+
}
|
|
5265
|
+
}, [session?.status]);
|
|
5266
|
+
const remove = useMemo(() => async () => {
|
|
5267
|
+
if (!session) return;
|
|
5268
|
+
await session.remove();
|
|
5269
|
+
onDeleted?.();
|
|
5270
|
+
}, [session?.remove]);
|
|
5271
|
+
if (!session) {
|
|
5272
|
+
const sentinelScope = target?.scope ?? { kind: "collection", raw: "" };
|
|
5273
|
+
return {
|
|
5274
|
+
value: defaultData?.() ?? {},
|
|
5275
|
+
onChange: () => {
|
|
5276
|
+
},
|
|
5277
|
+
source: "empty",
|
|
5278
|
+
recordId: void 0,
|
|
5279
|
+
parentValue: null,
|
|
5280
|
+
scope: sentinelScope,
|
|
5281
|
+
isDirty: false,
|
|
5282
|
+
save: async () => {
|
|
5283
|
+
},
|
|
5284
|
+
reset: () => {
|
|
5285
|
+
},
|
|
5286
|
+
remove: async () => {
|
|
5287
|
+
},
|
|
5288
|
+
canRemove: false,
|
|
5289
|
+
canSave: false,
|
|
5290
|
+
cannotSaveReason: void 0,
|
|
5291
|
+
isSaving: false,
|
|
5292
|
+
saveError: null,
|
|
5293
|
+
facetRule: null,
|
|
5294
|
+
onFacetRuleChange: () => {
|
|
5295
|
+
}
|
|
5296
|
+
};
|
|
5297
|
+
}
|
|
5298
|
+
return {
|
|
5299
|
+
value: session.value,
|
|
5300
|
+
onChange: session.onChange,
|
|
5301
|
+
source: session.source,
|
|
5302
|
+
recordId: session.recordId,
|
|
5303
|
+
parentValue: session.parentValue,
|
|
5304
|
+
scope: session.scope,
|
|
5305
|
+
isDirty: session.isDirty,
|
|
5306
|
+
save: session.save,
|
|
5307
|
+
reset: session.reset,
|
|
5308
|
+
remove,
|
|
5309
|
+
canRemove: session.canRemove,
|
|
5310
|
+
canSave: session.canSave,
|
|
5311
|
+
cannotSaveReason: session.cannotSaveReason,
|
|
5312
|
+
isSaving: session.status === "saving",
|
|
5313
|
+
saveError: session.error,
|
|
5314
|
+
facetRule: session.facetRule,
|
|
5315
|
+
onFacetRuleChange: session.onFacetRuleChange
|
|
5316
|
+
};
|
|
5317
|
+
}
|
|
5318
|
+
|
|
5319
|
+
// src/components/RecordsAdmin/data/csv.ts
|
|
5320
|
+
var escapeCell = (s) => {
|
|
5321
|
+
if (/[",\n]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
|
|
5322
|
+
return s;
|
|
4595
5323
|
};
|
|
4596
5324
|
var parseLine = (line) => {
|
|
4597
5325
|
const out = [];
|
|
@@ -4692,11 +5420,58 @@ var downloadBlob = (blob, filename) => {
|
|
|
4692
5420
|
URL.revokeObjectURL(url);
|
|
4693
5421
|
};
|
|
4694
5422
|
|
|
5423
|
+
// src/components/RecordsAdmin/hooks/useShellCsvBulk.ts
|
|
5424
|
+
function useShellCsvBulk(args) {
|
|
5425
|
+
const { csvSchema, recordType, label, items, ctx, refetchAll, onTelemetry } = args;
|
|
5426
|
+
const fileBase = useMemo(
|
|
5427
|
+
() => recordType ?? (label.toLowerCase().replace(/\s+/g, "-") || "records"),
|
|
5428
|
+
[recordType, label]
|
|
5429
|
+
);
|
|
5430
|
+
const handleExport = useCallback(() => {
|
|
5431
|
+
if (!csvSchema) return;
|
|
5432
|
+
const blob = exportCsv(items, csvSchema);
|
|
5433
|
+
downloadBlob(blob, `${fileBase}.csv`);
|
|
5434
|
+
onTelemetry?.({ type: "csv.export", recordType, rows: items.length });
|
|
5435
|
+
}, [csvSchema, items, fileBase, onTelemetry, recordType]);
|
|
5436
|
+
const handleImport = useCallback(() => {
|
|
5437
|
+
if (!csvSchema) return;
|
|
5438
|
+
const inp = document.createElement("input");
|
|
5439
|
+
inp.type = "file";
|
|
5440
|
+
inp.accept = ".csv,text/csv";
|
|
5441
|
+
inp.onchange = async () => {
|
|
5442
|
+
const file = inp.files?.[0];
|
|
5443
|
+
if (!file) return;
|
|
5444
|
+
const report = await importCsv(file, csvSchema, ctx);
|
|
5445
|
+
onTelemetry?.({ type: "csv.import", recordType, rows: report.total, errors: report.failed });
|
|
5446
|
+
if (report.failed > 0) {
|
|
5447
|
+
downloadBlob(
|
|
5448
|
+
new Blob([report.annotatedCsv], { type: "text/csv" }),
|
|
5449
|
+
`${fileBase}-errors.csv`
|
|
5450
|
+
);
|
|
5451
|
+
}
|
|
5452
|
+
refetchAll();
|
|
5453
|
+
};
|
|
5454
|
+
inp.click();
|
|
5455
|
+
}, [csvSchema, ctx, fileBase, onTelemetry, recordType, refetchAll]);
|
|
5456
|
+
return useMemo(
|
|
5457
|
+
() => csvSchema ? { onImportCsv: handleImport, onExportCsv: handleExport } : {},
|
|
5458
|
+
[csvSchema, handleImport, handleExport]
|
|
5459
|
+
);
|
|
5460
|
+
}
|
|
5461
|
+
|
|
4695
5462
|
// src/components/RecordsAdmin/shell/tokens.css
|
|
4696
5463
|
styleInject(':root {\n --ra-status-own: var(--ra-emerald, 142 71% 45%);\n --ra-status-shared: var(--ra-amber, 38 92% 50%);\n --ra-status-missing: var(--muted-foreground, 220 9% 46%);\n --ra-accent: var(--primary, 222 47% 11%);\n --ra-surface: var(--card, 0 0% 100%);\n --ra-border: var(--border, 220 13% 91%);\n --ra-text: var(--foreground, 222 47% 11%);\n --ra-muted: var(--muted, 220 14% 96%);\n --ra-muted-text: var(--muted-foreground, 220 9% 46%);\n --ra-radius: var(--radius, 0.625rem);\n --ra-dot-size: 0.5rem;\n --ra-page-bg: var(--background, 220 14% 98%);\n --ra-card-shadow: 0 1px 2px hsl(var(--ra-accent) / 0.04), 0 4px 12px hsl(var(--ra-accent) / 0.05);\n --ra-card-shadow-hover: 0 2px 4px hsl(var(--ra-accent) / 0.06), 0 8px 24px hsl(var(--ra-accent) / 0.08);\n --ra-row-hover: hsl(var(--ra-accent) / 0.05);\n --ra-row-active-bg: hsl(var(--ra-accent) / 0.10);\n --ra-row-active-bd: hsl(var(--ra-accent) / 0.45);\n --ra-focus-ring: hsl(var(--ra-accent) / 0.35);\n --ra-font-display: var(--font-display, var(--font-sans, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif));\n --ra-font-ui: var(--font-sans, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);\n --ra-title-weight: 600;\n --ra-display-weight: 700;\n --ra-info: var(--ra-blue, 214 95% 55%);\n --ra-success: var(--ra-emerald, 142 71% 45%);\n --ra-warning: var(--ra-amber, 38 92% 50%);\n --ra-danger: var(--destructive, 0 72% 51%);\n}\n:root {\n --sl-control-bg: var(--muted, 220 14% 96%);\n --sl-control-fg: var(--muted-foreground, 220 9% 40%);\n --sl-control-border: var(--border, 220 13% 88%);\n --sl-control-active-bg: var(--primary, 222 47% 11%);\n --sl-control-active-fg: var(--primary-foreground, 0 0% 100%);\n --sl-control-active-bd: var(--primary, 222 47% 11%);\n --sl-control-hover-bg: var(--sl-control-active-bg, 222 47% 11%);\n --sl-control-hover-fg: var(--foreground, 222 47% 11%);\n --sl-control-focus-ring: var(--sl-control-active-bg, 222 47% 11%);\n --sl-control-radius: var(--radius, 0.5rem);\n --sl-control-weight: 500;\n --sl-control-active-weight: 600;\n}\n.ra-status-dot {\n display: inline-block;\n width: var(--ra-dot-size);\n height: var(--ra-dot-size);\n border-radius: 9999px;\n flex-shrink: 0;\n}\n.ra-status-own {\n background: hsl(var(--ra-status-own));\n}\n.ra-status-shared {\n background: hsl(var(--ra-status-shared));\n}\n.ra-status-missing {\n background: hsl(var(--ra-status-missing) / 0.4);\n border: 1px solid hsl(var(--ra-status-missing) / 0.6);\n}\n.ra-row-active {\n background: var(--ra-row-active-bg);\n border-color: var(--ra-row-active-bd) !important;\n}\n');
|
|
4697
5464
|
|
|
4698
5465
|
// src/components/RecordsAdmin/shell/shell.css
|
|
4699
|
-
styleInject(".ra-shell {\n color: hsl(var(--ra-text));\n background: hsl(var(--ra-page-bg));\n font-family: var(--ra-font-ui);\n}\n.ra-shell *,\n.ra-shell *::before,\n.ra-shell *::after {\n box-sizing: border-box;\n}\n.ra-shell .ra-card {\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n box-shadow: var(--ra-card-shadow);\n}\n.ra-shell .ra-card-hover {\n transition:\n box-shadow .18s ease,\n transform .18s ease,\n border-color .18s ease;\n}\n.ra-shell .ra-card-hover:hover {\n box-shadow: var(--ra-card-shadow-hover);\n}\n.ra-shell .ra-display {\n font-family: var(--ra-font-display);\n font-weight: var(--ra-display-weight);\n letter-spacing: -0.01em;\n}\n.ra-shell .ra-title {\n font-weight: var(--ra-title-weight);\n}\n.ra-shell :where(button, [role=button], input, select, textarea, a):focus-visible {\n outline: none;\n box-shadow: 0 0 0 3px var(--ra-focus-ring);\n border-radius: calc(var(--ra-radius) * 0.6);\n}\n.ra-shell .ra-header {\n display: block;\n width: 100%;\n}\n.ra-shell .ra-header__main {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: flex-start;\n gap: 0.55rem;\n}\n.ra-shell .ra-header-aside {\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n flex-shrink: 0;\n}\n.ra-shell .ra-header-icon {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: flex-start;\n background: transparent;\n color: hsl(var(--ra-text));\n border: 0;\n padding: 0;\n margin-top: 0.1rem;\n}\n.ra-shell .ra-header-icon > svg {\n width: 1.05rem;\n height: 1.05rem;\n}\n.ra-shell .ra-header-text {\n flex: 1;\n min-width: 0;\n}\n.ra-shell .ra-header-title {\n font-family: var(--ra-font-display);\n font-weight: 700;\n font-size: 1.2rem;\n line-height: 1.2;\n color: hsl(var(--ra-text));\n letter-spacing: -0.015em;\n margin: 0;\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n}\n.ra-shell .ra-header-subtitle {\n font-size: 0.78rem;\n color: hsl(var(--ra-muted-text));\n margin-top: 0.1rem;\n line-height: 1.3;\n}\n.ra-shell .ra-header-stats {\n display: flex;\n align-items: stretch;\n gap: 0.15rem;\n padding: 0.15rem 0.4rem;\n border-radius: calc(var(--ra-radius) * 0.75);\n background: hsl(var(--ra-surface) / 0.7);\n border: 1px solid hsl(var(--ra-border));\n}\n.ra-shell .ra-header-stats--titled {\n flex-direction: column;\n align-items: stretch;\n padding: 0.4rem 0.55rem;\n gap: 0.3rem;\n}\n.ra-shell .ra-header-stats .ra-stats-items {\n display: flex;\n align-items: stretch;\n gap: 0.15rem;\n}\n.ra-shell .ra-header-stats .ra-stats-heading {\n display: flex;\n align-items: center;\n gap: 0.35rem;\n color: hsl(var(--ra-muted-text));\n font-size: 0.65rem;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n.ra-shell .ra-header-stats .ra-stats-heading-icon {\n display: inline-flex;\n align-items: center;\n color: hsl(var(--ra-text));\n opacity: 0.75;\n}\n.ra-shell .ra-header-stats .ra-stats-heading-icon > svg {\n width: 0.85rem;\n height: 0.85rem;\n}\n.ra-shell .ra-stat {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 0.15rem 0.45rem;\n min-width: 2.5rem;\n}\n.ra-shell .ra-stat-value {\n font-family: var(--ra-font-display);\n font-weight: var(--ra-display-weight);\n font-size: 0.85rem;\n color: hsl(var(--ra-text));\n line-height: 1;\n}\n.ra-shell .ra-stat-label {\n font-size: 0.6rem;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: hsl(var(--ra-muted-text));\n margin-top: 0.15rem;\n}\n.ra-shell .ra-stat-divider {\n width: 1px;\n background: hsl(var(--ra-border));\n margin: 0.25rem 0;\n}\n.ra-shell .ra-header-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n.ra-shell .ra-tabs {\n display: flex;\n gap: 0.25rem;\n padding: 0.25rem;\n background: hsl(var(--sl-control-bg));\n border-radius: var(--sl-control-radius);\n border: 1px solid hsl(var(--sl-control-border));\n}\n.ra-shell .ra-tab {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.4rem 0.7rem;\n border-radius: calc(var(--sl-control-radius) - 2px);\n font-size: 0.78rem;\n font-weight: var(--sl-control-weight);\n color: hsl(var(--sl-control-fg));\n background: transparent;\n border: 0;\n cursor: pointer;\n transition:\n background .15s ease,\n color .15s ease,\n transform .15s ease;\n white-space: nowrap;\n}\n.ra-shell .ra-tab:hover {\n background: hsl(var(--sl-control-hover-bg) / 0.10);\n color: hsl(var(--sl-control-hover-fg));\n}\n.ra-shell .ra-tab:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px hsl(var(--sl-control-focus-ring) / 0.45);\n}\n.ra-shell .ra-tab[aria-selected=true] {\n background: hsl(var(--sl-control-active-bg));\n color: hsl(var(--sl-control-active-fg));\n border-color: hsl(var(--sl-control-active-bd));\n font-weight: var(--sl-control-active-weight);\n box-shadow: 0 1px 2px hsl(var(--sl-control-active-bg) / 0.25);\n}\n.ra-shell .ra-tab[aria-selected=true]:hover {\n background: hsl(var(--sl-control-active-bg) / 0.92);\n color: hsl(var(--sl-control-active-fg));\n}\n.ra-shell .ra-tab[aria-selected=true] .ra-tab-icon {\n color: hsl(var(--sl-control-active-fg));\n}\n.ra-shell .ra-tab[disabled] {\n opacity: .5;\n cursor: not-allowed;\n}\n.ra-shell .ra-tab-count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 1.25rem;\n padding: 0 0.35rem;\n height: 1.1rem;\n border-radius: 999px;\n background: hsl(var(--sl-control-active-fg) / 0.20);\n color: hsl(var(--sl-control-active-fg));\n font-size: 0.625rem;\n font-weight: 600;\n line-height: 1;\n}\n.ra-shell .ra-tab[aria-selected=false] .ra-tab-count {\n background: hsl(var(--sl-control-fg) / 0.15);\n color: hsl(var(--sl-control-fg));\n}\n.ra-shell[data-density=compact] .ra-row {\n padding-block: 0.4rem;\n}\n.ra-shell .ra-row {\n display: flex;\n align-items: center;\n gap: 0.55rem;\n width: 100%;\n text-align: left;\n padding: 0.45rem 0.75rem;\n border-left: 3px solid transparent;\n background: transparent;\n border-bottom: 1px solid transparent;\n transition: background .12s ease, border-color .12s ease;\n cursor: pointer;\n color: hsl(var(--ra-text));\n font-family: inherit;\n}\n.ra-shell .ra-row + .ra-row {\n border-top: 1px solid hsl(var(--ra-border) / 0.6);\n}\n.ra-shell .ra-row:hover {\n background: var(--ra-row-hover);\n}\n.ra-shell .ra-row[data-selected=true] {\n background: var(--ra-row-active-bg);\n border-left-color: var(--ra-row-active-bd);\n}\n.ra-shell .ra-row-compact {\n padding-block: 0.3rem;\n}\n.ra-shell .ra-row-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.5rem;\n height: 1.5rem;\n border-radius: calc(var(--ra-radius) * 0.6);\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-muted-text));\n flex-shrink: 0;\n}\n.ra-shell .ra-row[data-selected=true] .ra-row-icon {\n background: hsl(var(--ra-accent) / 0.15);\n color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-row-body {\n flex: 1;\n min-width: 0;\n}\n.ra-shell .ra-row-title {\n font-weight: var(--ra-title-weight);\n font-size: 0.8125rem;\n line-height: 1.2;\n color: hsl(var(--ra-text));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-row-sub {\n font-size: 0.6875rem;\n color: hsl(var(--ra-muted-text));\n margin-top: 0.05rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-row-rule-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 0.2rem;\n margin-top: 0.2rem;\n}\n.ra-shell .ra-rule-chip {\n display: inline-flex;\n align-items: center;\n max-width: 100%;\n padding: 0.05rem 0.4rem;\n border-radius: 999px;\n font-size: 0.625rem;\n font-weight: 500;\n line-height: 1.4;\n background: hsl(var(--ra-accent) / 0.10);\n color: hsl(var(--ra-accent));\n border: 1px solid hsl(var(--ra-accent) / 0.20);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-rule-chip-more {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-muted-text));\n border-color: hsl(var(--ra-border));\n}\n.ra-shell[data-density=compact] .ra-row-rule-chips {\n margin-top: 0.15rem;\n gap: 0.15rem;\n}\n.ra-shell[data-density=compact] .ra-rule-chip {\n font-size: 0.6rem;\n padding: 0.02rem 0.35rem;\n}\n.ra-shell .ra-rule-filters {\n display: flex;\n flex-direction: column;\n gap: 0.3rem;\n}\n.ra-shell .ra-rule-filters-row {\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n}\n.ra-shell .ra-rule-filter-chip {\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n padding: 0.15rem 0.5rem;\n border-radius: 999px;\n font-size: 0.65rem;\n font-weight: 500;\n line-height: 1.4;\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-muted-text));\n border: 1px solid hsl(var(--ra-border));\n cursor: pointer;\n transition:\n background .12s ease,\n color .12s ease,\n border-color .12s ease;\n max-width: 100%;\n}\n.ra-shell .ra-rule-filter-chip:hover {\n background: hsl(var(--ra-accent) / 0.10);\n color: hsl(var(--ra-text));\n border-color: hsl(var(--ra-accent) / 0.25);\n}\n.ra-shell .ra-rule-filter-chip[data-active=true] {\n background: hsl(var(--ra-accent) / 0.15);\n color: hsl(var(--ra-accent));\n border-color: hsl(var(--ra-accent) / 0.40);\n}\n.ra-shell .ra-rule-filter-chip[data-tone=complexity][data-active=true] {\n background: hsl(var(--ra-info) / 0.15);\n color: hsl(var(--ra-info));\n border-color: hsl(var(--ra-info) / 0.40);\n}\n.ra-shell .ra-rule-filter-chip-label {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 9rem;\n}\n.ra-shell .ra-rule-filter-chip-count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 1.1rem;\n height: 1rem;\n padding: 0 0.3rem;\n border-radius: 999px;\n background: hsl(var(--ra-surface));\n color: hsl(var(--ra-muted-text));\n font-size: 0.6rem;\n font-weight: 600;\n}\n.ra-shell .ra-rule-filter-chip[data-active=true] .ra-rule-filter-chip-count {\n background: hsl(var(--ra-accent) / 0.18);\n color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-rule-filter-clear {\n align-self: flex-start;\n background: transparent;\n border: 0;\n padding: 0;\n color: hsl(var(--ra-muted-text));\n font-size: 0.65rem;\n cursor: pointer;\n text-decoration: underline;\n text-decoration-style: dotted;\n}\n.ra-shell .ra-rule-filter-clear:hover {\n color: hsl(var(--ra-text));\n}\n.ra-shell[data-density=compact] .ra-row {\n padding-block: 0.3rem;\n gap: 0.45rem;\n}\n.ra-shell[data-density=compact] .ra-row-title {\n font-size: 0.78125rem;\n}\n.ra-shell .ra-row-actions {\n display: inline-flex;\n align-items: center;\n gap: 0.15rem;\n margin-left: auto;\n opacity: 0;\n transition: opacity .15s ease;\n}\n.ra-shell .ra-row:hover .ra-row-actions,\n.ra-shell .ra-row:focus-within .ra-row-actions {\n opacity: 1;\n}\n.ra-shell .ra-row-action {\n width: 1.6rem;\n height: 1.6rem;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 999px;\n background: transparent;\n color: hsl(var(--ra-muted-text));\n border: 0;\n cursor: pointer;\n transition: background .15s ease, color .15s ease;\n}\n.ra-shell .ra-row-action:hover {\n background: hsl(var(--ra-accent) / 0.10);\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-row-action[data-tone=danger]:hover {\n background: hsl(var(--ra-danger) / 0.12);\n color: hsl(var(--ra-danger));\n}\n.ra-shell .ra-chip {\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n padding: 0.15rem 0.5rem;\n border-radius: 999px;\n font-size: 0.6875rem;\n font-weight: 500;\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-muted-text));\n border: 1px solid hsl(var(--ra-border));\n white-space: nowrap;\n max-width: 14rem;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-chip[data-tone=success] {\n background: hsl(var(--ra-success) / 0.12);\n color: hsl(var(--ra-success));\n border-color: hsl(var(--ra-success) / 0.30);\n}\n.ra-shell .ra-chip[data-tone=warning] {\n background: hsl(var(--ra-warning) / 0.14);\n color: hsl(var(--ra-warning));\n border-color: hsl(var(--ra-warning) / 0.35);\n}\n.ra-shell .ra-chip[data-tone=info] {\n background: hsl(var(--ra-info) / 0.10);\n color: hsl(var(--ra-info));\n border-color: hsl(var(--ra-info) / 0.30);\n}\n.ra-shell .ra-chip[data-tone=danger] {\n background: hsl(var(--ra-danger) / 0.10);\n color: hsl(var(--ra-danger));\n border-color: hsl(var(--ra-danger) / 0.30);\n}\n.ra-shell .ra-chip[data-tone=muted] {\n background: transparent;\n color: hsl(var(--ra-muted-text));\n border-style: dashed;\n}\n.ra-shell .ra-group {\n border-bottom: 1px solid hsl(var(--ra-border));\n}\n.ra-shell .ra-group:last-child {\n border-bottom: 0;\n}\n.ra-shell .ra-group-summary {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n width: 100%;\n padding: 0.5rem 0.85rem;\n background: hsl(var(--ra-muted) / 0.6);\n font-size: 0.7rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: hsl(var(--ra-muted-text));\n border: 0;\n cursor: pointer;\n transition: background .12s ease;\n}\n.ra-shell .ra-group-summary:hover {\n background: hsl(var(--ra-muted));\n}\n.ra-shell .ra-group-summary .ra-group-chevron {\n transition: transform .15s ease;\n}\n.ra-shell .ra-group[data-open=false] .ra-group-chevron {\n transform: rotate(-90deg);\n}\n.ra-shell .ra-group-name {\n flex: 1;\n text-align: left;\n}\n.ra-shell .ra-group-count {\n font-size: 0.65rem;\n font-weight: 600;\n color: hsl(var(--ra-muted-text));\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n border-radius: 999px;\n padding: 0.05rem 0.4rem;\n}\n.ra-shell .ra-group[data-open=false] .ra-group-body {\n display: none;\n}\n.ra-shell .ra-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n padding: 2.5rem 1.5rem;\n gap: 0.75rem;\n}\n.ra-shell .ra-empty-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 3.25rem;\n height: 3.25rem;\n border-radius: 999px;\n background: hsl(var(--ra-accent) / 0.08);\n color: hsl(var(--ra-accent));\n margin-bottom: 0.25rem;\n}\n.ra-shell .ra-empty-title {\n font-family: var(--ra-font-display);\n font-weight: var(--ra-display-weight);\n font-size: 1rem;\n color: hsl(var(--ra-text));\n margin: 0;\n letter-spacing: -0.01em;\n}\n.ra-shell .ra-empty-body {\n font-size: 0.8125rem;\n color: hsl(var(--ra-muted-text));\n max-width: 22rem;\n line-height: 1.45;\n}\n.ra-shell .ra-empty-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-top: 0.25rem;\n flex-wrap: wrap;\n justify-content: center;\n}\n.ra-shell .ra-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.45rem 0.85rem;\n border-radius: calc(var(--ra-radius) * 0.7);\n font-size: 0.8125rem;\n font-weight: 500;\n border: 1px solid hsl(var(--ra-border));\n background: hsl(var(--ra-surface));\n color: hsl(var(--ra-text));\n cursor: pointer;\n transition:\n background .15s ease,\n border-color .15s ease,\n box-shadow .15s ease,\n transform .1s ease;\n}\n.ra-shell .ra-btn:hover {\n background: hsl(var(--ra-muted));\n box-shadow: var(--ra-card-shadow);\n}\n.ra-shell .ra-btn:active {\n transform: translateY(1px);\n}\n.ra-shell .ra-btn[data-variant=primary] {\n background: hsl(var(--ra-accent));\n color: hsl(var(--ra-surface));\n border-color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-btn[data-variant=primary]:hover {\n background: hsl(var(--ra-accent) / 0.92);\n}\n.ra-shell .ra-btn[data-variant=ghost] {\n background: transparent;\n border-color: transparent;\n color: hsl(var(--ra-muted-text));\n}\n.ra-shell .ra-btn[data-variant=ghost]:hover {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-btn[data-variant=danger] {\n color: hsl(var(--ra-danger));\n}\n.ra-shell .ra-btn[data-variant=danger]:hover {\n background: hsl(var(--ra-danger) / 0.10);\n border-color: hsl(var(--ra-danger) / 0.40);\n}\n.ra-shell .ra-intro {\n position: relative;\n display: flex;\n align-items: center;\n gap: 0.55rem;\n padding: 0.4rem 2rem 0.4rem 0.5rem;\n border-radius: var(--ra-radius);\n border: 1px solid hsl(var(--ra-info) / 0.30);\n background: hsl(var(--ra-info) / 0.08);\n}\n.ra-shell .ra-intro[data-tone=success] {\n border-color: hsl(var(--ra-success) / 0.30);\n background: hsl(var(--ra-success) / 0.08);\n}\n.ra-shell .ra-intro[data-tone=warning] {\n border-color: hsl(var(--ra-warning) / 0.35);\n background: hsl(var(--ra-warning) / 0.10);\n}\n.ra-shell .ra-intro-icon {\n flex-shrink: 0;\n width: 1.5rem;\n height: 1.5rem;\n border-radius: 999px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: hsl(var(--ra-info) / 0.18);\n color: hsl(var(--ra-info));\n}\n.ra-shell .ra-intro[data-tone=success] .ra-intro-icon {\n background: hsl(var(--ra-success) / 0.18);\n color: hsl(var(--ra-success));\n}\n.ra-shell .ra-intro[data-tone=warning] .ra-intro-icon {\n background: hsl(var(--ra-warning) / 0.20);\n color: hsl(var(--ra-warning));\n}\n.ra-shell .ra-intro-body {\n flex: 1;\n min-width: 0;\n}\n.ra-shell .ra-intro-title {\n font-family: var(--ra-font-display);\n font-weight: var(--ra-title-weight);\n font-size: 0.8rem;\n color: hsl(var(--ra-text));\n margin: 0;\n line-height: 1.2;\n display: inline;\n}\n.ra-shell .ra-intro-text {\n font-size: 0.78rem;\n color: hsl(var(--ra-text) / 0.85);\n line-height: 1.35;\n display: inline;\n margin-left: 0.4rem;\n}\n.ra-shell .ra-intro-dismiss {\n position: absolute;\n top: 50%;\n right: 0.35rem;\n transform: translateY(-50%);\n width: 1.4rem;\n height: 1.4rem;\n border-radius: 999px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: 0;\n color: hsl(var(--ra-muted-text));\n cursor: pointer;\n padding: 0;\n flex-shrink: 0;\n}\n.ra-shell .ra-intro-dismiss:hover {\n background: hsl(var(--ra-text) / 0.06);\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-bulk-menu {\n min-width: 12rem;\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n border-radius: calc(var(--ra-radius) * 0.85);\n box-shadow: var(--ra-card-shadow-hover);\n padding: 0.3rem;\n z-index: 60;\n}\n.ra-shell .ra-bulk-item {\n display: flex;\n align-items: center;\n gap: 0.55rem;\n width: 100%;\n padding: 0.45rem 0.6rem;\n border-radius: calc(var(--ra-radius) * 0.6);\n font-size: 0.8125rem;\n color: hsl(var(--ra-text));\n background: transparent;\n border: 0;\n cursor: pointer;\n text-align: left;\n transition: background .12s ease, color .12s ease;\n}\n.ra-shell .ra-bulk-item:hover {\n background: hsl(var(--ra-muted));\n}\n.ra-shell .ra-bulk-item[data-tone=danger] {\n color: hsl(var(--ra-danger));\n}\n.ra-shell .ra-bulk-item[data-tone=danger]:hover {\n background: hsl(var(--ra-danger) / 0.10);\n}\n.ra-shell .ra-bulk-divider {\n height: 1px;\n background: hsl(var(--ra-border));\n margin: 0.25rem 0;\n}\n.ra-shell .ra-preview-rail {\n background: hsl(var(--ra-surface));\n border-left: 1px solid hsl(var(--ra-border));\n box-shadow: -4px 0 16px hsl(var(--ra-accent) / 0.04);\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n}\n.ra-shell .ra-preview-rail-header {\n position: sticky;\n top: 0;\n z-index: 1;\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1rem;\n background:\n linear-gradient(\n 180deg,\n hsl(var(--ra-surface)) 0%,\n hsl(var(--ra-surface) / 0.92) 100%);\n border-bottom: 1px solid hsl(var(--ra-border));\n backdrop-filter: blur(6px);\n}\n.ra-shell .ra-preview-rail-title {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n font-size: 0.7rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: hsl(var(--ra-muted-text));\n}\n.ra-shell .ra-preview-rail-body {\n flex: 1;\n overflow-y: auto;\n padding: 1rem;\n}\n.ra-confirm-root {\n position: fixed;\n inset: 0;\n z-index: 2147483000;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1rem;\n background: transparent;\n}\n.ra-confirm-root .ra-confirm-backdrop {\n position: absolute;\n inset: 0;\n background: hsl(0 0% 0% / 0.45);\n backdrop-filter: blur(2px);\n animation: ra-confirm-fade .12s ease-out;\n}\n.ra-confirm-root .ra-confirm-card {\n position: relative;\n width: min(440px, 100%);\n background: hsl(var(--ra-surface));\n color: hsl(var(--ra-text));\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n box-shadow: 0 1px 2px hsl(0 0% 0% / 0.08), 0 24px 48px -12px hsl(0 0% 0% / 0.32);\n padding: 1.25rem;\n animation: ra-confirm-pop .14s ease-out;\n}\n.ra-confirm-root .ra-confirm-header {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n margin-bottom: 0.5rem;\n}\n.ra-confirm-root .ra-confirm-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.75rem;\n height: 1.75rem;\n border-radius: 999px;\n background: hsl(var(--ra-warning, 38 92% 50%) / 0.12);\n color: hsl(var(--ra-warning, 38 92% 50%));\n}\n.ra-confirm-root .ra-confirm-title {\n font-family: var(--ra-font-display);\n font-weight: 600;\n font-size: 1rem;\n margin: 0;\n}\n.ra-confirm-root .ra-confirm-body {\n font-size: 0.875rem;\n color: hsl(var(--ra-muted-text));\n margin: 0 0 1.1rem;\n line-height: 1.45;\n}\n.ra-confirm-root .ra-confirm-actions {\n display: flex;\n justify-content: flex-end;\n gap: 0.5rem;\n flex-wrap: wrap;\n}\n.ra-confirm-root .ra-confirm-btn {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n border: 1px solid transparent;\n border-radius: calc(var(--ra-radius) - 2px);\n padding: 0.45rem 0.85rem;\n font-size: 0.8125rem;\n font-weight: 500;\n cursor: pointer;\n transition:\n background-color .12s ease,\n border-color .12s ease,\n color .12s ease;\n}\n.ra-confirm-root .ra-confirm-btn:focus-visible {\n outline: none;\n box-shadow: 0 0 0 3px var(--ra-focus-ring);\n}\n.ra-confirm-root .ra-confirm-btn-ghost {\n background: transparent;\n color: hsl(var(--ra-muted-text));\n border-color: hsl(var(--ra-border));\n}\n.ra-confirm-root .ra-confirm-btn-ghost:hover {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-text));\n}\n.ra-confirm-root .ra-confirm-btn-danger {\n background: transparent;\n color: hsl(var(--ra-danger, 0 72% 51%));\n border-color: hsl(var(--ra-danger, 0 72% 51%) / 0.45);\n}\n.ra-confirm-root .ra-confirm-btn-danger:hover {\n background: hsl(var(--ra-danger, 0 72% 51%) / 0.08);\n border-color: hsl(var(--ra-danger, 0 72% 51%));\n}\n.ra-confirm-root .ra-confirm-btn-primary {\n background: hsl(var(--ra-accent));\n color: hsl(var(--ra-accent-fg, 0 0% 100%));\n border-color: hsl(var(--ra-accent));\n}\n.ra-confirm-root .ra-confirm-btn-primary:hover {\n filter: brightness(0.95);\n}\n@keyframes ra-confirm-fade {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n@keyframes ra-confirm-pop {\n from {\n opacity: 0;\n transform: translateY(4px) scale(.98);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n.ra-shell .ra-unsaved-banner {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.5rem 0.75rem;\n border: 1px solid hsl(var(--ra-warning, 38 92% 50%) / 0.35);\n background: hsl(var(--ra-warning, 38 92% 50%) / 0.08);\n border-radius: var(--ra-radius);\n font-size: 0.8125rem;\n color: hsl(var(--ra-text));\n animation: ra-unsaved-slide .14s ease-out;\n}\n.ra-shell .ra-unsaved-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: hsl(var(--ra-warning, 38 92% 50%));\n flex-shrink: 0;\n}\n.ra-shell .ra-unsaved-text {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ra-shell .ra-unsaved-context {\n color: hsl(var(--ra-muted-text));\n font-weight: 400;\n}\n.ra-shell .ra-unsaved-error {\n color: hsl(var(--ra-danger, 0 72% 51%));\n font-size: 0.75rem;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n font-weight: 500;\n}\n.ra-shell .ra-unsaved-actions {\n display: inline-flex;\n gap: 0.4rem;\n flex-shrink: 0;\n}\n.ra-shell .ra-unsaved-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n border: 1px solid transparent;\n border-radius: calc(var(--ra-radius) - 2px);\n padding: 0.3rem 0.6rem;\n font-size: 0.75rem;\n font-weight: 500;\n cursor: pointer;\n transition:\n background-color .12s ease,\n border-color .12s ease,\n color .12s ease,\n opacity .12s ease;\n}\n.ra-shell .ra-unsaved-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.ra-shell .ra-unsaved-btn:focus-visible {\n outline: none;\n box-shadow: 0 0 0 3px var(--ra-focus-ring);\n}\n.ra-shell .ra-unsaved-btn-ghost {\n background: transparent;\n color: hsl(var(--ra-muted-text));\n border-color: hsl(var(--ra-border));\n}\n.ra-shell .ra-unsaved-btn-ghost:hover:not(:disabled) {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-unsaved-btn-primary {\n background: hsl(var(--ra-accent));\n color: hsl(var(--ra-accent-fg, 0 0% 100%));\n border-color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-unsaved-btn-primary:hover:not(:disabled) {\n filter: brightness(0.95);\n}\n@keyframes ra-unsaved-slide {\n from {\n opacity: 0;\n transform: translateY(-3px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.ra-shell .ra-clipboard-toast {\n position: fixed;\n bottom: 1.25rem;\n left: 50%;\n transform: translateX(-50%);\n z-index: 90;\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n max-width: min(28rem, calc(100vw - 2rem));\n padding: 0.55rem 0.85rem;\n border-radius: 999px;\n background: hsl(var(--ra-text));\n color: hsl(var(--ra-surface));\n font-size: 0.75rem;\n line-height: 1;\n box-shadow: 0 8px 24px -10px hsl(0 0% 0% / 0.45);\n animation: ra-clipboard-pop 0.18s ease-out both;\n pointer-events: none;\n}\n@keyframes ra-clipboard-pop {\n from {\n opacity: 0;\n transform: translate(-50%, 6px);\n }\n to {\n opacity: 1;\n transform: translate(-50%, 0);\n }\n}\n.ra-shell .ra-row-menu-wrap {\n display: inline-flex;\n align-items: center;\n margin-left: 0.25rem;\n}\n.ra-shell .ra-row-menu-trigger {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.5rem;\n height: 1.5rem;\n border-radius: 0.35rem;\n background: transparent;\n color: hsl(var(--ra-muted-text));\n opacity: 0;\n transition:\n opacity .15s ease,\n background .15s ease,\n color .15s ease;\n border: 1px solid transparent;\n}\n.ra-shell .ra-row:hover .ra-row-menu-trigger,\n.ra-shell .ra-card-hover:hover .ra-row-menu-trigger,\n.ra-shell .ra-row-menu-trigger:focus-visible,\n.ra-shell .ra-row-menu-trigger[aria-expanded=true] {\n opacity: 1;\n}\n.ra-shell .ra-row-menu-trigger:hover {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-row-menu {\n position: absolute;\n right: 0;\n top: calc(100% + 4px);\n z-index: 50;\n min-width: 11rem;\n padding: 0.25rem;\n border-radius: 0.5rem;\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n box-shadow: 0 12px 28px -10px hsl(0 0% 0% / 0.25);\n display: flex;\n flex-direction: column;\n gap: 0.125rem;\n}\n.ra-shell .ra-row-menu-item {\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.4rem 0.55rem;\n border-radius: 0.35rem;\n font-size: 0.75rem;\n color: hsl(var(--ra-text));\n background: transparent;\n border: 0;\n text-align: left;\n width: 100%;\n cursor: pointer;\n}\n.ra-shell .ra-row-menu-item:hover:not(:disabled) {\n background: hsl(var(--ra-muted));\n}\n.ra-shell .ra-row-menu-item:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ra-shell .ra-item-list {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n}\n.ra-shell .ra-item-list-body {\n flex: 1;\n min-height: 0;\n overflow: auto;\n padding: 1rem 1.25rem 1.5rem;\n}\n.ra-shell .ra-item-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n padding: 0.75rem 1.25rem;\n border-bottom: 1px solid hsl(var(--ra-border));\n background: hsl(var(--ra-surface));\n}\n.ra-shell .ra-item-toolbar-title {\n display: flex;\n align-items: baseline;\n gap: 0.5rem;\n min-width: 0;\n}\n.ra-shell .ra-item-toolbar-count {\n font-size: 0.7rem;\n font-weight: 600;\n color: hsl(var(--ra-muted-text));\n background: hsl(var(--ra-muted));\n border: 1px solid hsl(var(--ra-border));\n border-radius: 999px;\n padding: 0.05rem 0.45rem;\n}\n.ra-shell .ra-item-toolbar-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex-shrink: 0;\n}\n.ra-shell .ra-item-table-wrap {\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n background: hsl(var(--ra-surface));\n overflow: hidden;\n box-shadow: var(--ra-card-shadow);\n}\n.ra-shell .ra-item-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 0.85rem;\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-item-table thead th {\n text-align: left;\n font-size: 0.7rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: hsl(var(--ra-muted-text));\n padding: 0.65rem 0.85rem;\n background: hsl(var(--ra-muted) / 0.55);\n border-bottom: 1px solid hsl(var(--ra-border));\n}\n.ra-shell .ra-item-table tbody td {\n padding: 0.65rem 0.85rem;\n border-bottom: 1px solid hsl(var(--ra-border) / 0.7);\n vertical-align: middle;\n}\n.ra-shell .ra-item-table tbody tr:last-child td {\n border-bottom: 0;\n}\n.ra-shell .ra-item-row {\n cursor: pointer;\n transition: background .12s ease;\n}\n.ra-shell .ra-item-row:hover {\n background: var(--ra-row-hover);\n}\n.ra-shell .ra-item-row[data-selected=true] {\n background: var(--ra-row-active-bg);\n}\n.ra-shell .ra-item-row-title {\n font-weight: var(--ra-title-weight);\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-item-row-sub {\n font-size: 0.75rem;\n color: hsl(var(--ra-muted-text));\n margin-top: 0.15rem;\n}\n.ra-shell .ra-item-row-meta {\n font-size: 0.78rem;\n color: hsl(var(--ra-muted-text));\n}\n.ra-shell .ra-item-row-actions {\n text-align: right;\n white-space: nowrap;\n}\n.ra-shell .ra-item-row-actions .ra-row-action + .ra-row-action {\n margin-left: 0.15rem;\n}\n.ra-shell .ra-item-cards {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(var(--ra-item-card-min, 240px), 1fr));\n gap: 0.85rem;\n}\n.ra-shell .ra-item-gallery {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(var(--ra-item-gallery-min, 320px), 1fr));\n gap: 1rem;\n}\n.ra-shell .ra-item-cards[data-card-size=sm] {\n --ra-item-card-min: 180px;\n}\n.ra-shell .ra-item-cards[data-card-size=md] {\n --ra-item-card-min: 240px;\n}\n.ra-shell .ra-item-cards[data-card-size=lg] {\n --ra-item-card-min: 320px;\n gap: 1rem;\n}\n.ra-shell .ra-item-gallery[data-card-size=sm] {\n --ra-item-gallery-min: 240px;\n}\n.ra-shell .ra-item-gallery[data-card-size=md] {\n --ra-item-gallery-min: 320px;\n}\n.ra-shell .ra-item-gallery[data-card-size=lg] {\n --ra-item-gallery-min: 420px;\n gap: 1.25rem;\n}\n.ra-shell .ra-item-cards[data-card-size=lg] .ra-item-card-title,\n.ra-shell .ra-item-gallery[data-card-size=lg] .ra-item-card-title {\n font-size: 0.95rem;\n white-space: normal;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n.ra-shell .ra-item-cards[data-card-size=lg] .ra-item-card-body,\n.ra-shell .ra-item-gallery[data-card-size=lg] .ra-item-card-body {\n padding: 0.85rem 1rem 1rem;\n}\n.ra-shell .ra-item-card {\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: stretch;\n text-align: left;\n padding: 0;\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n overflow: hidden;\n cursor: pointer;\n transition:\n box-shadow .18s ease,\n transform .12s ease,\n border-color .15s ease;\n box-shadow: var(--ra-card-shadow);\n font-family: inherit;\n color: inherit;\n}\n.ra-shell .ra-item-card:hover {\n box-shadow: var(--ra-card-shadow-hover);\n border-color: hsl(var(--ra-accent) / 0.30);\n}\n.ra-shell .ra-item-card[data-selected=true] {\n border-color: hsl(var(--ra-accent) / 0.55);\n box-shadow: var(--ra-card-shadow-hover);\n}\n.ra-shell .ra-item-card-thumb {\n width: 100%;\n aspect-ratio: 1 / 1;\n background:\n linear-gradient(\n 135deg,\n hsl(var(--ra-accent) / 0.12),\n hsl(var(--ra-accent) / 0.04));\n display: flex;\n align-items: center;\n justify-content: center;\n color: hsl(var(--ra-accent));\n overflow: hidden;\n}\n.ra-shell .ra-item-card-thumb--gallery {\n aspect-ratio: 16 / 9;\n}\n.ra-shell .ra-item-card-thumb img {\n width: 100%;\n height: 100%;\n -o-object-fit: cover;\n object-fit: cover;\n}\n.ra-shell .ra-item-card-initials {\n font-family: var(--ra-font-display);\n font-weight: var(--ra-display-weight);\n font-size: 1.5rem;\n letter-spacing: 0.02em;\n}\n.ra-shell .ra-item-card-body {\n padding: 0.65rem 0.8rem 0.85rem;\n min-width: 0;\n}\n.ra-shell .ra-item-card-title {\n font-weight: var(--ra-title-weight);\n font-size: 0.85rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-item-card-sub {\n font-size: 0.75rem;\n color: hsl(var(--ra-muted-text));\n margin-top: 0.15rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-item-card-delete {\n position: absolute;\n top: 0.4rem;\n right: 0.4rem;\n background: hsl(var(--ra-surface) / 0.85);\n backdrop-filter: blur(4px);\n opacity: 0;\n transition: opacity .15s ease;\n}\n.ra-shell .ra-item-card:hover .ra-item-card-delete,\n.ra-shell .ra-item-card:focus-within .ra-item-card-delete {\n opacity: 1;\n}\n.ra-shell .ra-item-nav {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.5rem 1.25rem;\n border-bottom: 1px solid hsl(var(--ra-border));\n background: hsl(var(--ra-surface));\n}\n.ra-shell .ra-item-nav-position {\n font-size: 0.72rem;\n color: hsl(var(--ra-muted-text));\n font-variant-numeric: tabular-nums;\n}\n.ra-shell .ra-item-nav-arrows {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n gap: 0.15rem;\n}\n.ra-shell .ra-item-nav-arrows .ra-row-action[disabled] {\n opacity: 0.35;\n cursor: not-allowed;\n}\n.ra-shell .ra-sibling-rail {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n}\n.ra-shell .ra-sibling-back {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.6rem 0.85rem;\n font-size: 0.75rem;\n font-weight: 500;\n color: hsl(var(--ra-muted-text));\n background: hsl(var(--ra-muted) / 0.5);\n border: 0;\n border-bottom: 1px solid hsl(var(--ra-border));\n cursor: pointer;\n text-align: left;\n transition: background .12s ease, color .12s ease;\n}\n.ra-shell .ra-sibling-back:hover {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-sibling-heading {\n padding: 0.6rem 0.85rem 0.4rem;\n font-size: 0.65rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: hsl(var(--ra-muted-text));\n}\n.ra-shell .ra-sibling-body {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n}\n.ra-shell .ra-sibling-list {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.ra-shell .ra-status-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n border-radius: 9999px;\n}\n.ra-shell .ra-status-icon > svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n.ra-shell .ra-status-icon--own {\n color: hsl(var(--ra-status-own));\n}\n.ra-shell .ra-status-icon--shared {\n color: hsl(var(--ra-status-shared));\n}\n.ra-shell .ra-status-icon--missing {\n color: hsl(var(--ra-status-missing) / 0.7);\n}\n.ra-shell .ra-row-status {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.5rem;\n height: 1.5rem;\n flex-shrink: 0;\n}\n.ra-shell .ra-row-scope {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.25rem;\n height: 1.25rem;\n border-radius: calc(var(--ra-radius) * 0.5);\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-muted-text));\n flex-shrink: 0;\n margin-left: auto;\n opacity: 0.55;\n transition:\n opacity .12s ease,\n color .12s ease,\n background .12s ease;\n}\n.ra-shell .ra-row:hover .ra-row-scope {\n opacity: 0.85;\n}\n.ra-shell .ra-row[data-selected=true] .ra-row-scope {\n opacity: 1;\n background: hsl(var(--ra-accent) / 0.12);\n color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-row[data-tone=own] .ra-row-sub {\n color: hsl(var(--ra-status-own));\n}\n.ra-shell .ra-row[data-tone=shared] .ra-row-sub {\n color: hsl(var(--ra-status-shared));\n}\n.ra-shell .ra-row[data-selected=true] {\n background:\n linear-gradient(\n 90deg,\n hsl(var(--ra-accent) / 0.10) 0%,\n hsl(var(--ra-accent) / 0.04) 100%);\n border-left-width: 3px;\n border-left-color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-dirty-pip {\n display: inline-block;\n width: 0.45rem;\n height: 0.45rem;\n border-radius: 9999px;\n background: hsl(var(--ra-warning));\n box-shadow: 0 0 0 2px hsl(var(--ra-warning) / 0.18);\n flex-shrink: 0;\n}\n.ra-shell .ra-error-pip {\n display: inline-block;\n width: 0.45rem;\n height: 0.45rem;\n border-radius: 9999px;\n background: hsl(var(--ra-danger, 0 72% 51%));\n box-shadow: 0 0 0 2px hsl(var(--ra-danger, 0 72% 51%) / 0.22);\n flex-shrink: 0;\n}\n.ra-shell .ra-group-summary {\n background: transparent;\n}\n.ra-shell {\n position: relative;\n}\n.ra-shell .ra-help-float {\n position: absolute;\n top: 0.65rem;\n right: 0.85rem;\n z-index: 5;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.6rem;\n height: 1.6rem;\n padding: 0;\n color: hsl(var(--ra-muted-text));\n background: hsl(var(--ra-surface) / 0.85);\n backdrop-filter: blur(6px);\n border: 1px solid hsl(var(--ra-border));\n border-radius: 999px;\n cursor: pointer;\n transition:\n color .12s ease,\n background .12s ease,\n border-color .12s ease;\n}\n.ra-shell .ra-help-float:hover {\n color: hsl(var(--ra-accent));\n border-color: hsl(var(--ra-accent) / 0.4);\n background: hsl(var(--ra-surface));\n}\n.ra-shell .ra-help-float svg {\n width: 0.95rem;\n height: 0.95rem;\n}\n.ra-shell .ra-help-float > span {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n.ra-shell .ra-preview-reopen {\n position: absolute;\n top: 50%;\n right: 0;\n transform: translateY(-50%);\n z-index: 4;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.4rem;\n padding: 0.65rem 0.45rem;\n background: hsl(var(--ra-surface));\n color: hsl(var(--ra-muted-text));\n border: 1px solid hsl(var(--ra-border));\n border-right: 0;\n border-radius: calc(var(--ra-radius) * 0.85) 0 0 calc(var(--ra-radius) * 0.85);\n box-shadow: var(--ra-card-shadow);\n cursor: pointer;\n transition:\n color .12s ease,\n background .12s ease,\n padding-right .15s ease;\n writing-mode: vertical-rl;\n font-size: 0.7rem;\n font-weight: 600;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n}\n.ra-shell .ra-preview-reopen:hover {\n color: hsl(var(--ra-accent));\n background: hsl(var(--ra-accent) / 0.04);\n padding-right: 0.6rem;\n}\n.ra-shell .ra-preview-reopen svg {\n width: 0.85rem;\n height: 0.85rem;\n writing-mode: horizontal-tb;\n}\n.ra-shell .ra-unsaved-tray {\n position: relative;\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.5rem 0.75rem;\n border: 1px solid hsl(var(--ra-warning, 38 92% 50%) / 0.35);\n background: hsl(var(--ra-warning, 38 92% 50%) / 0.08);\n border-radius: var(--ra-radius);\n font-size: 0.8125rem;\n color: hsl(var(--ra-text));\n animation: ra-unsaved-slide .14s ease-out;\n}\n.ra-shell .ra-unsaved-count {\n flex: 1;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n padding: 0.15rem 0.4rem;\n margin: -0.15rem -0.4rem;\n background: transparent;\n border: 0;\n color: inherit;\n font: inherit;\n font-weight: 500;\n text-align: left;\n cursor: pointer;\n border-radius: calc(var(--ra-radius) - 4px);\n}\n.ra-shell .ra-unsaved-count:hover {\n background: hsl(var(--ra-muted) / 0.6);\n}\n.ra-shell .ra-unsaved-error-chip {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n border: 1px solid hsl(var(--ra-danger, 0 72% 51%) / 0.35);\n background: hsl(var(--ra-danger, 0 72% 51%) / 0.08);\n color: hsl(var(--ra-danger, 0 72% 51%));\n border-radius: 999px;\n padding: 0.15rem 0.55rem;\n font-size: 0.7rem;\n font-weight: 500;\n cursor: pointer;\n}\n.ra-shell .ra-unsaved-error-chip:hover {\n filter: brightness(0.97);\n}\n.ra-shell .ra-unsaved-popover {\n position: absolute;\n top: calc(100% + 6px);\n left: 0;\n z-index: 60;\n min-width: 18rem;\n max-height: 18rem;\n overflow: auto;\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n box-shadow: 0 12px 30px -10px hsl(0 0% 0% / 0.25);\n padding: 0.3rem;\n display: flex;\n flex-direction: column;\n gap: 0.15rem;\n}\n.ra-shell .ra-unsaved-popover-row {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.4rem 0.55rem;\n background: transparent;\n border: 0;\n border-radius: calc(var(--ra-radius) - 2px);\n cursor: pointer;\n text-align: left;\n font: inherit;\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-unsaved-popover-row:hover {\n background: hsl(var(--ra-muted));\n}\n.ra-shell .ra-unsaved-popover-dot {\n width: 0.5rem;\n height: 0.5rem;\n border-radius: 999px;\n flex-shrink: 0;\n}\n.ra-shell .ra-unsaved-popover-label {\n flex: 1;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n font-size: 0.8125rem;\n}\n.ra-shell .ra-unsaved-popover-ctx {\n color: hsl(var(--ra-muted-text));\n font-size: 0.7rem;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n.ra-shell .ra-unsaved-popover-err {\n color: hsl(var(--ra-danger, 0 72% 51%));\n font-size: 0.7rem;\n font-weight: 500;\n}\n.ra-saveall-overlay {\n position: fixed;\n inset: 0;\n z-index: 100;\n background: hsl(0 0% 0% / 0.45);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1rem;\n animation: ra-confirm-fade .12s ease-out;\n}\n.ra-saveall-card {\n background: hsl(var(--ra-surface));\n color: hsl(var(--ra-text));\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n box-shadow: 0 24px 48px -16px hsl(0 0% 0% / 0.45);\n width: min(28rem, 100%);\n max-height: min(80vh, 36rem);\n display: flex;\n flex-direction: column;\n animation: ra-confirm-pop .14s ease-out;\n}\n.ra-saveall-header {\n padding: 1rem 1rem 0.5rem;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.ra-saveall-title {\n font-weight: 600;\n font-size: 0.95rem;\n}\n.ra-saveall-progress {\n height: 4px;\n background: hsl(var(--ra-muted));\n border-radius: 999px;\n overflow: hidden;\n}\n.ra-saveall-progress-bar {\n height: 100%;\n background: hsl(var(--ra-accent));\n transition: width .2s ease;\n}\n.ra-saveall-counter {\n color: hsl(var(--ra-muted-text));\n font-size: 0.75rem;\n font-variant-numeric: tabular-nums;\n}\n.ra-saveall-list {\n list-style: none;\n margin: 0;\n padding: 0.25rem 0.5rem;\n overflow: auto;\n flex: 1;\n}\n.ra-saveall-row {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.45rem 0.5rem;\n border-radius: calc(var(--ra-radius) - 4px);\n font-size: 0.8125rem;\n}\n.ra-saveall-row[data-status=saving] {\n background: hsl(var(--ra-accent) / 0.06);\n}\n.ra-saveall-row[data-status=saved] {\n color: hsl(var(--ra-muted-text));\n}\n.ra-saveall-row[data-status=error] {\n background: hsl(var(--ra-danger, 0 72% 51%) / 0.06);\n}\n.ra-saveall-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n color: hsl(var(--ra-muted-text));\n}\n.ra-saveall-row[data-status=saved] .ra-saveall-icon {\n color: hsl(var(--ra-success, 142 71% 45%));\n}\n.ra-saveall-row[data-status=saving] .ra-saveall-icon {\n color: hsl(var(--ra-accent));\n}\n.ra-saveall-row[data-status=error] .ra-saveall-icon {\n color: hsl(var(--ra-danger, 0 72% 51%));\n}\n.ra-saveall-label {\n flex: 1;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-saveall-err {\n color: hsl(var(--ra-danger, 0 72% 51%));\n font-size: 0.7rem;\n max-width: 12rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-saveall-actions {\n padding: 0.75rem 1rem 1rem;\n display: flex;\n justify-content: flex-end;\n gap: 0.4rem;\n border-top: 1px solid hsl(var(--ra-border));\n}\n.ra-spin {\n animation: ra-spin 1s linear infinite;\n}\n@keyframes ra-spin {\n to {\n transform: rotate(360deg);\n }\n}\n");
|
|
5466
|
+
styleInject(".ra-shell {\n color: hsl(var(--ra-text));\n background: hsl(var(--ra-page-bg));\n font-family: var(--ra-font-ui);\n}\n.ra-shell *,\n.ra-shell *::before,\n.ra-shell *::after {\n box-sizing: border-box;\n}\n.ra-shell .ra-card {\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n box-shadow: var(--ra-card-shadow);\n}\n.ra-shell .ra-card-hover {\n transition:\n box-shadow .18s ease,\n transform .18s ease,\n border-color .18s ease;\n}\n.ra-shell .ra-card-hover:hover {\n box-shadow: var(--ra-card-shadow-hover);\n}\n.ra-shell .ra-display {\n font-family: var(--ra-font-display);\n font-weight: var(--ra-display-weight);\n letter-spacing: -0.01em;\n}\n.ra-shell .ra-title {\n font-weight: var(--ra-title-weight);\n}\n.ra-shell :where(button, [role=button], input, select, textarea, a):focus-visible {\n outline: none;\n box-shadow: 0 0 0 3px var(--ra-focus-ring);\n border-radius: calc(var(--ra-radius) * 0.6);\n}\n.ra-shell .ra-header {\n display: block;\n width: 100%;\n}\n.ra-shell .ra-header__main {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: flex-start;\n gap: 0.55rem;\n}\n.ra-shell .ra-header-aside {\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n flex-shrink: 0;\n}\n.ra-shell .ra-header-icon {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: flex-start;\n background: transparent;\n color: hsl(var(--ra-text));\n border: 0;\n padding: 0;\n margin-top: 0.1rem;\n}\n.ra-shell .ra-header-icon > svg {\n width: 1.05rem;\n height: 1.05rem;\n}\n.ra-shell .ra-header-text {\n flex: 1;\n min-width: 0;\n}\n.ra-shell .ra-header-title {\n font-family: var(--ra-font-display);\n font-weight: 700;\n font-size: 1.2rem;\n line-height: 1.2;\n color: hsl(var(--ra-text));\n letter-spacing: -0.015em;\n margin: 0;\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n}\n.ra-shell .ra-header-subtitle {\n font-size: 0.78rem;\n color: hsl(var(--ra-muted-text));\n margin-top: 0.1rem;\n line-height: 1.3;\n}\n.ra-shell .ra-header-stats {\n display: flex;\n align-items: stretch;\n gap: 0.15rem;\n padding: 0.15rem 0.4rem;\n border-radius: calc(var(--ra-radius) * 0.75);\n background: hsl(var(--ra-surface) / 0.7);\n border: 1px solid hsl(var(--ra-border));\n}\n.ra-shell .ra-header-stats--titled {\n flex-direction: column;\n align-items: stretch;\n padding: 0.4rem 0.55rem;\n gap: 0.3rem;\n}\n.ra-shell .ra-header-stats .ra-stats-items {\n display: flex;\n align-items: stretch;\n gap: 0.15rem;\n}\n.ra-shell .ra-header-stats .ra-stats-heading {\n display: flex;\n align-items: center;\n gap: 0.35rem;\n color: hsl(var(--ra-muted-text));\n font-size: 0.65rem;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n.ra-shell .ra-header-stats .ra-stats-heading-icon {\n display: inline-flex;\n align-items: center;\n color: hsl(var(--ra-text));\n opacity: 0.75;\n}\n.ra-shell .ra-header-stats .ra-stats-heading-icon > svg {\n width: 0.85rem;\n height: 0.85rem;\n}\n.ra-shell .ra-stat {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 0.15rem 0.45rem;\n min-width: 2.5rem;\n}\n.ra-shell .ra-stat-value {\n font-family: var(--ra-font-display);\n font-weight: var(--ra-display-weight);\n font-size: 0.85rem;\n color: hsl(var(--ra-text));\n line-height: 1;\n}\n.ra-shell .ra-stat-label {\n font-size: 0.6rem;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: hsl(var(--ra-muted-text));\n margin-top: 0.15rem;\n}\n.ra-shell .ra-stat-divider {\n width: 1px;\n background: hsl(var(--ra-border));\n margin: 0.25rem 0;\n}\n.ra-shell .ra-header-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n.ra-shell .ra-tabs {\n display: flex;\n gap: 0.25rem;\n padding: 0.25rem;\n background: hsl(var(--sl-control-bg));\n border-radius: var(--sl-control-radius);\n border: 1px solid hsl(var(--sl-control-border));\n}\n.ra-shell .ra-tab {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.4rem 0.7rem;\n border-radius: calc(var(--sl-control-radius) - 2px);\n font-size: 0.78rem;\n font-weight: var(--sl-control-weight);\n color: hsl(var(--sl-control-fg));\n background: transparent;\n border: 0;\n cursor: pointer;\n transition:\n background .15s ease,\n color .15s ease,\n transform .15s ease;\n white-space: nowrap;\n}\n.ra-shell .ra-tab:hover {\n background: hsl(var(--sl-control-hover-bg) / 0.10);\n color: hsl(var(--sl-control-hover-fg));\n}\n.ra-shell .ra-tab:focus-visible {\n outline: none;\n box-shadow: 0 0 0 2px hsl(var(--sl-control-focus-ring) / 0.45);\n}\n.ra-shell .ra-tab[aria-selected=true] {\n background: hsl(var(--sl-control-active-bg));\n color: hsl(var(--sl-control-active-fg));\n border-color: hsl(var(--sl-control-active-bd));\n font-weight: var(--sl-control-active-weight);\n box-shadow: 0 1px 2px hsl(var(--sl-control-active-bg) / 0.25);\n}\n.ra-shell .ra-tab[aria-selected=true]:hover {\n background: hsl(var(--sl-control-active-bg) / 0.92);\n color: hsl(var(--sl-control-active-fg));\n}\n.ra-shell .ra-tab[aria-selected=true] .ra-tab-icon {\n color: hsl(var(--sl-control-active-fg));\n}\n.ra-shell .ra-tab[disabled] {\n opacity: .5;\n cursor: not-allowed;\n}\n.ra-shell .ra-tab-count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 1.25rem;\n padding: 0 0.35rem;\n height: 1.1rem;\n border-radius: 999px;\n background: hsl(var(--sl-control-active-fg) / 0.20);\n color: hsl(var(--sl-control-active-fg));\n font-size: 0.625rem;\n font-weight: 600;\n line-height: 1;\n}\n.ra-shell .ra-tab[aria-selected=false] .ra-tab-count {\n background: hsl(var(--sl-control-fg) / 0.15);\n color: hsl(var(--sl-control-fg));\n}\n.ra-shell[data-density=compact] .ra-row {\n padding-block: 0.4rem;\n}\n.ra-shell .ra-row {\n display: flex;\n align-items: center;\n gap: 0.55rem;\n width: 100%;\n text-align: left;\n padding: 0.45rem 0.75rem;\n border-left: 3px solid transparent;\n background: transparent;\n border-bottom: 1px solid transparent;\n transition: background .12s ease, border-color .12s ease;\n cursor: pointer;\n color: hsl(var(--ra-text));\n font-family: inherit;\n}\n.ra-shell .ra-row + .ra-row {\n border-top: 1px solid hsl(var(--ra-border) / 0.6);\n}\n.ra-shell .ra-row:hover {\n background: var(--ra-row-hover);\n}\n.ra-shell .ra-row[data-selected=true] {\n background: var(--ra-row-active-bg);\n border-left-color: var(--ra-row-active-bd);\n}\n.ra-shell .ra-row-compact {\n padding-block: 0.3rem;\n}\n.ra-shell .ra-row-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.5rem;\n height: 1.5rem;\n border-radius: calc(var(--ra-radius) * 0.6);\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-muted-text));\n flex-shrink: 0;\n}\n.ra-shell .ra-row[data-selected=true] .ra-row-icon {\n background: hsl(var(--ra-accent) / 0.15);\n color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-row-body {\n flex: 1;\n min-width: 0;\n}\n.ra-shell .ra-row-title {\n font-weight: var(--ra-title-weight);\n font-size: 0.8125rem;\n line-height: 1.2;\n color: hsl(var(--ra-text));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-row-sub {\n font-size: 0.6875rem;\n color: hsl(var(--ra-muted-text));\n margin-top: 0.05rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-row-rule-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 0.2rem;\n margin-top: 0.2rem;\n}\n.ra-shell .ra-rule-chip {\n display: inline-flex;\n align-items: center;\n max-width: 100%;\n padding: 0.05rem 0.4rem;\n border-radius: 999px;\n font-size: 0.625rem;\n font-weight: 500;\n line-height: 1.4;\n background: hsl(var(--ra-accent) / 0.10);\n color: hsl(var(--ra-accent));\n border: 1px solid hsl(var(--ra-accent) / 0.20);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-rule-chip-more {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-muted-text));\n border-color: hsl(var(--ra-border));\n}\n.ra-shell[data-density=compact] .ra-row-rule-chips {\n margin-top: 0.15rem;\n gap: 0.15rem;\n}\n.ra-shell[data-density=compact] .ra-rule-chip {\n font-size: 0.6rem;\n padding: 0.02rem 0.35rem;\n}\n.ra-shell .ra-rule-filters {\n display: flex;\n flex-direction: column;\n gap: 0.3rem;\n}\n.ra-shell .ra-rule-filters-row {\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n}\n.ra-shell .ra-rule-filter-chip {\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n padding: 0.15rem 0.5rem;\n border-radius: 999px;\n font-size: 0.65rem;\n font-weight: 500;\n line-height: 1.4;\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-muted-text));\n border: 1px solid hsl(var(--ra-border));\n cursor: pointer;\n transition:\n background .12s ease,\n color .12s ease,\n border-color .12s ease;\n max-width: 100%;\n}\n.ra-shell .ra-rule-filter-chip:hover {\n background: hsl(var(--ra-accent) / 0.10);\n color: hsl(var(--ra-text));\n border-color: hsl(var(--ra-accent) / 0.25);\n}\n.ra-shell .ra-rule-filter-chip[data-active=true] {\n background: hsl(var(--ra-accent) / 0.15);\n color: hsl(var(--ra-accent));\n border-color: hsl(var(--ra-accent) / 0.40);\n}\n.ra-shell .ra-rule-filter-chip[data-tone=complexity][data-active=true] {\n background: hsl(var(--ra-info) / 0.15);\n color: hsl(var(--ra-info));\n border-color: hsl(var(--ra-info) / 0.40);\n}\n.ra-shell .ra-rule-filter-chip-label {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 9rem;\n}\n.ra-shell .ra-rule-filter-chip-count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 1.1rem;\n height: 1rem;\n padding: 0 0.3rem;\n border-radius: 999px;\n background: hsl(var(--ra-surface));\n color: hsl(var(--ra-muted-text));\n font-size: 0.6rem;\n font-weight: 600;\n}\n.ra-shell .ra-rule-filter-chip[data-active=true] .ra-rule-filter-chip-count {\n background: hsl(var(--ra-accent) / 0.18);\n color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-rule-filter-clear {\n align-self: flex-start;\n background: transparent;\n border: 0;\n padding: 0;\n color: hsl(var(--ra-muted-text));\n font-size: 0.65rem;\n cursor: pointer;\n text-decoration: underline;\n text-decoration-style: dotted;\n}\n.ra-shell .ra-rule-filter-clear:hover {\n color: hsl(var(--ra-text));\n}\n.ra-shell[data-density=compact] .ra-row {\n padding-block: 0.3rem;\n gap: 0.45rem;\n}\n.ra-shell[data-density=compact] .ra-row-title {\n font-size: 0.78125rem;\n}\n.ra-shell .ra-row-actions {\n display: inline-flex;\n align-items: center;\n gap: 0.15rem;\n margin-left: auto;\n opacity: 0;\n transition: opacity .15s ease;\n}\n.ra-shell .ra-row:hover .ra-row-actions,\n.ra-shell .ra-row:focus-within .ra-row-actions {\n opacity: 1;\n}\n.ra-shell .ra-row-action {\n width: 1.6rem;\n height: 1.6rem;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 999px;\n background: transparent;\n color: hsl(var(--ra-muted-text));\n border: 0;\n cursor: pointer;\n transition: background .15s ease, color .15s ease;\n}\n.ra-shell .ra-row-action:hover {\n background: hsl(var(--ra-accent) / 0.10);\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-row-action[data-tone=danger]:hover {\n background: hsl(var(--ra-danger) / 0.12);\n color: hsl(var(--ra-danger));\n}\n.ra-shell .ra-chip {\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n padding: 0.15rem 0.5rem;\n border-radius: 999px;\n font-size: 0.6875rem;\n font-weight: 500;\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-muted-text));\n border: 1px solid hsl(var(--ra-border));\n white-space: nowrap;\n max-width: 14rem;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-chip[data-tone=success] {\n background: hsl(var(--ra-success) / 0.12);\n color: hsl(var(--ra-success));\n border-color: hsl(var(--ra-success) / 0.30);\n}\n.ra-shell .ra-chip[data-tone=warning] {\n background: hsl(var(--ra-warning) / 0.14);\n color: hsl(var(--ra-warning));\n border-color: hsl(var(--ra-warning) / 0.35);\n}\n.ra-shell .ra-chip[data-tone=info] {\n background: hsl(var(--ra-info) / 0.10);\n color: hsl(var(--ra-info));\n border-color: hsl(var(--ra-info) / 0.30);\n}\n.ra-shell .ra-chip[data-tone=danger] {\n background: hsl(var(--ra-danger) / 0.10);\n color: hsl(var(--ra-danger));\n border-color: hsl(var(--ra-danger) / 0.30);\n}\n.ra-shell .ra-chip[data-tone=muted] {\n background: transparent;\n color: hsl(var(--ra-muted-text));\n border-style: dashed;\n}\n.ra-shell .ra-group {\n border-bottom: 1px solid hsl(var(--ra-border));\n}\n.ra-shell .ra-group:last-child {\n border-bottom: 0;\n}\n.ra-shell .ra-group-summary {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n width: 100%;\n padding: 0.5rem 0.85rem;\n background: hsl(var(--ra-muted) / 0.6);\n font-size: 0.7rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: hsl(var(--ra-muted-text));\n border: 0;\n cursor: pointer;\n transition: background .12s ease;\n}\n.ra-shell .ra-group-summary:hover {\n background: hsl(var(--ra-muted));\n}\n.ra-shell .ra-group-summary .ra-group-chevron {\n transition: transform .15s ease;\n}\n.ra-shell .ra-group[data-open=false] .ra-group-chevron {\n transform: rotate(-90deg);\n}\n.ra-shell .ra-group-name {\n flex: 1;\n text-align: left;\n}\n.ra-shell .ra-group-count {\n font-size: 0.65rem;\n font-weight: 600;\n color: hsl(var(--ra-muted-text));\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n border-radius: 999px;\n padding: 0.05rem 0.4rem;\n}\n.ra-shell .ra-group[data-open=false] .ra-group-body {\n display: none;\n}\n.ra-shell .ra-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n padding: 2.5rem 1.5rem;\n gap: 0.75rem;\n}\n.ra-shell .ra-empty-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 3.25rem;\n height: 3.25rem;\n border-radius: 999px;\n background: hsl(var(--ra-accent) / 0.08);\n color: hsl(var(--ra-accent));\n margin-bottom: 0.25rem;\n}\n.ra-shell .ra-empty-title {\n font-family: var(--ra-font-display);\n font-weight: var(--ra-display-weight);\n font-size: 1rem;\n color: hsl(var(--ra-text));\n margin: 0;\n letter-spacing: -0.01em;\n}\n.ra-shell .ra-empty-body {\n font-size: 0.8125rem;\n color: hsl(var(--ra-muted-text));\n max-width: 22rem;\n line-height: 1.45;\n}\n.ra-shell .ra-empty-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-top: 0.25rem;\n flex-wrap: wrap;\n justify-content: center;\n}\n.ra-shell .ra-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.45rem 0.85rem;\n border-radius: calc(var(--ra-radius) * 0.7);\n font-size: 0.8125rem;\n font-weight: 500;\n border: 1px solid hsl(var(--ra-border));\n background: hsl(var(--ra-surface));\n color: hsl(var(--ra-text));\n cursor: pointer;\n transition:\n background .15s ease,\n border-color .15s ease,\n box-shadow .15s ease,\n transform .1s ease;\n}\n.ra-shell .ra-btn:hover {\n background: hsl(var(--ra-muted));\n box-shadow: var(--ra-card-shadow);\n}\n.ra-shell .ra-btn:active {\n transform: translateY(1px);\n}\n.ra-shell .ra-btn[data-variant=primary] {\n background: hsl(var(--ra-accent));\n color: hsl(var(--ra-surface));\n border-color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-btn[data-variant=primary]:hover {\n background: hsl(var(--ra-accent) / 0.92);\n}\n.ra-shell .ra-btn[data-variant=ghost] {\n background: transparent;\n border-color: transparent;\n color: hsl(var(--ra-muted-text));\n}\n.ra-shell .ra-btn[data-variant=ghost]:hover {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-btn[data-variant=danger] {\n color: hsl(var(--ra-danger));\n}\n.ra-shell .ra-btn[data-variant=danger]:hover {\n background: hsl(var(--ra-danger) / 0.10);\n border-color: hsl(var(--ra-danger) / 0.40);\n}\n.ra-shell .ra-intro {\n position: relative;\n display: flex;\n align-items: center;\n gap: 0.55rem;\n padding: 0.4rem 2rem 0.4rem 0.5rem;\n border-radius: var(--ra-radius);\n border: 1px solid hsl(var(--ra-info) / 0.30);\n background: hsl(var(--ra-info) / 0.08);\n}\n.ra-shell .ra-intro[data-tone=success] {\n border-color: hsl(var(--ra-success) / 0.30);\n background: hsl(var(--ra-success) / 0.08);\n}\n.ra-shell .ra-intro[data-tone=warning] {\n border-color: hsl(var(--ra-warning) / 0.35);\n background: hsl(var(--ra-warning) / 0.10);\n}\n.ra-shell .ra-intro-icon {\n flex-shrink: 0;\n width: 1.5rem;\n height: 1.5rem;\n border-radius: 999px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: hsl(var(--ra-info) / 0.18);\n color: hsl(var(--ra-info));\n}\n.ra-shell .ra-intro[data-tone=success] .ra-intro-icon {\n background: hsl(var(--ra-success) / 0.18);\n color: hsl(var(--ra-success));\n}\n.ra-shell .ra-intro[data-tone=warning] .ra-intro-icon {\n background: hsl(var(--ra-warning) / 0.20);\n color: hsl(var(--ra-warning));\n}\n.ra-shell .ra-intro-body {\n flex: 1;\n min-width: 0;\n}\n.ra-shell .ra-intro-title {\n font-family: var(--ra-font-display);\n font-weight: var(--ra-title-weight);\n font-size: 0.8rem;\n color: hsl(var(--ra-text));\n margin: 0;\n line-height: 1.2;\n display: inline;\n}\n.ra-shell .ra-intro-text {\n font-size: 0.78rem;\n color: hsl(var(--ra-text) / 0.85);\n line-height: 1.35;\n display: inline;\n margin-left: 0.4rem;\n}\n.ra-shell .ra-intro-dismiss {\n position: absolute;\n top: 50%;\n right: 0.35rem;\n transform: translateY(-50%);\n width: 1.4rem;\n height: 1.4rem;\n border-radius: 999px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: 0;\n color: hsl(var(--ra-muted-text));\n cursor: pointer;\n padding: 0;\n flex-shrink: 0;\n}\n.ra-shell .ra-intro-dismiss:hover {\n background: hsl(var(--ra-text) / 0.06);\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-bulk-menu {\n min-width: 12rem;\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n border-radius: calc(var(--ra-radius) * 0.85);\n box-shadow: var(--ra-card-shadow-hover);\n padding: 0.3rem;\n z-index: 60;\n}\n.ra-shell .ra-bulk-item {\n display: flex;\n align-items: center;\n gap: 0.55rem;\n width: 100%;\n padding: 0.45rem 0.6rem;\n border-radius: calc(var(--ra-radius) * 0.6);\n font-size: 0.8125rem;\n color: hsl(var(--ra-text));\n background: transparent;\n border: 0;\n cursor: pointer;\n text-align: left;\n transition: background .12s ease, color .12s ease;\n}\n.ra-shell .ra-bulk-item:hover {\n background: hsl(var(--ra-muted));\n}\n.ra-shell .ra-bulk-item[data-tone=danger] {\n color: hsl(var(--ra-danger));\n}\n.ra-shell .ra-bulk-item[data-tone=danger]:hover {\n background: hsl(var(--ra-danger) / 0.10);\n}\n.ra-shell .ra-bulk-divider {\n height: 1px;\n background: hsl(var(--ra-border));\n margin: 0.25rem 0;\n}\n.ra-shell .ra-preview-rail {\n background: hsl(var(--ra-surface));\n border-left: 1px solid hsl(var(--ra-border));\n box-shadow: -4px 0 16px hsl(var(--ra-accent) / 0.04);\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n}\n.ra-shell .ra-preview-rail-header {\n position: sticky;\n top: 0;\n z-index: 1;\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1rem;\n background:\n linear-gradient(\n 180deg,\n hsl(var(--ra-surface)) 0%,\n hsl(var(--ra-surface) / 0.92) 100%);\n border-bottom: 1px solid hsl(var(--ra-border));\n backdrop-filter: blur(6px);\n}\n.ra-shell .ra-preview-rail-title {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n font-size: 0.7rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: hsl(var(--ra-muted-text));\n}\n.ra-shell .ra-preview-rail-body {\n flex: 1;\n overflow-y: auto;\n padding: 1rem;\n}\n.ra-confirm-root {\n position: fixed;\n inset: 0;\n z-index: 2147483000;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1rem;\n background: transparent;\n}\n.ra-confirm-root .ra-confirm-backdrop {\n position: absolute;\n inset: 0;\n background: hsl(0 0% 0% / 0.45);\n backdrop-filter: blur(2px);\n animation: ra-confirm-fade .12s ease-out;\n}\n.ra-confirm-root .ra-confirm-card {\n position: relative;\n width: min(440px, 100%);\n background: hsl(var(--ra-surface));\n color: hsl(var(--ra-text));\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n box-shadow: 0 1px 2px hsl(0 0% 0% / 0.08), 0 24px 48px -12px hsl(0 0% 0% / 0.32);\n padding: 1.25rem;\n animation: ra-confirm-pop .14s ease-out;\n}\n.ra-confirm-root .ra-confirm-header {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n margin-bottom: 0.5rem;\n}\n.ra-confirm-root .ra-confirm-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.75rem;\n height: 1.75rem;\n border-radius: 999px;\n background: hsl(var(--ra-warning, 38 92% 50%) / 0.12);\n color: hsl(var(--ra-warning, 38 92% 50%));\n}\n.ra-confirm-root .ra-confirm-title {\n font-family: var(--ra-font-display);\n font-weight: 600;\n font-size: 1rem;\n margin: 0;\n}\n.ra-confirm-root .ra-confirm-body {\n font-size: 0.875rem;\n color: hsl(var(--ra-muted-text));\n margin: 0 0 1.1rem;\n line-height: 1.45;\n}\n.ra-confirm-root .ra-confirm-actions {\n display: flex;\n justify-content: flex-end;\n gap: 0.5rem;\n flex-wrap: wrap;\n}\n.ra-confirm-root .ra-confirm-btn {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n border: 1px solid transparent;\n border-radius: calc(var(--ra-radius) - 2px);\n padding: 0.45rem 0.85rem;\n font-size: 0.8125rem;\n font-weight: 500;\n cursor: pointer;\n transition:\n background-color .12s ease,\n border-color .12s ease,\n color .12s ease;\n}\n.ra-confirm-root .ra-confirm-btn:focus-visible {\n outline: none;\n box-shadow: 0 0 0 3px var(--ra-focus-ring);\n}\n.ra-confirm-root .ra-confirm-btn-ghost {\n background: transparent;\n color: hsl(var(--ra-muted-text));\n border-color: hsl(var(--ra-border));\n}\n.ra-confirm-root .ra-confirm-btn-ghost:hover {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-text));\n}\n.ra-confirm-root .ra-confirm-btn-danger {\n background: transparent;\n color: hsl(var(--ra-danger, 0 72% 51%));\n border-color: hsl(var(--ra-danger, 0 72% 51%) / 0.45);\n}\n.ra-confirm-root .ra-confirm-btn-danger:hover {\n background: hsl(var(--ra-danger, 0 72% 51%) / 0.08);\n border-color: hsl(var(--ra-danger, 0 72% 51%));\n}\n.ra-confirm-root .ra-confirm-btn-primary {\n background: hsl(var(--ra-accent));\n color: hsl(var(--ra-accent-fg, 0 0% 100%));\n border-color: hsl(var(--ra-accent));\n}\n.ra-confirm-root .ra-confirm-btn-primary:hover {\n filter: brightness(0.95);\n}\n@keyframes ra-confirm-fade {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n@keyframes ra-confirm-pop {\n from {\n opacity: 0;\n transform: translateY(4px) scale(.98);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n.ra-shell .ra-unsaved-banner {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.5rem 0.75rem;\n border: 1px solid hsl(var(--ra-warning, 38 92% 50%) / 0.35);\n background: hsl(var(--ra-warning, 38 92% 50%) / 0.08);\n border-radius: var(--ra-radius);\n font-size: 0.8125rem;\n color: hsl(var(--ra-text));\n animation: ra-unsaved-slide .14s ease-out;\n}\n.ra-shell .ra-unsaved-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: hsl(var(--ra-warning, 38 92% 50%));\n flex-shrink: 0;\n}\n.ra-shell .ra-unsaved-text {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ra-shell .ra-unsaved-context {\n color: hsl(var(--ra-muted-text));\n font-weight: 400;\n}\n.ra-shell .ra-unsaved-error {\n color: hsl(var(--ra-danger, 0 72% 51%));\n font-size: 0.75rem;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n font-weight: 500;\n}\n.ra-shell .ra-unsaved-actions {\n display: inline-flex;\n gap: 0.4rem;\n flex-shrink: 0;\n}\n.ra-shell .ra-unsaved-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n border: 1px solid transparent;\n border-radius: calc(var(--ra-radius) - 2px);\n padding: 0.3rem 0.6rem;\n font-size: 0.75rem;\n font-weight: 500;\n cursor: pointer;\n transition:\n background-color .12s ease,\n border-color .12s ease,\n color .12s ease,\n opacity .12s ease;\n}\n.ra-shell .ra-unsaved-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.ra-shell .ra-unsaved-btn:focus-visible {\n outline: none;\n box-shadow: 0 0 0 3px var(--ra-focus-ring);\n}\n.ra-shell .ra-unsaved-btn-ghost {\n background: transparent;\n color: hsl(var(--ra-muted-text));\n border-color: hsl(var(--ra-border));\n}\n.ra-shell .ra-unsaved-btn-ghost:hover:not(:disabled) {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-unsaved-btn-primary {\n background: hsl(var(--ra-accent));\n color: hsl(var(--ra-accent-fg, 0 0% 100%));\n border-color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-unsaved-btn-primary:hover:not(:disabled) {\n filter: brightness(0.95);\n}\n.sl-aph .ra-unsaved-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n border: 1px solid transparent;\n border-radius: calc(var(--ra-radius, 8px) - 2px);\n padding: 0.3rem 0.6rem;\n font-size: 0.75rem;\n font-weight: 500;\n cursor: pointer;\n transition:\n background-color .12s ease,\n border-color .12s ease,\n color .12s ease,\n opacity .12s ease;\n}\n.sl-aph .ra-unsaved-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.sl-aph .ra-unsaved-btn:focus-visible {\n outline: none;\n box-shadow: 0 0 0 3px var(--ra-focus-ring, 0 0 0 3px hsl(var(--ra-accent) / 0.35));\n}\n.sl-aph .ra-unsaved-btn-ghost {\n background: transparent;\n color: hsl(var(--ra-muted-text));\n border-color: hsl(var(--ra-border));\n}\n.sl-aph .ra-unsaved-btn-ghost:hover:not(:disabled) {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-text));\n}\n.sl-aph .ra-unsaved-btn-primary {\n background: hsl(var(--ra-accent));\n color: hsl(var(--ra-accent-fg, 0 0% 100%));\n border-color: hsl(var(--ra-accent));\n}\n.sl-aph .ra-unsaved-btn-primary:hover:not(:disabled) {\n filter: brightness(0.95);\n}\n.sl-aph .ra-unsaved-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: hsl(var(--ra-warning, 38 92% 50%));\n flex-shrink: 0;\n}\n@keyframes ra-unsaved-slide {\n from {\n opacity: 0;\n transform: translateY(-3px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.ra-shell .ra-unsaved-pill,\n.sl-aph .ra-unsaved-pill {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.25rem 0.5rem 0.25rem 0.6rem;\n border: 1px solid hsl(var(--ra-warning, 38 92% 50%) / 0.35);\n background: hsl(var(--ra-warning, 38 92% 50%) / 0.08);\n border-radius: 999px;\n animation: ra-unsaved-slide .14s ease-out;\n}\n.ra-shell .ra-unsaved-pill .ra-unsaved-pill-text,\n.sl-aph .ra-unsaved-pill .ra-unsaved-pill-text {\n font-size: 0.75rem;\n font-weight: 500;\n color: hsl(var(--ra-text));\n white-space: nowrap;\n margin-right: 0.15rem;\n}\n.ra-shell .ra-clipboard-toast {\n position: fixed;\n bottom: 1.25rem;\n left: 50%;\n transform: translateX(-50%);\n z-index: 90;\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n max-width: min(28rem, calc(100vw - 2rem));\n padding: 0.55rem 0.85rem;\n border-radius: 999px;\n background: hsl(var(--ra-text));\n color: hsl(var(--ra-surface));\n font-size: 0.75rem;\n line-height: 1;\n box-shadow: 0 8px 24px -10px hsl(0 0% 0% / 0.45);\n animation: ra-clipboard-pop 0.18s ease-out both;\n pointer-events: none;\n}\n@keyframes ra-clipboard-pop {\n from {\n opacity: 0;\n transform: translate(-50%, 6px);\n }\n to {\n opacity: 1;\n transform: translate(-50%, 0);\n }\n}\n.ra-shell .ra-row-menu-wrap {\n display: inline-flex;\n align-items: center;\n margin-left: 0.25rem;\n}\n.ra-shell .ra-row-menu-trigger {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.5rem;\n height: 1.5rem;\n border-radius: 0.35rem;\n background: transparent;\n color: hsl(var(--ra-muted-text));\n opacity: 0;\n transition:\n opacity .15s ease,\n background .15s ease,\n color .15s ease;\n border: 1px solid transparent;\n}\n.ra-shell .ra-row:hover .ra-row-menu-trigger,\n.ra-shell .ra-card-hover:hover .ra-row-menu-trigger,\n.ra-shell .ra-row-menu-trigger:focus-visible,\n.ra-shell .ra-row-menu-trigger[aria-expanded=true] {\n opacity: 1;\n}\n.ra-shell .ra-row-menu-trigger:hover {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-row-menu {\n position: absolute;\n right: 0;\n top: calc(100% + 4px);\n z-index: 50;\n min-width: 11rem;\n padding: 0.25rem;\n border-radius: 0.5rem;\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n box-shadow: 0 12px 28px -10px hsl(0 0% 0% / 0.25);\n display: flex;\n flex-direction: column;\n gap: 0.125rem;\n}\n.ra-shell .ra-row-menu-item {\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.4rem 0.55rem;\n border-radius: 0.35rem;\n font-size: 0.75rem;\n color: hsl(var(--ra-text));\n background: transparent;\n border: 0;\n text-align: left;\n width: 100%;\n cursor: pointer;\n}\n.ra-shell .ra-row-menu-item:hover:not(:disabled) {\n background: hsl(var(--ra-muted));\n}\n.ra-shell .ra-row-menu-item:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ra-shell .ra-item-list {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n}\n.ra-shell .ra-item-list-body {\n flex: 1;\n min-height: 0;\n overflow: auto;\n padding: 1rem 1.25rem 1.5rem;\n}\n.ra-shell .ra-item-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n padding: 0.75rem 1.25rem;\n border-bottom: 1px solid hsl(var(--ra-border));\n background: hsl(var(--ra-surface));\n}\n.ra-shell .ra-item-toolbar-title {\n display: flex;\n align-items: baseline;\n gap: 0.5rem;\n min-width: 0;\n}\n.ra-shell .ra-item-toolbar-count {\n font-size: 0.7rem;\n font-weight: 600;\n color: hsl(var(--ra-muted-text));\n background: hsl(var(--ra-muted));\n border: 1px solid hsl(var(--ra-border));\n border-radius: 999px;\n padding: 0.05rem 0.45rem;\n}\n.ra-shell .ra-item-toolbar-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex-shrink: 0;\n}\n.ra-shell .ra-item-table-wrap {\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n background: hsl(var(--ra-surface));\n overflow: hidden;\n box-shadow: var(--ra-card-shadow);\n}\n.ra-shell .ra-item-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 0.85rem;\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-item-table thead th {\n text-align: left;\n font-size: 0.7rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: hsl(var(--ra-muted-text));\n padding: 0.65rem 0.85rem;\n background: hsl(var(--ra-muted) / 0.55);\n border-bottom: 1px solid hsl(var(--ra-border));\n}\n.ra-shell .ra-item-table tbody td {\n padding: 0.65rem 0.85rem;\n border-bottom: 1px solid hsl(var(--ra-border) / 0.7);\n vertical-align: middle;\n}\n.ra-shell .ra-item-table tbody tr:last-child td {\n border-bottom: 0;\n}\n.ra-shell .ra-item-row {\n cursor: pointer;\n transition: background .12s ease;\n}\n.ra-shell .ra-item-row:hover {\n background: var(--ra-row-hover);\n}\n.ra-shell .ra-item-row[data-selected=true] {\n background: var(--ra-row-active-bg);\n}\n.ra-shell .ra-item-row-title {\n font-weight: var(--ra-title-weight);\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-item-row-sub {\n font-size: 0.75rem;\n color: hsl(var(--ra-muted-text));\n margin-top: 0.15rem;\n}\n.ra-shell .ra-item-row-meta {\n font-size: 0.78rem;\n color: hsl(var(--ra-muted-text));\n}\n.ra-shell .ra-item-row-actions {\n text-align: right;\n white-space: nowrap;\n}\n.ra-shell .ra-item-row-actions .ra-row-action + .ra-row-action {\n margin-left: 0.15rem;\n}\n.ra-shell .ra-item-cards {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(var(--ra-item-card-min, 240px), 1fr));\n gap: 0.85rem;\n}\n.ra-shell .ra-item-gallery {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(var(--ra-item-gallery-min, 320px), 1fr));\n gap: 1rem;\n}\n.ra-shell .ra-item-cards[data-card-size=sm] {\n --ra-item-card-min: 180px;\n}\n.ra-shell .ra-item-cards[data-card-size=md] {\n --ra-item-card-min: 240px;\n}\n.ra-shell .ra-item-cards[data-card-size=lg] {\n --ra-item-card-min: 320px;\n gap: 1rem;\n}\n.ra-shell .ra-item-gallery[data-card-size=sm] {\n --ra-item-gallery-min: 240px;\n}\n.ra-shell .ra-item-gallery[data-card-size=md] {\n --ra-item-gallery-min: 320px;\n}\n.ra-shell .ra-item-gallery[data-card-size=lg] {\n --ra-item-gallery-min: 420px;\n gap: 1.25rem;\n}\n.ra-shell .ra-item-cards[data-card-size=lg] .ra-item-card-title,\n.ra-shell .ra-item-gallery[data-card-size=lg] .ra-item-card-title {\n font-size: 0.95rem;\n white-space: normal;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n.ra-shell .ra-item-cards[data-card-size=lg] .ra-item-card-body,\n.ra-shell .ra-item-gallery[data-card-size=lg] .ra-item-card-body {\n padding: 0.85rem 1rem 1rem;\n}\n.ra-shell .ra-item-card {\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: stretch;\n text-align: left;\n padding: 0;\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n overflow: hidden;\n cursor: pointer;\n transition:\n box-shadow .18s ease,\n transform .12s ease,\n border-color .15s ease;\n box-shadow: var(--ra-card-shadow);\n font-family: inherit;\n color: inherit;\n}\n.ra-shell .ra-item-card:hover {\n box-shadow: var(--ra-card-shadow-hover);\n border-color: hsl(var(--ra-accent) / 0.30);\n}\n.ra-shell .ra-item-card[data-selected=true] {\n border-color: hsl(var(--ra-accent) / 0.55);\n box-shadow: var(--ra-card-shadow-hover);\n}\n.ra-shell .ra-item-card-thumb {\n width: 100%;\n aspect-ratio: 1 / 1;\n background:\n linear-gradient(\n 135deg,\n hsl(var(--ra-accent) / 0.12),\n hsl(var(--ra-accent) / 0.04));\n display: flex;\n align-items: center;\n justify-content: center;\n color: hsl(var(--ra-accent));\n overflow: hidden;\n}\n.ra-shell .ra-item-card-thumb--gallery {\n aspect-ratio: 16 / 9;\n}\n.ra-shell .ra-item-card-thumb img {\n width: 100%;\n height: 100%;\n -o-object-fit: cover;\n object-fit: cover;\n}\n.ra-shell .ra-item-card-initials {\n font-family: var(--ra-font-display);\n font-weight: var(--ra-display-weight);\n font-size: 1.5rem;\n letter-spacing: 0.02em;\n}\n.ra-shell .ra-item-card-body {\n padding: 0.65rem 0.8rem 0.85rem;\n min-width: 0;\n}\n.ra-shell .ra-item-card-title {\n font-weight: var(--ra-title-weight);\n font-size: 0.85rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-item-card-sub {\n font-size: 0.75rem;\n color: hsl(var(--ra-muted-text));\n margin-top: 0.15rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-shell .ra-item-card-delete {\n position: absolute;\n top: 0.4rem;\n right: 0.4rem;\n background: hsl(var(--ra-surface) / 0.85);\n backdrop-filter: blur(4px);\n opacity: 0;\n transition: opacity .15s ease;\n}\n.ra-shell .ra-item-card:hover .ra-item-card-delete,\n.ra-shell .ra-item-card:focus-within .ra-item-card-delete {\n opacity: 1;\n}\n.ra-shell .ra-item-nav {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.5rem 1.25rem;\n border-bottom: 1px solid hsl(var(--ra-border));\n background: hsl(var(--ra-surface));\n}\n.ra-shell .ra-item-nav-position {\n font-size: 0.72rem;\n color: hsl(var(--ra-muted-text));\n font-variant-numeric: tabular-nums;\n}\n.ra-shell .ra-item-nav-arrows {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n gap: 0.15rem;\n}\n.ra-shell .ra-item-nav-arrows .ra-row-action[disabled] {\n opacity: 0.35;\n cursor: not-allowed;\n}\n.ra-shell .ra-sibling-rail {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n}\n.ra-shell .ra-sibling-back {\n display: inline-flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.6rem 0.85rem;\n font-size: 0.75rem;\n font-weight: 500;\n color: hsl(var(--ra-muted-text));\n background: hsl(var(--ra-muted) / 0.5);\n border: 0;\n border-bottom: 1px solid hsl(var(--ra-border));\n cursor: pointer;\n text-align: left;\n transition: background .12s ease, color .12s ease;\n}\n.ra-shell .ra-sibling-back:hover {\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-sibling-heading {\n padding: 0.6rem 0.85rem 0.4rem;\n font-size: 0.65rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: hsl(var(--ra-muted-text));\n}\n.ra-shell .ra-sibling-body {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n}\n.ra-shell .ra-sibling-list {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.ra-shell .ra-status-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n border-radius: 9999px;\n}\n.ra-shell .ra-status-icon > svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n.ra-shell .ra-status-icon--own {\n color: hsl(var(--ra-status-own));\n}\n.ra-shell .ra-status-icon--shared {\n color: hsl(var(--ra-status-shared));\n}\n.ra-shell .ra-status-icon--missing {\n color: hsl(var(--ra-status-missing) / 0.7);\n}\n.ra-shell .ra-row-status {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.5rem;\n height: 1.5rem;\n flex-shrink: 0;\n}\n.ra-shell .ra-row-scope {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.25rem;\n height: 1.25rem;\n border-radius: calc(var(--ra-radius) * 0.5);\n background: hsl(var(--ra-muted));\n color: hsl(var(--ra-muted-text));\n flex-shrink: 0;\n margin-left: auto;\n opacity: 0.55;\n transition:\n opacity .12s ease,\n color .12s ease,\n background .12s ease;\n}\n.ra-shell .ra-row:hover .ra-row-scope {\n opacity: 0.85;\n}\n.ra-shell .ra-row[data-selected=true] .ra-row-scope {\n opacity: 1;\n background: hsl(var(--ra-accent) / 0.12);\n color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-row[data-tone=own] .ra-row-sub {\n color: hsl(var(--ra-status-own));\n}\n.ra-shell .ra-row[data-tone=shared] .ra-row-sub {\n color: hsl(var(--ra-status-shared));\n}\n.ra-shell .ra-row[data-selected=true] {\n background:\n linear-gradient(\n 90deg,\n hsl(var(--ra-accent) / 0.10) 0%,\n hsl(var(--ra-accent) / 0.04) 100%);\n border-left-width: 3px;\n border-left-color: hsl(var(--ra-accent));\n}\n.ra-shell .ra-dirty-pip {\n display: inline-block;\n width: 0.45rem;\n height: 0.45rem;\n border-radius: 9999px;\n background: hsl(var(--ra-warning));\n box-shadow: 0 0 0 2px hsl(var(--ra-warning) / 0.18);\n flex-shrink: 0;\n}\n.ra-shell .ra-error-pip {\n display: inline-block;\n width: 0.45rem;\n height: 0.45rem;\n border-radius: 9999px;\n background: hsl(var(--ra-danger, 0 72% 51%));\n box-shadow: 0 0 0 2px hsl(var(--ra-danger, 0 72% 51%) / 0.22);\n flex-shrink: 0;\n}\n.ra-shell .ra-group-summary {\n background: transparent;\n}\n.ra-shell {\n position: relative;\n}\n.ra-shell .ra-help-float {\n position: absolute;\n top: 0.65rem;\n right: 0.85rem;\n z-index: 5;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.6rem;\n height: 1.6rem;\n padding: 0;\n color: hsl(var(--ra-muted-text));\n background: hsl(var(--ra-surface) / 0.85);\n backdrop-filter: blur(6px);\n border: 1px solid hsl(var(--ra-border));\n border-radius: 999px;\n cursor: pointer;\n transition:\n color .12s ease,\n background .12s ease,\n border-color .12s ease;\n}\n.ra-shell .ra-help-float:hover {\n color: hsl(var(--ra-accent));\n border-color: hsl(var(--ra-accent) / 0.4);\n background: hsl(var(--ra-surface));\n}\n.ra-shell .ra-help-float svg {\n width: 0.95rem;\n height: 0.95rem;\n}\n.ra-shell .ra-help-float > span {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n.ra-shell .ra-preview-reopen {\n position: absolute;\n top: 50%;\n right: 0;\n transform: translateY(-50%);\n z-index: 4;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.4rem;\n padding: 0.65rem 0.45rem;\n background: hsl(var(--ra-surface));\n color: hsl(var(--ra-muted-text));\n border: 1px solid hsl(var(--ra-border));\n border-right: 0;\n border-radius: calc(var(--ra-radius) * 0.85) 0 0 calc(var(--ra-radius) * 0.85);\n box-shadow: var(--ra-card-shadow);\n cursor: pointer;\n transition:\n color .12s ease,\n background .12s ease,\n padding-right .15s ease;\n writing-mode: vertical-rl;\n font-size: 0.7rem;\n font-weight: 600;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n}\n.ra-shell .ra-preview-reopen:hover {\n color: hsl(var(--ra-accent));\n background: hsl(var(--ra-accent) / 0.04);\n padding-right: 0.6rem;\n}\n.ra-shell .ra-preview-reopen svg {\n width: 0.85rem;\n height: 0.85rem;\n writing-mode: horizontal-tb;\n}\n.ra-shell .ra-unsaved-tray {\n position: relative;\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.5rem 0.75rem;\n border: 1px solid hsl(var(--ra-warning, 38 92% 50%) / 0.35);\n background: hsl(var(--ra-warning, 38 92% 50%) / 0.08);\n border-radius: var(--ra-radius);\n font-size: 0.8125rem;\n color: hsl(var(--ra-text));\n animation: ra-unsaved-slide .14s ease-out;\n}\n.ra-shell .ra-unsaved-count {\n flex: 1;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n padding: 0.15rem 0.4rem;\n margin: -0.15rem -0.4rem;\n background: transparent;\n border: 0;\n color: inherit;\n font: inherit;\n font-weight: 500;\n text-align: left;\n cursor: pointer;\n border-radius: calc(var(--ra-radius) - 4px);\n}\n.ra-shell .ra-unsaved-count:hover {\n background: hsl(var(--ra-muted) / 0.6);\n}\n.ra-shell .ra-unsaved-error-chip {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n border: 1px solid hsl(var(--ra-danger, 0 72% 51%) / 0.35);\n background: hsl(var(--ra-danger, 0 72% 51%) / 0.08);\n color: hsl(var(--ra-danger, 0 72% 51%));\n border-radius: 999px;\n padding: 0.15rem 0.55rem;\n font-size: 0.7rem;\n font-weight: 500;\n cursor: pointer;\n}\n.ra-shell .ra-unsaved-error-chip:hover {\n filter: brightness(0.97);\n}\n.ra-shell .ra-unsaved-popover {\n position: absolute;\n top: calc(100% + 6px);\n left: 0;\n z-index: 60;\n min-width: 18rem;\n max-height: 18rem;\n overflow: auto;\n background: hsl(var(--ra-surface));\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n box-shadow: 0 12px 30px -10px hsl(0 0% 0% / 0.25);\n padding: 0.3rem;\n display: flex;\n flex-direction: column;\n gap: 0.15rem;\n}\n.ra-shell .ra-unsaved-popover-row {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.4rem 0.55rem;\n background: transparent;\n border: 0;\n border-radius: calc(var(--ra-radius) - 2px);\n cursor: pointer;\n text-align: left;\n font: inherit;\n color: hsl(var(--ra-text));\n}\n.ra-shell .ra-unsaved-popover-row:hover {\n background: hsl(var(--ra-muted));\n}\n.ra-shell .ra-unsaved-popover-dot {\n width: 0.5rem;\n height: 0.5rem;\n border-radius: 999px;\n flex-shrink: 0;\n}\n.ra-shell .ra-unsaved-popover-label {\n flex: 1;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n font-size: 0.8125rem;\n}\n.ra-shell .ra-unsaved-popover-ctx {\n color: hsl(var(--ra-muted-text));\n font-size: 0.7rem;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n.ra-shell .ra-unsaved-popover-err {\n color: hsl(var(--ra-danger, 0 72% 51%));\n font-size: 0.7rem;\n font-weight: 500;\n}\n.ra-saveall-overlay {\n position: fixed;\n inset: 0;\n z-index: 100;\n background: hsl(0 0% 0% / 0.45);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1rem;\n animation: ra-confirm-fade .12s ease-out;\n}\n.ra-saveall-card {\n background: hsl(var(--ra-surface));\n color: hsl(var(--ra-text));\n border: 1px solid hsl(var(--ra-border));\n border-radius: var(--ra-radius);\n box-shadow: 0 24px 48px -16px hsl(0 0% 0% / 0.45);\n width: min(28rem, 100%);\n max-height: min(80vh, 36rem);\n display: flex;\n flex-direction: column;\n animation: ra-confirm-pop .14s ease-out;\n}\n.ra-saveall-header {\n padding: 1rem 1rem 0.5rem;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.ra-saveall-title {\n font-weight: 600;\n font-size: 0.95rem;\n}\n.ra-saveall-progress {\n height: 4px;\n background: hsl(var(--ra-muted));\n border-radius: 999px;\n overflow: hidden;\n}\n.ra-saveall-progress-bar {\n height: 100%;\n background: hsl(var(--ra-accent));\n transition: width .2s ease;\n}\n.ra-saveall-counter {\n color: hsl(var(--ra-muted-text));\n font-size: 0.75rem;\n font-variant-numeric: tabular-nums;\n}\n.ra-saveall-list {\n list-style: none;\n margin: 0;\n padding: 0.25rem 0.5rem;\n overflow: auto;\n flex: 1;\n}\n.ra-saveall-row {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.45rem 0.5rem;\n border-radius: calc(var(--ra-radius) - 4px);\n font-size: 0.8125rem;\n}\n.ra-saveall-row[data-status=saving] {\n background: hsl(var(--ra-accent) / 0.06);\n}\n.ra-saveall-row[data-status=saved] {\n color: hsl(var(--ra-muted-text));\n}\n.ra-saveall-row[data-status=error] {\n background: hsl(var(--ra-danger, 0 72% 51%) / 0.06);\n}\n.ra-saveall-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n color: hsl(var(--ra-muted-text));\n}\n.ra-saveall-row[data-status=saved] .ra-saveall-icon {\n color: hsl(var(--ra-success, 142 71% 45%));\n}\n.ra-saveall-row[data-status=saving] .ra-saveall-icon {\n color: hsl(var(--ra-accent));\n}\n.ra-saveall-row[data-status=error] .ra-saveall-icon {\n color: hsl(var(--ra-danger, 0 72% 51%));\n}\n.ra-saveall-label {\n flex: 1;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-saveall-err {\n color: hsl(var(--ra-danger, 0 72% 51%));\n font-size: 0.7rem;\n max-width: 12rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ra-saveall-actions {\n padding: 0.75rem 1rem 1rem;\n display: flex;\n justify-content: flex-end;\n gap: 0.4rem;\n border-top: 1px solid hsl(var(--ra-border));\n}\n.ra-spin {\n animation: ra-spin 1s linear infinite;\n}\n@keyframes ra-spin {\n to {\n transform: rotate(360deg);\n }\n}\n");
|
|
5467
|
+
var EditorPoolBody = ({
|
|
5468
|
+
editorId,
|
|
5469
|
+
renderEditor
|
|
5470
|
+
}) => {
|
|
5471
|
+
const ctx = useEditorSlotContext(editorId);
|
|
5472
|
+
if (!ctx) return null;
|
|
5473
|
+
return renderEditor(ctx);
|
|
5474
|
+
};
|
|
4700
5475
|
var TOP_LEVEL_SCOPES = ["collection", "rule", "product"];
|
|
4701
5476
|
var WARNED_FACET_DEPRECATED = false;
|
|
4702
5477
|
var DRAFT_ID = "__draft__";
|
|
@@ -4722,7 +5497,23 @@ var productItemToSummary = (p) => {
|
|
|
4722
5497
|
};
|
|
4723
5498
|
};
|
|
4724
5499
|
function RecordsAdminShell(props) {
|
|
4725
|
-
|
|
5500
|
+
const ctx = useMemo(
|
|
5501
|
+
() => ({
|
|
5502
|
+
SL: props.SL,
|
|
5503
|
+
collectionId: props.collectionId,
|
|
5504
|
+
appId: props.appId,
|
|
5505
|
+
recordType: props.recordType
|
|
5506
|
+
}),
|
|
5507
|
+
[props.SL, props.collectionId, props.appId, props.recordType]
|
|
5508
|
+
);
|
|
5509
|
+
return /* @__PURE__ */ jsx(
|
|
5510
|
+
EditorSessionProvider,
|
|
5511
|
+
{
|
|
5512
|
+
ctx,
|
|
5513
|
+
defaultValueFactory: props.defaultData,
|
|
5514
|
+
children: /* @__PURE__ */ jsx(RecordsAdminShellInner, { ...props })
|
|
5515
|
+
}
|
|
5516
|
+
);
|
|
4726
5517
|
}
|
|
4727
5518
|
function RecordsAdminShellInner(props) {
|
|
4728
5519
|
const {
|
|
@@ -4839,6 +5630,7 @@ function RecordsAdminShellInner(props) {
|
|
|
4839
5630
|
() => ({ SL, collectionId, appId, recordType }),
|
|
4840
5631
|
[SL, collectionId, appId, recordType]
|
|
4841
5632
|
);
|
|
5633
|
+
const queryClient = useQueryClient();
|
|
4842
5634
|
const probe = useScopeProbe({ SL, collectionId });
|
|
4843
5635
|
const topLevelScopes = useMemo(() => {
|
|
4844
5636
|
const requested = requestedScopes ?? [];
|
|
@@ -4880,10 +5672,6 @@ function RecordsAdminShellInner(props) {
|
|
|
4880
5672
|
useEffect(() => {
|
|
4881
5673
|
setActiveScope(initialScope);
|
|
4882
5674
|
}, [initialScope]);
|
|
4883
|
-
const [search, setSearch] = useState("");
|
|
4884
|
-
const [filter, setFilter] = useState("all");
|
|
4885
|
-
const [ruleFilters, setRuleFilters] = useState(EMPTY_RULE_FILTERS);
|
|
4886
|
-
const [facetBrowseFilter, setFacetBrowseFilter] = useState(null);
|
|
4887
5675
|
const [selectedRecordId, setSelectedRecordId] = useState(null);
|
|
4888
5676
|
const [draftKind, setDraftKind] = useState(null);
|
|
4889
5677
|
const [ruleWizardStep, setRuleWizardStep] = useState(null);
|
|
@@ -4925,40 +5713,32 @@ function RecordsAdminShellInner(props) {
|
|
|
4925
5713
|
if (requested === "header" && !headerWillRender) return "footer";
|
|
4926
5714
|
return requested;
|
|
4927
5715
|
}, [intro?.reopenAffordance, headerWillRender]);
|
|
4928
|
-
const
|
|
5716
|
+
const browser = useShellBrowser({
|
|
5717
|
+
ctx,
|
|
4929
5718
|
SL,
|
|
4930
5719
|
collectionId,
|
|
4931
|
-
|
|
4932
|
-
|
|
5720
|
+
activeScope,
|
|
5721
|
+
contextScope,
|
|
5722
|
+
probeIsLoading: probe.isLoading,
|
|
5723
|
+
selectedProductId,
|
|
5724
|
+
drillTab,
|
|
5725
|
+
classify: classify2
|
|
4933
5726
|
});
|
|
4934
|
-
const
|
|
4935
|
-
const recordList = useRecordList({
|
|
4936
|
-
ctx,
|
|
4937
|
-
scopeKind: activeScope,
|
|
5727
|
+
const {
|
|
4938
5728
|
search,
|
|
5729
|
+
setSearch,
|
|
4939
5730
|
filter,
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
SL,
|
|
4952
|
-
collectionId,
|
|
4953
|
-
productId: selectedProductId,
|
|
4954
|
-
kind: drillTab === "variant" ? "variant" : null
|
|
4955
|
-
});
|
|
4956
|
-
const batchChildren = useProductChildren({
|
|
4957
|
-
SL,
|
|
4958
|
-
collectionId,
|
|
4959
|
-
productId: selectedProductId,
|
|
4960
|
-
kind: drillTab === "batch" ? "batch" : null
|
|
4961
|
-
});
|
|
5731
|
+
setFilter,
|
|
5732
|
+
ruleFilters,
|
|
5733
|
+
setRuleFilters,
|
|
5734
|
+
facetBrowseFilter,
|
|
5735
|
+
setFacetBrowseFilter,
|
|
5736
|
+
productBrowse,
|
|
5737
|
+
recordList,
|
|
5738
|
+
facetBrowse,
|
|
5739
|
+
variantChildren,
|
|
5740
|
+
batchChildren
|
|
5741
|
+
} = browser;
|
|
4962
5742
|
useEffect(() => {
|
|
4963
5743
|
if (activeScope !== "product") return;
|
|
4964
5744
|
if (selectedProductId) return;
|
|
@@ -4975,10 +5755,6 @@ function RecordsAdminShellInner(props) {
|
|
|
4975
5755
|
if (first?.id) setSelectedRecordId(first.id);
|
|
4976
5756
|
}, [activeScope, selectedRecordId, recordList.items, cardinality]);
|
|
4977
5757
|
useEffect(() => {
|
|
4978
|
-
setSearch("");
|
|
4979
|
-
setFilter("all");
|
|
4980
|
-
setRuleFilters(EMPTY_RULE_FILTERS);
|
|
4981
|
-
setFacetBrowseFilter(null);
|
|
4982
5758
|
setSelectedRecordId(null);
|
|
4983
5759
|
setDraftKind(null);
|
|
4984
5760
|
}, [activeScope]);
|
|
@@ -5115,9 +5891,35 @@ function RecordsAdminShellInner(props) {
|
|
|
5115
5891
|
batchChildren.refetch();
|
|
5116
5892
|
if (isCollection) collectionItems.refetch();
|
|
5117
5893
|
}, [productBrowse, recordList, facetBrowse, variantChildren, batchChildren, isCollection, collectionItems]);
|
|
5118
|
-
const
|
|
5119
|
-
|
|
5120
|
-
|
|
5894
|
+
const editorTargetSpec = useMemo(() => {
|
|
5895
|
+
if (!editingTargetScope) return null;
|
|
5896
|
+
const initialFacetRule = editingTargetScope.kind === "rule" ? ruleWizardStep === 2 && ruleWizardRule ? ruleWizardRule : resolved.facetRule ?? { all: [] } : ruleWizardStep === 2 && ruleWizardRule && isCollection && !!selectedItemId ? ruleWizardRule : null;
|
|
5897
|
+
const label2 = deriveDraftLabel ? deriveDraftLabel(resolved.data, editingTargetScope) : void 0;
|
|
5898
|
+
return {
|
|
5899
|
+
scope: editingTargetScope,
|
|
5900
|
+
recordId: resolved.recordId,
|
|
5901
|
+
// Collection-cardinality item drafts must `create` (insert a new
|
|
5902
|
+
// row) instead of upsert — multiple items can share the same scope
|
|
5903
|
+
// + recordType, so an upsert would collide on the server's dedupe
|
|
5904
|
+
// key. Flip on whenever the right pane shows an item that hasn't
|
|
5905
|
+
// been saved yet (no resolved.recordId).
|
|
5906
|
+
createMode: isCollection && !!selectedItemId && !resolved.recordId,
|
|
5907
|
+
ref: editingTargetScope.kind === "rule" ? editingTargetScope.raw : void 0,
|
|
5908
|
+
initialFacetRule,
|
|
5909
|
+
label: label2
|
|
5910
|
+
};
|
|
5911
|
+
}, [
|
|
5912
|
+
editingTargetScope?.raw,
|
|
5913
|
+
editingTargetScope?.kind,
|
|
5914
|
+
resolved.recordId,
|
|
5915
|
+
resolved.facetRule,
|
|
5916
|
+
isCollection,
|
|
5917
|
+
selectedItemId,
|
|
5918
|
+
ruleWizardStep,
|
|
5919
|
+
ruleWizardRule
|
|
5920
|
+
]);
|
|
5921
|
+
const editorCtx = useEditorBridge({
|
|
5922
|
+
target: editorTargetSpec,
|
|
5121
5923
|
resolved: {
|
|
5122
5924
|
data: resolved.data,
|
|
5123
5925
|
source: resolved.source,
|
|
@@ -5127,18 +5929,6 @@ function RecordsAdminShellInner(props) {
|
|
|
5127
5929
|
facetRule: resolved.facetRule
|
|
5128
5930
|
},
|
|
5129
5931
|
defaultData,
|
|
5130
|
-
reseed: dirtyStrategy === "keep" ? "preserve-dirty" : "always",
|
|
5131
|
-
// Collection-cardinality item drafts must `create` (insert a new row)
|
|
5132
|
-
// instead of `upsert` — multiple items can share the same scope +
|
|
5133
|
-
// recordType, so an upsert would collide on the server's dedupe key.
|
|
5134
|
-
// Flip on whenever the right pane shows an item that hasn't been
|
|
5135
|
-
// saved yet (no resolved.recordId).
|
|
5136
|
-
createMode: isCollection && !!selectedItemId && !resolved.recordId,
|
|
5137
|
-
// Seed an empty rule for freshly-minted `rule:{id}` refs so the Targeting
|
|
5138
|
-
// section can render its empty-state picker. For existing rule records
|
|
5139
|
-
// pull the saved rule off the resolved record. Pinned scopes get `null`
|
|
5140
|
-
// and the Targeting section stays hidden.
|
|
5141
|
-
initialFacetRule: editingTargetScope?.kind === "rule" ? ruleWizardStep === 2 && ruleWizardRule ? ruleWizardRule : resolved.facetRule ?? { all: [] } : ruleWizardStep === 2 && ruleWizardRule && isCollection && !!selectedItemId ? ruleWizardRule : null,
|
|
5142
5932
|
onSaved: () => {
|
|
5143
5933
|
onTelemetry?.({ type: "record.save", recordType, ref: editingTargetScope?.raw ?? "", isCreate: resolved.source !== "self" });
|
|
5144
5934
|
if (ruleWizardStep !== null) {
|
|
@@ -5158,8 +5948,7 @@ function RecordsAdminShellInner(props) {
|
|
|
5158
5948
|
setSelectedBatchId(void 0);
|
|
5159
5949
|
}
|
|
5160
5950
|
refetchAll();
|
|
5161
|
-
}
|
|
5162
|
-
deriveDraftLabel
|
|
5951
|
+
}
|
|
5163
5952
|
});
|
|
5164
5953
|
useUnsavedGuard({
|
|
5165
5954
|
isDirty: editorCtx.isDirty,
|
|
@@ -5171,23 +5960,65 @@ function RecordsAdminShellInner(props) {
|
|
|
5171
5960
|
disableParentMessage: dirtyStrategy === "keep"
|
|
5172
5961
|
});
|
|
5173
5962
|
const dirtyConfirm = useConfirmDialog();
|
|
5174
|
-
const
|
|
5175
|
-
const
|
|
5176
|
-
const
|
|
5177
|
-
const dirtyKeys = useMemo(
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5963
|
+
const dirtyOverview = useDirtyOverview();
|
|
5964
|
+
const editorSelection = useEditorSelection();
|
|
5965
|
+
const dirtyItems = dirtyOverview.items;
|
|
5966
|
+
const dirtyKeys = useMemo(() => {
|
|
5967
|
+
const s = /* @__PURE__ */ new Set();
|
|
5968
|
+
for (const it of dirtyItems) {
|
|
5969
|
+
if (it.status === "saved") continue;
|
|
5970
|
+
if (it.recordId) s.add(it.recordId);
|
|
5971
|
+
if (it.scope?.raw) s.add(it.scope.raw);
|
|
5972
|
+
}
|
|
5973
|
+
return s;
|
|
5974
|
+
}, [dirtyItems]);
|
|
5975
|
+
const errorKeys = useMemo(() => {
|
|
5976
|
+
const s = /* @__PURE__ */ new Set();
|
|
5977
|
+
for (const it of dirtyItems) {
|
|
5978
|
+
if (it.status !== "error") continue;
|
|
5979
|
+
if (it.recordId) s.add(it.recordId);
|
|
5980
|
+
if (it.scope?.raw) s.add(it.scope.raw);
|
|
5981
|
+
}
|
|
5982
|
+
return s;
|
|
5983
|
+
}, [dirtyItems]);
|
|
5185
5984
|
const [saveAllOpen, setSaveAllOpen] = useState(false);
|
|
5186
|
-
const
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
}
|
|
5190
|
-
const
|
|
5985
|
+
const handleSaveAll = () => setSaveAllOpen(true);
|
|
5986
|
+
const handleDiscardAll = () => {
|
|
5987
|
+
dirtyOverview.discardAll();
|
|
5988
|
+
};
|
|
5989
|
+
const showHeaderUnsavedPill = dirtyStrategy === "keep" && canRenderInHeader(dirtyItems);
|
|
5990
|
+
const showTrayBanner = dirtyStrategy === "keep" && !showHeaderUnsavedPill && dirtyItems.length > 0;
|
|
5991
|
+
const composedHeaderActions = useMemo(() => {
|
|
5992
|
+
if (!showHeaderUnsavedPill) return headerActions;
|
|
5993
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5994
|
+
/* @__PURE__ */ jsx(
|
|
5995
|
+
HeaderUnsavedPill,
|
|
5996
|
+
{
|
|
5997
|
+
items: dirtyItems,
|
|
5998
|
+
isSaving: saveAllOpen,
|
|
5999
|
+
onSaveAll: handleSaveAll,
|
|
6000
|
+
onDiscardAll: handleDiscardAll,
|
|
6001
|
+
saveLabel: actionLabels?.save ?? i18n.save,
|
|
6002
|
+
discardLabel: actionLabels?.discard ?? i18n.discard,
|
|
6003
|
+
saveAllLabel: actionLabels?.saveAll ?? i18n.saveAll,
|
|
6004
|
+
discardAllLabel: actionLabels?.revertAll ?? i18n.discardAll,
|
|
6005
|
+
countTemplate: i18n.unsavedTrayCount,
|
|
6006
|
+
SaveIcon: actionIcons?.save,
|
|
6007
|
+
DiscardIcon: actionIcons?.discard
|
|
6008
|
+
}
|
|
6009
|
+
),
|
|
6010
|
+
headerActions
|
|
6011
|
+
] });
|
|
6012
|
+
}, [
|
|
6013
|
+
showHeaderUnsavedPill,
|
|
6014
|
+
headerActions,
|
|
6015
|
+
dirtyItems,
|
|
6016
|
+
saveAllOpen,
|
|
6017
|
+
actionLabels,
|
|
6018
|
+
actionIcons,
|
|
6019
|
+
i18n
|
|
6020
|
+
]);
|
|
6021
|
+
const onLeftSelectRef = useRef(null);
|
|
5191
6022
|
const { runWithGuard } = useDirtyNavigation({
|
|
5192
6023
|
strategy: dirtyStrategy,
|
|
5193
6024
|
isDirty: editorCtx.isDirty,
|
|
@@ -5202,32 +6033,15 @@ function RecordsAdminShellInner(props) {
|
|
|
5202
6033
|
cancel: i18n.unsavedPromptCancel
|
|
5203
6034
|
}
|
|
5204
6035
|
});
|
|
5205
|
-
const
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
const inp = document.createElement("input");
|
|
5215
|
-
inp.type = "file";
|
|
5216
|
-
inp.accept = ".csv,text/csv";
|
|
5217
|
-
inp.onchange = async () => {
|
|
5218
|
-
const file = inp.files?.[0];
|
|
5219
|
-
if (!file) return;
|
|
5220
|
-
const report = await importCsv(file, csvSchema, ctx);
|
|
5221
|
-
onTelemetry?.({ type: "csv.import", recordType, rows: report.total, errors: report.failed });
|
|
5222
|
-
if (report.failed > 0) {
|
|
5223
|
-
const fileBase = recordType ?? (label.toLowerCase().replace(/\s+/g, "-") || "records");
|
|
5224
|
-
downloadBlob(new Blob([report.annotatedCsv], { type: "text/csv" }), `${fileBase}-errors.csv`);
|
|
5225
|
-
}
|
|
5226
|
-
refetchAll();
|
|
5227
|
-
};
|
|
5228
|
-
inp.click();
|
|
5229
|
-
};
|
|
5230
|
-
const csvBulk = csvSchema ? { onImportCsv: handleImport, onExportCsv: handleExport } : {};
|
|
6036
|
+
const csvBulk = useShellCsvBulk({
|
|
6037
|
+
csvSchema,
|
|
6038
|
+
recordType,
|
|
6039
|
+
label,
|
|
6040
|
+
items: recordList.items,
|
|
6041
|
+
ctx,
|
|
6042
|
+
refetchAll,
|
|
6043
|
+
onTelemetry
|
|
6044
|
+
});
|
|
5231
6045
|
const [previewScope, setPreviewScope] = useState(null);
|
|
5232
6046
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
5233
6047
|
const [sidePreviewOpen, setSidePreviewOpen] = useState(true);
|
|
@@ -5256,158 +6070,25 @@ function RecordsAdminShellInner(props) {
|
|
|
5256
6070
|
}
|
|
5257
6071
|
return void 0;
|
|
5258
6072
|
}, [activeScope, editingScope, selectedProductId, productBrowse.items]);
|
|
5259
|
-
const
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
sourceRecordId: resolved.recordId,
|
|
5266
|
-
sourceLabel: editorHeaderLabel
|
|
5267
|
-
});
|
|
5268
|
-
onTelemetry?.({ type: "clipboard.copy", recordType, sourceRef: editingScope.raw });
|
|
5269
|
-
setClipboardNotice({
|
|
5270
|
-
message: i18n.copyToast.replace("{name}", editorHeaderLabel ?? editingScope.raw),
|
|
5271
|
-
variant: "copy"
|
|
5272
|
-
});
|
|
5273
|
-
}, [
|
|
5274
|
-
enableClipboard,
|
|
6073
|
+
const shellClipboard = useShellClipboard({
|
|
6074
|
+
enabled: !!enableClipboard,
|
|
6075
|
+
appId,
|
|
6076
|
+
recordType,
|
|
6077
|
+
i18n,
|
|
6078
|
+
editorCtx,
|
|
5275
6079
|
editingScope,
|
|
5276
|
-
onCopyOverride,
|
|
5277
|
-
editorCtx.value,
|
|
5278
|
-
clipboard,
|
|
5279
6080
|
editorHeaderLabel,
|
|
6081
|
+
isCollection,
|
|
6082
|
+
selectedItemId,
|
|
6083
|
+
selectedRecordId,
|
|
6084
|
+
resolved: { source: resolved.source, recordId: resolved.recordId },
|
|
5280
6085
|
onTelemetry,
|
|
5281
|
-
|
|
5282
|
-
i18n.copyToast
|
|
5283
|
-
]);
|
|
5284
|
-
const pasteCurrent = useCallback(async () => {
|
|
5285
|
-
if (!enableClipboard || !editingScope || !clipboard.entry) return;
|
|
5286
|
-
const compat = checkPasteCompatibility(clipboard.entry.sourceScope, editingScope);
|
|
5287
|
-
if (compat.status === "denied") {
|
|
5288
|
-
setClipboardNotice({ message: compat.reason ?? "Paste not allowed here", variant: "paste" });
|
|
5289
|
-
return;
|
|
5290
|
-
}
|
|
5291
|
-
if (compat.status === "warn") {
|
|
5292
|
-
const ok = await pasteConfirm.confirm({
|
|
5293
|
-
title: i18n.pasteWarnTitle,
|
|
5294
|
-
body: compat.reason ?? "",
|
|
5295
|
-
confirmLabel: i18n.pasteWarnContinue,
|
|
5296
|
-
cancelLabel: i18n.pasteConfirmCancel
|
|
5297
|
-
});
|
|
5298
|
-
if (!ok) return;
|
|
5299
|
-
}
|
|
5300
|
-
const willReplace = resolved.source === "self";
|
|
5301
|
-
if (willReplace) {
|
|
5302
|
-
const ok = await pasteConfirm.confirm({
|
|
5303
|
-
title: i18n.pasteConfirmTitle,
|
|
5304
|
-
body: i18n.pasteConfirmBody.replace(
|
|
5305
|
-
"{destination}",
|
|
5306
|
-
editorHeaderLabel ?? editingScope.raw
|
|
5307
|
-
),
|
|
5308
|
-
confirmLabel: i18n.pasteConfirmReplace,
|
|
5309
|
-
cancelLabel: i18n.pasteConfirmCancel
|
|
5310
|
-
});
|
|
5311
|
-
if (!ok) return;
|
|
5312
|
-
}
|
|
5313
|
-
const sourceParsed = clipboard.entry.sourceScope;
|
|
5314
|
-
const transformed = onPasteOverride ? onPasteOverride(
|
|
5315
|
-
{ value: clipboard.entry.value, sourceScope: sourceParsed },
|
|
5316
|
-
{ scope: editingScope, currentValue: editorCtx.value ?? null }
|
|
5317
|
-
) : cloneValue(clipboard.entry.value);
|
|
5318
|
-
if (transformed === null) return;
|
|
5319
|
-
editorCtx.onChange(transformed);
|
|
5320
|
-
onTelemetry?.({
|
|
5321
|
-
type: "clipboard.paste",
|
|
5322
|
-
recordType,
|
|
5323
|
-
sourceRef: clipboard.entry.sourceScope.raw,
|
|
5324
|
-
destinationRef: editingScope.raw,
|
|
5325
|
-
replaced: willReplace
|
|
5326
|
-
});
|
|
5327
|
-
setClipboardNotice({
|
|
5328
|
-
message: i18n.pasteToast.replace("{name}", clipboard.entry.sourceLabel ?? clipboard.entry.sourceScope.raw),
|
|
5329
|
-
variant: "paste"
|
|
5330
|
-
});
|
|
5331
|
-
}, [
|
|
5332
|
-
enableClipboard,
|
|
5333
|
-
editingScope,
|
|
5334
|
-
clipboard.entry,
|
|
5335
|
-
pasteConfirm,
|
|
5336
|
-
resolved.source,
|
|
6086
|
+
onCopyOverride,
|
|
5337
6087
|
onPasteOverride,
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
i18n.pasteWarnTitle,
|
|
5343
|
-
i18n.pasteWarnContinue,
|
|
5344
|
-
i18n.pasteConfirmCancel,
|
|
5345
|
-
i18n.pasteConfirmTitle,
|
|
5346
|
-
i18n.pasteConfirmBody,
|
|
5347
|
-
i18n.pasteConfirmReplace,
|
|
5348
|
-
i18n.pasteToast
|
|
5349
|
-
]);
|
|
5350
|
-
const editorPasteCompat = useMemo(() => {
|
|
5351
|
-
if (!enableClipboard || !clipboard.entry || !editingScope) return null;
|
|
5352
|
-
const sameRecord = clipboard.entry.sourceRecordId && resolved.recordId && clipboard.entry.sourceRecordId === resolved.recordId;
|
|
5353
|
-
const sameAnchor = !clipboard.entry.sourceRecordId && clipboard.entry.sourceScope.raw === editingScope.raw;
|
|
5354
|
-
if (sameRecord || sameAnchor) return { status: "denied" };
|
|
5355
|
-
return checkPasteCompatibility(clipboard.entry.sourceScope, editingScope);
|
|
5356
|
-
}, [enableClipboard, clipboard.entry, editingScope]);
|
|
5357
|
-
const editorClipboard = enableClipboard && editingScope ? {
|
|
5358
|
-
onCopy: copyCurrent,
|
|
5359
|
-
onPaste: () => {
|
|
5360
|
-
void pasteCurrent();
|
|
5361
|
-
},
|
|
5362
|
-
canCopy: true,
|
|
5363
|
-
canPaste: !!clipboard.entry && editorPasteCompat?.status !== "denied",
|
|
5364
|
-
pasteSourceLabel: clipboard.entry?.sourceLabel,
|
|
5365
|
-
pasteWillReplace: resolved.source === "self" && !!clipboard.entry
|
|
5366
|
-
} : void 0;
|
|
5367
|
-
const [pendingPasteTarget, setPendingPasteTarget] = useState(null);
|
|
5368
|
-
useEffect(() => {
|
|
5369
|
-
if (!pendingPasteTarget) return;
|
|
5370
|
-
if (!editingScope) return;
|
|
5371
|
-
const matched = pendingPasteTarget.kind === "record" ? (isCollection ? selectedItemId : selectedRecordId) === pendingPasteTarget.recordId : editingScope.raw === pendingPasteTarget.ref;
|
|
5372
|
-
if (!matched) return;
|
|
5373
|
-
const t = window.setTimeout(() => {
|
|
5374
|
-
setPendingPasteTarget(null);
|
|
5375
|
-
void pasteCurrent();
|
|
5376
|
-
}, 0);
|
|
5377
|
-
return () => window.clearTimeout(t);
|
|
5378
|
-
}, [pendingPasteTarget, editingScope, isCollection, selectedItemId, selectedRecordId, pasteCurrent]);
|
|
5379
|
-
const rowClipboard = enableClipboard ? (record) => {
|
|
5380
|
-
const summaryHasData = record.data != null;
|
|
5381
|
-
const sourceParsed = record.scope;
|
|
5382
|
-
const compat = clipboard.entry ? checkPasteCompatibility(clipboard.entry.sourceScope, sourceParsed) : null;
|
|
5383
|
-
const sameTarget = clipboard.entry ? clipboard.entry.sourceRecordId && record.id ? clipboard.entry.sourceRecordId === record.id : clipboard.entry.sourceScope.raw === record.ref : false;
|
|
5384
|
-
return {
|
|
5385
|
-
onCopy: summaryHasData ? () => {
|
|
5386
|
-
const value = onCopyOverride ? onCopyOverride({ value: record.data, scope: sourceParsed }) : cloneValue(record.data);
|
|
5387
|
-
clipboard.set({
|
|
5388
|
-
value,
|
|
5389
|
-
sourceScope: sourceParsed,
|
|
5390
|
-
sourceRecordId: record.id ?? void 0,
|
|
5391
|
-
sourceLabel: record.label
|
|
5392
|
-
});
|
|
5393
|
-
onTelemetry?.({ type: "clipboard.copy", recordType, sourceRef: record.ref });
|
|
5394
|
-
setClipboardNotice({
|
|
5395
|
-
message: i18n.copyToast.replace("{name}", record.label),
|
|
5396
|
-
variant: "copy"
|
|
5397
|
-
});
|
|
5398
|
-
} : void 0,
|
|
5399
|
-
onPaste: () => {
|
|
5400
|
-
onLeftSelectRef.current?.(record);
|
|
5401
|
-
setPendingPasteTarget(
|
|
5402
|
-
record.id ? { kind: "record", recordId: record.id } : { kind: "anchor", ref: record.ref }
|
|
5403
|
-
);
|
|
5404
|
-
},
|
|
5405
|
-
canPaste: !!clipboard.entry && !sameTarget && compat?.status !== "denied",
|
|
5406
|
-
pasteWillReplace: record.status === "configured",
|
|
5407
|
-
clipboardSourceLabel: clipboard.entry?.sourceLabel
|
|
5408
|
-
};
|
|
5409
|
-
} : void 0;
|
|
5410
|
-
const onLeftSelectRef = useRef(null);
|
|
6088
|
+
onLeftSelectRef
|
|
6089
|
+
});
|
|
6090
|
+
const editorClipboard = shellClipboard.editorClipboard;
|
|
6091
|
+
const rowClipboard = shellClipboard.rowClipboard;
|
|
5411
6092
|
const baseScopeRef = editingScope?.raw ?? "";
|
|
5412
6093
|
const itemNounLabel = itemNoun || "item";
|
|
5413
6094
|
const buildItemUrlValue = useCallback((id) => {
|
|
@@ -5422,11 +6103,52 @@ function RecordsAdminShellInner(props) {
|
|
|
5422
6103
|
scopeRef: baseScopeRef,
|
|
5423
6104
|
previousSelectedItemId: selectedItemId
|
|
5424
6105
|
});
|
|
6106
|
+
if (!isDraftId(itemId)) {
|
|
6107
|
+
const row = collectionItems.items.find((it) => it.itemId === itemId || it.id === itemId);
|
|
6108
|
+
if (row && row.data !== void 0) {
|
|
6109
|
+
const cacheKey = resolvedRecordQueryKey({
|
|
6110
|
+
collectionId,
|
|
6111
|
+
appId,
|
|
6112
|
+
recordType,
|
|
6113
|
+
productId: editingScope?.productId,
|
|
6114
|
+
variantId: editingScope?.variantId,
|
|
6115
|
+
batchId: editingScope?.batchId,
|
|
6116
|
+
facetId: editingScope?.facetId,
|
|
6117
|
+
facetValue: editingScope?.facetValue,
|
|
6118
|
+
proofId: editingScope?.proofId,
|
|
6119
|
+
recordId: itemId,
|
|
6120
|
+
withParent: true
|
|
6121
|
+
});
|
|
6122
|
+
if (queryClient.getQueryData(cacheKey) === void 0) {
|
|
6123
|
+
queryClient.setQueryData(cacheKey, {
|
|
6124
|
+
data: row.data,
|
|
6125
|
+
source: "self",
|
|
6126
|
+
sourceRef: row.ref || void 0,
|
|
6127
|
+
recordId: itemId,
|
|
6128
|
+
facetRule: row.facetRule ?? null
|
|
6129
|
+
});
|
|
6130
|
+
}
|
|
6131
|
+
}
|
|
6132
|
+
}
|
|
5425
6133
|
setSelectedItemId(itemId);
|
|
5426
6134
|
onTelemetry?.({ type: "item.open", recordType, scopeRef: baseScopeRef, itemId });
|
|
5427
6135
|
deepLinkState.emit({ recordId: itemId, scope: baseScopeRef || null }, "record.open");
|
|
5428
6136
|
});
|
|
5429
|
-
}, [
|
|
6137
|
+
}, [
|
|
6138
|
+
isCollection,
|
|
6139
|
+
runWithGuard,
|
|
6140
|
+
onTelemetry,
|
|
6141
|
+
recordType,
|
|
6142
|
+
baseScopeRef,
|
|
6143
|
+
deepLinkState,
|
|
6144
|
+
buildItemUrlValue,
|
|
6145
|
+
selectedItemId,
|
|
6146
|
+
collectionItems.items,
|
|
6147
|
+
queryClient,
|
|
6148
|
+
collectionId,
|
|
6149
|
+
appId,
|
|
6150
|
+
editingScope
|
|
6151
|
+
]);
|
|
5430
6152
|
const onItemCreate = useCallback(() => {
|
|
5431
6153
|
if (!isCollection) return;
|
|
5432
6154
|
void runWithGuard(() => {
|
|
@@ -5557,7 +6279,19 @@ function RecordsAdminShellInner(props) {
|
|
|
5557
6279
|
clipboard: editorClipboard,
|
|
5558
6280
|
actionLabels,
|
|
5559
6281
|
actionIcons,
|
|
5560
|
-
children:
|
|
6282
|
+
children: /* @__PURE__ */ jsx(
|
|
6283
|
+
EditorMountPool,
|
|
6284
|
+
{
|
|
6285
|
+
renderSlot: (slotId) => /* @__PURE__ */ jsx(
|
|
6286
|
+
EditorPoolBody,
|
|
6287
|
+
{
|
|
6288
|
+
editorId: slotId,
|
|
6289
|
+
renderEditor
|
|
6290
|
+
}
|
|
6291
|
+
),
|
|
6292
|
+
fallback: renderEditor(editorCtx)
|
|
6293
|
+
}
|
|
6294
|
+
)
|
|
5561
6295
|
}
|
|
5562
6296
|
);
|
|
5563
6297
|
const withNav = (node) => itemNav ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full min-h-0", children: [
|
|
@@ -5791,15 +6525,8 @@ function RecordsAdminShellInner(props) {
|
|
|
5791
6525
|
"data-density": density,
|
|
5792
6526
|
children: [
|
|
5793
6527
|
dirtyConfirm.dialog,
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
ClipboardToast,
|
|
5797
|
-
{
|
|
5798
|
-
message: clipboardNotice.message,
|
|
5799
|
-
variant: clipboardNotice.variant,
|
|
5800
|
-
onDismiss: () => setClipboardNotice(null)
|
|
5801
|
-
}
|
|
5802
|
-
),
|
|
6528
|
+
shellClipboard.confirmDialog,
|
|
6529
|
+
shellClipboard.toast,
|
|
5803
6530
|
(() => {
|
|
5804
6531
|
const showFloatHelp = !!intro && dismissed && resolvedReopenAffordance === "footer" && !headerWillRender;
|
|
5805
6532
|
if (!showFloatHelp) return null;
|
|
@@ -5851,7 +6578,7 @@ function RecordsAdminShellInner(props) {
|
|
|
5851
6578
|
title: title ?? label ?? recordType ?? "",
|
|
5852
6579
|
subtitle,
|
|
5853
6580
|
headerIcon,
|
|
5854
|
-
headerActions,
|
|
6581
|
+
headerActions: composedHeaderActions,
|
|
5855
6582
|
showStats,
|
|
5856
6583
|
showHeaderIcon,
|
|
5857
6584
|
recordType,
|
|
@@ -5878,25 +6605,25 @@ function RecordsAdminShellInner(props) {
|
|
|
5878
6605
|
onShowIntro: undismiss
|
|
5879
6606
|
}
|
|
5880
6607
|
) : null,
|
|
5881
|
-
|
|
6608
|
+
showTrayBanner && /* @__PURE__ */ jsx(
|
|
5882
6609
|
UnsavedTray,
|
|
5883
6610
|
{
|
|
5884
|
-
|
|
6611
|
+
items: dirtyItems,
|
|
5885
6612
|
isSaving: saveAllOpen,
|
|
5886
|
-
onSaveAll:
|
|
5887
|
-
onDiscardAll:
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
if (d.recordId) {
|
|
5893
|
-
setSelectedRecordId(d.recordId);
|
|
6613
|
+
onSaveAll: handleSaveAll,
|
|
6614
|
+
onDiscardAll: handleDiscardAll,
|
|
6615
|
+
onOpenItem: (it) => {
|
|
6616
|
+
editorSelection.focus(it.editorId);
|
|
6617
|
+
if (it.recordId) {
|
|
6618
|
+
setSelectedRecordId(it.recordId);
|
|
5894
6619
|
setDraftKind(null);
|
|
5895
6620
|
}
|
|
5896
6621
|
},
|
|
5897
6622
|
onJumpToError: () => {
|
|
5898
|
-
const first =
|
|
5899
|
-
if (first
|
|
6623
|
+
const first = dirtyItems.find((it) => it.status === "error");
|
|
6624
|
+
if (!first) return;
|
|
6625
|
+
editorSelection.focus(first.editorId);
|
|
6626
|
+
if (first.recordId) {
|
|
5900
6627
|
setSelectedRecordId(first.recordId);
|
|
5901
6628
|
setDraftKind(null);
|
|
5902
6629
|
}
|
|
@@ -5916,12 +6643,13 @@ function RecordsAdminShellInner(props) {
|
|
|
5916
6643
|
SaveAllProgress,
|
|
5917
6644
|
{
|
|
5918
6645
|
open: saveAllOpen,
|
|
5919
|
-
|
|
5920
|
-
|
|
6646
|
+
items: dirtyItems,
|
|
6647
|
+
saveOne: dirtyOverview.saveOne,
|
|
5921
6648
|
onClose: () => setSaveAllOpen(false),
|
|
5922
|
-
onJumpToError: (
|
|
5923
|
-
|
|
5924
|
-
|
|
6649
|
+
onJumpToError: (it) => {
|
|
6650
|
+
editorSelection.focus(it.editorId);
|
|
6651
|
+
if (it.recordId) {
|
|
6652
|
+
setSelectedRecordId(it.recordId);
|
|
5925
6653
|
setDraftKind(null);
|
|
5926
6654
|
}
|
|
5927
6655
|
},
|
|
@@ -6431,6 +7159,414 @@ var ResolvedPreview = ({ children }) => /* @__PURE__ */ jsxs("div", { className:
|
|
|
6431
7159
|
/* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wide mb-2", style: { color: "hsl(var(--ra-muted-text))" }, children: "Public preview" }),
|
|
6432
7160
|
children
|
|
6433
7161
|
] });
|
|
7162
|
+
var createStore = () => {
|
|
7163
|
+
const map = /* @__PURE__ */ new Map();
|
|
7164
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
7165
|
+
let nextOrder = 0;
|
|
7166
|
+
let cachedList = [];
|
|
7167
|
+
const recompute = () => {
|
|
7168
|
+
cachedList = Array.from(map.values()).sort((a, b) => a.order - b.order);
|
|
7169
|
+
};
|
|
7170
|
+
const emit = () => {
|
|
7171
|
+
recompute();
|
|
7172
|
+
listeners.forEach((l) => l());
|
|
7173
|
+
};
|
|
7174
|
+
return {
|
|
7175
|
+
list: () => cachedList,
|
|
7176
|
+
get: (key) => map.get(key),
|
|
7177
|
+
has: (key) => map.has(key),
|
|
7178
|
+
upsertDraft(input) {
|
|
7179
|
+
const existing = map.get(input.key);
|
|
7180
|
+
const order = existing?.order ?? input.order ?? nextOrder++;
|
|
7181
|
+
const status = input.status ?? existing?.status ?? "dirty";
|
|
7182
|
+
map.set(input.key, {
|
|
7183
|
+
...input,
|
|
7184
|
+
order,
|
|
7185
|
+
status
|
|
7186
|
+
});
|
|
7187
|
+
emit();
|
|
7188
|
+
},
|
|
7189
|
+
setStatus(key, status, error) {
|
|
7190
|
+
const existing = map.get(key);
|
|
7191
|
+
if (!existing) return;
|
|
7192
|
+
map.set(key, { ...existing, status, error });
|
|
7193
|
+
emit();
|
|
7194
|
+
},
|
|
7195
|
+
clearDraft(key) {
|
|
7196
|
+
if (map.delete(key)) emit();
|
|
7197
|
+
},
|
|
7198
|
+
clearAll() {
|
|
7199
|
+
if (map.size === 0) return;
|
|
7200
|
+
map.clear();
|
|
7201
|
+
emit();
|
|
7202
|
+
},
|
|
7203
|
+
subscribe(listener) {
|
|
7204
|
+
listeners.add(listener);
|
|
7205
|
+
return () => {
|
|
7206
|
+
listeners.delete(listener);
|
|
7207
|
+
};
|
|
7208
|
+
}
|
|
7209
|
+
};
|
|
7210
|
+
};
|
|
7211
|
+
var DirtyDraftContext = createContext(null);
|
|
7212
|
+
var DirtyDraftProvider = ({ children }) => {
|
|
7213
|
+
const storeRef = useRef(null);
|
|
7214
|
+
if (!storeRef.current) storeRef.current = createStore();
|
|
7215
|
+
return /* @__PURE__ */ jsx(DirtyDraftContext.Provider, { value: storeRef.current, children });
|
|
7216
|
+
};
|
|
7217
|
+
var useDirtyDraftStore = () => {
|
|
7218
|
+
const ctx = useContext(DirtyDraftContext);
|
|
7219
|
+
const fallbackRef = useRef(null);
|
|
7220
|
+
if (!ctx && !fallbackRef.current) fallbackRef.current = createStore();
|
|
7221
|
+
return ctx ?? fallbackRef.current;
|
|
7222
|
+
};
|
|
7223
|
+
var useDirtyDrafts = () => {
|
|
7224
|
+
const store = useDirtyDraftStore();
|
|
7225
|
+
return useSyncExternalStore(
|
|
7226
|
+
useCallback((cb) => store.subscribe(cb), [store]),
|
|
7227
|
+
useCallback(() => store.list(), [store]),
|
|
7228
|
+
useCallback(() => store.list(), [store])
|
|
7229
|
+
);
|
|
7230
|
+
};
|
|
7231
|
+
var useDirtyDraft = (key) => {
|
|
7232
|
+
const store = useDirtyDraftStore();
|
|
7233
|
+
const getSnapshot = useCallback(
|
|
7234
|
+
() => key ? store.get(key) : void 0,
|
|
7235
|
+
[store, key]
|
|
7236
|
+
);
|
|
7237
|
+
return useSyncExternalStore(
|
|
7238
|
+
useCallback((cb) => store.subscribe(cb), [store]),
|
|
7239
|
+
getSnapshot,
|
|
7240
|
+
getSnapshot
|
|
7241
|
+
);
|
|
7242
|
+
};
|
|
7243
|
+
var buildDraftKey = (opts) => {
|
|
7244
|
+
if (opts.recordId) return opts.recordId;
|
|
7245
|
+
const kind = opts.draftKind ?? "rec";
|
|
7246
|
+
return `draft:${kind}:${opts.scopeRaw}`;
|
|
7247
|
+
};
|
|
7248
|
+
var useDirtyDraftActions = () => {
|
|
7249
|
+
const store = useDirtyDraftStore();
|
|
7250
|
+
const drafts = useDirtyDrafts();
|
|
7251
|
+
const count = useMemo(() => drafts.filter((d) => d.status !== "saved").length, [drafts]);
|
|
7252
|
+
const errorCount = useMemo(() => drafts.filter((d) => d.status === "error").length, [drafts]);
|
|
7253
|
+
return { drafts, count, errorCount, store };
|
|
7254
|
+
};
|
|
7255
|
+
|
|
7256
|
+
// src/components/RecordsAdmin/hooks/useRecordEditor.ts
|
|
7257
|
+
var isEqual2 = (a, b) => {
|
|
7258
|
+
try {
|
|
7259
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
7260
|
+
} catch {
|
|
7261
|
+
return false;
|
|
7262
|
+
}
|
|
7263
|
+
};
|
|
7264
|
+
var cloneSeed = (resolved, defaultData) => {
|
|
7265
|
+
if (resolved.source === "self") return resolved.data;
|
|
7266
|
+
if (resolved.source === "inherited" && resolved.data != null) {
|
|
7267
|
+
try {
|
|
7268
|
+
return structuredClone(resolved.data);
|
|
7269
|
+
} catch {
|
|
7270
|
+
return JSON.parse(JSON.stringify(resolved.data));
|
|
7271
|
+
}
|
|
7272
|
+
}
|
|
7273
|
+
return defaultData?.() ?? {};
|
|
7274
|
+
};
|
|
7275
|
+
var getScopedResolvedRecordId = (scope, resolved) => {
|
|
7276
|
+
if (!resolved.recordId) return void 0;
|
|
7277
|
+
if (scope.itemId && scope.itemId === resolved.recordId) return resolved.recordId;
|
|
7278
|
+
const scopeRaw = scope.raw || "";
|
|
7279
|
+
const sourceRef = resolved.sourceRef || "";
|
|
7280
|
+
if (scopeRaw === sourceRef) return resolved.recordId;
|
|
7281
|
+
return void 0;
|
|
7282
|
+
};
|
|
7283
|
+
function useRecordEditor(args) {
|
|
7284
|
+
const {
|
|
7285
|
+
ctx,
|
|
7286
|
+
scope,
|
|
7287
|
+
resolved,
|
|
7288
|
+
defaultData,
|
|
7289
|
+
onSaved,
|
|
7290
|
+
onDeleted,
|
|
7291
|
+
onSaveError,
|
|
7292
|
+
reseed = "always",
|
|
7293
|
+
initialFacetRule = null,
|
|
7294
|
+
createMode = false,
|
|
7295
|
+
deriveDraftLabel
|
|
7296
|
+
} = args;
|
|
7297
|
+
const queryClient = useQueryClient();
|
|
7298
|
+
const draftStore = useDirtyDraftStore();
|
|
7299
|
+
const scopedResolvedRecordId = getScopedResolvedRecordId(scope, resolved);
|
|
7300
|
+
const draftKey = buildDraftKey({
|
|
7301
|
+
// Scope identity must win during record switches. `resolved.recordId`
|
|
7302
|
+
// can briefly lag behind the newly-selected target while the resolver is
|
|
7303
|
+
// catching up, and keying drafts off that stale id lets one edited item
|
|
7304
|
+
// borrow another item's draft. Once the resolver confirms the matching
|
|
7305
|
+
// record for this scope, we transparently migrate onto the concrete UUID.
|
|
7306
|
+
recordId: scopedResolvedRecordId,
|
|
7307
|
+
scopeRaw: scope.raw,
|
|
7308
|
+
draftKind: scope.kind
|
|
7309
|
+
});
|
|
7310
|
+
const initialDraft = draftStore.get(draftKey);
|
|
7311
|
+
const cleanSeed = cloneSeed(resolved, defaultData);
|
|
7312
|
+
const seed = initialDraft && reseed === "preserve-dirty" ? initialDraft.value : cleanSeed;
|
|
7313
|
+
const seedFacetRule = initialDraft && reseed === "preserve-dirty" ? initialDraft.facetRule : initialFacetRule;
|
|
7314
|
+
const [value, setValue] = useState(seed);
|
|
7315
|
+
const [savedSnapshot, setSavedSnapshot] = useState(
|
|
7316
|
+
initialDraft ? initialDraft.baseline : cleanSeed
|
|
7317
|
+
);
|
|
7318
|
+
const [facetRule, setFacetRule] = useState(seedFacetRule);
|
|
7319
|
+
const [savedFacetRule, setSavedFacetRule] = useState(
|
|
7320
|
+
initialDraft ? initialDraft.baselineFacetRule : initialFacetRule
|
|
7321
|
+
);
|
|
7322
|
+
const [userInteracted, setUserInteracted] = useState(!!initialDraft);
|
|
7323
|
+
const [optimisticSource, setOptimisticSource] = useState(null);
|
|
7324
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
7325
|
+
const [saveError, setSaveError] = useState(null);
|
|
7326
|
+
const valueRef = useRef(seed);
|
|
7327
|
+
useEffect(() => {
|
|
7328
|
+
valueRef.current = value;
|
|
7329
|
+
}, [value]);
|
|
7330
|
+
const prevTargetRef = useRef({
|
|
7331
|
+
scopeRaw: scope.raw,
|
|
7332
|
+
recordId: resolved.recordId
|
|
7333
|
+
});
|
|
7334
|
+
useEffect(() => {
|
|
7335
|
+
const prev = prevTargetRef.current;
|
|
7336
|
+
const targetChanged = prev.scopeRaw !== scope.raw || (prev.recordId ?? null) !== (resolved.recordId ?? null);
|
|
7337
|
+
prevTargetRef.current = { scopeRaw: scope.raw, recordId: resolved.recordId };
|
|
7338
|
+
const fresh = cloneSeed(resolved, defaultData);
|
|
7339
|
+
if (targetChanged) {
|
|
7340
|
+
const incomingKey = buildDraftKey({
|
|
7341
|
+
recordId: getScopedResolvedRecordId(scope, resolved),
|
|
7342
|
+
scopeRaw: scope.raw,
|
|
7343
|
+
draftKind: scope.kind
|
|
7344
|
+
});
|
|
7345
|
+
const incomingDraft = draftStore.get(incomingKey);
|
|
7346
|
+
if (incomingDraft && reseed === "preserve-dirty") {
|
|
7347
|
+
setValue(incomingDraft.value);
|
|
7348
|
+
setSavedSnapshot(incomingDraft.baseline);
|
|
7349
|
+
setFacetRule(incomingDraft.facetRule);
|
|
7350
|
+
setSavedFacetRule(incomingDraft.baselineFacetRule);
|
|
7351
|
+
setUserInteracted(true);
|
|
7352
|
+
} else {
|
|
7353
|
+
setValue(fresh);
|
|
7354
|
+
setSavedSnapshot(fresh);
|
|
7355
|
+
setFacetRule(initialFacetRule);
|
|
7356
|
+
setSavedFacetRule(initialFacetRule);
|
|
7357
|
+
setUserInteracted(false);
|
|
7358
|
+
}
|
|
7359
|
+
} else {
|
|
7360
|
+
if (reseed === "preserve-dirty") {
|
|
7361
|
+
const hasUnsaved = !isEqual2(valueRef.current, savedSnapshot);
|
|
7362
|
+
if (!hasUnsaved) setValue(fresh);
|
|
7363
|
+
} else {
|
|
7364
|
+
setValue(fresh);
|
|
7365
|
+
}
|
|
7366
|
+
setSavedSnapshot(fresh);
|
|
7367
|
+
setFacetRule(initialFacetRule);
|
|
7368
|
+
setSavedFacetRule(initialFacetRule);
|
|
7369
|
+
}
|
|
7370
|
+
setOptimisticSource(null);
|
|
7371
|
+
}, [scope.raw, resolved.recordId, resolved.source, resolved.sourceRef]);
|
|
7372
|
+
const valueDiff = !isEqual2(value, savedSnapshot) || !isEqual2(facetRule, savedFacetRule);
|
|
7373
|
+
const isDirty = userInteracted && valueDiff;
|
|
7374
|
+
const handleChange = useCallback((next) => {
|
|
7375
|
+
setUserInteracted(true);
|
|
7376
|
+
setValue(next);
|
|
7377
|
+
}, []);
|
|
7378
|
+
const handleFacetRuleChange = useCallback((next) => {
|
|
7379
|
+
setUserInteracted(true);
|
|
7380
|
+
setFacetRule(next);
|
|
7381
|
+
}, []);
|
|
7382
|
+
const save = useCallback(async () => {
|
|
7383
|
+
const anchors = parsedRefToScope(scope);
|
|
7384
|
+
const hasAnchors = !!(anchors.productId || anchors.variantId || anchors.batchId || anchors.proofId);
|
|
7385
|
+
const hasRule = !!(facetRule && facetRule.all && facetRule.all.length > 0);
|
|
7386
|
+
const isRuleScope2 = scope.kind === "rule";
|
|
7387
|
+
if (isRuleScope2 && !hasAnchors && !hasRule && !resolved.recordId && !createMode) {
|
|
7388
|
+
console.warn("[useRecordEditor] save() bailed \u2014 rule scope with no clauses, no recordId, not in createMode");
|
|
7389
|
+
return;
|
|
7390
|
+
}
|
|
7391
|
+
const previousSnapshot = savedSnapshot;
|
|
7392
|
+
const previousRuleSnapshot = savedFacetRule;
|
|
7393
|
+
resolved.source;
|
|
7394
|
+
const cacheKey = resolvedRecordQueryKey({
|
|
7395
|
+
collectionId: ctx.collectionId,
|
|
7396
|
+
appId: ctx.appId,
|
|
7397
|
+
recordType: ctx.recordType,
|
|
7398
|
+
productId: scope.productId,
|
|
7399
|
+
variantId: scope.variantId,
|
|
7400
|
+
batchId: scope.batchId,
|
|
7401
|
+
facetId: scope.facetId,
|
|
7402
|
+
facetValue: scope.facetValue,
|
|
7403
|
+
proofId: scope.proofId,
|
|
7404
|
+
recordId: resolved.recordId,
|
|
7405
|
+
withParent: true
|
|
7406
|
+
});
|
|
7407
|
+
const previousCache = queryClient.getQueryData(cacheKey);
|
|
7408
|
+
setSaveError(null);
|
|
7409
|
+
setIsSaving(true);
|
|
7410
|
+
setSavedSnapshot(value);
|
|
7411
|
+
setSavedFacetRule(facetRule);
|
|
7412
|
+
setOptimisticSource("self");
|
|
7413
|
+
queryClient.setQueryData(cacheKey, {
|
|
7414
|
+
data: value,
|
|
7415
|
+
source: "self",
|
|
7416
|
+
sourceRef: scope.raw,
|
|
7417
|
+
recordId: resolved.recordId,
|
|
7418
|
+
parentValue: previousCache?.parentValue ?? resolved.parentValue
|
|
7419
|
+
});
|
|
7420
|
+
try {
|
|
7421
|
+
if (resolved.recordId && resolved.source === "self") {
|
|
7422
|
+
await updateRecord(ctx, resolved.recordId, {
|
|
7423
|
+
data: value,
|
|
7424
|
+
facetRule
|
|
7425
|
+
});
|
|
7426
|
+
} else if (createMode) {
|
|
7427
|
+
await createRecord(ctx, {
|
|
7428
|
+
ref: scope.kind === "rule" && scope.raw ? scope.raw : void 0,
|
|
7429
|
+
scope: anchors,
|
|
7430
|
+
data: value,
|
|
7431
|
+
facetRule
|
|
7432
|
+
});
|
|
7433
|
+
} else {
|
|
7434
|
+
await upsertRecord(ctx, {
|
|
7435
|
+
// External ref only when the underlying scope already carries
|
|
7436
|
+
// one (e.g. rule:{id} for an existing rule record). Empty for
|
|
7437
|
+
// anchor-only writes — server addresses by anchors.
|
|
7438
|
+
ref: scope.kind === "rule" && scope.raw ? scope.raw : void 0,
|
|
7439
|
+
scope: anchors,
|
|
7440
|
+
data: value,
|
|
7441
|
+
facetRule
|
|
7442
|
+
});
|
|
7443
|
+
}
|
|
7444
|
+
draftStore.clearDraft(draftKey);
|
|
7445
|
+
onSaved?.();
|
|
7446
|
+
} catch (err) {
|
|
7447
|
+
setSavedSnapshot(previousSnapshot);
|
|
7448
|
+
setSavedFacetRule(previousRuleSnapshot);
|
|
7449
|
+
setOptimisticSource(null);
|
|
7450
|
+
if (previousCache !== void 0) {
|
|
7451
|
+
queryClient.setQueryData(cacheKey, previousCache);
|
|
7452
|
+
} else {
|
|
7453
|
+
queryClient.invalidateQueries({ queryKey: cacheKey });
|
|
7454
|
+
}
|
|
7455
|
+
setSaveError(err);
|
|
7456
|
+
onSaveError?.(err);
|
|
7457
|
+
throw err;
|
|
7458
|
+
} finally {
|
|
7459
|
+
setIsSaving(false);
|
|
7460
|
+
}
|
|
7461
|
+
}, [scope.raw, value, savedSnapshot, facetRule, savedFacetRule, resolved.source, resolved.parentValue, resolved.recordId, createMode, draftKey]);
|
|
7462
|
+
const reset = useCallback(() => {
|
|
7463
|
+
setValue(savedSnapshot);
|
|
7464
|
+
setFacetRule(savedFacetRule);
|
|
7465
|
+
setUserInteracted(false);
|
|
7466
|
+
draftStore.clearDraft(draftKey);
|
|
7467
|
+
}, [savedSnapshot, savedFacetRule, draftKey]);
|
|
7468
|
+
const remove = useCallback(async () => {
|
|
7469
|
+
if (resolved.source !== "self") return;
|
|
7470
|
+
if (!resolved.recordId) return;
|
|
7471
|
+
await removeRecord(ctx, resolved.recordId);
|
|
7472
|
+
draftStore.clearDraft(draftKey);
|
|
7473
|
+
onDeleted?.();
|
|
7474
|
+
}, [resolved.source, resolved.recordId, draftKey]);
|
|
7475
|
+
const prevDraftKeyRef = useRef(draftKey);
|
|
7476
|
+
const prevScopeRawRef = useRef(scope.raw);
|
|
7477
|
+
useEffect(() => {
|
|
7478
|
+
const prevKey = prevDraftKeyRef.current;
|
|
7479
|
+
const prevScopeRaw = prevScopeRawRef.current;
|
|
7480
|
+
if (prevKey && prevKey !== draftKey && prevScopeRaw === scope.raw) {
|
|
7481
|
+
const stale = draftStore.get(prevKey);
|
|
7482
|
+
if (stale) draftStore.clearDraft(prevKey);
|
|
7483
|
+
}
|
|
7484
|
+
prevDraftKeyRef.current = draftKey;
|
|
7485
|
+
prevScopeRawRef.current = scope.raw;
|
|
7486
|
+
}, [draftKey, scope.raw]);
|
|
7487
|
+
useEffect(() => {
|
|
7488
|
+
if (!isDirty) {
|
|
7489
|
+
return;
|
|
7490
|
+
}
|
|
7491
|
+
const anchors = parsedRefToScope(scope);
|
|
7492
|
+
const saveKind = resolved.recordId && resolved.source === "self" ? "update" : createMode ? "create" : "upsert";
|
|
7493
|
+
const deriveLabel = () => {
|
|
7494
|
+
if (deriveDraftLabel) {
|
|
7495
|
+
try {
|
|
7496
|
+
const custom = deriveDraftLabel(value, scope);
|
|
7497
|
+
if (typeof custom === "string" && custom.trim()) return custom.trim();
|
|
7498
|
+
} catch {
|
|
7499
|
+
}
|
|
7500
|
+
}
|
|
7501
|
+
const KEYS = ["title", "name", "label", "heading", "question", "slug"];
|
|
7502
|
+
const pickString = (obj) => {
|
|
7503
|
+
if (!obj || typeof obj !== "object") return void 0;
|
|
7504
|
+
for (const key of KEYS) {
|
|
7505
|
+
const raw = obj[key];
|
|
7506
|
+
if (typeof raw === "string" && raw.trim()) return raw.trim();
|
|
7507
|
+
}
|
|
7508
|
+
return void 0;
|
|
7509
|
+
};
|
|
7510
|
+
const v = value;
|
|
7511
|
+
const top = pickString(v);
|
|
7512
|
+
if (top) return top;
|
|
7513
|
+
if (v && typeof v === "object") {
|
|
7514
|
+
for (const wrapper of ["display", "content", "meta", "data"]) {
|
|
7515
|
+
const nested = pickString(v[wrapper]);
|
|
7516
|
+
if (nested) return nested;
|
|
7517
|
+
}
|
|
7518
|
+
}
|
|
7519
|
+
if (scope.raw?.startsWith("item:")) return "Untitled item";
|
|
7520
|
+
if (scope.kind === "rule") return "Rule";
|
|
7521
|
+
if (scope.kind && scope.kind !== "collection") {
|
|
7522
|
+
return scope.kind.charAt(0).toUpperCase() + scope.kind.slice(1);
|
|
7523
|
+
}
|
|
7524
|
+
return "Default";
|
|
7525
|
+
};
|
|
7526
|
+
draftStore.upsertDraft({
|
|
7527
|
+
key: draftKey,
|
|
7528
|
+
label: deriveLabel(),
|
|
7529
|
+
context: scope.kind,
|
|
7530
|
+
scopeRaw: scope.raw,
|
|
7531
|
+
recordId: resolved.recordId,
|
|
7532
|
+
value,
|
|
7533
|
+
facetRule,
|
|
7534
|
+
baseline: savedSnapshot,
|
|
7535
|
+
baselineFacetRule: savedFacetRule,
|
|
7536
|
+
createMode,
|
|
7537
|
+
scopeAnchors: anchors,
|
|
7538
|
+
ref: scope.kind === "rule" && scope.raw ? scope.raw : void 0,
|
|
7539
|
+
saveKind,
|
|
7540
|
+
save: async () => {
|
|
7541
|
+
await save();
|
|
7542
|
+
}
|
|
7543
|
+
});
|
|
7544
|
+
}, [isDirty, value, facetRule, savedSnapshot, savedFacetRule, scope.raw, resolved.recordId, resolved.source, createMode, save]);
|
|
7545
|
+
const effectiveSource = optimisticSource ?? resolved.source;
|
|
7546
|
+
const isRuleScope = scope.kind === "rule";
|
|
7547
|
+
const ruleValid = !isRuleScope || isFacetRuleValid(facetRule);
|
|
7548
|
+
const canSave = ruleValid;
|
|
7549
|
+
const cannotSaveReason = !ruleValid ? "Pick at least one value for every facet in the rule before saving." : void 0;
|
|
7550
|
+
return {
|
|
7551
|
+
value,
|
|
7552
|
+
onChange: handleChange,
|
|
7553
|
+
source: effectiveSource,
|
|
7554
|
+
recordId: resolved.recordId,
|
|
7555
|
+
parentValue: resolved.parentValue,
|
|
7556
|
+
scope,
|
|
7557
|
+
isDirty,
|
|
7558
|
+
save,
|
|
7559
|
+
reset,
|
|
7560
|
+
remove,
|
|
7561
|
+
canRemove: effectiveSource === "self",
|
|
7562
|
+
canSave,
|
|
7563
|
+
cannotSaveReason,
|
|
7564
|
+
isSaving,
|
|
7565
|
+
saveError,
|
|
7566
|
+
facetRule,
|
|
7567
|
+
onFacetRuleChange: handleFacetRuleChange
|
|
7568
|
+
};
|
|
7569
|
+
}
|
|
6434
7570
|
var resolveAllQueryKey = (args) => [
|
|
6435
7571
|
"records-admin",
|
|
6436
7572
|
"resolve-all",
|
|
@@ -6671,6 +7807,6 @@ function useMergedRecord(args) {
|
|
|
6671
7807
|
};
|
|
6672
7808
|
}
|
|
6673
7809
|
|
|
6674
|
-
export { ALL_ITEM_VIEWS, ALL_PRESENTATIONS, BatchList, BulkActionsMenu, DEFAULT_DEEP_LINK_PARAM_NAMES, DEFAULT_I18N, DEFAULT_ICONS, DefaultItemCards, DefaultItemTable, DefaultRecordCard, DefaultRecordRow, DeleteButton, DirtyDraftProvider, DrawerPreview, EditorItemNav, EmptyState, ErrorState, FacetList, InheritanceMarker, InheritanceProvider, InlinePreview, IntroCard, ItemListView, ItemViewSwitcher, LoadingState, PresentationSwitcher, PreviewScopePicker, PreviewToggleButton, ProductDrillDown, ProductList, RecordBrowser, RecordEditor, RecordList, RecordsAdminShell, ResolvedPreview, ScopeBreadcrumb, ScopeTabs, SiblingRail, SidePreview, StatusDot, StatusFilterPills, StatusIcon, TabbedPreview, UtilityRow, VariantList, buildDraftKey, buildRef, checkPasteCompatibility, cloneValue, createDefaultDeepLinkAdapter, downloadBlob, exportCsv, importCsv, mergeIcons, parseRef, pickHeaderIcon, resolutionChain, resolveRecord, statusToneLabel, useCollectedRecords, useCollectionItems, useDeepLinkState, useDirtyDraft, useDirtyDraftActions, useDirtyDraftStore, useDirtyDrafts, useDirtyNavigation, useFacetBrowse, useIntroDismissed, useItemViewPref, useMergedRecord, usePresentationPref, useProductBrowse, useProductChildren, useRecordClipboard, useRecordEditor, useRecordList, useResolveAllRecords, useResolvedRecord, useRulePreview, useScopeProbe, useUnsavedGuard };
|
|
7810
|
+
export { ALL_ITEM_VIEWS, ALL_PRESENTATIONS, BatchList, BulkActionsMenu, DEFAULT_DEEP_LINK_PARAM_NAMES, DEFAULT_I18N, DEFAULT_ICONS, DefaultItemCards, DefaultItemTable, DefaultRecordCard, DefaultRecordRow, DeleteButton, DirtyDraftProvider, DrawerPreview, EditorItemNav, EmptyState, ErrorState, FacetList, InheritanceMarker, InheritanceProvider, InlinePreview, IntroCard, ItemListView, ItemViewSwitcher, LoadingState, PresentationSwitcher, PreviewScopePicker, PreviewToggleButton, ProductDrillDown, ProductList, RecordBrowser, RecordEditor, RecordList, RecordsAdminShell, ResolvedPreview, ScopeBreadcrumb, ScopeTabs, SiblingRail, SidePreview, StatusDot, StatusFilterPills, StatusIcon, TabbedPreview, UtilityRow, VariantList, buildDraftKey, buildRef, checkPasteCompatibility, cloneValue, createDefaultDeepLinkAdapter, createPostMessageDeepLinkAdapter, downloadBlob, exportCsv, importCsv, isInSmartLinksIframe, mergeIcons, parseRef, pickHeaderIcon, resolutionChain, resolveRecord, statusToneLabel, useCollectedRecords, useCollectionItems, useDeepLinkState, useDirtyDraft, useDirtyDraftActions, useDirtyDraftStore, useDirtyDrafts, useDirtyNavigation, useFacetBrowse, useIntroDismissed, useItemViewPref, useMergedRecord, usePresentationPref, useProductBrowse, useProductChildren, useRecordClipboard, useRecordEditor, useRecordList, useResolveAllRecords, useResolvedRecord, useRulePreview, useScopeProbe, useUnsavedGuard };
|
|
6675
7811
|
//# sourceMappingURL=index.js.map
|
|
6676
7812
|
//# sourceMappingURL=index.js.map
|