@gjsify/adwaita-web 0.31.0 → 0.32.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 (176) hide show
  1. package/dist/adwaita-web.css +786 -272
  2. package/dist/adwaita-web.css.map +1 -1
  3. package/lib/types/adw-about-dialog.spec.d.ts +1 -0
  4. package/lib/types/adw-action-rows.spec.d.ts +1 -0
  5. package/lib/types/adw-avatar.spec.d.ts +1 -0
  6. package/lib/types/adw-banner.spec.d.ts +1 -0
  7. package/lib/types/adw-button-content.spec.d.ts +1 -0
  8. package/lib/types/adw-carousel.spec.d.ts +1 -0
  9. package/lib/types/adw-checks.spec.d.ts +1 -0
  10. package/lib/types/adw-header-bar.spec.d.ts +1 -0
  11. package/lib/types/adw-icon.spec.d.ts +1 -0
  12. package/lib/types/adw-progress-bar.spec.d.ts +1 -0
  13. package/lib/types/adw-sidebar.spec.d.ts +1 -0
  14. package/lib/types/adw-switch.spec.d.ts +1 -0
  15. package/lib/types/adw-wrap-box.spec.d.ts +1 -0
  16. package/lib/types/bottom-sheet.spec.d.ts +1 -0
  17. package/lib/types/breakpoints.d.ts +22 -0
  18. package/lib/types/breakpoints.spec.d.ts +1 -0
  19. package/lib/types/chrome.spec.d.ts +1 -0
  20. package/lib/types/elements/adw-about-dialog.d.ts +47 -8
  21. package/lib/types/elements/adw-action-row.d.ts +35 -0
  22. package/lib/types/elements/adw-avatar.d.ts +11 -0
  23. package/lib/types/elements/adw-bottom-sheet.d.ts +33 -4
  24. package/lib/types/elements/adw-button-content.d.ts +12 -0
  25. package/lib/types/elements/adw-button-row.d.ts +10 -1
  26. package/lib/types/elements/adw-carousel.d.ts +111 -8
  27. package/lib/types/elements/adw-checks.d.ts +57 -0
  28. package/lib/types/elements/adw-clamp.d.ts +4 -1
  29. package/lib/types/elements/adw-data-grid.d.ts +11 -31
  30. package/lib/types/elements/adw-drop-down.d.ts +17 -20
  31. package/lib/types/elements/adw-entry-row.d.ts +93 -3
  32. package/lib/types/elements/adw-expander-row.d.ts +7 -1
  33. package/lib/types/elements/adw-header-bar.d.ts +15 -0
  34. package/lib/types/elements/adw-icon.d.ts +25 -0
  35. package/lib/types/elements/adw-inline-view-switcher.d.ts +37 -11
  36. package/lib/types/elements/adw-menu-button.d.ts +6 -6
  37. package/lib/types/elements/adw-navigation-split-view.d.ts +6 -0
  38. package/lib/types/elements/adw-navigation-view.d.ts +63 -20
  39. package/lib/types/elements/adw-overlay-split-view.d.ts +38 -0
  40. package/lib/types/elements/adw-password-entry-row.d.ts +19 -10
  41. package/lib/types/elements/adw-popover.d.ts +58 -0
  42. package/lib/types/elements/adw-preferences-dialog.d.ts +57 -2
  43. package/lib/types/elements/adw-preferences-group.d.ts +31 -0
  44. package/lib/types/elements/adw-progress-bar.d.ts +38 -0
  45. package/lib/types/elements/adw-sidebar.d.ts +37 -5
  46. package/lib/types/elements/adw-split-button.d.ts +53 -7
  47. package/lib/types/elements/adw-switch-row.d.ts +11 -2
  48. package/lib/types/elements/adw-switch.d.ts +15 -0
  49. package/lib/types/elements/adw-tab-view.d.ts +129 -12
  50. package/lib/types/elements/adw-toolbar-view.d.ts +5 -0
  51. package/lib/types/elements/adw-view-stack.d.ts +27 -19
  52. package/lib/types/elements/adw-view-switcher-bar.d.ts +23 -7
  53. package/lib/types/elements/adw-view-switcher.d.ts +22 -11
  54. package/lib/types/elements/adw-window-title.d.ts +3 -1
  55. package/lib/types/elements/view-switcher-dom.d.ts +44 -0
  56. package/lib/types/entry-rows.spec.d.ts +1 -0
  57. package/lib/types/index.d.ts +7 -0
  58. package/lib/types/navigation-view.spec.d.ts +1 -0
  59. package/lib/types/preferences-search.d.ts +20 -0
  60. package/lib/types/preferences.spec.d.ts +1 -0
  61. package/lib/types/split-button.spec.d.ts +1 -0
  62. package/lib/types/split-views.spec.d.ts +1 -0
  63. package/lib/types/styles.generated.d.ts +1 -1
  64. package/lib/types/tab-view.spec.d.ts +1 -0
  65. package/lib/types/view-stack.spec.d.ts +1 -0
  66. package/lib/types/view-switcher.spec.d.ts +1 -0
  67. package/package.json +6 -6
  68. package/scss/_banner.scss +13 -0
  69. package/scss/_bottom_sheet.scss +5 -6
  70. package/scss/_button.scss +3 -26
  71. package/scss/_button_content.scss +28 -0
  72. package/scss/_checks.scss +228 -0
  73. package/scss/_clamp.scss +9 -3
  74. package/scss/_data_grid.scss +15 -7
  75. package/scss/_drop_down.scss +12 -55
  76. package/scss/_expander_row.scss +5 -50
  77. package/scss/_icon.scss +51 -0
  78. package/scss/_icons.generated.scss +6 -0
  79. package/scss/_inline_view_switcher.scss +34 -0
  80. package/scss/_labels.scss +135 -0
  81. package/scss/_menu_button.scss +10 -70
  82. package/scss/_popover.scss +186 -0
  83. package/scss/_preferences.scss +29 -0
  84. package/scss/_preferences_dialog.scss +17 -0
  85. package/scss/_progress_bar.scss +161 -0
  86. package/scss/_reset.scss +18 -7
  87. package/scss/_spinner.scss +7 -3
  88. package/scss/_split_button.scss +6 -50
  89. package/scss/_switch.scss +90 -0
  90. package/scss/_switch_row.scss +6 -52
  91. package/scss/_tab_view.scss +64 -0
  92. package/scss/_theme.scss +10 -0
  93. package/scss/_toolbar_view.scss +74 -0
  94. package/scss/_variables.scss +34 -0
  95. package/scss/_view_switcher.scss +40 -0
  96. package/scss/_view_switcher_bar.scss +37 -0
  97. package/scss/_wrap_box.scss +56 -6
  98. package/scss/adwaita-skin.scss +23 -0
  99. package/src/adw-about-dialog.spec.ts +350 -0
  100. package/src/adw-action-rows.spec.ts +342 -0
  101. package/src/adw-avatar.spec.ts +139 -0
  102. package/src/adw-banner.spec.ts +152 -0
  103. package/src/adw-button-content.spec.ts +183 -0
  104. package/src/adw-button-row.spec.ts +12 -6
  105. package/src/adw-carousel.spec.ts +389 -0
  106. package/src/adw-checks.spec.ts +368 -0
  107. package/src/adw-drop-down.spec.ts +123 -0
  108. package/src/adw-header-bar.spec.ts +83 -0
  109. package/src/adw-icon.spec.ts +167 -0
  110. package/src/adw-progress-bar.spec.ts +173 -0
  111. package/src/adw-sidebar.spec.ts +332 -0
  112. package/src/adw-switch.spec.ts +242 -0
  113. package/src/adw-tab-view.spec.ts +12 -3
  114. package/src/adw-view-switcher-bar.spec.ts +61 -13
  115. package/src/adw-wrap-box.spec.ts +194 -0
  116. package/src/bottom-sheet.spec.ts +325 -0
  117. package/src/breakpoints.spec.ts +147 -0
  118. package/src/breakpoints.ts +67 -0
  119. package/src/chrome.spec.ts +301 -0
  120. package/src/elements/adw-about-dialog.ts +223 -163
  121. package/src/elements/adw-action-row.ts +120 -10
  122. package/src/elements/adw-avatar.ts +48 -54
  123. package/src/elements/adw-banner.ts +46 -10
  124. package/src/elements/adw-bottom-sheet.ts +145 -60
  125. package/src/elements/adw-button-content.ts +107 -33
  126. package/src/elements/adw-button-row.ts +61 -37
  127. package/src/elements/adw-button.ts +5 -7
  128. package/src/elements/adw-carousel.ts +467 -105
  129. package/src/elements/adw-checks.ts +299 -0
  130. package/src/elements/adw-clamp.ts +80 -10
  131. package/src/elements/adw-combo-row.ts +15 -7
  132. package/src/elements/adw-data-grid.ts +43 -91
  133. package/src/elements/adw-drop-down.ts +117 -128
  134. package/src/elements/adw-entry-row.ts +404 -36
  135. package/src/elements/adw-expander-row.ts +61 -29
  136. package/src/elements/adw-header-bar.ts +31 -6
  137. package/src/elements/adw-icon.ts +148 -0
  138. package/src/elements/adw-inline-view-switcher.ts +240 -129
  139. package/src/elements/adw-menu-button.ts +93 -70
  140. package/src/elements/adw-navigation-split-view.ts +48 -7
  141. package/src/elements/adw-navigation-view.ts +286 -148
  142. package/src/elements/adw-overlay-split-view.ts +164 -14
  143. package/src/elements/adw-password-entry-row.ts +111 -89
  144. package/src/elements/adw-popover.ts +283 -0
  145. package/src/elements/adw-preferences-dialog.ts +120 -21
  146. package/src/elements/adw-preferences-group.ts +128 -22
  147. package/src/elements/adw-progress-bar.ts +189 -0
  148. package/src/elements/adw-sidebar.ts +301 -142
  149. package/src/elements/adw-spin-row.ts +11 -5
  150. package/src/elements/adw-spinner.ts +23 -8
  151. package/src/elements/adw-split-button.ts +371 -92
  152. package/src/elements/adw-status-page.ts +9 -9
  153. package/src/elements/adw-switch-row.ts +100 -30
  154. package/src/elements/adw-switch.ts +129 -0
  155. package/src/elements/adw-tab-view.ts +682 -129
  156. package/src/elements/adw-toggle-group.ts +5 -7
  157. package/src/elements/adw-toolbar-view.ts +84 -21
  158. package/src/elements/adw-view-stack.ts +162 -85
  159. package/src/elements/adw-view-switcher-bar.ts +163 -81
  160. package/src/elements/adw-view-switcher.ts +231 -112
  161. package/src/elements/adw-window-title.ts +44 -11
  162. package/src/elements/adw-wrap-box.ts +131 -35
  163. package/src/elements/view-switcher-dom.ts +108 -0
  164. package/src/entry-rows.spec.ts +391 -0
  165. package/src/index.ts +23 -0
  166. package/src/navigation-view.spec.ts +435 -0
  167. package/src/preferences-search.ts +135 -0
  168. package/src/preferences.spec.ts +250 -0
  169. package/src/source-view/adw-source-view.ts +2 -5
  170. package/src/split-button.spec.ts +470 -0
  171. package/src/split-views.spec.ts +309 -0
  172. package/src/styles.generated.ts +1 -1
  173. package/src/tab-view.spec.ts +451 -0
  174. package/src/test.browser.mts +49 -0
  175. package/src/view-stack.spec.ts +272 -0
  176. package/src/view-switcher.spec.ts +446 -0
