@gjsify/adwaita-web 0.47.0 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +3 -3
  2. package/dist/adwaita-web.css +62 -0
  3. package/dist/adwaita-web.css.map +1 -1
  4. package/lib/types/elements/adw-combo-row.d.ts +56 -0
  5. package/lib/types/elements/adw-spin-row.d.ts +11 -1
  6. package/lib/types/elements/adw-split-button.d.ts +43 -3
  7. package/lib/types/elements/adw-tab-view.d.ts +23 -5
  8. package/lib/types/elements/adw-view-stack.d.ts +17 -2
  9. package/lib/types/elements/gtk-drop-down.d.ts +22 -7
  10. package/lib/types/elements/gtk-menu-button.d.ts +43 -26
  11. package/lib/types/elements/gtk-popover.d.ts +14 -1
  12. package/lib/types/elements/popover-menu.d.ts +99 -0
  13. package/lib/types/index.d.ts +1 -1
  14. package/lib/types/shared-trees.spec.d.ts +2 -0
  15. package/lib/types/styles.generated.d.ts +1 -1
  16. package/package.json +6 -6
  17. package/scss/_popover.scss +99 -0
  18. package/src/adw-avatar.spec.ts +17 -0
  19. package/src/adw-row-state.spec.ts +335 -17
  20. package/src/adw-tab-view.spec.ts +8 -5
  21. package/src/elements/adw-avatar.ts +11 -3
  22. package/src/elements/adw-combo-row.ts +161 -22
  23. package/src/elements/adw-spin-row.ts +49 -28
  24. package/src/elements/adw-split-button.ts +117 -39
  25. package/src/elements/adw-tab-view.ts +42 -22
  26. package/src/elements/adw-view-stack.ts +20 -4
  27. package/src/elements/adw-view-switcher-bar.ts +6 -3
  28. package/src/elements/gtk-drop-down.ts +109 -66
  29. package/src/elements/gtk-menu-button.ts +112 -88
  30. package/src/elements/gtk-popover.ts +17 -2
  31. package/src/elements/popover-menu.ts +366 -0
  32. package/src/gtk-drop-down.spec.ts +117 -25
  33. package/src/gtk-image.spec.ts +3 -3
  34. package/src/gtk-menu-button.spec.ts +165 -11
  35. package/src/index.ts +14 -1
  36. package/src/keyboard-operable.spec.ts +170 -1
  37. package/src/shared-trees.spec.ts +151 -0
  38. package/src/split-button.spec.ts +36 -22
  39. package/src/styles.generated.ts +1 -1
  40. package/src/tab-view.spec.ts +83 -15
  41. package/src/test.browser.mts +2 -0
  42. package/src/view-stack.spec.ts +5 -3
@@ -13,10 +13,11 @@ import {
13
13
  POPOVER_SURFACE_VECTORS,
14
14
  SPLIT_BUTTON_CONTENT_VECTORS,
15
15
  SPLIT_BUTTON_DIRECTION_VECTORS,
16
- SPLIT_BUTTON_MENU_PARSE_VECTORS,
16
+ MENU_NORMALIZE_VECTORS,
17
17
  SPLIT_BUTTON_STYLE_CLASS_VECTORS,
18
18
  SPLIT_BUTTON_TOOLTIP_VECTORS,
19
19
  } from '@gjsify/adwaita-core/conformance';
20
+ import { ADW_MENU_SURFACE_WEB, flattenMenu, menuRefusals } from '@gjsify/adwaita-core';
20
21
  import type { SplitButtonProperty } from '@gjsify/adwaita-core';
21
22
  import type { AdwSplitButton } from './elements/adw-split-button.js';
22
23
  import { fallbackMask, maskOf } from './icon-registry.spec.js';
@@ -234,11 +235,24 @@ export const AdwSplitButtonTest = async () => {
234
235
  });
235
236
 
