@ai-matrx/design-system 0.18.2 → 0.18.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +22 -0
  3. package/dist/content-transfer.cjs +127 -30
  4. package/dist/content-transfer.cjs.map +1 -1
  5. package/dist/content-transfer.d.cts +2 -0
  6. package/dist/content-transfer.d.ts +2 -0
  7. package/dist/content-transfer.js +127 -30
  8. package/dist/content-transfer.js.map +1 -1
  9. package/dist/data-table/copy-helpers.d.cts +1 -1
  10. package/dist/data-table/copy-helpers.d.ts +1 -1
  11. package/dist/data-table/data-row-inspector.cjs +127 -30
  12. package/dist/data-table/data-row-inspector.cjs.map +1 -1
  13. package/dist/data-table/data-row-inspector.js +127 -30
  14. package/dist/data-table/data-row-inspector.js.map +1 -1
  15. package/dist/data-table/data-row-window.cjs +127 -30
  16. package/dist/data-table/data-row-window.cjs.map +1 -1
  17. package/dist/data-table/data-row-window.js +127 -30
  18. package/dist/data-table/data-row-window.js.map +1 -1
  19. package/dist/data-table/filter-engine.d.cts +1 -1
  20. package/dist/data-table/filter-engine.d.ts +1 -1
  21. package/dist/data-table/index.cjs +1158 -956
  22. package/dist/data-table/index.cjs.map +1 -1
  23. package/dist/data-table/index.d.cts +23 -3
  24. package/dist/data-table/index.d.ts +23 -3
  25. package/dist/data-table/index.js +1158 -956
  26. package/dist/data-table/index.js.map +1 -1
  27. package/dist/data-table/infer-filter.d.cts +1 -1
  28. package/dist/data-table/infer-filter.d.ts +1 -1
  29. package/dist/data-table/layered-filters.d.cts +1 -1
  30. package/dist/data-table/layered-filters.d.ts +1 -1
  31. package/dist/data-table/pagination.cjs +2 -2
  32. package/dist/data-table/pagination.cjs.map +1 -1
  33. package/dist/data-table/pagination.js +2 -2
  34. package/dist/data-table/pagination.js.map +1 -1
  35. package/dist/data-table/query-control.d.cts +1 -1
  36. package/dist/data-table/query-control.d.ts +1 -1
  37. package/dist/data-table/types.cjs.map +1 -1
  38. package/dist/data-table/types.d.cts +1 -1
  39. package/dist/data-table/types.d.ts +1 -1
  40. package/dist/data-table/url-state.d.cts +1 -1
  41. package/dist/data-table/url-state.d.ts +1 -1
  42. package/dist/data-table/xlsx.d.cts +1 -1
  43. package/dist/data-table/xlsx.d.ts +1 -1
  44. package/dist/index.cjs +2 -2
  45. package/dist/index.cjs.map +1 -1
  46. package/dist/index.js +2 -2
  47. package/dist/index.js.map +1 -1
  48. package/dist/{types-CD4xGL5Z.d.cts → types-Bx9SfdJM.d.cts} +2 -0
  49. package/dist/{types-BhSkeZQ_.d.ts → types-DyByb6tW.d.ts} +2 -0
  50. package/package.json +1 -1
@@ -648,6 +648,10 @@ function materialIdentity(material) {
648
648
  }
649
649
  return `payload:${id}`;
650
650
  }
