@multiplatform.one/forms 7.80.0 → 7.81.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/AdaptivePopup/index.cjs +2 -1
- package/dist/cjs/AdaptivePopup/index.native.js +2 -1
- package/dist/cjs/AdaptivePopup/index.native.js.map +1 -1
- package/dist/cjs/Button/index.cjs +12 -5
- package/dist/cjs/Button/index.native.js +14 -6
- package/dist/cjs/Button/index.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/PanelHeader.cjs +3 -8
- package/dist/cjs/FloatingPanel/PanelHeader.native.js +3 -8
- package/dist/cjs/FloatingPanel/PanelHeader.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/coverRect.cjs +0 -8
- package/dist/cjs/FloatingPanel/coverRect.native.js +0 -8
- package/dist/cjs/FloatingPanel/coverRect.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/echoContext.cjs +5 -27
- package/dist/cjs/FloatingPanel/echoContext.native.js +5 -28
- package/dist/cjs/FloatingPanel/echoContext.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/index.cjs +49 -131
- package/dist/cjs/FloatingPanel/index.native.js +216 -135
- package/dist/cjs/FloatingPanel/index.native.js.map +1 -1
- package/dist/cjs/fields/Combobox/index.cjs +3 -10
- package/dist/cjs/fields/Combobox/index.native.js +3 -10
- package/dist/cjs/fields/Combobox/index.native.js.map +1 -1
- package/dist/cjs/fields/Select/InlineChoices.cjs +4 -8
- package/dist/cjs/fields/Select/InlineChoices.native.js +4 -8
- package/dist/cjs/fields/Select/InlineChoices.native.js.map +1 -1
- package/dist/cjs/fields/Select/index.cjs +65 -201
- package/dist/cjs/fields/Select/index.native.js +74 -209
- package/dist/cjs/fields/Select/index.native.js.map +1 -1
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.cjs +2 -2
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js +2 -2
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/index.native.js +4 -0
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/shared/CheckGlyph.cjs +40 -0
- package/dist/cjs/shared/CheckGlyph.native.js +44 -0
- package/dist/cjs/shared/CheckGlyph.native.js.map +1 -0
- package/dist/cjs/shared/checkedOnField.cjs +104 -0
- package/dist/cjs/shared/checkedOnField.native.js +117 -0
- package/dist/cjs/shared/checkedOnField.native.js.map +1 -0
- package/dist/cjs/shared/floatingList.cjs +12 -5
- package/dist/cjs/shared/floatingList.native.js +15 -7
- package/dist/cjs/shared/floatingList.native.js.map +1 -1
- package/dist/esm/AdaptivePopup/index.mjs +3 -2
- package/dist/esm/AdaptivePopup/index.mjs.map +1 -1
- package/dist/esm/AdaptivePopup/index.native.js +3 -2
- package/dist/esm/AdaptivePopup/index.native.js.map +1 -1
- package/dist/esm/Button/index.mjs +12 -5
- package/dist/esm/Button/index.mjs.map +1 -1
- package/dist/esm/Button/index.native.js +14 -6
- package/dist/esm/Button/index.native.js.map +1 -1
- package/dist/esm/FloatingPanel/PanelHeader.mjs +3 -8
- package/dist/esm/FloatingPanel/PanelHeader.mjs.map +1 -1
- package/dist/esm/FloatingPanel/PanelHeader.native.js +3 -8
- package/dist/esm/FloatingPanel/PanelHeader.native.js.map +1 -1
- package/dist/esm/FloatingPanel/coverRect.mjs +1 -8
- package/dist/esm/FloatingPanel/coverRect.mjs.map +1 -1
- package/dist/esm/FloatingPanel/coverRect.native.js +1 -8
- package/dist/esm/FloatingPanel/coverRect.native.js.map +1 -1
- package/dist/esm/FloatingPanel/echoContext.mjs +4 -24
- package/dist/esm/FloatingPanel/echoContext.mjs.map +1 -1
- package/dist/esm/FloatingPanel/echoContext.native.js +4 -25
- package/dist/esm/FloatingPanel/echoContext.native.js.map +1 -1
- package/dist/esm/FloatingPanel/index.mjs +53 -135
- package/dist/esm/FloatingPanel/index.mjs.map +1 -1
- package/dist/esm/FloatingPanel/index.native.js +180 -99
- package/dist/esm/FloatingPanel/index.native.js.map +1 -1
- package/dist/esm/fields/Combobox/index.mjs +4 -11
- package/dist/esm/fields/Combobox/index.mjs.map +1 -1
- package/dist/esm/fields/Combobox/index.native.js +4 -11
- package/dist/esm/fields/Combobox/index.native.js.map +1 -1
- package/dist/esm/fields/Select/InlineChoices.mjs +2 -6
- package/dist/esm/fields/Select/InlineChoices.mjs.map +1 -1
- package/dist/esm/fields/Select/InlineChoices.native.js +2 -6
- package/dist/esm/fields/Select/InlineChoices.native.js.map +1 -1
- package/dist/esm/fields/Select/index.mjs +68 -204
- package/dist/esm/fields/Select/index.mjs.map +1 -1
- package/dist/esm/fields/Select/index.native.js +77 -212
- package/dist/esm/fields/Select/index.native.js.map +1 -1
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs +3 -3
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js +3 -3
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/esm/index.mjs +3 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +3 -1
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/shared/CheckGlyph.mjs +15 -0
- package/dist/esm/shared/CheckGlyph.mjs.map +1 -0
- package/dist/esm/shared/CheckGlyph.native.js +16 -0
- package/dist/esm/shared/CheckGlyph.native.js.map +1 -0
- package/dist/esm/shared/checkedOnField.mjs +76 -0
- package/dist/esm/shared/checkedOnField.mjs.map +1 -0
- package/dist/esm/shared/checkedOnField.native.js +86 -0
- package/dist/esm/shared/checkedOnField.native.js.map +1 -0
- package/dist/esm/shared/floatingList.mjs +9 -5
- package/dist/esm/shared/floatingList.mjs.map +1 -1
- package/dist/esm/shared/floatingList.native.js +12 -7
- package/dist/esm/shared/floatingList.native.js.map +1 -1
- package/dist/jsx/AdaptivePopup/index.mjs +3 -2
- package/dist/jsx/AdaptivePopup/index.mjs.map +1 -1
- package/dist/jsx/AdaptivePopup/index.native.js +2 -1
- package/dist/jsx/AdaptivePopup/index.native.js.map +1 -1
- package/dist/jsx/Button/index.mjs +12 -5
- package/dist/jsx/Button/index.mjs.map +1 -1
- package/dist/jsx/Button/index.native.js +14 -6
- package/dist/jsx/Button/index.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/PanelHeader.mjs +3 -8
- package/dist/jsx/FloatingPanel/PanelHeader.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/PanelHeader.native.js +3 -8
- package/dist/jsx/FloatingPanel/PanelHeader.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/coverRect.mjs +1 -8
- package/dist/jsx/FloatingPanel/coverRect.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/coverRect.native.js +0 -8
- package/dist/jsx/FloatingPanel/coverRect.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/echoContext.mjs +4 -24
- package/dist/jsx/FloatingPanel/echoContext.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/echoContext.native.js +5 -28
- package/dist/jsx/FloatingPanel/echoContext.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/index.mjs +53 -135
- package/dist/jsx/FloatingPanel/index.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/index.native.js +216 -135
- package/dist/jsx/FloatingPanel/index.native.js.map +1 -1
- package/dist/jsx/fields/Combobox/index.mjs +4 -11
- package/dist/jsx/fields/Combobox/index.mjs.map +1 -1
- package/dist/jsx/fields/Combobox/index.native.js +3 -10
- package/dist/jsx/fields/Combobox/index.native.js.map +1 -1
- package/dist/jsx/fields/Select/InlineChoices.mjs +2 -6
- package/dist/jsx/fields/Select/InlineChoices.mjs.map +1 -1
- package/dist/jsx/fields/Select/InlineChoices.native.js +4 -8
- package/dist/jsx/fields/Select/InlineChoices.native.js.map +1 -1
- package/dist/jsx/fields/Select/index.mjs +68 -204
- package/dist/jsx/fields/Select/index.mjs.map +1 -1
- package/dist/jsx/fields/Select/index.native.js +74 -209
- package/dist/jsx/fields/Select/index.native.js.map +1 -1
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs +3 -3
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js +2 -2
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/jsx/index.js +3 -1
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +3 -1
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +4 -0
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/shared/CheckGlyph.mjs +15 -0
- package/dist/jsx/shared/CheckGlyph.mjs.map +1 -0
- package/dist/jsx/shared/CheckGlyph.native.js +44 -0
- package/dist/jsx/shared/CheckGlyph.native.js.map +1 -0
- package/dist/jsx/shared/checkedOnField.mjs +76 -0
- package/dist/jsx/shared/checkedOnField.mjs.map +1 -0
- package/dist/jsx/shared/checkedOnField.native.js +117 -0
- package/dist/jsx/shared/checkedOnField.native.js.map +1 -0
- package/dist/jsx/shared/floatingList.mjs +9 -5
- package/dist/jsx/shared/floatingList.mjs.map +1 -1
- package/dist/jsx/shared/floatingList.native.js +15 -7
- package/dist/jsx/shared/floatingList.native.js.map +1 -1
- package/package.json +8 -8
- package/src/AdaptivePopup/index.tsx +6 -3
- package/src/Button/Button.iconButton.spec.tsx +39 -0
- package/src/Button/index.tsx +20 -8
- package/src/FloatingPanel/FloatingPanel.iconCover.spec.tsx +279 -0
- package/src/FloatingPanel/FloatingPanel.menuFace.native.spec.tsx +78 -69
- package/src/FloatingPanel/FloatingPanel.seamlessCover.spec.tsx +12 -0
- package/src/FloatingPanel/PanelHeader.tsx +4 -12
- package/src/FloatingPanel/coverRect.ts +2 -15
- package/src/FloatingPanel/echoContext.ts +6 -40
- package/src/FloatingPanel/index.native.tsx +209 -144
- package/src/FloatingPanel/index.tsx +71 -187
- package/src/FloatingPanel/pointAnchor.spec.ts +1 -1
- package/src/FloatingPanel/pointAnchor.ts +4 -4
- package/src/FloatingPanel/sheetGrabber.spec.tsx +53 -0
- package/src/fields/Combobox/index.tsx +4 -9
- package/src/fields/Select/InlineChoices.tsx +2 -2
- package/src/fields/Select/Select.containingBlock.spec.tsx +131 -130
- package/src/fields/Select/Select.popUp.native.spec.tsx +276 -0
- package/src/fields/Select/Select.stories.tsx +26 -0
- package/src/fields/Select/index.tsx +84 -255
- package/src/fields/nativeDateTime/OsDateTimeSheet.tsx +3 -3
- package/src/index.ts +2 -1
- package/src/shared/CheckGlyph.tsx +12 -0
- package/src/shared/checkedOnField.ts +116 -0
- package/src/shared/floatingList.spec.tsx +154 -1
- package/src/shared/floatingList.tsx +18 -9
- package/types/AdaptivePopup/index.d.ts.map +1 -1
- package/types/Button/index.d.ts.map +1 -1
- package/types/FloatingPanel/PanelHeader.d.ts +1 -6
- package/types/FloatingPanel/PanelHeader.d.ts.map +1 -1
- package/types/FloatingPanel/coverRect.d.ts +2 -11
- package/types/FloatingPanel/coverRect.d.ts.map +1 -1
- package/types/FloatingPanel/echoContext.d.ts +5 -22
- package/types/FloatingPanel/echoContext.d.ts.map +1 -1
- package/types/FloatingPanel/index.d.ts +22 -9
- package/types/FloatingPanel/index.d.ts.map +1 -1
- package/types/FloatingPanel/index.native.d.ts +8 -1
- package/types/FloatingPanel/index.native.d.ts.map +1 -1
- package/types/FloatingPanel/pointAnchor.d.ts +4 -4
- package/types/fields/Combobox/index.d.ts.map +1 -1
- package/types/fields/Select/index.d.ts.map +1 -1
- package/types/index.d.ts +2 -1
- package/types/index.d.ts.map +1 -1
- package/types/shared/CheckGlyph.d.ts +10 -0
- package/types/shared/CheckGlyph.d.ts.map +1 -0
- package/types/shared/checkedOnField.d.ts +54 -0
- package/types/shared/checkedOnField.d.ts.map +1 -0
- package/types/shared/floatingList.d.ts +5 -2
- package/types/shared/floatingList.d.ts.map +1 -1
- package/dist/cjs/FloatingPanel/iconEcho.cjs +0 -106
- package/dist/cjs/FloatingPanel/iconEcho.native.js +0 -116
- package/dist/cjs/FloatingPanel/iconEcho.native.js.map +0 -1
- package/dist/esm/FloatingPanel/iconEcho.mjs +0 -79
- package/dist/esm/FloatingPanel/iconEcho.mjs.map +0 -1
- package/dist/esm/FloatingPanel/iconEcho.native.js +0 -86
- package/dist/esm/FloatingPanel/iconEcho.native.js.map +0 -1
- package/dist/jsx/FloatingPanel/iconEcho.mjs +0 -79
- package/dist/jsx/FloatingPanel/iconEcho.mjs.map +0 -1
- package/dist/jsx/FloatingPanel/iconEcho.native.js +0 -116
- package/dist/jsx/FloatingPanel/iconEcho.native.js.map +0 -1
- package/src/FloatingPanel/FloatingPanel.iconEcho.spec.tsx +0 -357
- package/src/FloatingPanel/iconEcho.tsx +0 -124
- package/types/FloatingPanel/iconEcho.d.ts +0 -29
- package/types/FloatingPanel/iconEcho.d.ts.map +0 -1
|
@@ -54,51 +54,7 @@ describe("Select inside a transformed sheet (FZ-8438)", () => {
|
|
|
54
54
|
});
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
describe("Select
|
|
58
|
-
beforeEach(() => {
|
|
59
|
-
Object.defineProperty(window, "innerWidth", {
|
|
60
|
-
configurable: true,
|
|
61
|
-
writable: true,
|
|
62
|
-
value: 1200,
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
afterEach(() => {
|
|
66
|
-
vi.restoreAllMocks();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("opens on the field's top edge with the field echoed as a hidden first row", async () => {
|
|
70
|
-
const original = HTMLElement.prototype.getBoundingClientRect;
|
|
71
|
-
vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation(
|
|
72
|
-
function (this: HTMLElement) {
|
|
73
|
-
const field = this.querySelector('[data-testid="select-trigger"]');
|
|
74
|
-
if (this.dataset.testid === "select-trigger" || field) {
|
|
75
|
-
return {
|
|
76
|
-
x: 16,
|
|
77
|
-
y: 200,
|
|
78
|
-
top: 200,
|
|
79
|
-
left: 16,
|
|
80
|
-
right: 416,
|
|
81
|
-
bottom: 240,
|
|
82
|
-
width: 400,
|
|
83
|
-
height: 40,
|
|
84
|
-
toJSON: () => ({}),
|
|
85
|
-
} as DOMRect;
|
|
86
|
-
}
|
|
87
|
-
return original.call(this);
|
|
88
|
-
},
|
|
89
|
-
);
|
|
90
|
-
const result = renderWithProviders(<Select options={options} value="nbg" />);
|
|
91
|
-
const { dropdown, top } = await openTop(result);
|
|
92
|
-
expect(top).toBe(200);
|
|
93
|
-
const echo = dropdown.querySelector<HTMLElement>("[data-fp-trigger-row]");
|
|
94
|
-
expect(echo).not.toBeNull();
|
|
95
|
-
expect(dropdown.firstElementChild).toBe(echo);
|
|
96
|
-
expect(echo?.getAttribute("aria-hidden")).toBe("true");
|
|
97
|
-
expect(echo?.getAttribute("role")).toBeNull();
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe("Select cover and scroll (LC-112, FZ-8463)", () => {
|
|
57
|
+
describe("Select opens with its checked row on the field (LC-112, FZ-8557)", () => {
|
|
102
58
|
const field = (top: number) => ({
|
|
103
59
|
x: 16,
|
|
104
60
|
y: top,
|
|
@@ -110,8 +66,11 @@ describe("Select cover and scroll (LC-112, FZ-8463)", () => {
|
|
|
110
66
|
height: 40,
|
|
111
67
|
toJSON: () => ({}),
|
|
112
68
|
});
|
|
69
|
+
const rowTop = 4;
|
|
70
|
+
const rowHeight = 36;
|
|
113
71
|
let fieldTop = 700;
|
|
114
|
-
let
|
|
72
|
+
let viewportHeight = 768;
|
|
73
|
+
const contentOf = (count: number) => rowTop * 2 + count * rowHeight;
|
|
115
74
|
beforeEach(() => {
|
|
116
75
|
Object.defineProperty(window, "innerWidth", {
|
|
117
76
|
configurable: true,
|
|
@@ -120,8 +79,7 @@ describe("Select cover and scroll (LC-112, FZ-8463)", () => {
|
|
|
120
79
|
});
|
|
121
80
|
Object.defineProperty(window, "innerHeight", {
|
|
122
81
|
configurable: true,
|
|
123
|
-
|
|
124
|
-
value: 768,
|
|
82
|
+
get: () => viewportHeight,
|
|
125
83
|
});
|
|
126
84
|
const original = HTMLElement.prototype.getBoundingClientRect;
|
|
127
85
|
vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation(
|
|
@@ -135,113 +93,156 @@ describe("Select cover and scroll (LC-112, FZ-8463)", () => {
|
|
|
135
93
|
return original.call(this);
|
|
136
94
|
},
|
|
137
95
|
);
|
|
96
|
+
const rowsOf = (el: HTMLElement) =>
|
|
97
|
+
Array.from(
|
|
98
|
+
el.closest('[data-testid="select-dropdown"]')?.querySelectorAll('[role="option"]') ?? [],
|
|
99
|
+
);
|
|
138
100
|
vi.spyOn(HTMLElement.prototype, "scrollHeight", "get").mockImplementation(
|
|
139
101
|
function (this: HTMLElement) {
|
|
140
|
-
return this.dataset.testid === "select-dropdown" ?
|
|
102
|
+
return this.dataset.testid === "select-dropdown" ? contentOf(rowsOf(this).length) : 0;
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
vi.spyOn(HTMLElement.prototype, "offsetHeight", "get").mockImplementation(
|
|
106
|
+
function (this: HTMLElement) {
|
|
107
|
+
return this.getAttribute("role") === "option" ? rowHeight : 0;
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
vi.spyOn(HTMLElement.prototype, "offsetTop", "get").mockImplementation(
|
|
111
|
+
function (this: HTMLElement) {
|
|
112
|
+
return this.getAttribute("role") === "option"
|
|
113
|
+
? rowTop + rowsOf(this).indexOf(this) * rowHeight
|
|
114
|
+
: 0;
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
vi.spyOn(HTMLElement.prototype, "clientHeight", "get").mockImplementation(
|
|
118
|
+
function (this: HTMLElement) {
|
|
119
|
+
return this.dataset.testid === "select-dropdown" ? innerOf(this) : 0;
|
|
141
120
|
},
|
|
142
121
|
);
|
|
143
122
|
});
|
|
144
123
|
afterEach(() => {
|
|
124
|
+
viewportHeight = 768;
|
|
145
125
|
vi.restoreAllMocks();
|
|
146
126
|
});
|
|
147
127
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
128
|
+
const borders = (dropdown: HTMLElement) => {
|
|
129
|
+
const style = getComputedStyle(dropdown);
|
|
130
|
+
return {
|
|
131
|
+
top: parseFloat(style.borderTopWidth) || 0,
|
|
132
|
+
bottom: parseFloat(style.borderBottomWidth) || 0,
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
/** The panel's padding box, in viewport pixels. */
|
|
136
|
+
function innerOf(dropdown: HTMLElement) {
|
|
137
|
+
const border = borders(dropdown);
|
|
138
|
+
return (parseFloat(dropdown.style.maxHeight) || 0) - border.top - border.bottom;
|
|
139
|
+
}
|
|
140
|
+
const paddingTop = (dropdown: HTMLElement) =>
|
|
141
|
+
parseFloat(dropdown.style.top) + borders(dropdown).top;
|
|
142
|
+
/** The panel's outer edges: these meet the safe edges (`panelViewportPadding`). */
|
|
143
|
+
const outerTop = (dropdown: HTMLElement) => parseFloat(dropdown.style.top);
|
|
144
|
+
const outerBottom = (dropdown: HTMLElement) =>
|
|
145
|
+
outerTop(dropdown) + parseFloat(dropdown.style.maxHeight);
|
|
146
|
+
/**
|
|
147
|
+
* The checked row's value against the closed field's: both draw one line
|
|
148
|
+
* of text centred in their row, so the delta of the two centres is how far
|
|
149
|
+
* the value moves on open.
|
|
150
|
+
*/
|
|
151
|
+
const valueDelta = (dropdown: HTMLElement, row: number) =>
|
|
152
|
+
paddingTop(dropdown) +
|
|
153
|
+
rowTop +
|
|
154
|
+
row * rowHeight -
|
|
155
|
+
dropdown.scrollTop +
|
|
156
|
+
rowHeight / 2 -
|
|
157
|
+
(fieldTop + 20);
|
|
158
|
+
const many: SelectOption[] = Array.from({ length: 40 }, (_, i) => ({
|
|
159
|
+
value: `c${i}`,
|
|
160
|
+
label: `City ${i}`,
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
it("never echoes the field: the checked row is the value, shown once", async () => {
|
|
164
|
+
fieldTop = 120;
|
|
165
|
+
const result = renderWithProviders(<Select options={options} value="nbg" />);
|
|
152
166
|
const { dropdown } = await openTop(result);
|
|
153
|
-
await waitFor(() => expect(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
);
|
|
158
|
-
expect(parseFloat(dropdown.style.top)).toBeLessThan(700);
|
|
159
|
-
expect(parseFloat(dropdown.style.maxHeight)).toBeGreaterThanOrEqual(300);
|
|
160
|
-
expect(dropdown.textContent?.match(/Helsinki/g)?.length ?? 0).toBe(1);
|
|
167
|
+
await waitFor(() => expect(Math.abs(valueDelta(dropdown, 2))).toBeLessThanOrEqual(1));
|
|
168
|
+
expect(dropdown.querySelector("[data-fp-trigger-row]")).toBeNull();
|
|
169
|
+
expect(dropdown.textContent?.match(/Nuremberg/g)?.length ?? 0).toBe(1);
|
|
170
|
+
expect(dropdown.scrollTop).toBe(0);
|
|
161
171
|
});
|
|
162
172
|
|
|
163
|
-
it("
|
|
173
|
+
it("with no value, the first row sits on the field", async () => {
|
|
174
|
+
fieldTop = 120;
|
|
175
|
+
const result = renderWithProviders(<Select options={options} />);
|
|
176
|
+
const { dropdown } = await openTop(result);
|
|
177
|
+
await waitFor(() => expect(Math.abs(valueDelta(dropdown, 0))).toBeLessThanOrEqual(1));
|
|
178
|
+
expect(dropdown.querySelector("[data-fp-trigger-row]")).toBeNull();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("a short list by the bottom edge shows whole, slid up the least distance", async () => {
|
|
164
182
|
fieldTop = 700;
|
|
165
|
-
contentHeight = 2000;
|
|
166
183
|
const result = renderWithProviders(<Select options={options} value="hel" />);
|
|
167
184
|
const { dropdown } = await openTop(result);
|
|
168
|
-
await waitFor(() => expect(dropdown
|
|
169
|
-
|
|
170
|
-
expect(
|
|
185
|
+
await waitFor(() => expect(outerBottom(dropdown)).toBe(768 - 10));
|
|
186
|
+
expect(innerOf(dropdown)).toBe(contentOf(3));
|
|
187
|
+
expect(dropdown.scrollTop).toBe(0);
|
|
188
|
+
// Helsinki would sit on the field; the list stops at the safe edge instead.
|
|
189
|
+
expect(valueDelta(dropdown, 0)).toBeLessThan(0);
|
|
171
190
|
});
|
|
172
191
|
|
|
173
|
-
it("a
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const result = renderWithProviders(<Select options={
|
|
192
|
+
it("a long list that fits the screen shows every row with no scrolling", async () => {
|
|
193
|
+
viewportHeight = 1366;
|
|
194
|
+
fieldTop = 700;
|
|
195
|
+
const result = renderWithProviders(<Select options={many.slice(0, 36)} value="c20" />);
|
|
177
196
|
const { dropdown } = await openTop(result);
|
|
178
|
-
await waitFor(() => expect(dropdown
|
|
179
|
-
expect(
|
|
180
|
-
|
|
197
|
+
await waitFor(() => expect(innerOf(dropdown)).toBe(contentOf(36)));
|
|
198
|
+
expect(dropdown.scrollTop).toBe(0);
|
|
199
|
+
// Cairo's 20 rows above it don't fit over the field: the list slides down
|
|
200
|
+
// the least distance, its top on the safe edge, and the value sits just below.
|
|
201
|
+
expect(outerTop(dropdown)).toBe(10);
|
|
202
|
+
expect(outerBottom(dropdown)).toBeLessThanOrEqual(1366 - 10);
|
|
203
|
+
expect(valueDelta(dropdown, 20)).toBeGreaterThan(0);
|
|
181
204
|
});
|
|
182
205
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return this.getAttribute("role") === "option" ? rowHeight : 0;
|
|
194
|
-
},
|
|
195
|
-
);
|
|
196
|
-
vi.spyOn(HTMLElement.prototype, "offsetTop", "get").mockImplementation(
|
|
197
|
-
function (this: HTMLElement) {
|
|
198
|
-
return this.getAttribute("role") === "option" ? rowTop + rowIndex(this) * rowHeight : 0;
|
|
199
|
-
},
|
|
200
|
-
);
|
|
201
|
-
vi.spyOn(HTMLElement.prototype, "clientHeight", "get").mockImplementation(
|
|
202
|
-
function (this: HTMLElement) {
|
|
203
|
-
return this.dataset.testid === "select-dropdown"
|
|
204
|
-
? parseFloat(this.style.maxHeight) || 0
|
|
205
|
-
: 0;
|
|
206
|
-
},
|
|
207
|
-
);
|
|
208
|
-
});
|
|
206
|
+
it("a list taller than the screen keeps its value on the field and reaches both safe edges", async () => {
|
|
207
|
+
fieldTop = 700;
|
|
208
|
+
const result = renderWithProviders(<Select options={many} value="c20" />);
|
|
209
|
+
const { dropdown } = await openTop(result);
|
|
210
|
+
await waitFor(() => expect(Math.abs(valueDelta(dropdown, 20))).toBeLessThanOrEqual(1));
|
|
211
|
+
expect(dropdown.scrollTop).toBeGreaterThan(0);
|
|
212
|
+
// No whole-row trim: the clipped edges sit on the safe edges, a cut row under each band.
|
|
213
|
+
expect(outerTop(dropdown)).toBe(10);
|
|
214
|
+
expect(outerBottom(dropdown)).toBe(768 - 10);
|
|
215
|
+
});
|
|
209
216
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const visibleBottom = dropdown.scrollTop + parseFloat(dropdown.style.maxHeight);
|
|
219
|
-
expect((visibleBottom - rowTop) % rowHeight).toBe(0);
|
|
220
|
-
});
|
|
217
|
+
it("a high field's first row stays on the field and the list runs to the safe bottom edge", async () => {
|
|
218
|
+
fieldTop = 120;
|
|
219
|
+
const result = renderWithProviders(<Select options={many} value="c0" />);
|
|
220
|
+
const { dropdown } = await openTop(result);
|
|
221
|
+
await waitFor(() => expect(Math.abs(valueDelta(dropdown, 0))).toBeLessThanOrEqual(1));
|
|
222
|
+
expect(dropdown.scrollTop).toBe(0);
|
|
223
|
+
expect(outerBottom(dropdown)).toBe(768 - 10);
|
|
224
|
+
});
|
|
221
225
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
expect(parseFloat(dropdown.style.maxHeight)).toBeLessThan(768 - 10 - 120),
|
|
230
|
-
);
|
|
231
|
-
const visibleBottom = dropdown.scrollTop + parseFloat(dropdown.style.maxHeight);
|
|
232
|
-
expect((visibleBottom - rowTop) % rowHeight).toBe(0);
|
|
233
|
-
});
|
|
226
|
+
it("the last row checked on a low field ends the panel at the list's end", async () => {
|
|
227
|
+
fieldTop = 700;
|
|
228
|
+
const result = renderWithProviders(<Select options={many} value="c39" />);
|
|
229
|
+
const { dropdown } = await openTop(result);
|
|
230
|
+
await waitFor(() => expect(Math.abs(valueDelta(dropdown, 39))).toBeLessThanOrEqual(1));
|
|
231
|
+
expect(outerTop(dropdown)).toBe(10);
|
|
232
|
+
expect(dropdown.scrollTop + innerOf(dropdown)).toBe(contentOf(40));
|
|
234
233
|
});
|
|
235
234
|
|
|
236
|
-
it("a
|
|
235
|
+
it("scrolling grows a panel that stops short of the safe top edge, the rows staying put", async () => {
|
|
237
236
|
fieldTop = 120;
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
expect(dropdown
|
|
237
|
+
const result = renderWithProviders(<Select options={many} value="c0" />);
|
|
238
|
+
const { dropdown } = await openTop(result);
|
|
239
|
+
await waitFor(() => expect(Math.abs(valueDelta(dropdown, 0))).toBeLessThanOrEqual(1));
|
|
240
|
+
const before = paddingTop(dropdown);
|
|
241
|
+
dropdown.scrollTop = 50;
|
|
242
|
+
fireEvent.scroll(dropdown);
|
|
243
|
+
await waitFor(() => expect(paddingTop(dropdown)).toBe(before - 50));
|
|
244
|
+
expect(dropdown.scrollTop).toBe(0);
|
|
245
|
+
expect(outerBottom(dropdown)).toBe(768 - 10);
|
|
245
246
|
});
|
|
246
247
|
});
|
|
247
248
|
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FZ-8655 (LC-112, Round 40 parity): a native Select opens the way the web's
|
|
3
|
+
* covering Select does, a macOS pop-up. The rows are measured in the native
|
|
4
|
+
* FloatingPanel and placed by the placement code the web shares
|
|
5
|
+
* (`placeCheckedOnField`): the checked row on the field, or the first row with
|
|
6
|
+
* no value, and never a field echo. A list that fits shows whole, a taller one
|
|
7
|
+
* scrolls with its clipped edges on the safe edges. On a phone a long list
|
|
8
|
+
* keeps its sheet at page level.
|
|
9
|
+
*/
|
|
10
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
11
|
+
import { useResolvedKnobs } from "@multiplatform.one/theme";
|
|
12
|
+
import { act } from "@testing-library/react";
|
|
13
|
+
import type { ReactNode } from "react";
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
15
|
+
import { panelViewportPadding } from "../../FloatingPanel/useFloatingPanel";
|
|
16
|
+
import { OverlayStackProvider } from "../../shared/overlayStack";
|
|
17
|
+
import { placeCheckedOnField, scrollArrowBand } from "../../shared/checkedOnField";
|
|
18
|
+
import { Select } from "./index";
|
|
19
|
+
|
|
20
|
+
const screen = vi.hoisted(() => ({ width: 1024, height: 1366 }));
|
|
21
|
+
|
|
22
|
+
vi.mock("tamagui", async (importOriginal) => ({
|
|
23
|
+
...(await importOriginal<typeof import("tamagui")>()),
|
|
24
|
+
isWeb: false,
|
|
25
|
+
}));
|
|
26
|
+
vi.mock("react-native-web", async (importOriginal) => ({
|
|
27
|
+
...(await importOriginal<typeof import("react-native")>()),
|
|
28
|
+
ActionSheetIOS: undefined,
|
|
29
|
+
Modal: ({ children, visible }: { children: ReactNode; visible: boolean }) =>
|
|
30
|
+
visible ? children : null,
|
|
31
|
+
useWindowDimensions: () => ({ ...screen, scale: 2, fontScale: 1 }),
|
|
32
|
+
}));
|
|
33
|
+
vi.mock("../../FloatingPanel", async (importOriginal) => {
|
|
34
|
+
const actual = await importOriginal<typeof import("../../FloatingPanel")>();
|
|
35
|
+
const native = await import("../../FloatingPanel/index.native");
|
|
36
|
+
return { ...actual, FloatingPanel: native.FloatingPanel };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const rowHeight = 44;
|
|
40
|
+
let band = 0;
|
|
41
|
+
let field = { x: 40, y: 400, width: 320, height: 44 };
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
vi.useFakeTimers();
|
|
45
|
+
field = { x: 40, y: 400, width: 320, height: 44 };
|
|
46
|
+
screen.width = 1024;
|
|
47
|
+
screen.height = 1366;
|
|
48
|
+
vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation(() => ({
|
|
49
|
+
...field,
|
|
50
|
+
left: field.x,
|
|
51
|
+
top: field.y,
|
|
52
|
+
right: field.x + field.width,
|
|
53
|
+
bottom: field.y + field.height,
|
|
54
|
+
toJSON: () => ({}),
|
|
55
|
+
}));
|
|
56
|
+
});
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
vi.restoreAllMocks();
|
|
59
|
+
vi.useRealTimers();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/** The band the panel lays out for: half this preset's menu row. */
|
|
63
|
+
function BandProbe() {
|
|
64
|
+
const { knobProps } = useResolvedKnobs({ painted: false });
|
|
65
|
+
band = scrollArrowBand(knobProps.menuRow.minHeight);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const cities = (count: number) =>
|
|
70
|
+
Array.from({ length: count }, (_, i) => ({ value: `c${i}`, label: `City ${i}` }));
|
|
71
|
+
|
|
72
|
+
function tap(target: Element) {
|
|
73
|
+
act(() => {
|
|
74
|
+
target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, button: 0 }));
|
|
75
|
+
target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, button: 0 }));
|
|
76
|
+
target.dispatchEvent(new MouseEvent("click", { bubbles: true, button: 0 }));
|
|
77
|
+
});
|
|
78
|
+
act(() => {
|
|
79
|
+
vi.advanceTimersByTime(1);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** RN-web parks onLayout on the node for a ResizeObserver happy-dom never fires. */
|
|
84
|
+
function layout(node: Element, box: { y?: number; width: number; height: number }) {
|
|
85
|
+
const onLayout = (node as unknown as { __reactLayoutHandler?: (event: unknown) => void })
|
|
86
|
+
.__reactLayoutHandler;
|
|
87
|
+
expect(onLayout).toBeTypeOf("function");
|
|
88
|
+
act(() => {
|
|
89
|
+
onLayout?.({ nativeEvent: { layout: { x: 0, y: 0, ...box } } });
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const byTestId = (id: string) => document.querySelector<HTMLElement>(`[data-testid="${id}"]`);
|
|
94
|
+
const menu = () => byTestId("floating-panel-menu");
|
|
95
|
+
const px = (value: string | undefined) => Number.parseFloat(value ?? "");
|
|
96
|
+
|
|
97
|
+
function open({
|
|
98
|
+
count,
|
|
99
|
+
checked,
|
|
100
|
+
inOverlay = false,
|
|
101
|
+
}: {
|
|
102
|
+
count: number;
|
|
103
|
+
checked?: number;
|
|
104
|
+
inOverlay?: boolean;
|
|
105
|
+
}) {
|
|
106
|
+
const options = cities(count);
|
|
107
|
+
const select = (
|
|
108
|
+
<Select
|
|
109
|
+
aria-label="City"
|
|
110
|
+
options={options}
|
|
111
|
+
value={checked === undefined ? undefined : `c${checked}`}
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
114
|
+
const view = renderWithProviders(
|
|
115
|
+
<>
|
|
116
|
+
<BandProbe />
|
|
117
|
+
{inOverlay ? <OverlayStackProvider zIndex={100_000}>{select}</OverlayStackProvider> : select}
|
|
118
|
+
</>,
|
|
119
|
+
);
|
|
120
|
+
tap(view.getByRole("button", { name: "City" }));
|
|
121
|
+
return { view, options, contentHeight: count * rowHeight };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function measureRows(
|
|
125
|
+
view: ReturnType<typeof renderWithProviders>,
|
|
126
|
+
index: number,
|
|
127
|
+
contentHeight: number,
|
|
128
|
+
) {
|
|
129
|
+
const frame = menu();
|
|
130
|
+
expect(frame).not.toBeNull();
|
|
131
|
+
layout(frame as HTMLElement, { width: 320, height: Math.min(contentHeight + 2, 900) });
|
|
132
|
+
layout(view.getByRole("button", { name: `City ${index}` }), {
|
|
133
|
+
y: index * rowHeight,
|
|
134
|
+
width: 320,
|
|
135
|
+
height: rowHeight,
|
|
136
|
+
});
|
|
137
|
+
layout(byTestId("floating-panel-menu-content") as HTMLElement, {
|
|
138
|
+
width: 320,
|
|
139
|
+
height: contentHeight,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** The padding box the shared helper asks for, from the screen under test. */
|
|
144
|
+
function expected(index: number, contentHeight: number, border: number) {
|
|
145
|
+
return placeCheckedOnField({
|
|
146
|
+
fieldTop: field.y,
|
|
147
|
+
fieldBottom: field.y + field.height,
|
|
148
|
+
rowTop: index * rowHeight,
|
|
149
|
+
rowHeight,
|
|
150
|
+
contentHeight,
|
|
151
|
+
boundsTop: panelViewportPadding + border,
|
|
152
|
+
boundsBottom: screen.height - panelViewportPadding - border,
|
|
153
|
+
band,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function border() {
|
|
158
|
+
const surface = byTestId("floating-panel-menu-surface") as HTMLElement;
|
|
159
|
+
return Math.round(px(getComputedStyle(surface).borderTopWidth)) || 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
describe("Select native pop-up (FZ-8655)", () => {
|
|
163
|
+
it("never echoes the field, and paints nothing until its rows are measured", () => {
|
|
164
|
+
const { view, contentHeight } = open({ count: 5, checked: 2 });
|
|
165
|
+
expect(menu()?.style.opacity).toBe("0");
|
|
166
|
+
measureRows(view, 2, contentHeight);
|
|
167
|
+
expect(menu()?.style.opacity).toBe("1");
|
|
168
|
+
expect(byTestId("floating-panel-menu-echo")).toBeNull();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("centres the checked row on the field, the rows before it running up", () => {
|
|
172
|
+
const { view, contentHeight } = open({ count: 5, checked: 2 });
|
|
173
|
+
measureRows(view, 2, contentHeight);
|
|
174
|
+
const b = border();
|
|
175
|
+
const fit = expected(2, contentHeight, b);
|
|
176
|
+
expect(px(menu()?.style.top)).toBe(fit.top - b);
|
|
177
|
+
expect(fit.top + 2 * rowHeight).toBe(field.y);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("puts the first row on the field with no value", () => {
|
|
181
|
+
const { view, contentHeight } = open({ count: 5 });
|
|
182
|
+
measureRows(view, 0, contentHeight);
|
|
183
|
+
const b = border();
|
|
184
|
+
expect(px(menu()?.style.top) + b).toBe(field.y);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("shows a short list by the bottom edge whole, slid inside the safe edge", () => {
|
|
188
|
+
field = { ...field, y: 1300 };
|
|
189
|
+
const { view, contentHeight } = open({ count: 7, checked: 3 });
|
|
190
|
+
measureRows(view, 3, contentHeight);
|
|
191
|
+
const b = border();
|
|
192
|
+
const top = px(menu()?.style.top);
|
|
193
|
+
expect(top + b + contentHeight).toBe(screen.height - panelViewportPadding - b);
|
|
194
|
+
expect(px(menu()?.style.maxHeight)).toBe(contentHeight + 2 * b);
|
|
195
|
+
expect(byTestId("floating-panel-scroll-up")).toBeNull();
|
|
196
|
+
expect(byTestId("floating-panel-scroll-down")).toBeNull();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("opens at every list length at regular width, not a sheet or a regular panel", () => {
|
|
200
|
+
const { view, contentHeight } = open({ count: 20, checked: 10 });
|
|
201
|
+
measureRows(view, 10, contentHeight);
|
|
202
|
+
expect(byTestId("floating-panel-sheet")).toBeNull();
|
|
203
|
+
expect(byTestId("floating-panel-regular")).toBeNull();
|
|
204
|
+
expect(byTestId("floating-panel-menu-echo")).toBeNull();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("scrolls a list taller than the safe viewport, its clipped edges on the safe edges", () => {
|
|
208
|
+
const { view, contentHeight } = open({ count: 60, checked: 30 });
|
|
209
|
+
measureRows(view, 30, contentHeight);
|
|
210
|
+
const b = border();
|
|
211
|
+
const fit = expected(30, contentHeight, b);
|
|
212
|
+
expect(fit.scrollTop).toBeGreaterThan(0);
|
|
213
|
+
expect(px(menu()?.style.top)).toBe(panelViewportPadding);
|
|
214
|
+
expect(px(menu()?.style.maxHeight)).toBe(screen.height - 2 * panelViewportPadding);
|
|
215
|
+
expect(byTestId("floating-panel-scroll-up")).not.toBeNull();
|
|
216
|
+
expect(byTestId("floating-panel-scroll-down")).not.toBeNull();
|
|
217
|
+
const checkedTop = fit.top - fit.scrollTop + 30 * rowHeight;
|
|
218
|
+
expect(checkedTop + rowHeight / 2).toBe(field.y + field.height / 2);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("keeps an arrow only on the side that's clipped", () => {
|
|
222
|
+
const { view, contentHeight } = open({ count: 60, checked: 1 });
|
|
223
|
+
measureRows(view, 1, contentHeight);
|
|
224
|
+
expect(byTestId("floating-panel-scroll-up")).toBeNull();
|
|
225
|
+
expect(byTestId("floating-panel-scroll-down")).not.toBeNull();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it.each([
|
|
229
|
+
["top", 20],
|
|
230
|
+
["middle", 640],
|
|
231
|
+
["bottom", 1290],
|
|
232
|
+
])("keeps the checked row inside the panel, clear of its bands, at the %s", (_name, y) => {
|
|
233
|
+
field = { ...field, y };
|
|
234
|
+
for (const checked of [0, 1, 29, 58, 59]) {
|
|
235
|
+
const { view, contentHeight } = open({ count: 60, checked });
|
|
236
|
+
measureRows(view, checked, contentHeight);
|
|
237
|
+
const b = border();
|
|
238
|
+
const fit = expected(checked, contentHeight, b);
|
|
239
|
+
const rowTop = fit.top - fit.scrollTop + checked * rowHeight;
|
|
240
|
+
expect(px(menu()?.style.top)).toBe(fit.top - b);
|
|
241
|
+
expect(rowTop).toBeGreaterThanOrEqual(fit.top);
|
|
242
|
+
expect(rowTop + rowHeight).toBeLessThanOrEqual(fit.top + fit.height);
|
|
243
|
+
view.unmount();
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("keeps the sheet for a long list on a phone at page level", () => {
|
|
248
|
+
screen.width = 390;
|
|
249
|
+
screen.height = 844;
|
|
250
|
+
open({ count: 20, checked: 10 });
|
|
251
|
+
expect(byTestId("floating-panel-sheet")).not.toBeNull();
|
|
252
|
+
expect(menu()).toBeNull();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("opens a long list as a pop-up on a phone inside an overlay", () => {
|
|
256
|
+
screen.width = 390;
|
|
257
|
+
screen.height = 844;
|
|
258
|
+
field = { ...field, y: 300 };
|
|
259
|
+
const { view, contentHeight } = open({ count: 20, checked: 10, inOverlay: true });
|
|
260
|
+
measureRows(view, 10, contentHeight);
|
|
261
|
+
expect(byTestId("floating-panel-sheet")).toBeNull();
|
|
262
|
+
expect(byTestId("floating-panel-menu-echo")).toBeNull();
|
|
263
|
+
const b = border();
|
|
264
|
+
expect(px(menu()?.style.top)).toBe(expected(10, contentHeight, b).top - b);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("keeps a short list inside an overlay attached beside its field (LC-262)", () => {
|
|
268
|
+
screen.width = 390;
|
|
269
|
+
screen.height = 844;
|
|
270
|
+
open({ count: 4, checked: 1, inOverlay: true });
|
|
271
|
+
const frame = menu() as HTMLElement;
|
|
272
|
+
layout(frame, { width: 320, height: 178 });
|
|
273
|
+
expect(frame.style.opacity).toBe("1");
|
|
274
|
+
expect(px(frame.style.top)).toBe(field.y + field.height);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
@@ -10,6 +10,7 @@ import { Select as TamaguiSelect, Sheet, useMedia, XStack, YStack } from "tamagu
|
|
|
10
10
|
import { Button } from "../../Button";
|
|
11
11
|
import { useViewportGtSm } from "../../FloatingPanel";
|
|
12
12
|
import { Form } from "../../Form";
|
|
13
|
+
import { formReadableMaxWidth } from "../../formSpacing";
|
|
13
14
|
import { Select, type SelectOption } from "./index";
|
|
14
15
|
|
|
15
16
|
// tamagui v2 types Select.Content as children + scope + FocusScopeProps only,
|
|
@@ -21,6 +22,15 @@ const raiseAbovePortal: Record<string, unknown> = { zIndex: 200000 };
|
|
|
21
22
|
const meta: Meta<typeof Select> = {
|
|
22
23
|
title: "Forms/Select",
|
|
23
24
|
component: Select,
|
|
25
|
+
// LC-115 READABLE-FORMS: a field sits in a readable form column, so an open
|
|
26
|
+
// list on an iPad or a desktop is the column's width, not the screen's.
|
|
27
|
+
decorators: [
|
|
28
|
+
(Story) => (
|
|
29
|
+
<YStack width="100%" maxWidth={formReadableMaxWidth}>
|
|
30
|
+
<Story />
|
|
31
|
+
</YStack>
|
|
32
|
+
),
|
|
33
|
+
],
|
|
24
34
|
parameters: {
|
|
25
35
|
docs: {
|
|
26
36
|
description: {
|
|
@@ -613,3 +623,19 @@ export const LowOnPage: StoryObj<{ spaceAbove: number; count: number }> = {
|
|
|
613
623
|
);
|
|
614
624
|
},
|
|
615
625
|
};
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* LC-112 (FZ-8655): a field mid-screen. A list taller than the safe viewport
|
|
629
|
+
* reaches both safe edges and scrolls under its bands, the checked row on the
|
|
630
|
+
* field.
|
|
631
|
+
*/
|
|
632
|
+
export const MidOnPage: StoryObj<{ spaceAbove: number; count: number }> = {
|
|
633
|
+
...LowOnPage,
|
|
634
|
+
args: { ...LowOnPage.args, spaceAbove: 440 },
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
/** LC-112 (FZ-8655): a short list by the bottom edge shows whole, slid inside the safe edge. */
|
|
638
|
+
export const ShortLowOnPage: StoryObj<{ spaceAbove: number; count: number }> = {
|
|
639
|
+
...LowOnPage,
|
|
640
|
+
args: { ...LowOnPage.args, count: 7 },
|
|
641
|
+
};
|