@gjsify/adwaita-web 0.36.0 → 0.37.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 (146) hide show
  1. package/dist/adwaita-web.css +433 -33
  2. package/dist/adwaita-web.css.map +1 -1
  3. package/lib/types/adw-shortcut-label.spec.d.ts +2 -0
  4. package/lib/types/breakpoints.d.ts +10 -12
  5. package/lib/types/elements/adw-about-dialog.d.ts +19 -36
  6. package/lib/types/elements/adw-action-row.d.ts +9 -12
  7. package/lib/types/elements/adw-alert-dialog.d.ts +0 -12
  8. package/lib/types/elements/adw-avatar.d.ts +1 -0
  9. package/lib/types/elements/adw-bottom-sheet.d.ts +7 -12
  10. package/lib/types/elements/adw-button-content.d.ts +3 -4
  11. package/lib/types/elements/adw-button-row.d.ts +1 -1
  12. package/lib/types/elements/adw-carousel.d.ts +28 -49
  13. package/lib/types/elements/adw-checks.d.ts +2 -8
  14. package/lib/types/elements/adw-combo-row.d.ts +6 -6
  15. package/lib/types/elements/adw-data-grid.d.ts +5 -10
  16. package/lib/types/elements/adw-drop-down.d.ts +3 -11
  17. package/lib/types/elements/adw-entry-row.d.ts +20 -23
  18. package/lib/types/elements/adw-icon.d.ts +2 -4
  19. package/lib/types/elements/adw-inline-view-switcher.d.ts +18 -31
  20. package/lib/types/elements/adw-navigation-split-view.d.ts +7 -12
  21. package/lib/types/elements/adw-navigation-view.d.ts +2 -10
  22. package/lib/types/elements/adw-overlay-split-view.d.ts +28 -52
  23. package/lib/types/elements/adw-password-entry-row.d.ts +4 -5
  24. package/lib/types/elements/adw-popover.d.ts +2 -7
  25. package/lib/types/elements/adw-preferences-dialog.d.ts +21 -34
  26. package/lib/types/elements/adw-preferences-group.d.ts +12 -14
  27. package/lib/types/elements/adw-progress-bar.d.ts +3 -4
  28. package/lib/types/elements/adw-shortcut-label.d.ts +20 -0
  29. package/lib/types/elements/adw-sidebar.d.ts +5 -10
  30. package/lib/types/elements/adw-spinner.d.ts +3 -5
  31. package/lib/types/elements/adw-split-button.d.ts +6 -16
  32. package/lib/types/elements/adw-switch-row.d.ts +3 -3
  33. package/lib/types/elements/adw-tab-view.d.ts +16 -26
  34. package/lib/types/elements/adw-toast-overlay.d.ts +8 -13
  35. package/lib/types/elements/adw-view-stack.d.ts +7 -17
  36. package/lib/types/elements/adw-view-switcher-bar.d.ts +4 -8
  37. package/lib/types/elements/adw-view-switcher.d.ts +18 -38
  38. package/lib/types/elements/adw-wrap-box.d.ts +7 -17
  39. package/lib/types/elements/view-switcher-dom.d.ts +10 -13
  40. package/lib/types/index.d.ts +1 -0
  41. package/lib/types/source-view/hex.d.ts +7 -13
  42. package/lib/types/style-classes.spec.d.ts +11 -0
  43. package/lib/types/styles.generated.d.ts +1 -1
  44. package/package.json +6 -6
  45. package/scss/_about_dialog.scss +3 -2
  46. package/scss/_action_row.scss +33 -0
  47. package/scss/_avatar.scss +12 -0
  48. package/scss/_button.scss +44 -22
  49. package/scss/_card.scss +11 -8
  50. package/scss/_checks.scss +2 -4
  51. package/scss/_dialog.scss +3 -2
  52. package/scss/_headerbar.scss +23 -0
  53. package/scss/_popover.scss +8 -4
  54. package/scss/_preferences.scss +7 -7
  55. package/scss/_progress_bar.scss +9 -6
  56. package/scss/_reset.scss +1 -1
  57. package/scss/_shortcut_label.scss +80 -0
  58. package/scss/_sidebar.scss +57 -0
  59. package/scss/_status_page.scss +20 -0
  60. package/scss/_style_classes.scss +407 -0
  61. package/scss/_switch.scss +4 -3
  62. package/scss/_variables.scss +25 -0
  63. package/scss/adwaita-skin.scss +12 -0
  64. package/src/adw-about-dialog.spec.ts +34 -49
  65. package/src/adw-action-rows.spec.ts +25 -33
  66. package/src/adw-avatar.spec.ts +51 -20
  67. package/src/adw-banner.spec.ts +5 -8
  68. package/src/adw-button-content.spec.ts +9 -12
  69. package/src/adw-button-row.spec.ts +2 -4
  70. package/src/adw-carousel.spec.ts +40 -53
  71. package/src/adw-checks.spec.ts +36 -47
  72. package/src/adw-drop-down.spec.ts +25 -32
  73. package/src/adw-header-bar.spec.ts +5 -7
  74. package/src/adw-icon.spec.ts +23 -32
  75. package/src/adw-progress-bar.spec.ts +10 -12
  76. package/src/adw-row-state.spec.ts +9 -10
  77. package/src/adw-shortcut-label.spec.ts +112 -0
  78. package/src/adw-sidebar.spec.ts +17 -24
  79. package/src/adw-switch.spec.ts +11 -17
  80. package/src/adw-tab-view.spec.ts +7 -10
  81. package/src/adw-toast-overlay.spec.ts +7 -11
  82. package/src/adw-view-switcher-bar.spec.ts +10 -14
  83. package/src/adw-wrap-box.spec.ts +25 -36
  84. package/src/bottom-sheet.spec.ts +30 -36
  85. package/src/breakpoints.ts +15 -18
  86. package/src/chrome.spec.ts +13 -21
  87. package/src/elements/adw-about-dialog.ts +71 -114
  88. package/src/elements/adw-action-row.ts +22 -37
  89. package/src/elements/adw-alert-dialog.ts +25 -50
  90. package/src/elements/adw-avatar.ts +40 -15
  91. package/src/elements/adw-banner.ts +14 -18
  92. package/src/elements/adw-bottom-sheet.ts +31 -52
  93. package/src/elements/adw-button-content.ts +30 -37
  94. package/src/elements/adw-button-row.ts +14 -24
  95. package/src/elements/adw-carousel.ts +87 -148
  96. package/src/elements/adw-checks.ts +43 -56
  97. package/src/elements/adw-clamp.ts +8 -9
  98. package/src/elements/adw-combo-row.ts +17 -25
  99. package/src/elements/adw-data-grid.ts +26 -38
  100. package/src/elements/adw-dialog.ts +25 -56
  101. package/src/elements/adw-drop-down.ts +31 -51
  102. package/src/elements/adw-entry-row.ts +39 -46
  103. package/src/elements/adw-expander-row.ts +30 -53
  104. package/src/elements/adw-icon.ts +30 -53
  105. package/src/elements/adw-inline-view-switcher.ts +38 -67
  106. package/src/elements/adw-menu-button.ts +28 -58
  107. package/src/elements/adw-navigation-split-view.ts +35 -47
  108. package/src/elements/adw-navigation-view.ts +12 -28
  109. package/src/elements/adw-overlay-split-view.ts +100 -175
  110. package/src/elements/adw-password-entry-row.ts +18 -28
  111. package/src/elements/adw-popover.ts +28 -44
  112. package/src/elements/adw-preferences-dialog.ts +41 -66
  113. package/src/elements/adw-preferences-group.ts +33 -46
  114. package/src/elements/adw-progress-bar.ts +27 -47
  115. package/src/elements/adw-shortcut-label.ts +138 -0
  116. package/src/elements/adw-sidebar.ts +31 -39
  117. package/src/elements/adw-spin-row.ts +7 -14
  118. package/src/elements/adw-spinner.ts +31 -43
  119. package/src/elements/adw-split-button.ts +45 -75
  120. package/src/elements/adw-switch-row.ts +29 -46
  121. package/src/elements/adw-switch.ts +28 -47
  122. package/src/elements/adw-tab-view.ts +62 -103
  123. package/src/elements/adw-toast-overlay.ts +28 -35
  124. package/src/elements/adw-toggle-group.ts +9 -16
  125. package/src/elements/adw-view-stack.ts +32 -51
  126. package/src/elements/adw-view-switcher-bar.ts +26 -50
  127. package/src/elements/adw-view-switcher.ts +60 -110
  128. package/src/elements/adw-window-title.ts +4 -4
  129. package/src/elements/adw-wrap-box.ts +33 -59
  130. package/src/elements/view-switcher-dom.ts +15 -22
  131. package/src/entry-rows.spec.ts +18 -24
  132. package/src/index.ts +13 -15
  133. package/src/navigation-view.spec.ts +7 -10
  134. package/src/preferences-search.ts +11 -15
  135. package/src/preferences.spec.ts +13 -15
  136. package/src/source-view/adw-source-view.ts +6 -19
  137. package/src/source-view/hex.ts +7 -13
  138. package/src/split-button.spec.ts +32 -44
  139. package/src/split-views.spec.ts +83 -149
  140. package/src/style-classes.spec.ts +181 -0
  141. package/src/style-isolation.spec.ts +3 -6
  142. package/src/styles.generated.ts +1 -1
  143. package/src/tab-view.spec.ts +26 -35
  144. package/src/test.browser.mts +4 -0
  145. package/src/view-stack.spec.ts +23 -34
  146. package/src/view-switcher.spec.ts +56 -84
