@box-kite/react 2.2.0 → 2.2.1

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.
@@ -4,10 +4,10 @@ interface Props<TRow> {
4
4
  /** Position in the whole row list, not in the rendered window. */
5
5
  index: number;
6
6
  /**
7
- * The grid's store version. Not read: it is what tells the memo below that something the row draws
8
- * from the model has moved, since every other prop it takes holds still while the window slides.
7
+ * This row's version (`GridModel.rowVersion`). Not read: it is what tells the memo below that something
8
+ * the row draws from the model has moved, since every other prop it takes holds still while the window slides.
9
9
  */
10
- version: number;
10
+ version: string;
11
11
  }
12
12
  declare function DataGridGroupRowImpl<TRow>(props: Props<TRow>): import("react").JSX.Element;
13
13
  /**
@@ -1,6 +1,10 @@
1
1
  import { default as GridModel } from '../models/gridModel';
2
2
  interface Props<TRow> {
3
3
  grid: GridModel<TRow>;
4
+ /** `GridModel.headerVersion`. Not read: it is what tells the memo below the header has something new to draw. */
5
+ version: string;
4
6
  }
5
- export default function DataGridHeader<TRow>(props: Props<TRow>): import("react").JSX.Element;
6
- export {};
7
+ declare function DataGridHeaderImpl<TRow>(props: Props<TRow>): import("react").JSX.Element;
8
+ /** Memoized, so a press that only moves the tab stop does not redraw the header and the filters under it. */
9
+ declare const DataGridHeader: typeof DataGridHeaderImpl;
10
+ export default DataGridHeader;
@@ -4,10 +4,10 @@ interface Props<TRow> {
4
4
  /** Position in the whole row list, not in the rendered window. */
5
5
  index: number;
6
6
  /**
7
- * The grid's store version. Not read: it is what tells the memo below that something the row draws
8
- * from the model has moved, since every other prop it takes holds still while the window slides.
7
+ * This row's version (`GridModel.rowVersion`). Not read: it is what tells the memo below that something
8
+ * the row draws from the model has moved, since every other prop it takes holds still while the window slides.
9
9
  */
10
- version: number;
10
+ version: string;
11
11
  }
12
12
  declare function DataGridRowImpl<TRow>(props: Props<TRow>): import("react").JSX.Element;
13
13
  /**
@@ -4,6 +4,7 @@ import { ComponentsAndVariants } from '../../../types';
4
4
  import { SortDirection } from '../../../utils/array/arrayUtils';
5
5
  import { default as AggregateCellModel } from '../models/aggregateCellModel';
6
6
  import { default as CellModel } from '../models/cellModel';
7
+ import { DataGridLocaleText } from '../models/localeText';
7
8
  export type { SortDirection };
8
9
  export type Key = string | number;
9
10
  /**
@@ -589,10 +590,22 @@ export interface GridDefinition<TRow> {
589
590
  resizerStyle?: 'visible' | 'hover' | 'hidden';
590
591
  /** How the column follows the pointer during a resize drag. 'smooth' (default): updates batched once per animation frame (~60fps, ~1 frame behind the cursor). 'instant': updates synchronously on every pointer move so the column tracks the cursor with no added latency. */
591
592
  resizeMode?: 'smooth' | 'instant';
593
+ /**
594
+ * How a sortable header cell answers the pointer. `'static'` (default): each cell paints its own inset
595
+ * pill. `'sliding'`: one pill for the whole header that flows from cell to cell, leading with the edge it
596
+ * moves towards — measured, so it needs JavaScript, and reduced motion makes it jump instead.
597
+ */
598
+ headerHover?: 'static' | 'sliding';
592
599
  /** Control the header context menu for all columns. false hides it entirely. Object controls individual sections. Default: true. Individual column settings take priority. */
593
600
  contextMenu?: boolean | ContextMenuConfig;
594
601
  /** Custom component to render when data is empty */
595
602
  noDataComponent?: React.ReactNode;
