@lion/ui 0.18.0-menu-system.4 → 0.18.0-menu-system.6
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/components/collapsible/test-suites/DisclosureMixin.suite.js +30 -0
- package/components/menu/src/InteractiveListMixin.js +60 -24
- package/components/menu/src/LionMenu.js +1 -1
- package/components/menu/src/LionToolbar.js +1 -1
- package/components/menu/src/MoreButtonMenuMixin.js +18 -27
- package/components/menu/src/utils/listItemInteractions.js +27 -7
- package/components/menu/test/lion-menu.test.js +4 -1
- package/components/menu/test-suites/InteractiveListMixin.suite.js +502 -2
- package/components/menu/test-suites/LionMenuHybrid.suite.js +583 -0
- package/components/overlays/src/OverlayController.js +1 -1
- package/custom-elements.json +263 -19
- package/dist-types/components/collapsible/test-suites/DisclosureMixin.suite.d.ts +2 -0
- package/dist-types/components/collapsible/test-suites/DisclosureMixin.suite.d.ts.map +1 -0
- package/dist-types/components/menu/src/InteractiveListMixin.d.ts +11 -5
- package/dist-types/components/menu/src/InteractiveListMixin.d.ts.map +1 -1
- package/dist-types/components/menu/src/LionMenu.d.ts +4 -2
- package/dist-types/components/menu/src/LionMenu.d.ts.map +1 -1
- package/dist-types/components/menu/src/LionMenuOverlay.d.ts +8 -4
- package/dist-types/components/menu/src/LionMenuOverlay.d.ts.map +1 -1
- package/dist-types/components/menu/src/LionTree.d.ts +4 -2
- package/dist-types/components/menu/src/LionTree.d.ts.map +1 -1
- package/dist-types/components/menu/src/MoreButtonMenuMixin.d.ts +0 -1
- package/dist-types/components/menu/src/MoreButtonMenuMixin.d.ts.map +1 -1
- package/dist-types/components/menu/src/MultiLevelListMixin.d.ts +4 -2
- package/dist-types/components/menu/src/MultiLevelListMixin.d.ts.map +1 -1
- package/dist-types/components/menu/src/utils/listItemInteractions.d.ts +8 -2
- package/dist-types/components/menu/src/utils/listItemInteractions.d.ts.map +1 -1
- package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts +6 -5
- package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts.map +1 -1
- package/dist-types/components/menu/test-suites/LionMenuHybrid.suite.d.ts +5 -0
- package/dist-types/components/menu/test-suites/LionMenuHybrid.suite.d.ts.map +1 -0
- package/dist-types/exports/menu-test-suites.d.ts +2 -0
- package/dist-types/exports/menu-test-suites.d.ts.map +1 -0
- package/dist-types/tsconfig.tsbuildinfo +1 -1
- package/docs/components/checkbox-group/overview.md +2 -2
- package/docs/components/collapsible/overview.md +1 -1
- package/docs/components/combobox/overview.md +1 -1
- package/docs/components/combobox/use-cases.md +1 -1
- package/docs/components/dialog/overview.md +1 -1
- package/docs/components/fieldset/overview.md +5 -5
- package/docs/components/form/overview.md +3 -3
- package/docs/components/form/use-cases.md +2 -2
- package/docs/components/icon/use-cases.md +1 -1
- package/docs/components/input/overview.md +1 -1
- package/docs/components/input-amount/overview.md +3 -3
- package/docs/components/input-date/overview.md +3 -3
- package/docs/components/input-datepicker/overview.md +5 -5
- package/docs/components/input-email/overview.md +2 -2
- package/docs/components/input-file/overview.md +1 -1
- package/docs/components/input-iban/overview.md +2 -2
- package/docs/components/input-range/overview.md +2 -2
- package/docs/components/input-stepper/overview.md +1 -1
- package/docs/components/input-tel/overview.md +1 -1
- package/docs/components/input-tel/use-cases.md +6 -6
- package/docs/components/input-tel-dropdown/overview.md +1 -1
- package/docs/components/listbox/use-cases.md +1 -1
- package/docs/components/menu/api-table.md +197 -192
- package/docs/components/menu/overview.md +39 -39
- package/docs/components/radio-group/overview.md +3 -3
- package/docs/components/select/use-cases.md +1 -1
- package/docs/components/select-rich/use-cases.md +2 -2
- package/docs/components/tabs/examples.md +1 -1
- package/docs/fundamentals/index.md +4 -4
- package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
- package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
- package/docs/fundamentals/rationales/accessibility.md +1 -1
- package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
- package/docs/fundamentals/systems/core/overview.md +1 -1
- package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
- package/docs/fundamentals/systems/form/model-value.md +1 -1
- package/docs/fundamentals/systems/form/overview.md +28 -28
- package/docs/fundamentals/systems/form/validate.md +2 -2
- package/docs/fundamentals/systems/localize/overview.md +4 -4
- package/docs/fundamentals/systems/overlays/configuration.md +2 -2
- package/docs/fundamentals/systems/overlays/overview.md +5 -5
- package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
- package/docs/fundamentals/tools/helpers/overview.md +1 -1
- package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
- package/docs/guides/how-to/create-a-custom-field.md +5 -5
- package/docs/guides/how-to/create-a-form.md +4 -4
- package/docs/guides/how-to/extend-a-native-input.md +2 -2
- package/docs/guides/how-to/index.md +1 -1
- package/docs/guides/index.md +8 -8
- package/docs/guides/principles/index.md +5 -5
- package/docs/guides/principles/styling.md +1 -1
- package/exports/menu-test-suites.js +2 -0
- package/menu-test-suites.js +2 -0
- package/package.json +1 -1
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { LitElement } from 'lit';
|
|
2
|
-
import { expect, html, fixture, unsafeStatic, defineCE } from '@open-wc/testing';
|
|
2
|
+
import { expect, html, fixture as _fixture, unsafeStatic, defineCE } from '@open-wc/testing';
|
|
3
|
+
import { sendMouse, resetMouse } from '@web/test-runner-commands';
|
|
4
|
+
import { useFakeTimers } from 'sinon';
|
|
5
|
+
import { getDeepActiveElement } from '@lion/ui/overlays.js';
|
|
3
6
|
import { InteractiveListMixin } from '../src/InteractiveListMixin.js';
|
|
4
7
|
|
|
5
8
|
class InteractiveListClass extends InteractiveListMixin(LitElement) {}
|
|
@@ -20,10 +23,28 @@ const supportedRoles = [
|
|
|
20
23
|
];
|
|
21
24
|
|
|
22
25
|
/**
|
|
23
|
-
* @typedef {
|
|
26
|
+
* @typedef {import('../src/LionMenu.js').LionMenu} LionMenu
|
|
27
|
+
* @typedef {import('lit').TemplateResult} TemplateResult
|
|
28
|
+
*
|
|
29
|
+
* * @typedef {Object} TestConfig
|
|
24
30
|
* @property {string} tagString
|
|
25
31
|
*/
|
|
26
32
|
|
|
33
|
+
const fixture = /** @type {(arg: TemplateResult) => Promise<LionMenu>} */ (_fixture);
|
|
34
|
+
|
|
35
|
+
const l1Config = {
|
|
36
|
+
isBar: true,
|
|
37
|
+
hasFullWidthFlyout: true,
|
|
38
|
+
openableConfig: {
|
|
39
|
+
// We want disclosure behavior
|
|
40
|
+
placementMode: 'custom',
|
|
41
|
+
// N.B. we don't have an invoker...
|
|
42
|
+
isActivated: false,
|
|
43
|
+
inheritsReferenceWidth: 'none',
|
|
44
|
+
focusContentOnOpen: true,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
27
48
|
/**
|
|
28
49
|
* @param {TestConfig} [customConfig]
|
|
29
50
|
*/
|
|
@@ -122,6 +143,162 @@ export function runInteractiveListMixinSuite(customConfig) {
|
|
|
122
143
|
});
|
|
123
144
|
});
|
|
124
145
|
|
|
146
|
+
describe('Programmatic interaction', () => {
|
|
147
|
+
// If we have the item line directly in the fixture the linting rule lit-a11y/role-has-required-aria-attrs
|
|
148
|
+
// will fail. Inside our code we set the required aria attributes, but the linter doesn't know that.
|
|
149
|
+
const item = /** @param {number} i, @param {string} role */ (i, role) => html`
|
|
150
|
+
<div role="${role}" id="item${i}">Item ${i}</div>
|
|
151
|
+
`;
|
|
152
|
+
|
|
153
|
+
it('set checked state sets aria-current attribute with role="listitem"', async () => {
|
|
154
|
+
const el = await fixture(html`
|
|
155
|
+
<${tag}>
|
|
156
|
+
${item(1, 'listitem')}
|
|
157
|
+
${item(2, 'listitem')}
|
|
158
|
+
</${tag}>
|
|
159
|
+
`);
|
|
160
|
+
el.setCheckedIndex(1);
|
|
161
|
+
const item1 = el.listItems[1];
|
|
162
|
+
expect(item1.getAttribute('aria-current')).to.equal('true');
|
|
163
|
+
el.setCheckedIndex(0);
|
|
164
|
+
expect(item1.getAttribute('aria-current')).to.equal('false');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('set checked state sets aria-selected attribute with role="option"', async () => {
|
|
168
|
+
const el = await fixture(html`
|
|
169
|
+
<${tag}>
|
|
170
|
+
${item(1, 'option')}
|
|
171
|
+
${item(2, 'option')}
|
|
172
|
+
</${tag}>
|
|
173
|
+
`);
|
|
174
|
+
const item1 = el.listItems[1];
|
|
175
|
+
expect(item1.getAttribute('aria-selected')).to.equal('false');
|
|
176
|
+
|
|
177
|
+
el.setCheckedIndex(1);
|
|
178
|
+
expect(item1.getAttribute('aria-selected')).to.equal('true');
|
|
179
|
+
|
|
180
|
+
el.setCheckedIndex(0);
|
|
181
|
+
expect(item1.getAttribute('aria-selected')).to.equal('false');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('set checked state sets aria-selected attribute with role="treeitem"', async () => {
|
|
185
|
+
const el = await fixture(html`
|
|
186
|
+
<${tag}>
|
|
187
|
+
${item(1, 'treeitem')}
|
|
188
|
+
${item(2, 'treeitem')}
|
|
189
|
+
</${tag}>
|
|
190
|
+
`);
|
|
191
|
+
el.setCheckedIndex(1);
|
|
192
|
+
// @ts-ignore - test element with InteractiveListMixin properties
|
|
193
|
+
const item1 = el.listItems[1];
|
|
194
|
+
expect(item1.getAttribute('aria-selected')).to.equal('true');
|
|
195
|
+
el.setCheckedIndex(0);
|
|
196
|
+
expect(item1.getAttribute('aria-selected')).to.equal('false');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('set checked state sets aria-current attribute with role="menuitem"', async () => {
|
|
200
|
+
const el = await fixture(html`
|
|
201
|
+
<${tag}>
|
|
202
|
+
${item(1, 'menuitem')}
|
|
203
|
+
${item(2, 'menuitem')}
|
|
204
|
+
</${tag}>
|
|
205
|
+
`);
|
|
206
|
+
el.setCheckedIndex(1);
|
|
207
|
+
const item1 = el.listItems[1];
|
|
208
|
+
expect(item1.getAttribute('aria-current')).to.equal('true');
|
|
209
|
+
el.setCheckedIndex(0);
|
|
210
|
+
expect(item1.getAttribute('aria-current')).to.equal('false');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('set checked state sets aria-checked attribute with role="menuitemradio"', async () => {
|
|
214
|
+
const el = await fixture(html`
|
|
215
|
+
<${tag}>
|
|
216
|
+
${item(1, 'menuitemradio')}
|
|
217
|
+
${item(2, 'menuitemradio')}
|
|
218
|
+
</${tag}>
|
|
219
|
+
`);
|
|
220
|
+
el.setCheckedIndex(1);
|
|
221
|
+
const item1 = el.listItems[1];
|
|
222
|
+
expect(item1.getAttribute('aria-checked')).to.equal('true');
|
|
223
|
+
el.setCheckedIndex(0);
|
|
224
|
+
expect(item1.getAttribute('aria-checked')).to.equal('false');
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('set checked state sets aria-checked attribute with role="menuitemcheckbox"', async () => {
|
|
228
|
+
const el = await fixture(html`
|
|
229
|
+
<${tag}>
|
|
230
|
+
${item(1, 'menuitemcheckbox')}
|
|
231
|
+
${item(2, 'menuitemcheckbox')}
|
|
232
|
+
</${tag}>
|
|
233
|
+
`);
|
|
234
|
+
el.setCheckedIndex(1);
|
|
235
|
+
const item1 = el.listItems[1];
|
|
236
|
+
expect(item1.getAttribute('aria-checked')).to.equal('true');
|
|
237
|
+
el.setCheckedIndex(0);
|
|
238
|
+
// As this is multiple choice it doesn't unset the checked state
|
|
239
|
+
expect(item1.getAttribute('aria-checked')).to.equal('true');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('can set checked state to "page" for role="listitem"', async () => {
|
|
243
|
+
const el = await fixture(html`
|
|
244
|
+
<${tag}>
|
|
245
|
+
<a role="listitem" id="item1" href="#">Item 1</a>
|
|
246
|
+
<a role="listitem" id="item2" href="#">Item 2</a>
|
|
247
|
+
</${tag}>
|
|
248
|
+
`);
|
|
249
|
+
el.setCheckedIndex(1);
|
|
250
|
+
const item1 = el.listItems[1];
|
|
251
|
+
expect(item1.getAttribute('aria-current')).to.equal('page');
|
|
252
|
+
el.setCheckedIndex(0);
|
|
253
|
+
expect(item1.getAttribute('aria-current')).to.equal('false');
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('can set checked state to "mixed" for role="menuitemcheckbox"', async () => {
|
|
257
|
+
const el = await fixture(html`
|
|
258
|
+
<${tag}>
|
|
259
|
+
${item(1, 'menuitemcheckbox')}
|
|
260
|
+
${item(2, 'menuitemcheckbox')}
|
|
261
|
+
</${tag}>
|
|
262
|
+
`);
|
|
263
|
+
const item1 = el.listItems[1];
|
|
264
|
+
// @ts-ignore - not yet supported property
|
|
265
|
+
item1.mixedState = true;
|
|
266
|
+
el.setCheckedIndex(1);
|
|
267
|
+
expect(item1.getAttribute('aria-checked')).to.equal('mixed');
|
|
268
|
+
el.setCheckedIndex(0);
|
|
269
|
+
expect(item1.getAttribute('aria-checked')).to.equal('mixed');
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('does not allow to set checkedIndex or activeIndex to be out of bound', async () => {
|
|
273
|
+
const el = await fixture(html`
|
|
274
|
+
<${tag} has-no-default-selected autocomplete="list">
|
|
275
|
+
<div role="menuitem" id="item1">Item 1</div>
|
|
276
|
+
</${tag}>
|
|
277
|
+
`);
|
|
278
|
+
expect(() => {
|
|
279
|
+
el.checkedIndex = -1;
|
|
280
|
+
el.checkedIndex = 1;
|
|
281
|
+
}).to.not.throw();
|
|
282
|
+
expect(el.checkedIndex).to.equal(-1);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('unsets checked on other options when option becomes checked', async () => {
|
|
286
|
+
const el = await fixture(html`
|
|
287
|
+
<${tag}>
|
|
288
|
+
<div role="menuitem" id="item1">Item 1</div>
|
|
289
|
+
<div role="menuitem" id="item2">Item 2</div>
|
|
290
|
+
</${tag}>
|
|
291
|
+
`);
|
|
292
|
+
const { listItems } = el;
|
|
293
|
+
el.setCheckedIndex(0);
|
|
294
|
+
expect(listItems[0].hasAttribute('checked')).to.be.true;
|
|
295
|
+
expect(listItems[1].hasAttribute('checked')).to.be.false;
|
|
296
|
+
el.setCheckedIndex(1);
|
|
297
|
+
expect(listItems[0].hasAttribute('checked')).to.be.false;
|
|
298
|
+
expect(listItems[1].hasAttribute('checked')).to.be.true;
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
125
302
|
// Copy tests from ListboxMixin. Later, make this mixin a fundament of the ListboxMixin
|
|
126
303
|
|
|
127
304
|
// orientation
|
|
@@ -138,5 +315,328 @@ export function runInteractiveListMixinSuite(customConfig) {
|
|
|
138
315
|
it('can be extended to [role=toolbar]', async () => {});
|
|
139
316
|
it('can be extended to [role=tree]', async () => {});
|
|
140
317
|
});
|
|
318
|
+
|
|
319
|
+
describe('Overflow handling', () => {
|
|
320
|
+
/** @type {import('sinon').SinonFakeTimers | null} */
|
|
321
|
+
let clock = null;
|
|
322
|
+
|
|
323
|
+
/** @param {HTMLElement} el */
|
|
324
|
+
const isMoreButtonShown = el => {
|
|
325
|
+
const wrapper = /** @type {HTMLElement | null} */ (
|
|
326
|
+
el.querySelector('[data-more-button-wrapper]')
|
|
327
|
+
);
|
|
328
|
+
return wrapper?.style.display !== 'none';
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/** @param {HTMLElement} el */
|
|
332
|
+
const isMoreButtonMenuShown = el => {
|
|
333
|
+
const menu = el.querySelector('[data-more-button-menu]');
|
|
334
|
+
return menu ? getComputedStyle(menu).width !== '0px' : false;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
/** @param {HTMLElement} el */
|
|
338
|
+
const getMoreButton = el =>
|
|
339
|
+
/** @type {HTMLElement | null} */ (el.querySelector('[data-more-button]'));
|
|
340
|
+
/** @param {HTMLElement} el */
|
|
341
|
+
const getMoreButtonMenu = el => el.querySelector('[data-more-button-menu]');
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* @param {HTMLElement} element
|
|
345
|
+
*/
|
|
346
|
+
function getCoordinates(element) {
|
|
347
|
+
const { x, y, height } = element.getBoundingClientRect();
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
x: Math.floor(x + window.pageXOffset),
|
|
351
|
+
y: Math.floor(y + window.pageYOffset + height / 2),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
/** @param {HTMLElement} el */
|
|
355
|
+
const clickOnMoreButton = async el => {
|
|
356
|
+
const moreButton = getMoreButton(el);
|
|
357
|
+
if (!moreButton) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const { x, y } = getCoordinates(moreButton);
|
|
361
|
+
await sendMouse({ type: 'click', position: [x, y] });
|
|
362
|
+
clock?.tick(100);
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
/** @param {HTMLElement} el */
|
|
366
|
+
const getDirectListItemsUnderMoreButtonMenu = el => [
|
|
367
|
+
...Array.from(
|
|
368
|
+
el.querySelectorAll('[data-more-button-menu] > [role="listitem"]'),
|
|
369
|
+
item => /** @type {HTMLElement} */ (item),
|
|
370
|
+
),
|
|
371
|
+
];
|
|
372
|
+
|
|
373
|
+
/** @param {HTMLElement} el */
|
|
374
|
+
const getDirectListItemsUnderMainMenu = el => [
|
|
375
|
+
...Array.from(
|
|
376
|
+
el.querySelectorAll('[slot="list"] > [role="listitem"]'),
|
|
377
|
+
item => /** @type {HTMLElement} */ (item),
|
|
378
|
+
),
|
|
379
|
+
];
|
|
380
|
+
|
|
381
|
+
/** @param {HTMLElement & { updateComplete: Promise<unknown> }} el */
|
|
382
|
+
const waitResizeEventDebounce = async el => {
|
|
383
|
+
await el.updateComplete;
|
|
384
|
+
// wait for resize event debouncer
|
|
385
|
+
clock?.tick(55);
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
beforeEach(() => {
|
|
389
|
+
clock = useFakeTimers({
|
|
390
|
+
shouldAdvanceTime: true,
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
afterEach(async () => {
|
|
394
|
+
clock?.restore();
|
|
395
|
+
await resetMouse();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('should show no More button when all items fit', async () => {
|
|
399
|
+
const el = await fixture(html`
|
|
400
|
+
<${tag} name="foo" ._activeMode="${'tabbable-disclosure'}" .itemWrap="${true}"
|
|
401
|
+
data-has-full-width-flyout orientation="horizontal" style="min-width: 170px; max-width: 170px;">
|
|
402
|
+
<div role="listitem" id="item1" style="min-width: 50px; max-width: 50px;">
|
|
403
|
+
<a href="#">Item 1</a>
|
|
404
|
+
</div>
|
|
405
|
+
<div role="listitem" id="item2" style="min-width: 50px; max-width: 50px;">
|
|
406
|
+
<a href="#">Item 2</a>
|
|
407
|
+
</div>
|
|
408
|
+
<div role="listitem" id="item3" style="min-width: 50px; max-width: 50px;">
|
|
409
|
+
<a href="#">Item 3</a>
|
|
410
|
+
</div>
|
|
411
|
+
<div slot="more-button" style="min-width: 50px; max-width: 50px;">
|
|
412
|
+
<button>More</button>
|
|
413
|
+
</div>
|
|
414
|
+
</${tag}>
|
|
415
|
+
`);
|
|
416
|
+
await waitResizeEventDebounce(el);
|
|
417
|
+
// 3 items is 150px. They fit into 170px parent
|
|
418
|
+
expect(isMoreButtonShown(el)).to.equal(false);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('should show More button when not all items fit', async () => {
|
|
422
|
+
const el = await fixture(html`
|
|
423
|
+
<${tag}
|
|
424
|
+
.itemWrap="${true}"
|
|
425
|
+
?data-has-full-width-flyout="${l1Config.hasFullWidthFlyout}"
|
|
426
|
+
.config="${l1Config.openableConfig}"
|
|
427
|
+
.bar="${l1Config.isBar}"
|
|
428
|
+
._activeMode="${'tabbable-disclosure'}"
|
|
429
|
+
style="min-width: 170px; max-width: 170px;"
|
|
430
|
+
>
|
|
431
|
+
<div role="listitem" id="item1" style="min-width: 50px; max-width: 50px;">
|
|
432
|
+
<a href="#">Item 1</a>
|
|
433
|
+
</div>
|
|
434
|
+
<div role="listitem" id="item2" style="min-width: 50px; max-width: 50px;">
|
|
435
|
+
<a href="#">Item 2</a>
|
|
436
|
+
</div>
|
|
437
|
+
<div role="listitem" id="item3" style="min-width: 50px; max-width: 50px;">
|
|
438
|
+
<a href="#">Item 3</a>
|
|
439
|
+
</div>
|
|
440
|
+
<div role="listitem" id="item4" style="min-width: 50px; max-width: 50px;">
|
|
441
|
+
<a href="#">Item 4</a>
|
|
442
|
+
</div>
|
|
443
|
+
<div slot="more-button" style="min-width: 50px; max-width: 50px;">
|
|
444
|
+
<button>More</button>
|
|
445
|
+
</div>
|
|
446
|
+
</${tag}>
|
|
447
|
+
<style>
|
|
448
|
+
[orientation='horizontal'] ::slotted([slot='list']) {
|
|
449
|
+
display: flex;
|
|
450
|
+
}
|
|
451
|
+
</style>
|
|
452
|
+
`);
|
|
453
|
+
|
|
454
|
+
await waitResizeEventDebounce(el);
|
|
455
|
+
// 4 items is 200px. They don't fit into 170px parent. So More button should be shown
|
|
456
|
+
expect(isMoreButtonShown(el)).to.equal(true);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('should show 2 items when clicking on `More` button', async () => {
|
|
460
|
+
const el = await fixture(html`
|
|
461
|
+
<${tag}
|
|
462
|
+
.itemWrap="${true}"
|
|
463
|
+
?data-has-full-width-flyout="${l1Config.hasFullWidthFlyout}"
|
|
464
|
+
.config="${l1Config.openableConfig}"
|
|
465
|
+
.bar="${l1Config.isBar}"
|
|
466
|
+
._activeMode="${'tabbable-disclosure'}"
|
|
467
|
+
style="min-width: 170px; max-width: 170px;"
|
|
468
|
+
>
|
|
469
|
+
<div role="listitem" id="item1" style="min-width: 50px; max-width: 50px;">
|
|
470
|
+
<a href="#">Item 1</a>
|
|
471
|
+
</div>
|
|
472
|
+
<div role="listitem" id="item2" style="min-width: 50px; max-width: 50px;">
|
|
473
|
+
<a href="#">Item 2</a>
|
|
474
|
+
</div>
|
|
475
|
+
<div role="listitem" id="item3" style="min-width: 50px; max-width: 50px;">
|
|
476
|
+
<a href="#">Item 3</a>
|
|
477
|
+
</div>
|
|
478
|
+
<div role="listitem" id="item4" style="min-width: 50px; max-width: 50px;">
|
|
479
|
+
<a href="#">Item 4</a>
|
|
480
|
+
</div>
|
|
481
|
+
<div slot="more-button" style="min-width: 50px; max-width: 50px;">
|
|
482
|
+
<button>More</button>
|
|
483
|
+
</div>
|
|
484
|
+
</${tag}>
|
|
485
|
+
`);
|
|
486
|
+
|
|
487
|
+
await waitResizeEventDebounce(el);
|
|
488
|
+
await clickOnMoreButton(el);
|
|
489
|
+
|
|
490
|
+
expect(isMoreButtonMenuShown(el)).to.equal(true);
|
|
491
|
+
const labels = getDirectListItemsUnderMoreButtonMenu(el).map(
|
|
492
|
+
item => item.textContent?.trim() ?? '',
|
|
493
|
+
);
|
|
494
|
+
expect(labels).to.deep.equal(['Item 3', 'Item 4']);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it('should focus more button menu container when clicking on `More` button', async () => {
|
|
498
|
+
const el = await fixture(html`
|
|
499
|
+
<${tag}
|
|
500
|
+
.itemWrap="${true}"
|
|
501
|
+
?data-has-full-width-flyout="${l1Config.hasFullWidthFlyout}"
|
|
502
|
+
.config="${l1Config.openableConfig}"
|
|
503
|
+
.bar="${l1Config.isBar}"
|
|
504
|
+
._activeMode="${'tabbable-disclosure'}"
|
|
505
|
+
style="min-width: 170px; max-width: 170px;"
|
|
506
|
+
>
|
|
507
|
+
<div role="listitem" id="item1" style="min-width: 50px; max-width: 50px;">
|
|
508
|
+
<a href="#">Item 1</a>
|
|
509
|
+
</div>
|
|
510
|
+
<div role="listitem" id="item2" style="min-width: 50px; max-width: 50px;">
|
|
511
|
+
<a href="#">Item 2</a>
|
|
512
|
+
</div>
|
|
513
|
+
<div role="listitem" id="item3" style="min-width: 50px; max-width: 50px;">
|
|
514
|
+
<a href="#">Item 3</a>
|
|
515
|
+
</div>
|
|
516
|
+
<div role="listitem" id="item4" style="min-width: 50px; max-width: 50px;">
|
|
517
|
+
<a href="#">Item 4</a>
|
|
518
|
+
</div>
|
|
519
|
+
<div slot="more-button" style="min-width: 50px; max-width: 50px;">
|
|
520
|
+
<button>More</button>
|
|
521
|
+
</div>
|
|
522
|
+
</${tag}>
|
|
523
|
+
`);
|
|
524
|
+
|
|
525
|
+
await waitResizeEventDebounce(el);
|
|
526
|
+
await clickOnMoreButton(el);
|
|
527
|
+
|
|
528
|
+
expect(isMoreButtonMenuShown(el)).to.equal(true);
|
|
529
|
+
expect(getDeepActiveElement() === getMoreButtonMenu(el)).to.equal(true);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('should open and then close More button menu when clicking 2 times on the More button', async () => {
|
|
533
|
+
const el = await fixture(html`
|
|
534
|
+
<${tag}
|
|
535
|
+
.itemWrap="${true}"
|
|
536
|
+
?data-has-full-width-flyout="${l1Config.hasFullWidthFlyout}"
|
|
537
|
+
.config="${l1Config.openableConfig}"
|
|
538
|
+
.bar="${l1Config.isBar}"
|
|
539
|
+
._activeMode="${'tabbable-disclosure'}"
|
|
540
|
+
style="min-width: 170px; max-width: 170px;"
|
|
541
|
+
>
|
|
542
|
+
<div role="listitem" id="item1" style="min-width: 50px; max-width: 50px;">
|
|
543
|
+
<a href="#">Item 1</a>
|
|
544
|
+
</div>
|
|
545
|
+
<div role="listitem" id="item2" style="min-width: 50px; max-width: 50px;">
|
|
546
|
+
<a href="#">Item 2</a>
|
|
547
|
+
</div>
|
|
548
|
+
<div role="listitem" id="item3" style="min-width: 50px; max-width: 50px;">
|
|
549
|
+
<a href="#">Item 3</a>
|
|
550
|
+
</div>
|
|
551
|
+
<div role="listitem" id="item4" style="min-width: 50px; max-width: 50px;">
|
|
552
|
+
<a href="#">Item 4</a>
|
|
553
|
+
</div>
|
|
554
|
+
<div slot="more-button" style="min-width: 50px; max-width: 50px;">
|
|
555
|
+
<button>More</button>
|
|
556
|
+
</div>
|
|
557
|
+
</${tag}>
|
|
558
|
+
`);
|
|
559
|
+
|
|
560
|
+
await waitResizeEventDebounce(el);
|
|
561
|
+
expect(isMoreButtonMenuShown(el)).to.equal(false);
|
|
562
|
+
await clickOnMoreButton(el);
|
|
563
|
+
expect(isMoreButtonMenuShown(el)).to.equal(true);
|
|
564
|
+
await clickOnMoreButton(el);
|
|
565
|
+
expect(isMoreButtonMenuShown(el)).to.equal(false);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('should remove `More` button when making font smaller', async () => {
|
|
569
|
+
const el = await fixture(html`
|
|
570
|
+
<${tag} name="foo" ._activeMode="${'tabbable-disclosure'}" .itemWrap="${true}"
|
|
571
|
+
data-has-full-width-flyout orientation="horizontal" style="min-width: 170px; max-width: 170px;">
|
|
572
|
+
<div role="listitem" id="item1" style="min-width: 50px; max-width: 50px;">
|
|
573
|
+
<a href="#">Item 1</a>
|
|
574
|
+
</div>
|
|
575
|
+
<div role="listitem" id="item2" style="min-width: 50px; max-width: 50px;">
|
|
576
|
+
<a href="#">Item 2</a>
|
|
577
|
+
</div>
|
|
578
|
+
<div role="listitem" id="item3" style="min-width: 50px; max-width: 50px;">
|
|
579
|
+
<a href="#">Item 3</a>
|
|
580
|
+
</div>
|
|
581
|
+
<div slot="more-button" style="min-width: 50px; max-width: 50px;">
|
|
582
|
+
<button>More</button>
|
|
583
|
+
</div>
|
|
584
|
+
</${tag}>
|
|
585
|
+
`);
|
|
586
|
+
await waitResizeEventDebounce(el);
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Make font smaller for first level items to force
|
|
590
|
+
* the menu render all 4 items and no `More` button
|
|
591
|
+
*/
|
|
592
|
+
getDirectListItemsUnderMainMenu(el).forEach(item => {
|
|
593
|
+
// eslint-disable-next-line no-param-reassign
|
|
594
|
+
item.style.fontSize = '12px';
|
|
595
|
+
});
|
|
596
|
+
el?.dispatchEvent(new CustomEvent('resize', { composed: true, bubbles: true }));
|
|
597
|
+
await waitResizeEventDebounce(el);
|
|
598
|
+
expect(isMoreButtonShown(el)).to.equal(false);
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
it('should add `More` button when making font larger', async () => {
|
|
602
|
+
const el = await fixture(html`
|
|
603
|
+
<${tag}
|
|
604
|
+
.itemWrap="${true}"
|
|
605
|
+
?data-has-full-width-flyout="${l1Config.hasFullWidthFlyout}"
|
|
606
|
+
.config="${l1Config.openableConfig}"
|
|
607
|
+
.bar="${l1Config.isBar}"
|
|
608
|
+
._activeMode="${'tabbable-disclosure'}"
|
|
609
|
+
style="min-width: 170px; max-width: 170px;"
|
|
610
|
+
>
|
|
611
|
+
<div role="listitem" id="item1" style="min-width: 50px; max-width: 50px;">
|
|
612
|
+
<a href="#">Item 1</a>
|
|
613
|
+
</div>
|
|
614
|
+
<div role="listitem" id="item2" style="min-width: 50px; max-width: 50px;">
|
|
615
|
+
<a href="#">Item 2</a>
|
|
616
|
+
</div>
|
|
617
|
+
<div role="listitem" id="item3" style="min-width: 50px; max-width: 50px;">
|
|
618
|
+
<a href="#">Item 3</a>
|
|
619
|
+
</div>
|
|
620
|
+
<div slot="more-button" style="min-width: 50px; max-width: 50px;">
|
|
621
|
+
<button>More</button>
|
|
622
|
+
</div>
|
|
623
|
+
</${tag}>
|
|
624
|
+
`);
|
|
625
|
+
await waitResizeEventDebounce(el);
|
|
626
|
+
|
|
627
|
+
expect(getDirectListItemsUnderMainMenu(el).length).to.equal(3);
|
|
628
|
+
/**
|
|
629
|
+
* Make font larger for first level items to force
|
|
630
|
+
* the More button to appear
|
|
631
|
+
*/
|
|
632
|
+
getDirectListItemsUnderMainMenu(el).forEach(item => {
|
|
633
|
+
// eslint-disable-next-line no-param-reassign
|
|
634
|
+
item.style.fontSize = '40px';
|
|
635
|
+
});
|
|
636
|
+
el?.dispatchEvent(new CustomEvent('resize', { composed: true, bubbles: true }));
|
|
637
|
+
await waitResizeEventDebounce(el);
|
|
638
|
+
expect(isMoreButtonShown(el)).to.equal(true);
|
|
639
|
+
});
|
|
640
|
+
});
|
|
141
641
|
});
|
|
142
642
|
}
|