@lotics/ui 2.6.1 → 3.0.0

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.
Files changed (45) hide show
  1. package/package.json +1 -15
  2. package/src/react_native.d.ts +2 -2
  3. package/src/cell_date.tsx +0 -30
  4. package/src/cell_date_format.test.ts +0 -32
  5. package/src/cell_date_format.ts +0 -73
  6. package/src/cell_number.test.ts +0 -42
  7. package/src/cell_number.tsx +0 -25
  8. package/src/cell_number_format.ts +0 -42
  9. package/src/cell_select.tsx +0 -68
  10. package/src/cell_text.tsx +0 -45
  11. package/src/grid/data_grid.tsx +0 -2003
  12. package/src/grid/data_grid_columns.test.ts +0 -72
  13. package/src/grid/data_grid_columns.ts +0 -30
  14. package/src/grid/data_grid_context.ts +0 -119
  15. package/src/grid/dispatch_safely.ts +0 -39
  16. package/src/grid/engine.module.css +0 -114
  17. package/src/grid/engine.tsx +0 -1042
  18. package/src/grid/helpers.ts +0 -205
  19. package/src/grid/layout.test.ts +0 -515
  20. package/src/grid/layout.ts +0 -425
  21. package/src/grid/recycling.test.ts +0 -236
  22. package/src/grid/recycling.ts +0 -172
  23. package/src/grid/row_cell.module.css +0 -105
  24. package/src/grid/row_cell.tsx +0 -313
  25. package/src/grid/search_highlight.ts +0 -71
  26. package/src/grid/select_cell.tsx +0 -58
  27. package/src/grid/select_group_summary_cell.tsx +0 -76
  28. package/src/grid/select_header_cell.tsx +0 -32
  29. package/src/grid/skeleton_row.module.css +0 -34
  30. package/src/grid/skeleton_row.tsx +0 -20
  31. package/src/grid/use_grid_groups.ts +0 -311
  32. package/src/grid/use_scroll_to_cell.ts +0 -135
  33. package/src/grid/use_virtual_grid.ts +0 -383
  34. package/src/grid/visibility.test.ts +0 -208
  35. package/src/grid/visibility.ts +0 -77
  36. package/src/kanban/constants.ts +0 -18
  37. package/src/kanban/default_renderers.tsx +0 -160
  38. package/src/kanban/drag_preview.tsx +0 -157
  39. package/src/kanban/index.ts +0 -13
  40. package/src/kanban/insert_card_zone.tsx +0 -135
  41. package/src/kanban/kanban_board.tsx +0 -635
  42. package/src/kanban/kanban_card.tsx +0 -321
  43. package/src/kanban/kanban_column.tsx +0 -499
  44. package/src/kanban/placeholders.tsx +0 -54
  45. package/src/kanban/types.ts +0 -116