@@ -7,28 +7,111 @@
7
7
  // line-spacing (px, default 0) — spacing between lines → CSS row-gap
8
8
  // orientation ("horizontal" | "vertical", default "horizontal") — the main
9
9
  // axis children are packed along
10
- // align (0..1, default 0) — alignment of children within each line
11
- // (cross axis); 0 = start, 0.5 = center, 1 = end
10
+ // align (0..1, default 0) — where the children sit ALONG the line
11
+ // (main axis); 0 = start, 0.5 = middle, 1 = end
12
12
  // justify ("none" | "fill" | "spread", default "none") — whether/how
13
13
  // each line is stretched to fill the widget
14
- // justify-last-line (boolean) — also justify the last (incomplete) line
14
+ // justify-last-line (boolean) — also justify the last (final) line
15
15
  // line-homogeneous (boolean) — make every line take the same amount of space
16
16
  // pack-direction ("start-to-end" | "end-to-start", default "start-to-end") —
17
17
  // direction children are packed within a line
18
18
  // wrap-reverse (boolean) — reverse the wrap direction (lines wrap upwards)
19
19
  // Events: notify::child-spacing, notify::line-spacing (CustomEvent, bubbles —
20
- // mirrors GObject signal naming).
20
+ // mirrors GObject signal naming, including its "only on a real change" rule).
21
21
  // Reference: refs/libadwaita/src/adw-wrap-box.c / adw-wrap-layout.c (Adw.WrapBox)
