@gjsify/adwaita-web 0.40.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +98 -3
  2. package/dist/adwaita-web.css +142 -63
  3. package/dist/adwaita-web.css.map +1 -1
  4. package/lib/types/adw-fonts.spec.d.ts +2 -0
  5. package/lib/types/breakpoints.d.ts +10 -1
  6. package/lib/types/connect-lifecycle.spec.d.ts +1 -0
  7. package/lib/types/elements/adw-about-dialog.d.ts +1 -0
  8. package/lib/types/elements/adw-action-row.d.ts +2 -0
  9. package/lib/types/elements/adw-alert-dialog.d.ts +14 -2
  10. package/lib/types/elements/adw-carousel.d.ts +48 -0
  11. package/lib/types/elements/adw-dialog.d.ts +2 -7
  12. package/lib/types/elements/adw-icon.d.ts +17 -1
  13. package/lib/types/elements/adw-navigation-split-view.d.ts +7 -0
  14. package/lib/types/elements/adw-overlay-split-view.d.ts +6 -0
  15. package/lib/types/elements/adw-popover.d.ts +1 -0
  16. package/lib/types/elements/adw-preferences-dialog.d.ts +1 -0
  17. package/lib/types/elements/adw-switch.d.ts +10 -0
  18. package/lib/types/elements/adw-tab-view.d.ts +23 -0
  19. package/lib/types/elements/adw-toggle-group.d.ts +2 -0
  20. package/lib/types/elements/adw-toolbar-view.d.ts +8 -0
  21. package/lib/types/elements/modal-surface.d.ts +59 -0
  22. package/lib/types/elements/roving-focus.d.ts +26 -0
  23. package/lib/types/elements/row-activation.d.ts +26 -0
  24. package/lib/types/empty-sections.d.ts +15 -0
  25. package/lib/types/empty-sections.spec.d.ts +1 -0
  26. package/lib/types/fonts.d.ts +1 -0
  27. package/lib/types/icon-registry.d.ts +35 -0
  28. package/lib/types/icon-registry.spec.d.ts +18 -0
  29. package/lib/types/index.d.ts +3 -1
  30. package/lib/types/keyboard-operable.spec.d.ts +1 -0
  31. package/lib/types/scroll-shading.d.ts +56 -0
  32. package/lib/types/styles.generated.d.ts +1 -1
  33. package/package.json +10 -6
  34. package/scss/_banner.scss +1 -1
  35. package/scss/_carousel.scss +15 -1
  36. package/scss/_checks.scss +8 -8
  37. package/scss/_combo_row.scss +1 -1
  38. package/scss/_drop_down.scss +1 -1
  39. package/scss/_expander_row.scss +1 -1
  40. package/scss/_headerbar.scss +35 -6
  41. package/scss/_icon.scss +47 -0
  42. package/scss/_icons.generated.scss +36 -6
  43. package/scss/_password_entry_row.scss +1 -1
  44. package/scss/_preferences_dialog.scss +3 -2
  45. package/scss/_progress_bar.scss +1 -1
  46. package/scss/_row.scss +1 -1
  47. package/scss/_scrolling.scss +107 -0
  48. package/scss/_status_page.scss +1 -1
  49. package/scss/_style_classes.scss +21 -8
  50. package/scss/_tab_view.scss +1 -1
  51. package/scss/_theme.scss +1 -0
  52. package/scss/_toast.scss +6 -24
  53. package/scss/_toolbar_view.scss +52 -22
  54. package/scss/_variables.scss +30 -3
  55. package/scss/_view_switcher.scss +80 -20
  56. package/scss/adwaita-skin.scss +1 -0
  57. package/src/adw-action-rows.spec.ts +21 -9
  58. package/src/adw-button-content.spec.ts +11 -6
  59. package/src/adw-carousel.spec.ts +141 -1
  60. package/src/adw-fonts.spec.ts +188 -0
  61. package/src/adw-header-bar.spec.ts +92 -0
  62. package/src/adw-icon.spec.ts +17 -8
  63. package/src/adw-row-state.spec.ts +11 -1
  64. package/src/adw-tab-view.spec.ts +151 -0
  65. package/src/bottom-sheet.spec.ts +87 -0
  66. package/src/breakpoints.ts +18 -4
  67. package/src/chrome.spec.ts +233 -0
  68. package/src/connect-lifecycle.spec.ts +351 -0
  69. package/src/elements/adw-about-dialog.ts +24 -18
  70. package/src/elements/adw-action-row.ts +14 -2
  71. package/src/elements/adw-alert-dialog.ts +39 -20
  72. package/src/elements/adw-button-row.ts +3 -0
  73. package/src/elements/adw-carousel.ts +121 -4
  74. package/src/elements/adw-clamp.ts +3 -2
  75. package/src/elements/adw-data-grid.ts +5 -2
  76. package/src/elements/adw-dialog.ts +16 -63
  77. package/src/elements/adw-expander-row.ts +18 -4
  78. package/src/elements/adw-header-bar.ts +2 -0
  79. package/src/elements/adw-icon.ts +32 -6
  80. package/src/elements/adw-inline-view-switcher.ts +12 -0
  81. package/src/elements/adw-menu-button.ts +17 -10
  82. package/src/elements/adw-navigation-split-view.ts +30 -7
  83. package/src/elements/adw-overlay-split-view.ts +15 -3
  84. package/src/elements/adw-popover.ts +11 -0
  85. package/src/elements/adw-preferences-dialog.ts +18 -16
  86. package/src/elements/adw-preferences-group.ts +7 -0
  87. package/src/elements/adw-sidebar.ts +15 -0
  88. package/src/elements/adw-status-page.ts +12 -4
  89. package/src/elements/adw-switch-row.ts +7 -0
  90. package/src/elements/adw-switch.ts +19 -1
  91. package/src/elements/adw-tab-view.ts +70 -7
  92. package/src/elements/adw-toast-overlay.ts +8 -1
  93. package/src/elements/adw-toggle-group.ts +94 -2
  94. package/src/elements/adw-toolbar-view.ts +28 -2
  95. package/src/elements/adw-view-switcher-bar.ts +18 -0
  96. package/src/elements/adw-view-switcher.ts +14 -0
  97. package/src/elements/modal-surface.ts +180 -0
  98. package/src/elements/roving-focus.ts +154 -0
  99. package/src/elements/row-activation.ts +92 -0
  100. package/src/empty-sections.spec.ts +81 -0
  101. package/src/empty-sections.ts +54 -0
  102. package/src/fonts.ts +25 -0
  103. package/src/icon-registry.spec.ts +240 -0
  104. package/src/icon-registry.ts +157 -0
  105. package/src/index.ts +30 -1
  106. package/src/keyboard-operable.spec.ts +755 -0
  107. package/src/scroll-shading.ts +255 -0
  108. package/src/source-view/theme.ts +15 -3
  109. package/src/split-button.spec.ts +6 -2
  110. package/src/style-classes.spec.ts +6 -0
  111. package/src/styles.generated.ts +1 -1
  112. package/src/test.browser.mts +14 -0
  113. package/src/view-switcher.spec.ts +149 -0
