@reshaped/utilities 3.9.1-canary.2 → 3.10.0-canary.4

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 (100) hide show
  1. package/dist/a11y/Chain.d.ts +20 -0
  2. package/dist/a11y/Chain.js +60 -0
  3. package/dist/a11y/TrapFocus.d.ts +28 -0
  4. package/dist/a11y/TrapFocus.js +162 -0
  5. package/dist/a11y/TrapScreenReader.d.ts +15 -0
  6. package/dist/a11y/TrapScreenReader.js +42 -0
  7. package/dist/a11y/focus.d.ts +38 -0
  8. package/dist/a11y/focus.js +101 -0
  9. package/dist/a11y/index.d.ts +4 -0
  10. package/dist/a11y/index.js +3 -0
  11. package/dist/a11y/keyboardMode.d.ts +4 -0
  12. package/dist/a11y/keyboardMode.js +10 -0
  13. package/dist/a11y/tests/Chain.test.js +88 -0
  14. package/dist/a11y/tests/TrapFocus.test.d.ts +1 -0
  15. package/dist/a11y/tests/TrapFocus.test.js +313 -0
  16. package/dist/a11y/tests/TrapScreenReader.test.d.ts +1 -0
  17. package/dist/a11y/tests/TrapScreenReader.test.js +126 -0
  18. package/dist/a11y/tests/focus.test.d.ts +1 -0
  19. package/dist/a11y/tests/focus.test.js +278 -0
  20. package/dist/a11y/tests/keyboardMode.test.d.ts +1 -0
  21. package/dist/a11y/tests/keyboardMode.test.js +27 -0
  22. package/dist/a11y/types.d.ts +24 -0
  23. package/dist/a11y/types.js +1 -0
  24. package/dist/constants/keys.d.ts +11 -0
  25. package/dist/constants/keys.js +11 -0
  26. package/dist/css/StyleCache.d.ts +7 -0
  27. package/dist/css/StyleCache.js +19 -0
  28. package/dist/css/classNames.d.ts +7 -0
  29. package/dist/css/classNames.js +19 -0
  30. package/dist/css/index.d.ts +2 -0
  31. package/dist/css/index.js +4 -0
  32. package/dist/css/tests/StyleCache.test.d.ts +1 -0
  33. package/dist/css/tests/StyleCache.test.js +45 -0
  34. package/dist/css/tests/classNames.test.d.ts +1 -0
  35. package/dist/css/tests/classNames.test.js +63 -0
  36. package/dist/dom/findClosestScrollableContainer.d.ts +5 -0
  37. package/dist/dom/findClosestScrollableContainer.js +12 -0
  38. package/dist/dom/findParent.d.ts +2 -0
  39. package/dist/dom/findParent.js +10 -0
  40. package/dist/dom/index.d.ts +3 -0
  41. package/dist/dom/index.js +4 -0
  42. package/dist/dom/tests/findClosestScrollableContainer.test.d.ts +1 -0
  43. package/dist/dom/tests/findClosestScrollableContainer.test.js +61 -0
  44. package/dist/dom/tests/findParent.test.d.ts +1 -0
  45. package/dist/dom/tests/findParent.test.js +45 -0
  46. package/dist/flyout/Flyout.js +11 -9
  47. package/dist/flyout/constants.d.ts +1 -1
  48. package/dist/flyout/constants.js +1 -1
  49. package/dist/flyout/index.d.ts +1 -1
  50. package/dist/flyout/index.js +1 -1
  51. package/dist/flyout/tests/Flyout.test.js +1 -1
  52. package/dist/flyout/types.d.ts +1 -1
  53. package/dist/flyout/utilities/applyPosition.js +46 -26
  54. package/dist/flyout/utilities/calculateLayoutAdjustment.d.ts +19 -0
  55. package/dist/flyout/utilities/calculateLayoutAdjustment.js +73 -0
  56. package/dist/flyout/utilities/calculatePosition.d.ts +7 -20
  57. package/dist/flyout/utilities/calculatePosition.js +11 -87
  58. package/dist/flyout/utilities/isFullyVisible.d.ts +2 -4
  59. package/dist/flyout/utilities/isFullyVisible.js +11 -14
  60. package/dist/flyout/utilities/tests/applyPosition.test.js +1 -1
  61. package/dist/flyout/utilities/tests/calculateLayoutAdjustment.test.d.ts +1 -0
  62. package/dist/flyout/utilities/tests/calculateLayoutAdjustment.test.js +384 -0
  63. package/dist/flyout/utilities/tests/calculatePosition.test.js +244 -293
  64. package/dist/flyout/utilities/tests/centerBySize.test.js +1 -1
  65. package/dist/flyout/utilities/tests/getPositionFallbacks.test.js +1 -1
  66. package/dist/flyout/utilities/tests/getRTLPosition.test.js +1 -1
  67. package/dist/flyout/utilities/tests/isFullyVisible.test.js +28 -52
  68. package/dist/helpers/classNames.d.ts +7 -0
  69. package/dist/helpers/classNames.js +19 -0
  70. package/dist/helpers/index.d.ts +1 -0
  71. package/dist/helpers/index.js +2 -0
  72. package/dist/helpers/tests/classNames.test.d.ts +1 -0
  73. package/dist/helpers/tests/classNames.test.js +63 -0
  74. package/dist/i18n/index.d.ts +1 -0
  75. package/dist/i18n/index.js +2 -0
  76. package/dist/index.d.ts +5 -1
  77. package/dist/index.js +5 -1
  78. package/dist/internal.d.ts +11 -0
  79. package/dist/internal.js +10 -0
  80. package/dist/platform/index.d.ts +1 -0
  81. package/dist/platform/index.js +16 -0
  82. package/dist/scroll/disable.d.ts +7 -0
  83. package/dist/scroll/disable.js +15 -0
  84. package/dist/scroll/helpers.d.ts +1 -0
  85. package/dist/scroll/helpers.js +17 -0
  86. package/dist/scroll/index.d.ts +2 -0
  87. package/dist/scroll/index.js +4 -0
  88. package/dist/scroll/lock.d.ts +7 -0
  89. package/dist/scroll/lock.js +26 -0
  90. package/dist/scroll/lockSafari.d.ts +2 -0
  91. package/dist/scroll/lockSafari.js +20 -0
  92. package/dist/scroll/lockStandard.d.ts +4 -0
  93. package/dist/scroll/lockStandard.js +15 -0
  94. package/dist/scroll/tests/lock.test.d.ts +1 -0
  95. package/dist/scroll/tests/lock.test.js +81 -0
  96. package/package.json +6 -1
  97. package/dist/flyout/utilities/findClosestFixedContainer.d.ts +0 -5
  98. package/dist/flyout/utilities/findClosestFixedContainer.js +0 -18
  99. package/dist/flyout/utilities/tests/findClosestFixedContainer.test.js +0 -46
  100. /package/dist/{flyout/utilities/tests/findClosestFixedContainer.test.d.ts → a11y/tests/Chain.test.d.ts} +0 -0