@@ -10,16 +10,12 @@
10
10
  // CodeMirror. Importing the subpath registers <adw-source-view> and self-injects
11
11
  // its stylesheet.
12
12
  //
13
- // Attributes (kebab-case) mirror the properties (camelCase):
14
- // code, editable, readonly, selectable, line-numbers, line-number-start,
15
- // hex-addresses, language, copyable, copy-button-icon, copy-button-tooltip,
16
- // fill-height.
17
- // Events:
18
- // `code-changed` (CustomEvent, bubbles, detail = { code }) — on USER edits
19
- // only (programmatic `.code =` is suppressed so an EditorView two-way bind
20
- // does not loop). The consuming SourceViewWidget maps it to `changed`.
21
- // `copy` (CustomEvent, bubbles, detail = { code }) — when the copy button is
22
- // pressed; the clipboard receives the whitespace-stripped text in hex mode.
13
+ // Attributes are the kebab-case spelling of the properties. Events:
14
+ // `code-changed` (CustomEvent, bubbles, detail `{ code }`) — USER edits only;
15
+ // programmatic `.code =` is suppressed so an EditorView two-way bind does not loop.
16
+ // The consuming SourceViewWidget maps it to `changed`.
17
+ // `copy` (CustomEvent, bubbles, detail `{ code }`) — the clipboard receives the
18
+ // whitespace-stripped text in hex mode.
23
19
  // Reference: packages/app-gnome/src/widgets/source-view.ts (native parity target)