@@ -0,0 +1,157 @@
1
+ // The runtime half of icon resolution — how a name OUTSIDE the compiled set gets drawn.
2
+ //
3
+ // `scripts/build-scss.mjs` inlines a chosen subset of `@gjsify/adwaita-icons` into the
4
+ // stylesheet as `--icon-<name>` custom properties plus matching `.adw-icon--<name>` mask
5
+ // classes. The subset exists because the whole set does not fit — inlining every icon that
6
+ // package exports costs a measured ~1.07 MB of data-URI, some five times the stylesheet it
7
+ // would sit in — so a consumer whose app needs a glyph this package does not ship needs a
8
+ // way in that is not "wait for a release".
9
+ //
10
+ // That is this module. `registerIcon` writes the SAME pair the generator writes — the
11
+ // custom property on the document element, the mask class into a stylesheet this module
12
+ // owns — so a registered icon is indistinguishable from a compiled one at every later
13
+ // point: `<adw-icon icon-name="…">`, a hand-written `class="adw-icon adw-icon--…"`, a
14
+ // widget attribute, `var(--icon-…)` in the consumer's own CSS.
15
+ //
16
+ // import { registerIcon } from '@gjsify/adwaita-web';
17
+ // import { dialogErrorSymbolic } from '@gjsify/adwaita-icons/status';
18
+ //
19
+ // registerIcon('dialog-error-symbolic', dialogErrorSymbolic);
20
+ // // …then anywhere: <adw-icon icon-name="dialog-error-symbolic"></adw-icon>
21
+ //
22
+ // Register BEFORE the icon is mounted where you can. An `<adw-icon>` already in the
23
+ // document draws the `image-missing` fallback (see `_icon.scss`) until the registration
24
+ // lands; the CSS is live, so it corrects itself on the next style recalculation with no
25
+ // re-render, but there is a visible frame in between.
26
+ //
27
+ // Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+ — the icon SVGs.
28
+ // Original implementation.
29
+
30
+ import { normalizeIconName } from '@gjsify/adwaita-core';
31
+ import { toDataUri } from '@gjsify/adwaita-icons/utils';
32
+
33
+ import { MASK_CLASS_PREFIX } from './elements/adw-icon.js';
34
+
35
+ /** Marks the one `<style>` element this module owns, so a reload reuses it. */
36
+ const REGISTRY_STYLE_ID = 'adwaita-web-icon-registry';
37
+
38
+ /**
39
+ * Names already given a mask rule. Re-registering a name has to update the GLYPH without
40
+ * appending a second rule: the custom property carries the glyph and the rule only points
41
+ * at it, so the property assignment is the whole update and a duplicate rule would be
42
+ * dead weight that grows without bound under a live theme switcher.
43
+ */
44
+ const ruled = new Set<string>();
45
+
46
+ /** The one rule a registration adds: the class, pointing at the custom property. */
47
+ function insertMaskRule(sheet: CSSStyleSheet, name: string): void {
48
+ sheet.insertRule(
49
+ `.${MASK_CLASS_PREFIX}${name} { mask-image: var(--icon-${name}); ` +
50
+ `-webkit-mask-image: var(--icon-${name}); }`,
51
+ sheet.cssRules.length,
52
+ );
53
+ }
54
+
55
+ /** This module's stylesheet, created on first use and appended last so nothing hides it. */
56
+ function registrySheet(): CSSStyleSheet {
57
+ const existing = document.getElementById(REGISTRY_STYLE_ID) as HTMLStyleElement | null;
58
+ if (existing?.sheet) return existing.sheet;
59
+
60
+ const style = document.createElement('style');
61
+ style.id = REGISTRY_STYLE_ID;
62
+ document.head.appendChild(style);
63
+ // `sheet` is non-null the moment a <style> is in a document — the property is only
64
+ // null while the element is disconnected, which the append above rules out.
65
+ const sheet = style.sheet as CSSStyleSheet;
66
+
67
+ // Reaching here a SECOND time means the previous element was taken away — an SPA head
68
+ // swap, an Astro view transition — and every rule it held went with it. The custom
69
+ // properties did NOT: they sit on the document element. So re-issue the rules, or every
70
+ // name registered before the swap keeps its glyph in a property nothing consumes and
71
+ // draws the fallback forever. Nothing observes the head, so the recovery happens on the
72
+ // next `registerIcon` call rather than at the moment of the swap.
73
+ for (const name of ruled) insertMaskRule(sheet, name);
74
+ return sheet;
75
+ }
76
+
77
+ /**
78
+ * The second argument has to be SVG SOURCE, and nothing else can tell us so: `toDataUri`
79
+ * percent-encodes whatever it is handed, so `registerIcon('x', 'not an svg')` produces a
80
+ * perfectly well-formed `mask-image` that masks NOTHING — an icon strictly worse than an
81
+ * unregistered one, because the registered class beats `:where(.adw-icon)` and switches the
82
+ * `image-missing` fallback OFF, while `isIconAvailable` reports `true`. Measured in Firefox:
83
+ * `url("data:image/svg+xml,not%20an%20svg")`, 0 % of the box painted, no throw, no warning.
84
+ *
85
+ * A real parser rather than a `^<svg` test: an icon read from a file carries an XML prolog
86
+ * or a DOCTYPE before its root element, and both are legitimate here.
87
+ */
88
+ function svgRootOf(source: string): Element {
89
+ return new DOMParser().parseFromString(source, 'image/svg+xml').documentElement;
90
+ }
91
+
92
+ /**
93
+ * Make `name` drawable by every `.adw-icon` in the document, from an Adwaita symbolic SVG
94
+ * source string (what `@gjsify/adwaita-icons` exports).
95
+ *
96
+ * BOTH arguments are checked and a wrong one THROWS rather than registering nothing: unlike
97
+ * a render, this is an explicit call with a wrong argument, and silently doing nothing is
98
+ * the failure mode this whole area exists to end. The name is normalized exactly as
99
+ * `<adw-icon icon-name>` normalizes it — a single `-symbolic` suffix comes off, and the rest
100
+ * must be one CSS token; the SVG has to parse to an `<svg>` root. `<adw-icon>` draws
101
+ * `image-missing` for the same unusable name instead — there it came from markup, and
102
+ * taking the page down over a typo is not proportionate.
103
+ *
104
+ * Registering a name the stylesheet already compiles is allowed and REPLACES the glyph:
105
+ * the property is set on the document element, so it wins over the `:root` rule.
106
+ *
107
+ * CLIENT-SIDE ONLY: this touches `document.documentElement` and `document.head`, so at
108
+ * module top level in an SSR/SSG build it throws before a page is rendered.
109
+ */
110
+ export function registerIcon(name: string, svg: string): void {
111
+ const resolved = normalizeIconName(name);
112
+ if (resolved === '') {
113
+ throw new TypeError(
114
+ `registerIcon: ${JSON.stringify(name)} is not usable as an icon name — it has to be a single ` +
115
+ 'CSS token ([A-Za-z0-9_-]+) once an optional `-symbolic` suffix is removed.',
116
+ );
117
+ }
118
+
119
+ const root = svgRootOf(svg);
120
+ if (root.tagName !== 'svg') {
121
+ throw new TypeError(
122
+ `registerIcon(${JSON.stringify(resolved)}): the second argument has to be SVG SOURCE — the ` +
123
+ 'kind of string `@gjsify/adwaita-icons` exports, not a URL, a data-URI or a DOM node. ' +
124
+ `Parsing it produced <${root.tagName}>, so it would have masked nothing at all.`,
125
+ );
126
+ }
127
+
128
+ document.documentElement.style.setProperty(`--icon-${resolved}`, toDataUri(svg));
129
+
130
+ // BEFORE the `ruled` early return: taking the sheet is what detects a lost <style>,
131
+ // and a name already in `ruled` is exactly the one the early return would skip.
132
+ const sheet = registrySheet();
133
+ if (ruled.has(resolved)) return;
134
+ insertMaskRule(sheet, resolved);
135
+ ruled.add(resolved);
136
+ }
137
+
138
+ /**
139
+ * Whether `name` has a glyph — compiled into the stylesheet, or registered at runtime.
140
+ *
141
+ * Reads the live cascade rather than a generated list, so it answers for a glyph that
142
+ * arrived by any route, including a consumer's own `--icon-<name>` declaration. It is
143
+ * deliberately the CUSTOM PROPERTY that is read and not the rendered `mask-image` of a
144
+ * probe element: since `_icon.scss` gives every icon box the `image-missing` fallback,
145
+ * a rendered mask is never `none`, and comparing against the fallback would answer
146
+ * `false` for `image-missing` itself — the one name where being drawn and having fallen
147
+ * back look identical.
148
+ *
149
+ * Narrowing worth knowing: a consumer who writes `.adw-icon--x { mask-image: url(…) }`
150
+ * with no matching `--icon-x` gets `false` here and a drawn icon anyway. The generator and
151
+ * {@link registerIcon} are the two things that create the pair, and both create both.
152
+ */
153
+ export function isIconAvailable(name: string): boolean {
154
+ const resolved = normalizeIconName(name);
155
+ if (resolved === '') return false;
156
+ return getComputedStyle(document.documentElement).getPropertyValue(`--icon-${resolved}`).trim() !== '';
157
+ }
package/src/index.ts CHANGED
@@ -5,9 +5,26 @@
5
5
  // SCSS partials at `@gjsify/adwaita-web/scss/...` for custom theming.
