@mendylanda/ui 0.3.2-dev.58.0ef9785a4046 → 0.4.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.
@@ -146,14 +146,19 @@ function EditorHeading({ section, desktop, back, onCleared, }) {
146
146
  }
147
147
  function MenuHeading({ label }) {
148
148
  const ref = useRef(null);
149
+ const textRef = useRef(null);
149
150
  const [overflow, setOverflow] = useState(false);
150
151
  const [below, setBelow] = useState(false);
151
152
  const measure = () => {
152
153
  const element = ref.current;
153
- if (!element)
154
+ const text = textRef.current;
155
+ if (!element || !text)
154
156
  return;
155
- setOverflow(element.scrollHeight > element.clientHeight + 1);
156
- setBelow(element.scrollHeight - element.clientHeight - element.scrollTop > 1);
157
+ // Measure the text's line boxes, not glyph overflow. Fonts can paint below a
158
+ // single line without the title having another line to scroll to.
159
+ const height = text.getBoundingClientRect().height;
160
+ setOverflow(height > element.clientHeight + 1);
161
+ setBelow(height - element.clientHeight - element.scrollTop > 1);
157
162
  };
158
163
  useLayoutEffect(() => {
159
164
  const element = ref.current;
@@ -163,9 +168,11 @@ function MenuHeading({ label }) {
163
168
  measure();
164
169
  const observer = new ResizeObserver(measure);
165
170
  observer.observe(element);
171
+ if (textRef.current)
172
+ observer.observe(textRef.current);
166
173
  return () => observer.disconnect();
167
174
  }, [label]);
168
- return (_jsx("span", { ref: ref, onScroll: measure, title: label, tabIndex: overflow ? 0 : undefined, "data-more-below": below, className: "mui-1326520533f0 mui-184ddc11e5f9 mui-7bd5bab6d7f4 mui-2368e909f3a5 mui-726fca123972 mui-393883c1db68 mui-cacb19aa9640 mui-bbaaa5969856 mui-1df9125eebaf mui-223daa83ed2d", children: label }));
175
+ return (_jsx("span", { ref: ref, onScroll: measure, title: label, tabIndex: overflow ? 0 : undefined, "data-more-below": below, className: cn("mui-1326520533f0 mui-184ddc11e5f9 mui-7bd5bab6d7f4 mui-726fca123972 mui-393883c1db68 mui-cacb19aa9640 mui-bbaaa5969856 mui-1df9125eebaf mui-223daa83ed2d", overflow ? "mui-2368e909f3a5" : "mui-0fc84a692dad"), children: _jsx("span", { ref: textRef, className: "mui-496aca80e4d8", children: label }) }));
169
176
  }
170
177
  function FilterMenuList({ sections, selectedId, initialKey, panelId, desktop, detached, rows, choose, keyboardNavigation, onPointerMove, }) {
171
178
  const { classNames } = useMendyUI();
@@ -29,6 +29,8 @@ export interface ValueFormat<V> {
29
29
  }
30
30
  interface ValueOptions<T extends object, V> {
31
31
  label: string;
32
+ /** Override the formatter's alignment without changing display, copy, or export text. */
33
+ align?: "start" | "center" | "end";
32
34
  size?: number;
33
35
  grow?: number | false;
34
36
  minSize?: number;
@@ -14,7 +14,7 @@ function valueColumn(id, value, options) {
14
14
  maxSize: options.maxSize,
15
15
  defaultHidden: options.hidden,
16
16
  pin: options.pin,
17
- align: options.format?.align,
17
+ align: options.align ?? options.format?.align,
18
18
  enableSorting: options.sortable ?? true,
19
19
  cell: ({ row }) => {
20
20
  const v = value(row.original);
@@ -4,6 +4,8 @@ import type { TableViewProps } from "./table-view.js";
4
4
  import { type TableFiltersProps } from "./table-filters.js";
5
5
  type Appearance<T extends object> = Omit<TableViewProps<T>, "table"> & {
6
6
  toolbar?: ReactNode;
7
+ /** Custom controls beside the filter bar, before toolbar actions and column settings. */
8
+ toolbarStart?: ReactNode;
7
9
  footer?: ReactNode;
8
10
  showColumnSettings?: boolean;
9
11
  showPagination?: boolean;
@@ -1,13 +1,13 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useDataTable } from "./use-data-table.js";
4
4
  import { TableView } from "./table-view.js";
5
5
  import { TableColumnSettings, TablePagination } from "./table-controls.js";
6
6
  import { TableFilters } from "./table-filters.js";
7
7
  import { TableFrame } from "./table-frame.js";
8
- function ControlledTable({ table, toolbar, footer, showColumnSettings = true, showPagination = false, filters, filterBar, layout = "content", ...view }) {
8
+ function ControlledTable({ table, toolbar, toolbarStart, footer, showColumnSettings = true, showPagination = false, filters, filterBar, layout = "content", minHeight, ...view }) {
9
9
  const fill = layout === "fill";
10
- return (_jsx(TableFrame, { layout: layout, slot: "data-table", header: (toolbar || showColumnSettings || (filters && filterBar !== false)) && (_jsxs("div", { className: "mui-222f930b8752 mui-27ead27a81df mui-faa8a23c68f6 mui-b9677bd1cb66 mui-32c92bb1fde4 mui-074569488cca", children: [_jsxs("div", { className: "mui-184ddc11e5f9 mui-7bd5bab6d7f4 mui-267171770524", children: [filters && filterBar !== false && _jsx(TableFilters, { ...filterBar, filters: filters }), !filters && toolbar] }), filters && toolbar, showColumnSettings && _jsx(TableColumnSettings, { table: table })] })), footer: showPagination || footer ? (_jsxs(_Fragment, { children: [showPagination && (_jsx("div", { className: "mui-27ead27a81df", children: _jsx(TablePagination, { table: table, loading: view.status === "loading" || view.refreshing }) })), footer] })) : undefined, children: _jsx(TableView, { table: table, filters: filters, ...view, height: fill ? "100%" : view.height }) }));
10
+ return (_jsx(TableFrame, { layout: layout, minHeight: minHeight, slot: "data-table", header: (toolbarStart || toolbar || showColumnSettings || (filters && filterBar !== false)) && (_jsx(TableToolbar, { table: table, toolbar: toolbar, toolbarStart: toolbarStart, filters: filters, filterBar: filterBar, showColumnSettings: showColumnSettings })), footer: showPagination || footer ? (_jsxs(_Fragment, { children: [showPagination && (_jsx("div", { className: "mui-27ead27a81df", children: _jsx(TablePagination, { table: table, loading: view.status === "loading" || view.refreshing }) })), footer] })) : undefined, children: _jsx(TableView, { table: table, filters: filters, ...view, height: fill ? "100%" : view.height }) }));
11
11
  }
12
12
  function ConfiguredTable(props) {
13
13
  const table = useDataTable(props);
@@ -16,3 +16,6 @@ function ConfiguredTable(props) {
16
16
  export function DataTable(props) {
17
17
  return "table" in props ? _jsx(ControlledTable, { ...props }) : _jsx(ConfiguredTable, { ...props });
18
18
  }
19
+ function TableToolbar({ table, toolbar, toolbarStart, filters, filterBar, showColumnSettings, }) {
20
+ return (_jsxs("div", { className: "mui-222f930b8752 mui-27ead27a81df mui-faa8a23c68f6 mui-b9677bd1cb66 mui-32c92bb1fde4 mui-074569488cca", children: [_jsxs("div", { className: "mui-222f930b8752 mui-184ddc11e5f9 mui-7bd5bab6d7f4 mui-faa8a23c68f6 mui-71556df3b421 mui-074569488cca", children: [filters && filterBar !== false && _jsx(TableFilters, { ...filterBar, filters: filters }), toolbarStart, !filters && !toolbarStart && toolbar] }), _jsxs("div", { className: "mui-222f930b8752 mui-faa8a23c68f6 mui-71556df3b421 mui-074569488cca", children: [(filters || toolbarStart) && toolbar, showColumnSettings && _jsx(TableColumnSettings, { table: table })] })] }));
21
+ }
@@ -22,3 +22,7 @@ export declare function TableLoadMore({ loadMore, columnCount, rowIndex, onLoad,
22
22
  rowIndex: number;
23
23
  onLoad: () => void;
24
24
  }): import("react/jsx-runtime").JSX.Element;
25
+ export declare function TableRefreshError({ columnCount, rowIndex, error, retry, }: Pick<TableDataState, "error" | "retry"> & {
26
+ columnCount: number;
27
+ rowIndex: number;
28
+ }): import("react/jsx-runtime").JSX.Element;
@@ -19,3 +19,7 @@ export function TableLoadMore({ loadMore, columnCount, rowIndex, onLoad, }) {
19
19
  const { t } = useMendyLocale();
20
20
  return (_jsx(TableFeedbackRow, { columnCount: columnCount, rowIndex: rowIndex, className: "mui-964a9431ff49 mui-8368bbb62f66", children: _jsxs("div", { className: "mui-222f930b8752 mui-71556df3b421 mui-a503dd374cca mui-074569488cca mui-96b92a971c95 mui-c74ab393b96d mui-35f35c41d134", children: [loadMore.loading ? (_jsxs("div", { role: "status", className: "mui-222f930b8752 mui-71556df3b421 mui-074569488cca", children: [_jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", className: "mui-2bd43fb57913 mui-27ead27a81df mui-2c14b55d77e6 mui-237747164b60", children: _jsx("path", { d: "M12 3v3m6.366-.366-2.12 2.12M21 12h-3m.366 6.366-2.12-2.12M12 21v-3m-6.366.366 2.12-2.12M3 12h3m-.366-6.366 2.12 2.12" }) }), _jsx("span", { children: t("loadingMore") })] })) : (_jsx(Button, { size: "sm", variant: "ghost", onClick: onLoad, children: loadMore.error ? t("retryMore") : t("loadMore") })), loadMore.error && _jsx("span", { role: "alert", children: loadMore.error })] }) }));
21
21
  }
22
+ export function TableRefreshError({ columnCount, rowIndex, error, retry, }) {
23
+ const { t } = useMendyLocale();
24
+ return (_jsx(TableFeedbackRow, { columnCount: columnCount, rowIndex: rowIndex, className: "mui-964a9431ff49 mui-30150dd033ab", children: _jsxs("div", { role: "alert", className: "mui-5b272f3c5076 mui-094f5333853b", children: [error ?? t("refreshError"), retry && (_jsx(Button, { onClick: retry, variant: "outline", size: "sm", children: t("retry") }))] }) }));
25
+ }
@@ -1,7 +1,8 @@
1
1
  import type { ReactNode } from "react";
2
2
  /** Shared sizing for the composed view and the table with built-in controls. */
3
- export declare function TableFrame({ layout, stretch, slot, header, footer, children, }: {
3
+ export declare function TableFrame({ layout, minHeight, stretch, slot, header, footer, children, }: {
4
4
  layout: "content" | "fill";
5
+ minHeight?: number;
5
6
  stretch?: boolean;
6
7
  slot?: string;
7
8
  header?: ReactNode;
@@ -4,9 +4,9 @@ import { useMendyLocale } from "../locale-context.js";
4
4
  import { cn } from "../utils.js";
5
5
  import { useFillLayout } from "./use-fill-layout.js";
6
6
  /** Shared sizing for the composed view and the table with built-in controls. */
7
- export function TableFrame({ layout, stretch = false, slot = "table-view", header, footer, children, }) {
7
+ export function TableFrame({ layout, minHeight = 240, stretch = false, slot = "table-view", header, footer, children, }) {
8
8
  const { direction, code, configured } = useMendyLocale();
9
9
  const fill = layout === "fill";
10
- const ref = useFillLayout(fill);
11
- return (_jsxs("div", { ref: ref, "data-mendy-ui": "", "data-slot": slot, dir: configured ? direction : undefined, lang: code, "data-layout": layout, className: cn("mui-184ddc11e5f9", fill ? "mui-222f930b8752 mui-410da8dfa8ac mui-302c0d124a94 mui-074569488cca" : "mui-267171770524"), style: !fill && stretch ? { height: "100%" } : undefined, children: [header && _jsx("div", { className: "mui-27ead27a81df", children: header }), _jsx("div", { className: fill ? "mui-0f2a693e93e2 mui-410da8dfa8ac mui-7bd5bab6d7f4 mui-b5985369ee35" : undefined, style: !fill && stretch ? { height: "100%" } : undefined, children: children }), footer && _jsx("div", { className: "mui-27ead27a81df mui-267171770524", children: footer })] }));
10
+ const ref = useFillLayout(fill, minHeight);
11
+ return (_jsxs("div", { ref: ref, "data-mendy-ui": "", "data-slot": slot, dir: configured ? direction : undefined, lang: code, "data-layout": layout, className: cn("mui-184ddc11e5f9", fill ? "mui-222f930b8752 mui-410da8dfa8ac mui-302c0d124a94 mui-074569488cca" : "mui-267171770524"), style: fill ? { height: minHeight, minHeight } : stretch ? { height: "100%" } : undefined, children: [header && _jsx("div", { className: "mui-27ead27a81df", children: header }), _jsx("div", { className: fill ? "mui-0f2a693e93e2 mui-410da8dfa8ac mui-7bd5bab6d7f4 mui-b5985369ee35" : undefined, style: !fill && stretch ? { height: "100%" } : undefined, children: children }), footer && _jsx("div", { className: "mui-27ead27a81df mui-267171770524", children: footer })] }));
12
12
  }
@@ -22,6 +22,8 @@ export interface TableViewProps<T extends object> extends TableDataState {
22
22
  height?: number | string;
23
23
  /** Fill the remaining page height, or fit content by default. Fill overrides height. */
24
24
  layout?: "content" | "fill";
25
+ /** Minimum frame height in pixels for fill layout, including controls. Defaults to 240. */
26
+ minHeight?: number;
25
27
  /** Controls below the grid, included in the available height when layout is fill. */
26
28
  footer?: ReactNode;
27
29
  /** Auto measures multiline rows and retains all columns to keep their height stable. */
@@ -49,4 +51,4 @@ export interface TableViewProps<T extends object> extends TableDataState {
49
51
  rowSelectionControls?: boolean;
50
52
  onCopyError?: (error: unknown) => void;
51
53
  }
52
- export declare function TableView<T extends object>({ table, label, height, layout: layoutMode, footer, rowHeight: rowHeightOption, rowClassName, renderRowDetail, className, contentClassName, emptyState, loadingState, scrollRef, status, refreshing, error, retry, loadMore, queryKey, filters, renderHeader, onRowClick, onRowActivate, isRowHighlighted, rowSelectionControls, onCopyError, }: TableViewProps<T>): import("react/jsx-runtime").JSX.Element;
54
+ export declare function TableView<T extends object>({ table, label, height, layout: layoutMode, minHeight, footer, rowHeight: rowHeightOption, rowClassName, renderRowDetail, className, contentClassName, emptyState, loadingState, scrollRef, status, refreshing, error, retry, loadMore, queryKey, filters, renderHeader, onRowClick, onRowActivate, isRowHighlighted, rowSelectionControls, onCopyError, }: TableViewProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -4,18 +4,17 @@ import { useMendyLocale } from "../locale-context.js";
4
4
  import { Fragment, useCallback, useEffect, useLayoutEffect, useMemo, useRef } from "react";
5
5
  import { useVirtualizer } from "@tanstack/react-virtual";
6
6
  import { TableHeaderCell, TableBodyRow } from "./table-parts.js";
7
- import { Button } from "../primitives/button.js";
8
7
  import { cn } from "../utils.js";
9
8
  import { useColumnWindow } from "./use-column-window.js";
10
9
  import { useViewportWidth } from "./use-viewport-width.js";
11
10
  import { tableLayout } from "./table-layout.js";
12
11
  import { TableLoadingRows } from "./table-loading.js";
13
- import { TableInitialState, TableFeedbackRow, TableLoadMore } from "./table-feedback.js";
12
+ import { TableInitialState, TableRefreshError, TableLoadMore } from "./table-feedback.js";
14
13
  import { useTableInteraction } from "./use-table-interaction.js";
15
14
  import { useTableResults } from "./use-table-results.js";
16
15
  import { useInlineRowSelection } from "./use-inline-row-selection.js";
17
16
  import { TableFrame } from "./table-frame.js";
18
- export function TableView({ table, label, height = "auto", layout: layoutMode = "content", footer, rowHeight: rowHeightOption = 44, rowClassName, renderRowDetail, className, contentClassName, emptyState, loadingState, scrollRef, status = "ready", refreshing, error, retry, loadMore, queryKey, filters, renderHeader, onRowClick, onRowActivate, isRowHighlighted, rowSelectionControls = true, onCopyError, }) {
17
+ export function TableView({ table, label, height = "auto", layout: layoutMode = "content", minHeight, footer, rowHeight: rowHeightOption = 44, rowClassName, renderRowDetail, className, contentClassName, emptyState, loadingState, scrollRef, status = "ready", refreshing, error, retry, loadMore, queryKey, filters, renderHeader, onRowClick, onRowActivate, isRowHighlighted, rowSelectionControls = true, onCopyError, }) {
19
18
  const { t, direction, code } = useMendyLocale();
20
19
  const fill = layoutMode === "fill";
21
20
  const { resultKey, hasFilters, clearFilters } = useTableResults(table, queryKey, filters);
@@ -42,6 +41,9 @@ export function TableView({ table, label, height = "auto", layout: layoutMode =
42
41
  estimateSize,
43
42
  getItemKey,
44
43
  overscan: 8,
44
+ // Match server markup and the first client render with a bounded window.
45
+ // Browser measurements replace this estimate as soon as the grid mounts.
46
+ initialRect: { width: 0, height: rowHeight * 12 },
45
47
  });
46
48
  const viewportWidth = useViewportWidth(container);
47
49
  const startColumns = table.getStartVisibleLeafColumns();
@@ -106,12 +108,12 @@ export function TableView({ table, label, height = "auto", layout: layoutMode =
106
108
  virtual,
107
109
  ]);
108
110
  const headersById = new Map(table.getFlatHeaders().map((header) => [header.column.id, header]));
109
- return (_jsxs(TableFrame, { layout: layoutMode, stretch: height === "100%", footer: footer, children: [_jsx("div", { "aria-live": "polite", className: "mui-32fb090591d9", children: announcement }), _jsxs("div", { ref: setContainer, role: "grid", tabIndex: -1, "aria-label": label ?? t("dataTable"), dir: direction, lang: code, "aria-rowcount": renderRowDetail ? -1 : rows.length + 1 + feedbackRowCount, "aria-colcount": layout.columns.length, "aria-busy": status === "loading" || refreshing, style: {
111
+ return (_jsxs(TableFrame, { layout: layoutMode, minHeight: minHeight, stretch: height === "100%", footer: footer, children: [_jsx("div", { "aria-live": "polite", className: "mui-32fb090591d9", children: announcement }), _jsxs("div", { ref: setContainer, role: "grid", tabIndex: -1, "aria-label": label ?? t("dataTable"), dir: direction, lang: code, "aria-rowcount": renderRowDetail ? -1 : rows.length + 1 + feedbackRowCount, "aria-colcount": layout.columns.length, "aria-busy": status === "loading" || refreshing, style: {
110
112
  height: fill || height === "auto" ? undefined : height,
111
113
  maxHeight: !fill && height === "auto" ? "65dvh" : undefined,
112
114
  }, className: cn("mui-d2d9e1f13413 mui-014aadadffad mui-1a26a0d28420 mui-3fa8c572949b mui-4f1a55de40bc mui-5b272f3c5076 mui-c74ab393b96d mui-b30fc56058b6", fill && "mui-410da8dfa8ac mui-7bd5bab6d7f4", className), onKeyDown: onKeyDown, children: [_jsx("div", { role: "row", "aria-rowindex": 1, className: "mui-964a9431ff49 mui-98599e4ee250 mui-4e8f0a87a0dc mui-222f930b8752 mui-2bf6510f1330 mui-bbe39cfb5cc6 mui-5b272f3c5076", style: { width: totalWidth }, children: columns.map((column) => (_jsxs(Fragment, { children: [columnGaps.has(column.id) && (_jsx("div", { "aria-hidden": "true", className: "mui-27ead27a81df", style: { width: columnGaps.get(column.id) } })), _jsx(TableHeaderCell, { table: table, header: headersById.get(column.id), index: columnIndexes.get(column.id), style: cellStyle(column), renderHeader: renderHeader, contentClassName: contentClassName, selectLoaded: selectLoaded }, column.id)] }, column.id))) }), _jsx(TableInitialState, { columnCount: layout.columns.length, status: status, hasRows: rows.length > 0, loadingState: loadingState ?? (_jsx(TableLoadingRows, { columns: columns, columnGaps: columnGaps, width: totalWidth, rowHeight: rowHeight, cellStyle: cellStyle, count: !fill && height === "auto"
113
115
  ? 8
114
- : Math.max(1, Math.ceil(((virtual.scrollRect?.height ?? 400) - rowHeight) / rowHeight)) })), emptyState: emptyState, hasFilters: hasFilters, pageIndex: table.state.pagination.pageIndex, clearFilters: clearFilters, firstPage: () => table.setPageIndex(0), error: error, retry: retry }), _jsx("div", { role: "rowgroup", className: "mui-d2d9e1f13413 mui-2bf6510f1330", style: { height: virtual.getTotalSize(), width: totalWidth }, children: items.map((item) => (_jsx(TableBodyRow, { row: rows[item.index], rowIndex: item.index, start: item.start, rowHeight: rowHeight, autoRowHeight: autoRowHeight, measureElement: autoRowHeight ? virtual.measureElement : undefined, rowClassName: rowClassName, renderRowDetail: renderRowDetail, detailWidth: viewportWidth ?? totalWidth, cellStyle: cellStyle, columns: columns, columnGaps: columnGaps, columnIndexes: columnIndexes, contentVersion: table.options, contentState: table.state, focusedId: focused?.id, copied: copied, contentClassName: contentClassName, onRowClick: onRowClick, onRowActivate: onRowActivate, isRowHighlighted: isRowHighlighted, inlineSelection: inlineSelection }, item.key))) }), hasRefreshError && (_jsx(TableFeedbackRow, { columnCount: layout.columns.length, rowIndex: rows.length + 2, className: "mui-964a9431ff49 mui-30150dd033ab", children: _jsxs("div", { role: "alert", className: "mui-5b272f3c5076 mui-094f5333853b", children: [error ?? t("refreshError"), retry && (_jsx(Button, { onClick: retry, variant: "outline", size: "sm", children: t("retry") }))] }) })), loadMore?.available && (_jsx(TableLoadMore, { loadMore: loadMore, columnCount: layout.columns.length, rowIndex: rows.length + 2 + Number(hasInitialFeedback || hasRefreshError), onLoad: () => {
116
+ : Math.max(1, Math.ceil(((virtual.scrollRect?.height ?? 400) - rowHeight) / rowHeight)) })), emptyState: emptyState, hasFilters: hasFilters, pageIndex: table.state.pagination.pageIndex, clearFilters: clearFilters, firstPage: () => table.setPageIndex(0), error: error, retry: retry }), _jsx("div", { role: "rowgroup", className: "mui-d2d9e1f13413 mui-2bf6510f1330", style: { height: virtual.getTotalSize(), width: totalWidth }, children: items.map((item) => (_jsx(TableBodyRow, { row: rows[item.index], rowIndex: item.index, start: item.start, rowHeight: rowHeight, autoRowHeight: autoRowHeight, measureElement: autoRowHeight ? virtual.measureElement : undefined, rowClassName: rowClassName, renderRowDetail: renderRowDetail, detailWidth: viewportWidth ?? totalWidth, cellStyle: cellStyle, columns: columns, columnGaps: columnGaps, columnIndexes: columnIndexes, contentVersion: table.options, contentState: table.state, focusedId: focused?.id, copied: copied, contentClassName: contentClassName, onRowClick: onRowClick, onRowActivate: onRowActivate, isRowHighlighted: isRowHighlighted, inlineSelection: inlineSelection }, item.key))) }), hasRefreshError && (_jsx(TableRefreshError, { columnCount: layout.columns.length, rowIndex: rows.length + 2, error: error, retry: retry })), loadMore?.available && (_jsx(TableLoadMore, { loadMore: loadMore, columnCount: layout.columns.length, rowIndex: rows.length + 2 + Number(hasInitialFeedback || hasRefreshError), onLoad: () => {
115
117
  requested.current = null;
116
118
  void Promise.resolve()
117
119
  .then(() => loadMore.load())
@@ -1,2 +1,2 @@
1
1
  /** Size the containing frame, not the grid, so controls share its available height. */
2
- export declare function useFillLayout(enabled: boolean): (node: HTMLDivElement | null) => void;
2
+ export declare function useFillLayout(enabled: boolean, minHeight: number): (node: HTMLDivElement | null) => void;
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { useCallback, useRef } from "react";
3
3
  /** Size the containing frame, not the grid, so controls share its available height. */
4
- export function useFillLayout(enabled) {
4
+ export function useFillLayout(enabled, minHeight) {
5
5
  const dispose = useRef(undefined);
6
6
  return useCallback((node) => {
7
7
  dispose.current?.();
@@ -13,14 +13,19 @@ export function useFillLayout(enabled) {
13
13
  const measure = () => {
14
14
  frame = 0;
15
15
  let inset = 0;
16
+ let scroll = window.scrollY;
16
17
  let ancestor = node.parentElement;
17
18
  while (ancestor) {
18
19
  const style = getComputedStyle(ancestor);
19
20
  inset += parseFloat(style.paddingBottom) || 0;
20
21
  inset += parseFloat(style.borderBottomWidth) || 0;
22
+ if (ancestor !== document.body && ancestor !== document.documentElement)
23
+ scroll += ancestor.scrollTop;
21
24
  ancestor = ancestor.parentElement;
22
25
  }
23
- const height = Math.max(0, window.innerHeight - node.getBoundingClientRect().top - inset);
26
+ // Use the unscrolled position: resizing after scrolling down must not grow the page.
27
+ const top = node.getBoundingClientRect().top + scroll;
28
+ const height = Math.max(minHeight, window.innerHeight - top - inset);
24
29
  if (Math.abs(height - previous) < 0.5)
25
30
  return;
26
31
  previous = height;
@@ -63,5 +68,5 @@ export function useFillLayout(enabled) {
63
68
  if (node.style.height === `${previous}px`)
64
69
  node.style.removeProperty("height");
65
70
  };
66
- }, [enabled]);
71
+ }, [enabled, minHeight]);
67
72
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mendylanda/ui",
3
- "version": "0.3.2-dev.58.0ef9785a4046",
3
+ "version": "0.4.0",
4
4
  "description": "Mendy Landa’s reusable React components. Typed filters and tables with shared interaction behavior and customizable UI.",
5
5
  "homepage": "https://ui.mendylanda.com",
6
6
  "license": "MIT",
@@ -118,5 +118,5 @@
118
118
  "build": "tsc -p tsconfig.build.json && node scripts/build-css.mjs",
119
119
  "typecheck": "tsc -p tsconfig.build.json --noEmit"
120
120
  },
121
- "gitHead": "0ef9785a4046cf8030396c3278c4231b866fcef4"
121
+ "gitHead": "4351f6209bd1bf1ea4a286fbc490eab25fb6fba2"
122
122
  }