@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.
- package/dist/adwaita-web.css +786 -272
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/adw-about-dialog.spec.d.ts +1 -0
- package/lib/types/adw-action-rows.spec.d.ts +1 -0
- package/lib/types/adw-avatar.spec.d.ts +1 -0
- package/lib/types/adw-banner.spec.d.ts +1 -0
- package/lib/types/adw-button-content.spec.d.ts +1 -0
- package/lib/types/adw-carousel.spec.d.ts +1 -0
- package/lib/types/adw-checks.spec.d.ts +1 -0
- package/lib/types/adw-header-bar.spec.d.ts +1 -0
- package/lib/types/adw-icon.spec.d.ts +1 -0
- package/lib/types/adw-progress-bar.spec.d.ts +1 -0
- package/lib/types/adw-sidebar.spec.d.ts +1 -0
- package/lib/types/adw-switch.spec.d.ts +1 -0
- package/lib/types/adw-wrap-box.spec.d.ts +1 -0
- package/lib/types/bottom-sheet.spec.d.ts +1 -0
- package/lib/types/breakpoints.d.ts +22 -0
- package/lib/types/breakpoints.spec.d.ts +1 -0
- package/lib/types/chrome.spec.d.ts +1 -0
- package/lib/types/elements/adw-about-dialog.d.ts +47 -8
- package/lib/types/elements/adw-action-row.d.ts +35 -0
- package/lib/types/elements/adw-avatar.d.ts +11 -0
- package/lib/types/elements/adw-bottom-sheet.d.ts +33 -4
- package/lib/types/elements/adw-button-content.d.ts +12 -0
- package/lib/types/elements/adw-button-row.d.ts +10 -1
- package/lib/types/elements/adw-carousel.d.ts +111 -8
- package/lib/types/elements/adw-checks.d.ts +57 -0
- package/lib/types/elements/adw-clamp.d.ts +4 -1
- package/lib/types/elements/adw-data-grid.d.ts +11 -31
- package/lib/types/elements/adw-drop-down.d.ts +17 -20
- package/lib/types/elements/adw-entry-row.d.ts +93 -3
- package/lib/types/elements/adw-expander-row.d.ts +7 -1
- package/lib/types/elements/adw-header-bar.d.ts +15 -0
- package/lib/types/elements/adw-icon.d.ts +25 -0
- package/lib/types/elements/adw-inline-view-switcher.d.ts +37 -11
- package/lib/types/elements/adw-menu-button.d.ts +6 -6
- package/lib/types/elements/adw-navigation-split-view.d.ts +6 -0
- package/lib/types/elements/adw-navigation-view.d.ts +63 -20
- package/lib/types/elements/adw-overlay-split-view.d.ts +38 -0
- package/lib/types/elements/adw-password-entry-row.d.ts +19 -10
- package/lib/types/elements/adw-popover.d.ts +58 -0
- package/lib/types/elements/adw-preferences-dialog.d.ts +57 -2
- package/lib/types/elements/adw-preferences-group.d.ts +31 -0
- package/lib/types/elements/adw-progress-bar.d.ts +38 -0
- package/lib/types/elements/adw-sidebar.d.ts +37 -5
- package/lib/types/elements/adw-split-button.d.ts +53 -7
- package/lib/types/elements/adw-switch-row.d.ts +11 -2
- package/lib/types/elements/adw-switch.d.ts +15 -0
- package/lib/types/elements/adw-tab-view.d.ts +129 -12
- package/lib/types/elements/adw-toolbar-view.d.ts +5 -0
- package/lib/types/elements/adw-view-stack.d.ts +27 -19
- package/lib/types/elements/adw-view-switcher-bar.d.ts +23 -7
- package/lib/types/elements/adw-view-switcher.d.ts +22 -11
- package/lib/types/elements/adw-window-title.d.ts +3 -1
- package/lib/types/elements/view-switcher-dom.d.ts +44 -0
- package/lib/types/entry-rows.spec.d.ts +1 -0
- package/lib/types/index.d.ts +7 -0
- package/lib/types/navigation-view.spec.d.ts +1 -0
- package/lib/types/preferences-search.d.ts +20 -0
- package/lib/types/preferences.spec.d.ts +1 -0
- package/lib/types/split-button.spec.d.ts +1 -0
- package/lib/types/split-views.spec.d.ts +1 -0
- package/lib/types/styles.generated.d.ts +1 -1
- package/lib/types/tab-view.spec.d.ts +1 -0
- package/lib/types/view-stack.spec.d.ts +1 -0
- package/lib/types/view-switcher.spec.d.ts +1 -0
- package/package.json +6 -6
- package/scss/_banner.scss +13 -0
- package/scss/_bottom_sheet.scss +5 -6
- package/scss/_button.scss +3 -26
- package/scss/_button_content.scss +28 -0
- package/scss/_checks.scss +228 -0
- package/scss/_clamp.scss +9 -3
- package/scss/_data_grid.scss +15 -7
- package/scss/_drop_down.scss +12 -55
- package/scss/_expander_row.scss +5 -50
- package/scss/_icon.scss +51 -0
- package/scss/_icons.generated.scss +6 -0
- package/scss/_inline_view_switcher.scss +34 -0
- package/scss/_labels.scss +135 -0
- package/scss/_menu_button.scss +10 -70
- package/scss/_popover.scss +186 -0
- package/scss/_preferences.scss +29 -0
- package/scss/_preferences_dialog.scss +17 -0
- package/scss/_progress_bar.scss +161 -0
- package/scss/_reset.scss +18 -7
- package/scss/_spinner.scss +7 -3
- package/scss/_split_button.scss +6 -50
- package/scss/_switch.scss +90 -0
- package/scss/_switch_row.scss +6 -52
- package/scss/_tab_view.scss +64 -0
- package/scss/_theme.scss +10 -0
- package/scss/_toolbar_view.scss +74 -0
- package/scss/_variables.scss +34 -0
- package/scss/_view_switcher.scss +40 -0
- package/scss/_view_switcher_bar.scss +37 -0
- package/scss/_wrap_box.scss +56 -6
- package/scss/adwaita-skin.scss +23 -0
- package/src/adw-about-dialog.spec.ts +350 -0
- package/src/adw-action-rows.spec.ts +342 -0
- package/src/adw-avatar.spec.ts +139 -0
- package/src/adw-banner.spec.ts +152 -0
- package/src/adw-button-content.spec.ts +183 -0
- package/src/adw-button-row.spec.ts +12 -6
- package/src/adw-carousel.spec.ts +389 -0
- package/src/adw-checks.spec.ts +368 -0
- package/src/adw-drop-down.spec.ts +123 -0
- package/src/adw-header-bar.spec.ts +83 -0
- package/src/adw-icon.spec.ts +167 -0
- package/src/adw-progress-bar.spec.ts +173 -0
- package/src/adw-sidebar.spec.ts +332 -0
- package/src/adw-switch.spec.ts +242 -0
- package/src/adw-tab-view.spec.ts +12 -3
- package/src/adw-view-switcher-bar.spec.ts +61 -13
- package/src/adw-wrap-box.spec.ts +194 -0
- package/src/bottom-sheet.spec.ts +325 -0
- package/src/breakpoints.spec.ts +147 -0
- package/src/breakpoints.ts +67 -0
- package/src/chrome.spec.ts +301 -0
- package/src/elements/adw-about-dialog.ts +223 -163
- package/src/elements/adw-action-row.ts +120 -10
- package/src/elements/adw-avatar.ts +48 -54
- package/src/elements/adw-banner.ts +46 -10
- package/src/elements/adw-bottom-sheet.ts +145 -60
- package/src/elements/adw-button-content.ts +107 -33
- package/src/elements/adw-button-row.ts +61 -37
- package/src/elements/adw-button.ts +5 -7
- package/src/elements/adw-carousel.ts +467 -105
- package/src/elements/adw-checks.ts +299 -0
- package/src/elements/adw-clamp.ts +80 -10
- package/src/elements/adw-combo-row.ts +15 -7
- package/src/elements/adw-data-grid.ts +43 -91
- package/src/elements/adw-drop-down.ts +117 -128
- package/src/elements/adw-entry-row.ts +404 -36
- package/src/elements/adw-expander-row.ts +61 -29
- package/src/elements/adw-header-bar.ts +31 -6
- package/src/elements/adw-icon.ts +148 -0
- package/src/elements/adw-inline-view-switcher.ts +240 -129
- package/src/elements/adw-menu-button.ts +93 -70
- package/src/elements/adw-navigation-split-view.ts +48 -7
- package/src/elements/adw-navigation-view.ts +286 -148
- package/src/elements/adw-overlay-split-view.ts +164 -14
- package/src/elements/adw-password-entry-row.ts +111 -89
- package/src/elements/adw-popover.ts +283 -0
- package/src/elements/adw-preferences-dialog.ts +120 -21
- package/src/elements/adw-preferences-group.ts +128 -22
- package/src/elements/adw-progress-bar.ts +189 -0
- package/src/elements/adw-sidebar.ts +301 -142
- package/src/elements/adw-spin-row.ts +11 -5
- package/src/elements/adw-spinner.ts +23 -8
- package/src/elements/adw-split-button.ts +371 -92
- package/src/elements/adw-status-page.ts +9 -9
- package/src/elements/adw-switch-row.ts +100 -30
- package/src/elements/adw-switch.ts +129 -0
- package/src/elements/adw-tab-view.ts +682 -129
- package/src/elements/adw-toggle-group.ts +5 -7
- package/src/elements/adw-toolbar-view.ts +84 -21
- package/src/elements/adw-view-stack.ts +162 -85
- package/src/elements/adw-view-switcher-bar.ts +163 -81
- package/src/elements/adw-view-switcher.ts +231 -112
- package/src/elements/adw-window-title.ts +44 -11
- package/src/elements/adw-wrap-box.ts +131 -35
- package/src/elements/view-switcher-dom.ts +108 -0
- package/src/entry-rows.spec.ts +391 -0
- package/src/index.ts +23 -0
- package/src/navigation-view.spec.ts +435 -0
- package/src/preferences-search.ts +135 -0
- package/src/preferences.spec.ts +250 -0
- package/src/source-view/adw-source-view.ts +2 -5
- package/src/split-button.spec.ts +470 -0
- package/src/split-views.spec.ts +309 -0
- package/src/styles.generated.ts +1 -1
- package/src/tab-view.spec.ts +451 -0
- package/src/test.browser.mts +49 -0
- package/src/view-stack.spec.ts +272 -0
- package/src/view-switcher.spec.ts +446 -0
|
@@ -1,79 +1,159 @@
|
|
|
1
1
|
// <adw-inline-view-switcher> — A compact, toolbar-friendly view switcher (the
|
|
2
|
-
// web counterpart of Adw.InlineViewSwitcher).
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
2
|
+
// web counterpart of Adw.InlineViewSwitcher). Each declared <adw-view-stack-page>
|
|
3
|
+
// child becomes a toggle in a toggle-group-style bar and a panel in the stack,
|
|
4
|
+
// and exactly one page is visible at a time.
|
|
5
|
+
//
|
|
6
|
+
// Everything that decides WHAT to show is HEADLESS and lives in
|
|
7
|
+
// `@gjsify/adwaita-core` (ADR 0004) — including the TWO INDEX SPACES this widget
|
|
8
|
+
// is built on: a hidden page produces NO toggle, so the compacted toggle index
|
|
9
|
+
// and the page index diverge, and libadwaita reconciles them through the
|
|
10
|
+
// `child-index` it stashes on every toggle (adw-inline-view-switcher.c:346,
|
|
11
|
+
// :114-129, :434-453). `buildInlineToggles` carries both indices, so this file
|
|
12
|
+
// never has to reconcile them itself.
|
|
13
|
+
//
|
|
14
|
+
// What the lift fixed here, all of it C-derived:
|
|
15
|
+
// - hidden pages were not filtered at all, so the two index spaces collapsed
|
|
16
|
+
// into one and `AdwViewStackPage:visible` had no spelling (:370-378).
|
|
17
|
+
// - the display-mode default was 'both'; libadwaita's is 'labels' (:657, :715).
|
|
18
|
+
// - an unrecognised display-mode value REPLACED the current mode with 'both';
|
|
19
|
+
// `g_return_if_fail (mode <= …_BOTH)` rejects it and keeps the current one
|
|
20
|
+
// (:819).
|
|
21
|
+
// - the icons-mode tooltip used the RAW title, so `title="_Files"` showed the
|
|
22
|
+
// underscore; C strips the mnemonic when use-underline is set (:180-188).
|
|
23
|
+
// - a page with no icon rendered no icon; C substitutes `image-missing`
|
|
24
|
+
// (:137-142) in every mode that builds an image at all.
|
|
25
|
+
// - `active` was CLAMPED into range instead of refused, `notify::display-mode`
|
|
26
|
+
// was documented but never dispatched, the page query was unscoped (so a
|
|
27
|
+
// nested switcher lost its pages to the outer one), and the 500 ms
|
|
28
|
+
// drag-hover auto-switch did not exist (:80, :194-236).
|
|
29
|
+
//
|
|
8
30
|
// Attributes:
|
|
9
|
-
// active
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
31
|
+
// active — zero-based index of the visible PAGE (not of the toggle). Refused,
|
|
32
|
+
// never clamped, and reflected back to what actually holds.
|
|
33
|
+
// display-mode — labels | icons | both (default 'labels'). An unrecognised
|
|
34
|
+
// value is rejected; removing the attribute restores the default.
|
|
35
|
+
// flat / round — the Adw.ToggleGroup style classes.
|
|
36
|
+
// Page attributes: name, title, icon-name, hidden (= AdwViewStackPage:visible),
|
|
37
|
+
// use-underline, badge-number, needs-attention.
|
|
13
38
|
// Events:
|
|
14
|
-
// `notify::active` (CustomEvent, bubbles,
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// Reference: refs/libadwaita/src/
|
|
20
|
-
// Reference: refs/libadwaita/src/stylesheet/widgets/_toggle-group.scss (inline-view-switcher > toggle-group)
|
|
21
|
-
// Reference: refs/adwaita-web/adwaita-web/scss/_viewswitcher.scss
|
|
39
|
+
// `notify::active` (CustomEvent, bubbles,
|
|
40
|
+
// detail = { active, name, toggleIndex, interactive }) on every selection
|
|
41
|
+
// change, including the auto-pick.
|
|
42
|
+
// `notify::display-mode` (CustomEvent, bubbles, detail = { displayMode }).
|
|
43
|
+
// Reference: refs/libadwaita/src/adw-inline-view-switcher.c (AdwInlineViewSwitcher)
|
|
44
|
+
// Reference: refs/libadwaita/src/stylesheet/widgets/_toggle-group.scss
|
|
22
45
|
// Copyright (c) 2023 Maximiliano Sandoval / 2024 GNOME Foundation Inc. (libadwaita). LGPLv2.1+.
|
|
23
46
|
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
24
47
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
48
|
+
import {
|
|
49
|
+
INLINE_VIEW_SWITCHER_DISPLAY_MODES,
|
|
50
|
+
VIEW_SWITCHER_NO_SELECTION,
|
|
51
|
+
ViewSwitcherState,
|
|
52
|
+
buildInlineToggles,
|
|
53
|
+
isInlineViewSwitcherDisplayMode,
|
|
54
|
+
toggleIndexForPage,
|
|
55
|
+
} from '@gjsify/adwaita-core';
|
|
56
|
+
import type {
|
|
57
|
+
AdwInlineViewSwitcherDisplayMode,
|
|
58
|
+
AdwViewSwitcherPage,
|
|
59
|
+
ViewSwitcherStateChange,
|
|
60
|
+
} from '@gjsify/adwaita-core';
|
|
61
|
+
|
|
62
|
+
import {
|
|
63
|
+
applyDescription,
|
|
64
|
+
applyIndicator,
|
|
65
|
+
createSwitcherIcon,
|
|
66
|
+
domViewSwitcherScheduler,
|
|
67
|
+
readSwitcherPage,
|
|
68
|
+
} from './view-switcher-dom.js';
|
|
28
69
|
|
|
29
70
|
/** A single page. Children of <adw-inline-view-switcher>; consumed at connect time. */
|
|
30
71
|
export class AdwViewStackPage extends HTMLElement {
|
|
31
72
|
static get observedAttributes() {
|
|
32
|
-
return ['title', 'icon-name'];
|
|
73
|
+
return ['name', 'title', 'icon-name', 'badge-number', 'needs-attention', 'use-underline'];
|
|
33
74
|
}
|
|
34
75
|
}
|
|
35
76
|
|
|
36
77
|
export class AdwInlineViewSwitcher extends HTMLElement {
|
|
78
|
+
private readonly _state = new ViewSwitcherState({ scheduler: domViewSwitcherScheduler });
|
|
37
79
|
private _groupEl!: HTMLDivElement;
|
|
38
80
|
private _pagesEl!: HTMLDivElement;
|
|
81
|
+
private _panels: HTMLDivElement[] = [];
|
|
39
82
|
private _toggles: HTMLButtonElement[] = [];
|
|
40
|
-
private
|
|
41
|
-
private _active = 0;
|
|
42
|
-
private _displayMode: DisplayMode = 'both';
|
|
83
|
+
private _displayMode: AdwInlineViewSwitcherDisplayMode = 'labels';
|
|
43
84
|
private _initialized = false;
|
|
44
85
|
|
|
45
86
|
static get observedAttributes() {
|
|
46
87
|
return ['active', 'display-mode', 'flat', 'round'];
|
|
47
88
|
}
|
|
48
89
|
|
|
49
|
-
|
|
90
|
+
constructor() {
|
|
91
|
+
super();
|
|
92
|
+
this._state.subscribe((change) => this._onStateChange(change));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Zero-based index of the visible PAGE, `-1` when nothing is selected. */
|
|
50
96
|
get active(): number {
|
|
51
|
-
return this.
|
|
97
|
+
return this._state.selected;
|
|
52
98
|
}
|
|
53
99
|
|
|
54
100
|
set active(value: number) {
|
|
55
|
-
this.
|
|
101
|
+
this._state.setSelected(value);
|
|
102
|
+
this._reflectActive();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Index of the ACTIVE TOGGLE, `-1` when the visible page has none because it
|
|
107
|
+
* is hidden — the port-safe `GTK_INVALID_LIST_POSITION`
|
|
108
|
+
* (adw-inline-view-switcher.c:441-446).
|
|
109
|
+
*/
|
|
110
|
+
get activeToggle(): number {
|
|
111
|
+
return toggleIndexForPage(this._state.pages, this._state.selected);
|
|
56
112
|
}
|
|
57
113
|
|
|
58
114
|
/** What the toggles display: 'labels', 'icons' or 'both'. */
|
|
59
|
-
get displayMode():
|
|
115
|
+
get displayMode(): AdwInlineViewSwitcherDisplayMode {
|
|
60
116
|
return this._displayMode;
|
|
61
117
|
}
|
|
62
118
|
|
|
63
|
-
set displayMode(value:
|
|
119
|
+
set displayMode(value: AdwInlineViewSwitcherDisplayMode) {
|
|
64
120
|
this.setAttribute('display-mode', value);
|
|
65
121
|
}
|
|
66
122
|
|
|
123
|
+
/** Name of the visible page, `''` when nothing is selected. */
|
|
124
|
+
get visibleChildName(): string {
|
|
125
|
+
return this._state.selectedName;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
set visibleChildName(name: string | null | undefined) {
|
|
129
|
+
this._state.selectName(name);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The resolved page records. */
|
|
133
|
+
get pages(): readonly AdwViewSwitcherPage[] {
|
|
134
|
+
return this._state.pages;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Show or hide a page. Returns whether the SELECTION moved. Rebuilds the
|
|
139
|
+
* toggle row, because a hidden page has no toggle at all — C reacts to a
|
|
140
|
+
* page's `notify::visible` with `recreate_toggles`
|
|
141
|
+
* (adw-inline-view-switcher.c:425, :471-477).
|
|
142
|
+
*/
|
|
143
|
+
setPageVisible(name: string, visible: boolean): boolean {
|
|
144
|
+
const moved = this._state.setPageVisible(name, visible);
|
|
145
|
+
this._rebuildToggles();
|
|
146
|
+
return moved;
|
|
147
|
+
}
|
|
148
|
+
|
|
67
149
|
connectedCallback() {
|
|
68
150
|
if (this._initialized) return;
|
|
69
|
-
this._initialized = true;
|
|
70
151
|
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
const
|
|
152
|
+
// `:scope >` rather than a subtree scan — `<adw-view-stack>` uses the
|
|
153
|
+
// same tag for its pages, so an unscoped query adopted a nested stack's.
|
|
154
|
+
const declared = Array.from(this.querySelectorAll(':scope > adw-view-stack-page')) as HTMLElement[];
|
|
155
|
+
const declaredActive = this.getAttribute('active');
|
|
75
156
|
|
|
76
|
-
// The switcher bar — a toggle-group of one toggle per page.
|
|
77
157
|
this._groupEl = document.createElement('div');
|
|
78
158
|
this._groupEl.className = 'adw-inline-view-switcher-group';
|
|
79
159
|
this._groupEl.setAttribute('role', 'tablist');
|
|
@@ -81,140 +161,171 @@ export class AdwInlineViewSwitcher extends HTMLElement {
|
|
|
81
161
|
this._pagesEl = document.createElement('div');
|
|
82
162
|
this._pagesEl.className = 'adw-inline-view-switcher-pages';
|
|
83
163
|
|
|
84
|
-
this.
|
|
85
|
-
this._pages = [];
|
|
86
|
-
|
|
87
|
-
pages.forEach((page, index) => {
|
|
88
|
-
const title = page.getAttribute('title') ?? '';
|
|
89
|
-
const icon = (page.getAttribute('icon-name') ?? '').replace(/-symbolic$/, '');
|
|
90
|
-
|
|
91
|
-
const toggle = document.createElement('button');
|
|
92
|
-
toggle.type = 'button';
|
|
93
|
-
toggle.className = 'adw-inline-view-switcher-toggle';
|
|
94
|
-
toggle.setAttribute('role', 'tab');
|
|
95
|
-
// The toggle stores both title + icon; _render() shows the parts the
|
|
96
|
-
// current display mode calls for (mirrors libadwaita rebuilding the
|
|
97
|
-
// toggle child on a display-mode change).
|
|
98
|
-
toggle.dataset.title = title;
|
|
99
|
-
toggle.dataset.icon = icon;
|
|
100
|
-
|
|
101
|
-
const iconEl = document.createElement('span');
|
|
102
|
-
iconEl.className = `adw-icon${icon ? ` adw-icon--${icon}` : ''} adw-inline-view-switcher-icon`;
|
|
103
|
-
iconEl.setAttribute('aria-hidden', 'true'); // decorative (mask-image only)
|
|
104
|
-
toggle.appendChild(iconEl);
|
|
105
|
-
|
|
106
|
-
const labelEl = document.createElement('span');
|
|
107
|
-
labelEl.className = 'adw-inline-view-switcher-label';
|
|
108
|
-
labelEl.textContent = title;
|
|
109
|
-
toggle.appendChild(labelEl);
|
|
110
|
-
|
|
111
|
-
toggle.addEventListener('click', () => this._selectIndex(index));
|
|
112
|
-
this._groupEl.appendChild(toggle);
|
|
113
|
-
this._toggles.push(toggle);
|
|
114
|
-
|
|
115
|
-
// The page content area — the declared element's children are moved
|
|
116
|
-
// into a panel that the switcher toggles visible.
|
|
164
|
+
this._panels = declared.map((pageEl) => {
|
|
117
165
|
const panel = document.createElement('div');
|
|
118
166
|
panel.className = 'adw-inline-view-switcher-page';
|
|
119
167
|
panel.setAttribute('role', 'tabpanel');
|
|
120
|
-
|
|
168
|
+
panel.dataset.name = pageEl.getAttribute('name') ?? '';
|
|
169
|
+
for (const child of Array.from(pageEl.childNodes)) panel.appendChild(child);
|
|
121
170
|
this._pagesEl.appendChild(panel);
|
|
122
|
-
|
|
171
|
+
return panel;
|
|
123
172
|
});
|
|
124
173
|
|
|
125
174
|
this.replaceChildren(this._groupEl, this._pagesEl);
|
|
126
175
|
|
|
127
|
-
this._active = this._readActiveAttr();
|
|
128
176
|
this._displayMode = this._readDisplayModeAttr();
|
|
129
|
-
this.
|
|
177
|
+
this._initialized = true;
|
|
178
|
+
|
|
179
|
+
this._state.setPages(declared.map((pageEl) => readSwitcherPage(pageEl)));
|
|
180
|
+
if (declaredActive !== null) {
|
|
181
|
+
const index = Number.parseInt(declaredActive, 10);
|
|
182
|
+
if (!Number.isNaN(index)) this._state.setSelected(index, false);
|
|
183
|
+
}
|
|
184
|
+
this._rebuildToggles();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
disconnectedCallback() {
|
|
188
|
+
this._state.cancelDrag();
|
|
130
189
|
}
|
|
131
190
|
|
|
132
191
|
attributeChangedCallback(name: string) {
|
|
133
192
|
if (!this._initialized) return;
|
|
134
193
|
if (name === 'active') {
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
194
|
+
const raw = this.getAttribute('active');
|
|
195
|
+
const index = raw === null ? Number.NaN : Number.parseInt(raw, 10);
|
|
196
|
+
if (!Number.isNaN(index)) this._state.setSelected(index);
|
|
197
|
+
this._reflectActive();
|
|
140
198
|
return;
|
|
141
199
|
}
|
|
142
200
|
if (name === 'display-mode') {
|
|
143
201
|
const next = this._readDisplayModeAttr();
|
|
144
|
-
if (next
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
202
|
+
if (next === this._displayMode) return;
|
|
203
|
+
this._displayMode = next;
|
|
204
|
+
// C rebuilds every toggle's child on a mode change rather than
|
|
205
|
+
// toggling visibility on the existing ones (:852-854).
|
|
206
|
+
this._rebuildToggles();
|
|
207
|
+
this.dispatchEvent(
|
|
208
|
+
new CustomEvent('notify::display-mode', { bubbles: true, detail: { displayMode: next } }),
|
|
209
|
+
);
|
|
148
210
|
return;
|
|
149
211
|
}
|
|
150
212
|
// flat / round are styling-only.
|
|
151
|
-
this.
|
|
213
|
+
this._applyStyleClasses();
|
|
152
214
|
}
|
|
153
215
|
|
|
154
|
-
private
|
|
155
|
-
const raw =
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
return Math.min(Math.max(raw, 0), max);
|
|
216
|
+
private _readDisplayModeAttr(): AdwInlineViewSwitcherDisplayMode {
|
|
217
|
+
const raw = this.getAttribute('display-mode');
|
|
218
|
+
if (raw === null) return 'labels';
|
|
219
|
+
return isInlineViewSwitcherDisplayMode(raw) ? raw : this._displayMode;
|
|
159
220
|
}
|
|
160
221
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Rebuild the toggle row from the derived models. One node per VISIBLE page,
|
|
224
|
+
* in compacted order — the structural half of `recreate_toggles`. Selection
|
|
225
|
+
* alone never lands here: `selection_changed_cb` only sets the active toggle
|
|
226
|
+
* (adw-inline-view-switcher.c:434-453), and rebuilding mid-drag would drop
|
|
227
|
+
* the dwell the drag has already armed.
|
|
228
|
+
*/
|
|
229
|
+
private _rebuildToggles(): void {
|
|
230
|
+
if (!this._initialized) return;
|
|
231
|
+
|
|
232
|
+
const models = buildInlineToggles(this._state.pages, this._displayMode);
|
|
233
|
+
this._groupEl.replaceChildren();
|
|
234
|
+
this._toggles = models.map((model) => {
|
|
235
|
+
const toggle = document.createElement('button');
|
|
236
|
+
toggle.type = 'button';
|
|
237
|
+
toggle.className = 'adw-inline-view-switcher-toggle';
|
|
238
|
+
toggle.setAttribute('role', 'tab');
|
|
239
|
+
// Both index spaces, so a consumer never has to recompute either.
|
|
240
|
+
toggle.dataset.pageIndex = String(model.pageIndex);
|
|
241
|
+
toggle.dataset.toggleIndex = String(model.toggleIndex);
|
|
242
|
+
|
|
243
|
+
if (model.showIcon) {
|
|
244
|
+
toggle.appendChild(createSwitcherIcon(model.iconName, 'adw-inline-view-switcher-icon'));
|
|
245
|
+
}
|
|
246
|
+
if (model.showLabel) {
|
|
247
|
+
const label = document.createElement('span');
|
|
248
|
+
label.className = 'adw-inline-view-switcher-label';
|
|
249
|
+
label.textContent = model.label;
|
|
250
|
+
toggle.appendChild(label);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const indicator = document.createElement('span');
|
|
254
|
+
indicator.className = 'adw-inline-view-switcher-indicator';
|
|
255
|
+
indicator.setAttribute('aria-hidden', 'true');
|
|
256
|
+
applyIndicator(indicator, model.badgeLabel, model.needsAttention);
|
|
257
|
+
toggle.appendChild(indicator);
|
|
258
|
+
|
|
259
|
+
if (model.tooltip) toggle.title = model.tooltip;
|
|
260
|
+
// An icon-only toggle has no visible text; the tooltip is also its
|
|
261
|
+
// accessible name, which is exactly what C's toggle does.
|
|
262
|
+
if (!model.showLabel) toggle.setAttribute('aria-label', model.tooltip || model.iconName);
|
|
263
|
+
applyDescription(toggle, model.description);
|
|
264
|
+
|
|
265
|
+
toggle.addEventListener('click', () => {
|
|
266
|
+
this._state.setSelected(model.pageIndex);
|
|
267
|
+
});
|
|
268
|
+
toggle.addEventListener('dragenter', () => this._state.dragEnter(model.pageIndex));
|
|
269
|
+
toggle.addEventListener('dragleave', () => this._state.dragLeave(model.pageIndex));
|
|
270
|
+
|
|
271
|
+
this._groupEl.appendChild(toggle);
|
|
272
|
+
return toggle;
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
this._render();
|
|
164
276
|
}
|
|
165
277
|
|
|
166
|
-
private
|
|
167
|
-
if (index === this._active) return;
|
|
168
|
-
this._active = index;
|
|
169
|
-
// Keep the attribute in sync without re-entering via the guarded
|
|
170
|
-
// attributeChangedCallback (value already matches, so it no-ops).
|
|
171
|
-
this.setAttribute('active', String(index));
|
|
278
|
+
private _onStateChange(change: ViewSwitcherStateChange): void {
|
|
172
279
|
this._render();
|
|
173
|
-
this.dispatchEvent(
|
|
280
|
+
this.dispatchEvent(
|
|
281
|
+
new CustomEvent('notify::active', {
|
|
282
|
+
bubbles: true,
|
|
283
|
+
detail: {
|
|
284
|
+
active: change.selected,
|
|
285
|
+
name: change.name,
|
|
286
|
+
toggleIndex: this.activeToggle,
|
|
287
|
+
interactive: change.interactive,
|
|
288
|
+
},
|
|
289
|
+
}),
|
|
290
|
+
);
|
|
174
291
|
}
|
|
175
292
|
|
|
176
293
|
private _render(): void {
|
|
177
|
-
|
|
178
|
-
this.classList.toggle('round', this.hasAttribute('round'));
|
|
179
|
-
|
|
180
|
-
// The bar carries the active display mode as a class — mirrors
|
|
181
|
-
// libadwaita's inline-view-switcher > toggle-group.{labels,icons,both}.
|
|
182
|
-
for (const mode of DISPLAY_MODES) this._groupEl.classList.toggle(mode, mode === this._displayMode);
|
|
294
|
+
if (!this._initialized) return;
|
|
183
295
|
|
|
184
|
-
|
|
185
|
-
const showLabel = this._displayMode !== 'icons';
|
|
296
|
+
this._applyStyleClasses();
|
|
186
297
|
|
|
187
|
-
|
|
188
|
-
|
|
298
|
+
const activeToggle = this.activeToggle;
|
|
299
|
+
this._toggles.forEach((toggle, toggleIndex) => {
|
|
300
|
+
const isActive = toggleIndex === activeToggle && activeToggle !== VIEW_SWITCHER_NO_SELECTION;
|
|
189
301
|
toggle.classList.toggle('active', isActive);
|
|
190
302
|
toggle.setAttribute('aria-selected', String(isActive));
|
|
191
303
|
toggle.tabIndex = isActive ? 0 : -1;
|
|
192
|
-
|
|
193
|
-
const iconEl = toggle.querySelector('.adw-inline-view-switcher-icon') as HTMLElement | null;
|
|
194
|
-
const labelEl = toggle.querySelector('.adw-inline-view-switcher-label') as HTMLElement | null;
|
|
195
|
-
const hasIcon = (toggle.dataset.icon ?? '').length > 0;
|
|
196
|
-
const title = toggle.dataset.title ?? '';
|
|
197
|
-
|
|
198
|
-
if (iconEl) iconEl.hidden = !showIcon || !hasIcon;
|
|
199
|
-
if (labelEl) labelEl.hidden = !showLabel || title.length === 0;
|
|
200
|
-
|
|
201
|
-
// In icons-only mode the toggle has no visible text — surface the
|
|
202
|
-
// title as an accessible name + a hover tooltip (the native widget
|
|
203
|
-
// sets the toggle tooltip to the page title in ICONS mode).
|
|
204
|
-
if (!showLabel && title) {
|
|
205
|
-
toggle.setAttribute('aria-label', title);
|
|
206
|
-
toggle.title = title;
|
|
207
|
-
} else {
|
|
208
|
-
toggle.removeAttribute('aria-label');
|
|
209
|
-
toggle.removeAttribute('title');
|
|
210
|
-
}
|
|
211
304
|
});
|
|
212
305
|
|
|
213
|
-
this.
|
|
214
|
-
|
|
306
|
+
const selected = this._state.selected;
|
|
307
|
+
this._panels.forEach((panel, index) => {
|
|
308
|
+
const isActive = index === selected;
|
|
215
309
|
panel.classList.toggle('active-view', isActive);
|
|
216
310
|
panel.hidden = !isActive;
|
|
217
311
|
});
|
|
312
|
+
|
|
313
|
+
this._reflectActive();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private _applyStyleClasses(): void {
|
|
317
|
+
this.classList.toggle('flat', this.hasAttribute('flat'));
|
|
318
|
+
this.classList.toggle('round', this.hasAttribute('round'));
|
|
319
|
+
// The group carries the display-mode class, mirroring libadwaita's
|
|
320
|
+
// inline-view-switcher > toggle-group.{labels,icons,both} (:826-850).
|
|
321
|
+
for (const mode of INLINE_VIEW_SWITCHER_DISPLAY_MODES) {
|
|
322
|
+
this._groupEl.classList.toggle(mode, mode === this._displayMode);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private _reflectActive(): void {
|
|
327
|
+
const current = String(this._state.selected);
|
|
328
|
+
if (this.getAttribute('active') !== current) this.setAttribute('active', current);
|
|
218
329
|
}
|
|
219
330
|
}
|
|
220
331
|
|