@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.
- package/README.md +3 -3
- package/dist/adwaita-web.css +62 -0
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/elements/adw-combo-row.d.ts +56 -0
- package/lib/types/elements/adw-spin-row.d.ts +11 -1
- package/lib/types/elements/adw-split-button.d.ts +43 -3
- package/lib/types/elements/adw-tab-view.d.ts +23 -5
- package/lib/types/elements/adw-view-stack.d.ts +17 -2
- package/lib/types/elements/gtk-drop-down.d.ts +22 -7
- package/lib/types/elements/gtk-menu-button.d.ts +43 -26
- package/lib/types/elements/gtk-popover.d.ts +14 -1
- package/lib/types/elements/popover-menu.d.ts +99 -0
- package/lib/types/index.d.ts +1 -1
- package/lib/types/shared-trees.spec.d.ts +2 -0
- package/lib/types/styles.generated.d.ts +1 -1
- package/package.json +6 -6
- package/scss/_popover.scss +99 -0
- package/src/adw-avatar.spec.ts +17 -0
- package/src/adw-row-state.spec.ts +335 -17
- package/src/adw-tab-view.spec.ts +8 -5
- package/src/elements/adw-avatar.ts +11 -3
- package/src/elements/adw-combo-row.ts +161 -22
- package/src/elements/adw-spin-row.ts +49 -28
- package/src/elements/adw-split-button.ts +117 -39
- package/src/elements/adw-tab-view.ts +42 -22
- package/src/elements/adw-view-stack.ts +20 -4
- package/src/elements/adw-view-switcher-bar.ts +6 -3
- package/src/elements/gtk-drop-down.ts +109 -66
- package/src/elements/gtk-menu-button.ts +112 -88
- package/src/elements/gtk-popover.ts +17 -2
- package/src/elements/popover-menu.ts +366 -0
- package/src/gtk-drop-down.spec.ts +117 -25
- package/src/gtk-image.spec.ts +3 -3
- package/src/gtk-menu-button.spec.ts +165 -11
- package/src/index.ts +14 -1
- package/src/keyboard-operable.spec.ts +170 -1
- package/src/shared-trees.spec.ts +151 -0
- package/src/split-button.spec.ts +36 -22
- package/src/styles.generated.ts +1 -1
- package/src/tab-view.spec.ts +83 -15
- package/src/test.browser.mts +2 -0
- package/src/view-stack.spec.ts +5 -3
package/src/tab-view.spec.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
TAB_VIEW_VECTORS,
|
|
16
16
|
TABS_REVEALED_VECTORS,
|
|
17
17
|
applyTabViewOp,
|
|
18
|
+
replayTabPagesAsSplices,
|
|
18
19
|
seedTabViewPages,
|
|
19
20
|
tabViewClosing,
|
|
20
21
|
tabViewOrder,
|
|
@@ -113,6 +114,19 @@ export const AdwTabViewConformanceTest = async () => {
|
|
|
113
114
|
if (vector.pagesChanges) expect(mounted.pagesChanges).toStrictEqual([...vector.pagesChanges]);
|
|
114
115
|
if (vector.closeAttempts) expect(mounted.closeAttempts).toStrictEqual([...vector.closeAttempts]);
|
|
115
116
|
|
|
117
|
+
// ADR 0046: the page signal, replayed as portable `items-changed` splices,
|
|
118
|
+
// must reproduce the order this widget actually ended on. That is the whole
|
|
119
|
+
// claim — `TabViewPagesChange` IS the positional list signal — measured
|
|
120
|
+
// against a real change stream rather than asserted in an ADR.
|
|
121
|
+
if (vector.pagesChanges) {
|
|
122
|
+
expect(
|
|
123
|
+
replayTabPagesAsSplices(
|
|
124
|
+
vector.pages.map((page) => page.id),
|
|
125
|
+
mounted.pagesChanges,
|
|
126
|
+
),
|
|
127
|
+
).toStrictEqual([...vector.order]);
|
|
128
|
+
}
|
|
129
|
+
|
|
116
130
|
expect(tabViewOrder(view)).toStrictEqual([...vector.order]);
|
|
117
131
|
expect(view.nPinnedPages).toBe(vector.nPinnedPages);
|
|
118
132
|
expect(view.selectedId).toBe(vector.selectedId);
|
|
@@ -193,7 +207,7 @@ export const AdwTabViewConformanceTest = async () => {
|
|
|
193
207
|
});
|
|
194
208
|
});
|
|
195
209
|
|
|
196
|
-
await describe('adw-tab-view selected attribute', async () => {
|
|
210
|
+
await describe('adw-tab-view selected-page attribute', async () => {
|
|
197
211
|
function mountThree(attrs = ''): { view: AdwTabView; host: HTMLElement } {
|
|
198
212
|
const host = document.createElement('div');
|
|
199
213
|
host.innerHTML = `<adw-tab-view ${attrs}>
|
|
@@ -205,38 +219,92 @@ export const AdwTabViewConformanceTest = async () => {
|
|
|
205
219
|
return { view: host.querySelector('adw-tab-view') as AdwTabView, host };
|
|
206
220
|
}
|
|
207
221
|
|
|
208
|
-
await it('selects the declared
|
|
209
|
-
const { view, host } = mountThree('selected="
|
|
222
|
+
await it('selects the declared PAGE instead of the auto-pick', () => {
|
|
223
|
+
const { view, host } = mountThree('selected-page="c"');
|
|
210
224
|
expect(view.selectedIndex).toBe(2);
|
|
225
|
+
expect(view.selectedId).toBe('c');
|
|
211
226
|
expect(shownPanels(view)).toStrictEqual([false, false, true]);
|
|
212
227
|
host.remove();
|
|
213
228
|
});
|
|
214
229
|
|
|
215
|
-
await it('IGNORES an
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
//
|
|
230
|
+
await it('IGNORES an id no page holds instead of falling back to the first', () => {
|
|
231
|
+
// The ordinal spellings are just unknown ids now (ADR 0048), and a fallback to
|
|
232
|
+
// page 0 would be the same wrong answer clamping used to give: libadwaita
|
|
233
|
+
// refuses a foreign `AdwTabPage` outright.
|
|
219
234
|
const { view, host } = mountThree();
|
|
220
235
|
for (const value of ['99', '-1', 'oops']) {
|
|
221
|
-
view.setAttribute('selected', value);
|
|
236
|
+
view.setAttribute('selected-page', value);
|
|
222
237
|
expect(view.selectedIndex).toBe(0);
|
|
223
238
|
}
|
|
224
239
|
//...and the attribute is put back in sync rather than left lying.
|
|
225
|
-
view.setAttribute('selected', '
|
|
240
|
+
view.setAttribute('selected-page', 'b');
|
|
226
241
|
expect(view.selectedIndex).toBe(1);
|
|
227
|
-
expect(view.getAttribute('selected')).toBe(
|
|
242
|
+
expect(view.getAttribute('selected-page')).toBe(view.pages[1]!.id);
|
|
243
|
+
host.remove();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
await it('REFUSES a page belonging to ANOTHER view, and RECORDS it', () => {
|
|
247
|
+
// `page_belongs_to_this_view`. An id is only unique WITHIN a view — declared ones
|
|
248
|
+
// are the author's to repeat and `_nextId` counts per element, so two views both
|
|
249
|
+
// hold a `tab-1`. Both views here hold 'a'/'b'/'c', which is the collision.
|
|
250
|
+
const first = mountThree();
|
|
251
|
+
const second = mountThree();
|
|
252
|
+
second.view.selectedPage = second.view.pages[2]!;
|
|
253
|
+
expect(second.view.selectedIndex).toBe(2);
|
|
254
|
+
|
|
255
|
+
first.view.selectedPage = second.view.pages[2]!;
|
|
256
|
+
expect(first.view.selectedIndex).toBe(0);
|
|
257
|
+
expect(second.view.selectedIndex).toBe(2);
|
|
258
|
+
// The refusal carries C's own assertion. It did NOT while the check was a
|
|
259
|
+
// renderer-side guard returning in silence, which is why the check moved into
|
|
260
|
+
// `TabViewState` — the one place that owns the page list.
|
|
261
|
+
expect(first.view.diagnostics).toStrictEqual([
|
|
262
|
+
"adw_tab_view_set_selected_page: assertion 'page_belongs_to_this_view (self, selected_page)' failed",
|
|
263
|
+
]);
|
|
264
|
+
expect(second.view.diagnostics).toStrictEqual([]);
|
|
265
|
+
|
|
266
|
+
// ...and this view's OWN page of that same id is accepted, so what was refused
|
|
267
|
+
// is the IDENTITY and not the id.
|
|
268
|
+
first.view.selectedPage = first.view.pages[2]!;
|
|
269
|
+
expect(first.view.selectedIndex).toBe(2);
|
|
270
|
+
first.host.remove();
|
|
271
|
+
second.host.remove();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
await it('RECORDS the refusal, where the ordinal door recorded nothing', () => {
|
|
275
|
+
// The behaviour change ADR 0048 does not name: `selected="99"` went through
|
|
276
|
+
// `selectNthPage`, which refuses an out-of-range index SILENTLY, while an
|
|
277
|
+
// unknown id goes through `setSelectedPage`, which is where C's
|
|
278
|
+
// `page_belongs_to_this_view` assertion lives. Three refusals, three records —
|
|
279
|
+
// if this list stays empty the element is swallowing the typo the reader made.
|
|
280
|
+
const { view, host } = mountThree();
|
|
281
|
+
expect(view.diagnostics).toStrictEqual([]);
|
|
282
|
+
for (const value of ['99', 'oops']) view.setAttribute('selected-page', value);
|
|
283
|
+
expect(view.diagnostics).toStrictEqual([
|
|
284
|
+
"adw_tab_view_set_selected_page: assertion 'page_belongs_to_this_view (self, selected_page)' failed",
|
|
285
|
+
"adw_tab_view_set_selected_page: assertion 'page_belongs_to_this_view (self, selected_page)' failed",
|
|
286
|
+
]);
|
|
287
|
+
|
|
288
|
+
// ...and `null` while pages exist is the OTHER half of the same C assertion,
|
|
289
|
+
// which `view.selected = -1` also used to reach in silence.
|
|
290
|
+
view.selectedPage = null;
|
|
291
|
+
expect(view.selectedIndex).toBe(0);
|
|
292
|
+
expect(view.diagnostics).toHaveLength(3);
|
|
293
|
+
expect(view.diagnostics[2]).toBe(
|
|
294
|
+
"adw_tab_view_set_selected_page: assertion 'ADW_IS_TAB_PAGE (selected_page)' failed",
|
|
295
|
+
);
|
|
228
296
|
host.remove();
|
|
229
297
|
});
|
|
230
298
|
|
|
231
299
|
await it('notifies on a PROGRAMMATIC selection, not only on a click', () => {
|
|
232
|
-
// C notifies on EVERY path: a programmatic `view.
|
|
233
|
-
// a tab click does.
|
|
300
|
+
// C notifies on EVERY path: a programmatic `view.selectedPage = …` must notify
|
|
301
|
+
// as a tab click does.
|
|
234
302
|
const { view, host } = mountThree();
|
|
235
303
|
const seen: number[] = [];
|
|
236
304
|
view.addEventListener('notify::selected-page', (event) =>
|
|
237
305
|
seen.push((event as CustomEvent).detail.selected),
|
|
238
306
|
);
|
|
239
|
-
view.
|
|
307
|
+
view.selectedPage = view.pages[2]!;
|
|
240
308
|
expect(seen).toStrictEqual([2]);
|
|
241
309
|
host.remove();
|
|
242
310
|
});
|
|
@@ -245,11 +313,11 @@ export const AdwTabViewConformanceTest = async () => {
|
|
|
245
313
|
const { view, host } = mountThree();
|
|
246
314
|
const chips = tabChips(view);
|
|
247
315
|
chips[1]!.click();
|
|
248
|
-
expect(view.getAttribute('selected')).toBe(
|
|
316
|
+
expect(view.getAttribute('selected-page')).toBe(view.pages[1]!.id);
|
|
249
317
|
|
|
250
318
|
for (const id of ['a', 'b', 'c']) view.closePage(id);
|
|
251
319
|
expect(view.nPages).toBe(0);
|
|
252
|
-
expect(view.hasAttribute('selected')).toBe(false);
|
|
320
|
+
expect(view.hasAttribute('selected-page')).toBe(false);
|
|
253
321
|
expect(view.selectedIndex).toBe(-1);
|
|
254
322
|
host.remove();
|
|
255
323
|
});
|
package/src/test.browser.mts
CHANGED
|
@@ -53,8 +53,10 @@ import { AdwEmptySectionsTest } from './empty-sections.spec.js';
|
|
|
53
53
|
import { AdwSlottedChildrenTest } from './slotted-children.spec.js';
|
|
54
54
|
import { AdwKeyboardOperableTest } from './keyboard-operable.spec.js';
|
|
55
55
|
import { AdwFontsTest } from './adw-fonts.spec.js';
|
|
56
|
+
import { AdwSharedTreesTest } from './shared-trees.spec.js';
|
|
56
57
|
|
|
57
58
|
run({
|
|
59
|
+
AdwSharedTreesTest,
|
|
58
60
|
AdwKeyboardOperableTest,
|
|
59
61
|
AdwConnectLifecycleTest,
|
|
60
62
|
AdwEmptySectionsTest,
|
package/src/view-stack.spec.ts
CHANGED
|
@@ -66,7 +66,9 @@ function mountStack(pages: readonly ViewStackVectorPage[], visibleChildName?: st
|
|
|
66
66
|
function applyOp(stack: AdwViewStack, op: ViewStackVectorOp): void {
|
|
67
67
|
switch (op.kind) {
|
|
68
68
|
case 'selectIndex':
|
|
69
|
-
|
|
69
|
+
// ADR 0048: the element has no ordinal SETTER any more; the ordinal is this
|
|
70
|
+
// call, and it forwards to the same core guard the vector pins.
|
|
71
|
+
stack.selectNthPage(op.index);
|
|
70
72
|
break;
|
|
71
73
|
case 'selectName':
|
|
72
74
|
stack.visibleChildName = op.name;
|
|
@@ -224,7 +226,7 @@ export const AdwViewStackTest = async () => {
|
|
|
224
226
|
await it('reflects the selection back, including an empty name', () => {
|
|
225
227
|
const { stack, host } = mountStack([{ name: 'a' }, { name: '' }]);
|
|
226
228
|
expect(stack.getAttribute('visible-child-name')).toBe('a');
|
|
227
|
-
stack.
|
|
229
|
+
stack.selectNthPage(1);
|
|
228
230
|
// Reflection guarded by `if (current && …)` never fires for nameless pages.
|
|
229
231
|
expect(stack.getAttribute('visible-child-name')).toBe('');
|
|
230
232
|
host.remove();
|
|
@@ -252,7 +254,7 @@ export const AdwViewStackTest = async () => {
|
|
|
252
254
|
// with duplicate names a naive re-lookup resolves to the FIRST match and
|
|
253
255
|
// undoes the selection.
|
|
254
256
|
const { stack, host } = mountStack([{ name: 'dup' }, { name: 'dup' }]);
|
|
255
|
-
stack.
|
|
257
|
+
stack.selectNthPage(1);
|
|
256
258
|
expect(stack.visibleChildIndex).toBe(1);
|
|
257
259
|
expect(stack.getAttribute('visible-child-name')).toBe('dup');
|
|
258
260
|
host.remove();
|