236
237
  await describe('adw-split-button menu', async () => {
237
- for (const { json, entries, rule } of SPLIT_BUTTON_MENU_PARSE_VECTORS) {
238
- await it(`parses ${JSON.stringify(json)} into ${entries.length} entries — ${rule}`, () => {
239
- const { el, host } = mount(json === null ? {} : { menu: json });
240
- expect([...el.menuItems]).toStrictEqual([...entries]);
241
- expect(el.querySelectorAll('.adw-split-button-menu-item').length).toBe(entries.length);
238
+ for (const { input, model, rule } of MENU_NORMALIZE_VECTORS) {
239
+ await it(`normalises an authored menu — ${rule}`, () => {
240
+ const { el, host } = mount({ label: 'Save' });
241
+ // What THIS surface refuses is core's answer, not a list restated here:
242
+ // a `custom` item names an application widget the browser cannot host,
243
+ // and the setter is where that is said out loud.
244
+ if (menuRefusals(model, ADW_MENU_SURFACE_WEB).length > 0) {
245
+ expect(() => {
246
+ el.menuModel = input;
247
+ }).toThrow('adwaita-web');
248
+ host.remove();
249
+ return;
250
+ }
251
+ el.menuModel = input;
252
+ expect([...el.menuModel]).toStrictEqual([...model]);
253
+ // Every node the model draws is a row: a section is inlined, a submenu
254
+ // is one row of its own.
255
+ expect(el.querySelectorAll('.adw-split-button-menu-item').length).toBe(flattenMenu(model).length);
242
256
  host.remove();
243
257
  });
244
258
  }
@@ -255,7 +269,7 @@ export const AdwSplitButtonTest = async () => {
255
269
  });
256
270
 
257
271
  await it('opens and closes on the dropdown half once there is a menu', () => {
258
- const { el, host } = mount({ menu: '[{"label":"Save as…","action":"app.save-as"}]' });
272
+ const { el, host } = mount({ 'menu-model': '[{"label":"Save as…","action":"app.save-as"}]' });
259
273
  expect(el.dropdownEnabled).toBe(true);
260
274
  expect(dropdownHalf(el).disabled).toBe(false);
261
275
 
@@ -275,25 +289,25 @@ export const AdwSplitButtonTest = async () => {
275
289
 
276
290
  await it('dispatches a duplicate label BY POSITION, with its own action', () => {
277
291
  const { el, host } = mount({
278
- menu: '[{"label":"Copy","action":"app.copy"},{"label":"Copy","action":"app.copy-special"}]',
292
+ 'menu-model': '[{"label":"Copy","action":"app.copy"},{"label":"Copy","action":"app.copy-special"}]',
279
293
  });
280
- const activated: Array<{ label: string; action?: string; index: number }> = [];
294
+ const activated: Array<{ label: string; action?: string; id: string; path: number[] }> = [];
281
295
  el.addEventListener('menu-activated', (event) => activated.push((event as CustomEvent).detail));
282
296
 
283
297
  dropdownHalf(el).click();
284
298
  const items = el.querySelectorAll('.adw-split-button-menu-item');
285
299
  (items[1] as HTMLButtonElement).click();
286
300
 
287
- expect(activated).toStrictEqual([{ label: 'Copy', action: 'app.copy-special', index: 1 }]);
301
+ expect(activated).toStrictEqual([{ label: 'Copy', action: 'app.copy-special', id: 'Copy', path: [1] }]);
288
302
  expect(el.active).toBe(false);
289
303
  host.remove();
290
304
  });
291
305
 
292
306
  await it('losing its menu closes an open one', () => {
293
- const { el, host } = mount({ menu: '[{"label":"Print"}]' });
307
+ const { el, host } = mount({ 'menu-model': '[{"label":"Print"}]' });
294
308
  dropdownHalf(el).click();
295
309
  expect(el.active).toBe(true);
296
- el.setAttribute('menu', '[]');
310
+ el.setAttribute('menu-model', '[]');
297
311
  expect(el.active).toBe(false);
298
312
  expect(el.dropdownEnabled).toBe(false);
299
313
  host.remove();
@@ -316,7 +330,7 @@ export const AdwSplitButtonTest = async () => {
316
330
  });
317
331
 
318
332
  await it('marks the root checked while the menu is open (the update_state fold)', () => {
319
- const { el, host } = mount({ menu: '[{"label":"Print"}]' });
333
+ const { el, host } = mount({ 'menu-model': '[{"label":"Print"}]' });
320
334
  expect(el.classList.contains('checked')).toBe(false);
321
335
  dropdownHalf(el).click();
322
336
  expect(el.classList.contains('checked')).toBe(true);
@@ -331,7 +345,7 @@ export const AdwSplitButtonTest = async () => {
331
345
  // from `<gtk-popover>`, and these cases prove the lift was a lift, not a move.
332
346
  await describe('adw-split-button menu keyboard (gained with <gtk-popover>)', async () => {
333
347
  await it('Escape dismisses the menu and returns focus to the dropdown half', () => {
334
- const { el, host } = mount({ menu: '[{"label":"Print"},{"label":"Export"}]' });
348
+ const { el, host } = mount({ 'menu-model': '[{"label":"Print"},{"label":"Export"}]' });
335
349
  dropdownHalf(el).click();
336
350
  expect(el.active).toBe(true);
337
351
 
@@ -346,7 +360,7 @@ export const AdwSplitButtonTest = async () => {
346
360
  });
347
361
 
348
362
  await it('Escape reaches notify::active, so the dismissal is a real state change', () => {
349
- const { el, host } = mount({ menu: '[{"label":"Print"}]' });
363
+ const { el, host } = mount({ 'menu-model': '[{"label":"Print"}]' });
350
364
  const seen: boolean[] = [];
351
365
  el.addEventListener('notify::active', (event) => seen.push((event as CustomEvent).detail.active));
352
366
 
@@ -358,7 +372,7 @@ export const AdwSplitButtonTest = async () => {
358
372
  });
359
373
 
360
374
  await it('the arrow keys walk the menu items and wrap, from the row focused on open', () => {
361
- const { el, host } = mount({ menu: '[{"label":"Print"},{"label":"Export"},{"label":"Share"}]' });
375
+ const { el, host } = mount({ 'menu-model': '[{"label":"Print"},{"label":"Export"},{"label":"Share"}]' });
362
376
  dropdownHalf(el).click();
363
377
 
364
378
  // Queried AFTER opening on purpose: `_renderMenu` runs on every state change
@@ -391,9 +405,9 @@ export const AdwSplitButtonTest = async () => {
391
405
  });
392
406
 
393
407
  await it('Enter on a focused row activates it BY POSITION, exactly once', () => {
394
- const { el, host } = mount({ menu: '[{"label":"Copy"},{"label":"Copy"}]' });
395
- const seen: number[] = [];
396
- el.addEventListener('menu-activated', (event) => seen.push((event as CustomEvent).detail.index));
408
+ const { el, host } = mount({ 'menu-model': '[{"label":"Copy"},{"label":"Copy"}]' });
409
+ const seen: number[][] = [];
410
+ el.addEventListener('menu-activated', (event) => seen.push((event as CustomEvent).detail.path));
397
411
 
398
412
  dropdownHalf(el).click();
399
413
  const menu = el.querySelector('gtk-popover') as HTMLElement;
@@ -403,7 +417,7 @@ export const AdwSplitButtonTest = async () => {
403
417
  // Two entries share a label, so only position tells them apart; and
404
418
  // `preventDefault()` before the synthetic click keeps a focused <button> from
405
419
  // ALSO activating natively and firing twice.
406
- expect(seen).toStrictEqual([1]);
420
+ expect(seen).toStrictEqual([[1]]);
407
421
  host.remove();
408
422
  });
409
423
  });
@@ -423,7 +437,7 @@ export const AdwSplitButtonTest = async () => {
423
437
  });
424
438
 
425
439
  await it(`draws a ${menuVector?.borderRadius}px surface with ${menuVector?.padding}px padding and a THREE-layer shadow`, () => {
426
- const { el, host } = mount({ menu: '[{"label":"Print"}]' });
440
+ const { el, host } = mount({ 'menu-model': '[{"label":"Print"}]' });
427
441
  dropdownHalf(el).click();
428
442
  const surface = el.querySelector('gtk-popover') as HTMLElement;
429
443
  const style = getComputedStyle(surface);
@@ -445,7 +459,7 @@ export const AdwSplitButtonTest = async () => {
445
459
  });
446
460
 
447
461
  await it(`draws menu rows at the ${itemVector?.borderRadius}px modelbutton radius`, () => {
448
- const { el, host } = mount({ menu: '[{"label":"Print"}]' });
462
+ const { el, host } = mount({ 'menu-model': '[{"label":"Print"}]' });
449
463
  dropdownHalf(el).click();
450
464
  const row = el.querySelector('.adw-split-button-menu-item') as HTMLElement;
451
465
  const style = getComputedStyle(row);