@gjsify/adwaita-web 0.32.0 → 0.33.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 (38) hide show
  1. package/dist/adwaita-web.css +72 -50
  2. package/dist/adwaita-web.css.map +1 -1
  3. package/lib/types/elements/adw-combo-row.d.ts +10 -0
  4. package/lib/types/elements/adw-inline-view-switcher.d.ts +37 -1
  5. package/lib/types/elements/adw-navigation-split-view.d.ts +42 -0
  6. package/lib/types/elements/adw-overlay-split-view.d.ts +56 -0
  7. package/lib/types/elements/adw-spinner.d.ts +23 -0
  8. package/lib/types/elements/adw-view-stack.d.ts +12 -0
  9. package/lib/types/elements/adw-view-switcher-bar.d.ts +7 -5
  10. package/lib/types/elements/adw-view-switcher.d.ts +55 -3
  11. package/lib/types/elements/adw-wrap-box.d.ts +58 -3
  12. package/lib/types/styles.generated.d.ts +1 -1
  13. package/package.json +6 -6
  14. package/scss/_combo_row.scss +19 -0
  15. package/scss/_navigation_split_view.scss +47 -8
  16. package/scss/_overlay_split_view.scss +117 -32
  17. package/scss/_spinner.scss +35 -21
  18. package/scss/_tab_view.scss +12 -13
  19. package/scss/_wrap_box.scss +9 -0
  20. package/src/adw-avatar.spec.ts +37 -0
  21. package/src/adw-button-content.spec.ts +24 -0
  22. package/src/adw-row-state.spec.ts +49 -0
  23. package/src/adw-wrap-box.spec.ts +148 -1
  24. package/src/chrome.spec.ts +111 -12
  25. package/src/elements/adw-action-row.ts +11 -1
  26. package/src/elements/adw-combo-row.ts +17 -0
  27. package/src/elements/adw-inline-view-switcher.ts +91 -8
  28. package/src/elements/adw-navigation-split-view.ts +179 -14
  29. package/src/elements/adw-overlay-split-view.ts +378 -20
  30. package/src/elements/adw-spinner.ts +165 -21
  31. package/src/elements/adw-tab-view.ts +20 -2
  32. package/src/elements/adw-view-stack.ts +21 -0
  33. package/src/elements/adw-view-switcher-bar.ts +13 -5
  34. package/src/elements/adw-view-switcher.ts +103 -11
  35. package/src/elements/adw-wrap-box.ts +290 -135
  36. package/src/split-views.spec.ts +586 -2
  37. package/src/styles.generated.ts +1 -1
  38. package/src/view-switcher.spec.ts +219 -0
@@ -1,103 +1,75 @@
1
1
  // <adw-wrap-box> — A box-like container that lays its children out in a line and
