@deadragdoll/reactnu 0.1.73 → 0.1.74

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.
@@ -16,6 +16,8 @@ export type TreeListViewProps<T extends TreeListItemBase<T>> = Omit<HTMLAttribut
16
16
  acceptsDrop?: (item: NuDragDropItem) => boolean;
17
17
  activeItemId?: string;
18
18
  checkedIds?: string[];
19
+ /** Persists user-resized column widths in `localStorage` under `reactnu.<key>`. */
20
+ columnStoreKey?: string;
19
21
  onPopupMenu?: (event: MouseEvent<HTMLDivElement>, item: T, context: TreeListCellContext<T>) => void;
20
22
  columns: TreeListColumn<T>[];
21
23
  data: T[];
@@ -39,5 +41,9 @@ export type TreeListViewProps<T extends TreeListItemBase<T>> = Omit<HTMLAttribut
39
41
  selectedId?: string;
40
42
  uncheckedShape?: "box" | "none";
41
43
  };
42
- declare function TreeListViewInner<T extends TreeListItemBase<T>>({ acceptsDrop, activeItemId: activeItemIdProp, checkedIds, className, columns, onPopupMenu, data, dataVersion, defaultActiveItemId, defaultExpandedIds, emptyText, expandedIds, getDragItem, getCellContent, onActiveItemChange, onExpandedIdsChange, onItemCheckChange, onItemDoubleClick, onItemDragOut, onItemSelect, onDrop, selectedId, uncheckedShape, ...props }: TreeListViewProps<T>, ref: ForwardedRef<TreeListViewHandle>): import("react/jsx-runtime").JSX.Element;
44
+ declare function TreeListViewInner<T extends TreeListItemBase<T>>({ acceptsDrop, activeItemId: activeItemIdProp, checkedIds, className, columnStoreKey, columns, onPopupMenu, data, dataVersion, defaultActiveItemId, defaultExpandedIds, emptyText, expandedIds, getDragItem, getCellContent, onActiveItemChange, onExpandedIdsChange, onItemCheckChange, onItemDoubleClick, onItemDragOut, onItemSelect, onDrop, selectedId, uncheckedShape, ...props }: TreeListViewProps<T>, ref: ForwardedRef<TreeListViewHandle>): import("react/jsx-runtime").JSX.Element;
43
45
  export declare const TreeListView: <T extends TreeListItemBase<T>>(props: TreeListViewProps<T> & RefAttributes<TreeListViewHandle>) => ReturnType<typeof TreeListViewInner>;
