@instructure/ui-position 11.7.4-snapshot-185 → 11.7.4-snapshot-8
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/CHANGELOG.md +2 -7
- package/es/Position/index.js +34 -2
- package/es/PositionPropTypes.js +8 -0
- package/es/calculateElementPosition.js +99 -13
- package/lib/Position/index.js +34 -2
- package/lib/PositionPropTypes.js +8 -1
- package/lib/calculateElementPosition.js +98 -14
- package/package.json +12 -12
- package/src/Position/index.tsx +29 -5
- package/src/PositionPropTypes.ts +11 -0
- package/src/calculateElementPosition.ts +100 -14
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/Position/index.d.ts +4 -1
- package/types/Position/index.d.ts.map +1 -1
- package/types/PositionPropTypes.d.ts +10 -0
- package/types/PositionPropTypes.d.ts.map +1 -1
- package/types/calculateElementPosition.d.ts +2 -2
- package/types/calculateElementPosition.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,17 +3,12 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
## [11.7.4-snapshot-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
### Bug Fixes
|
|
10
|
-
|
|
11
|
-
* **many:** update dependencies, remove lots of Babel plugins, remove Webpack 4 support ([98ff8e8](https://github.com/instructure/instructure-ui/commit/98ff8e8126a70d8496d6967795a8fbb2779c6fd9))
|
|
6
|
+
## [11.7.4-snapshot-8](https://github.com/instructure/instructure-ui/compare/v11.7.3...v11.7.4-snapshot-8) (2026-05-19)
|
|
12
7
|
|
|
13
8
|
|
|
14
9
|
### Features
|
|
15
10
|
|
|
16
|
-
* **
|
|
11
|
+
* **ui-position:** add available space to position ([2ce7236](https://github.com/instructure/instructure-ui/commit/2ce7236db58f6678aabd544823c65dda4a51082c))
|
|
17
12
|
|
|
18
13
|
|
|
19
14
|
|
package/es/Position/index.js
CHANGED
|
@@ -60,9 +60,13 @@ let Position = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle, g
|
|
|
60
60
|
static contentLocatorAttribute = 'data-position-content';
|
|
61
61
|
constructor(props) {
|
|
62
62
|
super(props);
|
|
63
|
+
const initial = this.calculatePosition(props);
|
|
64
|
+
this._availableHeight = initial.availableHeight;
|
|
65
|
+
this._availableWidth = initial.availableWidth;
|
|
63
66
|
this.state = {
|
|
64
67
|
positioned: false,
|
|
65
|
-
|
|
68
|
+
placement: initial.placement,
|
|
69
|
+
style: initial.style
|
|
66
70
|
};
|
|
67
71
|
this.position = debounce(this.position, 0, {
|
|
68
72
|
leading: false,
|
|
@@ -76,6 +80,8 @@ let Position = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle, g
|
|
|
76
80
|
_listener = null;
|
|
77
81
|
_content;
|
|
78
82
|
_target;
|
|
83
|
+
_availableHeight;
|
|
84
|
+
_availableWidth;
|
|
79
85
|
handleRef = el => {
|
|
80
86
|
const {
|
|
81
87
|
elementRef
|
|
@@ -152,6 +158,22 @@ let Position = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle, g
|
|
|
152
158
|
}
|
|
153
159
|
}, 0));
|
|
154
160
|
};
|
|
161
|
+
|
|
162
|
+
// Write `--ui-position-available-{height,width}` directly on the content
|
|
163
|
+
// node's inline style
|
|
164
|
+
applyAvailableSpaceCustomProperties() {
|
|
165
|
+
const node = findDOMNode(this._content);
|
|
166
|
+
if (!node?.style) return;
|
|
167
|
+
const set = (name, value) => {
|
|
168
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
169
|
+
node.style.setProperty(name, `${value}px`);
|
|
170
|
+
} else {
|
|
171
|
+
node.style.removeProperty(name);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
set('--ui-position-available-height', this._availableHeight);
|
|
175
|
+
set('--ui-position-available-width', this._availableWidth);
|
|
176
|
+
}
|
|
155
177
|
calculatePosition(props) {
|
|
156
178
|
return calculateElementPosition(this._content, this._target, {
|
|
157
179
|
placement: props.placement,
|
|
@@ -163,9 +185,19 @@ let Position = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle, g
|
|
|
163
185
|
});
|
|
164
186
|
}
|
|
165
187
|
position = () => {
|
|
188
|
+
const {
|
|
189
|
+
placement,
|
|
190
|
+
style,
|
|
191
|
+
availableHeight,
|
|
192
|
+
availableWidth
|
|
193
|
+
} = this.calculatePosition(this.props);
|
|
194
|
+
this._availableHeight = availableHeight;
|
|
195
|
+
this._availableWidth = availableWidth;
|
|
196
|
+
this.applyAvailableSpaceCustomProperties();
|
|
166
197
|
this.setState({
|
|
167
198
|
positioned: true,
|
|
168
|
-
|
|
199
|
+
placement,
|
|
200
|
+
style
|
|
169
201
|
});
|
|
170
202
|
};
|
|
171
203
|
startTracking() {
|
package/es/PositionPropTypes.js
CHANGED
|
@@ -49,4 +49,12 @@ const mirrorMap = {
|
|
|
49
49
|
stretch: 'stretch',
|
|
50
50
|
offscreen: 'offscreen'
|
|
51
51
|
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The full output of `calculateElementPosition`, including the available
|
|
55
|
+
* space numbers driving `--ui-position-available-{height,width}`. Kept
|
|
56
|
+
* separate from `ElementPosition` so `PositionState` (which extends
|
|
57
|
+
* `ElementPosition`) doesn't falsely advertise these fields
|
|
58
|
+
*/
|
|
59
|
+
|
|
52
60
|
export { placementPropValues, mirrorMap };
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { getBoundingClientRect, getScrollParents, getOffsetParents, canUseDOM, findDOMNode, ownerDocument, ownerWindow } from '@instructure/ui-dom-utils';
|
|
26
|
-
import { mirrorPlacement } from "./mirrorPlacement.js";
|
|
26
|
+
import { mirrorPlacement } from "./mirrorPlacement.js";
|
|
27
|
+
import { px } from '@instructure/ui-utils';
|
|
27
28
|
/**
|
|
28
29
|
* ---
|
|
29
30
|
* category: utilities/position
|
|
@@ -63,9 +64,15 @@ function calculateElementPosition(element, target, options = {}) {
|
|
|
63
64
|
};
|
|
64
65
|
}
|
|
65
66
|
const pos = new PositionData(element, target, options);
|
|
67
|
+
const {
|
|
68
|
+
height: availableHeight,
|
|
69
|
+
width: availableWidth
|
|
70
|
+
} = pos.availableSpace;
|
|
66
71
|
return {
|
|
67
72
|
placement: pos.placement,
|
|
68
|
-
style: pos.style
|
|
73
|
+
style: pos.style,
|
|
74
|
+
availableHeight,
|
|
75
|
+
availableWidth
|
|
69
76
|
};
|
|
70
77
|
}
|
|
71
78
|
class PositionedElement {
|
|
@@ -211,7 +218,6 @@ class PositionData {
|
|
|
211
218
|
this.options = options || {};
|
|
212
219
|
const {
|
|
213
220
|
container,
|
|
214
|
-
constrain,
|
|
215
221
|
placement,
|
|
216
222
|
over
|
|
217
223
|
} = this.options;
|
|
@@ -222,16 +228,9 @@ class PositionData {
|
|
|
222
228
|
left: this.options.offsetX
|
|
223
229
|
});
|
|
224
230
|
this.target = new PositionedElement(target || this.container, over ? this.element.placement : this.element.mirroredPlacement);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
this.constrainTo(getScrollParents(this.target.node)[0]);
|
|
229
|
-
} else if (constrain === 'parent') {
|
|
230
|
-
this.constrainTo(this.container);
|
|
231
|
-
} else if (typeof constrain === 'function') {
|
|
232
|
-
this.constrainTo(findDOMNode(constrain.call(null)));
|
|
233
|
-
} else if (typeof constrain === 'object') {
|
|
234
|
-
this.constrainTo(findDOMNode(constrain));
|
|
231
|
+
const constraintNode = this.resolveConstraintNode();
|
|
232
|
+
if (constraintNode) {
|
|
233
|
+
this.constrainTo(constraintNode);
|
|
235
234
|
}
|
|
236
235
|
}
|
|
237
236
|
options;
|
|
@@ -418,6 +417,93 @@ class PositionData {
|
|
|
418
417
|
}
|
|
419
418
|
}
|
|
420
419
|
}
|
|
420
|
+
|
|
421
|
+
// Resolves the `constrain` option (`'window'`, `'scroll-parent'`,
|
|
422
|
+
// `'parent'`, a function, or an element) to the DOM node it points at.
|
|
423
|
+
resolveConstraintNode() {
|
|
424
|
+
const {
|
|
425
|
+
constrain
|
|
426
|
+
} = this.options;
|
|
427
|
+
const elementNode = this.element?.node;
|
|
428
|
+
if (!elementNode) return null;
|
|
429
|
+
if (constrain === 'window') return ownerWindow(elementNode) ?? null;
|
|
430
|
+
if (constrain === 'scroll-parent') {
|
|
431
|
+
return getScrollParents(this.target?.node)[0] ?? null;
|
|
432
|
+
}
|
|
433
|
+
if (constrain === 'parent') return findDOMNode(this.container) ?? null;
|
|
434
|
+
if (typeof constrain === 'function') {
|
|
435
|
+
return findDOMNode(constrain.call(null)) ?? null;
|
|
436
|
+
}
|
|
437
|
+
if (typeof constrain === 'object' && constrain) {
|
|
438
|
+
return findDOMNode(constrain) ?? null;
|
|
439
|
+
}
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Maximum height/width (in CSS px) the positioned element can occupy in
|
|
445
|
+
* the resolved placement before crossing the constraint. Drives the
|
|
446
|
+
* `--ui-position-available-{height,width}` CSS variables
|
|
447
|
+
*/
|
|
448
|
+
get availableSpace() {
|
|
449
|
+
const targetNode = this.target?.node;
|
|
450
|
+
const constraintNode = this.resolveConstraintNode();
|
|
451
|
+
if (!this.element || !targetNode?.getBoundingClientRect || !constraintNode) {
|
|
452
|
+
return {
|
|
453
|
+
height: Infinity,
|
|
454
|
+
width: Infinity
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
const targetRect = targetNode.getBoundingClientRect();
|
|
458
|
+
let constraintRect;
|
|
459
|
+
if ('getBoundingClientRect' in constraintNode) {
|
|
460
|
+
constraintRect = constraintNode.getBoundingClientRect();
|
|
461
|
+
} else {
|
|
462
|
+
const win = 'defaultView' in constraintNode ? constraintNode.defaultView : constraintNode;
|
|
463
|
+
if (!win) return {
|
|
464
|
+
height: Infinity,
|
|
465
|
+
width: Infinity
|
|
466
|
+
};
|
|
467
|
+
constraintRect = {
|
|
468
|
+
top: 0,
|
|
469
|
+
left: 0,
|
|
470
|
+
right: win.innerWidth,
|
|
471
|
+
bottom: win.innerHeight,
|
|
472
|
+
width: win.innerWidth,
|
|
473
|
+
height: win.innerHeight
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// `offsetX` / `offsetY` always push the popover *away* from the trigger
|
|
478
|
+
// so on the primary axis they consume available space.
|
|
479
|
+
const elementNode = this.element.node;
|
|
480
|
+
const offsetY = px(this.element._offset.top, elementNode);
|
|
481
|
+
const offsetX = px(this.element._offset.left, elementNode);
|
|
482
|
+
const [primary] = this.element.placement;
|
|
483
|
+
let height;
|
|
484
|
+
if (primary === 'bottom') {
|
|
485
|
+
height = constraintRect.bottom - targetRect.bottom - offsetY;
|
|
486
|
+
} else if (primary === 'top') {
|
|
487
|
+
height = targetRect.top - constraintRect.top - offsetY;
|
|
488
|
+
} else {
|
|
489
|
+
height = constraintRect.height;
|
|
490
|
+
}
|
|
491
|
+
let width;
|
|
492
|
+
if (primary === 'end') {
|
|
493
|
+
width = constraintRect.right - targetRect.right - offsetX;
|
|
494
|
+
} else if (primary === 'start') {
|
|
495
|
+
width = targetRect.left - constraintRect.left - offsetX;
|
|
496
|
+
} else {
|
|
497
|
+
width = constraintRect.width;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Floor at 16px so a consumer's `max-height` doesn't collapse to 0 in the
|
|
501
|
+
// frame(s) before placement flips when the trigger sits right at the edge.
|
|
502
|
+
return {
|
|
503
|
+
height: Math.max(16, height),
|
|
504
|
+
width: Math.max(16, width)
|
|
505
|
+
};
|
|
506
|
+
}
|
|
421
507
|
}
|
|
422
508
|
function addOffsets(offsets) {
|
|
423
509
|
return offsets.reduce((sum, offset) => {
|
package/lib/Position/index.js
CHANGED
|
@@ -72,9 +72,13 @@ let Position = exports.Position = (_dec = (0, _withDeterministicId.withDetermini
|
|
|
72
72
|
static contentLocatorAttribute = 'data-position-content';
|
|
73
73
|
constructor(props) {
|
|
74
74
|
super(props);
|
|
75
|
+
const initial = this.calculatePosition(props);
|
|
76
|
+
this._availableHeight = initial.availableHeight;
|
|
77
|
+
this._availableWidth = initial.availableWidth;
|
|
75
78
|
this.state = {
|
|
76
79
|
positioned: false,
|
|
77
|
-
|
|
80
|
+
placement: initial.placement,
|
|
81
|
+
style: initial.style
|
|
78
82
|
};
|
|
79
83
|
this.position = (0, _debounce.debounce)(this.position, 0, {
|
|
80
84
|
leading: false,
|
|
@@ -88,6 +92,8 @@ let Position = exports.Position = (_dec = (0, _withDeterministicId.withDetermini
|
|
|
88
92
|
_listener = null;
|
|
89
93
|
_content;
|
|
90
94
|
_target;
|
|
95
|
+
_availableHeight;
|
|
96
|
+
_availableWidth;
|
|
91
97
|
handleRef = el => {
|
|
92
98
|
const {
|
|
93
99
|
elementRef
|
|
@@ -164,6 +170,22 @@ let Position = exports.Position = (_dec = (0, _withDeterministicId.withDetermini
|
|
|
164
170
|
}
|
|
165
171
|
}, 0));
|
|
166
172
|
};
|
|
173
|
+
|
|
174
|
+
// Write `--ui-position-available-{height,width}` directly on the content
|
|
175
|
+
// node's inline style
|
|
176
|
+
applyAvailableSpaceCustomProperties() {
|
|
177
|
+
const node = (0, _findDOMNode.findDOMNode)(this._content);
|
|
178
|
+
if (!node?.style) return;
|
|
179
|
+
const set = (name, value) => {
|
|
180
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
181
|
+
node.style.setProperty(name, `${value}px`);
|
|
182
|
+
} else {
|
|
183
|
+
node.style.removeProperty(name);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
set('--ui-position-available-height', this._availableHeight);
|
|
187
|
+
set('--ui-position-available-width', this._availableWidth);
|
|
188
|
+
}
|
|
167
189
|
calculatePosition(props) {
|
|
168
190
|
return (0, _calculateElementPosition.calculateElementPosition)(this._content, this._target, {
|
|
169
191
|
placement: props.placement,
|
|
@@ -175,9 +197,19 @@ let Position = exports.Position = (_dec = (0, _withDeterministicId.withDetermini
|
|
|
175
197
|
});
|
|
176
198
|
}
|
|
177
199
|
position = () => {
|
|
200
|
+
const {
|
|
201
|
+
placement,
|
|
202
|
+
style,
|
|
203
|
+
availableHeight,
|
|
204
|
+
availableWidth
|
|
205
|
+
} = this.calculatePosition(this.props);
|
|
206
|
+
this._availableHeight = availableHeight;
|
|
207
|
+
this._availableWidth = availableWidth;
|
|
208
|
+
this.applyAvailableSpaceCustomProperties();
|
|
178
209
|
this.setState({
|
|
179
210
|
positioned: true,
|
|
180
|
-
|
|
211
|
+
placement,
|
|
212
|
+
style
|
|
181
213
|
});
|
|
182
214
|
};
|
|
183
215
|
startTracking() {
|
package/lib/PositionPropTypes.js
CHANGED
|
@@ -54,4 +54,11 @@ const mirrorMap = exports.mirrorMap = {
|
|
|
54
54
|
bottom: 'top',
|
|
55
55
|
stretch: 'stretch',
|
|
56
56
|
offscreen: 'offscreen'
|
|
57
|
-
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The full output of `calculateElementPosition`, including the available
|
|
61
|
+
* space numbers driving `--ui-position-available-{height,width}`. Kept
|
|
62
|
+
* separate from `ElementPosition` so `PositionState` (which extends
|
|
63
|
+
* `ElementPosition`) doesn't falsely advertise these fields
|
|
64
|
+
*/
|
|
@@ -14,6 +14,7 @@ var _findDOMNode = require("@instructure/ui-dom-utils/lib/findDOMNode.js");
|
|
|
14
14
|
var _ownerDocument = require("@instructure/ui-dom-utils/lib/ownerDocument.js");
|
|
15
15
|
var _ownerWindow = require("@instructure/ui-dom-utils/lib/ownerWindow.js");
|
|
16
16
|
var _mirrorPlacement = require("./mirrorPlacement");
|
|
17
|
+
var _px = require("@instructure/ui-utils/lib/px.js");
|
|
17
18
|
/*
|
|
18
19
|
* The MIT License (MIT)
|
|
19
20
|
*
|
|
@@ -38,8 +39,6 @@ var _mirrorPlacement = require("./mirrorPlacement");
|
|
|
38
39
|
* SOFTWARE.
|
|
39
40
|
*/
|
|
40
41
|
|
|
41
|
-
// @ts-expect-error will be needed for fix in the `offsetToPx` method
|
|
42
|
-
|
|
43
42
|
/**
|
|
44
43
|
* ---
|
|
45
44
|
* category: utilities/position
|
|
@@ -79,9 +78,15 @@ function calculateElementPosition(element, target, options = {}) {
|
|
|
79
78
|
};
|
|
80
79
|
}
|
|
81
80
|
const pos = new PositionData(element, target, options);
|
|
81
|
+
const {
|
|
82
|
+
height: availableHeight,
|
|
83
|
+
width: availableWidth
|
|
84
|
+
} = pos.availableSpace;
|
|
82
85
|
return {
|
|
83
86
|
placement: pos.placement,
|
|
84
|
-
style: pos.style
|
|
87
|
+
style: pos.style,
|
|
88
|
+
availableHeight,
|
|
89
|
+
availableWidth
|
|
85
90
|
};
|
|
86
91
|
}
|
|
87
92
|
class PositionedElement {
|
|
@@ -227,7 +232,6 @@ class PositionData {
|
|
|
227
232
|
this.options = options || {};
|
|
228
233
|
const {
|
|
229
234
|
container,
|
|
230
|
-
constrain,
|
|
231
235
|
placement,
|
|
232
236
|
over
|
|
233
237
|
} = this.options;
|
|
@@ -238,16 +242,9 @@ class PositionData {
|
|
|
238
242
|
left: this.options.offsetX
|
|
239
243
|
});
|
|
240
244
|
this.target = new PositionedElement(target || this.container, over ? this.element.placement : this.element.mirroredPlacement);
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
this.constrainTo((0, _getScrollParents.getScrollParents)(this.target.node)[0]);
|
|
245
|
-
} else if (constrain === 'parent') {
|
|
246
|
-
this.constrainTo(this.container);
|
|
247
|
-
} else if (typeof constrain === 'function') {
|
|
248
|
-
this.constrainTo((0, _findDOMNode.findDOMNode)(constrain.call(null)));
|
|
249
|
-
} else if (typeof constrain === 'object') {
|
|
250
|
-
this.constrainTo((0, _findDOMNode.findDOMNode)(constrain));
|
|
245
|
+
const constraintNode = this.resolveConstraintNode();
|
|
246
|
+
if (constraintNode) {
|
|
247
|
+
this.constrainTo(constraintNode);
|
|
251
248
|
}
|
|
252
249
|
}
|
|
253
250
|
options;
|
|
@@ -434,6 +431,93 @@ class PositionData {
|
|
|
434
431
|
}
|
|
435
432
|
}
|
|
436
433
|
}
|
|
434
|
+
|
|
435
|
+
// Resolves the `constrain` option (`'window'`, `'scroll-parent'`,
|
|
436
|
+
// `'parent'`, a function, or an element) to the DOM node it points at.
|
|
437
|
+
resolveConstraintNode() {
|
|
438
|
+
const {
|
|
439
|
+
constrain
|
|
440
|
+
} = this.options;
|
|
441
|
+
const elementNode = this.element?.node;
|
|
442
|
+
if (!elementNode) return null;
|
|
443
|
+
if (constrain === 'window') return (0, _ownerWindow.ownerWindow)(elementNode) ?? null;
|
|
444
|
+
if (constrain === 'scroll-parent') {
|
|
445
|
+
return (0, _getScrollParents.getScrollParents)(this.target?.node)[0] ?? null;
|
|
446
|
+
}
|
|
447
|
+
if (constrain === 'parent') return (0, _findDOMNode.findDOMNode)(this.container) ?? null;
|
|
448
|
+
if (typeof constrain === 'function') {
|
|
449
|
+
return (0, _findDOMNode.findDOMNode)(constrain.call(null)) ?? null;
|
|
450
|
+
}
|
|
451
|
+
if (typeof constrain === 'object' && constrain) {
|
|
452
|
+
return (0, _findDOMNode.findDOMNode)(constrain) ?? null;
|
|
453
|
+
}
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Maximum height/width (in CSS px) the positioned element can occupy in
|
|
459
|
+
* the resolved placement before crossing the constraint. Drives the
|
|
460
|
+
* `--ui-position-available-{height,width}` CSS variables
|
|
461
|
+
*/
|
|
462
|
+
get availableSpace() {
|
|
463
|
+
const targetNode = this.target?.node;
|
|
464
|
+
const constraintNode = this.resolveConstraintNode();
|
|
465
|
+
if (!this.element || !targetNode?.getBoundingClientRect || !constraintNode) {
|
|
466
|
+
return {
|
|
467
|
+
height: Infinity,
|
|
468
|
+
width: Infinity
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
const targetRect = targetNode.getBoundingClientRect();
|
|
472
|
+
let constraintRect;
|
|
473
|
+
if ('getBoundingClientRect' in constraintNode) {
|
|
474
|
+
constraintRect = constraintNode.getBoundingClientRect();
|
|
475
|
+
} else {
|
|
476
|
+
const win = 'defaultView' in constraintNode ? constraintNode.defaultView : constraintNode;
|
|
477
|
+
if (!win) return {
|
|
478
|
+
height: Infinity,
|
|
479
|
+
width: Infinity
|
|
480
|
+
};
|
|
481
|
+
constraintRect = {
|
|
482
|
+
top: 0,
|
|
483
|
+
left: 0,
|
|
484
|
+
right: win.innerWidth,
|
|
485
|
+
bottom: win.innerHeight,
|
|
486
|
+
width: win.innerWidth,
|
|
487
|
+
height: win.innerHeight
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// `offsetX` / `offsetY` always push the popover *away* from the trigger
|
|
492
|
+
// so on the primary axis they consume available space.
|
|
493
|
+
const elementNode = this.element.node;
|
|
494
|
+
const offsetY = (0, _px.px)(this.element._offset.top, elementNode);
|
|
495
|
+
const offsetX = (0, _px.px)(this.element._offset.left, elementNode);
|
|
496
|
+
const [primary] = this.element.placement;
|
|
497
|
+
let height;
|
|
498
|
+
if (primary === 'bottom') {
|
|
499
|
+
height = constraintRect.bottom - targetRect.bottom - offsetY;
|
|
500
|
+
} else if (primary === 'top') {
|
|
501
|
+
height = targetRect.top - constraintRect.top - offsetY;
|
|
502
|
+
} else {
|
|
503
|
+
height = constraintRect.height;
|
|
504
|
+
}
|
|
505
|
+
let width;
|
|
506
|
+
if (primary === 'end') {
|
|
507
|
+
width = constraintRect.right - targetRect.right - offsetX;
|
|
508
|
+
} else if (primary === 'start') {
|
|
509
|
+
width = targetRect.left - constraintRect.left - offsetX;
|
|
510
|
+
} else {
|
|
511
|
+
width = constraintRect.width;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Floor at 16px so a consumer's `max-height` doesn't collapse to 0 in the
|
|
515
|
+
// frame(s) before placement flips when the trigger sits right at the edge.
|
|
516
|
+
return {
|
|
517
|
+
height: Math.max(16, height),
|
|
518
|
+
width: Math.max(16, width)
|
|
519
|
+
};
|
|
520
|
+
}
|
|
437
521
|
}
|
|
438
522
|
function addOffsets(offsets) {
|
|
439
523
|
return offsets.reduce((sum, offset) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instructure/ui-position",
|
|
3
|
-
"version": "11.7.4-snapshot-
|
|
3
|
+
"version": "11.7.4-snapshot-8",
|
|
4
4
|
"description": "A component for positioning content with respect to a designated target.",
|
|
5
5
|
"author": "Instructure, Inc. Engineering and Product Design",
|
|
6
6
|
"module": "./es/index.js",
|
|
@@ -15,22 +15,22 @@
|
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@babel/runtime": "^7.29.2",
|
|
18
|
-
"@instructure/
|
|
19
|
-
"@instructure/
|
|
20
|
-
"@instructure/
|
|
21
|
-
"@instructure/ui-
|
|
22
|
-
"@instructure/ui-
|
|
23
|
-
"@instructure/ui-
|
|
24
|
-
"@instructure/ui-
|
|
25
|
-
"@instructure/uid": "11.7.4-snapshot-
|
|
26
|
-
"@instructure/ui-
|
|
18
|
+
"@instructure/shared-types": "11.7.4-snapshot-8",
|
|
19
|
+
"@instructure/debounce": "11.7.4-snapshot-8",
|
|
20
|
+
"@instructure/emotion": "11.7.4-snapshot-8",
|
|
21
|
+
"@instructure/ui-portal": "11.7.4-snapshot-8",
|
|
22
|
+
"@instructure/ui-dom-utils": "11.7.4-snapshot-8",
|
|
23
|
+
"@instructure/ui-utils": "11.7.4-snapshot-8",
|
|
24
|
+
"@instructure/ui-react-utils": "11.7.4-snapshot-8",
|
|
25
|
+
"@instructure/uid": "11.7.4-snapshot-8",
|
|
26
|
+
"@instructure/ui-themes": "11.7.4-snapshot-8"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@testing-library/jest-dom": "^6.6.3",
|
|
30
30
|
"@testing-library/react": "15.0.7",
|
|
31
31
|
"vitest": "^3.2.2",
|
|
32
|
-
"@instructure/ui-babel-preset": "11.7.4-snapshot-
|
|
33
|
-
"@instructure/ui-color-utils": "11.7.4-snapshot-
|
|
32
|
+
"@instructure/ui-babel-preset": "11.7.4-snapshot-8",
|
|
33
|
+
"@instructure/ui-color-utils": "11.7.4-snapshot-8"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"react": ">=18 <=19"
|
package/src/Position/index.tsx
CHANGED
|
@@ -80,9 +80,13 @@ class Position extends Component<PositionProps, PositionState> {
|
|
|
80
80
|
constructor(props: PositionProps) {
|
|
81
81
|
super(props)
|
|
82
82
|
|
|
83
|
+
const initial = this.calculatePosition(props)
|
|
84
|
+
this._availableHeight = initial.availableHeight
|
|
85
|
+
this._availableWidth = initial.availableWidth
|
|
83
86
|
this.state = {
|
|
84
87
|
positioned: false,
|
|
85
|
-
|
|
88
|
+
placement: initial.placement,
|
|
89
|
+
style: initial.style
|
|
86
90
|
}
|
|
87
91
|
this.position = debounce(this.position, 0, {
|
|
88
92
|
leading: false,
|
|
@@ -98,6 +102,8 @@ class Position extends Component<PositionProps, PositionState> {
|
|
|
98
102
|
_listener: PositionChangeListenerType | null = null
|
|
99
103
|
_content?: PositionElement
|
|
100
104
|
_target?: PositionElement
|
|
105
|
+
_availableHeight?: number
|
|
106
|
+
_availableWidth?: number
|
|
101
107
|
|
|
102
108
|
handleRef = (el: Element | null) => {
|
|
103
109
|
const { elementRef } = this.props
|
|
@@ -220,6 +226,22 @@ class Position extends Component<PositionProps, PositionState> {
|
|
|
220
226
|
)
|
|
221
227
|
}
|
|
222
228
|
|
|
229
|
+
// Write `--ui-position-available-{height,width}` directly on the content
|
|
230
|
+
// node's inline style
|
|
231
|
+
applyAvailableSpaceCustomProperties() {
|
|
232
|
+
const node = findDOMNode(this._content) as HTMLElement | null
|
|
233
|
+
if (!node?.style) return
|
|
234
|
+
const set = (name: string, value: number | undefined) => {
|
|
235
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
236
|
+
node.style.setProperty(name, `${value}px`)
|
|
237
|
+
} else {
|
|
238
|
+
node.style.removeProperty(name)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
set('--ui-position-available-height', this._availableHeight)
|
|
242
|
+
set('--ui-position-available-width', this._availableWidth)
|
|
243
|
+
}
|
|
244
|
+
|
|
223
245
|
calculatePosition(props: PositionProps) {
|
|
224
246
|
return calculateElementPosition(this._content, this._target, {
|
|
225
247
|
placement: props.placement,
|
|
@@ -232,10 +254,12 @@ class Position extends Component<PositionProps, PositionState> {
|
|
|
232
254
|
}
|
|
233
255
|
|
|
234
256
|
position = () => {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
257
|
+
const { placement, style, availableHeight, availableWidth } =
|
|
258
|
+
this.calculatePosition(this.props)
|
|
259
|
+
this._availableHeight = availableHeight
|
|
260
|
+
this._availableWidth = availableWidth
|
|
261
|
+
this.applyAvailableSpaceCustomProperties()
|
|
262
|
+
this.setState({ positioned: true, placement, style })
|
|
239
263
|
}
|
|
240
264
|
|
|
241
265
|
startTracking() {
|
package/src/PositionPropTypes.ts
CHANGED
|
@@ -135,6 +135,17 @@ export type ElementPosition = {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
/**
|
|
139
|
+
* The full output of `calculateElementPosition`, including the available
|
|
140
|
+
* space numbers driving `--ui-position-available-{height,width}`. Kept
|
|
141
|
+
* separate from `ElementPosition` so `PositionState` (which extends
|
|
142
|
+
* `ElementPosition`) doesn't falsely advertise these fields
|
|
143
|
+
*/
|
|
144
|
+
export type ElementPositionWithAvailableSpace = ElementPosition & {
|
|
145
|
+
availableHeight?: number
|
|
146
|
+
availableWidth?: number
|
|
147
|
+
}
|
|
148
|
+
|
|
138
149
|
export type PositionElement = UIElement
|
|
139
150
|
|
|
140
151
|
export type Offset<Type extends number | string | undefined = number> = {
|