6
6
  // Reference: refs/libadwaita (colors/sizing), refs/adwaita-web (component patterns).
7
7
 
8
- import '@gjsify/adwaita-fonts'; // Registers @font-face (fontsource pattern)
9
8
  import { ADWAITA_WEB_CSS } from './styles.generated.js';
10
9
 
10
+ // NO font import here, and the absence is the decision. This line used to be
11
+ // `import '@gjsify/adwaita-fonts';` with the comment "Registers @font-face
12
+ // (fontsource pattern)". It registered nothing: that package's `.` export is
13
+ // `index.css`, css-as-string turns it into `export default "<css>"`, and a
14
+ // SIDE-EFFECT import of a module with no side effect is tree-shaken — measured
15
+ // on 0.41.0 as a 0-byte bundle with zero `@font-face`, exit 0. It was invisible
16
+ // because the primary target platform installs the typeface system-wide, so
17
+ // every local screenshot looked right.
18
+ //
19
+ // The faces now travel through `@gjsify/adwaita-web/fonts` → the generated
20
+ // `@gjsify/adwaita-fonts/embedded`, as `data:` URIs behind a VALUE import, which
21
+ // is the shape a bundler cannot discard. It stays OUT of this entry because it
22
+ // is 2.39 MB / 1.18 MB gzip of base64 against a 190 KB / 26 KB stylesheet, and
23
+ // `--app browser` has no code splitting (`output.inlineDynamicImports`), so a
24
+ // lazy face would cost the same bytes as an eager one. The stacks that head with
25
+ // `'Adwaita Sans'` / `'Adwaita Mono'` are a progressive enhancement over a
26
+ // system-installed face, declared as such in `status/stylesheet-font-families.json`.
27
+
11
28
  // Self-applied so `import '@gjsify/adwaita-web'` styles the components under ANY bundler:
