@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.
- package/CHANGELOG.md +24 -0
- package/README.md +22 -0
- package/dist/content-transfer.cjs +127 -30
- package/dist/content-transfer.cjs.map +1 -1
- package/dist/content-transfer.d.cts +2 -0
- package/dist/content-transfer.d.ts +2 -0
- package/dist/content-transfer.js +127 -30
- package/dist/content-transfer.js.map +1 -1
- package/dist/data-table/copy-helpers.d.cts +1 -1
- package/dist/data-table/copy-helpers.d.ts +1 -1
- package/dist/data-table/data-row-inspector.cjs +127 -30
- package/dist/data-table/data-row-inspector.cjs.map +1 -1
- package/dist/data-table/data-row-inspector.js +127 -30
- package/dist/data-table/data-row-inspector.js.map +1 -1
- package/dist/data-table/data-row-window.cjs +127 -30
- package/dist/data-table/data-row-window.cjs.map +1 -1
- package/dist/data-table/data-row-window.js +127 -30
- package/dist/data-table/data-row-window.js.map +1 -1
- package/dist/data-table/filter-engine.d.cts +1 -1
- package/dist/data-table/filter-engine.d.ts +1 -1
- package/dist/data-table/index.cjs +1158 -956
- package/dist/data-table/index.cjs.map +1 -1
- package/dist/data-table/index.d.cts +23 -3
- package/dist/data-table/index.d.ts +23 -3
- package/dist/data-table/index.js +1158 -956
- package/dist/data-table/index.js.map +1 -1
- package/dist/data-table/infer-filter.d.cts +1 -1
- package/dist/data-table/infer-filter.d.ts +1 -1
- package/dist/data-table/layered-filters.d.cts +1 -1
- package/dist/data-table/layered-filters.d.ts +1 -1
- package/dist/data-table/pagination.cjs +2 -2
- package/dist/data-table/pagination.cjs.map +1 -1
- package/dist/data-table/pagination.js +2 -2
- package/dist/data-table/pagination.js.map +1 -1
- package/dist/data-table/query-control.d.cts +1 -1
- package/dist/data-table/query-control.d.ts +1 -1
- package/dist/data-table/types.cjs.map +1 -1
- package/dist/data-table/types.d.cts +1 -1
- package/dist/data-table/types.d.ts +1 -1
- package/dist/data-table/url-state.d.cts +1 -1
- package/dist/data-table/url-state.d.ts +1 -1
- package/dist/data-table/xlsx.d.cts +1 -1
- package/dist/data-table/xlsx.d.ts +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/{types-CD4xGL5Z.d.cts → types-Bx9SfdJM.d.cts} +2 -0
- package/dist/{types-BhSkeZQ_.d.ts → types-DyByb6tW.d.ts} +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.4 — 2026-09-12
|
|
4
|
+
|
|
5
|
+
Alchemy custom exports now preserve the exact captured filename through
|
|
6
|
+
preparation and delivery, with safe basename validation; omitting a filename
|
|
7
|
+
keeps the existing default behavior. Tables gain the shared responsive
|
|
8
|
+
`TableTitleRow`, canonical toolbar tooltips, a mobile-safe append footer, and
|
|
9
|
+
collision-free Select labels and chevrons.
|
|
10
|
+
|
|
11
|
+
### Consumer action
|
|
12
|
+
|
|
13
|
+
Update to 0.18.4. No host configuration changes are required.
|
|
14
|
+
|
|
15
|
+
## 0.18.3 — 2026-09-12
|
|
16
|
+
|
|
17
|
+
Reopening an Alchemy preparation now checks the current source while retaining
|
|
18
|
+
manual drafts. Changed direct sources offer an explicit keep-or-regenerate
|
|
19
|
+
choice, all-matching sources detect identity changes without eagerly fetching
|
|
20
|
+
pages, and failed refreshes remain visible instead of silently reusing stale
|
|
21
|
+
content.
|
|
22
|
+
|
|
23
|
+
### Consumer action
|
|
24
|
+
|
|
25
|
+
Update to 0.18.3. No host configuration changes are required.
|
|
26
|
+
|
|
3
27
|
## 0.18.2 — 2026-09-12
|
|
4
28
|
|
|
5
29
|
Alchemy copy sessions now reset only when their explicit identity changes,
|
package/README.md
CHANGED
|
@@ -4,6 +4,28 @@ The small, semantic UI foundation shared by AI Matrx React applications — prim
|
|
|
4
4
|
|
|
5
5
|
**Controls:** Button, Badge, Label, Separator, the Input family, the Textarea family, Checkbox, Switch, RadioGroup, Slider, Toggle + ToggleGroup, Avatar, Progress.
|
|
6
6
|
**Content transfer:** `@ai-matrx/design-system/content-transfer` supplies Matrx Alchemy's inlined glyph, accessible menu, projected capture workspace, optional AI capability seam, and locally scoped `SurfaceHandle` provider. Its source is captured on user action; preparation keeps an immutable original, preserves manual and alternate-source sessions, fences stale async results, and routes every copy/download through `Transport`. Import `@ai-matrx/design-system/content-transfer.css` with the package structural sheets.
|
|
7
|
+
|
|
8
|
+
```tsx
|
|
9
|
+
<MatrxCopyMenu
|
|
10
|
+
label="Log"
|
|
11
|
+
export={{
|
|
12
|
+
items: [
|
|
13
|
+
{
|
|
14
|
+
id: "raw",
|
|
15
|
+
label: "Download raw log",
|
|
16
|
+
build: () => ({
|
|
17
|
+
content: rawText,
|
|
18
|
+
filename: "worker.log",
|
|
19
|
+
extension: "txt",
|
|
20
|
+
mime: "text/plain",
|
|
21
|
+
}),
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
}}
|
|
25
|
+
/>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
An explicit filename is an exact safe basename captured with its bytes. Omit it to use the label-derived default; unsafe values fail the export visibly.
|
|
7
29
|
**Surfaces:** Card, Table, Tabs, Accordion, Collapsible, ScrollArea, Popover, Tooltip, HoverCard, Sheet, Dialog, AlertDialog, Drawer, BottomSheet + TabbedBottomSheet, Alert, Resizable, Skeleton.
|
|
8
30
|
**Menus:** DropdownMenu, ContextMenu, Command / CommandDialog, Select, CreatablePicker.
|
|
9
31
|
**Composed:** EditableLabel, SegmentedControl, ArchiveFilter, ScoreRing, OverflowToolbar, ConfirmDialog + ConfirmDialogHost.
|
|
@@ -649,6 +649,7 @@ var actionDone = (message) => ({
|
|
|
649
649
|
mimeTypes: [],
|
|
650
650
|
message
|
|
651
651
|
});
|
|
652
|
+
var isSafeFilename = (filename) => filename === filename.trim() && filename.length > 0 && !/^\.+$/.test(filename) && !/[\\/\x00-\x1F\x7F]/.test(filename);
|
|
652
653
|
var sourceFor = (id, label, build) => ({
|
|
653
654
|
id,
|
|
654
655
|
label,
|
|
@@ -797,7 +798,8 @@ function MatrxCopyMenuSession(props) {
|
|
|
797
798
|
value: {
|
|
798
799
|
bytes: Array.from(bytes),
|
|
799
800
|
mime: file.mime,
|
|
800
|
-
extension: file.extension
|
|
801
|
+
extension: file.extension,
|
|
802
|
+
...file.filename !== void 0 ? { filename: file.filename } : {}
|
|
801
803
|
}
|
|
802
804
|
};
|
|
803
805
|
},
|
|
@@ -814,12 +816,16 @@ function MatrxCopyMenuSession(props) {
|
|
|
814
816
|
(byte) => typeof byte !== "number" || !Number.isInteger(byte) || byte < 0 || byte > 255
|
|
815
817
|
))
|
|
816
818
|
throw new Error(`${item.label} did not capture a valid file.`);
|
|
819
|
+
if (value.filename !== void 0 && (typeof value.filename !== "string" || !isSafeFilename(value.filename)))
|
|
820
|
+
throw new Error(
|
|
821
|
+
`${item.label} has an unsafe filename. Use a nonempty basename without leading or trailing whitespace, dots only, path separators, or control characters.`
|
|
822
|
+
);
|
|
817
823
|
if (draft.omissions.some((omission) => omission.reason !== "duplicate"))
|
|
818
824
|
throw new Error(
|
|
819
825
|
"This custom file exceeds the configured export limits or contains excluded content. Use a standard format or revise the source options."
|
|
820
826
|
);
|
|
821
827
|
const bytes = new Uint8Array(value.bytes);
|
|
822
|
-
const filename = `${props.label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "export"}.${value.extension.replace(/[^a-zA-Z0-9.-]/g, "") || "bin"}`;
|
|
828
|
+
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"}`;
|
|
823
829
|
return transport.download(
|
|
824
830
|
(0, import_content_transfer.sealTransferArtifact)({
|
|
825
831
|
snapshotId: snapshot.id,
|
|
@@ -1250,6 +1256,10 @@ function materialIdentity(material) {
|
|
|
1250
1256
|
}
|
|
1251
1257
|
return `payload:${id}`;
|
|
1252
1258
|
}
|
|
1259
|
+
function snapshotContent(snapshot) {
|
|
1260
|
+
const { sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits } = snapshot;
|
|
1261
|
+
return JSON.stringify({ sourceId, label, payload, coverage, unsavedChanges, omissions, sections, limits });
|
|
1262
|
+
}
|
|
1253
1263
|
function useFence() {
|
|
1254
1264
|
const mounted = React8.useRef(true);
|
|
1255
1265
|
const nextId = React8.useRef(0);
|
|
@@ -1352,7 +1362,7 @@ function ContentTransferMenuSession({ source, surface, label, projection, sectio
|
|
|
1352
1362
|
const captured = await material.capture(signal);
|
|
1353
1363
|
sourceMaterial = { id: captured.snapshot.sourceId, label: captured.snapshot.label, capture: async () => captured };
|
|
1354
1364
|
} else if ("kind" in material) {
|
|
1355
|
-
sourceMaterial =
|
|
1365
|
+
sourceMaterial = { id: materialIdentity(material), label: sourceLabel, capture: async () => (0, import_content_transfer2.directSource)(material, { sourceId: materialIdentity(material), label: sourceLabel, ...applyMenuProjection && projection ? { projection } : {}, ...applyMenuProjection && sections ? { sections } : {} }) };
|
|
1356
1366
|
} else if (applyMenuProjection && (projection || sections)) {
|
|
1357
1367
|
sourceMaterial = {
|
|
1358
1368
|
id: material.id,
|
|
@@ -1369,7 +1379,7 @@ function ContentTransferMenuSession({ source, surface, label, projection, sectio
|
|
|
1369
1379
|
} else {
|
|
1370
1380
|
sourceMaterial = material;
|
|
1371
1381
|
}
|
|
1372
|
-
const raw =
|
|
1382
|
+
const raw = await sourceMaterial.capture({ scope, signal });
|
|
1373
1383
|
const constrained = (0, import_content_transfer2.resolveTransferPreferences)(providerPreferences, preferences, raw.snapshot.limits);
|
|
1374
1384
|
const result = await (0, import_content_transfer2.capture)({ id: raw.snapshot.sourceId, label: raw.snapshot.label, capture: async () => ({ ...raw, snapshot: { ...raw.snapshot, limits: constrained.limits } }) }, signal, scope);
|
|
1375
1385
|
if (scope !== "all-matching" || !collectPages) return result;
|
|
@@ -1429,31 +1439,117 @@ function ContentTransferMenuSession({ source, surface, label, projection, sectio
|
|
|
1429
1439
|
fence.finish(controller);
|
|
1430
1440
|
}
|
|
1431
1441
|
}, [captureOne, capturePrimary, fence, formatSources, initialScope, preferences, providerPreferences, report, transport]);
|
|
1432
|
-
const openWorkspace = React8.useCallback(
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
const
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1442
|
+
const openWorkspace = React8.useCallback(
|
|
1443
|
+
async (key = primaryWorkspaceKey, variant) => {
|
|
1444
|
+
const variantMaterial = variant && "kind" in variant.source ? {
|
|
1445
|
+
id: `variant:${key}`,
|
|
1446
|
+
label: variant.label,
|
|
1447
|
+
capture: async () => (0, import_content_transfer2.directSource)(variant.source, {
|
|
1448
|
+
sourceId: `variant:${key}`,
|
|
1449
|
+
label: variant.label
|
|
1450
|
+
})
|
|
1451
|
+
} : variant?.source;
|
|
1452
|
+
const captureWorkspace = (scope, signal, collectPages = true) => captureOne(variantMaterial ?? primary, scope, signal, true, collectPages);
|
|
1453
|
+
workspaceCaptures.current.set(key, captureWorkspace);
|
|
1454
|
+
const existing = workspaces[key];
|
|
1455
|
+
const { controller, id } = fence.begin();
|
|
1456
|
+
if (existing) {
|
|
1457
|
+
try {
|
|
1458
|
+
const allMatching = existing.scope === "all-matching";
|
|
1459
|
+
const fresh = await captureWorkspace(
|
|
1460
|
+
existing.scope,
|
|
1461
|
+
controller.signal,
|
|
1462
|
+
!allMatching
|
|
1463
|
+
);
|
|
1464
|
+
if (!fence.valid(id, controller) || !workspaceCaptures.current.has(key))
|
|
1465
|
+
return;
|
|
1466
|
+
if (allMatching && (!existing.allMatching || !fresh.allMatching))
|
|
1467
|
+
throw new Error(
|
|
1468
|
+
"This source can no longer provide an all-matching snapshot."
|
|
1469
|
+
);
|
|
1470
|
+
const collectionIdentity = (value) => value && {
|
|
1471
|
+
query: value.source.query,
|
|
1472
|
+
projection: value.projection,
|
|
1473
|
+
columns: value.columns,
|
|
1474
|
+
limits: value.limits
|
|
1475
|
+
};
|
|
1476
|
+
const changed = allMatching ? JSON.stringify(collectionIdentity(existing.allMatching)) !== JSON.stringify(collectionIdentity(fresh.allMatching)) : snapshotContent(existing.snapshot) !== snapshotContent(fresh.snapshot);
|
|
1477
|
+
setWorkspaces((current) => {
|
|
1478
|
+
const retained = current[key];
|
|
1479
|
+
if (!retained) return current;
|
|
1480
|
+
return {
|
|
1481
|
+
...current,
|
|
1482
|
+
[key]: {
|
|
1483
|
+
...retained,
|
|
1484
|
+
error: fresh.collectionError,
|
|
1485
|
+
...allMatching ? { sourceStale: changed } : changed ? { pending: { ...fresh, scope: retained.scope } } : { pending: void 0 }
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
});
|
|
1489
|
+
setWorkspaceKey(key);
|
|
1490
|
+
setWorkspaceOpen(true);
|
|
1491
|
+
} catch (error) {
|
|
1492
|
+
if (fence.valid(id, controller) && workspaceCaptures.current.has(key)) {
|
|
1493
|
+
setWorkspaces(
|
|
1494
|
+
(current) => current[key] ? {
|
|
1495
|
+
...current,
|
|
1496
|
+
[key]: {
|
|
1497
|
+
...current[key],
|
|
1498
|
+
error: errorMessage(error, "Could not refresh source")
|
|
1499
|
+
}
|
|
1500
|
+
} : current
|
|
1501
|
+
);
|
|
1502
|
+
setWorkspaceKey(key);
|
|
1503
|
+
setWorkspaceOpen(true);
|
|
1504
|
+
}
|
|
1505
|
+
} finally {
|
|
1506
|
+
fence.finish(controller);
|
|
1507
|
+
}
|
|
1508
|
+
return;
|
|
1509
|
+
}
|
|
1510
|
+
try {
|
|
1511
|
+
const result = await captureWorkspace(initialScope, controller.signal);
|
|
1512
|
+
if (!fence.valid(id, controller)) return;
|
|
1513
|
+
const resolved = (0, import_content_transfer2.resolveTransferPreferences)(
|
|
1514
|
+
providerPreferences,
|
|
1515
|
+
preferences,
|
|
1516
|
+
result.snapshot.limits
|
|
1517
|
+
);
|
|
1518
|
+
const original = (0, import_content_transfer2.createDraft)(result.snapshot);
|
|
1519
|
+
const base = {
|
|
1520
|
+
...result,
|
|
1521
|
+
key,
|
|
1522
|
+
original,
|
|
1523
|
+
draft: original,
|
|
1524
|
+
scope: initialScope,
|
|
1525
|
+
preferences: resolved,
|
|
1526
|
+
...result.collectionError ? { error: result.collectionError } : {}
|
|
1527
|
+
};
|
|
1528
|
+
setWorkspaces((current) => ({
|
|
1529
|
+
...current,
|
|
1530
|
+
[key]: { ...base, draft: rebuild(base, initialControls(base)) }
|
|
1531
|
+
}));
|
|
1532
|
+
setWorkspaceKey(key);
|
|
1533
|
+
setWorkspaceOpen(true);
|
|
1534
|
+
} catch (error) {
|
|
1535
|
+
if (fence.valid(id, controller))
|
|
1536
|
+
setNotice(errorMessage(error, "Could not capture content"));
|
|
1537
|
+
} finally {
|
|
1538
|
+
fence.finish(controller);
|
|
1539
|
+
}
|
|
1540
|
+
},
|
|
1541
|
+
[
|
|
1542
|
+
captureOne,
|
|
1543
|
+
capturePrimary,
|
|
1544
|
+
fence,
|
|
1545
|
+
initialScope,
|
|
1546
|
+
preferences,
|
|
1547
|
+
primaryWorkspaceKey,
|
|
1548
|
+
primary,
|
|
1549
|
+
providerPreferences,
|
|
1550
|
+
workspaces
|
|
1551
|
+
]
|
|
1552
|
+
);
|
|
1457
1553
|
React8.useImperativeHandle(controllerRef, () => ({
|
|
1458
1554
|
focusTrigger: () => triggerRef.current?.focus(),
|
|
1459
1555
|
prepare: async (requestedSource, options) => {
|
|
@@ -1676,7 +1772,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
|
|
|
1676
1772
|
setBusy("capture");
|
|
1677
1773
|
try {
|
|
1678
1774
|
const result = await captureScope(scope, controller.signal);
|
|
1679
|
-
if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, error: result.collectionError });
|
|
1775
|
+
if (fence.valid(id, controller) && latest.current.draft.revision === revision) onChange({ ...latest.current, pending: { ...result, scope }, sourceStale: false, error: result.collectionError });
|
|
1680
1776
|
} catch (error) {
|
|
1681
1777
|
if (fence.valid(id, controller)) onChange({ ...latest.current, error: errorMessage(error, "Refresh failed") });
|
|
1682
1778
|
} finally {
|
|
@@ -1919,6 +2015,7 @@ function PreparationWorkspace({ restoreFocus, open, state, scopes, onClose, onCh
|
|
|
1919
2015
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Button, { type: "button", variant: "outline", onClick: () => setPendingControls(null), children: "Keep edited draft" }),
|
|
1920
2016
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Button, { type: "button", onClick: applyPendingControls, children: "Rebuild from source" })
|
|
1921
2017
|
] }) : null,
|
|
2018
|
+
state.sourceStale ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { role: "alert", className: "matrx-alchemy-decision", children: "The source changed since this draft. Use Refresh source to review the latest data." }) : null,
|
|
1922
2019
|
state.pending ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "matrx-alchemy-decision", role: "alert", children: [
|
|
1923
2020
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { children: "A fresh source snapshot is ready." }),
|
|
1924
2021
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Button, { type: "button", variant: "outline", onClick: () => resolveCapture("keep"), children: "Keep current snapshot and draft" }),
|