@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.
@@ -33,7 +33,6 @@ import {
33
33
  } from '@instructure/ui-dom-utils'
34
34
  import type { RectType } from '@instructure/ui-dom-utils'
35
35
  import { mirrorPlacement } from './mirrorPlacement'
36
- // @ts-expect-error will be needed for fix in the `offsetToPx` method
37
36
  import { px } from '@instructure/ui-utils'
38
37
 
39
38
  import type {
@@ -42,6 +41,7 @@ import type {
42
41
  PositionConstraint,
43
42
  PositionMountNode,
44
43
  ElementPosition,
44
+ ElementPositionWithAvailableSpace,
45
45
  PositionElement,
46
46
  Size,
47
47
  Overflow,
@@ -95,7 +95,7 @@ function calculateElementPosition(
95
95
  element?: PositionElement,
96
96
  target?: PositionElement,
97
97
  options: Options = {}
98
- ): ElementPosition {
98
+ ): ElementPositionWithAvailableSpace {
99
99
  if (!element || options.placement === 'offscreen') {
100
100
  // hide offscreen content at the bottom of the DOM from screenreaders
101
101
  // unless content is contained somewhere else
@@ -113,10 +113,13 @@ function calculateElementPosition(
113
113
  }
114
114
 
115
115
  const pos = new PositionData(element, target, options)
116
+ const { height: availableHeight, width: availableWidth } = pos.availableSpace
116
117
 
117
118
  return {
118
119
  placement: pos.placement,
119
- style: pos.style
120
+ style: pos.style,
121
+ availableHeight,
122
+ availableWidth
120
123
  }
121
124
  }
122
125
 
@@ -304,7 +307,7 @@ class PositionData {
304
307
  ) {
305
308
  this.options = options || {}
306
309
 
307
- const { container, constrain, placement, over } = this.options
310
+ const { container, placement, over } = this.options
308
311
 
309
312
  if (!element || placement === 'offscreen') return
310
313
 
@@ -320,16 +323,9 @@ class PositionData {
320
323
  over ? this.element.placement : this.element.mirroredPlacement
321
324
  )
322
325
 
323
- if (constrain === 'window') {
324
- this.constrainTo(ownerWindow(element))
325
- } else if (constrain === 'scroll-parent') {
326
- this.constrainTo(getScrollParents(this.target.node)[0])
327
- } else if (constrain === 'parent') {
328
- this.constrainTo(this.container)
329
- } else if (typeof constrain === 'function') {
330
- this.constrainTo(findDOMNode(constrain.call(null)))
331
- } else if (typeof constrain === 'object') {
332
- this.constrainTo(findDOMNode(constrain))
326
+ const constraintNode = this.resolveConstraintNode()
327
+ if (constraintNode) {
328
+ this.constrainTo(constraintNode)
333
329
  }
334
330
  }
335
331
 
@@ -543,6 +539,96 @@ class PositionData {
543
539
  }
544
540
  }
545
541
  }
542
+
543
+ // Resolves the `constrain` option (`'window'`, `'scroll-parent'`,
544
+ // `'parent'`, a function, or an element) to the DOM node it points at.
545
+ resolveConstraintNode(): Node | Window | null {
546
+ const { constrain } = this.options
547
+ const elementNode = this.element?.node
548
+ if (!elementNode) return null
549
+
550
+ if (constrain === 'window') return ownerWindow(elementNode) ?? null
551
+ if (constrain === 'scroll-parent') {
552
+ return getScrollParents(this.target?.node)[0] ?? null
553
+ }
554
+ if (constrain === 'parent') return findDOMNode(this.container) ?? null
555
+ if (typeof constrain === 'function') {
556
+ return findDOMNode(constrain.call(null)) ?? null
557
+ }
558
+ if (typeof constrain === 'object' && constrain) {
559
+ return findDOMNode(constrain) ?? null
560
+ }
561
+ return null
562
+ }
563
+
564
+ /**
565
+ * Maximum height/width (in CSS px) the positioned element can occupy in
566
+ * the resolved placement before crossing the constraint. Drives the
567
+ * `--ui-position-available-{height,width}` CSS variables
568
+ */
569
+ get availableSpace(): { height: number; width: number } {
570
+ const targetNode = this.target?.node as Element | undefined
571
+ const constraintNode = this.resolveConstraintNode()
572
+ if (
573
+ !this.element ||
574
+ !targetNode?.getBoundingClientRect ||
575
+ !constraintNode
576
+ ) {
577
+ return { height: Infinity, width: Infinity }
578
+ }
579
+
580
+ const targetRect = targetNode.getBoundingClientRect()
581
+ let constraintRect: RectType
582
+ if ('getBoundingClientRect' in constraintNode) {
583
+ constraintRect = (constraintNode as Element).getBoundingClientRect()
584
+ } else {
585
+ const win =
586
+ 'defaultView' in constraintNode
587
+ ? (constraintNode as Document).defaultView
588
+ : (constraintNode as Window)
589
+ if (!win) return { height: Infinity, width: Infinity }
590
+ constraintRect = {
591
+ top: 0,
592
+ left: 0,
593
+ right: win.innerWidth,
594
+ bottom: win.innerHeight,
595
+ width: win.innerWidth,
596
+ height: win.innerHeight
597
+ }
598
+ }
599
+
600
+ // `offsetX` / `offsetY` always push the popover *away* from the trigger
601
+ // so on the primary axis they consume available space.
602
+ const elementNode = this.element.node
603
+ const offsetY = px(this.element._offset.top, elementNode)
604
+ const offsetX = px(this.element._offset.left, elementNode)
605
+ const [primary] = this.element.placement
606
+
607
+ let height: number
608
+ if (primary === 'bottom') {
609
+ height = constraintRect.bottom - targetRect.bottom - offsetY
610
+ } else if (primary === 'top') {
611
+ height = targetRect.top - constraintRect.top - offsetY
612
+ } else {
613
+ height = constraintRect.height
614
+ }
615
+
616
+ let width: number
617
+ if (primary === 'end') {
618
+ width = constraintRect.right - targetRect.right - offsetX
619
+ } else if (primary === 'start') {
620
+ width = targetRect.left - constraintRect.left - offsetX
621
+ } else {
622
+ width = constraintRect.width
623
+ }
624
+
625
+ // Floor at 16px so a consumer's `max-height` doesn't collapse to 0 in the
626
+ // frame(s) before placement flips when the trigger sits right at the edge.
627
+ return {
628
+ height: Math.max(16, height),
629
+ width: Math.max(16, width)
630
+ }
631
+ }
546
632
  }
547
633
 
548
634
  function addOffsets(offsets: Offset[]) {