22
22
  // Reference: refs/adwaita-web/adwaita-web/scss/_wrap_box.scss
23
23
  // Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
24
24
  // Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
25
25
 
26
- /** Adw.JustifyMode → flexbox justify-content. */
27
- const JUSTIFY_MAP: Record<string, string> = {
28
- none: 'flex-start',
29
- fill: 'space-between',
30
- spread: 'space-between',
31
- };
26
+ /** `AdwJustifyMode` (adw-wrap-layout.c:74-83). */
27
+ type JustifyMode = 'none' | 'fill' | 'spread';
28
+
29
+ /** The three modes, for validating the attribute against the C enum. */
30
+ const JUSTIFY_MODES: readonly JustifyMode[] = ['none', 'fill', 'spread'];
31
+
32
+ /** What one line does with the space its children do not use. */
33
+ interface LineLayout {
34
+ /** The mode governing this line, after the last-line gate. */
35
+ justify: JustifyMode;
36
+ /** Whether the CHILDREN absorb the leftover (adw-wrap-layout.c:336-341). */
37
+ growChildren: boolean;
38
+ /** Whether the GAPS absorb it (adw-wrap-layout.c:338-339, :752-757). */
39
+ growGaps: boolean;
40
+ /** The main-axis offset factor for an un-justified line (adw-wrap-layout.c:717-725). */
41
+ align: number;
42
+ }
43
+
44
+ /**
45
+ * The line-layout decision, straight from the C.
46
+ *
47
+ * `lastLine` is the FINAL line, complete or not (`i == *n_lines - 1`,
48
+ * adw-wrap-layout.c:463-464), so a wrap box whose children all fit on one line
49
+ * has that line governed by `justify-last-line` rather than by `justify`.
50
+ *
51
+ * The conformance table this must agree with is
52
+ * `@gjsify/adwaita-core/conformance` → `WRAP_BOX_LINE_VECTORS`; the same rows
53
+ * are asserted against the NativeScript port, so the two cannot drift apart
54
+ * without a test naming the input.
55
+ */
56
+ function resolveLine(
57
+ justify: JustifyMode,
58
+ justifyLastLine: boolean,
59
+ align: number,
60
+ lastLine: boolean,
61
+ childrenInLine: number,
62
+ ): LineLayout {
63
+ // adw-wrap-layout.c:397-400 / :705-706 — the final line is only justified on
64
+ // request; otherwise it falls back to ADW_JUSTIFY_NONE and to `align`.
65
+ const effective: JustifyMode = lastLine && !justifyLastLine ? 'none' : justify;
66
+ if (effective === 'none') return { justify: 'none', growChildren: false, growGaps: false, align };
67
+ // adw-wrap-layout.c:338 — SPREAD keeps children at their minimum size and
68
+ // widens the gaps, but the branch is guarded by `n_children > 1`: a lone
69
+ // child has nothing to spread against and is stretched instead, which the
70
+ // property documentation states outright (adw-wrap-box.c:349-352).
71
+ const spreadsGaps = effective === 'spread' && childrenInLine > 1;
72
+ return { justify: effective, growChildren: !spreadsGaps, growGaps: spreadsGaps, align: 0 };
73
+ }
74
+
75
+ /**
76
+ * `Adw.WrapBox:child-spacing` / `:line-spacing` normalisation.
77
+ *
78
+ * C clamps a negative value to 0 before it reaches the layout
79
+ * (adw-wrap-box.c:587-588, :927-928) — the property is declared with a minimum
80
+ * of 0, so without the clamp GObject would reject it. The non-numeric cases have
81
+ * no C counterpart (the property is an `int`); they resolve to the default so a
82
+ * typo cannot put `NaN` into a `column-gap`.
83
+ */
84
+ function normalizeSpacing(raw: string | null): number {
85
+ const value = Number.parseFloat(raw ?? '');
86
+ if (!Number.isFinite(value) || value < 0) return 0;
87
+ return value;
88
+ }
89
+
90
+ /** `Adw.WrapBox:align` — `g_param_spec_float (…, 0, 1, 0, …)`, adw-wrap-box.c:335-337. */
91
+ function normalizeAlign(raw: string | null): number {
92
+ const value = Number.parseFloat(raw ?? '');
93
+ if (!Number.isFinite(value)) return 0;
94
+ return Math.min(1, Math.max(0, value));
95
+ }
96
+
97
+ /** An out-of-range enum value leaves a GObject property at its default. */
98
+ function normalizeJustify(raw: string | null): JustifyMode {
99
+ return JUSTIFY_MODES.includes(raw as JustifyMode) ? (raw as JustifyMode) : 'none';
100
+ }
101
+
102
+ /**
103
+ * `align` as a `justify-content` keyword.
104
+ *
105
+ * C offsets the whole line block by `roundf (length_delta * align)` — a
106
+ * continuum. Flexbox has three main-axis positions, so the nearest one is taken;
107
+ * that is the renderer's approximation, not libadwaita's rule, which is why it
108
+ * lives here and not in the conformance table.
109
+ */
110
+ function alignToJustifyContent(align: number): string {
111
+ if (align < 0.25) return 'flex-start';
112
+ if (align < 0.75) return 'center';
113
+ return 'flex-end';
114
+ }
32
115
 
