@workday/canvas-kit-react 9.0.8 → 9.0.9

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 (39) hide show
  1. package/dist/commonjs/popup/lib/Popper.d.ts +8 -0
  2. package/dist/commonjs/popup/lib/Popper.d.ts.map +1 -1
  3. package/dist/commonjs/popup/lib/Popper.js +32 -8
  4. package/dist/commonjs/popup/lib/PopupPopper.d.ts +7 -0
  5. package/dist/commonjs/popup/lib/PopupPopper.d.ts.map +1 -1
  6. package/dist/commonjs/popup/lib/fallbackPlacements.d.ts +9 -0
  7. package/dist/commonjs/popup/lib/fallbackPlacements.d.ts.map +1 -0
  8. package/dist/commonjs/popup/lib/fallbackPlacements.js +94 -0
  9. package/dist/commonjs/popup/lib/hooks/usePopupPopper.d.ts.map +1 -1
  10. package/dist/commonjs/popup/lib/hooks/usePopupPopper.js +5 -2
  11. package/dist/commonjs/tooltip/lib/OverflowTooltip.d.ts +8 -1
  12. package/dist/commonjs/tooltip/lib/OverflowTooltip.d.ts.map +1 -1
  13. package/dist/commonjs/tooltip/lib/OverflowTooltip.js +2 -2
  14. package/dist/commonjs/tooltip/lib/Tooltip.d.ts +7 -0
  15. package/dist/commonjs/tooltip/lib/Tooltip.d.ts.map +1 -1
  16. package/dist/commonjs/tooltip/lib/Tooltip.js +2 -2
  17. package/dist/es6/popup/lib/Popper.d.ts +8 -0
  18. package/dist/es6/popup/lib/Popper.d.ts.map +1 -1
  19. package/dist/es6/popup/lib/Popper.js +31 -7
  20. package/dist/es6/popup/lib/PopupPopper.d.ts +7 -0
  21. package/dist/es6/popup/lib/PopupPopper.d.ts.map +1 -1
  22. package/dist/es6/popup/lib/fallbackPlacements.d.ts +9 -0
  23. package/dist/es6/popup/lib/fallbackPlacements.d.ts.map +1 -0
  24. package/dist/es6/popup/lib/fallbackPlacements.js +72 -0
  25. package/dist/es6/popup/lib/hooks/usePopupPopper.d.ts.map +1 -1
  26. package/dist/es6/popup/lib/hooks/usePopupPopper.js +5 -2
  27. package/dist/es6/tooltip/lib/OverflowTooltip.d.ts +8 -1
  28. package/dist/es6/tooltip/lib/OverflowTooltip.d.ts.map +1 -1
  29. package/dist/es6/tooltip/lib/OverflowTooltip.js +3 -3
  30. package/dist/es6/tooltip/lib/Tooltip.d.ts +7 -0
  31. package/dist/es6/tooltip/lib/Tooltip.d.ts.map +1 -1
  32. package/dist/es6/tooltip/lib/Tooltip.js +3 -3
  33. package/package.json +3 -3
  34. package/popup/lib/Popper.tsx +39 -6
  35. package/popup/lib/PopupPopper.tsx +7 -0
  36. package/popup/lib/fallbackPlacements.ts +90 -0
  37. package/popup/lib/hooks/usePopupPopper.ts +6 -2
  38. package/tooltip/lib/OverflowTooltip.tsx +15 -2
  39. package/tooltip/lib/Tooltip.tsx +15 -2
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
  import * as PopperJS from '@popperjs/core';
3
3
  export declare type Placement = `${PopperJS.Placement}`;
4
4
  export declare type PopperOptions = PopperJS.Options;
