@djangocfg/widget-data 0.1.1 → 0.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/widget-data",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Data surfaces: file tree, JSON viewer, data grid and table, kanban, listbox, masonry and timeline",
5
5
  "keywords": [
6
6
  "tree",
@@ -85,25 +85,26 @@
85
85
  "scripts": {
86
86
  "check": "tsc --noEmit",
87
87
  "check-types": "tsc --noEmit",
88
+ "lint": "eslint .",
88
89
  "test": "vitest run",
89
90
  "test:watch": "vitest"
90
91
  },
91
92
  "dependencies": {
92
- "@djangocfg/widget-kit": "^0.1.1",
93
+ "@djangocfg/widget-kit": "^0.1.3",
93
94
  "@dnd-kit/core": "^6.3.1",
94
95
  "@radix-ui/react-direction": "^1.1.2",
95
96
  "@radix-ui/react-slot": "^1.3.0",
96
97
  "class-variance-authority": "^0.7.1"
97
98
  },
98
99
  "peerDependencies": {
99
- "@djangocfg/ui-core": "^2.1.542",
100
+ "@djangocfg/ui-core": "^2.1.544",
100
101
  "lucide-react": "^0.545.0",
101
102
  "react": "^19.0.0",
102
103
  "react-dom": "^19.0.0"
103
104
  },
104
105
  "devDependencies": {
105
- "@djangocfg/typescript-config": "^2.1.542",
106
- "@djangocfg/ui-core": "^2.1.542",
106
+ "@djangocfg/typescript-config": "^2.1.544",
107
+ "@djangocfg/ui-core": "^2.1.544",
107
108
  "@storybook/react-vite": "^10.5.0",
108
109
  "@types/node": "^25.9.5",
109
110
  "@types/react": "19.2.15",
@@ -25,14 +25,14 @@ import type {
25
25
  // Reducer
26
26
  // =====================================================================
27
27
 
28
- interface State<T> {
28
+ interface State {
29
29
  sort: DataGridSortState;
30
30
  filters: DataGridFilterState;
31
31
  pagination: DataGridPaginationState;
32
32
  selected: Set<DataGridRowId>;
33
33
  }
34
34
 
35
- type Action<T> =
35
+ type Action =
36
36
  | { type: 'set-sort'; sort: DataGridSortState }
37
37
  | { type: 'set-filter'; key: string; value: string }
38
38
  | { type: 'clear-filters' }
@@ -44,7 +44,7 @@ type Action<T> =
44
44
  | { type: 'select-all'; ids: DataGridRowId[] }
45
45
  | { type: 'reset-pagination' };
46
46
 
47
- const reducer = <T,>(state: State<T>, action: Action<T>): State<T> => {
47
+ const reducer = (state: State, action: Action): State => {
48
48
  switch (action.type) {
49
49
  case 'set-sort':
50
50
  return { ...state, sort: action.sort };
@@ -203,7 +203,7 @@ export function DataGridProvider<T>(props: DataGridProviderProps<T>) {
203
203
  const isControlledFilters = controlledFilters !== undefined;
204
204
  const isControlledPagination = controlledPagination !== undefined;
205
205
 
206
- const [state, dispatch] = useReducer(reducer<T>, undefined, () => ({
206
+ const [state, dispatch] = useReducer(reducer, undefined, () => ({
207
207
  sort: initialSort ?? defaultSort,
208
208
  filters: initialFilters ?? {},
209
209
  pagination: initialPagination ?? defaultPagination,
@@ -5,7 +5,6 @@ import { cn } from '@djangocfg/ui-core/lib';
5
5
  import { ArrowUp, ArrowDown, ArrowUpDown } from 'lucide-react';
6
6
 
7
7
  import { useDataGridContext } from '../context/DataGridContext';
8
- import { DataGridCell } from './DataGridCell';
9
8
 
10
9
  export function DataGridHeader() {
11
10
  const { columns, selectionMode, selectAllVisible, isSelected, rows, getRowId, stickyHeader } =
@@ -25,14 +25,14 @@ import type {
25
25
  // Reducer
26
26
  // =====================================================================
27
27
 
28
- interface State<T> {
28
+ interface State {
29
29
  sort: DataTableSortState;
30
30
  filters: DataTableFilterState;
31
31
  pagination: DataTablePaginationState;
32
32
  selected: Set<DataTableRowId>;
33
33
  }
34
34
 
35
- type Action<T> =
35
+ type Action =
36
36
  | { type: 'set-sort'; sort: DataTableSortState }
37
37
  | { type: 'set-filter'; key: string; value: string }
38
38
  | { type: 'clear-filters' }
@@ -43,7 +43,7 @@ type Action<T> =
43
43
  | { type: 'clear-selection' }
44
44
  | { type: 'select-all'; ids: DataTableRowId[] };
45
45
 
46
- const reducer = <T,>(state: State<T>, action: Action<T>): State<T> => {
46
+ const reducer = (state: State, action: Action): State => {
47
47
  switch (action.type) {
48
48
  case 'set-sort':
49
49
  return { ...state, sort: action.sort };
@@ -197,7 +197,7 @@ export function DataTableProvider<T>(props: DataTableProviderProps<T>) {
197
197
  const isControlledFilters = controlledFilters !== undefined;
198
198
  const isControlledPagination = controlledPagination !== undefined;
199
199
 
200
- const [state, dispatch] = useReducer(reducer<T>, undefined, () => ({
200
+ const [state, dispatch] = useReducer(reducer, undefined, () => ({
201
201
  sort: initialSort ?? defaultSort,
202
202
  filters: initialFilters ?? {},
203
203
  pagination: initialPagination ?? defaultPagination,
@@ -4,13 +4,9 @@ import * as React from 'react';
4
4
  import { cn } from '@djangocfg/ui-core/lib';
5
5
  import {
6
6
  Table,
7
- TableHeader,
8
7
  TableBody,
9
- TableFooter,
10
8
  TableRow,
11
- TableHead,
12
9
  TableCell,
13
- TableCaption,
14
10
  } from '@djangocfg/ui-core/components';
15
11
 
16
12
  import type { DataTableProps } from '../types';
@@ -37,7 +37,9 @@ export function createFileIconSlot<T>({
37
37
  size = 16,
38
38
  folderOverrides,
39
39
  }: CreateFileIconSlotOptions<T>): TreeRowSlot<T> {
40
- return ({ node, isFolder, isExpanded }) => (
40
+ // Named so the slot shows as `FileIconSlot` in React devtools rather than
41
+ // as an anonymous function.
42
+ const FileIconSlot: TreeRowSlot<T> = ({ node, isFolder, isExpanded }) => (
41
43
  <FileIcon
42
44
  name={getName(node)}
43
45
  isFolder={isFolder}
@@ -46,4 +48,5 @@ export function createFileIconSlot<T>({
46
48
  folderOverrides={folderOverrides}
47
49
  />
48
50
  );
51
+ return FileIconSlot;
49
52
  }
@@ -99,11 +99,17 @@ function hasSearchMatch(value: JsonValue, key: string | number, query: string):
99
99
  * brick-red strings, forest-green numbers) — pastels read as washed-out
100
100
  * on a white background. Dark mirrors the One Dark family.
101
101
  */
102
+ // Syntax-highlighting hues, not status colours: `violet` names "this is a key",
103
+ // not "info", and `emerald` names "this is a string", not "success". A semantic
104
+ // token here would assert a meaning the value does not have, and re-theming
105
+ // would silently recolour the grammar.
102
106
  const TOKEN_CLASS: Record<string, string> = {
107
+ /* eslint-disable no-restricted-syntax */
103
108
  key: 'text-[#001080] dark:text-violet-400',
104
109
  string: 'text-[#a31515] dark:text-emerald-400',
105
110
  number: 'text-[#098658] dark:text-sky-400',
106
111
  boolean: 'text-[#0550ae] dark:text-amber-400',
112
+ /* eslint-enable no-restricted-syntax */
107
113
  null: 'text-muted-foreground/70 dark:text-muted-foreground/60',
108
114
  punctuation: 'text-muted-foreground dark:text-muted-foreground',
109
115
  };
@@ -13,7 +13,7 @@ interface KanbanColumnProps {
13
13
  }
14
14
 
15
15
  export function KanbanColumn({ column }: KanbanColumnProps) {
16
- const { renderColumnHeader, emptyColumnMessage, dragState, setDragState, moveCard, reorderCard } =
16
+ const { renderColumnHeader, emptyColumnMessage, dragState, setDragState, moveCard } =
17
17
  useKanbanContext();
18
18
 
19
19
  const handlePointerEnter = React.useCallback(() => {
@@ -2,7 +2,6 @@
2
2
 
3
3
  import * as React from "react";
4
4
  import { Slot } from "@radix-ui/react-slot";
5
- import { useComposedRefs } from "@djangocfg/ui-core/lib";
6
5
  import { cn } from "@djangocfg/ui-core/lib";
7
6
  import type { ListboxGroupProps, ListboxGroupLabelProps } from "../types";
8
7
 
@@ -47,8 +47,16 @@ const ListboxItem = React.forwardRef<HTMLDivElement, ListboxItemProps>(
47
47
  );
48
48
  }, [value, isDisabled, context.onItemRegister, groupContext?.id, onSelect]);
49
49
 
50
+ // The handlers below depend on individual context MEMBERS, not on
51
+ // `context`. Every one is a `useCallback` in ListboxProvider and the
52
+ // context is a `useMemo` over exactly them, so a member is stable whenever
53
+ // it matters. Depending on the whole object instead would rebuild all six
54
+ // handlers — and re-register the item — whenever any unrelated context
55
+ // field moved. The linter cannot see through the member access, hence the
56
+ // per-hook disables.
50
57
  const onBlur = React.useCallback(() => {
51
58
  context.onItemBlur();
59
+ // eslint-disable-next-line react-hooks/exhaustive-deps
52
60
  }, [context.onItemBlur]);
53
61
 
54
62
  const onClick = React.useCallback(
@@ -59,6 +67,7 @@ const ListboxItem = React.forwardRef<HTMLDivElement, ListboxItemProps>(
59
67
  context.onItemSelect(value, isMultipleSelectionKey);
60
68
  }
61
69
  },
70
+ // eslint-disable-next-line react-hooks/exhaustive-deps
62
71
  [context.onItemSelect, value, isDisabled, context.multiple],
63
72
  );
64
73
 
@@ -67,6 +76,7 @@ const ListboxItem = React.forwardRef<HTMLDivElement, ListboxItemProps>(
67
76
  context.onItemFocus(value);
68
77
  context.onItemHighlight(value);
69
78
  }
79
+ // eslint-disable-next-line react-hooks/exhaustive-deps
70
80
  }, [context.onItemFocus, context.onItemHighlight, isDisabled, value]);
71
81
 
72
82
  const onKeyDown = React.useCallback(
@@ -78,17 +88,20 @@ const ListboxItem = React.forwardRef<HTMLDivElement, ListboxItemProps>(
78
88
  return;
79
89
  }
80
90
  },
91
+ // eslint-disable-next-line react-hooks/exhaustive-deps
81
92
  [context.onItemBlur],
82
93
  );
83
94
 
84
95
  const onPointerLeave = React.useCallback(() => {
85
96
  context.onItemHighlight(null);
97
+ // eslint-disable-next-line react-hooks/exhaustive-deps
86
98
  }, [context.onItemHighlight]);
87
99
 
88
100
  const onPointerMove = React.useCallback(() => {
89
101
  if (!isDisabled) {
90
102
  context.onItemHighlight(value);
91
103
  }
104
+ // eslint-disable-next-line react-hooks/exhaustive-deps
92
105
  }, [context.onItemHighlight, isDisabled, value]);
93
106
 
94
107
  const ItemPrimitive = asChild ? Slot : "div";
@@ -3,7 +3,6 @@
3
3
  import * as React from "react";
4
4
  import { Slot } from "@radix-ui/react-slot";
5
5
  import { useComposedRefs } from "@djangocfg/ui-core/lib";
6
- import { cn } from "@djangocfg/ui-core/lib";
7
6
  import type { MasonryProps, MasonryItemProps, Positioner, PositionerItem } from "../types";
8
7
 
9
8
  // ============================================================================
@@ -615,13 +614,6 @@ function useDebouncedWindowSize(options: DebouncedWindowSizeOptions) {
615
614
  return size;
616
615
  }
617
616
 
618
- function useLazyRef<T>(init: () => T): React.RefObject<T> {
619
- const ref = React.useRef<T | null>(null);
620
- if (ref.current === null) {
621
- ref.current = init();
622
- }
623
- return ref as React.RefObject<T>;
624
- }
625
617
 
626
618
  type OnRafScheduleReturn<T extends unknown[]> = { (...args: T): void; cancel: () => void };
627
619
 
@@ -24,7 +24,7 @@ import { useSelection } from './selection';
24
24
  import { useRename } from './rename';
25
25
  import { useClipboard } from './clipboard';
26
26
  import { useResolvedMenu, renderItemsAsContextMenu, tidyMenuItems } from './menu';
27
- import { useDnd, type UseDndReturn } from './dnd';
27
+ import { useDnd } from './dnd';
28
28
  import { usePersistSync } from './persist';
29
29
  import type { TreeContextValue } from './TreeContextValue';
30
30
 
@@ -189,6 +189,11 @@ export function TreeProvider<T>(props: TreeProviderProps<T>) {
189
189
  cache,
190
190
  filterNode,
191
191
  }),
192
+ // `state.cacheTick` reads as unnecessary because `cache` is a stable
193
+ // ref-backed Map whose identity never changes when it mutates. It is the
194
+ // only invalidation signal: drop it and rows for async-loaded children are
195
+ // never flattened, so an expanded folder stays visually empty.
196
+ // eslint-disable-next-line react-hooks/exhaustive-deps
192
197
  [data, state.expanded, state.cacheTick, cache, filterNode],
193
198
  );
194
199
 
@@ -104,6 +104,11 @@ export function useAsyncChildren<T>({
104
104
  };
105
105
  walk(data);
106
106
  return map;
107
+ // `cacheTick` reads as unnecessary because the walk reaches the cache
108
+ // through `cacheRef.current`, which the linter cannot track. It is the only
109
+ // invalidation signal the cache has: drop it and `nodeById` keeps the map
110
+ // built before the async load, so loaded children stay invisible.
111
+ // eslint-disable-next-line react-hooks/exhaustive-deps
107
112
  }, [data, cacheTick]);
108
113
 
109
114
  // Auto-fetch every expanded folder that isn't already in cache. Cheap
@@ -50,7 +50,6 @@ export function useRename<T>({
50
50
  if (!enableInlineRename) return;
51
51
  if (!adapter?.rename) {
52
52
  if (process.env.NODE_ENV !== 'production') {
53
- // eslint-disable-next-line no-console
54
53
  console.warn(
55
54
  '[Tree] startRename called but adapter.rename is not defined.',
56
55
  );
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import type { FlatRow, TreeItemId, TreeMovePosition, TreeNode } from '../types';
3
+ import type { TreeItemId, TreeMovePosition, TreeNode } from '../types';
4
4
 
5
5
  // =====================================================================
6
6
  // Drag-and-drop primitives — pure, framework-agnostic.
@@ -9,12 +9,12 @@ import type { FlatRow } from '../../types';
9
9
  *
10
10
  * Always selects (single-select semantics for keyboard input).
11
11
  */
12
- export type ActivateOutcome<T> =
12
+ export type ActivateOutcome =
13
13
  | { kind: 'toggle-folder'; id: string; willExpand: boolean }
14
14
  | { kind: 'activate-leaf'; id: string }
15
15
  | { kind: 'noop' };
16
16
 
17
- export function resolveActivate<T>(current: FlatRow<T> | null): ActivateOutcome<T> {
17
+ export function resolveActivate<T>(current: FlatRow<T> | null): ActivateOutcome {
18
18
  if (!current) return { kind: 'noop' };
19
19
  if (current.isFolder) {
20
20
  return {
@@ -9,7 +9,7 @@ import type { FlatRow } from '../../types';
9
9
  * - expanded folder → jump to first visible child
10
10
  * - leaf → no-op
11
11
  */
12
- export type RightArrowOutcome<T> =
12
+ export type RightArrowOutcome =
13
13
  | { kind: 'expand'; id: string }
14
14
  | { kind: 'focus'; id: string }
15
15
  | { kind: 'noop' };
@@ -18,7 +18,7 @@ export function resolveRightArrow<T>(
18
18
  current: FlatRow<T> | null,
19
19
  rows: readonly FlatRow<T>[],
20
20
  idx: number,
21
- ): RightArrowOutcome<T> {
21
+ ): RightArrowOutcome {
22
22
  if (!current) return { kind: 'noop' };
23
23
  if (current.isFolder && !current.isExpanded) {
24
24
  return { kind: 'expand', id: current.node.id };
@@ -37,12 +37,12 @@ export function resolveRightArrow<T>(
37
37
  * - leaf / collapsed w/ parent → focus parent
38
38
  * - root leaf → no-op
39
39
  */
40
- export type LeftArrowOutcome<T> =
40
+ export type LeftArrowOutcome =
41
41
  | { kind: 'collapse'; id: string }
42
42
  | { kind: 'focus'; id: string }
43
43
  | { kind: 'noop' };
44
44
 
45
- export function resolveLeftArrow<T>(current: FlatRow<T> | null): LeftArrowOutcome<T> {
45
+ export function resolveLeftArrow<T>(current: FlatRow<T> | null): LeftArrowOutcome {
46
46
  if (!current) return { kind: 'noop' };
47
47
  if (current.isFolder && current.isExpanded) {
48
48
  return { kind: 'collapse', id: current.node.id };