@atlaskit/top-layer 0.5.0 → 0.6.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cjs/animations/presets.js +2 -2
  3. package/dist/cjs/arrow/index.js +6 -6
  4. package/dist/cjs/entry-points/open-layer-observer.js +33 -0
  5. package/dist/cjs/entry-points/use-anchor-position.js +2 -2
  6. package/dist/cjs/internal/anchor-positioning-fallback.js +1 -1
  7. package/dist/cjs/internal/{use-anchor-positioning.js → use-anchor-position.js} +46 -17
  8. package/dist/cjs/popup/popup-content.js +2 -2
  9. package/dist/cjs/testing/polyfill.js +2 -2
  10. package/dist/es2019/animations/presets.js +1 -1
  11. package/dist/es2019/arrow/index.js +1 -1
  12. package/dist/es2019/entry-points/open-layer-observer.js +4 -0
  13. package/dist/es2019/entry-points/use-anchor-position.js +1 -1
  14. package/dist/es2019/internal/anchor-positioning-fallback.js +1 -1
  15. package/dist/es2019/internal/{use-anchor-positioning.js → use-anchor-position.js} +46 -18
  16. package/dist/es2019/popup/popup-content.js +1 -1
  17. package/dist/es2019/testing/polyfill.js +2 -2
  18. package/dist/esm/animations/presets.js +1 -1
  19. package/dist/esm/arrow/index.js +1 -1
  20. package/dist/esm/entry-points/open-layer-observer.js +4 -0
  21. package/dist/esm/entry-points/use-anchor-position.js +1 -1
  22. package/dist/esm/internal/anchor-positioning-fallback.js +1 -1
  23. package/dist/esm/internal/{use-anchor-positioning.js → use-anchor-position.js} +47 -18
  24. package/dist/esm/popup/popup-content.js +1 -1
  25. package/dist/esm/testing/polyfill.js +2 -2
  26. package/dist/types/entry-points/open-layer-observer.d.ts +5 -0
  27. package/dist/types/entry-points/use-anchor-position.d.ts +1 -1
  28. package/dist/{types-ts4.5/internal/use-anchor-positioning.d.ts → types/internal/use-anchor-position.d.ts} +20 -1
  29. package/dist/types/testing/polyfill.d.ts +1 -1
  30. package/dist/types-ts4.5/entry-points/open-layer-observer.d.ts +5 -0
  31. package/dist/types-ts4.5/entry-points/use-anchor-position.d.ts +1 -1
  32. package/dist/{types/internal/use-anchor-positioning.d.ts → types-ts4.5/internal/use-anchor-position.d.ts} +20 -1
  33. package/dist/types-ts4.5/testing/polyfill.d.ts +1 -1
  34. package/open-layer-observer/package.json +17 -0
  35. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @atlaskit/top-layer
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`a75acf807a927`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/a75acf807a927) -
8
+ The public APIs for the Open Layer Observer from @atlaskit/layering have been re-exported from the
9
+ @atlaskit/top-layer package. The long term vision is for all layering concerns to come from the
10
+ top-layer package.
11
+
12
+ ## 0.5.1
13
+
14
+ ### Patch Changes
15
+
16
+ - [`693551fc3e8c4`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/693551fc3e8c4) -
17
+ useAnchorPosition no longer re-runs its positioning effect when a fresh placement object reference
18
+ with the same resolved shape is passed (eg an inline literal on every render). The hook
19
+ structurally compares the resolved placement and reuses the previous reference when the shape is
20
+ unchanged.
21
+
3
22
  ## 0.5.0
4
23
 
5
24
  ### Minor Changes
@@ -8,7 +8,7 @@ exports.dialogSlideUpAndFade = dialogSlideUpAndFade;
8
8
  exports.fade = fade;
9
9
  exports.scaleAndFade = scaleAndFade;
10
10
  exports.slideAndFade = slideAndFade;
11
- var _useAnchorPositioning = require("../internal/use-anchor-positioning");
11
+ var _useAnchorPosition = require("../internal/use-anchor-position");
12
12
  // ── slideAndFade ──
13
13
 