2
- // wraps them onto new lines when they run out of room (the web analog of
3
- // flexbox `flex-wrap: wrap` with a gap). Mirrors Adw.WrapBox.
4
- // Attributes:
5
- // child-spacing (px, default 0) — spacing between children on the same line
6
- // → CSS column-gap
7
- // line-spacing (px, default 0) — spacing between lines → CSS row-gap
8
- // orientation ("horizontal" | "vertical", default "horizontal") — the main
9
- // axis children are packed along
10
- // align (0..1, default 0) — where the children sit ALONG the line
11
- // (main axis); 0 = start, 0.5 = middle, 1 = end
12
- // justify ("none" | "fill" | "spread", default "none") — whether/how
13
- // each line is stretched to fill the widget
14
- // justify-last-line (boolean) — also justify the last (final) line
15
- // line-homogeneous (boolean) — make every line take the same amount of space
16
- // pack-direction ("start-to-end" | "end-to-start", default "start-to-end") —
17
- // direction children are packed within a line
18
- // wrap-reverse (boolean) — reverse the wrap direction (lines wrap upwards)
19
- // Events: notify::child-spacing, notify::line-spacing (CustomEvent, bubbles —
20
- // mirrors GObject signal naming, including its "only on a real change" rule).
2
+ // wraps them onto new lines when they run out of room (the web analog of flexbox
3
+ // `flex-wrap: wrap` with a gap). Mirrors Adw.WrapBox.
4
+ //
5
+ // Attributes — all fourteen of the widget's properties, each with a
6
+ // `notify::<property>` (adw-wrap-box.c:284-497). The element used to carry nine
7
+ // attributes and notify for two, so a consumer binding to any of the other
8
+ // twelve was listening to a signal that could not fire.
9
+ //
10
+ // child-spacing / line-spacing (int >= 0, default 0) -> column-gap / row-gap
11
+ // child-spacing-unit / line-spacing-unit ("px" | "pt" | "sp", default "px")
12
+ // orientation ("horizontal" | "vertical", default "horizontal")
13
+ // pack-direction ("start-to-end" | "end-to-start", default "start-to-end")
14
+ // align (0..1, default 0) — MAIN-axis offset of the whole line block
15
+ // justify ("none" | "fill" | "spread", default "none")
16
+ // justify-last-line (boolean)
17
+ // line-homogeneous (boolean)
18
+ // wrap-reverse (boolean)
19
+ // wrap-policy ("minimum" | "natural", default "natural")
20
+ // natural-line-length (int >= -1, default -1 = unset) + -unit
21
+ //
22
+ // The property CONTRACT — the normalisers and the justify/align/last-line
23
+ // decision — is `@gjsify/adwaita-core`'s `wrap-box.ts`, held to
24
+ // `WRAP_BOX_LINE_VECTORS` and friends, which the NativeScript suite drives too.
25
+ // This element used to carry its own copy of all of it.
26
+ //
21
27
  // Reference: refs/libadwaita/src/adw-wrap-box.c / adw-wrap-layout.c (Adw.WrapBox)
22
28
  // Reference: refs/adwaita-web/adwaita-web/scss/_wrap_box.scss
23
29
  // Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
24
30
  // Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
25
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
- }
32
+ import {
33
+ ADW_WRAP_BOX_NATURAL_LINE_LENGTH_UNSET,
34
+ normalizeNaturalLineLength,
35
+ normalizeWrapBoxAlign,
36
+ normalizeWrapBoxJustify,
37
+ normalizeWrapBoxLengthUnit,
38
+ normalizeWrapBoxPackDirection,
39
+ normalizeWrapBoxSpacing,
40
+ normalizeWrapPolicy,
41
+ resolveWrapBoxChildOrder,
42
+ resolveWrapBoxLine,
43
+ wrapBoxLengthToPx,
44
+ wrapPolicyFlexShrink,
45
+ type AdwLengthUnit,
46
+ } from '@gjsify/adwaita-core';
74
47
 
75
48
  /**
76
- * `Adw.WrapBox:child-spacing` / `:line-spacing` normalisation.
49
+ * The attributes that carry a property, in the order the C installs them.
77
50
  *
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`.
51
+ * Doubles as the `notify::` roster: every entry gets an event on a real change,
52
+ * so adding an attribute without its notification is not expressible here.
83
53
  */
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
- }
54
+ const PROPERTY_ATTRIBUTES = [
55
+ 'child-spacing',
56
+ 'child-spacing-unit',
57
+ 'pack-direction',
58
+ 'align',
59
+ 'justify',
60
+ 'justify-last-line',
61
+ 'line-spacing',
62
+ 'line-spacing-unit',
63
+ 'line-homogeneous',
64
+ 'natural-line-length',
65
+ 'natural-line-length-unit',
66
+ 'wrap-reverse',
67
+ 'wrap-policy',
68
+ 'orientation',
69
+ ] as const;
96
70
 
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
- }
71
+ /** One attribute's normalised value, for the "did it really change" comparison. */
72
+ type PropertyValue = string | number | boolean;
101
73
 