5
+ export declare const defaultFallbackPlacements: Placement[];
5
6
  export interface PopperProps {
6
7
  /**
7
8
  * The reference element used to position the Popper. Popper content will try to follow the
@@ -39,6 +40,13 @@ export interface PopperProps {
39
40
  * @default bottom
40
41
  */
41
42
  placement?: Placement;
43
+ /**
44
+ * Define fallback placements by providing a list of {@link Placement} in array (in order of preference).
45
+ * The default preference is following the order of `top`, `right`, `bottom`, and `left`. Once the initial
46
+ * and opposite placements are not available, the fallback placements will be in use. Use an empty array to
47
+ * disable the fallback placements.
48
+ */
49
+ fallbackPlacements?: Placement[];
42
50
  /**
43
51
  * A callback function that will be called whenever PopperJS chooses a placement that is different
44
52
  * from the provided `placement` preference. If a `placement` preference doesn't fit, PopperJS
@@ -1 +1 @@
1
- {"version":3,"file":"Popper.d.ts","sourceRoot":"","sources":["../../../../popup/lib/Popper.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,oBAAY,SAAS,GAAG,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC;AAChD,oBAAY,aAAa,GAAG,QAAQ,CAAC,OAAO,CAAC;AAK7C,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC;IAC1D;;;;;OAKG;IACH,QAAQ,EAAE,CAAC,CAAC,KAAK,EAAE;QAAC,SAAS,EAAE,SAAS,CAAA;KAAC,KAAK,KAAK,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC;IACjF;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC;IACpC;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;;OAKG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,KAAK,IAAI,CAAC;IACnD;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IACvC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;CAClD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,MAAM,oFAQlB,CAAC"}
1
+ {"version":3,"file":"Popper.d.ts","sourceRoot":"","sources":["../../../../popup/lib/Popper.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,oBAAY,SAAS,GAAG,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC;AAChD,oBAAY,aAAa,GAAG,QAAQ,CAAC,OAAO,CAAC;AAC7C,eAAO,MAAM,yBAAyB,EAAE,SAAS,EAAuC,CAAC;AAMzF,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC;IAC1D;;;;;OAKG;IACH,QAAQ,EAAE,CAAC,CAAC,KAAK,EAAE;QAAC,SAAS,EAAE,SAAS,CAAA;KAAC,KAAK,KAAK,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC;IACjF;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC;IACpC;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;;OAKG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,SAAS,EAAE,CAAC;IACjC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,KAAK,IAAI,CAAC;IACnD;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IACvC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;CAClD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,MAAM,oFAQlB,CAAC"}
@@ -19,12 +19,14 @@ var __importStar = (this && this.__importStar) || function (mod) {
19
19
  return result;
20
20
  };
21
21
  Object.defineProperty(exports, "__esModule", { value: true });
22
- exports.Popper = void 0;
22
+ exports.Popper = exports.defaultFallbackPlacements = void 0;
23
23
  const React = __importStar(require("react"));
24
24
  const ReactDOM = __importStar(require("react-dom"));
25
25
  const PopperJS = __importStar(require("@popperjs/core"));
26
+ exports.defaultFallbackPlacements = ['top', 'right', 'bottom', 'left'];
26
27
  const hooks_1 = require("./hooks");
27
28
  const common_1 = require("@workday/canvas-kit-react/common");
29
+ const fallbackPlacements_1 = require("./fallbackPlacements");
28
30
  /**
29
31
  * A thin wrapper component around the Popper.js positioning engine. For reference:
30
32
  * https://popper.js.org/. `Popper` also automatically works with the {@link PopupStack} system.
@@ -61,19 +63,23 @@ const getElementFromRefOrElement = (input) => {
61
63
  const defaultPopperOptions = {};
62
64
  // Popper bails early if `open` is false and React hooks cannot be called conditionally,
63
65
  // so we're breaking out the open version into another component.
64
- const OpenPopper = React.forwardRef(({ anchorElement, getAnchorClientRect, popperOptions = defaultPopperOptions, placement: popperPlacement = 'bottom', onPlacementChange, children, portal, popperInstanceRef, }, ref) => {
66
+ const OpenPopper = React.forwardRef(({ anchorElement, getAnchorClientRect, popperOptions = defaultPopperOptions, placement: popperPlacement = 'bottom', fallbackPlacements = exports.defaultFallbackPlacements, onPlacementChange, children, portal, popperInstanceRef, }, ref) => {
65
67
  const firstRender = React.useRef(true);
66
68
  const { localRef, elementRef } = common_1.useLocalRef(popperInstanceRef);
67
69
  const [placement, setPlacement] = React.useState(popperPlacement);
68
70
  const stackRef = hooks_1.usePopupStack(ref, anchorElement);
71
+ const placementRef = React.useRef(popperPlacement);
72
+ placementRef.current = placement;
69
73
  const placementModifier = React.useMemo(() => {
70
74
  return {
71
75
  name: 'setPlacement',
72
76
  enabled: true,
73
- phase: 'main',
77
+ phase: 'afterWrite',
74
78
  fn({ state }) {
75
- setPlacement(state.placement);
76
- onPlacementChange === null || onPlacementChange === void 0 ? void 0 : onPlacementChange(state.placement);
79
+ if (placementRef.current !== state.placement) {
80
+ setPlacement(state.placement);
81
+ onPlacementChange === null || onPlacementChange === void 0 ? void 0 : onPlacementChange(state.placement);
82
+ }
77
83
  },
78
84
  };
79
85
  }, [setPlacement, onPlacementChange]);
@@ -90,7 +96,16 @@ const OpenPopper = React.forwardRef(({ anchorElement, getAnchorClientRect, poppe
90
96
  const instance = PopperJS.createPopper(anchorEl, stackRef.current, {
91
97
  placement: popperPlacement,
92
98
  ...popperOptions,
93
- modifiers: [...(popperOptions.modifiers || []), placementModifier],
99
+ modifiers: [
100
+ placementModifier,
101
+ {
102
+ ...fallbackPlacements_1.fallbackPlacementsModifier,
103
+ options: {
104
+ fallbackPlacements,
105
+ },
106
+ },
107
+ ...(popperOptions.modifiers || []),
108
+ ],
94
109
  });
95
110
  elementRef(instance); // update the ref with the instance
96
111
  return () => {
@@ -110,11 +125,20 @@ const OpenPopper = React.forwardRef(({ anchorElement, getAnchorClientRect, poppe
110
125
  (_a = localRef.current) === null || _a === void 0 ? void 0 : _a.setOptions({
111
126
  placement: popperPlacement,
112
127
  ...popperOptions,
113
- modifiers: [...(popperOptions.modifiers || []), placementModifier],
128
+ modifiers: [
129
+ placementModifier,
130
+ {
131
+ ...fallbackPlacements_1.fallbackPlacementsModifier,
132
+ options: {
133
+ fallbackPlacements,
134
+ },
135
+ },
136
+ ...(popperOptions.modifiers || []),
137
+ ],
114
138
  });
115
139
  }
116
140
  firstRender.current = false;
117
- }, [popperOptions, popperPlacement, placementModifier, localRef]);
141
+ }, [popperOptions, popperPlacement, fallbackPlacements, placementModifier, localRef]);
118
142
  const contents = React.createElement(React.Fragment, null, isRenderProp(children) ? children({ placement }) : children);
119
143
  if (!portal) {
120
144
  return contents;
@@ -8,6 +8,13 @@ export interface PopupPopperProps extends PopperProps {
8
8
  * @default bottom
9
9
  */
10
10
  placement?: Placement;
11
+ /**
12
+ * Define fallback placements by providing a list of {@link Placement} in array (in order of preference).
13
+ * The default preference is following the order of `top`, `right`, `bottom`, and `left`. Once the initial
14
+ * and opposite placements are not available, the fallback placements will be in use. Use an empty array to
15
+ * disable the fallback placements.
16
+ */
17
+ fallbackPlacements?: Placement[];
11
18
  /**
12
19
  * The additional options passed to the Popper's `popper.js` instance.
13
20
  */
@@ -1 +1 @@
1
- {"version":3,"file":"PopupPopper.d.ts","sourceRoot":"","sources":["../../../../popup/lib/PopupPopper.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,EAAC,SAAS,EAAE,aAAa,EAAU,WAAW,EAAC,MAAM,UAAU,CAAC;AAEvE,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD;;;;;OAKG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;EAMtB,CAAC"}
1
+ {"version":3,"file":"PopupPopper.d.ts","sourceRoot":"","sources":["../../../../popup/lib/PopupPopper.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,EAAC,SAAS,EAAE,aAAa,EAAU,WAAW,EAAC,MAAM,UAAU,CAAC;AAEvE,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD;;;;;OAKG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,SAAS,EAAE,CAAC;IACjC;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;EAMtB,CAAC"}
@@ -0,0 +1,9 @@
1
+ import * as PopperJS from '@popperjs/core';
2
+ export declare type Placement = PopperJS.Placement;
3
+ export declare type Options = {
4
+ fallbackPlacements: Placement[];
5
+ };
6
+ declare const name = "fallbackModifier";
7
+ export declare const fallbackPlacementsModifier: PopperJS.Modifier<typeof name, Options>;
8
+ export {};
9
+ //# sourceMappingURL=fallbackPlacements.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fallbackPlacements.d.ts","sourceRoot":"","sources":["../../../../popup/lib/fallbackPlacements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,oBAAY,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC;AAyD3C,oBAAY,OAAO,GAAG;IACpB,kBAAkB,EAAE,SAAS,EAAE,CAAC;CACjC,CAAC;AAEF,QAAA,MAAM,IAAI,qBAAqB,CAAC;AAEhC,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,OAAO,CAwB9E,CAAC"}
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.fallbackPlacementsModifier = void 0;
23
+ const PopperJS = __importStar(require("@popperjs/core"));
24
+ const getOppositePlacement = (popperPlacement) => {
25
+ var _a;
26
+ const [first, second] = popperPlacement.split('-');
27
+ let oppositePlacement;
28
+ switch (first) {
29
+ case 'top':
30
+ oppositePlacement = 'bottom';
31
+ break;
32
+ case 'bottom':
33
+ oppositePlacement = 'top';
34
+ break;
35
+ case 'left':
36
+ oppositePlacement = 'right';
37
+ break;
38
+ case 'right':
39
+ oppositePlacement = 'left';
40
+ break;
41
+ default:
42
+ oppositePlacement = 'auto';
43
+ }
44
+ if (second) {
45
+ oppositePlacement =
46
+ (_a = PopperJS.placements.find(placement => placement.includes(`${oppositePlacement}-${second}`))) !== null && _a !== void 0 ? _a : oppositePlacement;
47
+ }
48
+ return oppositePlacement;
49
+ };
50
+ const choseAvailablePlacement = (placements, state, preferredPlacement) => {
51
+ if (placements.length === 0 || placements[0].split('-')[0] === 'auto') {
52
+ return preferredPlacement;
53
+ }
54
+ const { reference, popper } = state.rects;
55
+ const overflow = PopperJS.detectOverflow({ ...state, placement: placements[0] });
56
+ const direction = /left|right/.test(placements[0].split('-')[0]) ? 'horizontal' : 'vertical';
57
+ const isOverflowed = (overflow.top > 0 || overflow.bottom > 0 || overflow.left > 0 || overflow.right > 0) &&
58
+ (direction === 'horizontal'
59
+ ? popper.height / 2 - overflow.top < reference.height ||
60
+ popper.height / 2 - overflow.bottom < reference.height
61
+ : popper.width - overflow.left < reference.width ||
62
+ popper.width - overflow.right < reference.width);
63
+ const key = placements[0].split('-')[0];
64
+ if (!isOverflowed && overflow[key] <= 0) {
65
+ return placements[0];
66
+ }
67
+ else {
68
+ return choseAvailablePlacement(placements.slice(1), state, preferredPlacement);
69
+ }
70
+ };
71
+ const name = 'fallbackModifier';
72
+ exports.fallbackPlacementsModifier = {
73
+ name: name,
74
+ enabled: true,
75
+ phase: 'main',
76
+ data: {
77
+ _skip: false,
78
+ },
79
+ fn({ state, options }) {
80
+ const preferredPlacement = state.options.placement;
81
+ const { fallbackPlacements } = options;
82
+ const placements = [
83
+ preferredPlacement,
84
+ getOppositePlacement(preferredPlacement),
85
+ ...(fallbackPlacements || []),
86
+ ];
87
+ const placementOverride = choseAvailablePlacement(placements, state, preferredPlacement);
88
+ if (state.placement !== placementOverride) {
89
+ state.modifiersData[name]._skip = true;
90
+ state.placement = placementOverride;
91
+ state.reset = true;
92
+ }
93
+ },
94
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"usePopupPopper.d.ts","sourceRoot":"","sources":["../../../../../popup/lib/hooks/usePopupPopper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAC,SAAS,EAAC,MAAM,WAAW,CAAC;AAEpC;;;GAGG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;mCAGX,SAAS;EAevB,CAAC"}
1
+ {"version":3,"file":"usePopupPopper.d.ts","sourceRoot":"","sources":["../../../../../popup/lib/hooks/usePopupPopper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAC,SAAS,EAAC,MAAM,WAAW,CAAC;AAEpC;;;GAGG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;mCAOX,SAAS;EAevB,CAAC"}
@@ -13,12 +13,15 @@ const react_1 = __importDefault(require("react"));
13
13
  */
14
14
  exports.usePopupPopper = common_1.createElemPropsHook(usePopupModel_1.usePopupModel)(({ state, events }, ref) => {
15
15
  const elementRef = common_1.useForkRef(ref, state.stackRef);
16
+ // Create a mutable ref of the placement to keep a stable `onPlacementChange` reference.
17
+ const placementRef = react_1.default.useRef(state.placement);
18
+ placementRef.current = state.placement;
16
19
  const onPlacementChange = react_1.default.useCallback((placement) => {
17
- if (placement !== state.placement) {
20
+ if (placement !== placementRef.current) {
18
21
  // only update if the placement has changed
19
22
  events.updatePlacement({ placement });
20
23
  }
21
- }, [events, state.placement]);
24
+ }, [events]);
22
25
  return {
23
26
  open: state.visibility !== 'hidden',
24
27
  anchorElement: state.targetRef,
@@ -25,6 +25,13 @@ export interface OverflowTooltipProps extends Omit<React.HTMLAttributes<HTMLDivE
25
25
  * @default 'top'
26
26
  */
27
27
  placement?: Placement;
28
+ /**
29
+ * Define fallback placements by providing a list of {@link Placement} in array (in order of preference).
30
+ * The default preference is following the order of `top`, `right`, `bottom`, and `left`. Once the initial
31
+ * and opposite placements are not available, the fallback placements will be in use. Use an empty array to
32
+ * disable the fallback placements.
33
+ */
34
+ fallbackPlacements?: Placement[];
28
35
  }
29
- export declare const OverflowTooltip: ({ placement, children, ...elemProps }: OverflowTooltipProps) => JSX.Element;
36
+ export declare const OverflowTooltip: ({ placement, fallbackPlacements, children, ...elemProps }: OverflowTooltipProps) => JSX.Element;
30
37
  //# sourceMappingURL=OverflowTooltip.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"OverflowTooltip.d.ts","sourceRoot":"","sources":["../../../../tooltip/lib/OverflowTooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAA4B,SAAS,EAAS,MAAM,iCAAiC,CAAC;AAM7F;;;GAGG;AACH,eAAO,MAAM,mBAAmB,YAAa,OAAO,KAAG,OAAO,GAAG,IAmBhE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,YAAa,OAAO,KAAG,OAAO,GAAG,IAahE,CAAC;AAcF,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAC/F;;;;;;;OAOG;IACH,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAClC;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAiBD,eAAO,MAAM,eAAe,0CAIzB,oBAAoB,gBAwCtB,CAAC"}
1
+ {"version":3,"file":"OverflowTooltip.d.ts","sourceRoot":"","sources":["../../../../tooltip/lib/OverflowTooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAEL,SAAS,EAGV,MAAM,iCAAiC,CAAC;AAMzC;;;GAGG;AACH,eAAO,MAAM,mBAAmB,YAAa,OAAO,KAAG,OAAO,GAAG,IAmBhE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,YAAa,OAAO,KAAG,OAAO,GAAG,IAahE,CAAC;AAcF,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAC/F;;;;;;;OAOG;IACH,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAClC;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,SAAS,EAAE,CAAC;CAClC;AAiBD,eAAO,MAAM,eAAe,8DAKzB,oBAAoB,gBAwCtB,CAAC"}
@@ -88,7 +88,7 @@ function mergeCallbacks(elemProps, componentProps, keys = Object.keys(componentP
88
88
  return mergedProps;
89
89
  }, {});
90
90
  }
91
- const OverflowTooltip = ({ placement = 'top', children, ...elemProps }) => {
91
+ const OverflowTooltip = ({ placement = 'top', fallbackPlacements = popup_1.defaultFallbackPlacements, children, ...elemProps }) => {
92
92
  const [titleText, setTitleText] = React.useState('');
93
93
  const { targetProps, popperProps, tooltipProps } = useTooltip_1.useTooltip({ type: 'muted' });
94
94
  const onMouseEnter = (event) => {
@@ -113,7 +113,7 @@ const OverflowTooltip = ({ placement = 'top', children, ...elemProps }) => {
113
113
  'aria-label': undefined,
114
114
  ...mergeCallbacks({ ...targetProps, onMouseEnter, onFocus }, children.props),
115
115
  }),
116
- React.createElement(popup_1.Popper, Object.assign({ placement: placement }, popperProps), ({ placement }) => {
116
+ React.createElement(popup_1.Popper, Object.assign({ placement: placement, fallbackPlacements: fallbackPlacements }, popperProps), ({ placement }) => {
117
117
  const transformOrigin = popup_1.getTransformFromPlacement(placement);
118
118
  return (React.createElement(TooltipContainer_1.TooltipContainer, Object.assign({ transformOrigin: transformOrigin }, tooltipProps, elemProps), titleText));
119
119
  })));
@@ -20,6 +20,13 @@ export interface TooltipProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
20
20
  * @default 'top'
21
21
  */
22
22
  placement?: Placement;
23
+ /**
24
+ * Define fallback placements by providing a list of {@link Placement} in array (in order of preference).
25
+ * The default preference is following the order of `top`, `right`, `bottom`, and `left`. Once the initial
26
+ * and opposite placements are not available, the fallback placements will be in use. Use an empty array to
27
+ * disable the fallback placements.
28
+ */
29
+ fallbackPlacements?: Placement[];
23
30
  /**
24
31
  * Determines the tooltip type for accessibility.
25
32
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip.d.ts","sourceRoot":"","sources":["../../../../tooltip/lib/Tooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAA4B,SAAS,EAAS,MAAM,iCAAiC,CAAC;AAM7F,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IACvF;;;OAGG;IACH,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB;;;;;;;OAOG;IACH,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAClC;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;;;;;;;;;;;OAeG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,OAAO,CAAC;IACtC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAiBD,eAAO,MAAM,OAAO,kFAyClB,CAAC"}
1
+ {"version":3,"file":"Tooltip.d.ts","sourceRoot":"","sources":["../../../../tooltip/lib/Tooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAEL,SAAS,EAGV,MAAM,iCAAiC,CAAC;AAMzC,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IACvF;;;OAGG;IACH,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB;;;;;;;OAOG;IACH,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAClC;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,SAAS,EAAE,CAAC;IACjC;;;;;;;;;;;;;;;OAeG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,OAAO,CAAC;IACtC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAiBD,eAAO,MAAM,OAAO,kFA0ClB,CAAC"}
@@ -42,7 +42,7 @@ function mergeCallbacks(elemProps, componentProps, keys = Object.keys(componentP
42
42
  }
43
43
  exports.Tooltip = common_1.createComponent('div')({
44
44
  displayName: 'Tooltip',
45
- Component({ type = 'label', placement = 'top', title, children, showDelay = 300, hideDelay = 100, ...elemProps }) {
45
+ Component({ type = 'label', placement = 'top', title, children, showDelay = 300, hideDelay = 100, fallbackPlacements = popup_1.defaultFallbackPlacements, ...elemProps }) {
46
46
  const titleText = react_innertext_1.default(title);
47
47
  const { targetProps, popperProps, tooltipProps } = useTooltip_1.useTooltip({
48
48
  type,
@@ -58,7 +58,7 @@ exports.Tooltip = common_1.createComponent('div')({
58
58
  ? { 'aria-label': children.props['aria-label'] }
59
59
  : {}),
60
60
  }),
61
- React.createElement(popup_1.Popper, Object.assign({ placement: placement }, popperProps), ({ placement }) => {
61
+ React.createElement(popup_1.Popper, Object.assign({ placement: placement, fallbackPlacements: fallbackPlacements }, popperProps), ({ placement }) => {
62
62
  const transformOrigin = popup_1.getTransformFromPlacement(placement);
63
63
  return (React.createElement(TooltipContainer_1.TooltipContainer, Object.assign({ transformOrigin: transformOrigin }, elemProps, tooltipProps), title));
64
64
  })));
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
  import * as PopperJS from '@popperjs/core';
3
3
  export declare type Placement = `${PopperJS.Placement}`;
4
4
  export declare type PopperOptions = PopperJS.Options;
5
+ export declare const defaultFallbackPlacements: Placement[];
5
6
  export interface PopperProps {
6
7
  /**
7
8
  * The reference element used to position the Popper. Popper content will try to follow the
@@ -39,6 +40,13 @@ export interface PopperProps {
39
40
  * @default bottom
40
41
  */
41
42
  placement?: Placement;
43
+ /**
44
+ * Define fallback placements by providing a list of {@link Placement} in array (in order of preference).
45
+ * The default preference is following the order of `top`, `right`, `bottom`, and `left`. Once the initial
46
+ * and opposite placements are not available, the fallback placements will be in use. Use an empty array to
47
+ * disable the fallback placements.
48
+ */
49
+ fallbackPlacements?: Placement[];
42
50
  /**
43
51
  * A callback function that will be called whenever PopperJS chooses a placement that is different
44
52
  * from the provided `placement` preference. If a `placement` preference doesn't fit, PopperJS
@@ -1 +1 @@
1
- {"version":3,"file":"Popper.d.ts","sourceRoot":"","sources":["../../../../popup/lib/Popper.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,oBAAY,SAAS,GAAG,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC;AAChD,oBAAY,aAAa,GAAG,QAAQ,CAAC,OAAO,CAAC;AAK7C,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC;IAC1D;;;;;OAKG;IACH,QAAQ,EAAE,CAAC,CAAC,KAAK,EAAE;QAAC,SAAS,EAAE,SAAS,CAAA;KAAC,KAAK,KAAK,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC;IACjF;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC;IACpC;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;;OAKG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,KAAK,IAAI,CAAC;IACnD;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IACvC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;CAClD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,MAAM,oFAQlB,CAAC"}
1
+ {"version":3,"file":"Popper.d.ts","sourceRoot":"","sources":["../../../../popup/lib/Popper.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,oBAAY,SAAS,GAAG,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC;AAChD,oBAAY,aAAa,GAAG,QAAQ,CAAC,OAAO,CAAC;AAC7C,eAAO,MAAM,yBAAyB,EAAE,SAAS,EAAuC,CAAC;AAMzF,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC;IAC1D;;;;;OAKG;IACH,QAAQ,EAAE,CAAC,CAAC,KAAK,EAAE;QAAC,SAAS,EAAE,SAAS,CAAA;KAAC,KAAK,KAAK,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC;IACjF;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC;IACpC;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;;OAKG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,SAAS,EAAE,CAAC;IACjC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,KAAK,IAAI,CAAC;IACnD;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IACvC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;CAClD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,MAAM,oFAQlB,CAAC"}
@@ -1,8 +1,10 @@
1
1
  import * as React from 'react';
2
2
  import * as ReactDOM from 'react-dom';
3
3
  import * as PopperJS from '@popperjs/core';
4
+ export const defaultFallbackPlacements = ['top', 'right', 'bottom', 'left'];
4
5
  import { usePopupStack } from './hooks';
5
6
  import { useLocalRef } from '@workday/canvas-kit-react/common';
7
+ import { fallbackPlacementsModifier } from './fallbackPlacements';
6
8
  /**
7
9
  * A thin wrapper component around the Popper.js positioning engine. For reference:
8
10
  * https://popper.js.org/. `Popper` also automatically works with the {@link PopupStack} system.
@@ -39,19 +41,23 @@ const getElementFromRefOrElement = (input) => {
39
41
  const defaultPopperOptions = {};
40
42
  // Popper bails early if `open` is false and React hooks cannot be called conditionally,
41
43
  // so we're breaking out the open version into another component.
42
- const OpenPopper = React.forwardRef(({ anchorElement, getAnchorClientRect, popperOptions = defaultPopperOptions, placement: popperPlacement = 'bottom', onPlacementChange, children, portal, popperInstanceRef, }, ref) => {
44
+ const OpenPopper = React.forwardRef(({ anchorElement, getAnchorClientRect, popperOptions = defaultPopperOptions, placement: popperPlacement = 'bottom', fallbackPlacements = defaultFallbackPlacements, onPlacementChange, children, portal, popperInstanceRef, }, ref) => {
43
45
  const firstRender = React.useRef(true);
44
46
  const { localRef, elementRef } = useLocalRef(popperInstanceRef);
45
47
  const [placement, setPlacement] = React.useState(popperPlacement);
46
48
  const stackRef = usePopupStack(ref, anchorElement);
49
+ const placementRef = React.useRef(popperPlacement);
50
+ placementRef.current = placement;
47
51
  const placementModifier = React.useMemo(() => {
48
52
  return {
49
53
  name: 'setPlacement',
50
54
  enabled: true,
51
- phase: 'main',
55
+ phase: 'afterWrite',
52
56
  fn({ state }) {
53
- setPlacement(state.placement);
54
- onPlacementChange === null || onPlacementChange === void 0 ? void 0 : onPlacementChange(state.placement);
57
+ if (placementRef.current !== state.placement) {
58
+ setPlacement(state.placement);
59
+ onPlacementChange === null || onPlacementChange === void 0 ? void 0 : onPlacementChange(state.placement);
60
+ }
55
61
  },
56
62
  };
57
63
  }, [setPlacement, onPlacementChange]);
@@ -68,7 +74,16 @@ const OpenPopper = React.forwardRef(({ anchorElement, getAnchorClientRect, poppe
68
74
  const instance = PopperJS.createPopper(anchorEl, stackRef.current, {
69
75
  placement: popperPlacement,
70
76
  ...popperOptions,
71
- modifiers: [...(popperOptions.modifiers || []), placementModifier],
77
+ modifiers: [
78
+ placementModifier,
79
+ {
80
+ ...fallbackPlacementsModifier,
81
+ options: {
82
+ fallbackPlacements,
83
+ },
84
+ },
85
+ ...(popperOptions.modifiers || []),
86
+ ],
72
87
  });
73
88
  elementRef(instance); // update the ref with the instance
74
89
  return () => {
@@ -88,11 +103,20 @@ const OpenPopper = React.forwardRef(({ anchorElement, getAnchorClientRect, poppe
88
103
  (_a = localRef.current) === null || _a === void 0 ? void 0 : _a.setOptions({
89
104
  placement: popperPlacement,
90
105
  ...popperOptions,
91
- modifiers: [...(popperOptions.modifiers || []), placementModifier],
106
+ modifiers: [
107
+ placementModifier,
108
+ {
109
+ ...fallbackPlacementsModifier,
110
+ options: {
111
+ fallbackPlacements,
112
+ },
113
+ },
114
+ ...(popperOptions.modifiers || []),
115
+ ],
92
116
  });
93
117
  }
94
118
  firstRender.current = false;
95
- }, [popperOptions, popperPlacement, placementModifier, localRef]);
119
+ }, [popperOptions, popperPlacement, fallbackPlacements, placementModifier, localRef]);
96
120
  const contents = React.createElement(React.Fragment, null, isRenderProp(children) ? children({ placement }) : children);
97
121
  if (!portal) {
98
122
  return contents;
@@ -8,6 +8,13 @@ export interface PopupPopperProps extends PopperProps {
8
8
  * @default bottom
9
9
  */
10
10
  placement?: Placement;
11
+ /**
12
+ * Define fallback placements by providing a list of {@link Placement} in array (in order of preference).
13
+ * The default preference is following the order of `top`, `right`, `bottom`, and `left`. Once the initial
14
+ * and opposite placements are not available, the fallback placements will be in use. Use an empty array to
15
+ * disable the fallback placements.
16
+ */
17
+ fallbackPlacements?: Placement[];
11
18
  /**
12
19
  * The additional options passed to the Popper's `popper.js` instance.
13
20
  */
@@ -1 +1 @@
1
- {"version":3,"file":"PopupPopper.d.ts","sourceRoot":"","sources":["../../../../popup/lib/PopupPopper.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,EAAC,SAAS,EAAE,aAAa,EAAU,WAAW,EAAC,MAAM,UAAU,CAAC;AAEvE,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD;;;;;OAKG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;EAMtB,CAAC"}
1
+ {"version":3,"file":"PopupPopper.d.ts","sourceRoot":"","sources":["../../../../popup/lib/PopupPopper.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,EAAC,SAAS,EAAE,aAAa,EAAU,WAAW,EAAC,MAAM,UAAU,CAAC;AAEvE,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACnD;;;;;OAKG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,SAAS,EAAE,CAAC;IACjC;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;EAMtB,CAAC"}
@@ -0,0 +1,9 @@
1
+ import * as PopperJS from '@popperjs/core';
2
+ export declare type Placement = PopperJS.Placement;
3
+ export declare type Options = {
4
+ fallbackPlacements: Placement[];
5
+ };
6
+ declare const name = "fallbackModifier";
7
+ export declare const fallbackPlacementsModifier: PopperJS.Modifier<typeof name, Options>;
8
+ export {};
9
+ //# sourceMappingURL=fallbackPlacements.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fallbackPlacements.d.ts","sourceRoot":"","sources":["../../../../popup/lib/fallbackPlacements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,oBAAY,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC;AAyD3C,oBAAY,OAAO,GAAG;IACpB,kBAAkB,EAAE,SAAS,EAAE,CAAC;CACjC,CAAC;AAEF,QAAA,MAAM,IAAI,qBAAqB,CAAC;AAEhC,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,OAAO,CAwB9E,CAAC"}
@@ -0,0 +1,72 @@
1
+ import * as PopperJS from '@popperjs/core';
2
+ const getOppositePlacement = (popperPlacement) => {
3
+ var _a;
4
+ const [first, second] = popperPlacement.split('-');
5
+ let oppositePlacement;
6
+ switch (first) {
7
+ case 'top':
8
+ oppositePlacement = 'bottom';
9
+ break;
10
+ case 'bottom':
11
+ oppositePlacement = 'top';
12
+ break;
13
+ case 'left':
14
+ oppositePlacement = 'right';
15
+ break;
16
+ case 'right':
17
+ oppositePlacement = 'left';
18
+ break;
19
+ default:
20
+ oppositePlacement = 'auto';
21
+ }
22
+ if (second) {
23
+ oppositePlacement =
24
+ (_a = PopperJS.placements.find(placement => placement.includes(`${oppositePlacement}-${second}`))) !== null && _a !== void 0 ? _a : oppositePlacement;
25
+ }
26
+ return oppositePlacement;
27
+ };
28
+ const choseAvailablePlacement = (placements, state, preferredPlacement) => {
29
+ if (placements.length === 0 || placements[0].split('-')[0] === 'auto') {
30
+ return preferredPlacement;
31
+ }
32
+ const { reference, popper } = state.rects;
33
+ const overflow = PopperJS.detectOverflow({ ...state, placement: placements[0] });
34
+ const direction = /left|right/.test(placements[0].split('-')[0]) ? 'horizontal' : 'vertical';
35
+ const isOverflowed = (overflow.top > 0 || overflow.bottom > 0 || overflow.left > 0 || overflow.right > 0) &&
36
+ (direction === 'horizontal'
37
+ ? popper.height / 2 - overflow.top < reference.height ||
38
+ popper.height / 2 - overflow.bottom < reference.height
39
+ : popper.width - overflow.left < reference.width ||
40
+ popper.width - overflow.right < reference.width);
41
+ const key = placements[0].split('-')[0];
42
+ if (!isOverflowed && overflow[key] <= 0) {
43
+ return placements[0];
44
+ }
45
+ else {
46
+ return choseAvailablePlacement(placements.slice(1), state, preferredPlacement);
47
+ }
48
+ };
49
+ const name = 'fallbackModifier';
50
+ export const fallbackPlacementsModifier = {
51
+ name: name,
52
+ enabled: true,
53
+ phase: 'main',
54
+ data: {
55
+ _skip: false,
56
+ },
57
+ fn({ state, options }) {
58
+ const preferredPlacement = state.options.placement;
59
+ const { fallbackPlacements } = options;
60
+ const placements = [
61
+ preferredPlacement,
62
+ getOppositePlacement(preferredPlacement),
63
+ ...(fallbackPlacements || []),
64
+ ];
65
+ const placementOverride = choseAvailablePlacement(placements, state, preferredPlacement);
66
+ if (state.placement !== placementOverride) {
67
+ state.modifiersData[name]._skip = true;
68
+ state.placement = placementOverride;
69
+ state.reset = true;
70
+ }
71
+ },
72
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"usePopupPopper.d.ts","sourceRoot":"","sources":["../../../../../popup/lib/hooks/usePopupPopper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAC,SAAS,EAAC,MAAM,WAAW,CAAC;AAEpC;;;GAGG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;mCAGX,SAAS;EAevB,CAAC"}
1
+ {"version":3,"file":"usePopupPopper.d.ts","sourceRoot":"","sources":["../../../../../popup/lib/hooks/usePopupPopper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAC,SAAS,EAAC,MAAM,WAAW,CAAC;AAEpC;;;GAGG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;mCAOX,SAAS;EAevB,CAAC"}
@@ -7,12 +7,15 @@ import React from 'react';
7
7
  */
8
8
  export const usePopupPopper = createElemPropsHook(usePopupModel)(({ state, events }, ref) => {
9
9
  const elementRef = useForkRef(ref, state.stackRef);
10
+ // Create a mutable ref of the placement to keep a stable `onPlacementChange` reference.
11
+ const placementRef = React.useRef(state.placement);
12
+ placementRef.current = state.placement;
10
13
  const onPlacementChange = React.useCallback((placement) => {
11
- if (placement !== state.placement) {
14
+ if (placement !== placementRef.current) {
12
15
  // only update if the placement has changed
13
16
  events.updatePlacement({ placement });
14
17
  }
15
- }, [events, state.placement]);
18
+ }, [events]);
16
19
  return {
17
20
  open: state.visibility !== 'hidden',
18
21
  anchorElement: state.targetRef,
@@ -25,6 +25,13 @@ export interface OverflowTooltipProps extends Omit<React.HTMLAttributes<HTMLDivE
25
25
  * @default 'top'
26
26
  */
27
27
  placement?: Placement;
28
+ /**
29
+ * Define fallback placements by providing a list of {@link Placement} in array (in order of preference).
30
+ * The default preference is following the order of `top`, `right`, `bottom`, and `left`. Once the initial
31
+ * and opposite placements are not available, the fallback placements will be in use. Use an empty array to
32
+ * disable the fallback placements.
33
+ */
34
+ fallbackPlacements?: Placement[];
28
35
  }
29
- export declare const OverflowTooltip: ({ placement, children, ...elemProps }: OverflowTooltipProps) => JSX.Element;
36
+ export declare const OverflowTooltip: ({ placement, fallbackPlacements, children, ...elemProps }: OverflowTooltipProps) => JSX.Element;
30
37
  //# sourceMappingURL=OverflowTooltip.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"OverflowTooltip.d.ts","sourceRoot":"","sources":["../../../../tooltip/lib/OverflowTooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAA4B,SAAS,EAAS,MAAM,iCAAiC,CAAC;AAM7F;;;GAGG;AACH,eAAO,MAAM,mBAAmB,YAAa,OAAO,KAAG,OAAO,GAAG,IAmBhE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,YAAa,OAAO,KAAG,OAAO,GAAG,IAahE,CAAC;AAcF,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAC/F;;;;;;;OAOG;IACH,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAClC;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAiBD,eAAO,MAAM,eAAe,0CAIzB,oBAAoB,gBAwCtB,CAAC"}
1
+ {"version":3,"file":"OverflowTooltip.d.ts","sourceRoot":"","sources":["../../../../tooltip/lib/OverflowTooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAEL,SAAS,EAGV,MAAM,iCAAiC,CAAC;AAMzC;;;GAGG;AACH,eAAO,MAAM,mBAAmB,YAAa,OAAO,KAAG,OAAO,GAAG,IAmBhE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,YAAa,OAAO,KAAG,OAAO,GAAG,IAahE,CAAC;AAcF,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAC/F;;;;;;;OAOG;IACH,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAClC;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,SAAS,EAAE,CAAC;CAClC;AAiBD,eAAO,MAAM,eAAe,8DAKzB,oBAAoB,gBAwCtB,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { getTransformFromPlacement, Popper } from '@workday/canvas-kit-react/popup';
2
+ import { getTransformFromPlacement, Popper, defaultFallbackPlacements, } from '@workday/canvas-kit-react/popup';
3
3
  import { mergeCallback } from '@workday/canvas-kit-react/common';
4
4
  import { TooltipContainer } from './TooltipContainer';
5
5
  import { useTooltip } from './useTooltip';
@@ -64,7 +64,7 @@ function mergeCallbacks(elemProps, componentProps, keys = Object.keys(componentP
64
64
  return mergedProps;
65
65
  }, {});
66
66
  }
67
- export const OverflowTooltip = ({ placement = 'top', children, ...elemProps }) => {
67
+ export const OverflowTooltip = ({ placement = 'top', fallbackPlacements = defaultFallbackPlacements, children, ...elemProps }) => {
68
68
  const [titleText, setTitleText] = React.useState('');
69
69
  const { targetProps, popperProps, tooltipProps } = useTooltip({ type: 'muted' });
70
70
  const onMouseEnter = (event) => {
@@ -89,7 +89,7 @@ export const OverflowTooltip = ({ placement = 'top', children, ...elemProps }) =
89
89
  'aria-label': undefined,
90
90
  ...mergeCallbacks({ ...targetProps, onMouseEnter, onFocus }, children.props),
91
91
  }),
92
- React.createElement(Popper, Object.assign({ placement: placement }, popperProps), ({ placement }) => {
92
+ React.createElement(Popper, Object.assign({ placement: placement, fallbackPlacements: fallbackPlacements }, popperProps), ({ placement }) => {
93
93
  const transformOrigin = getTransformFromPlacement(placement);
94
94
  return (React.createElement(TooltipContainer, Object.assign({ transformOrigin: transformOrigin }, tooltipProps, elemProps), titleText));
95
95
  })));
@@ -20,6 +20,13 @@ export interface TooltipProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
20
20
  * @default 'top'
21
21
  */
22
22
  placement?: Placement;
23
+ /**
24
+ * Define fallback placements by providing a list of {@link Placement} in array (in order of preference).
25
+ * The default preference is following the order of `top`, `right`, `bottom`, and `left`. Once the initial
26
+ * and opposite placements are not available, the fallback placements will be in use. Use an empty array to
27
+ * disable the fallback placements.
28
+ */
29
+ fallbackPlacements?: Placement[];
23
30
  /**
24
31
  * Determines the tooltip type for accessibility.
25
32
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip.d.ts","sourceRoot":"","sources":["../../../../tooltip/lib/Tooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAA4B,SAAS,EAAS,MAAM,iCAAiC,CAAC;AAM7F,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IACvF;;;OAGG;IACH,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB;;;;;;;OAOG;IACH,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAClC;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;;;;;;;;;;;OAeG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,OAAO,CAAC;IACtC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAiBD,eAAO,MAAM,OAAO,kFAyClB,CAAC"}
1
+ {"version":3,"file":"Tooltip.d.ts","sourceRoot":"","sources":["../../../../tooltip/lib/Tooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAEL,SAAS,EAGV,MAAM,iCAAiC,CAAC;AAMzC,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IACvF;;;OAGG;IACH,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB;;;;;;;OAOG;IACH,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAClC;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,SAAS,EAAE,CAAC;IACjC;;;;;;;;;;;;;;;OAeG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,OAAO,CAAC;IACtC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAiBD,eAAO,MAAM,OAAO,kFA0ClB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import innerText from 'react-innertext';
3
- import { getTransformFromPlacement, Popper } from '@workday/canvas-kit-react/popup';
3
+ import { getTransformFromPlacement, Popper, defaultFallbackPlacements, } from '@workday/canvas-kit-react/popup';
4
4
  import { createComponent, mergeCallback } from '@workday/canvas-kit-react/common';
5
5
  import { TooltipContainer } from './TooltipContainer';
6
6
  import { useTooltip } from './useTooltip';
@@ -17,7 +17,7 @@ function mergeCallbacks(elemProps, componentProps, keys = Object.keys(componentP
17
17
  }
18
18
  export const Tooltip = createComponent('div')({
19
19
  displayName: 'Tooltip',
20
- Component({ type = 'label', placement = 'top', title, children, showDelay = 300, hideDelay = 100, ...elemProps }) {
20
+ Component({ type = 'label', placement = 'top', title, children, showDelay = 300, hideDelay = 100, fallbackPlacements = defaultFallbackPlacements, ...elemProps }) {
21
21
  const titleText = innerText(title);
22
22
  const { targetProps, popperProps, tooltipProps } = useTooltip({
23
23
  type,
@@ -33,7 +33,7 @@ export const Tooltip = createComponent('div')({
33
33
  ? { 'aria-label': children.props['aria-label'] }
34
34
  : {}),
35
35
  }),
36
- React.createElement(Popper, Object.assign({ placement: placement }, popperProps), ({ placement }) => {
36
+ React.createElement(Popper, Object.assign({ placement: placement, fallbackPlacements: fallbackPlacements }, popperProps), ({ placement }) => {
37
37
  const transformOrigin = getTransformFromPlacement(placement);
38
38
  return (React.createElement(TooltipContainer, Object.assign({ transformOrigin: transformOrigin }, elemProps, tooltipProps), title));
39
39
  })));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workday/canvas-kit-react",
3
- "version": "9.0.8",
3
+ "version": "9.0.9",
4
4
  "description": "The parent module that contains all Workday Canvas Kit React components",
5
5
  "author": "Workday, Inc. (https://www.workday.com)",
6
6
  "license": "Apache-2.0",
@@ -49,7 +49,7 @@
49
49
  "@emotion/styled": "^11.6.0",
50
50
  "@popperjs/core": "^2.5.4",
51
51
  "@workday/canvas-colors-web": "^2.0.0",
52
- "@workday/canvas-kit-popup-stack": "^9.0.8",
52
+ "@workday/canvas-kit-popup-stack": "^9.0.9",
53
53
  "@workday/canvas-system-icons-web": "^3.0.0",
54
54
  "@workday/design-assets-types": "^0.2.8",
55
55
  "chroma-js": "^2.1.0",
@@ -66,5 +66,5 @@
66
66
  "@workday/canvas-accent-icons-web": "^3.0.0",
67
67
  "@workday/canvas-applet-icons-web": "^2.0.0"
68
68
  },
69
- "gitHead": "e0f80f6255d61eceeb79932d6a1bae3d9adeffdc"
69
+ "gitHead": "31743535a5f56b8412783810e8ffbd5f856f7a6e"
70
70
  }
@@ -4,9 +4,11 @@ import * as PopperJS from '@popperjs/core';
4
4
 
5
5
  export type Placement = `${PopperJS.Placement}`; // Use template literals to make documentation list them out
6
6
  export type PopperOptions = PopperJS.Options;
7
+ export const defaultFallbackPlacements: Placement[] = ['top', 'right', 'bottom', 'left'];
7
8
 
8
9
  import {usePopupStack} from './hooks';
9
10
  import {useLocalRef} from '@workday/canvas-kit-react/common';
11
+ import {fallbackPlacementsModifier} from './fallbackPlacements';
10
12
 
11
13
  export interface PopperProps {
12
14
  /**
@@ -43,6 +45,13 @@ export interface PopperProps {
43
45
  * @default bottom
44
46
  */
45
47
  placement?: Placement;
48
+ /**
49
+ * Define fallback placements by providing a list of {@link Placement} in array (in order of preference).
50
+ * The default preference is following the order of `top`, `right`, `bottom`, and `left`. Once the initial
51
+ * and opposite placements are not available, the fallback placements will be in use. Use an empty array to
52
+ * disable the fallback placements.
53
+ */
54
+ fallbackPlacements?: Placement[];
46
55
  /**
47
56
  * A callback function that will be called whenever PopperJS chooses a placement that is different
48
57
  * from the provided `placement` preference. If a `placement` preference doesn't fit, PopperJS
@@ -117,6 +126,7 @@ const OpenPopper = React.forwardRef<HTMLDivElement, PopperProps>(
117
126
  getAnchorClientRect,
118
127
  popperOptions = defaultPopperOptions,
119
128
  placement: popperPlacement = 'bottom',
129
+ fallbackPlacements = defaultFallbackPlacements,
120
130
  onPlacementChange,
121
131
  children,
122
132
  portal,
@@ -129,14 +139,19 @@ const OpenPopper = React.forwardRef<HTMLDivElement, PopperProps>(
129
139
  const [placement, setPlacement] = React.useState(popperPlacement);
130
140
  const stackRef = usePopupStack(ref, anchorElement as HTMLElement);
131
141
 
142
+ const placementRef = React.useRef(popperPlacement);
143
+ placementRef.current = placement;
144
+
132
145
  const placementModifier = React.useMemo((): PopperJS.Modifier<any, any> => {
133
146
  return {
134
147
  name: 'setPlacement',
135
148
  enabled: true,
136
- phase: 'main',
149
+ phase: 'afterWrite',
137
150
  fn({state}) {
138
- setPlacement(state.placement);
139
- onPlacementChange?.(state.placement);
151
+ if (placementRef.current !== state.placement) {
152
+ setPlacement(state.placement);
153
+ onPlacementChange?.(state.placement);
154
+ }
140
155
  },
141
156
  };
142
157
  }, [setPlacement, onPlacementChange]);
@@ -157,7 +172,16 @@ const OpenPopper = React.forwardRef<HTMLDivElement, PopperProps>(
157
172
  const instance = PopperJS.createPopper(anchorEl, stackRef.current, {
158
173
  placement: popperPlacement,
159
174
  ...popperOptions,
160
- modifiers: [...(popperOptions.modifiers || []), placementModifier],
175
+ modifiers: [
176
+ placementModifier,
177
+ {
178
+ ...fallbackPlacementsModifier,
179
+ options: {
180
+ fallbackPlacements,
181
+ },
182
+ },
183
+ ...(popperOptions.modifiers || []),
184
+ ],
161
185
  });
162
186
  elementRef(instance); // update the ref with the instance
163
187
 
@@ -179,11 +203,20 @@ const OpenPopper = React.forwardRef<HTMLDivElement, PopperProps>(
179
203
  localRef.current?.setOptions({
180
204
  placement: popperPlacement,
181
205
  ...popperOptions,
182
- modifiers: [...(popperOptions.modifiers || []), placementModifier],
206
+ modifiers: [
207
+ placementModifier,
208
+ {
209
+ ...fallbackPlacementsModifier,
210
+ options: {
211
+ fallbackPlacements,
212
+ },
213
+ },
214
+ ...(popperOptions.modifiers || []),
215
+ ],
183
216
  });
184
217
  }
185
218
  firstRender.current = false;
186
- }, [popperOptions, popperPlacement, placementModifier, localRef]);
219
+ }, [popperOptions, popperPlacement, fallbackPlacements, placementModifier, localRef]);
187
220
 
188
221
  const contents = <>{isRenderProp(children) ? children({placement}) : children}</>;
189
222
 
@@ -13,6 +13,13 @@ export interface PopupPopperProps extends PopperProps {
13
13
  * @default bottom
14
14
  */
15
15
  placement?: Placement;
16
+ /**
17
+ * Define fallback placements by providing a list of {@link Placement} in array (in order of preference).
18
+ * The default preference is following the order of `top`, `right`, `bottom`, and `left`. Once the initial
19
+ * and opposite placements are not available, the fallback placements will be in use. Use an empty array to
20
+ * disable the fallback placements.
21
+ */
22
+ fallbackPlacements?: Placement[];
16
23
  /**
17
24
  * The additional options passed to the Popper's `popper.js` instance.
18
25
  */
@@ -0,0 +1,90 @@
1
+ import * as PopperJS from '@popperjs/core';
2
+
3
+ export type Placement = PopperJS.Placement; // Use template literals to make documentation list them out
4
+
5
+ const getOppositePlacement = (popperPlacement: Placement): Placement => {
6
+ const [first, second] = popperPlacement.split('-');
7
+ let oppositePlacement: Placement;
8
+ switch (first) {
9
+ case 'top':
10
+ oppositePlacement = 'bottom';
11
+ break;
12
+ case 'bottom':
13
+ oppositePlacement = 'top';
14
+ break;
15
+ case 'left':
16
+ oppositePlacement = 'right';
17
+ break;
18
+ case 'right':
19
+ oppositePlacement = 'left';
20
+ break;
21
+ default:
22
+ oppositePlacement = 'auto';
23
+ }
24
+ if (second) {
25
+ oppositePlacement =
26
+ PopperJS.placements.find(placement => placement.includes(`${oppositePlacement}-${second}`)) ??
27
+ oppositePlacement;
28
+ }
29
+ return oppositePlacement;
30
+ };
31
+
32
+ const choseAvailablePlacement = (
33
+ placements: Placement[],
34
+ state: PopperJS.State,
35
+ preferredPlacement: Placement
36
+ ): Placement => {
37
+ if (placements.length === 0 || placements[0].split('-')[0] === 'auto') {
38
+ return preferredPlacement;
39
+ }
40
+
41
+ const {reference, popper} = state.rects;
42
+ const overflow = PopperJS.detectOverflow({...state, placement: placements[0]});
43
+ const direction = /left|right/.test(placements[0].split('-')[0]) ? 'horizontal' : 'vertical';
44
+ const isOverflowed =
45
+ (overflow.top > 0 || overflow.bottom > 0 || overflow.left > 0 || overflow.right > 0) &&
46
+ (direction === 'horizontal'
47
+ ? popper.height / 2 - overflow.top < reference.height ||
48
+ popper.height / 2 - overflow.bottom < reference.height
49
+ : popper.width - overflow.left < reference.width ||
50
+ popper.width - overflow.right < reference.width);
51
+ const key = placements[0].split('-')[0] as keyof PopperJS.SideObject;
52
+
53
+ if (!isOverflowed && overflow[key] <= 0) {
54
+ return placements[0];
55
+ } else {
56
+ return choseAvailablePlacement(placements.slice(1), state, preferredPlacement);
57
+ }
58
+ };
59
+
60
+ export type Options = {
61
+ fallbackPlacements: Placement[];
62
+ };
63
+
64
+ const name = 'fallbackModifier';
65
+
66
+ export const fallbackPlacementsModifier: PopperJS.Modifier<typeof name, Options> = {
67
+ name: name,
68
+ enabled: true,
69
+ phase: 'main',
70
+ data: {
71
+ _skip: false,
72
+ },
73
+ fn({state, options}: PopperJS.ModifierArguments<Options>) {
74
+ const preferredPlacement = state.options.placement;
75
+ const {fallbackPlacements} = options;
76
+
77
+ const placements = [
78
+ preferredPlacement,
79
+ getOppositePlacement(preferredPlacement),
80
+ ...(fallbackPlacements || []),
81
+ ];
82
+
83
+ const placementOverride = choseAvailablePlacement(placements, state, preferredPlacement);
84
+ if (state.placement !== placementOverride) {
85
+ state.modifiersData[name]._skip = true;
86
+ state.placement = placementOverride;
87
+ state.reset = true;
88
+ }
89
+ },
90
+ };
@@ -9,14 +9,18 @@ import {Placement} from '../Popper';
9
9
  */
10
10
  export const usePopupPopper = createElemPropsHook(usePopupModel)(({state, events}, ref) => {
11
11
  const elementRef = useForkRef(ref, state.stackRef);
12
+
13
+ // Create a mutable ref of the placement to keep a stable `onPlacementChange` reference.
14
+ const placementRef = React.useRef(state.placement);
15
+ placementRef.current = state.placement;
12
16
  const onPlacementChange = React.useCallback(
13
17
  (placement: Placement) => {
14
- if (placement !== state.placement) {
18
+ if (placement !== placementRef.current) {
15
19
  // only update if the placement has changed
16
20
  events.updatePlacement({placement});
17
21
  }
18
22
  },
19
- [events, state.placement]
23
+ [events]
20
24
  );
21
25
 
22
26
  return {
@@ -1,6 +1,11 @@
1
1
  import * as React from 'react';
2
2
 
3
- import {getTransformFromPlacement, Placement, Popper} from '@workday/canvas-kit-react/popup';
3
+ import {
4
+ getTransformFromPlacement,
5
+ Placement,
6
+ Popper,
7
+ defaultFallbackPlacements,
8
+ } from '@workday/canvas-kit-react/popup';
4
9
  import {mergeCallback} from '@workday/canvas-kit-react/common';
5
10
 
6
11
  import {TooltipContainer} from './TooltipContainer';
@@ -77,6 +82,13 @@ export interface OverflowTooltipProps extends Omit<React.HTMLAttributes<HTMLDivE
77
82
  * @default 'top'
78
83
  */
79
84
  placement?: Placement;
85
+ /**
86
+ * Define fallback placements by providing a list of {@link Placement} in array (in order of preference).
87
+ * The default preference is following the order of `top`, `right`, `bottom`, and `left`. Once the initial
88
+ * and opposite placements are not available, the fallback placements will be in use. Use an empty array to
89
+ * disable the fallback placements.
90
+ */
91
+ fallbackPlacements?: Placement[];
80
92
  }
81
93
 
82
94
  function mergeCallbacks<T extends {[key: string]: any}>(
@@ -96,6 +108,7 @@ function mergeCallbacks<T extends {[key: string]: any}>(
96
108
 
97
109
  export const OverflowTooltip = ({
98
110
  placement = 'top',
111
+ fallbackPlacements = defaultFallbackPlacements,
99
112
  children,
100
113
  ...elemProps
101
114
  }: OverflowTooltipProps) => {
@@ -126,7 +139,7 @@ export const OverflowTooltip = ({
126
139
  'aria-label': undefined, // we don't need a label, the accessible name will be the content
127
140
  ...mergeCallbacks({...targetProps, onMouseEnter, onFocus}, children.props),
128
141
  })}
129
- <Popper placement={placement} {...popperProps}>
142
+ <Popper placement={placement} fallbackPlacements={fallbackPlacements} {...popperProps}>
130
143
  {({placement}) => {
131
144
  const transformOrigin = getTransformFromPlacement(placement);
132
145
  return (
@@ -1,7 +1,12 @@
1
1
  import * as React from 'react';
2
2
  import innerText from 'react-innertext';
3
3
 
4
- import {getTransformFromPlacement, Placement, Popper} from '@workday/canvas-kit-react/popup';
4
+ import {
5
+ getTransformFromPlacement,
6
+ Placement,
7
+ Popper,
8
+ defaultFallbackPlacements,
9
+ } from '@workday/canvas-kit-react/popup';
5
10
  import {createComponent, mergeCallback} from '@workday/canvas-kit-react/common';
6
11
 
7
12
  import {TooltipContainer} from './TooltipContainer';
@@ -27,6 +32,13 @@ export interface TooltipProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
27
32
  * @default 'top'
28
33
  */
29
34
  placement?: Placement;
35
+ /**
36
+ * Define fallback placements by providing a list of {@link Placement} in array (in order of preference).
37
+ * The default preference is following the order of `top`, `right`, `bottom`, and `left`. Once the initial
38
+ * and opposite placements are not available, the fallback placements will be in use. Use an empty array to
39
+ * disable the fallback placements.
40
+ */
41
+ fallbackPlacements?: Placement[];
30
42
  /**
31
43
  * Determines the tooltip type for accessibility.
32
44
  *
@@ -78,6 +90,7 @@ export const Tooltip = createComponent('div')({
78
90
  children,
79
91
  showDelay = 300,
80
92
  hideDelay = 100,
93
+ fallbackPlacements = defaultFallbackPlacements,
81
94
  ...elemProps
82
95
  }: TooltipProps) {
83
96
  const titleText = innerText(title);
@@ -97,7 +110,7 @@ export const Tooltip = createComponent('div')({
97
110
  ? {'aria-label': children.props['aria-label']}
98
111
  : {}),
99
112
  })}
100
- <Popper placement={placement} {...popperProps}>
113
+ <Popper placement={placement} fallbackPlacements={fallbackPlacements} {...popperProps}>
101
114
  {({placement}) => {
102
115
  const transformOrigin = getTransformFromPlacement(placement);
103
116
  return (