@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
@@ -701,6 +701,10 @@ function materialIdentity(material) {
701
701
  }
702
702
  return `payload:${id}`;
703
703
  }
704
+ function snapshotContent(snapshot) {
705
+ const { sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits } = snapshot;
706
+ return JSON.stringify({ sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits });
707
+ }
704
708
  function useFence() {
705
709
  const mounted = React7.useRef(true);
706
710
  const nextId = React7.useRef(0);
@@ -803,7 +807,7 @@ function ContentTransferMenuSession({ source, surface, label, projection, sectio
803
807
  const captured = await material.capture(signal);
804
808
  sourceMaterial = { id: captured.snapshot.sourceId, label: captured.snapshot.label, capture: async () => captured };
805
809
  } else if ("kind" in material) {
806
- sourceMaterial = applyMenuProjection ? { id: "direct", label: sourceLabel, capture: async () => directSource(material, { label: sourceLabel, ...projection ? { projection } : {}, ...sections ? { sections } : {} }) } : material;
810
+ sourceMaterial = { id: materialIdentity(material), label: sourceLabel, capture: async () => directSource(material, { sourceId: materialIdentity(material), label: sourceLabel, ...applyMenuProjection && projection ? { projection } : {}, ...applyMenuProjection && sections ? { sections } : {} }) };
807
811
  } else if (applyMenuProjection && (projection || sections)) {
808
812
  sourceMaterial = {
809
813
  id: material.id,
@@ -820,7 +824,7 @@ function ContentTransferMenuSession({ source, surface, label, projection, sectio
820
824
  } else {
821
825
  sourceMaterial = material;
822
826
  }
823
- const raw = "kind" in sourceMaterial ? directSource(sourceMaterial) : await sourceMaterial.capture({ scope, signal });
827
+ const raw = await sourceMaterial.capture({ scope, signal });
824
828
  const constrained = resolveTransferPreferences(providerPreferences, preferences, raw.snapshot.limits);
825
829
  const result = await capture({ id: raw.snapshot.sourceId, label: raw.snapshot.label, capture: async () => ({ ...raw, snapshot: { ...raw.snapshot, limits: constrained.limits } }) }, signal, scope);
826
830
  if (scope !== "all-matching" || !collectPages) return result;
@@ -880,31 +884,117 @@ function ContentTransferMenuSession({ source, surface, label, projection, sectio
880
884
  fence.finish(controller);
881
885
  }
882
886
  }, [captureOne, capturePrimary, fence, formatSources, initialScope, preferences, providerPreferences, report, transport]);
883
- const openWorkspace = React7.useCallback(async (key = primaryWorkspaceKey, variant) => {
884
- const variantMaterial = variant && "kind" in variant.source ? { id: `variant:${key}`, label: variant.label, capture: async () => directSource(variant.source, { label: variant.label }) } : variant?.source;
885
- const captureWorkspace = variantMaterial ? (scope, signal) => captureOne(variantMaterial, scope, signal, true) : capturePrimary;
886
- workspaceCaptures.current.set(key, captureWorkspace);
887
- if (workspaces[key]) {
888
- setWorkspaceKey(key);
889
- setWorkspaceOpen(true);
890
- return;
891
- }
892
- const { controller, id } = fence.begin();
893
- try {
894
- const result = await captureWorkspace(initialScope, controller.signal);
895
- if (!fence.valid(id, controller)) return;
896
- const resolved = resolveTransferPreferences(providerPreferences, preferences, result.snapshot.limits);
897
- const original = createDraft(result.snapshot);
898
- const base = { ...result, key, original, draft: original, scope: initialScope, preferences: resolved, ...result.collectionError ? { error: result.collectionError } : {} };
899
- setWorkspaces((current) => ({ ...current, [key]: { ...base, draft: rebuild(base, initialControls(base)) } }));
900
- setWorkspaceKey(key);
901
- setWorkspaceOpen(true);
902
- } catch (error) {
903
- if (fence.valid(id, controller)) setNotice(errorMessage(error, "Could not capture content"));
904
- } finally {
905
- fence.finish(controller);
906
- }
907
- }, [captureOne, capturePrimary, fence, initialScope, preferences, primaryWorkspaceKey, providerPreferences, workspaces]);
887
+ const openWorkspace = React7.useCallback(
888
+ async (key = primaryWorkspaceKey, variant) => {
889
+ const variantMaterial = variant && "kind" in variant.source ? {
890
+ id: `variant:${key}`,
891
+ label: variant.label,
892
+ capture: async () => directSource(variant.source, {
893
+ sourceId: `variant:${key}`,
894
+ label: variant.label
895
+ })
896
+ } : variant?.source;
897
+ const captureWorkspace = (scope, signal, collectPages = true) => captureOne(variantMaterial ?? primary, scope, signal, true, collectPages);
898
+ workspaceCaptures.current.set(key, captureWorkspace);
899
+ const existing = workspaces[key];
900
+ const { controller, id } = fence.begin();
901
+ if (existing) {
902
+ try {
903
+ const allMatching = existing.scope === "all-matching";
904
+ const fresh = await captureWorkspace(
905
+ existing.scope,
906
+ controller.signal,
907
+ !allMatching
908
+ );
909
+ if (!fence.valid(id, controller) || !workspaceCaptures.current.has(key))
910
+ return;
911
+ if (allMatching && (!existing.allMatching || !fresh.allMatching))
912
+ throw new Error(
913
+ "This source can no longer provide an all-matching snapshot."
914
+ );
915
+ const collectionIdentity = (value) => value && {
916
+ query: value.source.query,
917
+ projection: value.projection,
918
+ columns: value.columns,
919
+ limits: value.limits
920
+ };
921
+ const changed = allMatching ? JSON.stringify(collectionIdentity(existing.allMatching)) !== JSON.stringify(collectionIdentity(fresh.allMatching)) : snapshotContent(existing.snapshot) !== snapshotContent(fresh.snapshot);
922
+ setWorkspaces((current) => {
923
+ const retained = current[key];
924
+ if (!retained) return current;
925
+ return {
926
+ ...current,
927
+ [key]: {
928
+ ...retained,
929
+ error: fresh.collectionError,
930
+ ...allMatching ? { sourceStale: changed } : changed ? { pending: { ...fresh, scope: retained.scope } } : { pending: void 0 }
931
+ }
932
+ };
933
+ });
934
+ setWorkspaceKey(key);
935
+ setWorkspaceOpen(true);
936
+ } catch (error) {
937
+ if (fence.valid(id, controller) && workspaceCaptures.current.has(key)) {
938
+ setWorkspaces(
939
+ (current) => current[key] ? {
940
+ ...current,
941
+ [key]: {
942
+ ...current[key],
943
+ error: errorMessage(error, "Could not refresh source")
944
+ }
945
+ } : current
946
+ );
947
+ setWorkspaceKey(key);
948
+ setWorkspaceOpen(true);
949
+ }
950
+ } finally {
951
+ fence.finish(controller);
952
+ }
953
+ return;
954
+ }
955
+ try {
956
+ const result = await captureWorkspace(initialScope, controller.signal);
957
+ if (!fence.valid(id, controller)) return;
958
+ const resolved = resolveTransferPreferences(
959
+ providerPreferences,
960
+ preferences,
961
+ result.snapshot.limits
962
+ );
963
+ const original = createDraft(result.snapshot);
964
+ const base = {
965
+ ...result,
966
+ key,
967
+ original,
968
+ draft: original,
969
+ scope: initialScope,
970
+ preferences: resolved,
971
+ ...result.collectionError ? { error: result.collectionError } : {}
972
+ };
973
+ setWorkspaces((current) => ({
974
+ ...current,
975
+ [key]: { ...base, draft: rebuild(base, initialControls(base)) }
976
+ }));
977
+ setWorkspaceKey(key);
978
+ setWorkspaceOpen(true);
979
+ } catch (error) {
980
+ if (fence.valid(id, controller))
981
+ setNotice(errorMessage(error, "Could not capture content"));
982
+ } finally {
983
+ fence.finish(controller);
984
+ }
985
+ },
986
+ [
987
+ captureOne,
988
+ capturePrimary,
989
+ fence,
990
+ initialScope,
991
+ preferences,
992
+ primaryWorkspaceKey,
993
+ primary,
994
+ providerPreferences,
995
+ workspaces
996
+ ]
997
+ );
908
998
  React7.useImperativeHandle(controllerRef, () => ({
909
999
  focusTrigger: () => triggerRef.current?.focus(),
910
1000
  prepare: async (requestedSource, options) => {
@@ -1127,7 +1217,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
1127
1217
  setBusy("capture");
1128
1218
  try {
1129
1219
  const result = await captureScope(scope, controller.signal);
1130
- if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, error: result.collectionError });
1220
+ if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, sourceStale: false, error: result.collectionError });
1131
1221
  } catch (error) {
1132
1222
  if (fence.valid(id, controller)) onChange({ ...latest.current, error: errorMessage(error, "Refresh failed") });
1133
1223
  } finally {
@@ -1370,6 +1460,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
1370
1460
  /* @__PURE__ */ jsx10(Button, { type: "button", variant: "outline", onClick: () => setPendingControls(null), children: "Keep edited draft" }),
1371
1461
  /* @__PURE__ */ jsx10(Button, { type: "button", onClick: applyPendingControls, children: "Rebuild from source" })
1372
1462
  ] }) : null,
1463
+ state.sourceStale ? /* @__PURE__ */ jsx10("p", { role: "alert", className: "matrx-alchemy-decision", children: "The source changed since this draft. Use Refresh source to review the latest data." }) : null,
1373
1464
  state.pending ? /* @__PURE__ */ jsxs6("div", { className: "matrx-alchemy-decision", role: "alert", children: [
1374
1465
  /* @__PURE__ */ jsx10("p", { children: "A fresh source snapshot is ready." }),
1375
1466
  /* @__PURE__ */ jsx10(Button, { type: "button", variant: "outline", onClick: () => resolveCapture("keep"), children: "Keep current snapshot and draft" }),
@@ -1476,6 +1567,7 @@ var actionDone = (message) => ({
1476
1567
  mimeTypes: [],
1477
1568
  message
1478
1569
  });
1570
+ var isSafeFilename = (filename) => filename === filename.trim() && filename.length > 0 && !/^\.+$/.test(filename) && !/[\\/\x00-\x1F\x7F]/.test(filename);
1479
1571
  var sourceFor = (id, label, build) => ({
1480
1572
  id,
1481
1573
  label,
@@ -1624,7 +1716,8 @@ function MatrxCopyMenuSession(props) {
1624
1716
  value: {
1625
1717
  bytes: Array.from(bytes),
1626
1718
  mime: file.mime,
1627
- extension: file.extension
1719
+ extension: file.extension,
1720
+ ...file.filename !== void 0 ? { filename: file.filename } : {}
1628
1721
  }
1629
1722
  };
1630
1723
  },
@@ -1641,12 +1734,16 @@ function MatrxCopyMenuSession(props) {
1641
1734
  (byte) => typeof byte !== "number" || !Number.isInteger(byte) || byte < 0 || byte > 255
1642
1735
  ))
1643
1736
  throw new Error(`${item.label} did not capture a valid file.`);
1737
+ if (value.filename !== void 0 && (typeof value.filename !== "string" || !isSafeFilename(value.filename)))
1738
+ throw new Error(
1739
+ `${item.label} has an unsafe filename. Use a nonempty basename without leading or trailing whitespace, dots only, path separators, or control characters.`
1740
+ );
1644
1741
  if (draft.omissions.some((omission) => omission.reason !== "duplicate"))
1645
1742
  throw new Error(
1646
1743
  "This custom file exceeds the configured export limits or contains excluded content. Use a standard format or revise the source options."
1647
1744
  );
1648
1745
  const bytes = new Uint8Array(value.bytes);
1649
- const filename = `${props.label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "export"}.${value.extension.replace(/[^a-zA-Z0-9.-]/g, "") || "bin"}`;
1746
+ 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"}`;
1650
1747
  return transport.download(
1651
1748
  sealTransferArtifact2({
1652
1749
  snapshotId: snapshot.id,