102
74
  /**
103
75
  * `align` as a `justify-content` keyword.
@@ -117,37 +89,186 @@ export class AdwWrapBox extends HTMLElement {
117
89
  private _initialized = false;
118
90
 
119
91
  static get observedAttributes() {
120
- return [
121
- 'child-spacing',
122
- 'line-spacing',
123
- 'orientation',
124
- 'align',
125
- 'justify',
126
- 'justify-last-line',
127
- 'line-homogeneous',
128
- 'pack-direction',
129
- 'wrap-reverse',
130
- ];
131
- }
132
-
133
- /** Spacing between children on the same line, in px (CSS column-gap). */
92
+ return [...PROPERTY_ATTRIBUTES];
93
+ }
94
+
95
+ // --- properties ---
96
+
97
+ /** Spacing between children on the same line, in `child-spacing-unit`. */
134
98
  get childSpacing(): number {
135
- return normalizeSpacing(this.getAttribute('child-spacing'));
99
+ return normalizeWrapBoxSpacing(this.getAttribute('child-spacing'));
136
100
  }
137
101
 
138
102
  set childSpacing(value: number) {
139
103
  this.setAttribute('child-spacing', String(value));
140
104
  }
141
105
 
142
- /** Spacing between lines, in px (CSS row-gap). */
106
+ /** The unit `child-spacing` is written in. Defaults to `px`, as in C. */
107
+ get childSpacingUnit(): AdwLengthUnit {
108
+ return normalizeWrapBoxLengthUnit(this.getAttribute('child-spacing-unit'));
109
+ }
110
+
111
+ set childSpacingUnit(value: AdwLengthUnit) {
112
+ this.setAttribute('child-spacing-unit', value);
113
+ }
114
+
115
+ /** Spacing between lines, in `line-spacing-unit`. */
143
116
  get lineSpacing(): number {
144
- return normalizeSpacing(this.getAttribute('line-spacing'));
117
+ return normalizeWrapBoxSpacing(this.getAttribute('line-spacing'));
145
118
  }
146
119
 
147
120
  set lineSpacing(value: number) {
148
121
  this.setAttribute('line-spacing', String(value));
149
122
  }
150
123
 