@@ -1,72 +0,0 @@
1
- import { deriveHasSelectColumn, buildGridColumns } from "./data_grid_columns";
2
- import type { DataGridColumn } from "./data_grid";
3
- import type { RowPathKey } from "./layout";
4
-
5
- interface Row {
6
- name: string;
7
- }
8
-
9
- const userColumns: DataGridColumn<Row>[] = [
10
- { key: "name", name: "Name" },
11
- { key: "status", name: "Status" },
12
- ];
13
-
14
- const selectColumn: DataGridColumn<Row> = { key: "select", name: "" };
15
-
16
- const noop = (_rows: Set<RowPathKey>): void => {};
17
-
18
- // ─── deriveHasSelectColumn ────────────────────────────────────────────────
19
- // The select column exists only when the consumer participates in row
20
- // selection — by supplying `selectedRows`, `onSelectedRowsChange`, or both.
21
-
22
- test("deriveHasSelectColumn - false when selection is unwired", () => {
23
- expect(deriveHasSelectColumn(undefined, undefined)).toBe(false);
24
- });
25
-
26
- test("deriveHasSelectColumn - true when onSelectedRowsChange is provided", () => {
27
- expect(deriveHasSelectColumn(undefined, noop)).toBe(true);
28
- });
29
-
30
- test("deriveHasSelectColumn - true when only selectedRows is provided (read-only display)", () => {
31
- expect(deriveHasSelectColumn(new Set<RowPathKey>(), undefined)).toBe(true);
32
- });
33
-
34
- test("deriveHasSelectColumn - true when both props are provided", () => {
35
- expect(deriveHasSelectColumn(new Set<RowPathKey>(["0"]), noop)).toBe(true);
36
- });
37
-
38
- // ─── buildGridColumns ─────────────────────────────────────────────────────
39
-
40
- test("buildGridColumns - prepends the select column when selection is wired", () => {
41
- const columns = buildGridColumns(true, selectColumn, userColumns);
42
-
43
- expect(columns).toHaveLength(3);
44
- expect(columns[0].key).toBe("select");
45
- expect(columns[1].key).toBe("name");
46
- });
47
-
48
- test("buildGridColumns - omits the select column when selection is unwired", () => {
49
- const columns = buildGridColumns(false, selectColumn, userColumns);
50
-
51
- expect(columns).toHaveLength(2);
52
- // The first user column sits at index 0 — no inert checkbox column ahead of it.
53
- expect(columns[0].key).toBe("name");
54
- expect(columns.some((c) => c.key === "select")).toBe(false);
55
- });
56
-
57
- test("buildGridColumns - read-only selection (selectedRows only) still renders the select column", () => {
58
- const hasSelectColumn = deriveHasSelectColumn(new Set<RowPathKey>(["0"]), undefined);
59
- const columns = buildGridColumns(hasSelectColumn, selectColumn, userColumns);
60
-
61
- expect(columns[0].key).toBe("select");
62
- });
63
-
64
- test("buildGridColumns - does not mutate the input userColumns array", () => {
65
- const input: DataGridColumn<Row>[] = [{ key: "name", name: "Name" }];
66
- const before = [...input];
67
-
68
- buildGridColumns(true, selectColumn, input);
69
- buildGridColumns(false, selectColumn, input);
70
-
71
- expect(input).toEqual(before);
72
- });
@@ -1,30 +0,0 @@
1
- import type { RowPathKey } from "@lotics/ui/grid/layout";
2
- import type { DataGridColumn } from "./data_grid";
3
-
4
- /**
5
- * Whether the grid renders a select (checkbox) column. Row selection is fully
6
- * controlled: the column is interactive only when the consumer supplies
7
- * `selectedRows` (to display selection) or `onSelectedRowsChange` (to mutate
8
- * it). With neither, the column would be inert — uncheckable and unobservable —
9
- * so it must not exist or occupy layout space.
10
- */
11
- export function deriveHasSelectColumn(
12
- selectedRows: Set<RowPathKey> | undefined,
13
- onSelectedRowsChange: ((selectedRows: Set<RowPathKey>) => void) | undefined,
14
- ): boolean {
15
- return selectedRows !== undefined || onSelectedRowsChange !== undefined;
16
- }
17
-
18
- /**
19
- * Assemble the grid's column list. The select column is prepended only when
20
- * `hasSelectColumn` is true (see `deriveHasSelectColumn`).
21
- *
22
- * Pure: returns a new array, never mutates `userColumns`.
23
- */
24
- export function buildGridColumns<TRow>(
25
- hasSelectColumn: boolean,
26
- selectColumn: DataGridColumn<TRow>,
27
- userColumns: DataGridColumn<TRow>[],
28
- ): DataGridColumn<TRow>[] {
29
- return hasSelectColumn ? [selectColumn, ...userColumns] : [...userColumns];
30
- }
@@ -1,119 +0,0 @@
1
- import { createContext, useCallback, useContext, useMemo } from "react";
2
- import type { RowPathKey } from "@lotics/ui/grid/layout";
3
-
4
- export type RowId = string;
5
-
6
- // ─── Cell animation hook — optional, default returns false ────────────────
7
- //
8
- // Records page wires this to useRealtimeChangeAnimation(tableId, …) so remote
9
- // updates flash matching cells. Iframe apps leave it unset; row_cell always
10
- // calls the contextualized hook, so React's hook-order stays stable.
11
-
12
- export type UseCellAnimationFn = (rowId: RowId, columnKey: string) => boolean;
13
-
14
- /** Default no-op when consumer doesn't supply useCellAnimation. Module-level
15
- * so its identity is stable across renders. Exported so DataGrid can wire it
16
- * into its Provider without redeclaring. */
17
- export const noopUseCellAnimation: UseCellAnimationFn = () => false;
18
-
19
- export const CellAnimationContext = createContext<UseCellAnimationFn>(noopUseCellAnimation);
20
-
21
- export function useCellAnimation(rowId: RowId, columnKey: string): boolean {
22
- const fn = useContext(CellAnimationContext);
23
- return fn(rowId, columnKey);
24
- }
25
-
26
- export interface RowSelectionContextValue {
27
- selectedRows?: Set<RowPathKey>;
28
- onSelectedRowsChange?: (selectedRows: Set<RowPathKey>) => void;
29
- allRowPathKeys: Set<RowPathKey>;
30
- }
31
-
32
- export const RowSelectionContext = createContext<RowSelectionContextValue | null>(null);
33
-
34
- export function useRowSelection() {
35
- const context = useContext(RowSelectionContext);
36
- if (!context) {
37
- throw new Error("useRowSelection must be used within a DataGrid");
38
- }
39
-
40
- const { selectedRows, onSelectedRowsChange } = context;
41
-
42
- const isRowSelected = useCallback(
43
- (rowKey: RowPathKey) => {
44
- return selectedRows?.has(rowKey) ?? false;
45
- },
46
- [selectedRows],
47
- );
48
-
49
- const onRowSelectionChange = useCallback(
50
- (params: { rowKey: RowPathKey; checked: boolean; isShiftClick: boolean }) => {
51
- if (!onSelectedRowsChange) {
52
- return;
53
- }
54
-
55
- const newSelectedRows = new Set(selectedRows ?? []);
56
-
57
- if (params.checked) {
58
- newSelectedRows.add(params.rowKey);
59
- } else {
60
- newSelectedRows.delete(params.rowKey);
61
- }
62
-
63
- onSelectedRowsChange(newSelectedRows);
64
- },
65
- [selectedRows, onSelectedRowsChange],
66
- );
67
-
68
- return {
69
- isRowSelected,
70
- onRowSelectionChange,
71
- };
72
- }
73
-
74
- export function useHeaderRowSelection() {
75
- const context = useContext(RowSelectionContext);
76
- if (!context) {
77
- throw new Error("useHeaderRowSelection must be used within a DataGrid");
78
- }
79
-
80
- const { selectedRows, onSelectedRowsChange, allRowPathKeys } = context;
81
-
82
- const selectedCount = useMemo(() => {
83
- if (!selectedRows || !allRowPathKeys || !allRowPathKeys.size) {
84
- return 0;
85
- }
86
- return Array.from(allRowPathKeys).filter((key) => selectedRows.has(key)).length;
87
- }, [selectedRows, allRowPathKeys]);
88
-
89
- const isIndeterminate = useMemo(
90
- () => selectedCount > 0 && selectedCount < (allRowPathKeys?.size ?? 0),
91
- [selectedCount, allRowPathKeys?.size],
92
- );
93
-
94
- const isRowSelected = useMemo(
95
- () => selectedCount === (allRowPathKeys?.size ?? 0) && (allRowPathKeys?.size ?? 0) > 0,
96
- [selectedCount, allRowPathKeys?.size],
97
- );
98
-
99
- const onRowSelectionChange = useCallback(
100
- (params: { checked: boolean }) => {
101
- if (!onSelectedRowsChange) {
102
- return;
103
- }
104
-
105
- if (params.checked) {
106
- onSelectedRowsChange(new Set(allRowPathKeys));
107
- } else {
108
- onSelectedRowsChange(new Set());
109
- }
110
- },
111
- [onSelectedRowsChange, allRowPathKeys],
112
- );
113
-
114
- return {
115
- isIndeterminate,
116
- isRowSelected,
117
- onRowSelectionChange,
118
- };
119
- }
@@ -1,39 +0,0 @@
1
- /**
2
- * Pub-sub dispatcher with hard isolation between subscribers.
3
- *
4
- * Contract:
5
- * 1. Every item in `items` is invoked exactly once per call.
6
- * 2. A throwing invocation never stops later items from being invoked.
7
- * 3. A throwing invocation never propagates back to the dispatcher's caller.
8
- *
9
- * Subscriber exceptions are re-raised to the host error handler
10
- * (`globalThis.reportError`, falling back to a fresh task) so they surface
11
- * via `window.onerror` with full original stack. This is NOT a swallow:
12
- * the error is fully reported, loudly, with context — same mechanism the
13
- * HTML spec uses for EventTarget.dispatchEvent to "report the exception"
14
- * without joining listener failures to the dispatcher's call stack.
15
- *
16
- * Vendored from @lotics/ui-internal/dispatch_safely (workspace-private).
17
- * Kept inline in @lotics/ui/grid so the grid module can be published
18
- * without a dependency on the internal package.
19
- */
20
- export function dispatchSafely<T>(items: Iterable<T>, invoke: (item: T) => void): void {
21
- for (const item of items) {
22
- try {
23
- invoke(item);
24
- } catch (error) {
25
- reportListenerError(error);
26
- }
27
- }
28
- }
29
-
30
- function reportListenerError(error: unknown): void {
31
- const globalReportError = (globalThis as { reportError?: (error: unknown) => void }).reportError;
32
- if (typeof globalReportError === "function") {
33
- globalReportError(error);
34
- return;
35
- }
36
- setTimeout(() => {
37
- throw error;
38
- }, 0);
39
- }
@@ -1,114 +0,0 @@
1
- .root {
2
- position: relative;
3
- overflow: scroll;
4
- will-change: scroll-position;
5
- height: 100%;
6
- width: 100%;
7
- }
8
-
9
- .content {
10
- position: relative;
11
- transform: translateZ(0);
12
- }
13
-
14
- .rows_wrapper {
15
- position: absolute;
16
- transform: translateZ(0);
17
- }
18
-
19
- .row_wrapper {
20
- position: absolute;
21
- top: 0;
22
- left: 0;
23
- display: flex;
24
- flex-direction: row;
25
- will-change: transform;
26
- content-visibility: auto;
27
- }
28
-
29
- .row {
30
- position: relative;
31
- display: flex;
32
- flex-direction: row;
33
- flex-shrink: 0;
34
- }
35
-
36
- .frozen_cells {
37
- position: sticky;
38
- left: 0;
39
- z-index: 3;
40
- display: flex;
41
- flex-direction: row;
42
- flex-shrink: 0;
43
- box-sizing: border-box;
44
- }
45
-
46
- .frozen_cells::after {
47
- content: "";
48
- position: absolute;
49
- top: 0;
50
- right: -6px;
51
- width: 6px;
52
- height: 100%;
53
- background: linear-gradient(to right, rgba(0, 0, 0, 0.06), transparent);
54
- pointer-events: none;
55
- opacity: 0;
56
- transition: opacity 0.15s ease-out;
57
- }
58
-
59
- /* Show shadow only when grid is scrolled horizontally */
60
- .root[data-scrolled-x] .frozen_cells::after {
61
- opacity: 1;
62
- }
63
-
64
- .scrollable_cells {
65
- position: relative;
66
- flex-shrink: 0;
67
- }
68
-
69
- .cell {
70
- display: flex;
71
- flex-direction: column;
72
- flex-shrink: 0;
73
- contain: layout style paint;
74
- box-sizing: border-box;
75
- }
76
-
77
- .header_wrapper {
78
- position: sticky;
79
- top: 0;
80
- z-index: 4;
81
- display: flex;
82
- flex-direction: row;
83
- will-change: transform;
84
- }
85
-
86
- .footer_wrapper {
87
- position: sticky;
88
- bottom: 0;
89
- display: flex;
90
- flex-direction: row;
91
- }
92
-
93
- .group_heading_wrapper {
94
- position: absolute;
95
- top: 0;
96
- display: flex;
97
- flex-direction: row;
98
- will-change: transform;
99
- content-visibility: auto;
100
- }
101
-
102
- .group_heading_content {
103
- position: sticky;
104
- left: 0px;
105
- }
106
-
107
- .spacer {
108
- position: absolute;
109
- top: 0;
110
- left: 0;
111
- width: 100%;
112
- will-change: transform;
113
- content-visibility: auto;
114
- }