12
29
  // a separate `import '@gjsify/adwaita-web/style.css'` is a no-op under a gjsify
13
30
  // `--app browser` build, where css-as-string yields a string a side-effect import
@@ -34,6 +51,18 @@ export {
34
51
  } from './accent.js';
35
52
  export type { ApplyAccentOptions } from './accent.js';
36
53
 
54
+ // GtkScrolledWindow's undershoot/overshoot indicators. `adw-toolbar-view` drives
55
+ // this itself; exported so a consumer that owns its own chrome can shade a scroller
56
+ // without one.
57
+ export { AdwScrollShading } from './scroll-shading.js';
58
+ export type { AdwUndershootEdges } from './scroll-shading.js';
59
+
60
+ // The stylesheet compiles a chosen SUBSET of `@gjsify/adwaita-icons` (the whole set is
61
+ // ~1.07 MB of data-URI), so a name outside it draws the `image-missing` fallback. This is
62
+ // the way in for an app that needs a glyph this package does not ship — see
63
+ // `icon-registry.ts` for the full recipe.
64
+ export { isIconAvailable, registerIcon } from './icon-registry.js';
65
+
37
66
  export { AdwAvatar } from './elements/adw-avatar.js';
38
67
  export { AdwBanner } from './elements/adw-banner.js';
39
68
  export { AdwCard } from './elements/adw-card.js';