@schukai/monster 4.141.3 → 4.142.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/package.json +1 -1
- package/source/components/datatable/pagination.mjs +9 -3
- package/source/components/form/control-bar.mjs +55 -35
- package/source/components/form/select.mjs +190 -38
- package/source/components/form/style/control-bar.pcss +13 -0
- package/source/components/form/style/select.pcss +6 -0
- package/source/components/form/stylesheet/control-bar.mjs +1 -1
- package/source/components/form/stylesheet/select.mjs +1 -1
- package/source/components/form/util/floating-layout-queue.mjs +228 -0
- package/source/components/form/util/floating-ui.mjs +47 -8
- package/source/components/layout/panel.mjs +65 -12
- package/source/components/layout/split-panel.mjs +62 -47
- package/test/cases/components/form/button-bar.mjs +114 -2
- package/test/cases/components/form/select.mjs +488 -1
- package/test/cases/components/layout/panel.mjs +87 -73
- package/test/cases/components/layout/slit-panel.mjs +121 -73
|
@@ -194,8 +194,9 @@ describe("ButtonBar", function () {
|
|
|
194
194
|
return originalSetAttribute(name, value);
|
|
195
195
|
};
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
resizeObserver.triggerResize([]);
|
|
197
|
+
const wrapperResizeEntry = { target: wrapper };
|
|
198
|
+
resizeObserver.triggerResize([wrapperResizeEntry]);
|
|
199
|
+
resizeObserver.triggerResize([wrapperResizeEntry]);
|
|
199
200
|
|
|
200
201
|
expect(scheduledCallbacks.length).to.equal(1);
|
|
201
202
|
await flushFrames();
|
|
@@ -210,4 +211,115 @@ describe("ButtonBar", function () {
|
|
|
210
211
|
globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
|
|
211
212
|
}
|
|
212
213
|
});
|
|
214
|
+
|
|
215
|
+
it("should ignore resize feedback caused by its own layout writes", async function () {
|
|
216
|
+
const OriginalResizeObserver = window.ResizeObserver;
|
|
217
|
+
const originalGlobalResizeObserver = globalThis.ResizeObserver;
|
|
218
|
+
const originalRequestAnimationFrame = window.requestAnimationFrame;
|
|
219
|
+
const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
|
|
220
|
+
|
|
221
|
+
class TrackingResizeObserver extends ResizeObserverMock {
|
|
222
|
+
static instances = [];
|
|
223
|
+
|
|
224
|
+
constructor(callback) {
|
|
225
|
+
super(callback);
|
|
226
|
+
TrackingResizeObserver.instances.push(this);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const scheduledCallbacks = [];
|
|
231
|
+
const flushOneFrame = async () => {
|
|
232
|
+
const callback = scheduledCallbacks.shift();
|
|
233
|
+
if (callback instanceof Function) {
|
|
234
|
+
callback();
|
|
235
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
const flushFrames = async () => {
|
|
239
|
+
while (scheduledCallbacks.length > 0) {
|
|
240
|
+
await flushOneFrame();
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
window.ResizeObserver = TrackingResizeObserver;
|
|
246
|
+
globalThis.ResizeObserver = TrackingResizeObserver;
|
|
247
|
+
window.requestAnimationFrame = (callback) => {
|
|
248
|
+
scheduledCallbacks.push(callback);
|
|
249
|
+
return scheduledCallbacks.length;
|
|
250
|
+
};
|
|
251
|
+
globalThis.requestAnimationFrame = window.requestAnimationFrame;
|
|
252
|
+
|
|
253
|
+
const mocks = document.getElementById("mocks");
|
|
254
|
+
mocks.innerHTML = `
|
|
255
|
+
<div id="feedback-bar-wrapper">
|
|
256
|
+
<monster-button-bar id="feedback-bar">
|
|
257
|
+
<button type="button">One</button>
|
|
258
|
+
<button type="button">Two</button>
|
|
259
|
+
<button type="button">Three</button>
|
|
260
|
+
</monster-button-bar>
|
|
261
|
+
</div>
|
|
262
|
+
`;
|
|
263
|
+
|
|
264
|
+
const wrapper = document.getElementById("feedback-bar-wrapper");
|
|
265
|
+
const bar = document.getElementById("feedback-bar");
|
|
266
|
+
|
|
267
|
+
wrapper.style.boxSizing = "border-box";
|
|
268
|
+
wrapper.style.width = "80px";
|
|
269
|
+
Object.defineProperty(wrapper, "clientWidth", {
|
|
270
|
+
configurable: true,
|
|
271
|
+
value: 80,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const buttons = Array.from(bar.querySelectorAll("button"));
|
|
275
|
+
for (const button of buttons) {
|
|
276
|
+
Object.defineProperty(button, "offsetWidth", {
|
|
277
|
+
configurable: true,
|
|
278
|
+
value: 48,
|
|
279
|
+
});
|
|
280
|
+
button.getBoundingClientRect = () => ({
|
|
281
|
+
width: 48,
|
|
282
|
+
height: 32,
|
|
283
|
+
top: 0,
|
|
284
|
+
left: 0,
|
|
285
|
+
right: 48,
|
|
286
|
+
bottom: 32,
|
|
287
|
+
x: 0,
|
|
288
|
+
y: 0,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const switchButton = bar.shadowRoot.querySelector(
|
|
293
|
+
'[data-monster-role="switch"]',
|
|
294
|
+
);
|
|
295
|
+
Object.defineProperty(switchButton, "offsetWidth", {
|
|
296
|
+
configurable: true,
|
|
297
|
+
value: 20,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
await flushOneFrame();
|
|
301
|
+
|
|
302
|
+
const resizeObserver = TrackingResizeObserver.instances.find((observer) =>
|
|
303
|
+
observer.elements.includes(wrapper),
|
|
304
|
+
);
|
|
305
|
+
expect(resizeObserver).to.exist;
|
|
306
|
+
|
|
307
|
+
const suppressedFrameCount = scheduledCallbacks.length;
|
|
308
|
+
resizeObserver.triggerResize([{ target: wrapper }]);
|
|
309
|
+
expect(scheduledCallbacks.length).to.equal(suppressedFrameCount);
|
|
310
|
+
|
|
311
|
+
await flushFrames();
|
|
312
|
+
|
|
313
|
+
resizeObserver.triggerResize([{ target: wrapper }]);
|
|
314
|
+
expect(scheduledCallbacks.length).to.equal(1);
|
|
315
|
+
|
|
316
|
+
await flushFrames();
|
|
317
|
+
expect(scheduledCallbacks.length).to.equal(0);
|
|
318
|
+
} finally {
|
|
319
|
+
window.ResizeObserver = OriginalResizeObserver;
|
|
320
|
+
globalThis.ResizeObserver = originalGlobalResizeObserver;
|
|
321
|
+
window.requestAnimationFrame = originalRequestAnimationFrame;
|
|
322
|
+
globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
213
325
|
});
|
|
@@ -93,7 +93,11 @@ let Select,
|
|
|
93
93
|
resolveSelectPopperWidthConstraints,
|
|
94
94
|
resolveSelectVisibleRect,
|
|
95
95
|
resolveSelectViewportMetrics,
|
|
96
|
-
fetchReference
|
|
96
|
+
fetchReference,
|
|
97
|
+
FLOATING_LAYOUT_REASON,
|
|
98
|
+
cancelFloatingLayout,
|
|
99
|
+
enqueueFloatingLayout,
|
|
100
|
+
flushFloatingLayoutQueueForTests;
|
|
97
101
|
|
|
98
102
|
describe('Select', function () {
|
|
99
103
|
|
|
@@ -124,6 +128,12 @@ describe('Select', function () {
|
|
|
124
128
|
return import("../../../../source/components/form/stylesheet/select.mjs");
|
|
125
129
|
}).then((m) => {
|
|
126
130
|
SelectStyleSheet = m['SelectStyleSheet'];
|
|
131
|
+
return import("../../../../source/components/form/util/floating-layout-queue.mjs");
|
|
132
|
+
}).then((m) => {
|
|
133
|
+
FLOATING_LAYOUT_REASON = m['FLOATING_LAYOUT_REASON'];
|
|
134
|
+
cancelFloatingLayout = m['cancelFloatingLayout'];
|
|
135
|
+
enqueueFloatingLayout = m['enqueueFloatingLayout'];
|
|
136
|
+
flushFloatingLayoutQueueForTests = m['flushFloatingLayoutQueueForTests'];
|
|
127
137
|
done()
|
|
128
138
|
}).catch(e => done(e))
|
|
129
139
|
|
|
@@ -138,9 +148,242 @@ describe('Select', function () {
|
|
|
138
148
|
: "";
|
|
139
149
|
|
|
140
150
|
expect(cssText).to.contain("--monster-select-container-overflow");
|
|
151
|
+
expect(cssText).to.contain("--monster-select-container-align-self");
|
|
152
|
+
expect(cssText).to.contain("--monster-select-container-block-size");
|
|
141
153
|
expect(cssText).to.contain("--monster-select-control-padding");
|
|
142
154
|
expect(cssText).to.contain("--monster-select-selection-margin");
|
|
143
155
|
expect(cssText).to.contain("--monster-select-selection-flex-wrap");
|
|
156
|
+
expect(cssText.replace(/\s+/g, '')).to.contain('z-index:var(--monster-z-index-popover)');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should deduplicate floating layout queue jobs by popper element', async function () {
|
|
160
|
+
const popper = document.createElement('div');
|
|
161
|
+
const reasons = [];
|
|
162
|
+
let mutateCount = 0;
|
|
163
|
+
|
|
164
|
+
enqueueFloatingLayout({
|
|
165
|
+
popperElement: popper,
|
|
166
|
+
reason: FLOATING_LAYOUT_REASON.RESIZE,
|
|
167
|
+
mutate: (measurement, reason) => {
|
|
168
|
+
mutateCount += 1;
|
|
169
|
+
reasons.push(reason);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
enqueueFloatingLayout({
|
|
173
|
+
popperElement: popper,
|
|
174
|
+
reason: FLOATING_LAYOUT_REASON.POSITION,
|
|
175
|
+
mutate: (measurement, reason) => {
|
|
176
|
+
mutateCount += 1;
|
|
177
|
+
reasons.push(reason);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
await flushFloatingLayoutQueueForTests();
|
|
182
|
+
|
|
183
|
+
expect(mutateCount).to.equal(1);
|
|
184
|
+
expect(reasons[0] & FLOATING_LAYOUT_REASON.RESIZE).to.equal(FLOATING_LAYOUT_REASON.RESIZE);
|
|
185
|
+
expect(reasons[0] & FLOATING_LAYOUT_REASON.POSITION).to.equal(FLOATING_LAYOUT_REASON.POSITION);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should keep independent floating layout queue jobs for different poppers', async function () {
|
|
189
|
+
const firstPopper = document.createElement('div');
|
|
190
|
+
const secondPopper = document.createElement('div');
|
|
191
|
+
const flushed = [];
|
|
192
|
+
|
|
193
|
+
enqueueFloatingLayout({
|
|
194
|
+
popperElement: firstPopper,
|
|
195
|
+
reason: FLOATING_LAYOUT_REASON.RESIZE,
|
|
196
|
+
mutate: () => {
|
|
197
|
+
flushed.push('first');
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
enqueueFloatingLayout({
|
|
201
|
+
popperElement: secondPopper,
|
|
202
|
+
reason: FLOATING_LAYOUT_REASON.POSITION,
|
|
203
|
+
mutate: () => {
|
|
204
|
+
flushed.push('second');
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
await flushFloatingLayoutQueueForTests();
|
|
209
|
+
|
|
210
|
+
expect(flushed).to.deep.equal(['first', 'second']);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should merge position-only floating layout events without dropping pending mutation', async function () {
|
|
214
|
+
const popper = document.createElement('div');
|
|
215
|
+
const flushed = [];
|
|
216
|
+
|
|
217
|
+
enqueueFloatingLayout({
|
|
218
|
+
popperElement: popper,
|
|
219
|
+
reason: FLOATING_LAYOUT_REASON.CONTENT,
|
|
220
|
+
mutate: () => {
|
|
221
|
+
flushed.push('mutate');
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
enqueueFloatingLayout({
|
|
225
|
+
popperElement: popper,
|
|
226
|
+
reason: FLOATING_LAYOUT_REASON.POSITION,
|
|
227
|
+
position: () => {
|
|
228
|
+
flushed.push('position');
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
await flushFloatingLayoutQueueForTests();
|
|
233
|
+
|
|
234
|
+
expect(flushed).to.deep.equal(['mutate', 'position']);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('should defer reentrant floating layout queue jobs to a later flush', async function () {
|
|
238
|
+
const popper = document.createElement('div');
|
|
239
|
+
let mutateCount = 0;
|
|
240
|
+
|
|
241
|
+
enqueueFloatingLayout({
|
|
242
|
+
popperElement: popper,
|
|
243
|
+
reason: FLOATING_LAYOUT_REASON.RESIZE,
|
|
244
|
+
mutate: () => {
|
|
245
|
+
mutateCount += 1;
|
|
246
|
+
enqueueFloatingLayout({
|
|
247
|
+
popperElement: popper,
|
|
248
|
+
reason: FLOATING_LAYOUT_REASON.SETTLE,
|
|
249
|
+
mutate: () => {
|
|
250
|
+
mutateCount += 1;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
await flushFloatingLayoutQueueForTests();
|
|
257
|
+
expect(mutateCount).to.equal(1);
|
|
258
|
+
|
|
259
|
+
await flushFloatingLayoutQueueForTests();
|
|
260
|
+
expect(mutateCount).to.equal(2);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should flush floating layout queue jobs when requestAnimationFrame stalls', async function () {
|
|
264
|
+
const originalRequestAnimationFrame = global.requestAnimationFrame;
|
|
265
|
+
const originalCancelAnimationFrame = global.cancelAnimationFrame;
|
|
266
|
+
const popper = document.createElement('div');
|
|
267
|
+
let mutateCount = 0;
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
global.requestAnimationFrame = () => 1;
|
|
271
|
+
global.cancelAnimationFrame = () => {};
|
|
272
|
+
|
|
273
|
+
const promise = enqueueFloatingLayout({
|
|
274
|
+
popperElement: popper,
|
|
275
|
+
reason: FLOATING_LAYOUT_REASON.POSITION,
|
|
276
|
+
mutate: () => {
|
|
277
|
+
mutateCount += 1;
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
await waitForCondition(() => mutateCount === 1);
|
|
282
|
+
await promise;
|
|
283
|
+
|
|
284
|
+
expect(mutateCount).to.equal(1);
|
|
285
|
+
} finally {
|
|
286
|
+
global.requestAnimationFrame = originalRequestAnimationFrame;
|
|
287
|
+
global.cancelAnimationFrame = originalCancelAnimationFrame;
|
|
288
|
+
await flushFloatingLayoutQueueForTests();
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should cancel stalled floating layout queue jobs before the watchdog flushes', async function () {
|
|
293
|
+
const originalRequestAnimationFrame = global.requestAnimationFrame;
|
|
294
|
+
const originalCancelAnimationFrame = global.cancelAnimationFrame;
|
|
295
|
+
const popper = document.createElement('div');
|
|
296
|
+
let mutateCount = 0;
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
global.requestAnimationFrame = () => 1;
|
|
300
|
+
global.cancelAnimationFrame = () => {};
|
|
301
|
+
|
|
302
|
+
const promise = enqueueFloatingLayout({
|
|
303
|
+
popperElement: popper,
|
|
304
|
+
reason: FLOATING_LAYOUT_REASON.POSITION,
|
|
305
|
+
mutate: () => {
|
|
306
|
+
mutateCount += 1;
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
cancelFloatingLayout(popper);
|
|
311
|
+
await promise;
|
|
312
|
+
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
313
|
+
|
|
314
|
+
expect(mutateCount).to.equal(0);
|
|
315
|
+
} finally {
|
|
316
|
+
global.requestAnimationFrame = originalRequestAnimationFrame;
|
|
317
|
+
global.cancelAnimationFrame = originalCancelAnimationFrame;
|
|
318
|
+
await flushFloatingLayoutQueueForTests();
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('should flush reentrant floating layout queue jobs through the watchdog', async function () {
|
|
323
|
+
const originalRequestAnimationFrame = global.requestAnimationFrame;
|
|
324
|
+
const originalCancelAnimationFrame = global.cancelAnimationFrame;
|
|
325
|
+
const popper = document.createElement('div');
|
|
326
|
+
let mutateCount = 0;
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
global.requestAnimationFrame = () => 1;
|
|
330
|
+
global.cancelAnimationFrame = () => {};
|
|
331
|
+
|
|
332
|
+
enqueueFloatingLayout({
|
|
333
|
+
popperElement: popper,
|
|
334
|
+
reason: FLOATING_LAYOUT_REASON.RESIZE,
|
|
335
|
+
mutate: () => {
|
|
336
|
+
mutateCount += 1;
|
|
337
|
+
if (mutateCount === 1) {
|
|
338
|
+
enqueueFloatingLayout({
|
|
339
|
+
popperElement: popper,
|
|
340
|
+
reason: FLOATING_LAYOUT_REASON.SETTLE,
|
|
341
|
+
mutate: () => {
|
|
342
|
+
mutateCount += 1;
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
await waitForCondition(() => mutateCount === 2);
|
|
350
|
+
|
|
351
|
+
expect(mutateCount).to.equal(2);
|
|
352
|
+
} finally {
|
|
353
|
+
global.requestAnimationFrame = originalRequestAnimationFrame;
|
|
354
|
+
global.cancelAnimationFrame = originalCancelAnimationFrame;
|
|
355
|
+
await flushFloatingLayoutQueueForTests();
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('should flush many independent floating layout queue jobs in one stalled frame', async function () {
|
|
360
|
+
const originalRequestAnimationFrame = global.requestAnimationFrame;
|
|
361
|
+
const originalCancelAnimationFrame = global.cancelAnimationFrame;
|
|
362
|
+
const poppers = Array.from({length: 8}, () => document.createElement('div'));
|
|
363
|
+
const flushed = [];
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
global.requestAnimationFrame = () => 1;
|
|
367
|
+
global.cancelAnimationFrame = () => {};
|
|
368
|
+
|
|
369
|
+
for (const [index, popper] of poppers.entries()) {
|
|
370
|
+
enqueueFloatingLayout({
|
|
371
|
+
popperElement: popper,
|
|
372
|
+
reason: FLOATING_LAYOUT_REASON.POSITION,
|
|
373
|
+
mutate: () => {
|
|
374
|
+
flushed.push(index);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
await waitForCondition(() => flushed.length === poppers.length);
|
|
380
|
+
|
|
381
|
+
expect(flushed).to.deep.equal([0, 1, 2, 3, 4, 5, 6, 7]);
|
|
382
|
+
} finally {
|
|
383
|
+
global.requestAnimationFrame = originalRequestAnimationFrame;
|
|
384
|
+
global.cancelAnimationFrame = originalCancelAnimationFrame;
|
|
385
|
+
await flushFloatingLayoutQueueForTests();
|
|
386
|
+
}
|
|
144
387
|
});
|
|
145
388
|
|
|
146
389
|
describe('With fetch', function () {
|
|
@@ -279,6 +522,144 @@ describe('Select', function () {
|
|
|
279
522
|
mocks.innerHTML = "";
|
|
280
523
|
});
|
|
281
524
|
|
|
525
|
+
it('should render the popper outside of the internal control clipping container', function () {
|
|
526
|
+
const mocks = document.getElementById('mocks');
|
|
527
|
+
const select = document.createElement('monster-select');
|
|
528
|
+
|
|
529
|
+
select.setOption('options', [
|
|
530
|
+
{label: 'Alpha', value: 'alpha'},
|
|
531
|
+
{label: 'Beta', value: 'beta'}
|
|
532
|
+
]);
|
|
533
|
+
|
|
534
|
+
mocks.appendChild(select);
|
|
535
|
+
|
|
536
|
+
const shadowRoot = select.shadowRoot;
|
|
537
|
+
const control = shadowRoot.querySelector('[data-monster-role=control]');
|
|
538
|
+
const popper = shadowRoot.querySelector('[data-monster-role=popper]');
|
|
539
|
+
|
|
540
|
+
expect(control).to.exist;
|
|
541
|
+
expect(popper).to.exist;
|
|
542
|
+
expect(control.contains(popper)).to.equal(false);
|
|
543
|
+
expect(popper.parentNode).to.equal(shadowRoot);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it('should open in a constrained filter bar without a control overflow override', function (done) {
|
|
547
|
+
const mocks = document.getElementById('mocks');
|
|
548
|
+
const filterBar = document.createElement('div');
|
|
549
|
+
const select = document.createElement('monster-select');
|
|
550
|
+
|
|
551
|
+
filterBar.style.display = 'grid';
|
|
552
|
+
filterBar.style.gridTemplateColumns = 'minmax(0, 1fr)';
|
|
553
|
+
filterBar.style.height = '44px';
|
|
554
|
+
filterBar.style.overflow = 'hidden';
|
|
555
|
+
|
|
556
|
+
select.setOption('options', [
|
|
557
|
+
{label: 'Alpha', value: 'alpha'},
|
|
558
|
+
{label: 'Beta', value: 'beta'}
|
|
559
|
+
]);
|
|
560
|
+
|
|
561
|
+
filterBar.appendChild(select);
|
|
562
|
+
mocks.appendChild(filterBar);
|
|
563
|
+
|
|
564
|
+
const shadowRoot = select.shadowRoot;
|
|
565
|
+
const control = shadowRoot.querySelector('[data-monster-role=control]');
|
|
566
|
+
const popper = shadowRoot.querySelector('[data-monster-role=popper]');
|
|
567
|
+
|
|
568
|
+
control.style.overflow = 'hidden';
|
|
569
|
+
control.getBoundingClientRect = () => ({
|
|
570
|
+
width: 180,
|
|
571
|
+
height: 40,
|
|
572
|
+
top: 100,
|
|
573
|
+
left: 40,
|
|
574
|
+
right: 220,
|
|
575
|
+
bottom: 140,
|
|
576
|
+
x: 40,
|
|
577
|
+
y: 100
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
setTimeout(() => {
|
|
581
|
+
try {
|
|
582
|
+
shadowRoot.querySelector('[data-monster-role=container]').click();
|
|
583
|
+
setTimeout(() => {
|
|
584
|
+
try {
|
|
585
|
+
expect(control.style.overflow).to.equal('hidden');
|
|
586
|
+
expect(control.contains(popper)).to.equal(false);
|
|
587
|
+
expect(popper.style.display).to.equal('block');
|
|
588
|
+
done();
|
|
589
|
+
} catch (e) {
|
|
590
|
+
done(e);
|
|
591
|
+
}
|
|
592
|
+
}, 80);
|
|
593
|
+
} catch (e) {
|
|
594
|
+
done(e);
|
|
595
|
+
}
|
|
596
|
+
}, 20);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it('should preserve an immediate open intent while configured options render', async function () {
|
|
600
|
+
const mocks = document.getElementById('mocks');
|
|
601
|
+
const select = document.createElement('monster-select');
|
|
602
|
+
|
|
603
|
+
mocks.appendChild(select);
|
|
604
|
+
select.setOption('options', [
|
|
605
|
+
{label: 'Alpha', value: 'alpha'},
|
|
606
|
+
{label: 'Beta', value: 'beta'}
|
|
607
|
+
]);
|
|
608
|
+
|
|
609
|
+
const shadowRoot = select.shadowRoot;
|
|
610
|
+
const container = shadowRoot.querySelector('[data-monster-role=container]');
|
|
611
|
+
const popper = shadowRoot.querySelector('[data-monster-role=popper]');
|
|
612
|
+
|
|
613
|
+
container.click();
|
|
614
|
+
|
|
615
|
+
await waitForCondition(() => {
|
|
616
|
+
return shadowRoot.querySelectorAll('[data-monster-role=option]').length === 2;
|
|
617
|
+
});
|
|
618
|
+
await waitForCondition(() => {
|
|
619
|
+
return popper.style.display === 'block';
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
expect(popper.style.display).to.equal('block');
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it('should keep an empty select without popper filter closed', async function () {
|
|
626
|
+
const mocks = document.getElementById('mocks');
|
|
627
|
+
const select = document.createElement('monster-select');
|
|
628
|
+
|
|
629
|
+
mocks.appendChild(select);
|
|
630
|
+
|
|
631
|
+
const shadowRoot = select.shadowRoot;
|
|
632
|
+
const container = shadowRoot.querySelector('[data-monster-role=container]');
|
|
633
|
+
const popper = shadowRoot.querySelector('[data-monster-role=popper]');
|
|
634
|
+
|
|
635
|
+
container.click();
|
|
636
|
+
|
|
637
|
+
await new Promise((resolve) => setTimeout(resolve, 160));
|
|
638
|
+
|
|
639
|
+
expect(popper.style.display).to.not.equal('block');
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
it('should still open an empty select with a popper filter', async function () {
|
|
643
|
+
const mocks = document.getElementById('mocks');
|
|
644
|
+
const select = document.createElement('monster-select');
|
|
645
|
+
|
|
646
|
+
select.setOption('filter.mode', 'options');
|
|
647
|
+
select.setOption('filter.position', 'popper');
|
|
648
|
+
mocks.appendChild(select);
|
|
649
|
+
|
|
650
|
+
const shadowRoot = select.shadowRoot;
|
|
651
|
+
const container = shadowRoot.querySelector('[data-monster-role=container]');
|
|
652
|
+
const popper = shadowRoot.querySelector('[data-monster-role=popper]');
|
|
653
|
+
|
|
654
|
+
container.click();
|
|
655
|
+
|
|
656
|
+
await waitForCondition(() => {
|
|
657
|
+
return popper.style.display === 'block';
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
expect(popper.style.display).to.equal('block');
|
|
661
|
+
});
|
|
662
|
+
|
|
282
663
|
it('should allow the popper to become wider than a narrow control', function (done) {
|
|
283
664
|
const mocks = document.getElementById('mocks');
|
|
284
665
|
const select = document.createElement('monster-select');
|
|
@@ -370,6 +751,109 @@ describe('Select', function () {
|
|
|
370
751
|
}, 100);
|
|
371
752
|
});
|
|
372
753
|
|
|
754
|
+
it('should keep the control bar popper width stable while layout observers settle', function (done) {
|
|
755
|
+
const mocks = document.getElementById('mocks');
|
|
756
|
+
mocks.innerHTML = '<monster-control-bar id="stable-select-bar"></monster-control-bar>';
|
|
757
|
+
|
|
758
|
+
const bar = document.getElementById('stable-select-bar');
|
|
759
|
+
const select = document.createElement('monster-select');
|
|
760
|
+
|
|
761
|
+
select.setOption('options', [
|
|
762
|
+
{label: 'Alpha', value: 'alpha'},
|
|
763
|
+
{label: 'Beta', value: 'beta'}
|
|
764
|
+
]);
|
|
765
|
+
|
|
766
|
+
bar.appendChild(select);
|
|
767
|
+
|
|
768
|
+
const shadowRoot = select.shadowRoot;
|
|
769
|
+
const control = shadowRoot.querySelector('[data-monster-role=control]');
|
|
770
|
+
const container = shadowRoot.querySelector('[data-monster-role=container]');
|
|
771
|
+
const popper = shadowRoot.querySelector('[data-monster-role=popper]');
|
|
772
|
+
let measuredWidth = 528;
|
|
773
|
+
|
|
774
|
+
const getRect = () => ({
|
|
775
|
+
width: measuredWidth,
|
|
776
|
+
height: 40,
|
|
777
|
+
top: 100,
|
|
778
|
+
left: 40,
|
|
779
|
+
right: 40 + measuredWidth,
|
|
780
|
+
bottom: 140,
|
|
781
|
+
x: 40,
|
|
782
|
+
y: 100
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
control.getBoundingClientRect = getRect;
|
|
786
|
+
select.getBoundingClientRect = getRect;
|
|
787
|
+
|
|
788
|
+
setTimeout(() => {
|
|
789
|
+
try {
|
|
790
|
+
container.click();
|
|
791
|
+
setTimeout(() => {
|
|
792
|
+
try {
|
|
793
|
+
expect(popper.style.display).to.equal('block');
|
|
794
|
+
expect(popper.dataset.monsterPreferredWidth).to.equal('528');
|
|
795
|
+
expect(popper.style.width).to.equal('528px');
|
|
796
|
+
expect(select.style.flex).to.equal('0 0 528px');
|
|
797
|
+
|
|
798
|
+
measuredWidth = 279;
|
|
799
|
+
select.calcAndSetOptionsDimension();
|
|
800
|
+
|
|
801
|
+
expect(popper.dataset.monsterPreferredWidth).to.equal('528');
|
|
802
|
+
expect(popper.style.width).to.equal('528px');
|
|
803
|
+
|
|
804
|
+
container.click();
|
|
805
|
+
|
|
806
|
+
setTimeout(() => {
|
|
807
|
+
try {
|
|
808
|
+
expect(popper.style.display).to.equal('none');
|
|
809
|
+
expect(select.style.flex).to.equal('');
|
|
810
|
+
expect(select.style.inlineSize).to.equal('');
|
|
811
|
+
expect(select.style.minInlineSize).to.equal('');
|
|
812
|
+
done();
|
|
813
|
+
} catch (e) {
|
|
814
|
+
done(e);
|
|
815
|
+
}
|
|
816
|
+
}, 80);
|
|
817
|
+
} catch (e) {
|
|
818
|
+
done(e);
|
|
819
|
+
}
|
|
820
|
+
}, 80);
|
|
821
|
+
} catch (e) {
|
|
822
|
+
done(e);
|
|
823
|
+
}
|
|
824
|
+
}, 100);
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
it('should use the same sibling popper structure for derived selects', function () {
|
|
828
|
+
const tagName = 'issue-460-derived-select';
|
|
829
|
+
const mocks = document.getElementById('mocks');
|
|
830
|
+
|
|
831
|
+
if (!customElements.get(tagName)) {
|
|
832
|
+
customElements.define(tagName, class Issue460DerivedSelect extends Select {
|
|
833
|
+
static getTag() {
|
|
834
|
+
return tagName;
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const select = document.createElement(tagName);
|
|
840
|
+
select.setOption('options', [
|
|
841
|
+
{label: 'Alpha', value: 'alpha'},
|
|
842
|
+
{label: 'Beta', value: 'beta'}
|
|
843
|
+
]);
|
|
844
|
+
|
|
845
|
+
mocks.appendChild(select);
|
|
846
|
+
|
|
847
|
+
const shadowRoot = select.shadowRoot;
|
|
848
|
+
const control = shadowRoot.querySelector('[data-monster-role=control]');
|
|
849
|
+
const popper = shadowRoot.querySelector('[data-monster-role=popper]');
|
|
850
|
+
|
|
851
|
+
expect(control).to.exist;
|
|
852
|
+
expect(popper).to.exist;
|
|
853
|
+
expect(control.contains(popper)).to.equal(false);
|
|
854
|
+
expect(popper.parentNode).to.equal(shadowRoot);
|
|
855
|
+
});
|
|
856
|
+
|
|
373
857
|
it('should keep the option list height tight for short lists', function () {
|
|
374
858
|
const result = resolveSelectListDimension({
|
|
375
859
|
visibleOptionHeights: [28, 28],
|
|
@@ -1572,6 +2056,9 @@ describe('Select', function () {
|
|
|
1572
2056
|
|
|
1573
2057
|
expect(select.getOption('total')).to.equal(0);
|
|
1574
2058
|
expect(select.getOption('messages.total')).to.equal('');
|
|
2059
|
+
await waitForCondition(() => {
|
|
2060
|
+
return select.getOption('messages.emptyOptions').includes('Please consider modifying the filter');
|
|
2061
|
+
});
|
|
1575
2062
|
expect(select.getOption('messages.emptyOptions')).to.contain('Please consider modifying the filter');
|
|
1576
2063
|
});
|
|
1577
2064
|
|