603
+ /**
604
+ * Every word the grid writes itself — labels, accessible names, announcements, messages — for a
605
+ * translation or a house style. Any subset: what is left out stays English (`DATA_GRID_LOCALE_TEXT`).
606
+ * A value with something in it is a function, so a language can put the words in its own order.
607
+ */
608
+ localeText?: Partial<DataGridLocaleText>;
596
609
  /**
597
610
  * A row of grand totals pinned under the rows, over every column carrying an `aggregate`. It covers the
598
611
  * rows the grid holds after filtering — which is the page, not the table, when the server is paginating.
@@ -1,5 +1,6 @@
1
1
  import { ChangeDetails } from '../../react/a11y/useControllableState';
2
2
  import { RovingFocusItemProps, RovingFocusReason } from '../../react/a11y/useRovingFocus';
3
+ import { default as ActiveCellStore } from '../../utils/dataGrid/activeCellStore';
3
4
  /**
4
5
  * The grid's keyboard coordinates, shared with every cell. Rows are numbered as `aria-rowindex` is —
5
6
  * headers, then the filter row, then the body — and a cell asks for its roving tabindex by that pair, so
@@ -12,8 +13,10 @@ export interface GridNavigation {
12
13
  columnCount: number;
13
14
  /** How many rows sit above the body — the offset from a body row's index to its row number. */
14
15
  headerRowCount: number;
15
- /** The roving tabindex and the ref for one cell, in the coordinates above. */
16
- cellProps: (row: number, column: number) => RovingFocusItemProps;
16
+ /** The ref and the focus handler for one cell, in the coordinates above. Stable: the tabindex is `activeCell`. */
17
+ cellProps: (row: number, column: number) => Omit<RovingFocusItemProps, 'tabIndex'>;
18
+ /** Which cell holds the tab stop. Subscribed to per cell, so a move re-renders two cells rather than all of them. */
19
+ activeCell: ActiveCellStore;
17
20
  /**
18
21
  * Move the tab stop to a cell without a keystroke having done it — what Tab out of an editor and into
19
22
  * the next editable cell moves. `'programmatic'` rather than `'keyboard'` on purpose: a keyboard move
@@ -30,3 +33,5 @@ declare const GridNavigationContext: import('react').Context<GridNavigation | nu
30
33
  export default GridNavigationContext;
31
34
  /** The grid navigation a cell belongs to. */
32
35
  export declare function useGridNavigationContext(): GridNavigation | null;
36
+ /** One cell's roving tabindex, ref and focus handler — re-rendering the cell only when its own tabindex changes. */
37
+ export declare function useGridCellProps(navigation: GridNavigation | null, row: number, column: number): Partial<RovingFocusItemProps>;
@@ -3,10 +3,6 @@ import { default as ColumnModel } from './columnModel';
3
3
  import { default as GridModel } from './gridModel';
4
4
  import { RangeBounds } from './rangeModel';
5
5
  import { default as RowModel } from './rowModel';
6
- /** What a rejection with no message of its own says. A silent refusal is one nobody can act on. */
7
- export declare const DEFAULT_EDIT_ERROR = "Invalid value";
8
- /** What a pasted value is refused with when it is not the kind of value the cell holds at all. */
9
- export declare const WRONG_KIND_ERROR = "Wrong kind of value";
10
6
  /** A key that starts an edit by replacing the value: one printable character, unmodified. */
11
7
  export declare function isTypingKey(event: React.KeyboardEvent): boolean;
12
8
  /**
@@ -22,6 +18,10 @@ export declare function isTypingKey(event: React.KeyboardEvent): boolean;
22
18
  export default class EditModel<TRow> {
23
19
  readonly grid: GridModel<TRow>;
24
20
  constructor(grid: GridModel<TRow>);
21
+ /** What a rejection with no message of its own says. A silent refusal is one nobody can act on. */
22
+ private get invalidValue();
23
+ /** A validator that threw is a refusal, and what it threw is the message. */
24
+ private errorText;
25
25
  /** The accepted values, keyed by cell. Cleared by `clearEdits()` and by a datasource refresh. */
26
26
  private readonly values;
27
27
  /** The cells the last paste was refused on. They keep the invalid ring, which is all a batch can show. */
