@mshuja288/aksuicore 0.4.1 → 0.4.3

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 CHANGED
@@ -7,6 +7,13 @@ Versioning follows [VERSIONING.md](./VERSIONING.md).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.3] — 2026-10-13
11
+
12
+ ### Added
13
+
14
+ - `RecordWorkspace` persists preview on/off and side-rail expand/collapse globally (`aksui.workspace.chrome`) across all tables
15
+ - Filter values persist per screen at `${columnPrefsKey}.filters` when `columnPrefsKey` is set
16
+
10
17
  ## [0.4.1] — 2026-10-12
11
18
 
12
19
  ### Fixed
package/dist/aksui.css CHANGED
@@ -1222,7 +1222,8 @@ button.side-rail-add:hover {
1222
1222
  }
1223
1223
 
1224
1224
  .side-rail-columns,
1225
- .side-rail-download {
1225
+ .side-rail-download,
1226
+ .side-rail-bulk {
1226
1227
  display: grid;
1227
1228
  gap: 8px;
1228
1229
  }
@@ -1350,6 +1351,79 @@ button.side-rail-download-format:disabled {
1350
1351
  cursor: default;
1351
1352
  }
1352
1353
 
1354
+ button.side-rail-bulk-toggle {
1355
+ display: inline-flex;
1356
+ align-items: center;
1357
+ justify-content: center;
1358
+ gap: 8px;
1359
+ width: 100%;
1360
+ padding: 10px 12px;
1361
+ border: 1px solid var(--aks-border, #d7dde5);
1362
+ border-radius: var(--aks-radius);
1363
+ background: #fff;
1364
+ color: var(--aks-brand);
1365
+ box-shadow: none;
1366
+ }
1367
+
1368
+ button.side-rail-bulk-toggle svg {
1369
+ width: 18px;
1370
+ height: 18px;
1371
+ }
1372
+
1373
+ button.side-rail-bulk-toggle:hover:not(:disabled) {
1374
+ background: #f5f7fa;
1375
+ }
1376
+
1377
+ button.side-rail-bulk-toggle:disabled {
1378
+ opacity: 0.55;
1379
+ cursor: default;
1380
+ }
1381
+
1382
+ .side-rail-bulk-panel {
1383
+ display: grid;
1384
+ gap: 10px;
1385
+ padding: 8px;
1386
+ border: 1px solid var(--aks-line);
1387
+ border-radius: var(--aks-radius);
1388
+ background: #faf9f7;
1389
+ }
1390
+
1391
+ label.side-rail-bulk-mode,
1392
+ label.side-rail-bulk-file {
1393
+ display: grid;
1394
+ gap: 6px;
1395
+ margin: 0;
1396
+ font-size: 13px;
1397
+ font-weight: 600;
1398
+ color: var(--aks-brand);
1399
+ }
1400
+
1401
+ button.side-rail-bulk-submit {
1402
+ width: 100%;
1403
+ padding: 10px 12px;
1404
+ border: none;
1405
+ border-radius: var(--aks-radius);
1406
+ background: var(--aks-accent, #c45c26);
1407
+ color: #fff;
1408
+ font-weight: 600;
1409
+ box-shadow: none;
1410
+ }
1411
+
1412
+ button.side-rail-bulk-submit:disabled {
1413
+ opacity: 0.5;
1414
+ cursor: default;
1415
+ }
1416
+
1417
+ .side-rail-bulk-report {
1418
+ font-size: 12px;
1419
+ color: var(--aks-brand);
1420
+ }
1421
+
1422
+ .side-rail-bulk-errors {
1423
+ margin: 6px 0 0;
1424
+ padding-left: 18px;
1425
+ }
1426
+
1353
1427
  .side-rail-filters {
1354
1428
  display: grid;
1355
1429
  gap: 12px;
package/dist/index.cjs CHANGED
@@ -869,9 +869,45 @@ var EXPORT_FORMAT_OPTIONS = [
869
869
  { format: "xlsx", label: "Excel" },
870
870
  { format: "json", label: "JSON" }
871
871
  ];
872
+ var BULK_IMPORT_MODE_OPTIONS = [
873
+ { mode: "insert", label: "Insert" },
874
+ { mode: "update", label: "Update" },
875
+ { mode: "upsert", label: "Upsert" }
876
+ ];
872
877
  function defaultVisibleKeys(columns) {
873
878
  return columns.filter((column) => column.defaultVisible !== false).map((column) => column.key);
874
879
  }
880
+ var WORKSPACE_CHROME_KEY = "aksui.workspace.chrome";
881
+ function readWorkspaceChrome() {
882
+ if (typeof window === "undefined") {
883
+ return { previewEnabled: false, sideOpen: false };
884
+ }
885
+ try {
886
+ const raw = window.localStorage.getItem(WORKSPACE_CHROME_KEY);
887
+ if (!raw) {
888
+ return { previewEnabled: false, sideOpen: false };
889
+ }
890
+ const parsed = JSON.parse(raw);
891
+ return {
892
+ previewEnabled: Boolean(parsed.previewEnabled),
893
+ sideOpen: Boolean(parsed.sideOpen)
894
+ };
895
+ } catch {
896
+ return { previewEnabled: false, sideOpen: false };
897
+ }
898
+ }
899
+ function writeWorkspaceChrome(chrome) {
900
+ if (typeof window === "undefined") {
901
+ return;
902
+ }
903
+ try {
904
+ window.localStorage.setItem(WORKSPACE_CHROME_KEY, JSON.stringify(chrome));
905
+ } catch {
906
+ }
907
+ }
908
+ function filtersPrefsKey(columnPrefsKey) {
909
+ return columnPrefsKey ? `${columnPrefsKey}.filters` : void 0;
910
+ }
875
911
  function readStoredColumnKeys(prefsKey, fallback, allowed) {
876
912
  if (!prefsKey || typeof window === "undefined") {
877
913
  return fallback;
@@ -900,6 +936,45 @@ function writeStoredColumnKeys(prefsKey, keys) {
900
936
  } catch {
901
937
  }
902
938
  }
939
+ function readStoredFilters(prefsKey, allowed) {
940
+ if (!prefsKey || typeof window === "undefined") {
941
+ return {};
942
+ }
943
+ try {
944
+ const raw = window.localStorage.getItem(prefsKey);
945
+ if (!raw) {
946
+ return {};
947
+ }
948
+ const parsed = JSON.parse(raw);
949
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
950
+ return {};
951
+ }
952
+ const next = {};
953
+ for (const [key, value] of Object.entries(parsed)) {
954
+ if (allowed.has(key) && typeof value === "string" && value.trim()) {
955
+ next[key] = value;
956
+ }
957
+ }
958
+ return next;
959
+ } catch {
960
+ return {};
961
+ }
962
+ }
963
+ function writeStoredFilters(prefsKey, filters) {
964
+ if (!prefsKey || typeof window === "undefined") {
965
+ return;
966
+ }
967
+ try {
968
+ const trimmed = {};
969
+ for (const [key, value] of Object.entries(filters)) {
970
+ if (value.trim()) {
971
+ trimmed[key] = value;
972
+ }
973
+ }
974
+ window.localStorage.setItem(prefsKey, JSON.stringify(trimmed));
975
+ } catch {
976
+ }
977
+ }
903
978
  function compareColumnValues(column, left, right) {
904
979
  const leftRaw = column.display ? column.display(left) : column.value(left);
905
980
  const rightRaw = column.display ? column.display(right) : column.value(right);
@@ -945,6 +1020,13 @@ function PreviewIcon() {
945
1020
  /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "10", cy: "10", r: "2.4", fill: "none", stroke: "currentColor", strokeWidth: "1.6" })
946
1021
  ] });
947
1022
  }