46
+ /** Semantic alias for `TreeListView` when used as a hierarchical data table. */
47
+ export declare const TreeTable: <T extends TreeListItemBase<T>>(props: TreeListViewProps<T> & RefAttributes<TreeListViewHandle>) => ReturnType<typeof TreeListViewInner>;
48
+ export type TreeTableHandle = TreeListViewHandle;
49
+ export type TreeTableProps<T extends TreeListItemBase<T>> = TreeListViewProps<T>;
package/dist/index.cjs CHANGED
@@ -71,6 +71,7 @@ __export(index_exports, {
71
71
  ToolDropButton: () => ToolDropButton,
72
72
  ToolSeparator: () => ToolSeparator,
73
73
  TreeListView: () => TreeListView,
74
+ TreeTable: () => TreeTable,
74
75
  TreeView: () => TreeView,
75
76
  Window: () => Window,
76
77
  WindowBar: () => WindowBar,
@@ -9803,11 +9804,42 @@ var TreeListViewRow = (0, import_react46.memo)(
9803
9804
 
9804
9805
  // src/components/TreeListView/TreeListView.tsx
9805
9806
  var import_jsx_runtime63 = require("react/jsx-runtime");
9807
+ function getColumnStorageKey(columnStoreKey) {
9808
+ const normalizedKey = columnStoreKey?.trim();
9809
+ return normalizedKey ? `reactnu.${normalizedKey}` : null;
9810
+ }
9811
+ function readStoredColumnWidths(storageKey, columns) {
9812
+ if (!storageKey || typeof window === "undefined") {
9813
+ return {};
9814
+ }
9815
+ try {
9816
+ const rawValue = window.localStorage.getItem(storageKey);
9817
+ if (!rawValue) {
9818
+ return {};
9819
+ }
9820
+ const parsedValue = JSON.parse(rawValue);
9821
+ if (!parsedValue || typeof parsedValue !== "object") {
9822
+ return {};
9823
+ }
9824
+ return Object.fromEntries(
9825
+ columns.flatMap((column) => {
9826
+ const width = parsedValue[column.id];
9827
+ if (typeof width !== "number" || !Number.isFinite(width)) {
9828
+ return [];
9829
+ }
9830
+ return [[column.id, Math.max(width, column.minWidth ?? 0)]];
9831
+ })
9832
+ );
9833
+ } catch {
9834
+ return {};
9835
+ }
9836
+ }
9806
9837
  function TreeListViewInner({
9807
9838
  acceptsDrop,
9808
9839
  activeItemId: activeItemIdProp,
9809
9840
  checkedIds,
9810
9841
  className,
9842
+ columnStoreKey,
9811
9843
  columns,
9812
9844
  onPopupMenu,
9813
9845
  data,
@@ -9832,6 +9864,9 @@ function TreeListViewInner({
9832
9864
  const rootRef = (0, import_react47.useRef)(null);
9833
9865
  const [rootElement, setRootElement] = (0, import_react47.useState)(null);
9834
9866
  const treeId = (0, import_react47.useId)();
9867
+ const storageKey = getColumnStorageKey(columnStoreKey);
9868
+ const loadedStorageKeyRef = (0, import_react47.useRef)(storageKey);
9869
+ const shouldPersistColumnWidthsRef = (0, import_react47.useRef)(false);
9835
9870
  const itemRefs = (0, import_react47.useRef)({});
9836
9871
  const resizeFrameRef = (0, import_react47.useRef)(null);
9837
9872
  const resizeStateRef = (0, import_react47.useRef)(null);
@@ -9845,7 +9880,7 @@ function TreeListViewInner({
9845
9880
  });
9846
9881
  const [uncontrolledSelectedId, setUncontrolledSelectedId] = (0, import_react47.useState)(null);
9847
9882
  const [autoColumnWidths, setAutoColumnWidths] = (0, import_react47.useState)({});
9848
- const [userColumnWidths, setUserColumnWidths] = (0, import_react47.useState)({});
9883
+ const [userColumnWidths, setUserColumnWidths] = (0, import_react47.useState)(() => readStoredColumnWidths(storageKey, columns));
9849
9884
  const isActiveControlled = activeItemIdProp !== void 0;
9850
9885
  const resolvedExpandedIds = isExpandedControlled ? expandedIds : uncontrolledExpandedIds;
9851
9886
  const expandedIdSet = (0, import_react47.useMemo)(
@@ -9907,6 +9942,27 @@ function TreeListViewInner({
9907
9942
  block: "nearest"
9908
9943
  });
9909
9944
  }, [resolvedActiveItemId]);
9945
+ (0, import_react47.useEffect)(() => {
9946
+ if (loadedStorageKeyRef.current === storageKey) {
9947
+ return;
9948
+ }
9949
+ loadedStorageKeyRef.current = storageKey;
9950
+ shouldPersistColumnWidthsRef.current = false;
9951
+ setUserColumnWidths(readStoredColumnWidths(storageKey, columns));
9952
+ }, [columns, storageKey]);
9953
+ (0, import_react47.useEffect)(() => {
9954
+ if (!shouldPersistColumnWidthsRef.current) {
9955
+ return;
9956
+ }
9957
+ shouldPersistColumnWidthsRef.current = false;
9958
+ if (!storageKey || typeof window === "undefined") {
9959
+ return;
9960
+ }
9961
+ try {
9962
+ window.localStorage.setItem(storageKey, JSON.stringify(userColumnWidths));
9963
+ } catch {
9964
+ }
9965
+ }, [storageKey, userColumnWidths]);
9910
9966
  const registerItemRef = (0, import_react47.useCallback)(
9911
9967
  (itemId, node) => {
9912
9968
  itemRefs.current[itemId] = node;
@@ -10197,9 +10253,15 @@ function TreeListViewInner({
10197
10253
  return;
10198
10254
  }
10199
10255
  setUserColumnWidths(
10200
- (currentWidths) => currentWidths[resizeState.columnId] === resizeState.nextWidth ? currentWidths : {
10201
- ...currentWidths,
10202
- [resizeState.columnId]: resizeState.nextWidth
10256
+ (currentWidths) => {
10257
+ if (currentWidths[resizeState.columnId] === resizeState.nextWidth) {
10258
+ return currentWidths;
10259
+ }
10260
+ shouldPersistColumnWidthsRef.current = true;
10261
+ return {
10262
+ ...currentWidths,
10263
+ [resizeState.columnId]: resizeState.nextWidth
10264
+ };
10203
10265
  }
10204
10266
  );
10205
10267
  }
@@ -10330,6 +10392,7 @@ function TreeListViewInner({
10330
10392
  );
10331
10393
  }
10332
10394
  var TreeListView = (0, import_react47.forwardRef)(TreeListViewInner);
10395
+ var TreeTable = TreeListView;
10333
10396
 
10334
10397
  // src/theme/NuThemeProvider.tsx
10335
10398
  var import_react49 = require("react");
@@ -10789,6 +10852,7 @@ function NuThemeProvider({
10789
10852
  ToolDropButton,
10790
10853
  ToolSeparator,
10791
10854
  TreeListView,
10855
+ TreeTable,
10792
10856
  TreeView,
10793
10857
  Window,
10794
10858
  WindowBar,
package/dist/index.js CHANGED
@@ -9841,11 +9841,42 @@ var TreeListViewRow = memo5(
9841
9841
 
9842
9842
  // src/components/TreeListView/TreeListView.tsx
9843
9843
  import { jsx as jsx63, jsxs as jsxs41 } from "react/jsx-runtime";
9844
+ function getColumnStorageKey(columnStoreKey) {
9845
+ const normalizedKey = columnStoreKey?.trim();
9846
+ return normalizedKey ? `reactnu.${normalizedKey}` : null;
9847
+ }
9848
+ function readStoredColumnWidths(storageKey, columns) {
9849
+ if (!storageKey || typeof window === "undefined") {
9850
+ return {};
9851
+ }
9852
+ try {
9853
+ const rawValue = window.localStorage.getItem(storageKey);
9854
+ if (!rawValue) {
9855
+ return {};
9856
+ }
9857
+ const parsedValue = JSON.parse(rawValue);
9858
+ if (!parsedValue || typeof parsedValue !== "object") {
9859
+ return {};
9860
+ }
9861
+ return Object.fromEntries(
9862
+ columns.flatMap((column) => {
9863
+ const width = parsedValue[column.id];
9864
+ if (typeof width !== "number" || !Number.isFinite(width)) {
9865
+ return [];
9866
+ }
9867
+ return [[column.id, Math.max(width, column.minWidth ?? 0)]];
9868
+ })
9869
+ );
9870
+ } catch {
9871
+ return {};
9872
+ }
9873
+ }
9844
9874
  function TreeListViewInner({
9845
9875
  acceptsDrop,
9846
9876
  activeItemId: activeItemIdProp,
9847
9877
  checkedIds,
9848
9878
  className,
9879
+ columnStoreKey,
9849
9880
  columns,
9850
9881
  onPopupMenu,
9851
9882
  data,
@@ -9870,6 +9901,9 @@ function TreeListViewInner({
9870
9901
  const rootRef = useRef23(null);
9871
9902
  const [rootElement, setRootElement] = useState31(null);
9872
9903
  const treeId = useId17();
9904
+ const storageKey = getColumnStorageKey(columnStoreKey);
9905
+ const loadedStorageKeyRef = useRef23(storageKey);
9906
+ const shouldPersistColumnWidthsRef = useRef23(false);
9873
9907
  const itemRefs = useRef23({});
9874
9908
  const resizeFrameRef = useRef23(null);
9875
9909
  const resizeStateRef = useRef23(null);
@@ -9883,7 +9917,7 @@ function TreeListViewInner({
9883
9917
  });
9884
9918
  const [uncontrolledSelectedId, setUncontrolledSelectedId] = useState31(null);
9885
9919
  const [autoColumnWidths, setAutoColumnWidths] = useState31({});
9886
- const [userColumnWidths, setUserColumnWidths] = useState31({});
9920
+ const [userColumnWidths, setUserColumnWidths] = useState31(() => readStoredColumnWidths(storageKey, columns));
9887
9921
  const isActiveControlled = activeItemIdProp !== void 0;
9888
9922
  const resolvedExpandedIds = isExpandedControlled ? expandedIds : uncontrolledExpandedIds;
9889
9923
  const expandedIdSet = useMemo20(
@@ -9945,6 +9979,27 @@ function TreeListViewInner({
9945
9979
  block: "nearest"
9946
9980
  });
9947
9981
  }, [resolvedActiveItemId]);
9982
+ useEffect17(() => {
9983
+ if (loadedStorageKeyRef.current === storageKey) {
9984
+ return;
9985
+ }
9986
+ loadedStorageKeyRef.current = storageKey;
9987
+ shouldPersistColumnWidthsRef.current = false;
9988
+ setUserColumnWidths(readStoredColumnWidths(storageKey, columns));
9989
+ }, [columns, storageKey]);
9990
+ useEffect17(() => {
9991
+ if (!shouldPersistColumnWidthsRef.current) {
9992
+ return;
9993
+ }
9994
+ shouldPersistColumnWidthsRef.current = false;
9995
+ if (!storageKey || typeof window === "undefined") {
9996
+ return;
9997
+ }
9998
+ try {
9999
+ window.localStorage.setItem(storageKey, JSON.stringify(userColumnWidths));
10000
+ } catch {
10001
+ }
10002
+ }, [storageKey, userColumnWidths]);
9948
10003
  const registerItemRef = useCallback8(
9949
10004
  (itemId, node) => {
9950
10005
  itemRefs.current[itemId] = node;
@@ -10235,9 +10290,15 @@ function TreeListViewInner({
10235
10290
  return;
10236
10291
  }
10237
10292
  setUserColumnWidths(
10238
- (currentWidths) => currentWidths[resizeState.columnId] === resizeState.nextWidth ? currentWidths : {
10239
- ...currentWidths,
10240
- [resizeState.columnId]: resizeState.nextWidth
10293
+ (currentWidths) => {
10294
+ if (currentWidths[resizeState.columnId] === resizeState.nextWidth) {
10295
+ return currentWidths;
10296
+ }
10297
+ shouldPersistColumnWidthsRef.current = true;
10298
+ return {
10299
+ ...currentWidths,
10300
+ [resizeState.columnId]: resizeState.nextWidth
10301
+ };
10241
10302
  }
10242
10303
  );
10243
10304
  }
@@ -10368,6 +10429,7 @@ function TreeListViewInner({
10368
10429
  );
10369
10430
  }
10370
10431
  var TreeListView = forwardRef4(TreeListViewInner);
10432
+ var TreeTable = TreeListView;
10371
10433
 
10372
10434
  // src/theme/NuThemeProvider.tsx
10373
10435
  import {
@@ -10831,6 +10893,7 @@ export {
10831
10893
  ToolDropButton,
10832
10894
  ToolSeparator,
10833
10895
  TreeListView,
10896
+ TreeTable,
10834
10897
  TreeView,
10835
10898
  Window,
10836
10899
  WindowBar,
@@ -96,8 +96,8 @@ See: [Slot Customization](./SLOT_CUSTOMIZATION.md)
96
96
  docs: [ListView](./ListView.md)
97
97
  - `TreeView`
98
98
  docs: [TreeView](./TreeView.md)
99
- - `TreeListView`
100
- docs: [TreeListView](./TreeListView.md)
99
+ - `TreeListView`, `TreeTable`
100
+ docs: [TreeListView](./TreeListView.md), [TreeTable](./TreeTable.md)
101
101
  - Shared drag-and-drop contract
102
102
  docs: [NuDragDropProvider](./NuDragDropProvider.md)
103
103
 
@@ -42,6 +42,7 @@
42
42
  - [TickBar](./TickBar.md)
43
43
  - [ToolBar](./ToolBar.md)
44
44
  - [TreeListView](./TreeListView.md)
45
+ - [TreeTable](./TreeTable.md)
45
46
  - [TreeView](./TreeView.md)
46
47
  - [NuView](./NuView.md)
47
48
  - [Window](./Window.md)
@@ -44,6 +44,8 @@ const columns: TreeListColumn<Node>[] = [
44
44
  ## Key Props
45
45
 
46
46
  - `columns`
47
+ - `columnStoreKey` — stores user-resized widths under `reactnu.<key>` in
48
+ `localStorage`
47
49
  - `data`
48
50
  - `getCellContent`
49
51
  - `dataVersion`
@@ -87,6 +89,9 @@ const columns: TreeListColumn<Node>[] = [
87
89
  - Columns auto-size from visible content by default.
88
90
  - `minWidth` sets a lower bound for auto-sized and resized columns.
89
91
  - Columns are resizable unless `resizable={false}` is set on a column.
92
+ - Give `columnStoreKey` a stable, application-owned name to restore a user's
93
+ resized widths on the next mount. Missing, malformed, or unavailable browser
94
+ storage falls back to the column definitions without throwing.
90
95
  - Right-clicking an enabled row activates it and calls
91
96
  `onPopupMenu(event, item, context)`. `context` has the same `depth`, `isLeaf`,
92
97
  `item`, and `rowIndex` fields as `getCellContent`; the host owns rendering the
@@ -0,0 +1,21 @@
1
+ # TreeTable
2
+
3
+ `TreeTable` is the semantic name for `TreeListView` when it is used as a
4
+ hierarchical report table. It is the same component and accepts the same props,
5
+ including tree navigation, report cells, drag-and-drop, and column resizing.
6
+
7
+ ## Usage
8
+
9
+ ```tsx
10
+ <TreeTable
11
+ columnStoreKey="workspace-files"
12
+ columns={columns}
13
+ data={nodes}
14
+ />
15
+ ```
16
+
17
+ `columnStoreKey="workspace-files"` persists user-resized widths as JSON in
18
+ `localStorage` under `reactnu.workspace-files`. Only widths changed by the user
19
+ are stored. Invalid or unavailable storage is ignored safely.
20
+
21
+ For the full API, see [TreeListView](./TreeListView.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deadragdoll/reactnu",
3
- "version": "0.1.73",
3
+ "version": "0.1.74",
4
4
  "private": false,
5
5
  "description": "React components with a precise DOS-era utility interface.",
6
6
  "license": "MIT",