24
20
  // Copyright (c) 2025 gjsify contributors. MIT License.
25
21
 
@@ -53,7 +49,6 @@ function boolAttr(value: string | null): boolean {
53
49
  }
54
50
 
55
51
  export class AdwSourceView extends HTMLElement {
56
- // ─── instance state ──────────────────────────────────────────────────────
57
52
  private _view?: EditorView;
58
53
  private _editorHost!: HTMLDivElement;
59
54
  private _copyButton!: HTMLButtonElement;
@@ -95,8 +90,6 @@ export class AdwSourceView extends HTMLElement {
95
90
  ];
96
91
  }
97
92
 
98
- // ─── SourceViewWidget surface ────────────────────────────────────────────
99
-
100
93
  /** The document text. Programmatic assignment does NOT emit `code-changed`. */
101
94
  get code(): string {
102
95
  return this._view ? this._view.state.doc.toString() : this._code;
@@ -232,8 +225,6 @@ export class AdwSourceView extends HTMLElement {
232
225
  return this._view ?? null;
233
226
  }
234
227
 
235
- // ─── lifecycle ───────────────────────────────────────────────────────────
236
-
237
228
  connectedCallback(): void {
238
229
  if (this._initialized) return;
239
230
  this._initialized = true;
@@ -315,8 +306,6 @@ export class AdwSourceView extends HTMLElement {
315
306
  }
316
307
  }
317
308
 
318
- // ─── CodeMirror wiring ───────────────────────────────────────────────────
319
-
320
309
  private _baseExtensions(): Extension {
321
310
  return [
322
311
  this._editableComp.of(this._editableExtension()),
@@ -401,8 +390,6 @@ export class AdwSourceView extends HTMLElement {
401
390
  this._view?.dispatch({ effects: compartment.reconfigure(extension) });
402
391
  }
403
392
 
404
- // ─── copy button ─────────────────────────────────────────────────────────
405
-
406
393
  private _onCopyButtonClick(): void {
407
394
  const text = this._hexAddresses ? stripWhitespace(this.code) : this.code;
408
395
  const clipboard = (globalThis.navigator as Navigator | undefined)?.clipboard;
@@ -8,13 +8,10 @@
8
8
  export const HEX_ADDRESS_STRIDE = 16;
9
9
 
10
10
  /**
11
- * Format a 1-based CodeMirror line number as a 4-digit uppercase hex address,
12
- * the way the native GtkSourceView hex gutter renders monitor/hexdump rows:
13
- * each row advances the address by `stride` bytes from `start`.
14
- *
15
- * @param lineNo 1-based line number (CodeMirror's `formatNumber` contract).
16
- * @param start base address of the first row (e.g. 0x0600 for a 6502 program).
17
- * @param stride bytes per row (default 16 — one hexdump row).
11
+ * Format a 1-based CodeMirror line number as a 4-digit uppercase hex address, the way the
12
+ * native GtkSourceView hex gutter renders monitor/hexdump rows: each row advances the
13
+ * address by `stride` bytes from `start` (the base address of the first row, e.g. 0x0600
14
+ * for a 6502 program). `lineNo` is 1-based, per CodeMirror's `formatNumber` contract.
18
15
  */
19
16
  export function formatHexAddress(lineNo: number, start: number, stride: number = HEX_ADDRESS_STRIDE): string {
20
17
  const address = start + (lineNo - 1) * stride;
@@ -22,12 +19,9 @@ export function formatHexAddress(lineNo: number, start: number, stride: number =
22
19
  }
23
20
 
24
21
  /**
25
- * Format a 1-based CodeMirror line number for the normal (decimal) gutter,
26
- * offset by `start` so a pane can begin numbering at an arbitrary value —
27
- * the web twin of the native renderer's `line + startValue`.
28
- *
29
- * @param lineNo 1-based line number.
30
- * @param start value shown on the first line (default 1 → identity).
22
+ * Format a 1-based CodeMirror line number for the normal (decimal) gutter, offset so
23
+ * `start` is the value shown on the first line — the web twin of the native renderer's
24
+ * `line + startValue`.
31
25
  */
32
26
  export function formatLineNumber(lineNo: number, start: number = 1): string {
33
27
  return String(lineNo - 1 + start);
@@ -1,14 +1,11 @@
1
1
  // DOM-level conformance tests for <adw-split-button>, driven by the SAME vectors
2
2
  // the NativeScript renderer asserts against (`@gjsify/adwaita-core/conformance`).
3
3
  //
4
- // The two renderers used to carry independent split-button logic, and the copies
5
- // had drifted apart AND away from libadwaita: this element rendered the label AND
6
- // the icon at once where GTK clears whichever slot it is not using, could not
7
- // clear the label at all (so the storybook's icon-only mode showed the icon next
8
- // to a stale "Save"), trimmed `label=" "` away, hardcoded "Menu" as the
9
- // dropdown's accessible name and left it EMPTY for `dropdown-tooltip=""`, and
10
- // opened a fully styled empty popover when there was no menu at all. Nothing
11
- // failed, because nothing compared them. This suite is that comparison.
4
+ // The rules independent copies of the split-button logic drifted from: GTK clears
5
+ // whichever of label/icon it is not using, so both never render at once and the label
6
+ // must be clearable; a whitespace-only `label` is kept, not trimmed away; the dropdown's
7
+ // accessible name comes from `dropdown-tooltip`, with `DEFAULT_DROPDOWN_TOOLTIP` as the
8
+ // fallback and no hardcoded "Menu"; and no menu means no popover at all.
12
9
  import { describe, expect, it } from '@gjsify/unit';
13
10
 
14
11
  import { DEFAULT_DROPDOWN_TOOLTIP } from '@gjsify/adwaita-core';
@@ -36,9 +33,9 @@ const NOTIFY_PROPERTIES: readonly SplitButtonProperty[] = [
36
33
  ];
37
34
 
38
35
  /**
39
- * Mount a split button, setting attributes through `setAttribute` rather than
40
- * parsed HTML — several vectors hinge on exact whitespace (`label=" "`), which
41
- * an attribute value in markup is not a reliable carrier for.
36
+ * Mount a split button, setting attributes through `setAttribute` rather than parsed
37
+ * HTML: several vectors hinge on exact whitespace (`label=" "`), which markup is not a
38
+ * reliable carrier for.
42
39
  */
43
40
  function mount(attributes: Record<string, string> = {}): { el: AdwSplitButton; host: HTMLElement } {
44
41
  const host = document.createElement('div');
@@ -86,8 +83,8 @@ export const AdwSplitButtonTest = async () => {
86
83
  }
87
84
 
88
85
  for (const step of vector.steps) {
89
- // A DOM element has no `child` slot; clearing the content is
90
- // the one child step it can express (both attributes gone).
86
+ // A DOM element has no `child` slot, so clearing the content (both
87
+ // attributes gone) is the one child step it can express.
91
88
  if (step.op === 'child' && step.value !== null) break;
92
89
 
93
90
  seen.length = 0;
@@ -98,14 +95,13 @@ export const AdwSplitButtonTest = async () => {
98
95
  el.removeAttribute('icon-name');
99
96
  }
100
97
 
101
- // The element re-emits libadwaita's notify sequence verbatim,
102
- // so the ORDER — cleared slots first, then the set one — is
103
- // observable from the DOM.
98
+ // The element re-emits libadwaita's notify sequence verbatim, so the
99
+ // ORDER — cleared slots first, then the set one — is DOM-observable.
104
100
  expect(seen).toStrictEqual([...step.notified]);
105
101
  expect(el.label).toBe(step.label);
106
102
  expect(el.iconName).toBe(step.iconName);
107
- // An empty label is SET but paints nothing, so it reads as an
108
- // empty action half — the `label[0]` distinction, on screen.
103
+ // An empty label is SET but paints nothing — the `label[0]`
104
+ // distinction, on screen.
109
105
  const painted = step.mode === 'icon' ? 'icon' : (step.label ?? '').length > 0 ? 'label' : 'empty';
110
106
  expect(renderedMode(el)).toBe(painted);
111
107
  expect(rootStyleClasses(el)).toStrictEqual([...step.styleClasses]);
@@ -219,11 +215,9 @@ export const AdwSplitButtonTest = async () => {
219
215
  }
220
216
 
221
217
  await it('`none` draws the DOWN caret, not the open-menu hamburger (_buttons.scss:621-623)', () => {
222
- // The element used to key its mask table by DIRECTION and put
223
- // `open-menu` on `none`, which is the PLAIN menubutton glyph
224
- // (:454-456). Inside a `splitbutton` that rule is overridden, so the
225
- // symbolic name now comes from `splitButtonArrowIcon` and only the
226
- // glyph→mask mapping is still decided here.
218
+ // `open-menu` on `none` is the PLAIN menubutton glyph, and inside a
219
+ // `splitbutton` that rule is overridden — so the symbolic name comes from
220
+ // `splitButtonArrowIcon` and only the glyph→mask mapping is decided here.
227
221
  expect(arrowClass('none')).toBe(arrowClass('down'));
228
222
  expect(arrowClass('none').includes('adw-icon--open-menu')).toBe(false);
229
223
  });
@@ -287,7 +281,6 @@ export const AdwSplitButtonTest = async () => {
287
281
  (items[1] as HTMLButtonElement).click();
288
282
 
289
283
  expect(activated).toStrictEqual([{ label: 'Copy', action: 'app.copy-special', index: 1 }]);
290
- // Activating an entry dismisses the menu.
291
284
  expect(el.active).toBe(false);
292
285
  host.remove();
293
286
  });
@@ -329,11 +322,9 @@ export const AdwSplitButtonTest = async () => {
329
322
  });
330
323
  });
331
324
 
332
- // The two behaviours this element NEVER HAD. Its hand-rolled popover carried
333
- // an outside-click handler and nothing else — a menu you could open from the
334
- // keyboard and then only leave with the mouse, whose items the arrow keys
335
- // could not reach at all. Both arrive with `<adw-popover>`; these are the
336
- // cases that prove the lift was a lift and not a move.
325
+ // The two behaviours a hand-rolled popover with only an outside-click handler cannot
326
+ // give: leaving by keyboard, and reaching the items with the arrow keys. Both come
327
+ // from `<adw-popover>`, and these cases prove the lift was a lift, not a move.
337
328
  await describe('adw-split-button menu keyboard (gained with <adw-popover>)', async () => {
338
329
  await it('Escape dismisses the menu and returns focus to the dropdown half', () => {
339
330
  const { el, host } = mount({ menu: '[{"label":"Print"},{"label":"Export"}]' });
@@ -343,8 +334,8 @@ export const AdwSplitButtonTest = async () => {
343
334
  document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
344
335
 
345
336
  expect(el.active).toBe(false);
346
- // SplitButtonState stays the source of truth — a dismissal the
347
- // popover owns is fed back into it, not held only in the popover.
337
+ // SplitButtonState stays the source of truth: a dismissal the popover owns is
338
+ // fed back into it, not held only there.
348
339
  expect(el.classList.contains('checked')).toBe(false);
349
340
  expect(document.activeElement).toBe(dropdownHalf(el));
350
341
  host.remove();
@@ -366,10 +357,10 @@ export const AdwSplitButtonTest = async () => {
366
357
  const { el, host } = mount({ menu: '[{"label":"Print"},{"label":"Export"},{"label":"Share"}]' });
367
358
  dropdownHalf(el).click();
368
359
 
369
- // Queried AFTER opening on purpose: `_renderMenu` runs on every state
370
- // change and `replaceChildren()`s the rows, so nodes captured before
371
- // the click are detached and `document.activeElement` can never be one
372
- // of them. The popover ELEMENT survives — only its children are rebuilt.
360
+ // Queried AFTER opening on purpose: `_renderMenu` runs on every state change
361
+ // and `replaceChildren()`s the rows, so nodes captured before the click are
362
+ // detached and can never be `document.activeElement`. The popover ELEMENT
363
+ // survives; only its children are rebuilt.
373
364
  const items = [...el.querySelectorAll<HTMLButtonElement>('.adw-split-button-menu-item')];
374
365
  expect(items.length).toBe(3);
375
366
  // Opening focuses the first row — without that there is nothing to move from.
@@ -405,20 +396,17 @@ export const AdwSplitButtonTest = async () => {
405
396
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
406
397
  menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
407
398
 
408
- // Two entries share a label, so only the position tells them apart —
409
- // and `preventDefault()` before the synthetic click keeps a focused
410
- // <button> from ALSO activating natively, which would fire twice.
399
+ // Two entries share a label, so only position tells them apart; and
400
+ // `preventDefault()` before the synthetic click keeps a focused <button> from
401
+ // ALSO activating natively and firing twice.
411
402
  expect(seen).toStrictEqual([1]);
412
403
  host.remove();
413
404
  });
414
405
  });
415
406
 
416
- // The assertion that would have CAUGHT the original bug. The core vectors pin
417
- // the numbers against the vendored stylesheet; nothing pinned the numbers the
418
- // browser actually paints, which is how a 9px surface under a two-layer shadow
419
- // shipped here (and a 12px one under the right shadow shipped twice more).
420
- // Computed style, not the SCSS text: a rule that loses the cascade reads fine
421
- // in the source and renders wrong.
407
+ // The core vectors pin the numbers against the vendored stylesheet; only a COMPUTED
408
+ // read pins what the browser paints, which is how a 9px surface under a two-layer
409
+ // shadow shipped. A rule that loses the cascade reads fine in the SCSS source.
422
410
  await describe('adw-split-button menu surface (libadwaita popover vectors)', async () => {
423
411
  /** The `popover.menu` row of the shared table — what this element renders. */
424
412
  const menuVector = POPOVER_SURFACE_VECTORS.find((vector) => vector.variant === 'menu');