14
14
  function getSlideAndFadeProperties(_ref) {
@@ -16,7 +16,7 @@ function getSlideAndFadeProperties(_ref) {
16
16
  distance = _ref.distance;
17
17
  var pos = "".concat(distance, "px");
18
18
  var neg = "-".concat(distance, "px");
19
- var _getPlacement = (0, _useAnchorPositioning.getPlacement)({
19
+ var _getPlacement = (0, _useAnchorPosition.getPlacement)({
20
20
  placement: placement
21
21
  }),
22
22
  axis = _getPlacement.axis,
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.arrow = void 0;
8
8
  var _once = _interopRequireDefault(require("@atlaskit/ds-lib/once"));
9
- var _useAnchorPositioning = require("../internal/use-anchor-positioning");
9
+ var _useAnchorPosition = require("../internal/use-anchor-position");
10
10
  /**
11
11
  * CSS for popover arrows using the `clip-path: inset() margin-box` technique.
12
12
  *
@@ -51,7 +51,7 @@ function positionAreaToRuleName(_ref) {
51
51
  */
52
52
  function getTryFallbacks(_ref2) {
53
53
  var placement = _ref2.placement;
54
- var _getPlacement = (0, _useAnchorPositioning.getPlacement)({
54
+ var _getPlacement = (0, _useAnchorPosition.getPlacement)({
55
55
  placement: placement
56
56
  }),
57
57
  axis = _getPlacement.axis,
@@ -61,7 +61,7 @@ function getTryFallbacks(_ref2) {
61
61
  // Single-axis: only flip the edge
62
62
  var _flippedEdge = edge === 'start' ? 'end' : 'start';
63
63
  return positionAreaToRuleName({
64
- positionArea: (0, _useAnchorPositioning.placementToPositionArea)({
64
+ positionArea: (0, _useAnchorPosition.placementToPositionArea)({
65
65
  placement: {
66
66
  axis: axis,
67
67
  edge: _flippedEdge
@@ -74,7 +74,7 @@ function getTryFallbacks(_ref2) {
74
74
  var flippedEdge = edge === 'start' ? 'end' : 'start';
75
75
  var flippedAlign = align === 'start' ? 'end' : 'start';
76
76
  return [positionAreaToRuleName({
77
- positionArea: (0, _useAnchorPositioning.placementToPositionArea)({
77
+ positionArea: (0, _useAnchorPosition.placementToPositionArea)({
78
78
  placement: {
79
79
  axis: axis,
80
80
  edge: flippedEdge,
@@ -82,7 +82,7 @@ function getTryFallbacks(_ref2) {
82
82
  }
83
83
  })
84
84
  }), positionAreaToRuleName({
85
- positionArea: (0, _useAnchorPositioning.placementToPositionArea)({
85
+ positionArea: (0, _useAnchorPosition.placementToPositionArea)({
86
86
  placement: {
87
87
  axis: axis,
88
88
  edge: edge,
@@ -90,7 +90,7 @@ function getTryFallbacks(_ref2) {
90
90
  }
91
91
  })
92
92
  }), positionAreaToRuleName({
93
- positionArea: (0, _useAnchorPositioning.placementToPositionArea)({
93
+ positionArea: (0, _useAnchorPosition.placementToPositionArea)({
94
94
  placement: {
95
95
  axis: axis,
96
96
  edge: flippedEdge,
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "OpenLayerObserver", {
7
+ enumerable: true,
8
+ get: function get() {
9
+ return _openLayerObserver.OpenLayerObserver;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "OpenLayerObserverNamespaceProvider", {
13
+ enumerable: true,
14
+ get: function get() {
15
+ return _openLayerObserverNamespaceProvider.OpenLayerObserverNamespaceProvider;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "useNotifyOpenLayerObserver", {
19
+ enumerable: true,
20
+ get: function get() {
21
+ return _useNotifyOpenLayerObserver.useNotifyOpenLayerObserver;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "useOpenLayerObserver", {
25
+ enumerable: true,
26
+ get: function get() {
27
+ return _useOpenLayerObserver.useOpenLayerObserver;
28
+ }
29
+ });
30
+ var _openLayerObserver = require("@atlaskit/layering/experimental/open-layer-observer/open-layer-observer");
31
+ var _useNotifyOpenLayerObserver = require("@atlaskit/layering/experimental/use-notify-open-layer-observer");
32
+ var _useOpenLayerObserver = require("@atlaskit/layering/experimental/use-open-layer-observer");
33
+ var _openLayerObserverNamespaceProvider = require("@atlaskit/layering/experimental/open-layer-observer-namespace-provider");
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  Object.defineProperty(exports, "useAnchorPosition", {
7
7
  enumerable: true,
8
8
  get: function get() {
9
- return _useAnchorPositioning.useAnchorPosition;
9
+ return _useAnchorPosition.useAnchorPosition;
10
10
  }
11
11
  });
12
- var _useAnchorPositioning = require("../internal/use-anchor-positioning");
12
+ var _useAnchorPosition = require("../internal/use-anchor-position");
@@ -131,7 +131,7 @@ function applyAlignment(_ref2) {
131
131
  * Applies the cross-axis shift offset.
132
132
  *
133
133
  * The sign mirrors the CSS path (see `crossAxisShiftMargin` in
134
- * `use-anchor-positioning.tsx`):
134
+ * `use-anchor-position.tsx`):
135
135
  * - `align: 'start' | 'center'` + `forwards` → push toward end (positive)
136
136
  * - `align: 'end'` + `forwards` → push toward start (negative)
137
137
  * - `backwards` flips the sign in both cases.
@@ -210,6 +210,30 @@ function crossAxisShiftMargin(_ref4) {
210
210
  };
211
211
  }
212
212
 
213
+ /**
214
+ * Returns a referentially-stable, fully-resolved `TPlacement`: the same
215
+ * reference is preserved across renders as long as the resolved shape
216
+ * does not change.
217
+ */
218
+ function useStablePlacement(placement) {
219
+ var resolved = (0, _resolvePlacement.getPlacement)({
220
+ placement: placement
221
+ });
222
+ var stableRef = (0, _react.useRef)(resolved);
223
+ if (!isResolvedPlacementEqual(stableRef.current, resolved)) {
224
+ stableRef.current = resolved;
225
+ }
226
+ return stableRef.current;
227
+ }
228
+
229
+ /**
230
+ * Shallow-equals two fully-resolved placements. Avoids `JSON.stringify` so
231
+ * the comparison cost stays proportional to the number of fields.
232
+ */
233
+ function isResolvedPlacementEqual(a, b) {
234
+ return a.axis === b.axis && a.edge === b.edge && a.align === b.align && a.offset.gap === b.offset.gap && a.offset.crossAxisShift.value === b.offset.crossAxisShift.value && a.offset.crossAxisShift.direction === b.offset.crossAxisShift.direction;
235
+ }
236
+
213
237
  /**
214
238
  * Hook that positions an element relative to an anchor element using
215
239
  * CSS Anchor Positioning (with a JS fallback for older browsers).
@@ -225,39 +249,44 @@ function crossAxisShiftMargin(_ref4) {
225
249
  * When not supported, it falls back to JavaScript-based positioning using
226
250
  * `position: fixed` with measured coordinates, re-running on scroll (capture)
227
251
  * and resize events.
252
+ *
253
+ * `placement` may be a fresh inline object on every render: a structural
254
+ * comparison is used internally so re-runs are skipped when the resolved
255
+ * placement shape is unchanged.
228
256
  */
229
257
  function useAnchorPosition(_ref5) {
230
258
  var anchorRef = _ref5.anchorRef,
231
259
  popoverRef = _ref5.popoverRef,
232
- placement = _ref5.placement,
260
+ _ref5$placement = _ref5.placement,
261
+ placement = _ref5$placement === void 0 ? {} : _ref5$placement,
233
262
  _ref5$forceFallbackPo = _ref5.forceFallbackPositioning,
234
263
  forceFallbackPositioning = _ref5$forceFallbackPo === void 0 ? false : _ref5$forceFallbackPo,
235
264
  arrow = _ref5.arrow;
236
265
  var id = (0, _react.useId)();
266
+ // Stabilize `placement` object so the same object literal
267
+ // value returns an object with the same reference
268
+ var stablePlacement = useStablePlacement(placement);
237
269
  (0, _react.useLayoutEffect)(function () {
238
270
  var trigger = anchorRef.current;
239
271
  var popover = popoverRef.current;
240
272
  if (!trigger || !popover) {
241
273
  return;
242
274
  }
243
- var resolvedPlacement = (0, _resolvePlacement.getPlacement)({
244
- placement: placement
245
- });
246
- var gapCssValue = resolvedPlacement.offset.gap;
247
- var crossAxisShiftCssValue = resolvedPlacement.offset.crossAxisShift.value;
275
+ var gapCssValue = stablePlacement.offset.gap;
276
+ var crossAxisShiftCssValue = stablePlacement.offset.crossAxisShift.value;
248
277
  var shouldForceFallback = Boolean(forceFallbackPositioning);
249
278
  if (supportsAnchorPositioning() && !shouldForceFallback) {
250
279
  // Reuse an existing `anchor-name` if the element already has one.
251
280
  // Another popover might already be anchored to the old name.
252
281
  var anchorName = trigger.style.getPropertyValue('anchor-name') || "--anchor-".concat(id.replace(/:/g, ''));
253
282
  var gap = edgeMargin({
254
- placement: resolvedPlacement,
283
+ placement: stablePlacement,
255
284
  offset: gapCssValue
256
285
  });
257
286
  var crossAxisShift = crossAxisShiftMargin({
258
- placement: resolvedPlacement,
287
+ placement: stablePlacement,
259
288
  crossAxisShiftCssValue: crossAxisShiftCssValue,
260
- direction: resolvedPlacement.offset.crossAxisShift.direction
289
+ direction: stablePlacement.offset.crossAxisShift.direction
261
290
  });
262
291
  var popoverStyles = [{
263
292
  property: 'position-anchor',
@@ -265,14 +294,14 @@ function useAnchorPosition(_ref5) {
265
294
  }, {
266
295
  property: 'position-area',
267
296
  value: placementToPositionArea({
268
- placement: resolvedPlacement
297
+ placement: stablePlacement
269
298
  })
270
299
  }, {
271
300
  property: 'position-try-fallbacks',
272
301
  value: arrow ? arrow.getTryFallbacks({
273
- placement: resolvedPlacement
302
+ placement: stablePlacement
274
303
  }) : placementToTryFallbacks({
275
- placement: resolvedPlacement
304
+ placement: stablePlacement
276
305
  })
277
306
  },
278
307
  // Reset browser default popover positioning that conflicts
@@ -316,9 +345,9 @@ function useAnchorPosition(_ref5) {
316
345
  value: '0px'
317
346
  });
318
347
  var crossAxisShiftActive = crossAxisShiftMargin({
319
- placement: resolvedPlacement,
348
+ placement: stablePlacement,
320
349
  crossAxisShiftCssValue: crossAxisShiftCssValue,
321
- direction: resolvedPlacement.offset.crossAxisShift.direction
350
+ direction: stablePlacement.offset.crossAxisShift.direction
322
351
  });
323
352
  // Map margin property to its corresponding custom property name.
324
353
  var customPropertyByMarginProperty = {
@@ -418,12 +447,12 @@ function useAnchorPosition(_ref5) {
418
447
  var _computeFallbackPosit = (0, _anchorPositioningFallback.computeFallbackPosition)({
419
448
  triggerRect: triggerRect,
420
449
  popoverEl: popover,
421
- placement: resolvedPlacement,
450
+ placement: stablePlacement,
422
451
  viewport: viewport,
423
452
  gap: gapPx,
424
453
  crossAxisShift: {
425
454
  value: crossAxisShiftPx,
426
- direction: resolvedPlacement.offset.crossAxisShift.direction
455
+ direction: stablePlacement.offset.crossAxisShift.direction
427
456
  }
428
457
  }),
429
458
  top = _computeFallbackPosit.top,
@@ -498,5 +527,5 @@ function useAnchorPosition(_ref5) {
498
527
  popover.style.removeProperty('opacity');
499
528
  });
500
529
  return undoPositioning;
501
- }, [anchorRef, popoverRef, placement, forceFallbackPositioning, arrow, id]);
530
+ }, [anchorRef, popoverRef, stablePlacement, forceFallbackPositioning, arrow, id]);
502
531
  }
@@ -12,7 +12,7 @@ var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")
12
12
  var _react = _interopRequireWildcard(require("react"));
13
13
  var _mergeRefs = _interopRequireDefault(require("@atlaskit/ds-lib/merge-refs"));
14
14
  var _roleTypes = require("../internal/role-types");
15
- var _useAnchorPositioning = require("../internal/use-anchor-positioning");
15
+ var _useAnchorPosition = require("../internal/use-anchor-position");
16
16
  var _usePresetStyles = require("../internal/use-preset-styles");
17
17
  var _popover = require("../popover/popover");
18
18
  var _popupContext = require("./popup-context");
@@ -131,7 +131,7 @@ var PopupContent = exports.PopupContent = /*#__PURE__*/(0, _react.forwardRef)(fu
131
131
  var arrowPreset = (0, _usePresetStyles.usePresetStyles)({
132
132
  preset: arrow
133
133
  });
134
- (0, _useAnchorPositioning.useAnchorPosition)({
134
+ (0, _useAnchorPosition.useAnchorPosition)({
135
135
  anchorRef: triggerRef !== null && triggerRef !== void 0 ? triggerRef : {
136
136
  current: null
137
137
  },
@@ -17,7 +17,7 @@ var _bindEventListener = require("bind-event-listener");
17
17
  * - Auto popovers: maintain an open stack, perform light-dismiss on document mousedown
18
18
  * outside the open chain, and dismiss the topmost on Escape.
19
19
  * - On open, removes any inline `opacity: 0` from the popover element so callers that
20
- * rely on a `ResizeObserver` reveal callback (e.g. `use-anchor-positioning`) become
20
+ * rely on a `ResizeObserver` reveal callback (e.g. `use-anchor-position`) become
21
21
  * visible in jsdom, where layout-driven RO callbacks never fire.
22
22
  *
23
23
  * Companion `toBeVisible` patch lives at `./to-be-visible`.
@@ -251,7 +251,7 @@ function stackForMode(_ref9) {
251
251
 
252
252
  // Polyfill-only. Real browsers reveal the popover once `ResizeObserver`
253
253
  // callbacks fire after the first layout pass. jsdom has no layout, so we clear
254
- // any inline `opacity: 0` (set by callers like `use-anchor-positioning`) once
254
+ // any inline `opacity: 0` (set by callers like `use-anchor-position`) once
255
255
  // the open toggle has fired.
256
256
  function clearOpacityOnOpenToggle(_ref0) {
257
257
  var element = _ref0.element;
@@ -1,4 +1,4 @@
1
- import { getPlacement } from '../internal/use-anchor-positioning';
1
+ import { getPlacement } from '../internal/use-anchor-position';
2
2
 
3
3
  // ── slideAndFade ──
4
4
 
@@ -1,5 +1,5 @@
1
1
  import once from '@atlaskit/ds-lib/once';
2
- import { getPlacement, placementToPositionArea } from '../internal/use-anchor-positioning';
2
+ import { getPlacement, placementToPositionArea } from '../internal/use-anchor-position';
3
3
  /**
4
4
  * CSS for popover arrows using the `clip-path: inset() margin-box` technique.
5
5
  *
@@ -0,0 +1,4 @@
1
+ export { OpenLayerObserver } from '@atlaskit/layering/experimental/open-layer-observer/open-layer-observer';
2
+ export { useNotifyOpenLayerObserver } from '@atlaskit/layering/experimental/use-notify-open-layer-observer';
3
+ export { useOpenLayerObserver } from '@atlaskit/layering/experimental/use-open-layer-observer';
4
+ export { OpenLayerObserverNamespaceProvider } from '@atlaskit/layering/experimental/open-layer-observer-namespace-provider';
@@ -1 +1 @@
1
- export { useAnchorPosition } from '../internal/use-anchor-positioning';
1
+ export { useAnchorPosition } from '../internal/use-anchor-position';
@@ -127,7 +127,7 @@ function applyAlignment({
127
127
  * Applies the cross-axis shift offset.
128
128
  *
129
129
  * The sign mirrors the CSS path (see `crossAxisShiftMargin` in
130
- * `use-anchor-positioning.tsx`):
130
+ * `use-anchor-position.tsx`):
131
131
  * - `align: 'start' | 'center'` + `forwards` → push toward end (positive)
132
132
  * - `align: 'end'` + `forwards` → push toward start (negative)
133
133
  * - `backwards` flips the sign in both cases.
@@ -12,7 +12,7 @@
12
12
  * popover with `opacity: 0` until the first measurement completes, so it
13
13
  * is never painted at the wrong location.
14
14
  */
15
- import { useId, useLayoutEffect } from 'react';
15
+ import { useId, useLayoutEffect, useRef } from 'react';
16
16
  import { bind } from 'bind-event-listener';
17
17
  import rafSchedule from 'raf-schd';
18
18
  import once from '@atlaskit/ds-lib/once';
@@ -206,6 +206,30 @@ function crossAxisShiftMargin({
206
206
  };
207
207
  }
208
208
 
209
+ /**
210
+ * Returns a referentially-stable, fully-resolved `TPlacement`: the same
211
+ * reference is preserved across renders as long as the resolved shape
212
+ * does not change.
213
+ */
214
+ function useStablePlacement(placement) {
215
+ const resolved = getPlacement({
216
+ placement
217
+ });
218
+ const stableRef = useRef(resolved);
219
+ if (!isResolvedPlacementEqual(stableRef.current, resolved)) {
220
+ stableRef.current = resolved;
221
+ }
222
+ return stableRef.current;
223
+ }
224
+
225
+ /**
226
+ * Shallow-equals two fully-resolved placements. Avoids `JSON.stringify` so
227
+ * the comparison cost stays proportional to the number of fields.
228
+ */
229
+ function isResolvedPlacementEqual(a, b) {
230
+ return a.axis === b.axis && a.edge === b.edge && a.align === b.align && a.offset.gap === b.offset.gap && a.offset.crossAxisShift.value === b.offset.crossAxisShift.value && a.offset.crossAxisShift.direction === b.offset.crossAxisShift.direction;
231
+ }
232
+
209
233
  /**
210
234
  * Hook that positions an element relative to an anchor element using
211
235
  * CSS Anchor Positioning (with a JS fallback for older browsers).
@@ -221,39 +245,43 @@ function crossAxisShiftMargin({
221
245
  * When not supported, it falls back to JavaScript-based positioning using
222
246
  * `position: fixed` with measured coordinates, re-running on scroll (capture)
223
247
  * and resize events.
248
+ *
249
+ * `placement` may be a fresh inline object on every render: a structural
250
+ * comparison is used internally so re-runs are skipped when the resolved
251
+ * placement shape is unchanged.
224
252
  */
225
253
  export function useAnchorPosition({
226
254
  anchorRef,
227
255
  popoverRef,
228
- placement,
256
+ placement = {},
229
257
  forceFallbackPositioning = false,
230
258
  arrow
231
259
  }) {
232
260
  const id = useId();
261
+ // Stabilize `placement` object so the same object literal
262
+ // value returns an object with the same reference
263
+ const stablePlacement = useStablePlacement(placement);
233
264
  useLayoutEffect(() => {
234
265
  const trigger = anchorRef.current;
235
266
  const popover = popoverRef.current;
236
267
  if (!trigger || !popover) {
237
268
  return;
238
269
  }
239
- const resolvedPlacement = getPlacement({
240
- placement
241
- });
242
- const gapCssValue = resolvedPlacement.offset.gap;
243
- const crossAxisShiftCssValue = resolvedPlacement.offset.crossAxisShift.value;
270
+ const gapCssValue = stablePlacement.offset.gap;
271
+ const crossAxisShiftCssValue = stablePlacement.offset.crossAxisShift.value;
244
272
  const shouldForceFallback = Boolean(forceFallbackPositioning);
245
273
  if (supportsAnchorPositioning() && !shouldForceFallback) {
246
274
  // Reuse an existing `anchor-name` if the element already has one.
247
275
  // Another popover might already be anchored to the old name.
248
276
  const anchorName = trigger.style.getPropertyValue('anchor-name') || `--anchor-${id.replace(/:/g, '')}`;
249
277
  const gap = edgeMargin({
250
- placement: resolvedPlacement,
278
+ placement: stablePlacement,
251
279
  offset: gapCssValue
252
280
  });
253
281
  const crossAxisShift = crossAxisShiftMargin({
254
- placement: resolvedPlacement,
282
+ placement: stablePlacement,
255
283
  crossAxisShiftCssValue,
256
- direction: resolvedPlacement.offset.crossAxisShift.direction
284
+ direction: stablePlacement.offset.crossAxisShift.direction
257
285
  });
258
286
  const popoverStyles = [{
259
287
  property: 'position-anchor',
@@ -261,14 +289,14 @@ export function useAnchorPosition({
261
289
  }, {
262
290
  property: 'position-area',
263
291
  value: placementToPositionArea({
264
- placement: resolvedPlacement
292
+ placement: stablePlacement
265
293
  })
266
294
  }, {
267
295
  property: 'position-try-fallbacks',
268
296
  value: arrow ? arrow.getTryFallbacks({
269
- placement: resolvedPlacement
297
+ placement: stablePlacement
270
298
  }) : placementToTryFallbacks({
271
- placement: resolvedPlacement
299
+ placement: stablePlacement
272
300
  })
273
301
  },
274
302
  // Reset browser default popover positioning that conflicts
@@ -312,9 +340,9 @@ export function useAnchorPosition({
312
340
  value: '0px'
313
341
  });
314
342
  const crossAxisShiftActive = crossAxisShiftMargin({
315
- placement: resolvedPlacement,
343
+ placement: stablePlacement,
316
344
  crossAxisShiftCssValue,
317
- direction: resolvedPlacement.offset.crossAxisShift.direction
345
+ direction: stablePlacement.offset.crossAxisShift.direction
318
346
  });
319
347
  // Map margin property to its corresponding custom property name.
320
348
  const customPropertyByMarginProperty = {
@@ -415,12 +443,12 @@ export function useAnchorPosition({
415
443
  } = computeFallbackPosition({
416
444
  triggerRect,
417
445
  popoverEl: popover,
418
- placement: resolvedPlacement,
446
+ placement: stablePlacement,
419
447
  viewport,
420
448
  gap: gapPx,
421
449
  crossAxisShift: {
422
450
  value: crossAxisShiftPx,
423
- direction: resolvedPlacement.offset.crossAxisShift.direction
451
+ direction: stablePlacement.offset.crossAxisShift.direction
424
452
  }
425
453
  });
426
454
  popover.style.setProperty('top', `${top}px`);
@@ -491,5 +519,5 @@ export function useAnchorPosition({
491
519
  popover.style.removeProperty('opacity');
492
520
  });
493
521
  return undoPositioning;
494
- }, [anchorRef, popoverRef, placement, forceFallbackPositioning, arrow, id]);
522
+ }, [anchorRef, popoverRef, stablePlacement, forceFallbackPositioning, arrow, id]);
495
523
  }
@@ -4,7 +4,7 @@ import { ax, ix } from "@compiled/react/runtime";
4
4
  import React, { forwardRef, useCallback, useLayoutEffect, useRef } from 'react';
5
5
  import mergeRefs from '@atlaskit/ds-lib/merge-refs';
6
6
  import { roleToAriaHasPopup } from '../internal/role-types';
7
- import { useAnchorPosition } from '../internal/use-anchor-positioning';
7
+ import { useAnchorPosition } from '../internal/use-anchor-position';
8
8
  import { usePresetStyles } from '../internal/use-preset-styles';
9
9
  import { Popover } from '../popover/popover';
10
10
  import { useMaybePopupContext } from './popup-context';
@@ -14,7 +14,7 @@
14
14
  * - Auto popovers: maintain an open stack, perform light-dismiss on document mousedown
15
15
  * outside the open chain, and dismiss the topmost on Escape.
16
16
  * - On open, removes any inline `opacity: 0` from the popover element so callers that
17
- * rely on a `ResizeObserver` reveal callback (e.g. `use-anchor-positioning`) become
17
+ * rely on a `ResizeObserver` reveal callback (e.g. `use-anchor-position`) become
18
18
  * visible in jsdom, where layout-driven RO callbacks never fire.
19
19
  *
20
20
  * Companion `toBeVisible` patch lives at `./to-be-visible`.
@@ -258,7 +258,7 @@ function stackForMode({
258
258
 
259
259
  // Polyfill-only. Real browsers reveal the popover once `ResizeObserver`
260
260
  // callbacks fire after the first layout pass. jsdom has no layout, so we clear
261
- // any inline `opacity: 0` (set by callers like `use-anchor-positioning`) once
261
+ // any inline `opacity: 0` (set by callers like `use-anchor-position`) once
262
262
  // the open toggle has fired.
263
263
  function clearOpacityOnOpenToggle({
264
264
  element
@@ -1,4 +1,4 @@
1
- import { getPlacement } from '../internal/use-anchor-positioning';
1
+ import { getPlacement } from '../internal/use-anchor-position';
2
2
 
3
3
  // ── slideAndFade ──
4
4
 
@@ -1,5 +1,5 @@
1
1
  import once from '@atlaskit/ds-lib/once';
2
- import { getPlacement, placementToPositionArea } from '../internal/use-anchor-positioning';
2
+ import { getPlacement, placementToPositionArea } from '../internal/use-anchor-position';
3
3
  /**
4
4
  * CSS for popover arrows using the `clip-path: inset() margin-box` technique.
5
5
  *
@@ -0,0 +1,4 @@
1
+ export { OpenLayerObserver } from '@atlaskit/layering/experimental/open-layer-observer/open-layer-observer';
2
+ export { useNotifyOpenLayerObserver } from '@atlaskit/layering/experimental/use-notify-open-layer-observer';
3
+ export { useOpenLayerObserver } from '@atlaskit/layering/experimental/use-open-layer-observer';
4
+ export { OpenLayerObserverNamespaceProvider } from '@atlaskit/layering/experimental/open-layer-observer-namespace-provider';
@@ -1 +1 @@
1
- export { useAnchorPosition } from '../internal/use-anchor-positioning';
1
+ export { useAnchorPosition } from '../internal/use-anchor-position';
@@ -124,7 +124,7 @@ function applyAlignment(_ref2) {
124
124
  * Applies the cross-axis shift offset.
125
125
  *
126
126
  * The sign mirrors the CSS path (see `crossAxisShiftMargin` in
127
- * `use-anchor-positioning.tsx`):
127
+ * `use-anchor-position.tsx`):
128
128
  * - `align: 'start' | 'center'` + `forwards` → push toward end (positive)
129
129
  * - `align: 'end'` + `forwards` → push toward start (negative)
130
130
  * - `backwards` flips the sign in both cases.
@@ -12,7 +12,7 @@
12
12
  * popover with `opacity: 0` until the first measurement completes, so it
13
13
  * is never painted at the wrong location.
14
14
  */
15
- import { useId, useLayoutEffect } from 'react';
15
+ import { useId, useLayoutEffect, useRef } from 'react';
16
16
  import { bind } from 'bind-event-listener';
17
17
  import rafSchedule from 'raf-schd';
18
18
  import once from '@atlaskit/ds-lib/once';
@@ -196,6 +196,30 @@ function crossAxisShiftMargin(_ref4) {
196
196
  };
197
197
  }
198
198
 
199
+ /**
200
+ * Returns a referentially-stable, fully-resolved `TPlacement`: the same
201
+ * reference is preserved across renders as long as the resolved shape
202
+ * does not change.
203
+ */
204
+ function useStablePlacement(placement) {
205
+ var resolved = getPlacement({
206
+ placement: placement
207
+ });
208
+ var stableRef = useRef(resolved);
209
+ if (!isResolvedPlacementEqual(stableRef.current, resolved)) {
210
+ stableRef.current = resolved;
211
+ }
212
+ return stableRef.current;
213
+ }
214
+
215
+ /**
216
+ * Shallow-equals two fully-resolved placements. Avoids `JSON.stringify` so
217
+ * the comparison cost stays proportional to the number of fields.
218
+ */
219
+ function isResolvedPlacementEqual(a, b) {
220
+ return a.axis === b.axis && a.edge === b.edge && a.align === b.align && a.offset.gap === b.offset.gap && a.offset.crossAxisShift.value === b.offset.crossAxisShift.value && a.offset.crossAxisShift.direction === b.offset.crossAxisShift.direction;
221
+ }
222
+
199
223
  /**
200
224
  * Hook that positions an element relative to an anchor element using
201
225
  * CSS Anchor Positioning (with a JS fallback for older browsers).
@@ -211,39 +235,44 @@ function crossAxisShiftMargin(_ref4) {
211
235
  * When not supported, it falls back to JavaScript-based positioning using
212
236
  * `position: fixed` with measured coordinates, re-running on scroll (capture)
213
237
  * and resize events.
238
+ *
239
+ * `placement` may be a fresh inline object on every render: a structural
240
+ * comparison is used internally so re-runs are skipped when the resolved
241
+ * placement shape is unchanged.
214
242
  */
215
243
  export function useAnchorPosition(_ref5) {
216
244
  var anchorRef = _ref5.anchorRef,
217
245
  popoverRef = _ref5.popoverRef,
218
- placement = _ref5.placement,
246
+ _ref5$placement = _ref5.placement,
247
+ placement = _ref5$placement === void 0 ? {} : _ref5$placement,
219
248
  _ref5$forceFallbackPo = _ref5.forceFallbackPositioning,
220
249
  forceFallbackPositioning = _ref5$forceFallbackPo === void 0 ? false : _ref5$forceFallbackPo,
221
250
  arrow = _ref5.arrow;
222
251
  var id = useId();
252
+ // Stabilize `placement` object so the same object literal
253
+ // value returns an object with the same reference
254
+ var stablePlacement = useStablePlacement(placement);
223
255
  useLayoutEffect(function () {
224
256
  var trigger = anchorRef.current;
225
257
  var popover = popoverRef.current;
226
258
  if (!trigger || !popover) {
227
259
  return;
228
260
  }
229
- var resolvedPlacement = getPlacement({
230
- placement: placement
231
- });
232
- var gapCssValue = resolvedPlacement.offset.gap;
233
- var crossAxisShiftCssValue = resolvedPlacement.offset.crossAxisShift.value;
261
+ var gapCssValue = stablePlacement.offset.gap;
262
+ var crossAxisShiftCssValue = stablePlacement.offset.crossAxisShift.value;
234
263
  var shouldForceFallback = Boolean(forceFallbackPositioning);
235
264
  if (supportsAnchorPositioning() && !shouldForceFallback) {
236
265
  // Reuse an existing `anchor-name` if the element already has one.
237
266
  // Another popover might already be anchored to the old name.
238
267
  var anchorName = trigger.style.getPropertyValue('anchor-name') || "--anchor-".concat(id.replace(/:/g, ''));
239
268
  var gap = edgeMargin({
240
- placement: resolvedPlacement,
269
+ placement: stablePlacement,
241
270
  offset: gapCssValue
242
271
  });
243
272
  var crossAxisShift = crossAxisShiftMargin({
244
- placement: resolvedPlacement,
273
+ placement: stablePlacement,
245
274
  crossAxisShiftCssValue: crossAxisShiftCssValue,
246
- direction: resolvedPlacement.offset.crossAxisShift.direction
275
+ direction: stablePlacement.offset.crossAxisShift.direction
247
276
  });
248
277
  var popoverStyles = [{
249
278
  property: 'position-anchor',
@@ -251,14 +280,14 @@ export function useAnchorPosition(_ref5) {
251
280
  }, {
252
281
  property: 'position-area',
253
282
  value: placementToPositionArea({
254
- placement: resolvedPlacement
283
+ placement: stablePlacement
255
284
  })
256
285
  }, {
257
286
  property: 'position-try-fallbacks',
258
287
  value: arrow ? arrow.getTryFallbacks({
259
- placement: resolvedPlacement
288
+ placement: stablePlacement
260
289
  }) : placementToTryFallbacks({
261
- placement: resolvedPlacement
290
+ placement: stablePlacement
262
291
  })
263
292
  },
264
293
  // Reset browser default popover positioning that conflicts
@@ -302,9 +331,9 @@ export function useAnchorPosition(_ref5) {
302
331
  value: '0px'
303
332
  });
304
333
  var crossAxisShiftActive = crossAxisShiftMargin({
305
- placement: resolvedPlacement,
334
+ placement: stablePlacement,
306
335
  crossAxisShiftCssValue: crossAxisShiftCssValue,
307
- direction: resolvedPlacement.offset.crossAxisShift.direction
336
+ direction: stablePlacement.offset.crossAxisShift.direction
308
337
  });
309
338
  // Map margin property to its corresponding custom property name.
310
339
  var customPropertyByMarginProperty = {
@@ -404,12 +433,12 @@ export function useAnchorPosition(_ref5) {
404
433
  var _computeFallbackPosit = computeFallbackPosition({
405
434
  triggerRect: triggerRect,
406
435
  popoverEl: popover,
407
- placement: resolvedPlacement,
436
+ placement: stablePlacement,
408
437
  viewport: viewport,
409
438
  gap: gapPx,
410
439
  crossAxisShift: {
411
440
  value: crossAxisShiftPx,
412
- direction: resolvedPlacement.offset.crossAxisShift.direction
441
+ direction: stablePlacement.offset.crossAxisShift.direction
413
442
  }
414
443
  }),
415
444
  top = _computeFallbackPosit.top,
@@ -484,5 +513,5 @@ export function useAnchorPosition(_ref5) {
484
513
  popover.style.removeProperty('opacity');
485
514
  });
486
515
  return undoPositioning;
487
- }, [anchorRef, popoverRef, placement, forceFallbackPositioning, arrow, id]);
516
+ }, [anchorRef, popoverRef, stablePlacement, forceFallbackPositioning, arrow, id]);
488
517
  }
@@ -4,7 +4,7 @@ import { ax, ix } from "@compiled/react/runtime";
4
4
  import React, { forwardRef, useCallback, useLayoutEffect, useRef } from 'react';
5
5
  import mergeRefs from '@atlaskit/ds-lib/merge-refs';
6
6
  import { roleToAriaHasPopup } from '../internal/role-types';
7
- import { useAnchorPosition } from '../internal/use-anchor-positioning';
7
+ import { useAnchorPosition } from '../internal/use-anchor-position';
8
8
  import { usePresetStyles } from '../internal/use-preset-styles';
9
9
  import { Popover } from '../popover/popover';
10
10
  import { useMaybePopupContext } from './popup-context';
@@ -14,7 +14,7 @@
14
14
  * - Auto popovers: maintain an open stack, perform light-dismiss on document mousedown
15
15
  * outside the open chain, and dismiss the topmost on Escape.
16
16
  * - On open, removes any inline `opacity: 0` from the popover element so callers that
17
- * rely on a `ResizeObserver` reveal callback (e.g. `use-anchor-positioning`) become
17
+ * rely on a `ResizeObserver` reveal callback (e.g. `use-anchor-position`) become
18
18
  * visible in jsdom, where layout-driven RO callbacks never fire.
19
19
  *
20
20
  * Companion `toBeVisible` patch lives at `./to-be-visible`.
@@ -249,7 +249,7 @@ function stackForMode(_ref9) {
249
249
 
250
250
  // Polyfill-only. Real browsers reveal the popover once `ResizeObserver`
251
251
  // callbacks fire after the first layout pass. jsdom has no layout, so we clear
252
- // any inline `opacity: 0` (set by callers like `use-anchor-positioning`) once
252
+ // any inline `opacity: 0` (set by callers like `use-anchor-position`) once
253
253
  // the open toggle has fired.
254
254
  function clearOpacityOnOpenToggle(_ref0) {
255
255
  var element = _ref0.element;
@@ -0,0 +1,5 @@
1
+ export { OpenLayerObserver } from '@atlaskit/layering/experimental/open-layer-observer/open-layer-observer';
2
+ export { useNotifyOpenLayerObserver } from '@atlaskit/layering/experimental/use-notify-open-layer-observer';
3
+ export { useOpenLayerObserver } from '@atlaskit/layering/experimental/use-open-layer-observer';
4
+ export { OpenLayerObserverNamespaceProvider } from '@atlaskit/layering/experimental/open-layer-observer-namespace-provider';
5
+ export type { LayerType } from '@atlaskit/layering/experimental/types';
@@ -1 +1 @@
1
- export { useAnchorPosition } from '../internal/use-anchor-positioning';
1
+ export { useAnchorPosition } from '../internal/use-anchor-position';
@@ -72,6 +72,10 @@ export declare function placementToTryFallbacks({ placement }: {
72
72
  * When not supported, it falls back to JavaScript-based positioning using
73
73
  * `position: fixed` with measured coordinates, re-running on scroll (capture)
74
74
  * and resize events.
75
+ *
76
+ * `placement` may be a fresh inline object on every render: a structural
77
+ * comparison is used internally so re-runs are skipped when the resolved
78
+ * placement shape is unchanged.
75
79
  */
76
80
  export declare function useAnchorPosition({ anchorRef, popoverRef, placement, forceFallbackPositioning, arrow, }: {
77
81
  /**
@@ -86,8 +90,23 @@ export declare function useAnchorPosition({ anchorRef, popoverRef, placement, fo
86
90
  * Where to place the element relative to the anchor.
87
91
  * `offset.gap` and `offset.crossAxisShift` are part of the
88
92
  * placement object.
93
+ *
94
+ * All fields are optional. Omitted fields fall back to:
95
+ * - `axis`: `'block'`
96
+ * - `edge`: `'end'`
97
+ * - `align`: `'center'`
98
+ * - `offset.gap`: `token('space.100', '8px')`
99
+ * - `offset.crossAxisShift.value`: `'0px'`
100
+ * - `offset.crossAxisShift.direction`: `'forwards'`
101
+ *
102
+ * The full default placement (`{}`) renders the popover centered
103
+ * below the trigger with one `space.100` of gap and no cross-axis
104
+ * shift.
105
+ *
106
+ * `offset.gap` and `offset.crossAxisShift.value` accept either a
107
+ * number (pixels) or a CSS length string (eg `token('space.200')`).
89
108
  */
90
- placement: TPlacementOptions;
109
+ placement?: TPlacementOptions;
91
110
  /**
92
111
  * Forces the JavaScript positioning fallback even when the browser
93
112
  * supports CSS Anchor Positioning. Useful for testing fallback
@@ -13,7 +13,7 @@
13
13
  * - Auto popovers: maintain an open stack, perform light-dismiss on document mousedown
14
14
  * outside the open chain, and dismiss the topmost on Escape.
15
15
  * - On open, removes any inline `opacity: 0` from the popover element so callers that
16
- * rely on a `ResizeObserver` reveal callback (e.g. `use-anchor-positioning`) become
16
+ * rely on a `ResizeObserver` reveal callback (e.g. `use-anchor-position`) become
17
17
  * visible in jsdom, where layout-driven RO callbacks never fire.
18
18
  *
19
19
  * Companion `toBeVisible` patch lives at `./to-be-visible`.
@@ -0,0 +1,5 @@
1
+ export { OpenLayerObserver } from '@atlaskit/layering/experimental/open-layer-observer/open-layer-observer';
2
+ export { useNotifyOpenLayerObserver } from '@atlaskit/layering/experimental/use-notify-open-layer-observer';
3
+ export { useOpenLayerObserver } from '@atlaskit/layering/experimental/use-open-layer-observer';
4
+ export { OpenLayerObserverNamespaceProvider } from '@atlaskit/layering/experimental/open-layer-observer-namespace-provider';
5
+ export type { LayerType } from '@atlaskit/layering/experimental/types';
@@ -1 +1 @@
1
- export { useAnchorPosition } from '../internal/use-anchor-positioning';
1
+ export { useAnchorPosition } from '../internal/use-anchor-position';
@@ -72,6 +72,10 @@ export declare function placementToTryFallbacks({ placement }: {
72
72
  * When not supported, it falls back to JavaScript-based positioning using
73
73
  * `position: fixed` with measured coordinates, re-running on scroll (capture)
74
74
  * and resize events.
75
+ *
76
+ * `placement` may be a fresh inline object on every render: a structural
77
+ * comparison is used internally so re-runs are skipped when the resolved
78
+ * placement shape is unchanged.
75
79
  */
76
80
  export declare function useAnchorPosition({ anchorRef, popoverRef, placement, forceFallbackPositioning, arrow, }: {
77
81
  /**
@@ -86,8 +90,23 @@ export declare function useAnchorPosition({ anchorRef, popoverRef, placement, fo
86
90
  * Where to place the element relative to the anchor.
87
91
  * `offset.gap` and `offset.crossAxisShift` are part of the
88
92
  * placement object.
93
+ *
94
+ * All fields are optional. Omitted fields fall back to:
95
+ * - `axis`: `'block'`
96
+ * - `edge`: `'end'`
97
+ * - `align`: `'center'`
98
+ * - `offset.gap`: `token('space.100', '8px')`
99
+ * - `offset.crossAxisShift.value`: `'0px'`
100
+ * - `offset.crossAxisShift.direction`: `'forwards'`
101
+ *
102
+ * The full default placement (`{}`) renders the popover centered
103
+ * below the trigger with one `space.100` of gap and no cross-axis
104
+ * shift.
105
+ *
106
+ * `offset.gap` and `offset.crossAxisShift.value` accept either a
107
+ * number (pixels) or a CSS length string (eg `token('space.200')`).
89
108
  */
90
- placement: TPlacementOptions;
109
+ placement?: TPlacementOptions;
91
110
  /**
92
111
  * Forces the JavaScript positioning fallback even when the browser
93
112
  * supports CSS Anchor Positioning. Useful for testing fallback
@@ -13,7 +13,7 @@
13
13
  * - Auto popovers: maintain an open stack, perform light-dismiss on document mousedown
14
14
  * outside the open chain, and dismiss the topmost on Escape.
15
15
  * - On open, removes any inline `opacity: 0` from the popover element so callers that
16
- * rely on a `ResizeObserver` reveal callback (e.g. `use-anchor-positioning`) become
16
+ * rely on a `ResizeObserver` reveal callback (e.g. `use-anchor-position`) become
17
17
  * visible in jsdom, where layout-driven RO callbacks never fire.
18
18
  *
19
19
  * Companion `toBeVisible` patch lives at `./to-be-visible`.
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@atlaskit/top-layer/open-layer-observer",
3
+ "main": "../dist/cjs/entry-points/open-layer-observer.js",
4
+ "module": "../dist/esm/entry-points/open-layer-observer.js",
5
+ "module:es2019": "../dist/es2019/entry-points/open-layer-observer.js",
6
+ "sideEffects": [
7
+ "*.compiled.css"
8
+ ],
9
+ "types": "../dist/types/entry-points/open-layer-observer.d.ts",
10
+ "typesVersions": {
11
+ ">=4.5 <5.9": {
12
+ "*": [
13
+ "../dist/types-ts4.5/entry-points/open-layer-observer.d.ts"
14
+ ]
15
+ }
16
+ }
17
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/top-layer",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Low-level layering primitives based on popover and dialog",
5
5
  "homepage": "https://atlassian.design/components/top-layer",
6
6
  "repository": "https://bitbucket.org/atlassian/atlassian-frontend-mirror",
@@ -37,6 +37,7 @@
37
37
  "@atlaskit/browser-apis": "^0.0.1",
38
38
  "@atlaskit/css": "^0.19.0",
39
39
  "@atlaskit/ds-lib": "^7.0.0",
40
+ "@atlaskit/layering": "^3.7.0",
40
41
  "@atlaskit/tokens": "^13.0.0",
41
42
  "@babel/runtime": "^7.0.0",
42
43
  "@compiled/react": "^0.20.0",