@@ -36,6 +36,8 @@ export default class EditModel<TRow> {
36
36
  /** The editor a cell opens, worked out from the value in it when the column names none. */
37
37
  editorFor(column: ColumnModel<TRow>, value: unknown): CellEditorConfig<TRow>;
38
38
  isEditing(rowKey: Key, columnKey: Key): boolean;
39
+ /** Everything about this row the editor draws: which cell is open, what is in it and what it was told. */
40
+ rowDigest(rowKey: Key): string;
39
41
  /** Whether an editor is open anywhere. A Ctrl+V while one is belongs to the editor, not to the grid. */
40
42
  get isOpen(): boolean;
41
43
  /** Whether the last paste was refused on this cell, which is the only mark a batch of them can carry. */
@@ -47,6 +47,6 @@ export default class ExportModel<TRow> {
47
47
  * of its own to say — the same column it occupies on screen when that column is exported at all.
48
48
  */
49
49
  private footerRow;
50
- /** The file's name, without an extension: what was asked for, else the grid's title, else `export`. */
50
+ /** The file's name, without an extension: what was asked for, else the grid's title, else `localeText.exportFileName`. */
51
51
  fileName(options?: DataGridExportOptions): string;
52
52
  }
@@ -9,6 +9,7 @@ import { default as EditModel } from './editModel';
9
9
  import { default as ExportModel } from './exportModel';
10
10
  import { default as FilterModel } from './filterModel';
11
11
  import { default as GroupRowModel } from './groupRowModel';
12
+ import { DataGridLocaleText } from './localeText';
12
13
  import { default as PaginationModel } from './paginationModel';
13
14
  import { default as RangeModel } from './rangeModel';
14
15
  import { default as RowModel } from './rowModel';
@@ -25,8 +26,27 @@ export default class GridModel<TRow> {
25
26
  subscribe: (listener: () => void) => (() => void);
26
27
  /** Monotonic snapshot version — stable between changes, increments on notify(). */
27
28
  getSnapshot: () => number;
28
- /** Signal subscribers that state changed and a re-render is needed. */
29
+ private static drawnPropsChanged;
30
+ /** Bumped by a change every row draws from — a sort, a filter, the columns — and by nothing row-local. */
31
+ private _epoch;
32
+ /** Signal subscribers that state changed and a re-render is needed. Every rendered row redraws. */
29
33
  notify: () => void;
34
+ /**
35
+ * A change that `rowVersion` sees row by row — a selection, the current cell, a keystroke in an editor —
36
+ * so only the rows whose version moved redraw, rather than every row on screen (bug #237).
37
+ */
38
+ notifyRows: () => void;
39
+ /**
40
+ * What a rendered row's memo compares: the grid-wide epoch plus the row's own state. A `Cell` of your own
41
+ * that reads something outside its row redraws on a grid-wide change, not on another row's selection.
42
+ */
43
+ rowVersion(row: RowModel<TRow> | GroupRowModel<TRow> | DetailRowModel<TRow>, index: number): string;
44
+ private _selectionVersion;
45
+ /**
46
+ * What the memoized header compares: a grid-wide change, or the selection its checkbox shows. A focus move
47
+ * re-renders the component holding the tab stop, and the header — filter row included — no longer with it.
48
+ */
49
+ get headerVersion(): string;
30
50
  /**
31
51
  * Sync incoming props. Called during render by the framework adapter — clears
32
52
  * the affected memos but does NOT notify (no extra render cycle; memos recompute
@@ -56,6 +76,10 @@ export default class GridModel<TRow> {
56
76
  get titleId(): string;
57
77
  get resizerStyle(): 'visible' | 'hover' | 'hidden';
58
78
  get resizeMode(): 'smooth' | 'instant';
79
+ get headerHover(): 'static' | 'sliding';
80
+ private _localeText?;
81
+ /** The grid's words: `def.localeText` over the English defaults, merged once per object it is given. */
82
+ get localeText(): DataGridLocaleText;
59
83
  readonly sourceColumns: import('../../../utils/memo').Memo<ColumnModel<TRow>[]>;
60
84
  readonly columns: import('../../../utils/memo').Memo<{
61
85
  start: ColumnModel<TRow>[];
@@ -231,9 +255,34 @@ export default class GridModel<TRow> {
231
255
  /** Whether any user-facing column is currently visible (else the empty-columns state shows). */
232
256
  get hasVisibleColumns(): boolean;
233
257
  get isEmpty(): boolean;
234
- /** Header select-all checkbox state. */
258
+ /**
259
+ * The rows the header checkbox acts on: what the filters leave on screen. A select-all reaching rows
260
+ * nobody can see is a bulk action on rows nobody chose; with a datasource it is the blocks it holds.
261
+ */
262
+ get selectableRows(): TRow[];
263
+ /** The keys of `selectableRows`. A datasource's grow as blocks arrive, so only the client side is kept. */
264
+ private readonly _selectableKeys;
265
+ private get selectableKeys();
266
+ private _selectionCounts?;
267
+ /** How many selected rows are on screen — read by the header and the footer on every render, so kept. */
268
+ private get shownSelectedCount();
269
+ /** Header select-all checkbox state: every row on screen is selected, whatever the filters hide. */
235
270
  get allRowsSelected(): boolean;
236
271
  get someRowsSelected(): boolean;
272
+ /**
273
+ * Selected rows the filters hide. A filter never drops a selection, so the footer says what it is
274
+ * keeping out of sight; a server's rows are unknown until fetched, so there it is never counted.
275
+ */
276
+ get hiddenSelectedCount(): number;
277
+ /** The keys "Show selected" froze when it was pressed, so a row unticked in that view stays put. */
278
+ private _selectionView;
279
+ get isSelectionView(): boolean;
280
+ /** Only the grid's own filtering can be set aside; a server's or a tree's cannot. */
281
+ get canShowSelection(): boolean;
282
+ /** "Show selected" sets the filters aside rather than adding to them: the rows it is for are the hidden ones. */
283
+ toggleSelectionView: () => void;
284
+ /** A filter typed while viewing the selection is a new question, so the view gives way to it. */
285
+ private leaveSelectionView;
237
286
  private _selectionAnnounced;
238
287
  /**
239
288
  * What the grid's live region says. Ticking a checkbox halfway down a virtualized grid changes nothing a
@@ -279,7 +328,11 @@ export default class GridModel<TRow> {
279
328
  toggleRowSelection: (rowKey: Key) => void;
280
329
  /** `all` is the header checkbox rather than a count: a group covering every row is still not select-all. */
281
330
  toggleRowsSelection: (rowKeys: Key[], all?: boolean) => void;
331
+ /** Ticks or clears the rows on screen only — selected rows the filters hide are left as they are. */
282
332
  toggleSelectAllRows: () => void;
333
+ /** Every row, hidden ones included: the footer's way out of a selection the filters keep half out of sight. */
334
+ clearSelection: () => void;
335
+ private selectionChanged;
283
336
  toggleColumnVisibility: (columnKey: Key) => void;
284
337
  setWidth: (columnKey: Key, width: number) => void;
285
338
  private _groupColumns?;
@@ -14,6 +14,8 @@ export default class HeaderCellModel<TRow> {
14
14
  */
15
15
  get hiddenLabel(): string | undefined;
16
16
  get isSortable(): boolean;
17
+ /** Whether the cell paints its own hover pill: under `'sliding'` the header draws one for every cell. */
18
+ get hasHoverPill(): boolean;
17
19
  get isSorted(): boolean;
18
20
  get sortDirection(): SortDirection | undefined;
19
21
  get showResizer(): boolean;
@@ -54,6 +56,9 @@ export default class HeaderCellModel<TRow> {
54
56
  isFirstEndPinned: boolean;
55
57
  isLastEndPinned: boolean;
56
58
  isSortable: boolean;
59
+ hasHoverPill: boolean;
60
+ isPillBeforeResizer: boolean;
61
+ isPillAfterResizer: boolean;
57
62
  isRowNumber: boolean;
58
63
  isFirstLeaf: boolean;
59
64
  isLastLeaf: boolean;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Every word the grid writes itself — visible text, accessible names, announcements and messages. A value
3
+ * with something in it is a function, because word order and plurals differ between languages and a
4
+ * sentence glued from pieces cannot be translated. Override any subset through `def.localeText`.
5
+ */
6
+ export interface DataGridLocaleText {
7
+ /** The global search box's placeholder. */
8
+ searchPlaceholder: string;
9
+ /** The column chooser's accessible name. */
10
+ columnsMenu: string;
11
+ columnsMenuSearchPlaceholder: string;
12
+ columnsMenuShowAll: string;
13
+ columnsMenuHideAll: string;
14
+ /** The ✕ on a grouped column's chip. */
15
+ stopGrouping: (column: string) => string;
16
+ /** The export buttons' visible text, and their accessible names. */
17
+ exportCsv: string;
18
+ exportCsvLabel: string;
19
+ exportExcel: string;
20
+ exportExcelLabel: string;
21
+ /** The exported file's name when there is no `fileName` and no string `title`. */
22
+ exportFileName: string;
23
+ selectAllRows: string;
24
+ /** The hidden names of the row-number and row-detail columns, which draw no header text. */
25
+ rowNumberHeader: string;
26
+ rowDetailHeader: string;
27
+ /** The grouping column's header when more than one column is grouped. */
28
+ groupHeader: string;
29
+ columnOptions: (column: string) => string;
30
+ resizeColumn: (column: string) => string;
31
+ /** A resizer's `aria-valuetext`. */
32
+ columnWidth: (pixels: number) => string;
33
+ sortAscending: string;
34
+ sortDescending: string;
35
+ clearSort: string;
36
+ /** The pin items name a screen side, so a right-to-left page swaps them. */
37
+ pinLeft: string;
38
+ pinRight: string;
39
+ unpin: string;
40
+ groupBy: (column: string) => string;
41
+ ungroupAll: string;
42
+ filterPlaceholder: string;
43
+ filterColumn: (column: string) => string;
44
+ clearFilter: (column: string) => string;
45
+ filterComparison: (column: string) => string;
46
+ filterValuePlaceholder: string;
47
+ filterFromPlaceholder: string;
48
+ filterToPlaceholder: string;
49
+ filterFrom: (column: string) => string;
50
+ filterTo: (column: string) => string;
51
+ filterSelectPlaceholder: string;
52
+ filterSelectSearchPlaceholder: string;
53
+ filterSelectClear: string;
54
+ filterSelectAll: string;
55
+ /** How a multiselect filter lists a `null` value. */
56
+ filterEmptyValue: string;
57
+ /** What an empty grid shows when there is no `noDataComponent`. */
58
+ noRows: string;
59
+ loadingRows: string;
60
+ selectRow: (rowNumber: number) => string;
61
+ selectGroupRows: (group: string) => string;
62
+ expandRowDetail: (rowNumber: number) => string;
63
+ collapseRowDetail: (rowNumber: number) => string;
64
+ expandTreeRow: (name: string) => string;
65
+ collapseTreeRow: (name: string) => string;
66
+ /** What a tree row with no value of its own is called in its expander's name. */
67
+ unnamedRow: (rowNumber: number) => string;
68
+ editCell: (column: string) => string;
69
+ /** A refused edit with no message of its own. */
70
+ invalidValue: string;
71
+ /** A pasted value the column cannot read, such as text in a number column. */
72
+ wrongKindOfValue: string;
73
+ /** The totals row's label when `def.footer` has none. */
74
+ footerTotal: string;
75
+ noColumnsTitle: string;
76
+ noColumnsDescription: string;
77
+ /** A failed fetch whose error carries no message. */
78
+ loadError: string;
79
+ retry: string;
80
+ rowCount: (filtered: number, total: number) => string;
81
+ pageRowRange: (start: number, end: number, total: number) => string;
82
+ /** `hidden` is how many selected rows the filters keep off screen. */
83
+ selectedCount: (selected: number, hidden: number) => string;
84
+ clearFilters: string;
85
+ /** A toggle: it shows the selected rows whatever the filters say, and pressing it again shows the rest. */
86
+ showSelected: string;
87
+ clearSelection: string;
88
+ rowsPerPage: string;
89
+ /** An entry in the page-size list. */
90
+ pageSizeOption: (size: number) => string;
91
+ pageNumber: string;
92
+ /** The text after the page box: "of 12". */
93
+ pageCount: (totalPages: number) => string;
94
+ firstPage: string;
95
+ previousPage: string;
96
+ nextPage: string;
97
+ lastPage: string;
98
+ /** What the live region says when the selection changes. */
99
+ selectionAnnouncement: (selected: number, total: number) => string;
100
+ }
101
+ /** The grid's own words, in English. Spread it to start a translation: `{ ...DATA_GRID_LOCALE_TEXT, clearFilters: 'Rensa filter' }`. */
102
+ export declare const DATA_GRID_LOCALE_TEXT: DataGridLocaleText;
@@ -57,6 +57,8 @@ export default class RangeModel<TRow> {
57
57
  isCurrent(row: number, column: number): boolean;
58
58
  /** Whether the cell is part of a marked block. A single current cell is the cursor, not a selection. */
59
59
  isSelected(row: number, column: number): boolean;
60
+ /** Everything about this body row the range draws: the current column, the block's columns, and a drag. */
61
+ rowDigest(row: number): string;
60
62
  /**
61
63
  * Put the current cell here. `extend` keeps the anchor where it is, which is what makes Shift+arrow and
62
64
  * a drag grow a rectangle instead of moving a cursor.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * A debounced input over a value the grid owns. When the grid's value changes to something the draft does
3
+ * not already say — "Clear filters" in the footer, a controlled prop — the draft takes it and any pending
4
+ * commit is dropped; a copy read once at mount kept showing a filter that was gone.
5
+ */
6
+ export default function useFilterDraft<T>(committed: T, matches: (committed: T) => boolean, adopt: (committed: T) => void): {
7
+ debounce: (key: string, commit: () => void) => void;
8
+ cancel: () => void;
9
+ };