@@ -1,6 +1,5 @@
1
- import { expect, test, describe, vi } from "vitest";
2
- import { CONTAINER_OFFSET } from "../../constants.js";
3
- import calculatePosition from "../calculatePosition.js";
1
+ import { expect, test, describe, vi, beforeEach } from "vitest";
2
+ import calculatePosition from "flyout/utilities/calculatePosition";
4
3
  describe("flyout/calculatePosition", () => {
5
4
  const createBounds = (left, top, width, height) => {
6
5
  return {
@@ -15,522 +14,474 @@ describe("flyout/calculatePosition", () => {
15
14
  toJSON: vi.fn(),
16
15
  };
17
16
  };
18
- test("calculates position for top placement", () => {
17
+ beforeEach(() => {
18
+ // Set consistent viewport dimensions for tests
19
+ Object.defineProperty(window, "innerWidth", { value: 1000, writable: true });
20
+ Object.defineProperty(window, "innerHeight", { value: 800, writable: true });
21
+ });
22
+ test("calculates position for 'top'", () => {
19
23
  const triggerBounds = createBounds(100, 200, 50, 30);
20
24
  const flyoutBounds = createBounds(0, 0, 40, 60);
21
- const containerBounds = createBounds(0, 0, 1000, 800);
22
25
  const result = calculatePosition({
23
26
  triggerBounds,
24
27
  flyoutBounds,
25
- containerBounds,
26
28
  position: "top",
27
29
  rtl: false,
28
30
  contentGap: 0,
29
31
  contentShift: 0,
30
32
  });
31
- // top position: above trigger, centered horizontally
32
- // relativeTop = 200 - 0 = 200, top = 200 - 60 = 140
33
- // relativeLeft = 100 - 0 = 100, left = 100 + centerBySize(50, 40) = 105
34
- expect(result.boundaries.top).toBe(140);
35
- expect(result.boundaries.left).toBe(105);
36
- // top position sets bottom, so top style is null and bottom is "0px"
37
- expect(result.styles.top).toBe(null);
38
- expect(result.styles.bottom).toBe("0px");
39
- expect(result.styles.left).toBe("0px");
33
+ // top: relativeTop - flyoutHeight - contentGap = 200 - 60 - 0 = 140
34
+ // left: relativeLeft + centerBySize(triggerWidth, flyoutWidth) = 100 + (50/2 - 40/2) = 105
35
+ // bottom: relativeBottom + triggerHeight + contentGap = (800 - 230) + 30 + 0 = 600
36
+ // right: null
37
+ expect(result.position).toBe("top");
38
+ expect(result.styles.top).toBe(140);
39
+ expect(result.styles.left).toBe(105);
40
+ expect(result.styles.bottom).toBe(600);
40
41
  expect(result.styles.right).toBe(null);
41
42
  });
42
- test("calculates position for bottom, start, and end placements", () => {
43
+ test("calculates position for 'bottom'", () => {
43
44
  const triggerBounds = createBounds(100, 200, 50, 30);
44
45
  const flyoutBounds = createBounds(0, 0, 40, 60);
45
- const containerBounds = createBounds(0, 0, 1000, 800);
46
- const bottom = calculatePosition({
46
+ const result = calculatePosition({
47
47
  triggerBounds,
48
48
  flyoutBounds,
49
- containerBounds,
50
49
  position: "bottom",
51
50
  rtl: false,
52
51
  contentGap: 0,
53
52
  contentShift: 0,
54
53
  });
55
- // relativeTop = 200, top = 200 + 30 = 230
56
- // relativeLeft = 100, left = 100 + centerBySize(50, 40) = 105
57
- expect(bottom.boundaries.top).toBe(230);
58
- expect(bottom.boundaries.left).toBe(105);
59
- const start = calculatePosition({
54
+ // top: relativeTop + triggerHeight + contentGap = 200 + 30 + 0 = 230
55
+ // left: relativeLeft + centerBySize(triggerWidth, flyoutWidth) = 100 + 5 = 105
56
+ // bottom: null
57
+ // right: null
58
+ expect(result.position).toBe("bottom");
59
+ expect(result.styles.top).toBe(230);
60
+ expect(result.styles.left).toBe(105);
61
+ expect(result.styles.bottom).toBe(null);
62
+ expect(result.styles.right).toBe(null);
63
+ });
64
+ test("calculates position for 'start'", () => {
65
+ const triggerBounds = createBounds(100, 200, 50, 30);
66
+ const flyoutBounds = createBounds(0, 0, 40, 60);
67
+ const result = calculatePosition({
60
68
  triggerBounds,
61
69
  flyoutBounds,
62
- containerBounds,
63
70
  position: "start",
64
71
  rtl: false,
65
72
  contentGap: 0,
66
73
  contentShift: 0,
67
74
  });
68
- // relativeLeft = 100, left = 100 - 40 = 60
69
- // relativeTop = 200, top = 200 + centerBySize(30, 60) = 200 - 15 = 185
70
- expect(start.boundaries.left).toBe(60);
71
- expect(start.boundaries.top).toBe(185);
72
- const end = calculatePosition({
75
+ // left: relativeLeft - flyoutWidth - contentGap = 100 - 40 - 0 = 60
76
+ // right: relativeRight + triggerWidth + contentGap = (1000 - 150) + 50 + 0 = 900
77
+ // top: relativeTop + centerBySize(triggerHeight, flyoutHeight) = 200 + (30/2 - 60/2) = 185
78
+ // bottom: null
79
+ expect(result.position).toBe("start");
80
+ expect(result.styles.left).toBe(60);
81
+ expect(result.styles.right).toBe(900);
82
+ expect(result.styles.top).toBe(185);
83
+ expect(result.styles.bottom).toBe(null);
84
+ });
85
+ test("calculates position for 'end'", () => {
86
+ const triggerBounds = createBounds(100, 200, 50, 30);
87
+ const flyoutBounds = createBounds(0, 0, 40, 60);
88
+ const result = calculatePosition({
73
89
  triggerBounds,
74
90
  flyoutBounds,
75
- containerBounds,
76
91
  position: "end",
77
92
  rtl: false,
78
93
  contentGap: 0,
79
94
  contentShift: 0,
80
95
  });
81
- // relativeLeft = 100, left = 100 + 50 = 150
82
- // relativeTop = 200, top = 200 + centerBySize(30, 60) = 185
83
- expect(end.boundaries.left).toBe(150);
84
- expect(end.boundaries.top).toBe(185);
96
+ // left: relativeLeft + triggerWidth + contentGap = 100 + 50 + 0 = 150
97
+ // right: null
98
+ // top: relativeTop + centerBySize(triggerHeight, flyoutHeight) = 200 + (30/2 - 60/2) = 185
99
+ // bottom: null
100
+ expect(result.position).toBe("end");
101
+ expect(result.styles.left).toBe(150);
102
+ expect(result.styles.right).toBe(null);
103
+ expect(result.styles.top).toBe(185);
104
+ expect(result.styles.bottom).toBe(null);
85
105
  });
86
- test("calculates position for top-start, top-end, bottom-start, and bottom-end", () => {
106
+ test("calculates position for 'top-start'", () => {
87
107
  const triggerBounds = createBounds(100, 200, 50, 30);
88
108
  const flyoutBounds = createBounds(0, 0, 40, 60);
89
- const containerBounds = createBounds(0, 0, 1000, 800);
90
- const topStart = calculatePosition({
109
+ const result = calculatePosition({
91
110
  triggerBounds,
92
111
  flyoutBounds,
93
- containerBounds,
94
112
  position: "top-start",
95
113
  rtl: false,
96
114
  contentGap: 0,
97
115
  contentShift: 0,
98
116
  });
99
- // relativeTop = 200, top = 200 - 60 = 140
100
- // relativeLeft = 100, left = 100
101
- expect(topStart.boundaries.top).toBe(140);
102
- expect(topStart.boundaries.left).toBe(100);
103
- const topEnd = calculatePosition({
117
+ // left: relativeLeft + contentShift = 100 + 0 = 100
118
+ // top: relativeTop - flyoutHeight - contentGap = 200 - 60 - 0 = 140
119
+ // bottom: relativeBottom + triggerHeight + contentGap = 570 + 30 + 0 = 600
120
+ // right: null
121
+ expect(result.position).toBe("top-start");
122
+ expect(result.styles.left).toBe(100);
123
+ expect(result.styles.top).toBe(140);
124
+ expect(result.styles.bottom).toBe(600);
125
+ expect(result.styles.right).toBe(null);
126
+ });
127
+ test("calculates position for 'top-end'", () => {
128
+ const triggerBounds = createBounds(100, 200, 50, 30);
129
+ const flyoutBounds = createBounds(0, 0, 40, 60);
130
+ const result = calculatePosition({
104
131
  triggerBounds,
105
132
  flyoutBounds,
106
- containerBounds,
107
133
  position: "top-end",
108
134
  rtl: false,
109
135
  contentGap: 0,
110
136
  contentShift: 0,
111
137
  });
112
- // relativeTop = 200, top = 200 - 60 = 140
113
- // relativeLeft = 100, left = 100 + 50 - 40 = 110
114
- // relativeRight = 1000 - 150 = 850, right = 850 - 0 = 850 (internal, not in boundaries)
115
- expect(topEnd.boundaries.top).toBe(140);
116
- expect(topEnd.boundaries.left).toBe(110);
117
- const bottomStart = calculatePosition({
138
+ // left: relativeLeft + triggerWidth - flyoutWidth + contentShift = 100 + 50 - 40 + 0 = 110
139
+ // right: relativeRight - contentShift = 850 - 0 = 850
140
+ // top: relativeTop - flyoutHeight - contentGap = 200 - 60 - 0 = 140
141
+ // bottom: relativeBottom + triggerHeight + contentGap = 570 + 30 + 0 = 600
142
+ expect(result.position).toBe("top-end");
143
+ expect(result.styles.left).toBe(110);
144
+ expect(result.styles.right).toBe(850);
145
+ expect(result.styles.top).toBe(140);
146
+ expect(result.styles.bottom).toBe(600);
147
+ });
148
+ test("calculates position for 'bottom-start'", () => {
149
+ const triggerBounds = createBounds(100, 200, 50, 30);
150
+ const flyoutBounds = createBounds(0, 0, 40, 60);
151
+ const result = calculatePosition({
118
152
  triggerBounds,
119
153
  flyoutBounds,
120
- containerBounds,
121
154
  position: "bottom-start",
122
155
  rtl: false,
123
156
  contentGap: 0,
124
157
  contentShift: 0,
125
158
  });
126
- // relativeTop = 200, top = 200 + 30 = 230
127
- // relativeLeft = 100, left = 100
128
- expect(bottomStart.boundaries.top).toBe(230);
129
- expect(bottomStart.boundaries.left).toBe(100);
130
- const bottomEnd = calculatePosition({
159
+ // left: relativeLeft + contentShift = 100 + 0 = 100
160
+ // top: relativeTop + triggerHeight + contentGap = 200 + 30 + 0 = 230
161
+ // bottom: null
162
+ // right: null
163
+ expect(result.position).toBe("bottom-start");
164
+ expect(result.styles.left).toBe(100);
165
+ expect(result.styles.top).toBe(230);
166
+ expect(result.styles.bottom).toBe(null);
167
+ expect(result.styles.right).toBe(null);
168
+ });
169
+ test("calculates position for 'bottom-end'", () => {
170
+ const triggerBounds = createBounds(100, 200, 50, 30);
171
+ const flyoutBounds = createBounds(0, 0, 40, 60);
172
+ const result = calculatePosition({
131
173
  triggerBounds,
132
174
  flyoutBounds,
133
- containerBounds,
134
175
  position: "bottom-end",
135
176
  rtl: false,
136
177
  contentGap: 0,
137
178
  contentShift: 0,
138
179
  });
139
- // relativeTop = 200, top = 200 + 30 = 230
140
- // relativeLeft = 100, left = 100 + 50 - 40 = 110
141
- expect(bottomEnd.boundaries.top).toBe(230);
142
- expect(bottomEnd.boundaries.left).toBe(110);
180
+ // left: relativeLeft + triggerWidth - flyoutWidth + contentShift = 100 + 50 - 40 + 0 = 110
181
+ // right: relativeRight - contentShift = 850 - 0 = 850
182
+ // top: relativeTop + triggerHeight + contentGap = 200 + 30 + 0 = 230
183
+ // bottom: null
184
+ expect(result.position).toBe("bottom-end");
185
+ expect(result.styles.left).toBe(110);
186
+ expect(result.styles.right).toBe(850);
187
+ expect(result.styles.top).toBe(230);
188
+ expect(result.styles.bottom).toBe(null);
143
189
  });
144
- test("calculates position for start-top, start-bottom, end-top, and end-bottom", () => {
190
+ test("calculates position for 'start-top'", () => {
145
191
  const triggerBounds = createBounds(100, 200, 50, 30);
146
192
  const flyoutBounds = createBounds(0, 0, 40, 60);
147
- const containerBounds = createBounds(0, 0, 1000, 800);
148
- const startTop = calculatePosition({
193
+ const result = calculatePosition({
149
194
  triggerBounds,
150
195
  flyoutBounds,
151
- containerBounds,
152
196
  position: "start-top",
153
197
  rtl: false,
154
198
  contentGap: 0,
155
199
  contentShift: 0,
156
200
  });
157
- // relativeLeft = 100, left = 100 - 40 = 60
158
- // relativeTop = 200, top = 200
159
- expect(startTop.boundaries.left).toBe(60);
160
- expect(startTop.boundaries.top).toBe(200);
161
- const startBottom = calculatePosition({
201
+ // left: relativeLeft - flyoutWidth - contentGap = 100 - 40 - 0 = 60
202
+ // right: relativeRight + triggerWidth + contentGap = 850 + 50 + 0 = 900
203
+ // top: relativeTop + contentShift = 200 + 0 = 200
204
+ // bottom: null
205
+ expect(result.position).toBe("start-top");
206
+ expect(result.styles.left).toBe(60);
207
+ expect(result.styles.right).toBe(900);
208
+ expect(result.styles.top).toBe(200);
209
+ expect(result.styles.bottom).toBe(null);
210
+ });
211
+ test("calculates position for 'start-bottom'", () => {
212
+ const triggerBounds = createBounds(100, 200, 50, 30);
213
+ const flyoutBounds = createBounds(0, 0, 40, 60);
214
+ const result = calculatePosition({
162
215
  triggerBounds,
163
216
  flyoutBounds,
164
- containerBounds,
165
217
  position: "start-bottom",
166
218
  rtl: false,
167
219
  contentGap: 0,
168
220
  contentShift: 0,
169
221
  });
170
- // relativeLeft = 100, left = 100 - 40 = 60
171
- // relativeTop = 200, top = 200 + 30 - 60 = 170
172
- // relativeRight = 1000 - 150 = 850, right = 850 - 0 = 850 (internal)
173
- expect(startBottom.boundaries.left).toBe(60);
174
- expect(startBottom.boundaries.top).toBe(170);
175
- const endTop = calculatePosition({
222
+ // left: relativeLeft - flyoutWidth - contentGap = 100 - 40 - 0 = 60
223
+ // right: relativeRight + triggerWidth + contentGap = 850 + 50 + 0 = 900
224
+ // top: relativeTop + triggerHeight - flyoutHeight + contentShift = 200 + 30 - 60 + 0 = 170
225
+ // bottom: relativeBottom - contentShift = 570 - 0 = 570
226
+ expect(result.position).toBe("start-bottom");
227
+ expect(result.styles.left).toBe(60);
228
+ expect(result.styles.right).toBe(900);
229
+ expect(result.styles.top).toBe(170);
230
+ expect(result.styles.bottom).toBe(570);
231
+ });
232
+ test("calculates position for 'end-top'", () => {
233
+ const triggerBounds = createBounds(100, 200, 50, 30);
234
+ const flyoutBounds = createBounds(0, 0, 40, 60);
235
+ const result = calculatePosition({
176
236
  triggerBounds,
177
237
  flyoutBounds,
178
- containerBounds,
179
238
  position: "end-top",
180
239
  rtl: false,
181
240
  contentGap: 0,
182
241
  contentShift: 0,
183
242
  });
184
- // relativeLeft = 100, left = 100 + 50 = 150
185
- // relativeTop = 200, top = 200
186
- expect(endTop.boundaries.left).toBe(150);
187
- expect(endTop.boundaries.top).toBe(200);
188
- const endBottom = calculatePosition({
243
+ // left: relativeLeft + triggerWidth + contentGap = 100 + 50 + 0 = 150
244
+ // right: null
245
+ // top: relativeTop + contentShift = 200 + 0 = 200
246
+ // bottom: null
247
+ expect(result.position).toBe("end-top");
248
+ expect(result.styles.left).toBe(150);
249
+ expect(result.styles.right).toBe(null);
250
+ expect(result.styles.top).toBe(200);
251
+ expect(result.styles.bottom).toBe(null);
252
+ });
253
+ test("calculates position for 'end-bottom'", () => {
254
+ const triggerBounds = createBounds(100, 200, 50, 30);
255
+ const flyoutBounds = createBounds(0, 0, 40, 60);
256
+ const result = calculatePosition({
189
257
  triggerBounds,
190
258
  flyoutBounds,
191
- containerBounds,
192
259
  position: "end-bottom",
193
260
  rtl: false,
194
261
  contentGap: 0,
195
262
  contentShift: 0,
196
263
  });
197
- // relativeLeft = 100, left = 100 + 50 = 150
198
- // relativeTop = 200, top = 200 + 30 - 60 = 170
199
- expect(endBottom.boundaries.left).toBe(150);
200
- expect(endBottom.boundaries.top).toBe(170);
264
+ // left: relativeLeft + triggerWidth + contentGap = 100 + 50 + 0 = 150
265
+ // right: null
266
+ // top: relativeTop + triggerHeight - flyoutHeight + contentShift = 200 + 30 - 60 + 0 = 170
267
+ // bottom: relativeBottom - contentShift = 570 - 0 = 570
268
+ expect(result.position).toBe("end-bottom");
269
+ expect(result.styles.left).toBe(150);
270
+ expect(result.styles.right).toBe(null);
271
+ expect(result.styles.top).toBe(170);
272
+ expect(result.styles.bottom).toBe(570);
201
273
  });
202
- test("applies contentGap correctly", () => {
274
+ test("applies contentGap for vertical positions", () => {
203
275
  const triggerBounds = createBounds(100, 200, 50, 30);
204
276
  const flyoutBounds = createBounds(0, 0, 40, 60);
205
- const containerBounds = createBounds(0, 0, 1000, 800);
206
277
  const top = calculatePosition({
207
278
  triggerBounds,
208
279
  flyoutBounds,
209
- containerBounds,
210
280
  position: "top",
211
281
  rtl: false,
212
282
  contentGap: 10,
213
283
  contentShift: 0,
214
284
  });
215
- // relativeTop = 200, top = 200 - 60 - 10 = 130
216
- expect(top.boundaries.top).toBe(130);
285
+ // top: relativeTop - flyoutHeight - contentGap = 200 - 60 - 10 = 130
286
+ // bottom: relativeBottom + triggerHeight + contentGap = 570 + 30 + 10 = 610
287
+ expect(top.styles.top).toBe(130);
288
+ expect(top.styles.bottom).toBe(610);
217
289
  const bottom = calculatePosition({
218
290
  triggerBounds,
219
291
  flyoutBounds,
220
- containerBounds,
221
292
  position: "bottom",
222
293
  rtl: false,
223
294
  contentGap: 10,
224
295
  contentShift: 0,
225
296
  });
226
- // relativeTop = 200, top = 200 + 30 + 10 = 240
227
- expect(bottom.boundaries.top).toBe(240);
297
+ // top: relativeTop + triggerHeight + contentGap = 200 + 30 + 10 = 240
298
+ expect(bottom.styles.top).toBe(240);
299
+ });
300
+ test("applies contentGap for horizontal positions", () => {
301
+ const triggerBounds = createBounds(100, 200, 50, 30);
302
+ const flyoutBounds = createBounds(0, 0, 40, 60);
228
303
  const start = calculatePosition({
229
304
  triggerBounds,
230
305
  flyoutBounds,
231
- containerBounds,
232
306
  position: "start",
233
307
  rtl: false,
234
308
  contentGap: 10,
235
309
  contentShift: 0,
236
310
  });
237
- // relativeLeft = 100, left = 100 - 40 - 10 = 50
238
- // relativeRight = 850, right = 850 + 50 + 10 = 910 (internal)
239
- expect(start.boundaries.left).toBe(50);
311
+ // left: relativeLeft - flyoutWidth - contentGap = 100 - 40 - 10 = 50
312
+ // right: relativeRight + triggerWidth + contentGap = 850 + 50 + 10 = 910
313
+ expect(start.styles.left).toBe(50);
314
+ expect(start.styles.right).toBe(910);
240
315
  const end = calculatePosition({
241
316
  triggerBounds,
242
317
  flyoutBounds,
243
- containerBounds,
244
318
  position: "end",
245
319
  rtl: false,
246
320
  contentGap: 10,
247
321
  contentShift: 0,
248
322
  });
249
- // relativeLeft = 100, left = 100 + 50 + 10 = 160
250
- expect(end.boundaries.left).toBe(160);
323
+ // left: relativeLeft + triggerWidth + contentGap = 100 + 50 + 10 = 160
324
+ expect(end.styles.left).toBe(160);
251
325
  });
252
- test("applies contentShift correctly", () => {
326
+ test("applies contentShift for centered positions", () => {
253
327
  const triggerBounds = createBounds(100, 200, 50, 30);
254
328
  const flyoutBounds = createBounds(0, 0, 40, 60);
255
- const containerBounds = createBounds(0, 0, 1000, 800);
256
329
  const top = calculatePosition({
257
330
  triggerBounds,
258
331
  flyoutBounds,
259
- containerBounds,
260
332
  position: "top",
261
333
  rtl: false,
262
334
  contentGap: 0,
263
- contentShift: 5,
335
+ contentShift: 10,
264
336
  });
265
- // relativeLeft = 100, left = 100 + centerBySize(50, 40) + 5 = 100 + 5 + 5 = 110
266
- expect(top.boundaries.left).toBe(110);
267
- const topStart = calculatePosition({
268
- triggerBounds,
269
- flyoutBounds,
270
- containerBounds,
271
- position: "top-start",
272
- rtl: false,
273
- contentGap: 0,
274
- contentShift: 5,
275
- });
276
- // relativeLeft = 100, left = 100 + 5 = 105
277
- expect(topStart.boundaries.left).toBe(105);
337
+ // left: relativeLeft + centerBySize(50, 40) + contentShift = 100 + 5 + 10 = 115
338
+ expect(top.styles.left).toBe(115);
278
339
  const start = calculatePosition({
279
340
  triggerBounds,
280
341
  flyoutBounds,
281
- containerBounds,
282
342
  position: "start",
283
343
  rtl: false,
284
344
  contentGap: 0,
285
- contentShift: 5,
345
+ contentShift: 10,
286
346
  });
287
- // relativeTop = 200, top = 200 + centerBySize(30, 60) + 5 = 200 - 15 + 5 = 190
288
- expect(start.boundaries.top).toBe(190);
347
+ // top: relativeTop + centerBySize(30, 60) + contentShift = 200 + (-15) + 10 = 195
348
+ expect(start.styles.top).toBe(195);
289
349
  });
290
- test("handles RTL by converting positions", () => {
350
+ test("applies contentShift for aligned positions", () => {
291
351
  const triggerBounds = createBounds(100, 200, 50, 30);
292
352
  const flyoutBounds = createBounds(0, 0, 40, 60);
293
- const containerBounds = createBounds(0, 0, 1000, 800);
294
- // In RTL, "start" should behave like "end" in LTR
295
- const startRTL = calculatePosition({
296
- triggerBounds,
297
- flyoutBounds,
298
- containerBounds,
299
- position: "start",
300
- rtl: true,
301
- contentGap: 0,
302
- contentShift: 0,
303
- });
304
- const endLTR = calculatePosition({
305
- triggerBounds,
306
- flyoutBounds,
307
- containerBounds,
308
- position: "end",
309
- rtl: false,
310
- contentGap: 0,
311
- contentShift: 0,
312
- });
313
- expect(startRTL.boundaries.left).toBe(endLTR.boundaries.left);
314
- expect(startRTL.position).toBe("end");
315
- // In RTL, "top-start" should behave like "top-end" in LTR
316
- const topStartRTL = calculatePosition({
353
+ const topStart = calculatePosition({
317
354
  triggerBounds,
318
355
  flyoutBounds,
319
- containerBounds,
320
356
  position: "top-start",
321
- rtl: true,
357
+ rtl: false,
322
358
  contentGap: 0,
323
- contentShift: 0,
359
+ contentShift: 5,
324
360
  });
325
- const topEndLTR = calculatePosition({
361
+ // left: relativeLeft + contentShift = 100 + 5 = 105
362
+ expect(topStart.styles.left).toBe(105);
363
+ const topEnd = calculatePosition({
326
364
  triggerBounds,
327
365
  flyoutBounds,
328
- containerBounds,
329
366
  position: "top-end",
330
367
  rtl: false,
331
368
  contentGap: 0,
332
- contentShift: 0,
369
+ contentShift: 5,
333
370
  });
334
- expect(topStartRTL.boundaries.left).toBe(topEndLTR.boundaries.left);
335
- expect(topStartRTL.position).toBe("top-end");
336
- });
337
- test("handles container scrolling", () => {
338
- const triggerBounds = createBounds(100, 200, 50, 30);
339
- const flyoutBounds = createBounds(0, 0, 40, 60);
340
- const containerBounds = createBounds(0, 0, 1000, 800);
341
- const container = {
342
- scrollLeft: 50,
343
- scrollTop: 100,
344
- clientWidth: 1000,
345
- clientHeight: 800,
346
- };
347
- const result = calculatePosition({
371
+ // left: relativeLeft + triggerWidth - flyoutWidth + contentShift = 100 + 50 - 40 + 5 = 115
372
+ // right: relativeRight - contentShift = 850 - 5 = 845
373
+ expect(topEnd.styles.left).toBe(115);
374
+ expect(topEnd.styles.right).toBe(845);
375
+ const startTop = calculatePosition({
348
376
  triggerBounds,
349
377
  flyoutBounds,
350
- containerBounds,
351
- passedContainer: container,
352
- position: "top",
378
+ position: "start-top",
353
379
  rtl: false,
354
380
  contentGap: 0,
355
- contentShift: 0,
381
+ contentShift: 5,
356
382
  });
357
- // With container scroll, relativeLeft should account for scrollLeft
358
- // relativeLeft = triggerBounds.left - containerBounds.left + containerX
359
- // = 100 - 0 + 50 = 150
360
- // So left = 150 + centerBySize(50, 40) = 155
361
- expect(result.boundaries.left).toBe(150 + 5);
362
- });
363
- test("handles window scrolling when no container is provided", () => {
364
- Object.defineProperty(window, "scrollX", { value: 100, writable: true });
365
- Object.defineProperty(window, "scrollY", { value: 200, writable: true });
366
- const triggerBounds = createBounds(100, 200, 50, 30);
367
- const flyoutBounds = createBounds(0, 0, 40, 60);
368
- const containerBounds = createBounds(0, 0, 1000, 800);
369
- const result = calculatePosition({
383
+ // top: relativeTop + contentShift = 200 + 5 = 205
384
+ expect(startTop.styles.top).toBe(205);
385
+ const startBottom = calculatePosition({
370
386
  triggerBounds,
371
387
  flyoutBounds,
372
- containerBounds,
373
- position: "top",
388
+ position: "start-bottom",
374
389
  rtl: false,
375
390
  contentGap: 0,
376
- contentShift: 0,
391
+ contentShift: 5,
377
392
  });
378
- // Without container, containerX/Y are undefined, so relativeLeft uses 0
379
- // relativeLeft = triggerBounds.left - containerBounds.left + 0 = 100
380
- // left = 100 + centerBySize(50, 40) = 105
381
- // Window scrollX/Y only affect overflow calculations, not position calculations
382
- expect(result.boundaries.left).toBe(105);
383
- Object.defineProperty(window, "scrollX", { value: 0, writable: true });
384
- Object.defineProperty(window, "scrollY", { value: 0, writable: true });
393
+ // top: relativeTop + triggerHeight - flyoutHeight + contentShift = 200 + 30 - 60 + 5 = 175
394
+ // bottom: relativeBottom - contentShift = 570 - 5 = 565
395
+ expect(startBottom.styles.top).toBe(175);
396
+ expect(startBottom.styles.bottom).toBe(565);
385
397
  });
386
- test("calculates transform correctly for left and right positioning", () => {
398
+ test("converts positions for RTL", () => {
387
399
  const triggerBounds = createBounds(100, 200, 50, 30);
388
400
  const flyoutBounds = createBounds(0, 0, 40, 60);
389
- const containerBounds = createBounds(0, 0, 1000, 800);
390
- const start = calculatePosition({
401
+ // 'start' in RTL should behave like 'end' in LTR
402
+ const startRTL = calculatePosition({
391
403
  triggerBounds,
392
404
  flyoutBounds,
393
- containerBounds,
394
405
  position: "start",
395
- rtl: false,
406
+ rtl: true,
396
407
  contentGap: 0,
397
408
  contentShift: 0,
398
409
  });
399
- // start position sets right internally, so translateX should be -right
400
- // right = relativeRight + triggerWidth = 850 + 50 = 900
401
- // translateX = -900
402
- expect(start.styles.right).toBe("0px");
403
- expect(start.styles.left).toBe(null);
404
- expect(start.styles.transform).toContain("translate(-900px");
405
- const end = calculatePosition({
410
+ const endLTR = calculatePosition({
406
411
  triggerBounds,
407
412
  flyoutBounds,
408
- containerBounds,
409
413
  position: "end",
410
414
  rtl: false,
411
415
  contentGap: 0,
412
416
  contentShift: 0,
413
417
  });
414
- // end position doesn't set right, so translateX should be left
415
- // left = 150, translateX = 150
416
- expect(end.styles.right).toBe(null);
417
- expect(end.styles.left).toBe("0px");
418
- expect(end.styles.transform).toContain("translate(150px");
419
- });
420
- test("calculates transform correctly for top and bottom positioning", () => {
421
- const triggerTop = 200;
422
- const triggerHeight = 30;
423
- const triggerBounds = createBounds(100, triggerTop, 50, triggerHeight);
424
- const flyoutBounds = createBounds(0, 0, 40, 60);
425
- const containerBounds = createBounds(0, 0, 1000, 800);
426
- const top = calculatePosition({
418
+ expect(startRTL.position).toBe("end");
419
+ expect(startRTL.styles).toEqual(endLTR.styles);
420
+ // 'top-start' in RTL should behave like 'top-end' in LTR
421
+ const topStartRTL = calculatePosition({
427
422
  triggerBounds,
428
423
  flyoutBounds,
429
- containerBounds,
430
- position: "top",
431
- rtl: false,
424
+ position: "top-start",
425
+ rtl: true,
432
426
  contentGap: 0,
433
427
  contentShift: 0,
434
428
  });
435
- // top position sets bottom internally, so translateY should be -bottom
436
- // containerBoundsBottom = 800, relativeBottom = 800 - 230 = 570
437
- expect(top.styles.bottom).toBe("0px");
438
- expect(top.styles.top).toBe(null);
439
- expect(top.styles.transform).toBe(`translate(105px, ${triggerTop - window.innerHeight}px)`);
440
- const bottom = calculatePosition({
429
+ const topEndLTR = calculatePosition({
441
430
  triggerBounds,
442
431
  flyoutBounds,
443
- containerBounds,
444
- position: "bottom",
432
+ position: "top-end",
445
433
  rtl: false,
446
434
  contentGap: 0,
447
435
  contentShift: 0,
448
436
  });
449
- // bottom position doesn't set bottom, so translateY should be top
450
- // top = 230, translateY = 230
451
- expect(bottom.styles.bottom).toBe(null);
452
- expect(bottom.styles.top).toBe("0px");
453
- expect(bottom.styles.transform).toBe(`translate(105px, ${triggerTop + triggerHeight}px)`);
454
- });
455
- test("handles width option '100%'", () => {
456
- const triggerBounds = createBounds(100, 200, 50, 30);
457
- const flyoutBounds = createBounds(0, 0, 40, 60);
458
- const containerBounds = createBounds(0, 0, 1000, 800);
459
- const result = calculatePosition({
437
+ expect(topStartRTL.position).toBe("top-end");
438
+ expect(topStartRTL.styles).toEqual(topEndLTR.styles);
439
+ // 'end-bottom' in RTL should behave like 'start-bottom' in LTR
440
+ const endBottomRTL = calculatePosition({
460
441
  triggerBounds,
461
442
  flyoutBounds,
462
- containerBounds,
463
- position: "top",
464
- rtl: false,
465
- width: "100%",
443
+ position: "end-bottom",
444
+ rtl: true,
466
445
  contentGap: 0,
467
446
  contentShift: 0,
468
447
  });
469
- expect(result.boundaries.left).toBe(CONTAINER_OFFSET);
470
- expect(result.boundaries.width).toBe(window.innerWidth - CONTAINER_OFFSET * 2);
471
- expect(result.styles.width).toBe(`${window.innerWidth - CONTAINER_OFFSET * 2}px`);
472
- });
473
- test("handles width option 'trigger'", () => {
474
- const triggerBounds = createBounds(100, 200, 50, 30);
475
- const flyoutBounds = createBounds(0, 0, 40, 60);
476
- const containerBounds = createBounds(0, 0, 1000, 800);
477
- const result = calculatePosition({
448
+ const startBottomLTR = calculatePosition({
478
449
  triggerBounds,
479
450
  flyoutBounds,
480
- containerBounds,
481
- position: "top",
451
+ position: "start-bottom",
482
452
  rtl: false,
483
- width: "trigger",
484
453
  contentGap: 0,
485
454
  contentShift: 0,
486
455
  });
487
- expect(result.boundaries.width).toBe(50);
488
- expect(result.styles.width).toBe("50px");
456
+ expect(endBottomRTL.position).toBe("start-bottom");
457
+ expect(endBottomRTL.styles).toEqual(startBottomLTR.styles);
489
458
  });
490
- test("handles fallbackAdjustLayout for vertical positions with horizontal overflow", () => {
491
- const triggerBounds = createBounds(0, 200, 50, 30); // At left edge
459
+ test("handles different viewport sizes", () => {
460
+ const triggerBounds = createBounds(100, 200, 50, 30);
492
461
  const flyoutBounds = createBounds(0, 0, 40, 60);
493
- const containerBounds = createBounds(0, 0, 1000, 800);
494
- Object.defineProperty(window, "scrollX", { value: 0, writable: true });
495
- Object.defineProperty(window, "scrollY", { value: 0, writable: true });
462
+ Object.defineProperty(window, "innerWidth", { value: 1920, writable: true });
463
+ Object.defineProperty(window, "innerHeight", { value: 1080, writable: true });
496
464
  const result = calculatePosition({
497
465
  triggerBounds,
498
466
  flyoutBounds,
499
- containerBounds,
500
- position: "top", // Vertical position - checks horizontal overflow
467
+ position: "start",
501
468
  rtl: false,
502
469
  contentGap: 0,
503
470
  contentShift: 0,
504
- fallbackAdjustLayout: true,
505
471
  });
506
- // left = 0 + centerBySize(50, 40) = 5
507
- // overflow.left = -5 + 0 + 8 = 3 > 0, so left = 8
508
- expect(result.boundaries.left).toBe(CONTAINER_OFFSET);
509
- });
510
- test("handles fallbackAdjustLayout for vertical positions with overflow and height adjustment", () => {
511
- const triggerBounds = createBounds(100, 5, 50, 30); // Very close to top
512
- const flyoutBounds = createBounds(0, 0, 40, 200); // Large flyout
513
- const containerBounds = createBounds(0, 0, 1000, 800);
514
- Object.defineProperty(window, "scrollX", { value: 0, writable: true });
515
- Object.defineProperty(window, "scrollY", { value: 0, writable: true });
516
- const result = calculatePosition({
472
+ // relativeRight = 1920 - 150 = 1770
473
+ // right: relativeRight + triggerWidth + contentGap = 1770 + 50 + 0 = 1820
474
+ expect(result.styles.right).toBe(1820);
475
+ const result2 = calculatePosition({
517
476
  triggerBounds,
518
477
  flyoutBounds,
519
- containerBounds,
520
478
  position: "top",
521
479
  rtl: false,
522
480
  contentGap: 0,
523
481
  contentShift: 0,
524
- fallbackAdjustLayout: true,
525
- fallbackMinHeight: "50",
526
482
  });
527
- // Should adjust height when overflowing top
528
- if (result.boundaries.top < CONTAINER_OFFSET) {
529
- expect(result.styles.height).not.toBe(null);
530
- if (result.styles.height) {
531
- const height = parseInt(result.styles.height);
532
- expect(height).toBeGreaterThanOrEqual(50); // fallbackMinHeight
533
- }
534
- }
483
+ // relativeBottom = 1080 - 230 = 850
484
+ // bottom: relativeBottom + triggerHeight + contentGap = 850 + 30 + 0 = 880
485
+ expect(result2.styles.bottom).toBe(880);
535
486
  });
536
487
  });