rich-react-component 0.3.0 → 0.3.2
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/README.md +40 -1
- package/dist/base/ContextMenu.d.ts +87 -0
- package/dist/base/DataGrid.d.ts +27 -1
- package/dist/base/RichComponentBase.d.ts +12 -0
- package/dist/base/contextMenuModel.d.ts +49 -0
- package/dist/base/contextMenuPosition.d.ts +41 -0
- package/dist/base/dataGridResponsive.d.ts +169 -0
- package/dist/base/index.d.ts +15 -0
- package/dist/base/shared/Portal.d.ts +20 -0
- package/dist/base/shared/composeRefs.d.ts +3 -0
- package/dist/base/shared/useElementResize.d.ts +16 -0
- package/dist/base/shared/useIsomorphicLayoutEffect.d.ts +12 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2261 -1615
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +72 -72
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ import { RemoteComboBox } from "rich-react-component";
|
|
|
83
83
|
|
|
84
84
|
## What's in each layer
|
|
85
85
|
|
|
86
|
-
**Base** (
|
|
86
|
+
**Base** (57 components) — form fields (`Input`, `NumberInput`, `PasswordInput`, `FileInput`, `CheckBox`, `Switch`, `RadioGroup`, `Select`, `MultiSelect`, `DatePicker`, `TimePicker`, `DateTimePicker`, `ComboBox`, `AutoComplete`, `FormField`), feedback (`Modal`, `Toast`, `Confirm`, `Alert`, `Spinner`, `Badge`, `Skeleton`), data display (`DataGrid`, `Pagination`, `Avatar`, `Tag`, `Rating`, `ProgressBar`, `ListItem`, `Sparkline`, `Statistic`), navigation (`Breadcrumb`, `Menu`, `Stepper`, `Tabs`, `Navbar`, `Sidebar`, `PageHeader`), layout (`Card`, `Divider`, `Stack`, `Flex`, `Container`, `Row`, `Col`), typography (`Text`), and more (`Button`, `IconButton`, `Tooltip`, `Popover`, `Accordion`, `Icon`, `RichComponentBase`, `ContextMenu`).
|
|
87
87
|
|
|
88
88
|
`DataGrid` columns are declarative by default (`{ field: "conversion", header: "CONV.", align: "end" }`) — `render` stays available as the escape hatch for genuinely custom cells, and `format`/`formatter` cover the common presentation cases (`dataGridFormatters` exports the built-in `text`/`date`/`currency`/`boolean` set). `Card` accepts `title`/`subtitle`/`icon`/`avatar`/`actions`/`loading` directly, with `header` remaining as the raw escape hatch. `Tabs` supports `variant` (`default`/`underline`/`pill`/`card`/`button`), `orientation`, `size`, `stretch`, and per-item `icon`/`badge`, with roving-tabindex keyboard navigation.
|
|
89
89
|
|
|
@@ -97,6 +97,45 @@ import { RemoteComboBox } from "rich-react-component";
|
|
|
97
97
|
- **Headless engine** — `useSmartField` / `useSmartAction` resolve one field/action's metadata (visibility, validation, localized label, resolved request params); `useSmartDependencies` tracks `dependsOn` and reports which field needs fresh metadata when a value it depends on changes.
|
|
98
98
|
- **Components** — `SmartField` / `SmartAction` resolve one piece of metadata into UI; `SmartForm` / `SmartActions` are thin iteration helpers over a whole `SmartFormMetadata` — no field-value ownership, no business-rule evaluation, no layout decisions.
|
|
99
99
|
|
|
100
|
+
## Context menu
|
|
101
|
+
|
|
102
|
+
`RichComponentBase` attaches a declarative context menu to any component in the
|
|
103
|
+
package. The menu is written as a child component — never as a prop, a metadata
|
|
104
|
+
array or a callback-returned object — and the behavior lives in that one place,
|
|
105
|
+
so no component carries a copy of it and none gained a `contextMenu` prop.
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
<RichComponentBase>
|
|
109
|
+
<ContextMenu>
|
|
110
|
+
<ContextMenu.Label>Actions</ContextMenu.Label>
|
|
111
|
+
<ContextMenu.Item icon={<Icon name="bi bi-pencil" />} onSelect={handleEdit}>Düzenle</ContextMenu.Item>
|
|
112
|
+
<ContextMenu.Separator />
|
|
113
|
+
<ContextMenu.SubMenu label="Export">
|
|
114
|
+
<ContextMenu.Item onSelect={exportPdf}>PDF</ContextMenu.Item>
|
|
115
|
+
<ContextMenu.Item onSelect={exportExcel}>Excel</ContextMenu.Item>
|
|
116
|
+
</ContextMenu.SubMenu>
|
|
117
|
+
<ContextMenu.Item danger onSelect={handleDelete}>Sil</ContextMenu.Item>
|
|
118
|
+
</ContextMenu>
|
|
119
|
+
|
|
120
|
+
<Button>İşlem</Button>
|
|
121
|
+
</RichComponentBase>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- **No DOM tax.** With a single host-element child (`<div>`, `<table>`, …) the
|
|
125
|
+
trigger is attached to that element and nothing is added. Otherwise the content
|
|
126
|
+
is wrapped in a `display: contents` element, which generates no layout box, so
|
|
127
|
+
flex/grid placement, sizing and positioning are unchanged. `trigger="child"`
|
|
128
|
+
forces the no-DOM path; `as` picks the wrapper element (`as="tbody"`).
|
|
129
|
+
- **Transparent without a menu.** No `<ContextMenu>` child means no listeners and
|
|
130
|
+
no interception — the browser's own menu is untouched. The same holds for a
|
|
131
|
+
`disabled` or effectively empty declaration.
|
|
132
|
+
- **Composes, never replaces.** A consumer's own `onContextMenu` runs first; if it
|
|
133
|
+
calls `preventDefault()`, the base stands down. `className`, `style`, refs and
|
|
134
|
+
accessibility props are merged, not overwritten.
|
|
135
|
+
- **Real keyboard support.** Context Menu key and Shift + F10 open it; arrows,
|
|
136
|
+
Home/End, Enter/Space, Arrow Right/Left for submenus and Escape all work, with
|
|
137
|
+
focus moved on open and restored on close.
|
|
138
|
+
|
|
100
139
|
## Sidebar
|
|
101
140
|
|
|
102
141
|
One recursive navigation tree with a single authoritative expansion state and a
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import { ContextMenuPoint } from './contextMenuPosition';
|
|
3
|
+
/**
|
|
4
|
+
* Declarative context menu.
|
|
5
|
+
*
|
|
6
|
+
* `<ContextMenu>` is a *declaration*, not page content: on its own it renders
|
|
7
|
+
* nothing at all. `RichComponentBase` lifts the declaration out of its
|
|
8
|
+
* children and re-renders it inside a surface context when the user invokes
|
|
9
|
+
* the menu, which is why a stray `<ContextMenu>` anywhere else in a tree can
|
|
10
|
+
* never leak markup into the page.
|
|
11
|
+
*
|
|
12
|
+
* Layer: Base. It performs no HTTP and knows nothing about metadata, so
|
|
13
|
+
* Remote and Smart can compose it without inverting the dependency direction.
|
|
14
|
+
*/
|
|
15
|
+
export type ContextMenuCloseReason = "select" | "escape" | "dismiss";
|
|
16
|
+
export interface ContextMenuSurfaceValue {
|
|
17
|
+
/** Viewport coordinates the menu was invoked at. */
|
|
18
|
+
point: ContextMenuPoint;
|
|
19
|
+
/** Changes on every invocation, so a repeated right-click re-measures. */
|
|
20
|
+
openId: number;
|
|
21
|
+
close: (reason: ContextMenuCloseReason) => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Internal wiring between `RichComponentBase` and the surface. Deliberately
|
|
25
|
+
* not part of the package's public exports — open/close state and positioning
|
|
26
|
+
* are implementation details, not API.
|
|
27
|
+
*/
|
|
28
|
+
export declare const ContextMenuSurfaceContext: import('react').Context<ContextMenuSurfaceValue | null>;
|
|
29
|
+
export interface ContextMenuProps {
|
|
30
|
+
children?: ReactNode;
|
|
31
|
+
/**
|
|
32
|
+
* Leaves the browser's own context menu intact without removing the
|
|
33
|
+
* declaration — the menu is switched off, not deleted.
|
|
34
|
+
*/
|
|
35
|
+
disabled?: boolean;
|
|
36
|
+
/** Accessible name for the surface, when the rows alone are not enough. */
|
|
37
|
+
"aria-label"?: string;
|
|
38
|
+
className?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface ContextMenuItemProps {
|
|
41
|
+
children?: ReactNode;
|
|
42
|
+
/** Optional stable identifier; emitted as the row's DOM id. */
|
|
43
|
+
id?: string;
|
|
44
|
+
disabled?: boolean;
|
|
45
|
+
/** Not rendered at all, and so skipped by keyboard navigation. */
|
|
46
|
+
hidden?: boolean;
|
|
47
|
+
/** Destructive emphasis — the same semantic name `Menu`'s item model uses. */
|
|
48
|
+
danger?: boolean;
|
|
49
|
+
/** Icon content, e.g. `<Icon name="bi bi-pencil" />`. No icon set is bundled. */
|
|
50
|
+
icon?: ReactNode;
|
|
51
|
+
/** Accessible name, for a row whose visible text is not sufficient. */
|
|
52
|
+
"aria-label"?: string;
|
|
53
|
+
className?: string;
|
|
54
|
+
onSelect?: () => void | Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
export interface ContextMenuLabelProps {
|
|
57
|
+
children?: ReactNode;
|
|
58
|
+
className?: string;
|
|
59
|
+
}
|
|
60
|
+
export interface ContextMenuSeparatorProps {
|
|
61
|
+
className?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface ContextMenuSubMenuProps {
|
|
64
|
+
/** Visible text of the row that opens the submenu. */
|
|
65
|
+
label: ReactNode;
|
|
66
|
+
children?: ReactNode;
|
|
67
|
+
id?: string;
|
|
68
|
+
disabled?: boolean;
|
|
69
|
+
hidden?: boolean;
|
|
70
|
+
icon?: ReactNode;
|
|
71
|
+
/** Accessible name, for a trigger whose visible text is not sufficient. */
|
|
72
|
+
"aria-label"?: string;
|
|
73
|
+
className?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface ContextMenuComponent {
|
|
76
|
+
(props: ContextMenuProps): ReactElement | null;
|
|
77
|
+
Item: (props: ContextMenuItemProps) => ReactElement | null;
|
|
78
|
+
Separator: (props: ContextMenuSeparatorProps) => ReactElement;
|
|
79
|
+
Label: (props: ContextMenuLabelProps) => ReactElement;
|
|
80
|
+
SubMenu: (props: ContextMenuSubMenuProps) => ReactElement | null;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The compound is annotated explicitly rather than inferred from
|
|
84
|
+
* `Object.assign`, so the generated declaration file names only exported
|
|
85
|
+
* types and never a private local one.
|
|
86
|
+
*/
|
|
87
|
+
export declare const ContextMenu: ContextMenuComponent;
|
package/dist/base/DataGrid.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
+
import { GridResponsiveOptions } from './dataGridResponsive';
|
|
2
3
|
export type DataGridAlign = "start" | "center" | "end";
|
|
3
4
|
/**
|
|
4
5
|
* Closed, finite formatter set — the same contract the Smart layer's
|
|
@@ -34,6 +35,17 @@ export type DataGridSort = {
|
|
|
34
35
|
key: string;
|
|
35
36
|
direction: "asc" | "desc";
|
|
36
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* A column's identity as the grid resolves it: `key ?? field ?? header`.
|
|
40
|
+
*
|
|
41
|
+
* The row's own field names are offered as suggestions — they are what most
|
|
42
|
+
* columns are keyed by — while an explicit `key` (a `render`-only column, a
|
|
43
|
+
* command column) is still accepted. `string & {}` is what keeps the literal
|
|
44
|
+
* suggestions from collapsing into bare `string`.
|
|
45
|
+
*/
|
|
46
|
+
export type DataGridColumnKey<TRow> = (keyof TRow & string) | (string & {});
|
|
47
|
+
/** Responsive options for one grid, keyed by that grid's column identities. */
|
|
48
|
+
export type DataGridResponsiveOptions<TRow> = GridResponsiveOptions<DataGridColumnKey<TRow>>;
|
|
37
49
|
export interface DataGridProps<TRow> {
|
|
38
50
|
columns: DataGridColumn<TRow>[];
|
|
39
51
|
rows: TRow[];
|
|
@@ -57,6 +69,20 @@ export interface DataGridProps<TRow> {
|
|
|
57
69
|
pageSize?: number;
|
|
58
70
|
totalCount?: number;
|
|
59
71
|
onPageChange?: (page: number) => void;
|
|
72
|
+
/**
|
|
73
|
+
* Progressive column reduction for narrow containers. Presence of this
|
|
74
|
+
* object is what enables the behavior — there is no second boolean to
|
|
75
|
+
* disagree with it. Omitted (the default), the grid is byte-for-byte what it
|
|
76
|
+
* was: every column stays in the row and a wide table scrolls horizontally
|
|
77
|
+
* inside its own container.
|
|
78
|
+
*
|
|
79
|
+
* Enabled, columns leave the tabular layout one at a time as the *grid's own
|
|
80
|
+
* container* gets too narrow for them, in the order of `columns`, and come
|
|
81
|
+
* back in the exact reverse order as it widens. `primaryColumn` always stays.
|
|
82
|
+
* With no `columns` list, every eligible data column is processed in declared
|
|
83
|
+
* order with `"Responsive"` behavior, so nothing is hidden by default.
|
|
84
|
+
*/
|
|
85
|
+
responsive?: DataGridResponsiveOptions<TRow>;
|
|
60
86
|
}
|
|
61
87
|
/**
|
|
62
88
|
* Intentionally focused first iteration (doc section 10/18): columns, rows,
|
|
@@ -64,4 +90,4 @@ export interface DataGridProps<TRow> {
|
|
|
64
90
|
* No built-in filtering UI, column resize/reorder, or virtualization yet —
|
|
65
91
|
* those are future extensions, not implemented speculatively.
|
|
66
92
|
*/
|
|
67
|
-
export declare function DataGrid<TRow>({ columns, rows, rowKey, loading, error, emptyText, className, selectedRowKeys, onSelectionChange, sort, onSortChange, page, pageSize, totalCount, onPageChange, }: DataGridProps<TRow>): import("react").JSX.Element;
|
|
93
|
+
export declare function DataGrid<TRow>({ columns, rows, rowKey, loading, error, emptyText, className, selectedRowKeys, onSelectionChange, sort, onSortChange, page, pageSize, totalCount, onPageChange, responsive, }: DataGridProps<TRow>): import("react").JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { HTMLAttributes, ReactHTML, ReactNode } from 'react';
|
|
2
|
+
/** How the context-menu trigger is attached to the visible content. */
|
|
3
|
+
export type RichComponentBaseTrigger = "auto" | "child" | "wrapper";
|
|
4
|
+
export interface RichComponentBaseProps extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
5
|
+
/** A single optional `<ContextMenu>` declaration plus the visible content. */
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
/** See the component doc comment. Defaults to `"auto"`. */
|
|
8
|
+
trigger?: RichComponentBaseTrigger;
|
|
9
|
+
/** Wrapper element used by the wrapper path. Defaults to `"div"`. */
|
|
10
|
+
as?: keyof ReactHTML;
|
|
11
|
+
}
|
|
12
|
+
export declare const RichComponentBase: import('react').ForwardRefExoticComponent<RichComponentBaseProps & import('react').RefAttributes<HTMLElement>>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ReactElement, ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Identification of the declarative context-menu nodes.
|
|
4
|
+
*
|
|
5
|
+
* Recognition is by component *identity*, recorded in a module-private
|
|
6
|
+
* WeakMap that is written once while this module initializes and never
|
|
7
|
+
* mutated afterwards. Deliberately not `displayName`, not `Component.name`
|
|
8
|
+
* and not a string on `props`: those are all rewritten or dropped by a
|
|
9
|
+
* minifier, which would make the whole feature fail only in a production
|
|
10
|
+
* bundle. A WeakMap keyed on the component reference survives minification
|
|
11
|
+
* for exactly the same reason `type === Component` does, and needs no type
|
|
12
|
+
* assertion to read back.
|
|
13
|
+
*/
|
|
14
|
+
export type ContextMenuNodeKind = "menu" | "item" | "label" | "separator" | "submenu";
|
|
15
|
+
/** Records what a component is. Called once per component at module scope. */
|
|
16
|
+
export declare function markContextMenuNode<TComponent extends object>(component: TComponent, kind: ContextMenuNodeKind): TComponent;
|
|
17
|
+
/** The kind of a rendered node, or `undefined` for anything this module did not mark. */
|
|
18
|
+
export declare function contextMenuNodeKind(node: ReactNode): ContextMenuNodeKind | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Flattens the direct children of a node one fragment level at a time.
|
|
21
|
+
*
|
|
22
|
+
* Fragments are handled deliberately rather than by accident: a
|
|
23
|
+
* `<>{menu}{content}</>` child must not hide the menu declaration from the
|
|
24
|
+
* partition below, and its visible children must not be dropped either.
|
|
25
|
+
*/
|
|
26
|
+
export declare function flattenMenuChildren(children: ReactNode): ReactNode[];
|
|
27
|
+
export interface RichComponentBaseChildren {
|
|
28
|
+
/** The single `<ContextMenu>` declaration, if one was supplied. */
|
|
29
|
+
menu?: ReactElement;
|
|
30
|
+
/** Everything else, rendered as ordinary page content. */
|
|
31
|
+
content: ReactNode[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Splits `RichComponentBase`'s children into the menu declaration and the
|
|
35
|
+
* visible content. A second `<ContextMenu>` is ignored rather than rendered,
|
|
36
|
+
* so a stray declaration can never leak into the page as content.
|
|
37
|
+
*/
|
|
38
|
+
export declare function partitionRichComponentBaseChildren(children: ReactNode): RichComponentBaseChildren;
|
|
39
|
+
/**
|
|
40
|
+
* Whether a declaration would put at least one selectable row on screen.
|
|
41
|
+
*
|
|
42
|
+
* This is what decides if the browser's own context menu is suppressed, so it
|
|
43
|
+
* is answered from the declaration *before* anything is rendered. Labels and
|
|
44
|
+
* separators do not count — a surface made only of those is "effectively
|
|
45
|
+
* empty" and must leave native behavior alone. Anything this module did not
|
|
46
|
+
* mark (a consumer's own wrapper component, arbitrary markup) counts as
|
|
47
|
+
* content: guessing it away would silently disable a menu that does render.
|
|
48
|
+
*/
|
|
49
|
+
export declare function contextMenuHasContent(children: ReactNode): boolean;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure viewport placement math for the context menu surface.
|
|
3
|
+
*
|
|
4
|
+
* Kept free of React and of the DOM (the caller measures and passes plain
|
|
5
|
+
* numbers) so the flip/clamp rules are unit-testable on their own, the same
|
|
6
|
+
* split `sidebarModel.ts` uses for the Sidebar's navigation model.
|
|
7
|
+
*/
|
|
8
|
+
export interface ContextMenuPoint {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
}
|
|
12
|
+
export interface ContextMenuSize {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
}
|
|
16
|
+
export interface ContextMenuViewport {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
}
|
|
20
|
+
/** Rect of a submenu's trigger row, in viewport coordinates. */
|
|
21
|
+
export interface ContextMenuTriggerBox {
|
|
22
|
+
left: number;
|
|
23
|
+
right: number;
|
|
24
|
+
top: number;
|
|
25
|
+
bottom: number;
|
|
26
|
+
}
|
|
27
|
+
/** Breathing room kept between the surface and the viewport edge. */
|
|
28
|
+
export declare const CONTEXT_MENU_VIEWPORT_MARGIN = 8;
|
|
29
|
+
/**
|
|
30
|
+
* Places the root surface at the pointer. Flips to the opposite side of the
|
|
31
|
+
* pointer when the preferred side would overflow, then clamps, so a menu that
|
|
32
|
+
* is simply larger than the viewport still starts inside it rather than
|
|
33
|
+
* scrolling the page.
|
|
34
|
+
*/
|
|
35
|
+
export declare function placeContextMenuAtPoint(point: ContextMenuPoint, size: ContextMenuSize, viewport: ContextMenuViewport, margin?: number): ContextMenuPoint;
|
|
36
|
+
/**
|
|
37
|
+
* Places a submenu beside its trigger row: flush to the trigger's right edge
|
|
38
|
+
* by default so the pointer can travel into it without crossing a gap, and
|
|
39
|
+
* mirrored to the left when that would overflow.
|
|
40
|
+
*/
|
|
41
|
+
export declare function placeSubMenuBeside(trigger: ContextMenuTriggerBox, size: ContextMenuSize, viewport: ContextMenuViewport, margin?: number): ContextMenuPoint;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Responsive column model for grid-like components.
|
|
4
|
+
*
|
|
5
|
+
* The problem: when a grid's *actual available width* is too small for its
|
|
6
|
+
* visible columns, rows stop being readable. The answer here is an ordered
|
|
7
|
+
* list of rules that progressively take columns out of the tabular layout —
|
|
8
|
+
* either relocating them into the row's own label/value detail area
|
|
9
|
+
* (`"Responsive"`) or dropping them entirely (`"Hide"`) — while one primary
|
|
10
|
+
* column always stays in the normal row.
|
|
11
|
+
*
|
|
12
|
+
* This module owns the whole algorithm: column classification, primary-column
|
|
13
|
+
* resolution, rule normalization, development-time validation and the
|
|
14
|
+
* width-driven fit loop. `DataGrid` is its only consumer, and `RemoteDataGrid`
|
|
15
|
+
* and the Smart `dataGridResolver` inherit the behavior by composing
|
|
16
|
+
* `DataGrid` — the algorithm exists exactly once.
|
|
17
|
+
*
|
|
18
|
+
* It is deliberately React-only-at-the-edges: everything above
|
|
19
|
+
* `useResponsiveColumnFit` is a pure function of a structural column model,
|
|
20
|
+
* so it is unit-testable without rendering a grid, and it never imports the
|
|
21
|
+
* grid itself (no cycle, and the grid stays the sole owner of its column type).
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* What happens to a column when the grid runs out of room for it.
|
|
25
|
+
*
|
|
26
|
+
* - `"Responsive"` — leaves the tabular row and is shown as a label/value pair
|
|
27
|
+
* in that same row's detail area.
|
|
28
|
+
* - `"Hide"` — leaves the tabular row and is not shown anywhere.
|
|
29
|
+
*
|
|
30
|
+
* Capitalized, unlike this library's other string unions (`"asc"`, `"text"`,
|
|
31
|
+
* `"auto"`, …), because these two are a specified external contract. One
|
|
32
|
+
* representation only: the lower-case spellings are deliberately *not*
|
|
33
|
+
* accepted, so there is never a second competing form of the same concept.
|
|
34
|
+
*/
|
|
35
|
+
export type ResponsiveColumnBehavior = "Responsive" | "Hide";
|
|
36
|
+
/**
|
|
37
|
+
* One entry of the ordered rule list. Position in the array is the priority —
|
|
38
|
+
* there is no separate priority number to keep in sync with it.
|
|
39
|
+
*/
|
|
40
|
+
export interface ResponsiveColumnRule<TField> {
|
|
41
|
+
field: TField;
|
|
42
|
+
behavior: ResponsiveColumnBehavior;
|
|
43
|
+
}
|
|
44
|
+
export interface GridResponsiveOptions<TField> {
|
|
45
|
+
/**
|
|
46
|
+
* The column that carries the row's identity in a narrow layout. It always
|
|
47
|
+
* stays in the tabular row: it is never relocated to the detail area and
|
|
48
|
+
* never removed by a `"Hide"` rule.
|
|
49
|
+
*/
|
|
50
|
+
primaryColumn?: TField;
|
|
51
|
+
/**
|
|
52
|
+
* Ordered rules. The first is processed first as width shrinks, and the last
|
|
53
|
+
* processed is the first restored as width grows. Columns not listed here
|
|
54
|
+
* are still handled — see `buildResponsiveColumnPlan`.
|
|
55
|
+
*/
|
|
56
|
+
columns?: readonly ResponsiveColumnRule<TField>[];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The parts of a grid column this model needs, as a structural shape.
|
|
60
|
+
*
|
|
61
|
+
* `isData` / `isUtility` / `isTextual` are classifications the grid derives
|
|
62
|
+
* from its own column type, so this module never has to know what a
|
|
63
|
+
* `DataGridColumn` is.
|
|
64
|
+
*/
|
|
65
|
+
export interface ResponsiveColumnModel {
|
|
66
|
+
/** The grid's own column identity. */
|
|
67
|
+
key: string;
|
|
68
|
+
/** Reads a value out of the row (as opposed to a command/action column). */
|
|
69
|
+
isData: boolean;
|
|
70
|
+
/** A command/action/control column: renders arbitrary content, reads no value. */
|
|
71
|
+
isUtility: boolean;
|
|
72
|
+
/** A data column whose value is textual, so it reads well as a row title. */
|
|
73
|
+
isTextual: boolean;
|
|
74
|
+
}
|
|
75
|
+
export type ResponsiveProblemKind = "unknown-column" | "duplicate-rule" | "primary-column-rule" | "invalid-behavior" | "utility-column-responsive" | "invalid-primary-column";
|
|
76
|
+
/** One development-time diagnostic, in the same shape the demo registry uses. */
|
|
77
|
+
export interface ResponsiveProblem {
|
|
78
|
+
kind: ResponsiveProblemKind;
|
|
79
|
+
detail: string;
|
|
80
|
+
}
|
|
81
|
+
/** A rule after validation, addressed by the grid's resolved column key. */
|
|
82
|
+
export interface NormalizedResponsiveRule {
|
|
83
|
+
key: string;
|
|
84
|
+
behavior: ResponsiveColumnBehavior;
|
|
85
|
+
}
|
|
86
|
+
export interface ResponsiveColumnPlan {
|
|
87
|
+
/** Resolved primary column, or `undefined` when no column is eligible. */
|
|
88
|
+
primaryKey: string | undefined;
|
|
89
|
+
/** The single ordered list the fit loop walks. */
|
|
90
|
+
rules: readonly NormalizedResponsiveRule[];
|
|
91
|
+
problems: readonly ResponsiveProblem[];
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Deterministic resolution, in order:
|
|
95
|
+
*
|
|
96
|
+
* 1. the configured `primaryColumn`, when it names an eligible column;
|
|
97
|
+
* 2. otherwise the first textual data column in declared column order;
|
|
98
|
+
* 3. otherwise the first data column in declared column order;
|
|
99
|
+
* 4. otherwise `undefined` — the grid keeps its existing behavior.
|
|
100
|
+
*
|
|
101
|
+
* Utility columns (selection, row number, expanders, actions, command and
|
|
102
|
+
* drag-handle columns) are never chosen automatically, and a configured one is
|
|
103
|
+
* rejected: a checkbox or a button is not a row's identity.
|
|
104
|
+
*
|
|
105
|
+
* A configured column that is absent from the current column set — because it
|
|
106
|
+
* was filtered out upstream by a column chooser, by a permission check or by
|
|
107
|
+
* Smart metadata — cannot be the *visible* primary column, so resolution falls
|
|
108
|
+
* through to the automatic rules. Nothing about the upstream decision is
|
|
109
|
+
* touched or written back.
|
|
110
|
+
*/
|
|
111
|
+
export declare function resolvePrimaryColumnKey(columns: readonly ResponsiveColumnModel[], configured: string | undefined): string | undefined;
|
|
112
|
+
/**
|
|
113
|
+
* Turns the configured options into one ordered, validated rule list.
|
|
114
|
+
*
|
|
115
|
+
* Configured rules come first, in their declared order. Every remaining
|
|
116
|
+
* eligible data column is then appended in *declared column order* with the
|
|
117
|
+
* default `"Responsive"` behavior, so responsive layout works without asking
|
|
118
|
+
* a consumer to describe every column — and nothing is hidden by default.
|
|
119
|
+
*
|
|
120
|
+
* Skipped, each with a diagnostic: a rule for an unknown column, a duplicate
|
|
121
|
+
* rule, a rule for the primary column, a rule with an invalid behavior, and a
|
|
122
|
+
* `"Responsive"` rule for a utility column (an action cell has no meaningful
|
|
123
|
+
* label/value form). A `"Hide"` rule for a utility column is honored — that is
|
|
124
|
+
* an explicit instruction to drop a control when space runs out, not a silent
|
|
125
|
+
* loss of data. Utility columns are never added by default.
|
|
126
|
+
*/
|
|
127
|
+
export declare function buildResponsiveColumnPlan(columns: readonly ResponsiveColumnModel[], options: GridResponsiveOptions<string> | undefined): ResponsiveColumnPlan;
|
|
128
|
+
/**
|
|
129
|
+
* A stable string identity for one plan. Used to reset the fit loop and to
|
|
130
|
+
* report diagnostics once per configuration rather than once per render or —
|
|
131
|
+
* far worse — once per resize.
|
|
132
|
+
*/
|
|
133
|
+
export declare function responsivePlanSignature(plan: ResponsiveColumnPlan): string;
|
|
134
|
+
export interface ResponsiveColumnFitOptions {
|
|
135
|
+
enabled: boolean;
|
|
136
|
+
/** The grid's own scroll container — the element whose width actually matters. */
|
|
137
|
+
containerRef: RefObject<HTMLElement | null>;
|
|
138
|
+
ruleCount: number;
|
|
139
|
+
/** Resets the loop when the columns or the rules themselves change. */
|
|
140
|
+
signature: string;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* How many rules are currently applied.
|
|
144
|
+
*
|
|
145
|
+
* Overflow is read from the grid's existing scroll container
|
|
146
|
+
* (`scrollWidth` vs `clientWidth`) rather than from a re-implementation of
|
|
147
|
+
* column sizing, so the browser's own table layout — fixed, minimum and
|
|
148
|
+
* maximum column widths, measured content, `white-space` — remains the single
|
|
149
|
+
* sizing authority. Two DOM reads per pass, independent of row count: the
|
|
150
|
+
* algorithm is O(1) in rows and never scans cells.
|
|
151
|
+
*
|
|
152
|
+
* Shrinking applies one more rule per pass and records the width at which the
|
|
153
|
+
* previous count did not fit. Growing releases one rule per pass, but only
|
|
154
|
+
* once the container is wider than that recorded width plus the hysteresis, so
|
|
155
|
+
* a restore that would immediately overflow again is not attempted. Passes are
|
|
156
|
+
* capped per width, so a pathological layout cannot loop forever.
|
|
157
|
+
*/
|
|
158
|
+
export declare function useResponsiveColumnFit({ enabled, containerRef, ruleCount, signature, }: ResponsiveColumnFitOptions): number;
|
|
159
|
+
/**
|
|
160
|
+
* Reports configuration problems once per configuration.
|
|
161
|
+
*
|
|
162
|
+
* Follows the library's existing development-time validation convention
|
|
163
|
+
* (`useControllableState`, `Sidebar`): `NODE_ENV`-guarded, `console.error`
|
|
164
|
+
* with the `[react-components]` prefix, from an effect keyed on a stable
|
|
165
|
+
* signature — so it never throws during render and never logs on resize.
|
|
166
|
+
* Production fails safe: the malformed rule is skipped, and no data is hidden
|
|
167
|
+
* because of it.
|
|
168
|
+
*/
|
|
169
|
+
export declare function useResponsivePlanDiagnostics(plan: ResponsiveColumnPlan, signature: string): void;
|
package/dist/base/index.d.ts
CHANGED
|
@@ -17,6 +17,12 @@ export * from './AutoComplete';
|
|
|
17
17
|
export * from './FormField';
|
|
18
18
|
export * from './Modal';
|
|
19
19
|
export * from './DataGrid';
|
|
20
|
+
/**
|
|
21
|
+
* Responsive column model shared by every grid-like component. Exported from
|
|
22
|
+
* the Base layer so Remote and Smart grids inherit it by composition rather
|
|
23
|
+
* than by a second implementation.
|
|
24
|
+
*/
|
|
25
|
+
export type { GridResponsiveOptions, ResponsiveColumnBehavior, ResponsiveColumnRule, } from './dataGridResponsive';
|
|
20
26
|
export * from './Pagination';
|
|
21
27
|
export * from './Button';
|
|
22
28
|
export * from './Spinner';
|
|
@@ -57,6 +63,15 @@ export { AppearanceProvider, useAppearance, createAppearanceInitScript, THEME_MO
|
|
|
57
63
|
export type { AppearanceContextValue, AppearanceInitScriptOptions, AppearanceProviderProps, AppearanceSettings, AppearanceStorage, ResolvedTheme, SidebarPresentation, SidebarTone, ThemeMode, } from './Appearance';
|
|
58
64
|
export * from './appearanceSchemes';
|
|
59
65
|
export * from './AppearanceMenu';
|
|
66
|
+
/**
|
|
67
|
+
* Context menu system. `RichComponentBase` and the `ContextMenu` compound are
|
|
68
|
+
* the whole public surface — the surface context, the positioning helpers and
|
|
69
|
+
* the node-kind model behind them stay internal.
|
|
70
|
+
*/
|
|
71
|
+
export { RichComponentBase } from './RichComponentBase';
|
|
72
|
+
export type { RichComponentBaseProps, RichComponentBaseTrigger } from './RichComponentBase';
|
|
73
|
+
export { ContextMenu } from './ContextMenu';
|
|
74
|
+
export type { ContextMenuComponent, ContextMenuItemProps, ContextMenuLabelProps, ContextMenuProps, ContextMenuSeparatorProps, ContextMenuSubMenuProps, } from './ContextMenu';
|
|
60
75
|
export { Popup } from './shared/Popup';
|
|
61
76
|
export type { PopupProps } from './shared/Popup';
|
|
62
77
|
export { Label } from './shared/Label';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
export interface PortalProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
/** Defaults to `document.body` once mounted on the client. */
|
|
5
|
+
container?: Element | null;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Minimal SSR-safe portal.
|
|
9
|
+
*
|
|
10
|
+
* The repository had no portal infrastructure before this (Popup positions
|
|
11
|
+
* itself with plain CSS and Modal renders in place, both of which say so in
|
|
12
|
+
* their own doc comments), so this is the smallest primitive that lets an
|
|
13
|
+
* overlay escape an ancestor's `overflow`/`transform` clipping.
|
|
14
|
+
*
|
|
15
|
+
* `document` is never touched during module initialization or during render:
|
|
16
|
+
* the host is resolved in an effect, so a server render produces nothing and
|
|
17
|
+
* the first client render is identical to it. Intentionally internal — it is
|
|
18
|
+
* plumbing for the context menu surface, not a second public overlay API.
|
|
19
|
+
*/
|
|
20
|
+
export declare function Portal({ children, container }: PortalProps): import('react').ReactPortal | null;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Calls `onResize` whenever the observed element's box changes.
|
|
4
|
+
*
|
|
5
|
+
* The element's *own* box, not the viewport: a grid can sit in a narrow
|
|
6
|
+
* dashboard panel, a modal, a split view or a resizable container, and a
|
|
7
|
+
* global breakpoint knows nothing about any of those.
|
|
8
|
+
*
|
|
9
|
+
* `ResizeObserver` is used when the environment has it, and only then; the
|
|
10
|
+
* window-resize listener is a fallback for environments without it, so a
|
|
11
|
+
* modern browser never gets a permanent global listener per grid. Nothing is
|
|
12
|
+
* touched during module initialization or during render, so importing this on
|
|
13
|
+
* a server is inert, and the observer is disconnected on cleanup — which is
|
|
14
|
+
* also what makes it safe under React Strict Mode's double mount.
|
|
15
|
+
*/
|
|
16
|
+
export declare function useElementResize(ref: RefObject<Element | null>, enabled: boolean, onResize: () => void): void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* `useLayoutEffect` in the browser, `useEffect` on the server.
|
|
4
|
+
*
|
|
5
|
+
* Measurement has to happen after layout but before paint, which is what
|
|
6
|
+
* `useLayoutEffect` is for — but React warns when it runs during server
|
|
7
|
+
* rendering, where there is no layout to read. The `typeof window` test never
|
|
8
|
+
* touches a browser global (it cannot throw) and is evaluated once, so the
|
|
9
|
+
* choice is constant for the lifetime of the module and the rules of hooks
|
|
10
|
+
* still hold.
|
|
11
|
+
*/
|
|
12
|
+
export declare const useIsomorphicLayoutEffect: typeof useEffect;
|