@gjsify/adwaita-web 0.1.3

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.
@@ -0,0 +1 @@
1
+ export declare const adwaitaCSS: string;
@@ -0,0 +1,11 @@
1
+ export declare class AdwComboRow extends HTMLElement {
2
+ private _select;
3
+ private _valueEl;
4
+ private _items;
5
+ private _initialized;
6
+ static get observedAttributes(): string[];
7
+ get selected(): number;
8
+ set selected(value: number);
9
+ connectedCallback(): void;
10
+ attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
11
+ }
@@ -0,0 +1,10 @@
1
+ export declare class AdwHeaderBar extends HTMLElement {
2
+ private _initialized;
3
+ private _startEl;
4
+ private _endEl;
5
+ /** The start (left) section container — append buttons/widgets here. */
6
+ get startSection(): HTMLDivElement | null;
7
+ /** The end (right) section container — append buttons/widgets here. */
8
+ get endSection(): HTMLDivElement | null;
9
+ connectedCallback(): void;
10
+ }
@@ -0,0 +1,21 @@
1
+ export declare class AdwOverlaySplitView extends HTMLElement {
2
+ private _initialized;
3
+ private _sidebarEl;
4
+ private _contentEl;
5
+ private _backdropEl;
6
+ static get observedAttributes(): string[];
7
+ get showSidebar(): boolean;
8
+ set showSidebar(v: boolean);
9
+ get collapsed(): boolean;
10
+ set collapsed(v: boolean);
11
+ get minSidebarWidth(): number;
12
+ get maxSidebarWidth(): number;
13
+ get sidebarWidthFraction(): number;
14
+ connectedCallback(): void;
15
+ attributeChangedCallback(_name: string, _old: string | null, _val: string | null): void;
16
+ openSidebar(): void;
17
+ hideSidebar(): void;
18
+ toggleSidebar(): void;
19
+ private _syncClasses;
20
+ private _syncSidebarWidth;
21
+ }
@@ -0,0 +1,4 @@
1
+ export declare class AdwPreferencesGroup extends HTMLElement {
2
+ private _initialized;
3
+ connectedCallback(): void;
4
+ }
@@ -0,0 +1,17 @@
1
+ export declare class AdwSpinRow extends HTMLElement {
2
+ private _input;
3
+ private _min;
4
+ private _max;
5
+ private _step;
6
+ private _value;
7
+ private _initialized;
8
+ static get observedAttributes(): string[];
9
+ get value(): number;
10
+ set value(v: number);
11
+ connectedCallback(): void;
12
+ attributeChangedCallback(name: string, _old: string | null, val: string | null): void;
13
+ private _adjust;
14
+ private _emitChange;
15
+ private _countDecimals;
16
+ private _formatValue;
17
+ }
@@ -0,0 +1,9 @@
1
+ export declare class AdwSwitchRow extends HTMLElement {
2
+ private _checkbox;
3
+ private _initialized;
4
+ static get observedAttributes(): string[];
5
+ get active(): boolean;
6
+ set active(value: boolean);
7
+ connectedCallback(): void;
8
+ attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
9
+ }
@@ -0,0 +1,8 @@
1
+ export declare class AdwToastOverlay extends HTMLElement {
2
+ /**
3
+ * Show a toast notification.
4
+ * @param title - The text to display.
5
+ * @param timeout - Time in ms before the toast auto-dismisses (default 2000).
6
+ */
7
+ addToast(title: string, timeout?: number): void;
8
+ }
@@ -0,0 +1,3 @@
1
+ export declare class AdwWindow extends HTMLElement {
2
+ connectedCallback(): void;
3
+ }
@@ -0,0 +1,9 @@
1
+ import '@gjsify/adwaita-fonts';
2
+ export { AdwWindow } from './elements/adw-window.js';
3
+ export { AdwHeaderBar } from './elements/adw-header-bar.js';
4
+ export { AdwPreferencesGroup } from './elements/adw-preferences-group.js';
5
+ export { AdwSwitchRow } from './elements/adw-switch-row.js';
6
+ export { AdwComboRow } from './elements/adw-combo-row.js';
7
+ export { AdwSpinRow } from './elements/adw-spin-row.js';
8
+ export { AdwToastOverlay } from './elements/adw-toast-overlay.js';
9
+ export { AdwOverlaySplitView } from './elements/adw-overlay-split-view.js';
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@gjsify/adwaita-web",
3
+ "version": "0.1.3",
4
+ "description": "Adwaita/Libadwaita web components for browser targets",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "types": "src/index.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/index.ts",
11
+ "default": "./src/index.ts"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "clear": "rm -rf lib tsconfig.tsbuildinfo || exit 0",
16
+ "check": "tsc --noEmit",
17
+ "build": "yarn build:gjsify && yarn build:types",
18
+ "build:gjsify": "gjsify build --library 'src/**/*.ts' --exclude 'src/**/*.spec.ts'",
19
+ "build:types": "tsc"
20
+ },
21
+ "dependencies": {
22
+ "@gjsify/adwaita-fonts": "^0.1.3",
23
+ "@gjsify/adwaita-icons": "^0.1.3"
24
+ },
25
+ "devDependencies": {
26
+ "@gjsify/cli": "^0.1.3",
27
+ "typescript": "^6.0.2"
28
+ }
29
+ }
@@ -0,0 +1,592 @@
1
+ // Adwaita CSS subset for browser targets.
2
+ // Colors from refs/libadwaita/src/stylesheet/_colors.scss (canonical).
3
+ // Component styles adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
4
+ // Copyright (c) GNOME contributors (libadwaita), LGPLv2.1+.
5
+ // Copyright (c) 2025 csm (adwaita-web). MIT License.
6
+ // Modifications: Extracted as CSS-in-JS for @gjsify/adwaita-web.
7
+
8
+ import { editPasteSymbolic, goDownSymbolic, sidebarShowSymbolic } from '@gjsify/adwaita-icons/actions';
9
+ import { toDataUri } from '@gjsify/adwaita-icons/utils';
10
+
11
+ const goDownDataUri = toDataUri(goDownSymbolic);
12
+ const editPasteDataUri = toDataUri(editPasteSymbolic);
13
+ const sidebarShowDataUri = toDataUri(sidebarShowSymbolic);
14
+
15
+ export const adwaitaCSS = `
16
+ /* ═══════════════════════════════════════════════════════════════
17
+ Adwaita CSS Custom Properties — Light Theme (default)
18
+ ═══════════════════════════════════════════════════════════════ */
19
+ :root {
20
+ /* Icons (Adwaita symbolic, encoded as data-URIs) */
21
+ --icon-edit-paste: ${editPasteDataUri};
22
+ --icon-go-down: ${goDownDataUri};
23
+ --icon-sidebar-show: ${sidebarShowDataUri};
24
+
25
+ /* Window */
26
+ --window-bg-color: #fafafb;
27
+ --window-fg-color: rgba(0, 0, 6, 0.8);
28
+
29
+ /* Views */
30
+ --view-bg-color: #ffffff;
31
+ --view-fg-color: rgba(0, 0, 6, 0.8);
32
+
33
+ /* Header bar */
34
+ --headerbar-bg-color: #ffffff;
35
+ --headerbar-fg-color: rgba(0, 0, 6, 0.8);
36
+ --headerbar-shade-color: rgba(0, 0, 6, 0.12);
37
+
38
+ /* Sidebar */
39
+ --sidebar-bg-color: #ebebed;
40
+
41
+ /* Cards / boxed lists */
42
+ --card-bg-color: #ffffff;
43
+ --card-fg-color: rgba(0, 0, 6, 0.8);
44
+ --card-shade-color: rgba(0, 0, 6, 0.07);
45
+
46
+ /* Accent */
47
+ --accent-bg-color: #3584e4;
48
+ --accent-fg-color: #ffffff;
49
+ --accent-color: #1c71d8;
50
+
51
+ /* Switch */
52
+ --switch-off-bg: rgba(0, 0, 0, 0.2);
53
+ --switch-knob-bg: #ffffff;
54
+
55
+ /* Layout */
56
+ --window-radius: 15px;
57
+ --card-radius: 12px;
58
+ --button-radius: 9px;
59
+
60
+ /* Spacing */
61
+ --spacing-xs: 6px;
62
+ --spacing-s: 9px;
63
+ --spacing-m: 12px;
64
+ --spacing-l: 18px;
65
+ --spacing-xl: 24px;
66
+
67
+ /* Typography — GNOME default: Adwaita Sans 11 */
68
+ --font-family: 'Adwaita Sans', 'Cantarell', 'Inter', 'Segoe UI', sans-serif;
69
+ --font-size-base: 11pt;
70
+ --font-size-small: 9pt;
71
+ --font-size-heading: 12pt;
72
+ --dim-opacity: 0.55;
73
+ }
74
+
75
+ /* ═══════════════════════════════════════════════════════════════
76
+ Dark Theme — auto via prefers-color-scheme
77
+ ═══════════════════════════════════════════════════════════════ */
78
+ @media (prefers-color-scheme: dark) {
79
+ :root:not(.theme-light) {
80
+ --window-bg-color: #222226;
81
+ --window-fg-color: #ffffff;
82
+ --view-bg-color: #1d1d20;
83
+ --view-fg-color: #ffffff;
84
+ --headerbar-bg-color: #2e2e32;
85
+ --headerbar-fg-color: #ffffff;
86
+ --headerbar-shade-color: rgba(0, 0, 6, 0.36);
87
+ --sidebar-bg-color: #2e2e32;
88
+ --card-bg-color: rgba(255, 255, 255, 0.08);
89
+ --card-fg-color: #ffffff;
90
+ --card-shade-color: rgba(0, 0, 6, 0.36);
91
+ --accent-color: #78aeed;
92
+ --switch-off-bg: rgba(255, 255, 255, 0.2);
93
+ --switch-knob-bg: #deddda;
94
+ }
95
+ }
96
+
97
+ /* Dark theme manual override */
98
+ :root.theme-dark {
99
+ --window-bg-color: #222226;
100
+ --window-fg-color: #ffffff;
101
+ --view-bg-color: #1d1d20;
102
+ --view-fg-color: #ffffff;
103
+ --headerbar-bg-color: #2e2e32;
104
+ --headerbar-fg-color: #ffffff;
105
+ --headerbar-shade-color: rgba(0, 0, 6, 0.36);
106
+ --sidebar-bg-color: #2e2e32;
107
+ --card-bg-color: rgba(255, 255, 255, 0.08);
108
+ --card-fg-color: #ffffff;
109
+ --card-shade-color: rgba(0, 0, 6, 0.36);
110
+ --accent-color: #78aeed;
111
+ --switch-off-bg: rgba(255, 255, 255, 0.2);
112
+ --switch-knob-bg: #deddda;
113
+ }
114
+
115
+ /* ═══════════════════════════════════════════════════════════════
116
+ Component Styles
117
+ ═══════════════════════════════════════════════════════════════ */
118
+
119
+ /* --- adw-window --- */
120
+ adw-window {
121
+ display: flex;
122
+ flex-direction: column;
123
+ background-color: var(--window-bg-color);
124
+ color: var(--window-fg-color);
125
+ font-family: var(--font-family);
126
+ font-size: var(--font-size-base);
127
+ border-radius: var(--window-radius);
128
+ box-shadow:
129
+ 0 0 14px 5px rgb(0 0 0 / 15%),
130
+ 0 0 5px 2px rgb(0 0 0 / 10%),
131
+ 0 0 0 1px rgb(0 0 0 / 5%);
132
+ overflow: hidden;
133
+ outline: 1px solid rgb(255 255 255 / 7%);
134
+ outline-offset: -1px;
135
+ }
136
+
137
+ /* --- adw-header-bar --- */
138
+ adw-header-bar {
139
+ display: flex;
140
+ align-items: center;
141
+ min-height: 47px;
142
+ padding: 0 6px;
143
+ background-color: var(--headerbar-bg-color);
144
+ color: var(--headerbar-fg-color);
145
+ box-shadow: inset 0 -1px var(--headerbar-shade-color);
146
+ flex-shrink: 0;
147
+ }
148
+
149
+ adw-header-bar .adw-header-bar-center {
150
+ flex: 1;
151
+ display: flex;
152
+ justify-content: center;
153
+ }
154
+
155
+ adw-header-bar .adw-header-bar-title {
156
+ font-weight: bold;
157
+ font-size: var(--font-size-base);
158
+ white-space: nowrap;
159
+ overflow: hidden;
160
+ text-overflow: ellipsis;
161
+ }
162
+
163
+ adw-header-bar .adw-header-bar-start {
164
+ display: flex;
165
+ align-items: center;
166
+ padding: 0 6px;
167
+ gap: 4px;
168
+ }
169
+
170
+ adw-header-bar .adw-header-bar-end {
171
+ display: flex;
172
+ align-items: center;
173
+ padding: 0 6px;
174
+ gap: 4px;
175
+ }
176
+
177
+ adw-header-bar .adw-header-btn {
178
+ position: relative;
179
+ width: 34px;
180
+ height: 34px;
181
+ border: none;
182
+ border-radius: var(--button-radius);
183
+ background: transparent;
184
+ color: var(--headerbar-fg-color);
185
+ cursor: pointer;
186
+ padding: 0;
187
+ margin: 0;
188
+ flex-shrink: 0;
189
+ }
190
+
191
+ adw-header-bar .adw-header-btn:hover {
192
+ background: rgba(128, 128, 128, 0.12);
193
+ }
194
+
195
+ adw-header-bar .adw-header-btn:active {
196
+ background: rgba(128, 128, 128, 0.22);
197
+ }
198
+
199
+ /* --- adw-preferences-group --- */
200
+ adw-preferences-group {
201
+ display: block;
202
+ }
203
+
204
+ adw-preferences-group .adw-preferences-group-header {
205
+ padding: var(--spacing-xs) 2px;
206
+ }
207
+
208
+ adw-preferences-group .adw-preferences-group-title {
209
+ font-size: var(--font-size-small);
210
+ font-weight: 700;
211
+ text-transform: uppercase;
212
+ letter-spacing: 0.5px;
213
+ opacity: var(--dim-opacity);
214
+ margin: 0;
215
+ color: var(--window-fg-color);
216
+ }
217
+
218
+ adw-preferences-group .adw-preferences-group-listbox {
219
+ background-color: var(--card-bg-color);
220
+ color: var(--card-fg-color);
221
+ border-radius: var(--card-radius);
222
+ box-shadow:
223
+ 0 0 0 1px rgb(0 0 6 / 3%),
224
+ 0 1px 3px 1px rgb(0 0 6 / 7%),
225
+ 0 2px 6px 2px rgb(0 0 6 / 3%);
226
+ overflow: hidden;
227
+ }
228
+
229
+ /* --- Row base (shared by switch-row, combo-row) --- */
230
+ adw-switch-row,
231
+ adw-combo-row {
232
+ display: flex;
233
+ align-items: center;
234
+ justify-content: space-between;
235
+ min-height: 50px;
236
+ padding: var(--spacing-s) var(--spacing-m);
237
+ gap: var(--spacing-m);
238
+ border-bottom: 1px solid var(--card-shade-color);
239
+ }
240
+
241
+ adw-switch-row:last-child,
242
+ adw-combo-row:last-child {
243
+ border-bottom: none;
244
+ }
245
+
246
+ /* --- adw-switch-row --- */
247
+ adw-switch-row .adw-row-title {
248
+ flex: 1;
249
+ font-size: var(--font-size-base);
250
+ color: var(--card-fg-color);
251
+ }
252
+
253
+ adw-switch-row .adw-switch {
254
+ position: relative;
255
+ display: inline-block;
256
+ width: 44px;
257
+ height: 24px;
258
+ flex-shrink: 0;
259
+ }
260
+
261
+ adw-switch-row .adw-switch input {
262
+ opacity: 0;
263
+ width: 0;
264
+ height: 0;
265
+ position: absolute;
266
+ }
267
+
268
+ adw-switch-row .adw-switch-slider {
269
+ position: absolute;
270
+ cursor: pointer;
271
+ inset: 0;
272
+ background-color: var(--switch-off-bg);
273
+ border-radius: 12px;
274
+ transition: background-color 0.15s ease-out;
275
+ }
276
+
277
+ adw-switch-row .adw-switch-slider::before {
278
+ content: "";
279
+ position: absolute;
280
+ height: 20px;
281
+ width: 20px;
282
+ left: 2px;
283
+ bottom: 2px;
284
+ background-color: var(--switch-knob-bg);
285
+ border-radius: 50%;
286
+ transition: transform 0.15s ease-out;
287
+ box-shadow: 0 1px 2px rgb(0 0 0 / 20%);
288
+ }
289
+
290
+ adw-switch-row .adw-switch input:checked + .adw-switch-slider {
291
+ background-color: var(--accent-bg-color);
292
+ }
293
+
294
+ adw-switch-row .adw-switch input:checked + .adw-switch-slider::before {
295
+ transform: translateX(20px);
296
+ }
297
+
298
+ adw-switch-row .adw-switch input:focus-visible + .adw-switch-slider {
299
+ outline: 2px solid var(--accent-color);
300
+ outline-offset: 2px;
301
+ }
302
+
303
+ /* --- adw-combo-row --- */
304
+ adw-combo-row {
305
+ position: relative;
306
+ cursor: pointer;
307
+ }
308
+
309
+ adw-combo-row .adw-row-title {
310
+ font-size: var(--font-size-base);
311
+ color: var(--card-fg-color);
312
+ pointer-events: none;
313
+ }
314
+
315
+ adw-combo-row select {
316
+ appearance: none;
317
+ -webkit-appearance: none;
318
+ background-color: transparent;
319
+ border: none;
320
+ padding: 6px 24px 6px 8px;
321
+ font-family: var(--font-family);
322
+ font-size: var(--font-size-base);
323
+ color: var(--card-fg-color);
324
+ cursor: pointer;
325
+ text-align: right;
326
+ background-image: ${goDownDataUri};
327
+ background-repeat: no-repeat;
328
+ background-position: right 4px center;
329
+ background-size: 12px 12px;
330
+ /* Stretch select over the entire row to make it clickable anywhere */
331
+ position: absolute;
332
+ inset: 0;
333
+ opacity: 0;
334
+ width: 100%;
335
+ height: 100%;
336
+ z-index: 1;
337
+ }
338
+
339
+ adw-combo-row .adw-row-value {
340
+ font-size: var(--font-size-base);
341
+ color: var(--card-fg-color);
342
+ opacity: var(--dim-opacity);
343
+ display: flex;
344
+ align-items: center;
345
+ gap: 4px;
346
+ }
347
+
348
+ adw-combo-row .adw-row-value::after {
349
+ content: "";
350
+ display: inline-block;
351
+ width: 12px;
352
+ height: 12px;
353
+ background-image: ${goDownDataUri};
354
+ background-repeat: no-repeat;
355
+ background-size: contain;
356
+ }
357
+
358
+ adw-combo-row select:focus-visible {
359
+ outline: 2px solid var(--accent-color);
360
+ outline-offset: -1px;
361
+ }
362
+
363
+ adw-combo-row select option {
364
+ background-color: var(--window-bg-color);
365
+ color: var(--window-fg-color);
366
+ }
367
+
368
+ /* --- Utility: separator --- */
369
+ .adw-separator-vertical {
370
+ width: 1px;
371
+ align-self: stretch;
372
+ background-color: var(--card-shade-color);
373
+ flex-shrink: 0;
374
+ }
375
+
376
+ /* --- adw-spin-row --- */
377
+ adw-spin-row {
378
+ display: flex;
379
+ align-items: center;
380
+ justify-content: space-between;
381
+ min-height: 50px;
382
+ padding: var(--spacing-s) var(--spacing-m);
383
+ gap: var(--spacing-m);
384
+ border-bottom: 1px solid var(--card-shade-color);
385
+ }
386
+
387
+ adw-spin-row:last-child {
388
+ border-bottom: none;
389
+ }
390
+
391
+ adw-spin-row .adw-row-title {
392
+ flex: 1;
393
+ font-size: var(--font-size-base);
394
+ color: var(--card-fg-color);
395
+ }
396
+
397
+ .adw-spin-control {
398
+ display: flex;
399
+ align-items: center;
400
+ gap: 2px;
401
+ }
402
+
403
+ .adw-spin-control button {
404
+ width: 32px;
405
+ height: 32px;
406
+ border: none;
407
+ border-radius: var(--button-radius);
408
+ background: transparent;
409
+ color: var(--card-fg-color);
410
+ cursor: pointer;
411
+ font-size: 16px;
412
+ display: flex;
413
+ align-items: center;
414
+ justify-content: center;
415
+ flex-shrink: 0;
416
+ }
417
+
418
+ .adw-spin-control button:hover {
419
+ background: rgba(128, 128, 128, 0.15);
420
+ }
421
+
422
+ .adw-spin-control input {
423
+ width: 56px;
424
+ text-align: center;
425
+ border: none;
426
+ border-radius: var(--button-radius);
427
+ background: rgba(128, 128, 128, 0.1);
428
+ color: var(--card-fg-color);
429
+ font-family: var(--font-family);
430
+ font-size: var(--font-size-base);
431
+ padding: 4px 2px;
432
+ }
433
+
434
+ .adw-spin-control input:focus {
435
+ outline: 2px solid var(--accent-color);
436
+ outline-offset: -1px;
437
+ }
438
+
439
+ /* --- adw-toast-overlay ---
440
+ Reference: refs/adwaita-web/adwaita-web/scss/_toast_overlay.scss */
441
+ adw-toast-overlay {
442
+ position: fixed;
443
+ bottom: 24px;
444
+ left: 50%;
445
+ transform: translateX(-50%);
446
+ z-index: 9999;
447
+ display: flex;
448
+ flex-direction: column-reverse;
449
+ gap: 9px;
450
+ align-items: center;
451
+ pointer-events: none;
452
+ }
453
+
454
+ /* --- adw-toast ---
455
+ Reference: refs/adwaita-web/adwaita-web/scss/_toast.scss */
456
+ .adw-toast {
457
+ background-color: #3d3846;
458
+ color: #ffffff;
459
+ padding: 9px 12px;
460
+ border-radius: var(--card-radius);
461
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.25);
462
+ display: flex;
463
+ align-items: center;
464
+ gap: 9px;
465
+ width: max-content;
466
+ max-width: 400px;
467
+ min-height: 36px;
468
+ pointer-events: auto;
469
+ font-family: var(--font-family);
470
+ font-size: var(--font-size-base);
471
+
472
+ opacity: 0;
473
+ transform: translateY(100%) scale(0.9);
474
+ transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1),
475
+ transform 0.2s cubic-bezier(0, 0, 0.2, 1);
476
+ will-change: transform, opacity;
477
+ }
478
+
479
+ .adw-toast.visible {
480
+ opacity: 1;
481
+ transform: translateY(0) scale(1);
482
+ }
483
+
484
+ .adw-toast.hiding {
485
+ opacity: 0;
486
+ transform: translateY(100%) scale(0.9);
487
+ transition-duration: 0.15s;
488
+ transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
489
+ }
490
+
491
+ .adw-toast .adw-toast-title {
492
+ font-weight: normal;
493
+ line-height: 1.3;
494
+ }
495
+
496
+ /* ═══════════════════════════════════════════════════════════════
497
+ adw-overlay-split-view
498
+ Reference: Adw.OverlaySplitView from libadwaita
499
+ ═══════════════════════════════════════════════════════════════ */
500
+ adw-overlay-split-view {
501
+ display: flex;
502
+ flex-direction: row;
503
+ flex: 1;
504
+ min-height: 0;
505
+ position: relative;
506
+ overflow: hidden;
507
+ }
508
+
509
+ adw-overlay-split-view .adw-osv-sidebar {
510
+ background-color: var(--sidebar-bg-color);
511
+ overflow-y: auto;
512
+ flex-shrink: 0;
513
+ transition: transform 0.2s ease, opacity 0.2s ease, margin 0.2s ease;
514
+ z-index: 10;
515
+ align-self: stretch;
516
+ }
517
+
518
+ adw-overlay-split-view .adw-osv-content {
519
+ flex: 1;
520
+ min-width: 0;
521
+ min-height: 0;
522
+ display: flex;
523
+ flex-direction: column;
524
+ align-self: stretch;
525
+ }
526
+
527
+ /* Docked mode (not collapsed) — sidebar beside content */
528
+ adw-overlay-split-view:not(.collapsed) .adw-osv-sidebar {
529
+ position: relative;
530
+ }
531
+
532
+ /* Docked hide: slide left and collapse space via negative margin.
533
+ Sidebar keeps its intrinsic width so internal elements don't reflow. */
534
+ adw-overlay-split-view:not(.collapsed):not(.show-sidebar) .adw-osv-sidebar {
535
+ transform: translateX(-100%);
536
+ opacity: 0;
537
+ pointer-events: none;
538
+ }
539
+
540
+ /* Overlay mode (collapsed) — sidebar on top of content */
541
+ adw-overlay-split-view.collapsed .adw-osv-sidebar {
542
+ position: absolute;
543
+ top: 0;
544
+ bottom: 0;
545
+ left: 0;
546
+ transform: translateX(-100%);
547
+ opacity: 0;
548
+ pointer-events: none;
549
+ }
550
+
551
+ adw-overlay-split-view.collapsed.sidebar-end .adw-osv-sidebar {
552
+ left: auto;
553
+ right: 0;
554
+ transform: translateX(100%);
555
+ }
556
+
557
+ adw-overlay-split-view.collapsed.show-sidebar .adw-osv-sidebar {
558
+ transform: translateX(0);
559
+ opacity: 1;
560
+ pointer-events: auto;
561
+ }
562
+
563
+ /* Backdrop — visible only in overlay mode when sidebar is open */
564
+ adw-overlay-split-view .adw-osv-backdrop {
565
+ display: none;
566
+ }
567
+
568
+ adw-overlay-split-view.collapsed.show-sidebar .adw-osv-backdrop {
569
+ display: block;
570
+ position: absolute;
571
+ inset: 0;
572
+ background: rgba(0, 0, 0, 0.3);
573
+ z-index: 9;
574
+ }
575
+
576
+ /* Sidebar toggle button — sidebar-show-symbolic icon via CSS mask */
577
+ .adw-sidebar-toggle-icon::after {
578
+ content: '';
579
+ position: absolute;
580
+ inset: 0;
581
+ background-color: currentColor;
582
+ -webkit-mask-image: var(--icon-sidebar-show);
583
+ mask-image: var(--icon-sidebar-show);
584
+ -webkit-mask-repeat: no-repeat;
585
+ mask-repeat: no-repeat;
586
+ -webkit-mask-size: 16px 16px;
587
+ mask-size: 16px 16px;
588
+ -webkit-mask-position: center;
589
+ mask-position: center;
590
+ pointer-events: none;
591
+ }
592
+ `;
@@ -0,0 +1,81 @@
1
+ // <adw-combo-row> — Row with a label and dropdown select.
2
+ // Attributes: title, items (JSON string[]), selected (index number)
3
+ // Events: notify::selected (CustomEvent, mirrors GJS GObject signal naming)
4
+ // The native <select> is stretched invisibly over the row so clicking anywhere opens it.
5
+ // Adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
6
+ // Copyright (c) 2025 csm. MIT License.
7
+ // Modifications: Reimplemented as Web Component for @gjsify/adwaita-web.
8
+
9
+ export class AdwComboRow extends HTMLElement {
10
+ private _select!: HTMLSelectElement;
11
+ private _valueEl!: HTMLSpanElement;
12
+ private _items: string[] = [];
13
+ private _initialized = false;
14
+
15
+ static get observedAttributes() { return ['selected']; }
16
+
17
+ get selected(): number {
18
+ return this._select ? this._select.selectedIndex : parseInt(this.getAttribute('selected') || '0', 10);
19
+ }
20
+
21
+ set selected(value: number) {
22
+ this.setAttribute('selected', String(value));
23
+ }
24
+
25
+ connectedCallback() {
26
+ if (this._initialized) return;
27
+ this._initialized = true;
28
+
29
+ const title = this.getAttribute('title') || '';
30
+ this._items = JSON.parse(this.getAttribute('items') || '[]');
31
+ const selectedIdx = parseInt(this.getAttribute('selected') || '0', 10);
32
+
33
+ // Title label
34
+ const titleEl = document.createElement('span');
35
+ titleEl.className = 'adw-row-title';
36
+ titleEl.textContent = title;
37
+
38
+ // Visible selected value display
39
+ const valueEl = document.createElement('span');
40
+ valueEl.className = 'adw-row-value';
41
+ valueEl.textContent = this._items[selectedIdx] ?? '';
42
+ this._valueEl = valueEl;
43
+
44
+ // Hidden select overlaying the entire row
45
+ const select = document.createElement('select');
46
+ this._items.forEach((item, i) => {
47
+ const option = document.createElement('option');
48
+ option.value = String(i);
49
+ option.textContent = item;
50
+ if (i === selectedIdx) option.selected = true;
51
+ select.appendChild(option);
52
+ });
53
+
54
+ this.appendChild(titleEl);
55
+ this.appendChild(valueEl);
56
+ this.appendChild(select);
57
+
58
+ this._select = select;
59
+ this._select.addEventListener('change', () => {
60
+ const idx = this._select.selectedIndex;
61
+ this._valueEl.textContent = this._items[idx] ?? '';
62
+ this.setAttribute('selected', String(idx));
63
+ this.dispatchEvent(new CustomEvent('notify::selected', {
64
+ bubbles: true,
65
+ detail: { selected: idx },
66
+ }));
67
+ });
68
+ }
69
+
70
+ attributeChangedCallback(name: string, _old: string | null, value: string | null) {
71
+ if (name === 'selected' && this._select) {
72
+ const idx = parseInt(value || '0', 10);
73
+ this._select.selectedIndex = idx;
74
+ if (this._valueEl) {
75
+ this._valueEl.textContent = this._items[idx] ?? '';
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ customElements.define('adw-combo-row', AdwComboRow);
@@ -0,0 +1,50 @@
1
+ // <adw-header-bar> — Adwaita header bar with centered title and start/end button slots.
2
+ // Adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
3
+ // Copyright (c) 2025 csm. MIT License.
4
+ // Modifications: Reimplemented as Web Component for @gjsify/adwaita-web.
5
+
6
+ export class AdwHeaderBar extends HTMLElement {
7
+ private _initialized = false;
8
+ private _startEl: HTMLDivElement | null = null;
9
+ private _endEl: HTMLDivElement | null = null;
10
+
11
+ /** The start (left) section container — append buttons/widgets here. */
12
+ get startSection(): HTMLDivElement | null { return this._startEl; }
13
+
14
+ /** The end (right) section container — append buttons/widgets here. */
15
+ get endSection(): HTMLDivElement | null { return this._endEl; }
16
+
17
+ connectedCallback() {
18
+ if (this._initialized) return;
19
+ this._initialized = true;
20
+
21
+ const title = this.getAttribute('title') || '';
22
+
23
+ // Capture any pre-existing slotted children before clearing
24
+ const startChildren = Array.from(this.querySelectorAll(':scope > [slot="start"]'));
25
+ const endChildren = Array.from(this.querySelectorAll(':scope > [slot="end"]'));
26
+
27
+ // Start section
28
+ this._startEl = document.createElement('div');
29
+ this._startEl.className = 'adw-header-bar-start';
30
+ for (const child of startChildren) this._startEl.appendChild(child);
31
+
32
+ // Center section with title
33
+ const center = document.createElement('div');
34
+ center.className = 'adw-header-bar-center';
35
+ const titleEl = document.createElement('span');
36
+ titleEl.className = 'adw-header-bar-title';
37
+ titleEl.textContent = title;
38
+ center.appendChild(titleEl);
39
+
40
+ // End section
41
+ this._endEl = document.createElement('div');
42
+ this._endEl.className = 'adw-header-bar-end';
43
+ for (const child of endChildren) this._endEl.appendChild(child);
44
+
45
+ // Replace all children atomically
46
+ this.replaceChildren(this._startEl, center, this._endEl);
47
+ }
48
+ }
49
+
50
+ customElements.define('adw-header-bar', AdwHeaderBar);
@@ -0,0 +1,135 @@
1
+ // <adw-overlay-split-view> — Responsive sidebar that docks or overlays content.
2
+ // Reference: Adw.OverlaySplitView from libadwaita.
3
+ // Docs: refs/adwaita-web/adwaita-web/docs/widgets/overlaysplitview.md
4
+
5
+ export class AdwOverlaySplitView extends HTMLElement {
6
+ private _initialized = false;
7
+ private _sidebarEl!: HTMLDivElement;
8
+ private _contentEl!: HTMLDivElement;
9
+ private _backdropEl!: HTMLDivElement;
10
+
11
+ static get observedAttributes() {
12
+ return ['show-sidebar', 'collapsed', 'sidebar-position', 'min-sidebar-width', 'max-sidebar-width', 'sidebar-width-fraction'];
13
+ }
14
+
15
+ get showSidebar(): boolean {
16
+ return this.hasAttribute('show-sidebar');
17
+ }
18
+
19
+ set showSidebar(v: boolean) {
20
+ if (v) this.setAttribute('show-sidebar', '');
21
+ else this.removeAttribute('show-sidebar');
22
+ }
23
+
24
+ get collapsed(): boolean {
25
+ return this.hasAttribute('collapsed');
26
+ }
27
+
28
+ set collapsed(v: boolean) {
29
+ if (v) this.setAttribute('collapsed', '');
30
+ else this.removeAttribute('collapsed');
31
+ }
32
+
33
+ get minSidebarWidth(): number {
34
+ return parseFloat(this.getAttribute('min-sidebar-width') || '280');
35
+ }
36
+
37
+ get maxSidebarWidth(): number {
38
+ return parseFloat(this.getAttribute('max-sidebar-width') || '400');
39
+ }
40
+
41
+ get sidebarWidthFraction(): number {
42
+ return parseFloat(this.getAttribute('sidebar-width-fraction') || '0.30');
43
+ }
44
+
45
+ connectedCallback() {
46
+ if (this._initialized) return;
47
+ this._initialized = true;
48
+
49
+ // Capture slotted children before rebuilding DOM
50
+ const sidebarChildren = Array.from(this.querySelectorAll('[slot="sidebar"]'));
51
+ const contentChildren = Array.from(this.querySelectorAll('[slot="content"]'));
52
+ // Any remaining unslotted children go to content
53
+ const unslotted = Array.from(this.childNodes).filter(
54
+ n => !sidebarChildren.includes(n as Element) && !contentChildren.includes(n as Element),
55
+ );
56
+
57
+ // Clear children safely
58
+ this.replaceChildren();
59
+
60
+ // Sidebar container
61
+ this._sidebarEl = document.createElement('div');
62
+ this._sidebarEl.className = 'adw-osv-sidebar';
63
+ sidebarChildren.forEach(c => this._sidebarEl.appendChild(c));
64
+
65
+ // Content container
66
+ this._contentEl = document.createElement('div');
67
+ this._contentEl.className = 'adw-osv-content';
68
+ contentChildren.forEach(c => this._contentEl.appendChild(c));
69
+ unslotted.forEach(c => this._contentEl.appendChild(c));
70
+
71
+ // Backdrop for overlay dismiss
72
+ this._backdropEl = document.createElement('div');
73
+ this._backdropEl.className = 'adw-osv-backdrop';
74
+ this._backdropEl.addEventListener('click', () => this.hideSidebar());
75
+
76
+ this.append(this._sidebarEl, this._contentEl, this._backdropEl);
77
+
78
+ // Apply initial state
79
+ this._syncClasses();
80
+ this._syncSidebarWidth();
81
+ }
82
+
83
+ attributeChangedCallback(_name: string, _old: string | null, _val: string | null) {
84
+ if (!this._initialized) return;
85
+ this._syncClasses();
86
+ if (_name === 'min-sidebar-width' || _name === 'max-sidebar-width' || _name === 'sidebar-width-fraction') {
87
+ this._syncSidebarWidth();
88
+ }
89
+ }
90
+
91
+ openSidebar() {
92
+ this.showSidebar = true;
93
+ this.dispatchEvent(new CustomEvent('sidebar-toggled', { detail: { isVisible: true } }));
94
+ }
95
+
96
+ hideSidebar() {
97
+ this.showSidebar = false;
98
+ this.dispatchEvent(new CustomEvent('sidebar-toggled', { detail: { isVisible: false } }));
99
+ }
100
+
101
+ toggleSidebar() {
102
+ this.showSidebar = !this.showSidebar;
103
+ this.dispatchEvent(new CustomEvent('sidebar-toggled', {
104
+ detail: { isVisible: this.showSidebar },
105
+ }));
106
+ }
107
+
108
+ private _syncClasses() {
109
+ this.classList.toggle('collapsed', this.collapsed);
110
+ this.classList.toggle('show-sidebar', this.showSidebar);
111
+ const pos = this.getAttribute('sidebar-position') || 'start';
112
+ this.classList.toggle('sidebar-start', pos === 'start');
113
+ this.classList.toggle('sidebar-end', pos === 'end');
114
+
115
+ // In docked mode, use negative margin to collapse sidebar space
116
+ // while keeping the sidebar's intrinsic width (slide animation).
117
+ if (this._sidebarEl && !this.collapsed) {
118
+ if (!this.showSidebar) {
119
+ const w = this._sidebarEl.offsetWidth;
120
+ this._sidebarEl.style.marginRight = `-${w}px`;
121
+ } else {
122
+ this._sidebarEl.style.marginRight = '';
123
+ }
124
+ }
125
+ }
126
+
127
+ private _syncSidebarWidth() {
128
+ if (!this._sidebarEl) return;
129
+ this._sidebarEl.style.minWidth = `${this.minSidebarWidth}px`;
130
+ this._sidebarEl.style.maxWidth = `${this.maxSidebarWidth}px`;
131
+ this._sidebarEl.style.width = `${this.sidebarWidthFraction * 100}%`;
132
+ }
133
+ }
134
+
135
+ customElements.define('adw-overlay-split-view', AdwOverlaySplitView);
@@ -0,0 +1,34 @@
1
+ // <adw-preferences-group> — Groups child rows in a boxed list with a title label.
2
+ // Adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
3
+ // Copyright (c) 2025 csm. MIT License.
4
+ // Modifications: Reimplemented as Web Component for @gjsify/adwaita-web.
5
+
6
+ export class AdwPreferencesGroup extends HTMLElement {
7
+ private _initialized = false;
8
+
9
+ connectedCallback() {
10
+ if (this._initialized) return;
11
+ this._initialized = true;
12
+
13
+ const title = this.getAttribute('title') || '';
14
+ const children = Array.from(this.childNodes);
15
+
16
+ // Build header with title
17
+ const header = document.createElement('div');
18
+ header.className = 'adw-preferences-group-header';
19
+ const titleEl = document.createElement('span');
20
+ titleEl.className = 'adw-preferences-group-title';
21
+ titleEl.textContent = title;
22
+ header.appendChild(titleEl);
23
+
24
+ // Build boxed list container and move children into it
25
+ const listbox = document.createElement('div');
26
+ listbox.className = 'adw-preferences-group-listbox';
27
+ children.forEach(child => listbox.appendChild(child));
28
+
29
+ this.appendChild(header);
30
+ this.appendChild(listbox);
31
+ }
32
+ }
33
+
34
+ customElements.define('adw-preferences-group', AdwPreferencesGroup);
@@ -0,0 +1,123 @@
1
+ // <adw-spin-row> — Row with a label and numeric spin control (+/− buttons).
2
+ // Attributes: title, min, max, step, value
3
+ // Events: notify::value (CustomEvent, mirrors GJS GObject signal naming)
4
+ // Adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
5
+ // Copyright (c) 2025 csm. MIT License.
6
+ // Modifications: Reimplemented as Web Component for @gjsify/adwaita-web.
7
+
8
+ export class AdwSpinRow extends HTMLElement {
9
+ private _input!: HTMLInputElement;
10
+ private _min = 0;
11
+ private _max = 100;
12
+ private _step = 1;
13
+ private _value = 0;
14
+ private _initialized = false;
15
+
16
+ static get observedAttributes() { return ['value', 'min', 'max', 'step']; }
17
+
18
+ get value(): number {
19
+ return this._value;
20
+ }
21
+
22
+ set value(v: number) {
23
+ const clamped = Math.min(this._max, Math.max(this._min, v));
24
+ // Round to step precision to avoid floating-point drift
25
+ const decimals = this._countDecimals(this._step);
26
+ this._value = parseFloat(clamped.toFixed(decimals));
27
+ if (this._input) {
28
+ this._input.value = this._formatValue(this._value);
29
+ }
30
+ this.setAttribute('value', String(this._value));
31
+ }
32
+
33
+ connectedCallback() {
34
+ if (this._initialized) return;
35
+ this._initialized = true;
36
+
37
+ const title = this.getAttribute('title') || '';
38
+ this._min = parseFloat(this.getAttribute('min') || '0');
39
+ this._max = parseFloat(this.getAttribute('max') || '100');
40
+ this._step = parseFloat(this.getAttribute('step') || '1');
41
+ this._value = parseFloat(this.getAttribute('value') || String(this._min));
42
+
43
+ // Title
44
+ const titleEl = document.createElement('span');
45
+ titleEl.className = 'adw-row-title';
46
+ titleEl.textContent = title;
47
+
48
+ // Spin control container
49
+ const control = document.createElement('div');
50
+ control.className = 'adw-spin-control';
51
+
52
+ // Decrement button
53
+ const decBtn = document.createElement('button');
54
+ decBtn.className = 'adw-spin-dec';
55
+ decBtn.textContent = '−';
56
+ decBtn.addEventListener('click', () => this._adjust(-this._step));
57
+
58
+ // Value input
59
+ const input = document.createElement('input');
60
+ input.type = 'text';
61
+ input.value = this._formatValue(this._value);
62
+ input.addEventListener('change', () => {
63
+ const parsed = parseFloat(input.value);
64
+ if (!isNaN(parsed)) {
65
+ this.value = parsed;
66
+ this._emitChange();
67
+ } else {
68
+ input.value = this._formatValue(this._value);
69
+ }
70
+ });
71
+
72
+ // Increment button
73
+ const incBtn = document.createElement('button');
74
+ incBtn.className = 'adw-spin-inc';
75
+ incBtn.textContent = '+';
76
+ incBtn.addEventListener('click', () => this._adjust(this._step));
77
+
78
+ control.append(decBtn, input, incBtn);
79
+ this.append(titleEl, control);
80
+ this._input = input;
81
+ }
82
+
83
+ attributeChangedCallback(name: string, _old: string | null, val: string | null) {
84
+ if (!this._initialized) return;
85
+ const num = parseFloat(val || '0');
86
+ switch (name) {
87
+ case 'value':
88
+ if (num !== this._value) {
89
+ this._value = Math.min(this._max, Math.max(this._min, num));
90
+ this._input.value = this._formatValue(this._value);
91
+ }
92
+ break;
93
+ case 'min': this._min = num; break;
94
+ case 'max': this._max = num; break;
95
+ case 'step': this._step = num; break;
96
+ }
97
+ }
98
+
99
+ private _adjust(delta: number) {
100
+ this.value = this._value + delta;
101
+ this._emitChange();
102
+ }
103
+
104
+ private _emitChange() {
105
+ this.dispatchEvent(new CustomEvent('notify::value', {
106
+ bubbles: true,
107
+ detail: { value: this._value },
108
+ }));
109
+ }
110
+
111
+ private _countDecimals(n: number): number {
112
+ const s = String(n);
113
+ const dot = s.indexOf('.');
114
+ return dot === -1 ? 0 : s.length - dot - 1;
115
+ }
116
+
117
+ private _formatValue(v: number): string {
118
+ const decimals = this._countDecimals(this._step);
119
+ return decimals > 0 ? v.toFixed(decimals) : String(v);
120
+ }
121
+ }
122
+
123
+ customElements.define('adw-spin-row', AdwSpinRow);
@@ -0,0 +1,69 @@
1
+ // <adw-switch-row> — Row with a label and toggle switch.
2
+ // Attributes: title, active (boolean)
3
+ // Events: notify::active (CustomEvent, mirrors GJS GObject signal naming)
4
+ // Adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
5
+ // Copyright (c) 2025 csm. MIT License.
6
+ // Modifications: Reimplemented as Web Component for @gjsify/adwaita-web.
7
+
8
+ export class AdwSwitchRow extends HTMLElement {
9
+ private _checkbox!: HTMLInputElement;
10
+ private _initialized = false;
11
+
12
+ static get observedAttributes() { return ['active']; }
13
+
14
+ get active(): boolean {
15
+ return this.hasAttribute('active');
16
+ }
17
+
18
+ set active(value: boolean) {
19
+ if (value) this.setAttribute('active', '');
20
+ else this.removeAttribute('active');
21
+ }
22
+
23
+ connectedCallback() {
24
+ if (this._initialized) return;
25
+ this._initialized = true;
26
+
27
+ const title = this.getAttribute('title') || '';
28
+ const checked = this.hasAttribute('active');
29
+
30
+ // Title
31
+ const titleEl = document.createElement('span');
32
+ titleEl.className = 'adw-row-title';
33
+ titleEl.textContent = title;
34
+
35
+ // Switch label with hidden checkbox + slider
36
+ const label = document.createElement('label');
37
+ label.className = 'adw-switch';
38
+
39
+ const input = document.createElement('input');
40
+ input.type = 'checkbox';
41
+ input.checked = checked;
42
+
43
+ const slider = document.createElement('span');
44
+ slider.className = 'adw-switch-slider';
45
+
46
+ label.appendChild(input);
47
+ label.appendChild(slider);
48
+
49
+ this.appendChild(titleEl);
50
+ this.appendChild(label);
51
+
52
+ this._checkbox = input;
53
+ this._checkbox.addEventListener('change', () => {
54
+ this.toggleAttribute('active', this._checkbox.checked);
55
+ this.dispatchEvent(new CustomEvent('notify::active', {
56
+ bubbles: true,
57
+ detail: { active: this._checkbox.checked },
58
+ }));
59
+ });
60
+ }
61
+
62
+ attributeChangedCallback(name: string, _old: string | null, value: string | null) {
63
+ if (name === 'active' && this._checkbox) {
64
+ this._checkbox.checked = value !== null;
65
+ }
66
+ }
67
+ }
68
+
69
+ customElements.define('adw-switch-row', AdwSwitchRow);
@@ -0,0 +1,43 @@
1
+ // <adw-toast-overlay> — Adwaita toast notification overlay.
2
+ // Displays temporary feedback messages at the bottom of the viewport.
3
+ // Adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
4
+ // Reference: refs/adwaita-web/adwaita-web/scss/_toast.scss, _toast_overlay.scss
5
+ // Reference: refs/libadwaita/src/adw-toast-overlay.c
6
+ // Copyright (c) 2025 csm (adwaita-web). MIT License.
7
+ // Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
8
+ // Modifications: Reimplemented as Web Component for @gjsify/adwaita-web.
9
+
10
+ export class AdwToastOverlay extends HTMLElement {
11
+ /**
12
+ * Show a toast notification.
13
+ * @param title - The text to display.
14
+ * @param timeout - Time in ms before the toast auto-dismisses (default 2000).
15
+ */
16
+ addToast(title: string, timeout = 2000): void {
17
+ const toast = document.createElement('div');
18
+ toast.className = 'adw-toast';
19
+
20
+ const titleEl = document.createElement('span');
21
+ titleEl.className = 'adw-toast-title';
22
+ titleEl.textContent = title;
23
+ toast.appendChild(titleEl);
24
+
25
+ this.appendChild(toast);
26
+
27
+ // Trigger enter animation on next frame
28
+ requestAnimationFrame(() => {
29
+ toast.classList.add('visible');
30
+ });
31
+
32
+ // Auto-dismiss after timeout
33
+ setTimeout(() => {
34
+ toast.classList.remove('visible');
35
+ toast.classList.add('hiding');
36
+ toast.addEventListener('transitionend', () => toast.remove(), { once: true });
37
+ // Fallback if transitionend doesn't fire
38
+ setTimeout(() => { if (toast.parentNode) toast.remove(); }, 300);
39
+ }, timeout);
40
+ }
41
+ }
42
+
43
+ customElements.define('adw-toast-overlay', AdwToastOverlay);
@@ -0,0 +1,18 @@
1
+ // <adw-window> — Adwaita application window frame.
2
+ // Provides rounded corners, shadow, and flex column layout.
3
+ // Adapted from Adwaita Web UI Framework (https://github.com/mclellac/adwaita-web).
4
+ // Copyright (c) 2025 csm. MIT License.
5
+ // Modifications: Reimplemented as Web Component for @gjsify/adwaita-web.
6
+
7
+ export class AdwWindow extends HTMLElement {
8
+ connectedCallback() {
9
+ // Light DOM: children remain in place, element itself is the styled container.
10
+ // Set dimensions from attributes if provided.
11
+ const w = this.getAttribute('width');
12
+ const h = this.getAttribute('height');
13
+ if (w) this.style.width = `${w}px`;
14
+ if (h) this.style.height = `${h}px`;
15
+ }
16
+ }
17
+
18
+ customElements.define('adw-window', AdwWindow);
package/src/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ // @gjsify/adwaita-web — Adwaita/Libadwaita web components for browser targets.
2
+ // Importing this module registers all custom elements and injects the Adwaita CSS.
3
+ // Reference: refs/libadwaita (colors/sizing), refs/adwaita-web (component patterns).
4
+
5
+ import '@gjsify/adwaita-fonts'; // Registers @font-face (fontsource pattern)
6
+ import { adwaitaCSS } from './adwaita-css.js';
7
+
8
+ // Register custom elements (side-effect imports)
9
+ export { AdwWindow } from './elements/adw-window.js';
10
+ export { AdwHeaderBar } from './elements/adw-header-bar.js';
11
+ export { AdwPreferencesGroup } from './elements/adw-preferences-group.js';
12
+ export { AdwSwitchRow } from './elements/adw-switch-row.js';
13
+ export { AdwComboRow } from './elements/adw-combo-row.js';
14
+ export { AdwSpinRow } from './elements/adw-spin-row.js';
15
+ export { AdwToastOverlay } from './elements/adw-toast-overlay.js';
16
+ export { AdwOverlaySplitView } from './elements/adw-overlay-split-view.js';
17
+
18
+ // Inject Adwaita CSS into the document
19
+ if (typeof document !== 'undefined') {
20
+ const style = document.createElement('style');
21
+ style.textContent = adwaitaCSS;
22
+ document.head.appendChild(style);
23
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "ESNext",
4
+ "target": "ESNext",
5
+ "lib": ["ESNext", "DOM"],
6
+ "rootDir": "src",
7
+ "outDir": "lib",
8
+ "declarationDir": "lib/types",
9
+ "declaration": true,
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "emitDeclarationOnly": true,
13
+ "strict": false
14
+ },
15
+ "include": ["src/**/*.ts"]
16
+ }