@mezzanine-ui/react 1.2.0 → 1.3.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.
@@ -10,20 +10,10 @@ import Button from '../Button/Button.js';
10
10
  import { useDocumentEvents } from '../hooks/useDocumentEvents.js';
11
11
  import Translate from '../Transition/Translate.js';
12
12
  import { composeRefs } from '../utils/composeRefs.js';
13
+ import { getElementRef } from '../utils/getElementRef.js';
13
14
  import DropdownItem from './DropdownItem.js';
14
15
  import Popper from '../Popper/Popper.js';
15
16
 
16
- /**
17
- * Extracts ref from a ReactElement, supporting both React 18 and 19.
18
- * In React 18, ref is on the element itself; in React 19, ref is in props.
19
- */
20
- function getElementRef(element) {
21
- var _a;
22
- // React 19: ref is in props
23
- const propsRef = (_a = element.props) === null || _a === void 0 ? void 0 : _a.ref;
24
- // React 18: ref is on the element itself
25
- return propsRef !== null && propsRef !== void 0 ? propsRef : element.ref;
26
- }
27
17
  /**
28
18
  * 下拉選單元件,以 `Button` 或 `Input` 作為觸發元素,點擊後展開選項列表。
29
19
  *
@@ -53,10 +53,19 @@ const TableResizeHandle = memo(function TableResizeHandle({ column, columnIndex,
53
53
  // If there's no next column, we can't do adjacent resize
54
54
  return;
55
55
  }
56
+ const lastColumnIndex = columns.length - 1;
57
+ const lastColumn = columns[lastColumnIndex];
58
+ // When resizing the second-to-last column, nextColumn === lastColumn.
59
+ // We collapse to the legacy adjacent-compensation path to avoid
60
+ // double-writing the same key.
61
+ const isAdjacentToLast = columnIndex + 1 === lastColumnIndex;
56
62
  // Get the actual rendered widths from the DOM
57
63
  const currentWidth = getColumnActualWidth(columnIndex);
58
64
  const nextWidth = getColumnActualWidth(columnIndex + 1);
59
- if (currentWidth === 0 || nextWidth === 0) {
65
+ const lastWidth = isAdjacentToLast
66
+ ? nextWidth
67
+ : getColumnActualWidth(lastColumnIndex);
68
+ if (currentWidth === 0 || nextWidth === 0 || lastWidth === 0) {
60
69
  return;
61
70
  }
62
71
  startXRef.current = event.clientX;
@@ -66,32 +75,59 @@ const TableResizeHandle = memo(function TableResizeHandle({ column, columnIndex,
66
75
  const maxWidth = column.maxWidth;
67
76
  const nextMinWidth = nextColumn.minWidth;
68
77
  const nextMaxWidth = nextColumn.maxWidth;
78
+ const lastMinWidth = lastColumn.minWidth;
79
+ const lastMaxWidth = lastColumn.maxWidth;
80
+ // Slack the last column can absorb (signed against `diff`):
81
+ // - positive `diff` shrinks last → capped by lastShrinkBudget
82
+ // - negative `diff` grows last → capped by lastGrowBudget
83
+ const lastShrinkBudget = lastWidth - (lastMinWidth !== null && lastMinWidth !== void 0 ? lastMinWidth : 0);
84
+ const lastGrowBudget = lastMaxWidth !== undefined ? lastMaxWidth - lastWidth : Infinity;
69
85
  const handleMouseMove = (moveEvent) => {
70
86
  const diff = moveEvent.clientX - startXRef.current;
71
87
  const newWidth = startWidthRef.current + diff;
72
- const newNextWidth = nextStartWidthRef.current - diff;
73
- let isConstrained = false;
74
- // Check current column constraints
75
- if (minWidth !== undefined && newWidth < minWidth) {
76
- isConstrained = true;
77
- }
78
- if (maxWidth !== undefined && newWidth > maxWidth) {
79
- isConstrained = true;
88
+ // Current column constraint checks
89
+ if (minWidth !== undefined && newWidth < minWidth)
90
+ return;
91
+ if (maxWidth !== undefined && newWidth > maxWidth)
92
+ return;
93
+ if (newWidth < 0)
94
+ return;
95
+ if (isAdjacentToLast) {
96
+ // Legacy adjacent compensation: donor === N+1 === last
97
+ const newNextWidth = nextStartWidthRef.current - diff;
98
+ if (nextMinWidth !== undefined && newNextWidth < nextMinWidth)
99
+ return;
100
+ if (nextMaxWidth !== undefined && newNextWidth > nextMaxWidth)
101
+ return;
102
+ if (newNextWidth < 0)
103
+ return;
104
+ setResizedColumnWidth === null || setResizedColumnWidth === void 0 ? void 0 : setResizedColumnWidth(column.key, newWidth);
105
+ setResizedColumnWidth === null || setResizedColumnWidth === void 0 ? void 0 : setResizedColumnWidth(nextColumn.key, newNextWidth);
106
+ return;
80
107
  }
81
- // Check next column constraints
82
- if (nextMinWidth !== undefined && newNextWidth < nextMinWidth) {
83
- isConstrained = true;
108
+ // Last-column donor strategy:
109
+ // Project `diff` onto the last column first (clamped to its budget).
110
+ // Any overflow falls back to the adjacent column (N+1).
111
+ let toLast;
112
+ if (diff >= 0) {
113
+ toLast = Math.min(diff, lastShrinkBudget);
84
114
  }
85
- if (nextMaxWidth !== undefined && newNextWidth > nextMaxWidth) {
86
- isConstrained = true;
115
+ else {
116
+ toLast = Math.max(diff, -lastGrowBudget);
87
117
  }
88
- if (isConstrained) {
118
+ const overflow = diff - toLast;
119
+ const newLastWidth = lastWidth - toLast;
120
+ // Always write N+1 too — when overflow returns to 0 on the return
121
+ // drag, this restores N+1 to its start width (LIFO donor restoration).
122
+ const newNextWidth = nextStartWidthRef.current - overflow;
123
+ if (nextMinWidth !== undefined && newNextWidth < nextMinWidth)
89
124
  return;
90
- }
91
- if (newWidth < 0 || newNextWidth < 0) {
125
+ if (nextMaxWidth !== undefined && newNextWidth > nextMaxWidth)
126
+ return;
127
+ if (newNextWidth < 0)
92
128
  return;
93
- }
94
129
  setResizedColumnWidth === null || setResizedColumnWidth === void 0 ? void 0 : setResizedColumnWidth(column.key, newWidth);
130
+ setResizedColumnWidth === null || setResizedColumnWidth === void 0 ? void 0 : setResizedColumnWidth(lastColumn.key, newLastWidth);
95
131
  setResizedColumnWidth === null || setResizedColumnWidth === void 0 ? void 0 : setResizedColumnWidth(nextColumn.key, newNextWidth);
96
132
  };
97
133
  const handleMouseUp = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mezzanine-ui/react",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "React components for mezzanine-ui",
5
5
  "author": "Mezzanine",
6
6
  "repository": {
@@ -57,6 +57,5 @@
57
57
  "moment": "^2.30.1",
58
58
  "react": "^19.2.0",
59
59
  "react-dom": "^19.2.0"
60
- },
61
- "gitHead": "f5fa136dac936a0debafff9ea5a3815ff0b5c040"
60
+ }
62
61
  }
@@ -0,0 +1,27 @@
1
+ import { ReactElement, Ref } from 'react';
2
+ /**
3
+ * Helper type to extract ref from a ReactElement.
4
+ * Models `ref` on the element itself, which is compatible with React 18 and 19.
5
+ */
6
+ export type ReactElementWithRef<P, E extends Element = HTMLElement> = ReactElement<P> & {
7
+ ref?: Ref<E>;
8
+ };
9
+ /**
10
+ * Extracts ref from a ReactElement, supporting both React 18 and 19.
11
+ * In React 18, ref is on the element itself; in React 19, ref is in props.
12
+ *
13
+ * Reading the "wrong" location in dev mode triggers React warning getters:
14
+ *
15
+ * - React 18 installs a warning getter on `props.ref` when the element was
16
+ * created with a ref — accessing it logs
17
+ * "`ref` is not a prop. Trying to access it will result in `undefined` being returned."
18
+ * - React 19 installs a deprecation getter on `element.ref` when the element
19
+ * was created with a ref — accessing it logs
20
+ * "Accessing element.ref was removed in React 19."
21
+ *
22
+ * So instead of unconditionally reading `props.ref` first, detect the
23
+ * dev-mode warning getters (marked with `isReactWarning`) and read the ref
24
+ * from the location where it actually lives. Same approach as
25
+ * `getElementRef` in radix-ui/primitives.
26
+ */
27
+ export declare function getElementRef<E extends Element = HTMLElement>(element: ReactElementWithRef<unknown, E>): Ref<E> | undefined;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Whether the given property getter is a React dev-mode warning getter.
3
+ * React marks them with `isReactWarning = true`
4
+ * (see `defineRefPropWarningGetter` in the React source).
5
+ */
6
+ function isReactWarningGetter(getter) {
7
+ return (typeof getter === 'function' &&
8
+ Boolean(getter.isReactWarning));
9
+ }
10
+ /**
11
+ * Extracts ref from a ReactElement, supporting both React 18 and 19.
12
+ * In React 18, ref is on the element itself; in React 19, ref is in props.
13
+ *
14
+ * Reading the "wrong" location in dev mode triggers React warning getters:
15
+ *
16
+ * - React 18 installs a warning getter on `props.ref` when the element was
17
+ * created with a ref — accessing it logs
18
+ * "`ref` is not a prop. Trying to access it will result in `undefined` being returned."
19
+ * - React 19 installs a deprecation getter on `element.ref` when the element
20
+ * was created with a ref — accessing it logs
21
+ * "Accessing element.ref was removed in React 19."
22
+ *
23
+ * So instead of unconditionally reading `props.ref` first, detect the
24
+ * dev-mode warning getters (marked with `isReactWarning`) and read the ref
25
+ * from the location where it actually lives. Same approach as
26
+ * `getElementRef` in radix-ui/primitives.
27
+ */
28
+ function getElementRef(element) {
29
+ var _a, _b, _c;
30
+ const props = element.props;
31
+ // React 18 dev mode: `props.ref` is a warning getter; the actual ref
32
+ // lives on the element itself.
33
+ const propsRefGetter = props
34
+ ? (_a = Object.getOwnPropertyDescriptor(props, 'ref')) === null || _a === void 0 ? void 0 : _a.get
35
+ : undefined;
36
+ if (isReactWarningGetter(propsRefGetter)) {
37
+ return element.ref;
38
+ }
39
+ // React 19 dev mode: `element.ref` may be a deprecation warning getter;
40
+ // the actual ref lives in props as a regular property.
41
+ const elementRefGetter = (_b = Object.getOwnPropertyDescriptor(element, 'ref')) === null || _b === void 0 ? void 0 : _b.get;
42
+ if (isReactWarningGetter(elementRefGetter)) {
43
+ return props === null || props === void 0 ? void 0 : props.ref;
44
+ }
45
+ // No warning getters (production builds, or no ref was given):
46
+ // prefer `props.ref` (React 19), fall back to `element.ref` (React 18).
47
+ // Safe on React 19 dev — its `element.ref` deprecation getter is only
48
+ // installed when a ref exists, in which case `props.ref` is returned here.
49
+ return (_c = props === null || props === void 0 ? void 0 : props.ref) !== null && _c !== void 0 ? _c : element.ref;
50
+ }
51
+
52
+ export { getElementRef };