651
+ function snapshotContent(snapshot) {
652
+ const { sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits } = snapshot;
653
+ return JSON.stringify({ sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits });
654
+ }
651
655
  function useFence() {
652
656
  const mounted = React7.useRef(true);
653
657
  const nextId = React7.useRef(0);
@@ -750,7 +754,7 @@ function ContentTransferMenuSession({ source, surface, label, projection, sectio
750
754
  const captured = await material.capture(signal);
751
755
  sourceMaterial = { id: captured.snapshot.sourceId, label: captured.snapshot.label, capture: async () => captured };
752
756
  } else if ("kind" in material) {
753
- sourceMaterial = applyMenuProjection ? { id: "direct", label: sourceLabel, capture: async () => directSource(material, { label: sourceLabel, ...projection ? { projection } : {}, ...sections ? { sections } : {} }) } : material;
757
+ sourceMaterial = { id: materialIdentity(material), label: sourceLabel, capture: async () => directSource(material, { sourceId: materialIdentity(material), label: sourceLabel, ...applyMenuProjection && projection ? { projection } : {}, ...applyMenuProjection && sections ? { sections } : {} }) };
754
758
  } else if (applyMenuProjection && (projection || sections)) {
755
759
  sourceMaterial = {
756
760
  id: material.id,
@@ -767,7 +771,7 @@ function ContentTransferMenuSession({ source, surface, label, projection, sectio
767
771
  } else {
768
772
  sourceMaterial = material;
769
773
  }
770
- const raw = "kind" in sourceMaterial ? directSource(sourceMaterial) : await sourceMaterial.capture({ scope, signal });
774
+ const raw = await sourceMaterial.capture({ scope, signal });
771
775
  const constrained = resolveTransferPreferences(providerPreferences, preferences, raw.snapshot.limits);
772
776
  const result = await capture({ id: raw.snapshot.sourceId, label: raw.snapshot.label, capture: async () => ({ ...raw, snapshot: { ...raw.snapshot, limits: constrained.limits } }) }, signal, scope);
773
777
  if (scope !== "all-matching" || !collectPages) return result;
@@ -827,31 +831,117 @@ function ContentTransferMenuSession({ source, surface, label, projection, sectio
827
831
  fence.finish(controller);
828
832
  }
829
833
  }, [captureOne, capturePrimary, fence, formatSources, initialScope, preferences, providerPreferences, report, transport]);
830
- const openWorkspace = React7.useCallback(async (key = primaryWorkspaceKey, variant) => {
831
- const variantMaterial = variant && "kind" in variant.source ? { id: `variant:${key}`, label: variant.label, capture: async () => directSource(variant.source, { label: variant.label }) } : variant?.source;
832
- const captureWorkspace = variantMaterial ? (scope, signal) => captureOne(variantMaterial, scope, signal, true) : capturePrimary;
833
- workspaceCaptures.current.set(key, captureWorkspace);
834
- if (workspaces[key]) {
835
- setWorkspaceKey(key);
836
- setWorkspaceOpen(true);
837
- return;
838
- }
839
- const { controller, id } = fence.begin();
840
- try {
841
- const result = await captureWorkspace(initialScope, controller.signal);
842
- if (!fence.valid(id, controller)) return;
843
- const resolved = resolveTransferPreferences(providerPreferences, preferences, result.snapshot.limits);
844
- const original = createDraft(result.snapshot);
845
- const base = { ...result, key, original, draft: original, scope: initialScope, preferences: resolved, ...result.collectionError ? { error: result.collectionError } : {} };
846
- setWorkspaces((current) => ({ ...current, [key]: { ...base, draft: rebuild(base, initialControls(base)) } }));
847
- setWorkspaceKey(key);
848
- setWorkspaceOpen(true);
849
- } catch (error) {
850
- if (fence.valid(id, controller)) setNotice(errorMessage(error, "Could not capture content"));
851
- } finally {
852
- fence.finish(controller);
853
- }
854
- }, [captureOne, capturePrimary, fence, initialScope, preferences, primaryWorkspaceKey, providerPreferences, workspaces]);
834
+ const openWorkspace = React7.useCallback(
835
+ async (key = primaryWorkspaceKey, variant) => {
836
+ const variantMaterial = variant && "kind" in variant.source ? {
837
+ id: `variant:${key}`,
838
+ label: variant.label,
839
+ capture: async () => directSource(variant.source, {
840
+ sourceId: `variant:${key}`,
841
+ label: variant.label
842
+ })
843
+ } : variant?.source;
844
+ const captureWorkspace = (scope, signal, collectPages = true) => captureOne(variantMaterial ?? primary, scope, signal, true, collectPages);
845
+ workspaceCaptures.current.set(key, captureWorkspace);
846
+ const existing = workspaces[key];
847
+ const { controller, id } = fence.begin();
848
+ if (existing) {
849
+ try {
850
+ const allMatching = existing.scope === "all-matching";
851
+ const fresh = await captureWorkspace(
852
+ existing.scope,
853
+ controller.signal,
854
+ !allMatching
855
+ );
856
+ if (!fence.valid(id, controller) || !workspaceCaptures.current.has(key))
857
+ return;
858
+ if (allMatching && (!existing.allMatching || !fresh.allMatching))
859
+ throw new Error(
860
+ "This source can no longer provide an all-matching snapshot."
861
+ );
862
+ const collectionIdentity = (value) => value && {
863
+ query: value.source.query,
864
+ projection: value.projection,
865
+ columns: value.columns,
866
+ limits: value.limits
867
+ };
868
+ const changed = allMatching ? JSON.stringify(collectionIdentity(existing.allMatching)) !== JSON.stringify(collectionIdentity(fresh.allMatching)) : snapshotContent(existing.snapshot) !== snapshotContent(fresh.snapshot);
869
+ setWorkspaces((current) => {
870
+ const retained = current[key];
871
+ if (!retained) return current;
872
+ return {
873
+ ...current,
874
+ [key]: {
875
+ ...retained,
876
+ error: fresh.collectionError,
877
+ ...allMatching ? { sourceStale: changed } : changed ? { pending: { ...fresh, scope: retained.scope } } : { pending: void 0 }
878
+ }
879
+ };
880
+ });
881
+ setWorkspaceKey(key);
882
+ setWorkspaceOpen(true);
883
+ } catch (error) {
884
+ if (fence.valid(id, controller) && workspaceCaptures.current.has(key)) {
885
+ setWorkspaces(
886
+ (current) => current[key] ? {
887
+ ...current,
888
+ [key]: {
889
+ ...current[key],
890
+ error: errorMessage(error, "Could not refresh source")
891
+ }
892
+ } : current
893
+ );
894
+ setWorkspaceKey(key);
895
+ setWorkspaceOpen(true);
896
+ }
897
+ } finally {
898
+ fence.finish(controller);
899
+ }
900
+ return;
901
+ }
902
+ try {
903
+ const result = await captureWorkspace(initialScope, controller.signal);
904
+ if (!fence.valid(id, controller)) return;
905
+ const resolved = resolveTransferPreferences(
906
+ providerPreferences,
907
+ preferences,
908
+ result.snapshot.limits
909
+ );
910
+ const original = createDraft(result.snapshot);
911
+ const base = {
912
+ ...result,
913
+ key,
914
+ original,
915
+ draft: original,
916
+ scope: initialScope,
917
+ preferences: resolved,
918
+ ...result.collectionError ? { error: result.collectionError } : {}
919
+ };
920
+ setWorkspaces((current) => ({
921
+ ...current,
922
+ [key]: { ...base, draft: rebuild(base, initialControls(base)) }
923
+ }));
924
+ setWorkspaceKey(key);
925
+ setWorkspaceOpen(true);
926
+ } catch (error) {
927
+ if (fence.valid(id, controller))
928
+ setNotice(errorMessage(error, "Could not capture content"));
929
+ } finally {
930
+ fence.finish(controller);
931
+ }
932
+ },
933
+ [
934
+ captureOne,
935
+ capturePrimary,
936
+ fence,
937
+ initialScope,
938
+ preferences,
939
+ primaryWorkspaceKey,
940
+ primary,
941
+ providerPreferences,
942
+ workspaces
943
+ ]
944
+ );
855
945
  React7.useImperativeHandle(controllerRef, () => ({
856
946
  focusTrigger: () => triggerRef.current?.focus(),
857
947
  prepare: async (requestedSource, options) => {
@@ -1074,7 +1164,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
1074
1164
  setBusy("capture");
1075
1165
  try {
1076
1166
  const result = await captureScope(scope, controller.signal);
1077
- if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, error: result.collectionError });
1167
+ if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, sourceStale: false, error: result.collectionError });
1078
1168
  } catch (error) {
1079
1169
  if (fence.valid(id, controller)) onChange({ ...latest.current, error: errorMessage(error, "Refresh failed") });
1080
1170
  } finally {
@@ -1317,6 +1407,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
1317
1407
  /* @__PURE__ */ jsx7(Button, { type: "button", variant: "outline", onClick: () => setPendingControls(null), children: "Keep edited draft" }),
1318
1408
  /* @__PURE__ */ jsx7(Button, { type: "button", onClick: applyPendingControls, children: "Rebuild from source" })
1319
1409
  ] }) : null,
1410
+ state.sourceStale ? /* @__PURE__ */ jsx7("p", { role: "alert", className: "matrx-alchemy-decision", children: "The source changed since this draft. Use Refresh source to review the latest data." }) : null,
1320
1411
  state.pending ? /* @__PURE__ */ jsxs4("div", { className: "matrx-alchemy-decision", role: "alert", children: [
1321
1412
  /* @__PURE__ */ jsx7("p", { children: "A fresh source snapshot is ready." }),
1322
1413
  /* @__PURE__ */ jsx7(Button, { type: "button", variant: "outline", onClick: () => resolveCapture("keep"), children: "Keep current snapshot and draft" }),
@@ -1423,6 +1514,7 @@ var actionDone = (message) => ({
1423
1514
  mimeTypes: [],
1424
1515
  message
1425
1516
  });
1517
+ var isSafeFilename = (filename) => filename === filename.trim() && filename.length > 0 && !/^\.+$/.test(filename) && !/[\\/\x00-\x1F\x7F]/.test(filename);
1426
1518
  var sourceFor = (id, label, build) => ({
1427
1519
  id,
1428
1520
  label,
@@ -1571,7 +1663,8 @@ function MatrxCopyMenuSession(props) {
1571
1663
  value: {
1572
1664
  bytes: Array.from(bytes),
1573
1665
  mime: file.mime,
1574
- extension: file.extension
1666
+ extension: file.extension,
1667
+ ...file.filename !== void 0 ? { filename: file.filename } : {}
1575
1668
  }
1576
1669
  };
1577
1670
  },
@@ -1588,12 +1681,16 @@ function MatrxCopyMenuSession(props) {
1588
1681
  (byte) => typeof byte !== "number" || !Number.isInteger(byte) || byte < 0 || byte > 255
1589
1682
  ))
1590
1683
  throw new Error(`${item.label} did not capture a valid file.`);
1684
+ if (value.filename !== void 0 && (typeof value.filename !== "string" || !isSafeFilename(value.filename)))
1685
+ throw new Error(
1686
+ `${item.label} has an unsafe filename. Use a nonempty basename without leading or trailing whitespace, dots only, path separators, or control characters.`
1687
+ );
1591
1688
  if (draft.omissions.some((omission) => omission.reason !== "duplicate"))
1592
1689
  throw new Error(
1593
1690
  "This custom file exceeds the configured export limits or contains excluded content. Use a standard format or revise the source options."
1594
1691
  );
1595
1692
  const bytes = new Uint8Array(value.bytes);
1596
- const filename = `${props.label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "export"}.${value.extension.replace(/[^a-zA-Z0-9.-]/g, "") || "bin"}`;
1693
+ const filename = typeof value.filename === "string" ? value.filename : `${props.label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "export"}.${value.extension.replace(/[^a-zA-Z0-9.-]/g, "") || "bin"}`;
1597
1694
  return transport.download(
1598
1695
  sealTransferArtifact2({
1599
1696
  snapshotId: snapshot.id,