1023
+ function UploadIcon() {
1024
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 20 20", "aria-hidden": "true", children: [
1025
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 16.5V7.3", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round" }),
1026
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6.5 10.8 10 7.2l3.5 3.6", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }),
1027
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 4.5h12", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round" })
1028
+ ] });
1029
+ }
948
1030
  function DownloadIcon() {
949
1031
  return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 20 20", "aria-hidden": "true", children: [
950
1032
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 3.5v8.2", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round" }),
@@ -994,7 +1076,11 @@ function RecordWorkspace({
994
1076
  canDelete,
995
1077
  onDownload,
996
1078
  canDownload = true,
1079
+ canExport,
997
1080
  downloadBusy = false,
1081
+ onBulkImport,
1082
+ canImport = true,
1083
+ importBusy = false,
998
1084
  showColumnPicker = true,
999
1085
  columnPrefsKey,
1000
1086
  visibleColumnKeys,
@@ -1012,13 +1098,21 @@ function RecordWorkspace({
1012
1098
  const [internalVisibleKeys, setInternalVisibleKeys] = react.useState(
1013
1099
  () => readStoredColumnKeys(columnPrefsKey, fallbackKeys, allowedKeys)
1014
1100
  );
1015
- const [filters, setFilters] = react.useState({});
1101
+ const filterStorageKey = filtersPrefsKey(columnPrefsKey);
1102
+ const [filters, setFilters] = react.useState(
1103
+ () => readStoredFilters(filterStorageKey, allowedKeys)
1104
+ );
1016
1105
  const [sort, setSort] = react.useState(null);
1017
- const [sideOpen, setSideOpen] = react.useState(false);
1106
+ const [sideOpen, setSideOpen] = react.useState(() => readWorkspaceChrome().sideOpen);
1018
1107
  const [toolsDrawerOpen, setToolsDrawerOpen] = react.useState(false);
1019
1108
  const [downloadOpen, setDownloadOpen] = react.useState(false);
1109
+ const [bulkOpen, setBulkOpen] = react.useState(false);
1110
+ const [bulkMode, setBulkMode] = react.useState("upsert");
1111
+ const [bulkFile, setBulkFile] = react.useState(null);
1112
+ const [bulkReport, setBulkReport] = react.useState(null);
1113
+ const bulkFileInputRef = react.useRef(null);
1020
1114
  const [columnsOpen, setColumnsOpen] = react.useState(false);
1021
- const [previewEnabled, setPreviewEnabled] = react.useState(false);
1115
+ const [previewEnabled, setPreviewEnabled] = react.useState(() => readWorkspaceChrome().previewEnabled);
1022
1116
  const [previewKey, setPreviewKey] = react.useState(null);
1023
1117
  const [tooltip, setTooltip] = react.useState(null);
1024
1118
  const [pageSize, setPageSize] = react.useState("15");
@@ -1033,6 +1127,9 @@ function RecordWorkspace({
1033
1127
  }
1034
1128
  setInternalVisibleKeys(readStoredColumnKeys(columnPrefsKey, fallbackKeys, allowedKeys));
1035
1129
  }, [allowedKeys, columnPrefsKey, fallbackKeys, isControlled]);
1130
+ react.useEffect(() => {
1131
+ setFilters(readStoredFilters(filtersPrefsKey(columnPrefsKey), allowedKeys));
1132
+ }, [allowedKeys, columnPrefsKey]);
1036
1133
  const resolvedVisibleKeys = react.useMemo(() => {
1037
1134
  const source = isControlled ? visibleColumnKeys : internalVisibleKeys;
1038
1135
  const filtered2 = source.filter((key) => allowedKeys.has(key));
@@ -1111,7 +1208,9 @@ function RecordWorkspace({
1111
1208
  const start = showAll ? 0 : (currentPage - 1) * numericPageSize;
1112
1209
  const pageItems = showAll ? sorted : sorted.slice(start, start + numericPageSize);
1113
1210
  const showAdd = canAdd && Boolean(onAdd);
1114
- const showDownload = Boolean(onDownload) && canDownload;
1211
+ const exportAllowed = (canExport ?? true) && canDownload;
1212
+ const showDownload = Boolean(onDownload) && exportAllowed;
1213
+ const showBulkImport = Boolean(onBulkImport) && canImport;
1115
1214
  const showFilters = filterableColumns.length > 0;
1116
1215
  const showColumns = showColumnPicker && columns.length > 0;
1117
1216
  const previewItem = previewKey === null ? null : sorted.find((item) => rowKey(item) === previewKey) ?? null;
@@ -1156,6 +1255,29 @@ function RecordWorkspace({
1156
1255
  onDownload(format, exportColumns);
1157
1256
  setDownloadOpen(false);
1158
1257
  }
1258
+ async function submitBulkImport() {
1259
+ if (!onBulkImport || importBusy || !bulkFile) {
1260
+ return;
1261
+ }
1262
+ setBulkReport(null);
1263
+ try {
1264
+ const report = await onBulkImport(bulkMode, bulkFile);
1265
+ setBulkReport(report);
1266
+ if (report.applied) {
1267
+ setBulkFile(null);
1268
+ if (bulkFileInputRef.current) {
1269
+ bulkFileInputRef.current.value = "";
1270
+ }
1271
+ }
1272
+ } catch {
1273
+ setBulkReport({
1274
+ totalRows: 0,
1275
+ failed: 1,
1276
+ applied: false,
1277
+ errors: [{ row: 0, code: "ImportFailed", message: "Import request failed." }]
1278
+ });
1279
+ }
1280
+ }
1159
1281
  function cellText(column, item) {
1160
1282
  const raw = column.display ? column.display(item) : column.value(item);
1161
1283
  return raw === null || raw === void 0 || raw === "" ? "\u2014" : String(raw);
@@ -1168,13 +1290,22 @@ function RecordWorkspace({
1168
1290
  }
1169
1291
  const previewTitle = previewItem && visibleColumns[0] ? cellText(visibleColumns[0], previewItem) : null;
1170
1292
  function changeFilter(key, value) {
1171
- setFilters((current) => ({ ...current, [key]: value }));
1293
+ setFilters((current) => {
1294
+ const next = { ...current, [key]: value };
1295
+ writeStoredFilters(filterStorageKey, next);
1296
+ return next;
1297
+ });
1172
1298
  setPage(1);
1173
1299
  }
1174
1300
  function clearFilters() {
1175
1301
  setFilters({});
1302
+ writeStoredFilters(filterStorageKey, {});
1176
1303
  setPage(1);
1177
1304
  }
1305
+ function setSideOpenValue(open) {
1306
+ setSideOpen(open);
1307
+ writeWorkspaceChrome({ previewEnabled, sideOpen: open });
1308
+ }
1178
1309
  function toggleSort(column) {
1179
1310
  if (column.sortable === false) {
1180
1311
  return;
@@ -1192,6 +1323,7 @@ function RecordWorkspace({
1192
1323
  }
1193
1324
  function setPreviewEnabledValue(enabled) {
1194
1325
  setPreviewEnabled(enabled);
1326
+ writeWorkspaceChrome({ previewEnabled: enabled, sideOpen });
1195
1327
  setTooltip(null);
1196
1328
  if (!enabled) {
1197
1329
  setPreviewKey(null);
@@ -1252,7 +1384,7 @@ function RecordWorkspace({
1252
1384
  title: "Columns",
1253
1385
  "aria-label": "Columns",
1254
1386
  onClick: () => {
1255
- setSideOpen(true);
1387
+ setSideOpenValue(true);
1256
1388
  setColumnsOpen(true);
1257
1389
  },
1258
1390
  children: [
@@ -1270,12 +1402,27 @@ function RecordWorkspace({
1270
1402
  "aria-label": "Download",
1271
1403
  disabled: downloadBusy,
1272
1404
  onClick: () => {
1273
- setSideOpen(true);
1405
+ setSideOpenValue(true);
1274
1406
  setDownloadOpen(true);
1275
1407
  },
1276
1408
  children: /* @__PURE__ */ jsxRuntime.jsx(DownloadIcon, {})
1277
1409
  }
1278
1410
  ) : null,
1411
+ showBulkImport ? /* @__PURE__ */ jsxRuntime.jsx(
1412
+ "button",
1413
+ {
1414
+ type: "button",
1415
+ className: "ghost side-rail-icon",
1416
+ title: "Bulk entry",
1417
+ "aria-label": "Bulk entry",
1418
+ disabled: importBusy,
1419
+ onClick: () => {
1420
+ setSideOpenValue(true);
1421
+ setBulkOpen(true);
1422
+ },
1423
+ children: /* @__PURE__ */ jsxRuntime.jsx(UploadIcon, {})
1424
+ }
1425
+ ) : null,
1279
1426
  showFilters ? /* @__PURE__ */ jsxRuntime.jsxs(
1280
1427
  "button",
1281
1428
  {
@@ -1400,6 +1547,77 @@ function RecordWorkspace({
1400
1547
  option.format
1401
1548
  )) }) : null
1402
1549
  ] }) : null,
1550
+ showBulkImport ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "side-rail-bulk", children: [
1551
+ /* @__PURE__ */ jsxRuntime.jsxs(
1552
+ "button",
1553
+ {
1554
+ type: "button",
1555
+ className: "ghost side-rail-bulk-toggle",
1556
+ "aria-expanded": bulkOpen,
1557
+ disabled: importBusy,
1558
+ onClick: () => setBulkOpen((open) => !open),
1559
+ children: [
1560
+ /* @__PURE__ */ jsxRuntime.jsx(UploadIcon, {}),
1561
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: importBusy ? "Importing\u2026" : "Bulk entry" })
1562
+ ]
1563
+ }
1564
+ ),
1565
+ bulkOpen ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "side-rail-bulk-panel", role: "group", "aria-label": "Bulk entry", children: [
1566
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "side-rail-bulk-mode", children: [
1567
+ "Mode",
1568
+ /* @__PURE__ */ jsxRuntime.jsx(
1569
+ Select,
1570
+ {
1571
+ value: bulkMode,
1572
+ options: BULK_IMPORT_MODE_OPTIONS.map((option) => ({ value: option.mode, label: option.label })),
1573
+ onChange: (value) => setBulkMode(value)
1574
+ }
1575
+ )
1576
+ ] }),
1577
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "side-rail-bulk-file", children: [
1578
+ "File",
1579
+ /* @__PURE__ */ jsxRuntime.jsx(
1580
+ "input",
1581
+ {
1582
+ ref: bulkFileInputRef,
1583
+ type: "file",
1584
+ accept: ".csv,.xlsx,.json",
1585
+ disabled: importBusy,
1586
+ onChange: (event) => {
1587
+ setBulkFile(event.target.files?.[0] ?? null);
1588
+ setBulkReport(null);
1589
+ }
1590
+ }
1591
+ )
1592
+ ] }),
1593
+ /* @__PURE__ */ jsxRuntime.jsx(
1594
+ "button",
1595
+ {
1596
+ type: "button",
1597
+ className: "side-rail-bulk-submit",
1598
+ disabled: importBusy || !bulkFile,
1599
+ onClick: () => void submitBulkImport(),
1600
+ children: importBusy ? "Importing\u2026" : "Import"
1601
+ }
1602
+ ),
1603
+ bulkReport ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "side-rail-bulk-report", role: "status", children: [
1604
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: bulkReport.applied ? `Applied ${bulkReport.totalRows} row(s).` : `${bulkReport.failed} error(s) in ${bulkReport.totalRows} row(s).` }),
1605
+ bulkReport.errors.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "side-rail-bulk-errors", children: [
1606
+ bulkReport.errors.slice(0, 8).map((error, index) => /* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
1607
+ "Row ",
1608
+ error.row,
1609
+ ": ",
1610
+ error.message
1611
+ ] }, `${error.row}-${error.code}-${index}`)),
1612
+ bulkReport.errors.length > 8 ? /* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
1613
+ "\u2026and ",
1614
+ bulkReport.errors.length - 8,
1615
+ " more"
1616
+ ] }) : null
1617
+ ] }) : null
1618
+ ] }) : null
1619
+ ] }) : null
1620
+ ] }) : null,
1403
1621
  showFilters ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "side-rail-filters", children: [
1404
1622
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "filter-panel-head", children: [
1405
1623
  /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Filters" }),
@@ -1601,7 +1819,7 @@ function RecordWorkspace({
1601
1819
  className: "ghost side-rail-toggle",
1602
1820
  "aria-expanded": sideOpen,
1603
1821
  title: sideOpen ? "Collapse" : "Expand",
1604
- onClick: () => setSideOpen((open) => !open),
1822
+ onClick: () => setSideOpenValue(!sideOpen),
1605
1823
  children: [
1606
1824
  /* @__PURE__ */ jsxRuntime.jsx(ChevronIcon, { open: sideOpen }),
1607
1825
  sideOpen ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Collapse" }) : null