124
+ /** The unit `line-spacing` is written in. Defaults to `px`, as in C. */
125
+ get lineSpacingUnit(): AdwLengthUnit {
126
+ return normalizeWrapBoxLengthUnit(this.getAttribute('line-spacing-unit'));
127
+ }
128
+
129
+ set lineSpacingUnit(value: AdwLengthUnit) {
130
+ this.setAttribute('line-spacing-unit', value);
131
+ }
132
+
133
+ /** Where the children sit ALONG the line (main axis): 0 start, 0.5 middle, 1 end. */
134
+ get align(): number {
135
+ return normalizeWrapBoxAlign(this.getAttribute('align'));
136
+ }
137
+
138
+ set align(value: number) {
139
+ this.setAttribute('align', String(value));
140
+ }
141
+
142
+ /** Whether and how each line is stretched to fill the widget. */
143
+ get justify(): 'none' | 'fill' | 'spread' {
144
+ return normalizeWrapBoxJustify(this.getAttribute('justify'));
145
+ }
146
+
147
+ set justify(value: 'none' | 'fill' | 'spread') {
148
+ this.setAttribute('justify', value);
149
+ }
150
+
151
+ /** Whether the FINAL line is justified too. Default false, as in C. */
152
+ get justifyLastLine(): boolean {
153
+ return this.hasAttribute('justify-last-line');
154
+ }
155
+
156
+ set justifyLastLine(value: boolean) {
157
+ this.toggleAttribute('justify-last-line', !!value);
158
+ }
159
+
160
+ /** Whether an overflowing line squeezes its children or lets them spill. */
161
+ get wrapPolicy(): 'minimum' | 'natural' {
162
+ return normalizeWrapPolicy(this.getAttribute('wrap-policy'));
163
+ }
164
+
165
+ set wrapPolicy(value: 'minimum' | 'natural') {
166
+ this.setAttribute('wrap-policy', value);
167
+ }
168
+
169
+ /** The direction children are packed in each line. */
170
+ get packDirection(): 'start-to-end' | 'end-to-start' {
171
+ return normalizeWrapBoxPackDirection(this.getAttribute('pack-direction'));
172
+ }
173
+
174
+ set packDirection(value: 'start-to-end' | 'end-to-start') {
175
+ this.setAttribute('pack-direction', value);
176
+ }
177
+
178
+ /** Whether lines wrap upwards (horizontal) / towards the start (vertical). */
179
+ get wrapReverse(): boolean {
180
+ return this.hasAttribute('wrap-reverse');
181
+ }
182
+
183
+ set wrapReverse(value: boolean) {
184
+ this.toggleAttribute('wrap-reverse', !!value);
185
+ }
186
+
187
+ /** Whether every line takes the same amount of space. */
188
+ get lineHomogeneous(): boolean {
189
+ return this.hasAttribute('line-homogeneous');
190
+ }
191
+
192
+ set lineHomogeneous(value: boolean) {
193
+ this.toggleAttribute('line-homogeneous', !!value);
194
+ }
195
+
196
+ /** The natural line length in `natural-line-length-unit`, or `-1` when unset. */
197
+ get naturalLineLength(): number {
198
+ return normalizeNaturalLineLength(this.getAttribute('natural-line-length'));
199
+ }
200
+
201
+ set naturalLineLength(value: number) {
202
+ this.setAttribute('natural-line-length', String(value));
203
+ }
204
+
205
+ /** The unit `natural-line-length` is written in. Defaults to `px`, as in C. */
206
+ get naturalLineLengthUnit(): AdwLengthUnit {
207
+ return normalizeWrapBoxLengthUnit(this.getAttribute('natural-line-length-unit'));
208
+ }
209
+
210
+ set naturalLineLengthUnit(value: AdwLengthUnit) {
211
+ this.setAttribute('natural-line-length-unit', value);
212
+ }
213
+
214
+ /** The axis children are packed along. */
215
+ get orientation(): 'horizontal' | 'vertical' {
216
+ return this.getAttribute('orientation') === 'vertical' ? 'vertical' : 'horizontal';
217
+ }
218
+
219
+ set orientation(value: 'horizontal' | 'vertical') {
220
+ this.setAttribute('orientation', value);
221
+ }
222
+
223
+ // --- child list (adw_wrap_box_insert_child_after / reorder / remove_all) ---
224
+ //
225
+ // `adw_wrap_box_append`, `_prepend` and `_remove` are deliberately NOT
226
+ // redeclared: `ParentNode.append` / `.prepend` and `Node.removeChild` are
227
+ // those three, with the same semantics, and shadowing them would give this
228
+ // element two spellings of one operation. Only the three the DOM has no
229
+ // counterpart for are added.
230
+
231
+ /** The element children, which are what the layout actually lays out. */
232
+ private get _children(): Element[] {
233
+ return [...this.children];
234
+ }
235
+
236
+ /**
237
+ * `adw_wrap_box_insert_child_after` (adw-wrap-box.c:1283-1300).
238
+ *
239
+ * A NULL/absent `sibling` inserts at the FIRST position, not the last —
240
+ * `gtk_widget_insert_after`'s documented rule, pinned by
241
+ * `WRAP_BOX_CHILD_ORDER_VECTORS`. Returns whether the insert happened; a
242
+ * refusal is where C would have hit a `g_return_if_fail`.
243
+ */
244
+ insertChildAfter(child: Element, sibling: Element | null = null): boolean {
245
+ return this._applyOrder('insert-after', child, sibling);
246
+ }
247
+
248
+ /** `adw_wrap_box_reorder_child_after` (adw-wrap-box.c:1315-1332). Same NULL rule. */
249
+ reorderChildAfter(child: Element, sibling: Element | null = null): boolean {
250
+ return this._applyOrder('reorder-after', child, sibling);
251
+ }
252
+
253
+ /** `adw_wrap_box_remove_all` (adw-wrap-box.c:1406-1414). */
254
+ removeAll(): void {
255
+ for (const child of this._children) this.removeChild(child);
256
+ }
257
+
258
+ private _applyOrder(op: 'insert-after' | 'reorder-after', child: Element, sibling: Element | null): boolean {
259
+ const next = resolveWrapBoxChildOrder({ children: this._children, child, sibling, op });
260
+ if (next === null) return false;
261
+ // Move exactly the one node: `insertBefore` relocates a node already in
262
+ // the tree, and a null reference node appends. Re-appending the whole
263
+ // resolved list would reach the same order while detaching and
264
+ // re-attaching every sibling, which resets focus and any element state.
265
+ const at = next.indexOf(child);
266
+ this.insertBefore(child, next[at + 1] ?? null);
267
+ return true;
268
+ }
269
+
270
+ // --- lifecycle ---
271
+
151
272
  connectedCallback() {
152
273
  if (this._initialized) return;
153
274
  this._initialized = true;
@@ -159,40 +280,56 @@ export class AdwWrapBox extends HTMLElement {
159
280
  this._sync();
160
281
  // C compares AFTER normalising and returns early when nothing changed
161
282
  // (adw-wrap-box.c:592-593), so `child-spacing="-5"` on a box already at 0
162
- // notifies nobody.
163
- if (name === 'child-spacing') {
164
- const next = normalizeSpacing(value);
165
- if (next === normalizeSpacing(old)) return;
166
- this.dispatchEvent(
167
- new CustomEvent('notify::child-spacing', { bubbles: true, detail: { childSpacing: next } }),
168
- );
169
- } else if (name === 'line-spacing') {
170
- const next = normalizeSpacing(value);
171
- if (next === normalizeSpacing(old)) return;
172
- this.dispatchEvent(
173
- new CustomEvent('notify::line-spacing', { bubbles: true, detail: { lineSpacing: next } }),
174
- );
283
+ // notifies nobody. The same gate applies to every property, not just the
284
+ // two spacings this element used to special-case.
285
+ const next = this._normalized(name, value);
286
+ if (next === this._normalized(name, old)) return;
287
+ this.dispatchEvent(new CustomEvent(`notify::${name}`, { bubbles: true, detail: { [name]: next } }));
288
+ }
289
+
290
+ /** One attribute's value as the widget stores it — the notify comparison key. */
291
+ private _normalized(name: string, raw: string | null): PropertyValue {
292
+ switch (name) {
293
+ case 'child-spacing':
294
+ case 'line-spacing':
295
+ return normalizeWrapBoxSpacing(raw);
296
+ case 'child-spacing-unit':
297
+ case 'line-spacing-unit':
298
+ case 'natural-line-length-unit':
299
+ return normalizeWrapBoxLengthUnit(raw);
300
+ case 'align':
301
+ return normalizeWrapBoxAlign(raw);
302
+ case 'justify':
303
+ return normalizeWrapBoxJustify(raw);
304
+ case 'wrap-policy':
305
+ return normalizeWrapPolicy(raw);
306
+ case 'pack-direction':
307
+ return normalizeWrapBoxPackDirection(raw);
308
+ case 'natural-line-length':
309
+ return normalizeNaturalLineLength(raw);
310
+ case 'orientation':
311
+ return raw === 'vertical' ? 'vertical' : 'horizontal';
312
+ default:
313
+ // The three booleans: a present attribute is true whatever it says.
314
+ return raw !== null;
175
315
  }
176
316
  }
177
317
 
178
318
  private _sync() {
179
319
  const style = this.style;
180
- const vertical = this.getAttribute('orientation') === 'vertical';
181
- const reverseWrap = this.hasAttribute('wrap-reverse');
182
- const packEndToStart = this.getAttribute('pack-direction') === 'end-to-start';
320
+ const vertical = this.orientation === 'vertical';
183
321
 
184
322
  // Main axis is the packing direction; lines wrap along the cross axis.
185
323
  const direction = vertical ? 'column' : 'row';
186
- const reversed = packEndToStart ? `${direction}-reverse` : direction;
187
- style.flexDirection = reversed;
188
- style.flexWrap = reverseWrap ? 'wrap-reverse' : 'wrap';
324
+ style.flexDirection = this.packDirection === 'end-to-start' ? `${direction}-reverse` : direction;
325
+ style.flexWrap = this.wrapReverse ? 'wrap-reverse' : 'wrap';
189
326
 
190
327
  // child-spacing is along the line (main axis), line-spacing is between
191
328
  // lines (cross axis). For a horizontal box that maps to column-gap /
192
- // row-gap respectively; the mapping flips for a vertical box.
193
- const childSpacing = this.childSpacing;
194
- const childGap = `${childSpacing}px`;
195
- const lineGap = `${this.lineSpacing}px`;
329
+ // row-gap respectively; the mapping flips for a vertical box. Both are
330
+ // resolved through their OWN unit first, which is why they can disagree.
331
+ const childGap = `${wrapBoxLengthToPx(this.childSpacing, this.childSpacingUnit)}px`;
332
+ const lineGap = `${wrapBoxLengthToPx(this.lineSpacing, this.lineSpacingUnit)}px`;
196
333
  if (vertical) {
197
334
  style.rowGap = childGap;
198
335
  style.columnGap = lineGap;
@@ -204,9 +341,9 @@ export class AdwWrapBox extends HTMLElement {
204
341
  // main-axis gap so it can never push a full line into a new one.
205
342
  style.setProperty('--adw-wrap-box-child-spacing', childGap);
206
343
 
207
- const justify = normalizeJustify(this.getAttribute('justify'));
208
- const justifyLastLine = this.hasAttribute('justify-last-line');
209
- const align = normalizeAlign(this.getAttribute('align'));
344
+ const justify = this.justify;
345
+ const justifyLastLine = this.justifyLastLine;
346
+ const align = this.align;
210
347
 
211
348
  // A flex container has ONE `justify-content` and applies it to every
212
349
  // line, so it carries the COMPLETE-line rule; the final-line rule is
@@ -216,8 +353,8 @@ export class AdwWrapBox extends HTMLElement {
216
353
  // The pair is deliberately never collapsed into one attribute: flexbox
217
354
  // cannot express "every line but the last", and pretending otherwise is
218
355
  // 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);
356
+ const line = resolveWrapBoxLine({ justify, justifyLastLine, align, lastLine: false, childrenInLine: 2 });
357
+ const lastLine = resolveWrapBoxLine({ justify, justifyLastLine, align, lastLine: true, childrenInLine: 2 });
221
358
  this.dataset.lineJustify = line.justify;
222
359
  this.dataset.lastLineJustify = lastLine.justify;
223
360
 
@@ -228,7 +365,25 @@ export class AdwWrapBox extends HTMLElement {
228
365
 
229
366
  // line-homogeneous makes every line take the same amount of space, which
230
367
  // flexbox approximates by stretching the lines across the cross axis.
231
- style.alignContent = this.hasAttribute('line-homogeneous') ? 'stretch' : 'flex-start';
368
+ style.alignContent = this.lineHomogeneous ? 'stretch' : 'flex-start';
369
+
370
+ // `wrap-policy` decides whether an overflowing line squeezes its children
371
+ // or lets them spill. CSS defaults `flex-shrink` to 1, i.e. to
372
+ // ADW_WRAP_MINIMUM — the value libadwaita does NOT default to, so an
373
+ // unset policy has been drawing the wrong one on every wrap box. What
374
+ // flexbox cannot do either way is pack MORE children onto a line, since
375
+ // line breaking runs on hypothetical sizes; see `wrapPolicyFlexShrink`.
376
+ style.setProperty('--adw-wrap-box-child-shrink', String(wrapPolicyFlexShrink(this.wrapPolicy)));
377
+
378
+ // DELIBERATE DEVIATION: libadwaita's `natural-line-length` caps the box's
379
+ // NATURAL size request, leaving a larger allocation free to happen; CSS
380
+ // has no property that caps only the intrinsic contribution, so this is a
381
+ // max-size on the main axis and therefore caps the allocation too. It is
382
+ // the intended use (limiting line length inside a popover) either way.
383
+ const natural = wrapBoxLengthToPx(this.naturalLineLength, this.naturalLineLengthUnit);
384
+ const cap = natural === ADW_WRAP_BOX_NATURAL_LINE_LENGTH_UNSET ? '' : `${natural}px`;
385
+ style.maxWidth = vertical ? '' : cap;
386
+ style.maxHeight = vertical ? cap : '';
232
387
  }
233
388
  }
234
389