33
116
  export class AdwWrapBox extends HTMLElement {
34
117
  private _initialized = false;
@@ -49,7 +132,7 @@ export class AdwWrapBox extends HTMLElement {
49
132
 
50
133
  /** Spacing between children on the same line, in px (CSS column-gap). */
51
134
  get childSpacing(): number {
52
- return parseFloat(this.getAttribute('child-spacing') || '0');
135
+ return normalizeSpacing(this.getAttribute('child-spacing'));
53
136
  }
54
137
 
55
138
  set childSpacing(value: number) {
@@ -58,7 +141,7 @@ export class AdwWrapBox extends HTMLElement {
58
141
 
59
142
  /** Spacing between lines, in px (CSS row-gap). */
60
143
  get lineSpacing(): number {
61
- return parseFloat(this.getAttribute('line-spacing') || '0');
144
+ return normalizeSpacing(this.getAttribute('line-spacing'));
62
145
  }
63
146
 
64
147
  set lineSpacing(value: number) {
@@ -74,20 +157,20 @@ export class AdwWrapBox extends HTMLElement {
74
157
  attributeChangedCallback(name: string, old: string | null, value: string | null) {
75
158
  if (!this._initialized) return;
76
159
  this._sync();
77
- if (old === value) return;
160
+ // C compares AFTER normalising and returns early when nothing changed
161
+ // (adw-wrap-box.c:592-593), so `child-spacing="-5"` on a box already at 0
162
+ // notifies nobody.
78
163
  if (name === 'child-spacing') {
164
+ const next = normalizeSpacing(value);
165
+ if (next === normalizeSpacing(old)) return;
79
166
  this.dispatchEvent(
80
- new CustomEvent('notify::child-spacing', {
81
- bubbles: true,
82
- detail: { childSpacing: this.childSpacing },
83
- }),
167
+ new CustomEvent('notify::child-spacing', { bubbles: true, detail: { childSpacing: next } }),
84
168
  );
85
169
  } else if (name === 'line-spacing') {
170
+ const next = normalizeSpacing(value);
171
+ if (next === normalizeSpacing(old)) return;
86
172
  this.dispatchEvent(
87
- new CustomEvent('notify::line-spacing', {
88
- bubbles: true,
89
- detail: { lineSpacing: this.lineSpacing },
90
- }),
173
+ new CustomEvent('notify::line-spacing', { bubbles: true, detail: { lineSpacing: next } }),
91
174
  );
92
175
  }
93
176
  }
@@ -107,7 +190,8 @@ export class AdwWrapBox extends HTMLElement {
107
190
  // child-spacing is along the line (main axis), line-spacing is between
108
191
  // lines (cross axis). For a horizontal box that maps to column-gap /
109
192
  // row-gap respectively; the mapping flips for a vertical box.
110
- const childGap = `${this.childSpacing}px`;
193
+ const childSpacing = this.childSpacing;
194
+ const childGap = `${childSpacing}px`;
111
195
  const lineGap = `${this.lineSpacing}px`;
112
196
  if (vertical) {
113
197
  style.rowGap = childGap;
@@ -116,19 +200,31 @@ export class AdwWrapBox extends HTMLElement {
116
200
  style.columnGap = childGap;
117
201
  style.rowGap = lineGap;
118
202
  }
119
-
120
- // justify-content stretches each complete line along the main axis; the
121
- // last (incomplete) line is only stretched when justify-last-line is set.
122
- const justify = this.getAttribute('justify') ?? 'none';
123
- style.justifyContent = JUSTIFY_MAP[justify] ?? 'flex-start';
124
-
125
- // align (0..1) positions children within each line on the cross axis.
126
- // It is only honoured when the line is not justified.
127
- const align = Math.max(0, Math.min(1, parseFloat(this.getAttribute('align') || '0')));
128
- let crossAlign = 'flex-start';
129
- if (align >= 0.75) crossAlign = 'flex-end';
130
- else if (align >= 0.25) crossAlign = 'center';
131
- style.alignItems = crossAlign;
203
+ // Read back by the last-line filler, which has to cancel exactly one
204
+ // main-axis gap so it can never push a full line into a new one.
205
+ style.setProperty('--adw-wrap-box-child-spacing', childGap);
206
+
207
+ const justify = normalizeJustify(this.getAttribute('justify'));
208
+ const justifyLastLine = this.hasAttribute('justify-last-line');
209
+ const align = normalizeAlign(this.getAttribute('align'));
210
+
211
+ // A flex container has ONE `justify-content` and applies it to every
212
+ // line, so it carries the COMPLETE-line rule; the final-line rule is
213
+ // published beside it for the stylesheet, which reaches that line through
214
+ // the only two selectors CSS offers — `:only-child` (a box with one child
215
+ // has one line, and it is the last) and the generated `::after` filler.
216
+ // The pair is deliberately never collapsed into one attribute: flexbox
217
+ // cannot express "every line but the last", and pretending otherwise is
218
+ // what made `justify-last-line` a no-op in the first place.
219
+ const line = resolveLine(justify, justifyLastLine, align, false, 2);
220
+ const lastLine = resolveLine(justify, justifyLastLine, align, true, 2);
221
+ this.dataset.lineJustify = line.justify;
222
+ this.dataset.lastLineJustify = lastLine.justify;
223
+
224
+ // SPREAD widens the gaps; FILL grows the children (stylesheet) and leaves
225
+ // the gaps alone; NONE offsets the whole line block by `align` — along
226
+ // the MAIN axis, never the cross one.
227
+ style.justifyContent = line.growGaps ? 'space-between' : alignToJustifyContent(line.align);
132
228
 
133
229
  // line-homogeneous makes every line take the same amount of space, which
134
230
  // flexbox approximates by stretching the lines across the cross axis.
@@ -0,0 +1,108 @@
1
+ // The DOM half of the view-switcher family — what `@gjsify/adwaita-core` cannot
2
+ // decide because it is genuinely per-renderer.
3
+ //
4
+ // Everything that decides WHAT a switcher shows (the button-visibility rule, the
5
+ // `image-missing` fallback, the badge label + screen-reader description, the
6
+ // mnemonic-stripped label, the tooltip, the reveal gate, the two index spaces)
7
+ // lives in `@gjsify/adwaita-core` and is pinned by the conformance vectors.
8
+ // What is left, and lives here, is how a derived model becomes nodes: an icon
9
+ // NAME turned into a CSS mask class, an indicator span, and a `setTimeout`
10
+ // wrapped in the scheduler seam the core takes for the drag-hover dwell.
11
+ //
12
+ // Shared by all three switcher elements because it was three copies before:
13
+ // `<adw-view-switcher>`, `<adw-inline-view-switcher>` and
14
+ // `<adw-view-switcher-bar>` each built their own icon span and their own page
15
+ // parsing, and the copies had already drifted (a titleless page rendered `''` in
16
+ // one and `page-<index>` in another).
17
+ //
18
+ // Reference: refs/libadwaita/src/adw-view-switcher.c
19
+ // Reference: refs/libadwaita/src/adw-indicator-bin.c
20
+ // Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
21
+ // Modifications: Implemented for @gjsify/adwaita-web Web Components.
22
+
23
+ import { viewSwitcherIconName } from '@gjsify/adwaita-core';
24
+ import type { AdwViewSwitcherPageInit, ViewSwitcherScheduler, ViewSwitcherTimerHandle } from '@gjsify/adwaita-core';
25
+
26
+ import { type AdwIcon, createAdwIcon } from './adw-icon.js';
27
+
28
+ /**
29
+ * The browser's timer behind the core's scheduler seam.
30
+ *
31
+ * One shared instance rather than one per element: it holds no state, and the
32
+ * seam exists so the CORE stays free of a global timer, not so each element
33
+ * invents its own.
34
+ */
35
+ export const domViewSwitcherScheduler: ViewSwitcherScheduler = {
36
+ schedule(callback: () => void, ms: number): ViewSwitcherTimerHandle {
37
+ return setTimeout(callback, ms);
38
+ },
39
+ cancel(handle: ViewSwitcherTimerHandle): void {
40
+ clearTimeout(handle as ReturnType<typeof setTimeout>);
41
+ },
42
+ };
43
+
44
+ /**
45
+ * Build a switcher button's icon node, carrying its own position class.
46
+ *
47
+ * The core resolves NULL/empty to `image-missing` (which the stylesheet carries
48
+ * a real glyph for); the `-symbolic` strip and the CSS-token guard are
49
+ * `normalizeIconName`'s, applied by `<adw-icon>` — that strip is this package's
50
+ * own mask-class convention and has no counterpart in C, where the name reaches
51
+ * `GtkImage` untouched.
52
+ */
53
+ export function createSwitcherIcon(iconName: string, extra: string): AdwIcon {
54
+ return createAdwIcon(viewSwitcherIconName(iconName), extra);
55
+ }
56
+
57
+ /** Repaint an existing switcher icon from a page's declared name. */
58
+ export function applySwitcherIcon(icon: AdwIcon, iconName: string): void {
59
+ icon.iconName = viewSwitcherIconName(iconName);
60
+ }
61
+
62
+ /**
63
+ * Read a declared page element into the core's page init.
64
+ *
65
+ * `getAttribute` returns `null` for an absent attribute, which is EXACTLY C's
66
+ * NULL — so the values are passed through unchanged rather than defaulted to
67
+ * `''`. That `?? ''` is what made the button-visibility rule unimplementable in
68
+ * the old code: once an absent title has become the empty string, "the page has
69
+ * no title" is no longer expressible.
70
+ */
71
+ export function readSwitcherPage(element: Element): AdwViewSwitcherPageInit {
72
+ const badge = Number.parseInt(element.getAttribute('badge-number') ?? '', 10);
73
+ return {
74
+ // A page with no `name` is the page named '' — the same choice
75
+ // `<adw-view-stack>` made, and for the same reason: C stores the name
76
+ // verbatim (adw-view-stack.c:1170) and an invented `page-<index>` is
77
+ // addressable by a name the author never wrote.
78
+ name: element.getAttribute('name') ?? '',
79
+ title: element.getAttribute('title'),
80
+ iconName: element.getAttribute('icon-name'),
81
+ // `hidden` is the DOM spelling of AdwViewStackPage:visible.
82
+ visible: !element.hasAttribute('hidden'),
83
+ useUnderline: element.hasAttribute('use-underline'),
84
+ badgeNumber: Number.isNaN(badge) ? 0 : badge,
85
+ needsAttention: element.hasAttribute('needs-attention'),
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Paint `AdwIndicatorBin`'s badge/dot onto a span: the badge text when there is
91
+ * one, a bare dot when only `needs-attention` is set, nothing otherwise
92
+ * (adw-indicator-bin.c:58-68 + the `.needs-attention` style class).
93
+ */
94
+ export function applyIndicator(indicator: HTMLElement, badgeLabel: string, needsAttention: boolean): void {
95
+ indicator.textContent = badgeLabel;
96
+ indicator.classList.toggle('needs-attention', needsAttention && badgeLabel.length === 0);
97
+ indicator.hidden = badgeLabel.length === 0 && !needsAttention;
98
+ }
99
+
100
+ /**
101
+ * Mirror the derived screen-reader description onto the element, REMOVING the
102
+ * attribute when there is none — `update_description_cb` resets the accessible
103
+ * property rather than setting it to `""` (adw-view-switcher-button.c:104-113).
104
+ */
105
+ export function applyDescription(element: HTMLElement, description: string): void {
106
+ if (description) element.setAttribute('aria-description', description);
107
+ else element.removeAttribute('aria-description');
108
+ }