@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,35 +1,85 @@
|
|
|
1
1
|
// <adw-overlay-split-view> — Responsive sidebar that docks or overlays content.
|
|
2
2
|
// Reference: Adw.OverlaySplitView from libadwaita.
|
|
3
3
|
// Docs: refs/adwaita-web/adwaita-web/docs/widgets/overlaysplitview.md
|
|
4
|
+
//
|
|
5
|
+
// `breakpoint` takes an Adwaita condition (e.g. "max-width: 720px") and drives
|
|
6
|
+
// `collapsed` from the element's own box — the browser counterpart to an
|
|
7
|
+
// Adw.Breakpoint with an add_setter(), so the sidebar overlays on narrow widths
|
|
8
|
+
// without the application wiring a media query itself.
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_MAX_SIDEBAR_WIDTH,
|
|
12
|
+
DEFAULT_MIN_SIDEBAR_WIDTH,
|
|
13
|
+
DEFAULT_SIDEBAR_WIDTH_FRACTION,
|
|
14
|
+
OverlaySplitViewState,
|
|
15
|
+
resolveSidebarBounds,
|
|
16
|
+
type AdwPackType,
|
|
17
|
+
} from '@gjsify/adwaita-core';
|
|
18
|
+
|
|
19
|
+
import { bindBreakpointSetter } from '../breakpoints.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Read a boolean attribute the way a GTK property with a TRUE default has to be
|
|
23
|
+
* read: absence means the default, not false.
|
|
24
|
+
*
|
|
25
|
+
* HTML boolean attributes are presence-only, which can only ever express a FALSE
|
|
26
|
+
* default — so `show-sidebar` used to start hidden while `Adw.OverlaySplitView`
|
|
27
|
+
* starts shown (adw-overlay-split-view.c:974-976). `show-sidebar="false"` is the
|
|
28
|
+
* explicit off switch.
|
|
29
|
+
*/
|
|
30
|
+
function readTriStateAttr(element: Element, name: string, fallback: boolean): boolean {
|
|
31
|
+
const raw = element.getAttribute(name);
|
|
32
|
+
if (raw === null) return fallback;
|
|
33
|
+
return raw !== 'false' && raw !== '0';
|
|
34
|
+
}
|
|
4
35
|
|
|
5
36
|
export class AdwOverlaySplitView extends HTMLElement {
|
|
6
37
|
private _initialized = false;
|
|
7
38
|
private _sidebarEl!: HTMLDivElement;
|
|
8
39
|
private _contentEl!: HTMLDivElement;
|
|
9
40
|
private _backdropEl!: HTMLDivElement;
|
|
41
|
+
private _disposeBreakpoint: (() => void) | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* The single source of truth for the property interplay, shared with the
|
|
44
|
+
* NativeScript renderer (ADR 0004). The element owns the attributes and the
|
|
45
|
+
* painting; every rule about what `collapsed` + `show-sidebar` + `pin-sidebar`
|
|
46
|
+
* MEAN together lives in core, held to `OVERLAY_COLLAPSE_VECTORS`.
|
|
47
|
+
*/
|
|
48
|
+
private _state = new OverlaySplitViewState();
|
|
49
|
+
/** Re-entrancy guard for {@link _reflectShowSidebar}. */
|
|
50
|
+
private _reflecting = false;
|
|
10
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Every attribute {@link _readAttribute} handles must be listed here — an
|
|
54
|
+
* unobserved attribute never reaches `attributeChangedCallback`, so its branch
|
|
55
|
+
* is DEAD after construction. `pin-sidebar` was missing, which made the
|
|
56
|
+
* property that suppresses the collapse coupling settable only in the markup
|
|
57
|
+
* a parser saw: `view.setAttribute('pin-sidebar', '')` did nothing at all.
|
|
58
|
+
*/
|
|
11
59
|
static get observedAttributes() {
|
|
12
60
|
return [
|
|
13
61
|
'show-sidebar',
|
|
14
62
|
'collapsed',
|
|
63
|
+
'pin-sidebar',
|
|
15
64
|
'sidebar-position',
|
|
16
65
|
'min-sidebar-width',
|
|
17
66
|
'max-sidebar-width',
|
|
18
67
|
'sidebar-width-fraction',
|
|
68
|
+
'breakpoint',
|
|
19
69
|
];
|
|
20
70
|
}
|
|
21
71
|
|
|
22
72
|
get showSidebar(): boolean {
|
|
23
|
-
return this.
|
|
73
|
+
return this._state.showSidebar;
|
|
24
74
|
}
|
|
25
75
|
|
|
26
76
|
set showSidebar(v: boolean) {
|
|
27
|
-
|
|
28
|
-
|
|
77
|
+
// `false` has to be spelled out — absence means the libadwaita default.
|
|
78
|
+
this.setAttribute('show-sidebar', v ? '' : 'false');
|
|
29
79
|
}
|
|
30
80
|
|
|
31
81
|
get collapsed(): boolean {
|
|
32
|
-
return this.
|
|
82
|
+
return this._state.collapsed;
|
|
33
83
|
}
|
|
34
84
|
|
|
35
85
|
set collapsed(v: boolean) {
|
|
@@ -37,16 +87,23 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
37
87
|
else this.removeAttribute('collapsed');
|
|
38
88
|
}
|
|
39
89
|
|
|
90
|
+
// The three defaults come from the widget, not from an app's taste: 180 /
|
|
91
|
+
// 280 / 0.25 (adw-overlay-split-view.c:1036-1075). They used to be 280 /
|
|
92
|
+
// 400 / 0.30 here, which are the values the three.js and canvas2d showcases
|
|
93
|
+
// set EXPLICITLY in their `.blp` — so an app's preference had been copied
|
|
94
|
+
// into the widget. The visible cost was in the storybook, whose whole
|
|
95
|
+
// purpose is a 1:1 comparison: the GTK story takes the real defaults and the
|
|
96
|
+
// browser story took these, so the two sides showed different sidebars.
|
|
40
97
|
get minSidebarWidth(): number {
|
|
41
|
-
return parseFloat(this.getAttribute('min-sidebar-width') ||
|
|
98
|
+
return parseFloat(this.getAttribute('min-sidebar-width') || String(DEFAULT_MIN_SIDEBAR_WIDTH));
|
|
42
99
|
}
|
|
43
100
|
|
|
44
101
|
get maxSidebarWidth(): number {
|
|
45
|
-
return parseFloat(this.getAttribute('max-sidebar-width') ||
|
|
102
|
+
return parseFloat(this.getAttribute('max-sidebar-width') || String(DEFAULT_MAX_SIDEBAR_WIDTH));
|
|
46
103
|
}
|
|
47
104
|
|
|
48
105
|
get sidebarWidthFraction(): number {
|
|
49
|
-
return parseFloat(this.getAttribute('sidebar-width-fraction') ||
|
|
106
|
+
return parseFloat(this.getAttribute('sidebar-width-fraction') || String(DEFAULT_SIDEBAR_WIDTH_FRACTION));
|
|
50
107
|
}
|
|
51
108
|
|
|
52
109
|
connectedCallback() {
|
|
@@ -83,18 +140,95 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
83
140
|
this.append(this._sidebarEl, this._contentEl, this._backdropEl);
|
|
84
141
|
|
|
85
142
|
// Apply initial state
|
|
143
|
+
// The attributes present at parse time ARE the construction options —
|
|
144
|
+
// `Adw.OverlaySplitView` is built with its properties, it is not built
|
|
145
|
+
// and then mutated. Applying them as sequential setters instead would
|
|
146
|
+
// fire the collapse transition and auto-hide a sidebar that the markup
|
|
147
|
+
// explicitly asked to keep shown.
|
|
148
|
+
this._state = new OverlaySplitViewState({
|
|
149
|
+
collapsed: this.hasAttribute('collapsed'),
|
|
150
|
+
showSidebar: readTriStateAttr(this, 'show-sidebar', true),
|
|
151
|
+
pinSidebar: this.hasAttribute('pin-sidebar'),
|
|
152
|
+
sidebarPosition: this.getAttribute('sidebar-position') === 'end' ? 'end' : ('start' as AdwPackType),
|
|
153
|
+
});
|
|
154
|
+
this._reflectShowSidebar();
|
|
86
155
|
this._syncClasses();
|
|
87
156
|
this._syncSidebarWidth();
|
|
157
|
+
this._syncBreakpoint();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
disconnectedCallback() {
|
|
161
|
+
this._disposeBreakpoint?.();
|
|
162
|
+
this._disposeBreakpoint = undefined;
|
|
88
163
|
}
|
|
89
164
|
|
|
90
165
|
attributeChangedCallback(_name: string, _old: string | null, _val: string | null) {
|
|
91
166
|
if (!this._initialized) return;
|
|
167
|
+
if (_name === 'breakpoint') {
|
|
168
|
+
this._syncBreakpoint();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this._readAttribute(_name);
|
|
92
172
|
this._syncClasses();
|
|
93
173
|
if (_name === 'min-sidebar-width' || _name === 'max-sidebar-width' || _name === 'sidebar-width-fraction') {
|
|
94
174
|
this._syncSidebarWidth();
|
|
95
175
|
}
|
|
96
176
|
}
|
|
97
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Push ONE attribute into the state, then reflect back what the state made
|
|
180
|
+
* of it.
|
|
181
|
+
*
|
|
182
|
+
* Per-attribute rather than a re-read of all of them, because `show-sidebar`
|
|
183
|
+
* is a value the state ALSO owns: collapsing auto-hides an unpinned sidebar,
|
|
184
|
+
* and a blanket re-read would immediately clobber that decision with the
|
|
185
|
+
* stale attribute.
|
|
186
|
+
*/
|
|
187
|
+
private _readAttribute(name: string) {
|
|
188
|
+
const state = this._state;
|
|
189
|
+
// `pin-sidebar` defaults FALSE (adw-overlay-split-view.c:990-992), so it
|
|
190
|
+
// is an ordinary presence attribute; `show-sidebar` below is the one that
|
|
191
|
+
// cannot be, and `sidebar-position` is an enum defaulting to 'start'.
|
|
192
|
+
if (name === 'pin-sidebar') state.setPinSidebar(this.hasAttribute('pin-sidebar'));
|
|
193
|
+
if (name === 'sidebar-position') {
|
|
194
|
+
const position = this.getAttribute('sidebar-position');
|
|
195
|
+
state.setSidebarPosition(position === 'end' ? 'end' : ('start' as AdwPackType));
|
|
196
|
+
}
|
|
197
|
+
if (name === 'show-sidebar') {
|
|
198
|
+
state.setShowSidebar(readTriStateAttr(this, 'show-sidebar', true));
|
|
199
|
+
}
|
|
200
|
+
// Last: it can change `show-sidebar`, so it must not be overwritten after.
|
|
201
|
+
if (name === 'collapsed') state.setCollapsed(this.hasAttribute('collapsed'));
|
|
202
|
+
this._reflectShowSidebar();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Mirror the state's `showSidebar` back onto the attribute, so the DOM keeps
|
|
207
|
+
* telling the truth after an auto-hide.
|
|
208
|
+
*
|
|
209
|
+
* Guarded: writing the attribute re-enters `attributeChangedCallback`, and
|
|
210
|
+
* without the flag that is an infinite reflection loop rather than a render.
|
|
211
|
+
*/
|
|
212
|
+
private _reflectShowSidebar() {
|
|
213
|
+
if (this._reflecting) return;
|
|
214
|
+
const wanted = this._state.showSidebar ? '' : 'false';
|
|
215
|
+
if (this.getAttribute('show-sidebar') === wanted) return;
|
|
216
|
+
this._reflecting = true;
|
|
217
|
+
try {
|
|
218
|
+
this.setAttribute('show-sidebar', wanted);
|
|
219
|
+
} finally {
|
|
220
|
+
this._reflecting = false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** (Re)bind the `breakpoint` condition to `collapsed`. */
|
|
225
|
+
private _syncBreakpoint() {
|
|
226
|
+
this._disposeBreakpoint?.();
|
|
227
|
+
this._disposeBreakpoint = bindBreakpointSetter(this, this.getAttribute('breakpoint'), (active) => {
|
|
228
|
+
this.collapsed = active;
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
98
232
|
openSidebar() {
|
|
99
233
|
this.showSidebar = true;
|
|
100
234
|
this.dispatchEvent(new CustomEvent('sidebar-toggled', { detail: { isVisible: true } }));
|
|
@@ -115,11 +249,18 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
115
249
|
}
|
|
116
250
|
|
|
117
251
|
private _syncClasses() {
|
|
118
|
-
|
|
119
|
-
this.classList.toggle('
|
|
120
|
-
|
|
121
|
-
this.classList.toggle('sidebar-start',
|
|
122
|
-
this.classList.toggle('sidebar-end',
|
|
252
|
+
const state = this._state;
|
|
253
|
+
this.classList.toggle('collapsed', state.collapsed);
|
|
254
|
+
this.classList.toggle('show-sidebar', state.showSidebar);
|
|
255
|
+
this.classList.toggle('sidebar-start', state.sidebarPosition === 'start');
|
|
256
|
+
this.classList.toggle('sidebar-end', state.sidebarPosition === 'end');
|
|
257
|
+
|
|
258
|
+
// Derived, not re-decided here: the shield only exists while collapsed
|
|
259
|
+
// AND revealed, and each pane is reachable by keyboard only when it is
|
|
260
|
+
// the one on screen.
|
|
261
|
+
if (this._backdropEl) this._backdropEl.hidden = !state.shieldVisible;
|
|
262
|
+
this._sidebarEl?.setAttribute('aria-hidden', String(!state.sidebarFocusable));
|
|
263
|
+
this._contentEl?.setAttribute('aria-hidden', String(!state.contentFocusable));
|
|
123
264
|
|
|
124
265
|
// In docked mode, use negative margin to collapse sidebar space
|
|
125
266
|
// while keeping the sidebar's intrinsic width (slide animation).
|
|
@@ -135,8 +276,17 @@ export class AdwOverlaySplitView extends HTMLElement {
|
|
|
135
276
|
|
|
136
277
|
private _syncSidebarWidth() {
|
|
137
278
|
if (!this._sidebarEl) return;
|
|
138
|
-
|
|
139
|
-
|
|
279
|
+
// Normalise through the core rather than writing the raw attributes:
|
|
280
|
+
// libadwaita never lets `max` fall below `min`, and CSS resolves that
|
|
281
|
+
// conflict the other way round (min-width wins over max-width), so an
|
|
282
|
+
// inverted pair would render differently here than in GTK.
|
|
283
|
+
const bounds = resolveSidebarBounds(
|
|
284
|
+
{ minSidebarWidth: this.minSidebarWidth, maxSidebarWidth: this.maxSidebarWidth },
|
|
285
|
+
0,
|
|
286
|
+
{ ceil: true },
|
|
287
|
+
);
|
|
288
|
+
this._sidebarEl.style.minWidth = `${bounds.min}px`;
|
|
289
|
+
this._sidebarEl.style.maxWidth = `${bounds.max}px`;
|
|
140
290
|
this._sidebarEl.style.width = `${this.sidebarWidthFraction * 100}%`;
|
|
141
291
|
}
|
|
142
292
|
}
|
|
@@ -1,120 +1,142 @@
|
|
|
1
|
-
// <adw-password-entry-row> —
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
1
|
+
// <adw-password-entry-row> — An entry row tailored for secrets: the value is
|
|
2
|
+
// masked, a trailing peek button reveals it, and a warning shows while Caps Lock
|
|
3
|
+
// is engaged.
|
|
4
|
+
//
|
|
5
|
+
// It now EXTENDS `<adw-entry-row>`, as `AdwPasswordEntryRow` extends
|
|
6
|
+
// `AdwEntryRow` in the C (adw-password-entry-row.c:50). This file used to be a
|
|
7
|
+
// near copy-paste of `adw-entry-row.ts` — a normalized diff showed the
|
|
8
|
+
// connectedCallback and attributeChangedCallback bodies were the same code — so
|
|
9
|
+
// every entry-row fix had to be made twice and one of the two always drifted.
|
|
10
|
+
// What is left here is only what the C subclass adds: the masked input, the peek
|
|
11
|
+
// toggle, and the caps-lock source.
|
|
12
|
+
//
|
|
13
|
+
// The reveal/caps-lock DERIVATION is HEADLESS and lives in
|
|
14
|
+
// `@gjsify/adwaita-core` (ADR 0004) as {@link PasswordEntryRowState}, which
|
|
15
|
+
// composes the parent's `EntryRowState` exactly like the C drives its
|
|
16
|
+
// parent through `adw_entry_row_set_show_indicator`. Two rules fall out of it
|
|
17
|
+
// that neither port had: peeking suppresses the caps-lock warning, and so does
|
|
18
|
+
// losing focus.
|
|
19
|
+
//
|
|
20
|
+
// Attributes: everything `<adw-entry-row>` observes, plus `revealed`.
|
|
21
|
+
// Properties: `revealed`, `capsLockOn` (+ everything inherited).
|
|
22
|
+
// Events: `notify::revealed` — only on a REAL change. The old implementation
|
|
23
|
+
// fired it on every `setAttribute`, including a redundant one, because
|
|
24
|
+
// attributeChangedCallback runs for a same-value write; the core's setter
|
|
25
|
+
// guard (the house rule at adw-entry-row.c:984/:1198/:1247) is what fixes it.
|
|
26
|
+
//
|
|
27
|
+
// Reference: refs/libadwaita/src/adw-password-entry-row.c
|
|
10
28
|
// Reference: refs/adwaita-web/adwaita-web/docs/widgets/passwordentryrow.md
|
|
11
|
-
// Reference: refs/libadwaita/src/adw-password-entry-row.c (peek toggle: the
|
|
12
|
-
// view-reveal / view-conceal symbolic swap; CSS node `row` carries `.entry`
|
|
13
|
-
// and `.password` style classes).
|
|
14
29
|
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
15
30
|
// Modifications: Implemented as a Web Component for @gjsify/adwaita-web.
|
|
16
31
|
|
|
17
|
-
|
|
18
|
-
private _input!: HTMLInputElement;
|
|
19
|
-
private _titleEl!: HTMLSpanElement;
|
|
20
|
-
private _toggle!: HTMLButtonElement;
|
|
21
|
-
private _toggleIcon!: HTMLSpanElement;
|
|
22
|
-
private _initialized = false;
|
|
32
|
+
import { PasswordEntryRowState, type PasswordEntryRowRenderState } from '@gjsify/adwaita-core';
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
import { type AdwIcon, createAdwIcon } from './adw-icon.js';
|
|
35
|
+
|
|
36
|
+
import { AdwEntryRow } from './adw-entry-row.js';
|
|
37
|
+
|
|
38
|
+
export class AdwPasswordEntryRow extends AdwEntryRow {
|
|
39
|
+
/** The headless peek + caps-lock derivation, composing the parent row's state. */
|
|
40
|
+
private readonly _password = new PasswordEntryRowState(this._state);
|
|
41
|
+
private _toggle!: HTMLButtonElement;
|
|
42
|
+
private _toggleIcon!: AdwIcon;
|
|
27
43
|
|
|
28
|
-
get
|
|
29
|
-
return
|
|
44
|
+
static get observedAttributes(): string[] {
|
|
45
|
+
return [...AdwEntryRow.observedAttributes, 'revealed'];
|
|
30
46
|
}
|
|
31
47
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
48
|
+
// `_password_entry_row.scss` scopes its rules to `.adw-password-entry-row-*`,
|
|
49
|
+
// so the inherited DOM builder must name the parts that way.
|
|
50
|
+
protected override get _classPrefix(): string {
|
|
51
|
+
return 'adw-password-entry-row';
|
|
35
52
|
}
|
|
36
53
|
|
|
37
|
-
/** Whether the contents are shown in clear text (
|
|
54
|
+
/** Whether the contents are shown in clear text (`GtkText:visibility`). */
|
|
38
55
|
get revealed(): boolean {
|
|
39
|
-
return this.
|
|
56
|
+
return this._password.revealed;
|
|
40
57
|
}
|
|
41
58
|
|
|
42
59
|
set revealed(value: boolean) {
|
|
43
|
-
|
|
44
|
-
else this.removeAttribute('revealed');
|
|
60
|
+
this._password.setRevealed(value);
|
|
45
61
|
}
|
|
46
62
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this.
|
|
63
|
+
/** Whether Caps Lock is engaged, as last observed from a keyboard event. */
|
|
64
|
+
get capsLockOn(): boolean {
|
|
65
|
+
return this._password.capsLockOn;
|
|
66
|
+
}
|
|
50
67
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Feed the platform's Caps Lock state in — GDK gets it from the keyboard
|
|
70
|
+
* device (adw-password-entry-row.c:111-115). Public because a host that
|
|
71
|
+
* knows better than `KeyboardEvent.getModifierState` (a virtual keyboard, a
|
|
72
|
+
* test) can drive it directly.
|
|
73
|
+
*/
|
|
74
|
+
setCapsLockOn(on: boolean): void {
|
|
75
|
+
this._password.setCapsLockOn(on);
|
|
76
|
+
}
|
|
54
77
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// and starts with visibility off.
|
|
59
|
-
this._input.type = this.hasAttribute('revealed') ? 'text' : 'password';
|
|
60
|
-
this._input.value = this.getAttribute('text') ?? '';
|
|
78
|
+
protected override _onConnected(): void {
|
|
79
|
+
// `gtk_text_set_visibility (…, FALSE)` + GTK_INPUT_PURPOSE_PASSWORD (C:158-160).
|
|
80
|
+
this._input.type = 'password';
|
|
61
81
|
this._input.autocomplete = 'off';
|
|
62
|
-
this._input.addEventListener('input', () => {
|
|
63
|
-
this.dispatchEvent(new CustomEvent('changed', { bubbles: true, detail: { text: this._input.value } }));
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// Title floats as a small label above the masked value, both left-aligned
|
|
67
|
-
// in a column (the filled Adw.EntryRow look).
|
|
68
|
-
const text = document.createElement('div');
|
|
69
|
-
text.className = 'adw-password-entry-row-text';
|
|
70
|
-
text.append(this._titleEl, this._input);
|
|
71
82
|
|
|
72
|
-
// Trailing peek toggle — a flat icon button, vertically centred. Swaps
|
|
73
|
-
// between the reveal / conceal symbolics like the native widget.
|
|
74
83
|
this._toggle = document.createElement('button');
|
|
75
|
-
this._toggle.className = 'adw-button flat circular icon-only adw-password-entry-row-toggle';
|
|
76
84
|
this._toggle.type = 'button';
|
|
77
|
-
this.
|
|
78
|
-
this._toggleIcon
|
|
79
|
-
this._toggle.
|
|
80
|
-
this._toggle.addEventListener('click', () =>
|
|
81
|
-
|
|
85
|
+
this._toggle.className = `adw-button flat circular icon-only ${this._classPrefix}-toggle`;
|
|
86
|
+
this._toggleIcon = createAdwIcon(null);
|
|
87
|
+
this._toggle.append(this._toggleIcon);
|
|
88
|
+
this._toggle.addEventListener('click', () => this._password.togglePeek());
|
|
89
|
+
// C:152 — installed through add_suffix, so it is the FIRST suffix and a
|
|
90
|
+
// consumer's own suffix lands after it instead of replacing it.
|
|
91
|
+
this.addSuffix(this._toggle);
|
|
92
|
+
|
|
93
|
+
// GDK reads caps lock off the keyboard device; the browser exposes it
|
|
94
|
+
// only on keyboard events, so those are the source here.
|
|
95
|
+
const readCapsLock = (event: KeyboardEvent) => this._password.setCapsLockOn(event.getModifierState('CapsLock'));
|
|
96
|
+
this._input.addEventListener('keydown', readCapsLock);
|
|
97
|
+
this._input.addEventListener('keyup', readCapsLock);
|
|
98
|
+
|
|
99
|
+
// The indicator icon + tooltip are set once at init (C:169-171); their
|
|
100
|
+
// canonical names come from the core so both ports spell them alike.
|
|
101
|
+
const initial = this._password.state;
|
|
102
|
+
this.setIndicatorIconName(initial.indicatorIconName);
|
|
103
|
+
this.setIndicatorTooltip(initial.indicatorTooltip);
|
|
104
|
+
|
|
105
|
+
// Seed from the attribute BEFORE subscribing, so the first paint emits no
|
|
106
|
+
// `notify::revealed`.
|
|
107
|
+
if (this.hasAttribute('revealed')) this._password.setRevealed(this.getAttribute('revealed') !== 'false');
|
|
108
|
+
this._renderPassword(this._password.state);
|
|
109
|
+
this._password.subscribe((state) => {
|
|
110
|
+
this._renderPassword(state);
|
|
111
|
+
this.dispatchEvent(
|
|
112
|
+
new CustomEvent('notify::revealed', { bubbles: true, detail: { revealed: state.revealed } }),
|
|
113
|
+
);
|
|
82
114
|
});
|
|
83
|
-
|
|
84
|
-
// Trailing edit pencil (before the peek toggle), mirroring native.
|
|
85
|
-
const edit = document.createElement('span');
|
|
86
|
-
edit.className = 'adw-row-edit adw-icon adw-icon--document-edit';
|
|
87
|
-
edit.setAttribute('aria-hidden', 'true');
|
|
88
|
-
edit.addEventListener('click', () => this._input.focus());
|
|
89
|
-
|
|
90
|
-
this.replaceChildren(text, edit, this._toggle);
|
|
91
|
-
this._renderRevealed();
|
|
92
115
|
}
|
|
93
116
|
|
|
94
|
-
attributeChangedCallback(name: string,
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
} else if (name === 'text') {
|
|
99
|
-
if (this._input.value !== (value ?? '')) this._input.value = value ?? '';
|
|
100
|
-
} else if (name === 'revealed') {
|
|
101
|
-
this._renderRevealed();
|
|
102
|
-
this.dispatchEvent(
|
|
103
|
-
new CustomEvent('notify::revealed', { bubbles: true, detail: { revealed: this.revealed } }),
|
|
104
|
-
);
|
|
117
|
+
override attributeChangedCallback(name: string, oldValue: string | null, value: string | null) {
|
|
118
|
+
if (name !== 'revealed') {
|
|
119
|
+
super.attributeChangedCallback(name, oldValue, value);
|
|
120
|
+
return;
|
|
105
121
|
}
|
|
122
|
+
if (!this._initialized) return;
|
|
123
|
+
// The core guard makes this idempotent, which is what keeps the reflection
|
|
124
|
+
// in `_renderPassword` from bouncing back as a second notification.
|
|
125
|
+
this._password.setRevealed(value !== null && value !== 'false');
|
|
106
126
|
}
|
|
107
127
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
this._input.type = revealed ? 'text' : 'password';
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
this._toggleIcon.
|
|
114
|
-
|
|
115
|
-
this._toggle.title =
|
|
116
|
-
this._toggle.setAttribute('aria-label',
|
|
117
|
-
this._toggle.setAttribute('aria-pressed', String(revealed));
|
|
128
|
+
/** `notify_visibility_cb` (C:62-81), applied to the DOM. */
|
|
129
|
+
private _renderPassword(state: PasswordEntryRowRenderState): void {
|
|
130
|
+
this._input.type = state.revealed ? 'text' : 'password';
|
|
131
|
+
// The libadwaita name travels in `data-icon-name`; the mask class is the
|
|
132
|
+
// curated @gjsify/adwaita-icons spelling of the same symbolic.
|
|
133
|
+
this._toggleIcon.dataset.iconName = state.peekIconName;
|
|
134
|
+
this._toggleIcon.iconName = state.peekIconName;
|
|
135
|
+
this._toggle.title = state.peekLabel;
|
|
136
|
+
this._toggle.setAttribute('aria-label', state.peekLabel);
|
|
137
|
+
this._toggle.setAttribute('aria-pressed', String(state.revealed));
|
|
138
|
+
if (state.revealed) this.setAttribute('revealed', '');
|
|
139
|
+
else this.removeAttribute('revealed');
|
|
118
140
|
}
|
|
119
141
|
}
|
|
120
142
|
|