@gjsify/adwaita-storybook 0.38.0 → 0.39.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.
|
@@ -6,7 +6,7 @@ export type StoryArgsListener = (args: StoryArgs) => void;
|
|
|
6
6
|
* Base class for browser story elements.
|
|
7
7
|
*
|
|
8
8
|
* Provides default chrome — a clamped boxed group with a title + description above a
|
|
9
|
-
* centered,
|
|
9
|
+
* centered, tinted preview stage. Simple stories compose their preview by
|
|
10
10
|
* calling {@link addContent}; a subclass needing a bespoke layout passes its own root
|
|
11
11
|
* element to the constructor, and {@link addContent} is then a no-op.
|
|
12
12
|
*/
|
package/lib/types/styles.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const STORYBOOK_WEB_CSS = "\n/* Appearance dialog \u2014 round swatches with a selection ring. The web twin of the GTK\n renderer's rules, themselves adapted from Learn6502.\n\n The CARD is not decoration: in the dark scheme the \"dark\" swatch is a near-black\n circle on a near-black dialog and is invisible without a lighter surface behind\n it. Centring happens INSIDE the full-width card, so the row does not read as\n left-aligned under the group title. */\n.sb-swatch-card {\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n gap: 8px;\n padding: 12px;\n}\n/* Squaring the card's top alone leaves a notch: the row above lives in the group's\n own rounded list, so BOTH sides have to be squared for the join to read as one\n list. The attached card and the group rule below are the two halves of that. */\n.sb-swatch-card--attached {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n.sb-attached-group .boxed-list {\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n}\n/* Desaturated rather than merely dimmed: while the accent follows the desktop the\n palette must read as unavailable, not as nine slightly faded choices. */\n.sb-swatch-card--disabled {\n filter: saturate(0.4) brightness(1.05);\n opacity: 0.7;\n}\n/* A radio input, so grouping and arrow-key navigation are the platform's. Its own\n glyph is switched off because the swatch IS the indicator \u2014 a dot drawn on a\n colour competes with the ring that already says which one is selected. */\n.sb-swatch {\n appearance: none;\n -webkit-appearance: none;\n margin: 0;\n width: 24px;\n height: 24px;\n border-radius: 9999px;\n box-shadow: 0 0 0 3px transparent;\n cursor: pointer;\n /* A hairline on the fill itself. Without it the LIGHT scheme swatch is a\n near-white circle on a near-white card and vanishes \u2014 the exact mirror of the\n dark swatch on a dark dialog, and a card alone only fixes one direction.\n --border-color is a currentColor mix, so it holds in both schemes. */\n border: 1px solid var(--border-color);\n}\n.sb-swatch:checked {\n box-shadow: 0 0 0 3px var(--accent-bg-color);\n}\n.sb-swatch:focus-visible {\n outline: 2px solid var(--accent-color);\n outline-offset: 3px;\n}\n.sb-swatch:disabled {\n cursor: default;\n}\n/* The scheme swatches are larger \u2014 they are the dialog's primary choice. */\n.sb-swatch--scheme {\n width: 44px;\n height: 44px;\n}\n/* \"Follow system\" is the two schemes meeting on a diagonal, so the option looks like\n what it does instead of needing a word for it. */\n/* light_2 / dark_4 from libadwaita's palette (_palette.scss:37,44), which is what\n Learn6502's selector uses. NOT light_1 (#ffffff): pure white is invisible against\n a light card even with the hairline, and light_2 is the shade the palette offers\n for exactly this. */\n.sb-swatch--scheme-system {\n background: linear-gradient(to bottom right, #f6f5f4 49.99%, #241f31 50.01%);\n}\n.sb-swatch--scheme-light {\n background-color: #f6f5f4;\n}\n.sb-swatch--scheme-dark {\n background-color: #241f31;\n}\n\n.sb-window {\n width: 100%;\n height: 100%;\n min-width: 0;\n min-height: 0;\n}\n\n/* Each pane in the outer split view is a flex column that fills its height. */\n.sb-sidebar-pane,\n.sb-content-pane {\n display: flex;\n flex-direction: column;\n min-height: 0;\n min-width: 0;\n flex: 1 1 auto;\n}\n\n/* The toolbar-view content holds either a scroller (sidebar) or the split view\n (content); the inner panes own the scrolling, so this level never scrolls. */\n.sb-sidebar-pane adw-toolbar-view > .adw-toolbar-view-content,\n.sb-content-pane adw-toolbar-view > .adw-toolbar-view-content {\n overflow: hidden;\n}\n\n/* Background scheme: the content header + preview + controls all sit on the\n window (story) background as one surface; only the left story-list column\n (its header + list) keeps the distinct sidebar shade. Matches the native\n renderer. The default adw-header-bar background is its own headerbar shade,\n so override it per pane here. */\n.sb-content-pane adw-header-bar {\n background-color: var(--window-bg-color);\n}\n.sb-sidebar-pane adw-header-bar {\n background-color: var(--sidebar-bg-color);\n}\n\n.sb-sidebar-scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n background-color: var(--sidebar-bg-color);\n}\n\n/* --- Story list (mirrors GTK .navigation-sidebar) --- */\n.sb-sidebar-list {\n padding: var(--spacing-xs);\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n\n.sb-category {\n font-size: var(--font-size-small);\n font-weight: 700;\n opacity: var(--dim-opacity);\n padding: 10px var(--spacing-m) 4px;\n}\n\n.sb-story-row {\n padding: var(--spacing-xs) var(--spacing-m);\n margin: 0 var(--spacing-xs);\n border-radius: var(--button-radius);\n cursor: pointer;\n color: var(--window-fg-color);\n font-size: var(--font-size-base);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sb-story-row:hover {\n background-color: var(--button-bg-color);\n}\n\n.sb-story-row.selected {\n background-color: var(--accent-bg-color);\n color: var(--accent-fg-color);\n}\n\n/* --- Preview area --- */\n.sb-preview-scroll {\n flex: 1 1 auto;\n min-height: 0;\n min-width: 0;\n overflow: auto;\n background-color: var(--window-bg-color);\n}\n\n.sb-story-page {\n display: flex;\n justify-content: center;\n padding: var(--spacing-xl) var(--spacing-m);\n min-height: 100%;\n box-sizing: border-box;\n}\n\n.sb-story-clamp {\n width: 100%;\n max-width: 600px;\n}\n\n.sb-story-group-header {\n padding: var(--spacing-xs) 2px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.sb-story-group-title {\n font-size: var(--font-size-heading);\n font-weight: 700;\n color: var(--window-fg-color);\n}\n\n.sb-story-group-description {\n font-size: var(--font-size-base);\n opacity: var(--dim-opacity);\n color: var(--window-fg-color);\n}\n\n/* Centered, dashed-framed preview stage (mirrors the native .story-stage). */\n.story-stage {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--spacing-m);\n padding: var(--spacing-l);\n margin-top: var(--spacing-s);\n border: 1px dashed rgba(127, 127, 127, 0.28);\n border-radius: var(--card-radius);\n min-height: 80px;\n}\n\n/* --- Controls panel --- */\n.sb-controls-scroll {\n height: 100%;\n overflow-y: auto;\n background-color: var(--window-bg-color);\n}\n\n.sb-controls-page {\n padding: var(--spacing-l);\n}\n\n/* Range-control card (mirrors the native .story-range-row). */\n.sb-range-row {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-xs);\n padding: var(--spacing-m);\n border-bottom: 1px solid var(--card-shade-color);\n}\n\n.sb-range-row:last-child {\n border-bottom: none;\n}\n\n.sb-range-header {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n gap: var(--spacing-m);\n}\n\n.sb-range-title {\n font-size: var(--font-size-base);\n font-weight: 700;\n color: var(--card-fg-color);\n}\n\n.sb-range-value {\n font-size: var(--font-size-base);\n font-variant-numeric: tabular-nums;\n opacity: var(--dim-opacity);\n}\n\n.sb-range-description {\n font-size: var(--font-size-small);\n opacity: var(--dim-opacity);\n}\n\n.sb-range-input {\n width: 100%;\n accent-color: var(--accent-bg-color);\n}\n\n.sb-color-input {\n width: 42px;\n height: 28px;\n padding: 0;\n border: 1px solid var(--card-shade-color);\n border-radius: var(--button-radius);\n background: none;\n cursor: pointer;\n}\n";
|
|
1
|
+
export declare const STORYBOOK_WEB_CSS = "\n/* Appearance dialog \u2014 round swatches with a selection ring. The web twin of the GTK\n renderer's rules, themselves adapted from Learn6502.\n\n The CARD is not decoration: in the dark scheme the \"dark\" swatch is a near-black\n circle on a near-black dialog and is invisible without a lighter surface behind\n it. Centring happens INSIDE the full-width card, so the row does not read as\n left-aligned under the group title. */\n.sb-swatch-card {\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n gap: 8px;\n padding: 12px;\n}\n/* Squaring the card's top alone leaves a notch: the row above lives in the group's\n own rounded list, so BOTH sides have to be squared for the join to read as one\n list. The attached card and the group rule below are the two halves of that. */\n.sb-swatch-card--attached {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n.sb-attached-group .boxed-list {\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n}\n/* Desaturated rather than merely dimmed: while the accent follows the desktop the\n palette must read as unavailable, not as nine slightly faded choices. */\n.sb-swatch-card--disabled {\n filter: saturate(0.4) brightness(1.05);\n opacity: 0.7;\n}\n/* A radio input, so grouping and arrow-key navigation are the platform's. Its own\n glyph is switched off because the swatch IS the indicator \u2014 a dot drawn on a\n colour competes with the ring that already says which one is selected. */\n.sb-swatch {\n appearance: none;\n -webkit-appearance: none;\n margin: 0;\n width: 24px;\n height: 24px;\n border-radius: 9999px;\n box-shadow: 0 0 0 3px transparent;\n cursor: pointer;\n /* A hairline on the fill itself. Without it the LIGHT scheme swatch is a\n near-white circle on a near-white card and vanishes \u2014 the exact mirror of the\n dark swatch on a dark dialog, and a card alone only fixes one direction.\n --border-color is a currentColor mix, so it holds in both schemes. */\n border: 1px solid var(--border-color);\n}\n.sb-swatch:checked {\n box-shadow: 0 0 0 3px var(--accent-bg-color);\n}\n.sb-swatch:focus-visible {\n outline: 2px solid var(--accent-color);\n outline-offset: 3px;\n}\n.sb-swatch:disabled {\n cursor: default;\n}\n/* The scheme swatches are larger \u2014 they are the dialog's primary choice. */\n.sb-swatch--scheme {\n width: 44px;\n height: 44px;\n}\n/* \"Follow system\" is the two schemes meeting on a diagonal, so the option looks like\n what it does instead of needing a word for it. */\n/* light_2 / dark_4 from libadwaita's palette (_palette.scss:37,44), which is what\n Learn6502's selector uses. NOT light_1 (#ffffff): pure white is invisible against\n a light card even with the hairline, and light_2 is the shade the palette offers\n for exactly this. */\n.sb-swatch--scheme-system {\n background: linear-gradient(to bottom right, #f6f5f4 49.99%, #241f31 50.01%);\n}\n.sb-swatch--scheme-light {\n background-color: #f6f5f4;\n}\n.sb-swatch--scheme-dark {\n background-color: #241f31;\n}\n\n.sb-window {\n width: 100%;\n height: 100%;\n min-width: 0;\n min-height: 0;\n}\n\n/* Each pane in the outer split view is a flex column that fills its height. */\n.sb-sidebar-pane,\n.sb-content-pane {\n display: flex;\n flex-direction: column;\n min-height: 0;\n min-width: 0;\n flex: 1 1 auto;\n}\n\n/* The toolbar-view content holds either a scroller (sidebar) or the split view\n (content); the inner panes own the scrolling, so this level never scrolls. */\n.sb-sidebar-pane adw-toolbar-view > .adw-toolbar-view-content,\n.sb-content-pane adw-toolbar-view > .adw-toolbar-view-content {\n overflow: hidden;\n}\n\n/* Background scheme: the content header + preview + controls all sit on the\n window (story) background as one surface; only the left story-list column\n (its header + list) keeps the distinct sidebar shade. Matches the native\n renderer. The default adw-header-bar background is its own headerbar shade,\n so override it per pane here. */\n.sb-content-pane adw-header-bar {\n background-color: var(--window-bg-color);\n}\n.sb-sidebar-pane adw-header-bar {\n background-color: var(--sidebar-bg-color);\n}\n\n.sb-sidebar-scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n background-color: var(--sidebar-bg-color);\n}\n\n/* --- Story list (mirrors GTK .navigation-sidebar) --- */\n.sb-sidebar-list {\n padding: var(--spacing-xs);\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n\n.sb-category {\n font-size: var(--font-size-small);\n font-weight: 700;\n opacity: var(--dim-opacity);\n padding: 10px var(--spacing-m) 4px;\n}\n\n.sb-story-row {\n padding: var(--spacing-xs) var(--spacing-m);\n margin: 0 var(--spacing-xs);\n border-radius: var(--button-radius);\n cursor: pointer;\n color: var(--window-fg-color);\n font-size: var(--font-size-base);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sb-story-row:hover {\n background-color: var(--button-bg-color);\n}\n\n.sb-story-row.selected {\n background-color: var(--accent-bg-color);\n color: var(--accent-fg-color);\n}\n\n/* --- Preview area --- */\n\n/* The slotted content pane has to PASS ITS BOUND DOWN, or the scroller below it has\n nothing to scroll against.\n\n Measured before this rule existed: .adw-osv-content bounded itself correctly at\n 697px while its content wanted 1008px, but this div \u2014 display: block,\n min-height: auto, flex: 0 1 auto \u2014 ignored that and grew to 1008. So\n .sb-preview-scroll inherited clientHeight === scrollHeight, had nothing to scroll,\n and everything past the fold was clipped by the toolbar view's overflow: hidden:\n a tall story just ended mid-page.\n\n The sidebar never had this. Its scroller sits directly inside the toolbar view's own\n flex content wrapper, so nothing breaks the chain there; this pane is the one place\n a plain div sits between the bound and the scroller.\n\n NOTE: no backticks in this file's comments \u2014 the whole stylesheet is a JS template\n literal, so a backtick ends it and the parse error lands nine lines away. */\n.sb-preview-pane {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-height: 0;\n min-width: 0;\n}\n\n/* The tint belongs to the whole preview AREA, not to a box inside it. On the website\n it sits on the preview panel and the stage within is only centring; on the stage it\n drew a grey rectangle floating in the pane with no defined edge, which is how it read.\n\n HERE and not on .sb-preview-pane, measured: this element paints an OPAQUE\n background-color, so a gradient one level up is covered completely. Sampling five\n points across the pane returned the identical colour while the computed style still\n reported a background-image \u2014 true and meaningless.\n\n A background on a scroll container is painted against its padding box, so it stays\n put while the content scrolls, the way a wallpaper does. */\n.sb-preview-scroll {\n flex: 1 1 auto;\n min-height: 0;\n min-width: 0;\n overflow: auto;\n background-color: var(--window-bg-color);\n background-image:\n radial-gradient(circle at 0% 0%, color-mix(in srgb, var(--accent-color) 7%, transparent), transparent 45%),\n radial-gradient(circle at 100% 100%, color-mix(in srgb, #926ee4 6%, transparent), transparent 45%);\n}\n\n.sb-story-page {\n display: flex;\n justify-content: center;\n padding: var(--spacing-xl) var(--spacing-m);\n min-height: 100%;\n box-sizing: border-box;\n}\n\n.sb-story-clamp {\n width: 100%;\n max-width: 600px;\n}\n\n.sb-story-group-header {\n padding: var(--spacing-xs) 2px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.sb-story-group-title {\n font-size: var(--font-size-heading);\n font-weight: 700;\n color: var(--window-fg-color);\n}\n\n.sb-story-group-description {\n font-size: var(--font-size-base);\n opacity: var(--dim-opacity);\n color: var(--window-fg-color);\n}\n\n/* Centring and breathing room only \u2014 the surface is the pane's.\n\n The stage carried the tint first, and against the pane's own background that read\n as a grey rectangle floating with no edge rather than as a surface. It has no\n border-radius for the same reason: nothing here is a box any more.\n\n The requirement the tint answers is unchanged \u2014 the preview area stays locatable\n when the widget on it is transparent or empty \u2014 it is just answered by the AREA\n now instead of by a rectangle inside it. Scheme handling moved with it, see\n .sb-preview-pane. */\n.story-stage {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--spacing-m);\n padding: var(--spacing-l);\n margin-top: var(--spacing-s);\n min-height: 80px;\n}\n\n/* --- Controls panel --- */\n.sb-controls-scroll {\n height: 100%;\n overflow-y: auto;\n background-color: var(--window-bg-color);\n}\n\n.sb-controls-page {\n padding: var(--spacing-l);\n}\n\n/* Range-control card (mirrors the native .story-range-row). */\n.sb-range-row {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-xs);\n padding: var(--spacing-m);\n border-bottom: 1px solid var(--card-shade-color);\n}\n\n.sb-range-row:last-child {\n border-bottom: none;\n}\n\n.sb-range-header {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n gap: var(--spacing-m);\n}\n\n.sb-range-title {\n font-size: var(--font-size-base);\n font-weight: 700;\n color: var(--card-fg-color);\n}\n\n.sb-range-value {\n font-size: var(--font-size-base);\n font-variant-numeric: tabular-nums;\n opacity: var(--dim-opacity);\n}\n\n.sb-range-description {\n font-size: var(--font-size-small);\n opacity: var(--dim-opacity);\n}\n\n.sb-range-input {\n width: 100%;\n accent-color: var(--accent-bg-color);\n}\n\n.sb-color-input {\n width: 42px;\n height: 28px;\n padding: 0;\n border: 1px solid var(--card-shade-color);\n border-radius: var(--button-radius);\n background: none;\n cursor: pointer;\n}\n";
|
|
2
2
|
/** Inject the chrome stylesheet once (idempotent, browser-only). */
|
|
3
3
|
export declare function injectStorybookStyles(): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/adwaita-storybook",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "Browser renderer for the @gjsify/stories contract — the web counterpart of @gjsify/storybook, built on @gjsify/adwaita-web",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"clear": "gjsify clear lib tsconfig.tsbuildinfo",
|
|
20
20
|
"check": "gjsify tsc --noEmit",
|
|
21
21
|
"build": "gjsify run build:types",
|
|
22
|
-
"build:types": "gjsify tsc"
|
|
22
|
+
"build:types": "gjsify tsc",
|
|
23
|
+
"build:test:browser": "gjsify build src/test.browser.mts --app browser --outfile dist/test.browser.mjs"
|
|
23
24
|
},
|
|
24
25
|
"keywords": [
|
|
25
26
|
"storybook",
|
|
@@ -29,13 +30,13 @@
|
|
|
29
30
|
"browser"
|
|
30
31
|
],
|
|
31
32
|
"dependencies": {
|
|
32
|
-
"@gjsify/adwaita-core": "^0.
|
|
33
|
-
"@gjsify/adwaita-web": "^0.
|
|
34
|
-
"@gjsify/stories": "^0.
|
|
35
|
-
"@gjsify/storybook-core": "^0.
|
|
33
|
+
"@gjsify/adwaita-core": "^0.39.0",
|
|
34
|
+
"@gjsify/adwaita-web": "^0.39.0",
|
|
35
|
+
"@gjsify/stories": "^0.39.0",
|
|
36
|
+
"@gjsify/storybook-core": "^0.39.0"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
|
-
"@gjsify/cli": "^0.
|
|
39
|
+
"@gjsify/cli": "^0.39.0",
|
|
39
40
|
"typescript": "^6.0.3"
|
|
40
41
|
},
|
|
41
42
|
"gjsify": {
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Where the preview area's scroll BOUND actually lands.
|
|
2
|
+
//
|
|
3
|
+
// WHY THIS IS THE FIRST TEST IN THIS PACKAGE. The web preview stopped scrolling
|
|
4
|
+
// entirely: a tall story ended mid-page and nothing below was reachable. Every
|
|
5
|
+
// layer's CSS was individually valid — the overlay split view held itself to the
|
|
6
|
+
// window height, the scroller declared overflow: auto and min-height: 0 — but the
|
|
7
|
+
// slotted pane between them had no rule in this target, so it grew to the content's
|
|
8
|
+
// height and handed the scroller a bound equal to its own content. clientHeight ===
|
|
9
|
+
// scrollHeight, nothing to scroll, remainder clipped by an overflow: hidden further
|
|
10
|
+
// up.
|
|
11
|
+
//
|
|
12
|
+
// Nothing could have caught that by reading state or class names, which is all the
|
|
13
|
+
// sibling suites do. The rule this encodes: a layout has to be asserted by WHERE
|
|
14
|
+
// things land — measure the box, not the declaration.
|
|
15
|
+
//
|
|
16
|
+
// Runs in the tests/browser Playwright harness, which drives FIREFOX in CI, not just
|
|
17
|
+
// Chromium.
|
|
18
|
+
|
|
19
|
+
import { describe, expect, it } from '@gjsify/unit';
|
|
20
|
+
|
|
21
|
+
// Registers the custom elements the storybook chrome is built from, and injects the
|
|
22
|
+
// adwaita-web stylesheet. Without it the panes are unstyled inline boxes and every
|
|
23
|
+
// measurement below would be meaningless rather than wrong.
|
|
24
|
+
import '@gjsify/adwaita-web';
|
|
25
|
+
|
|
26
|
+
import type { StoryMeta } from '@gjsify/stories';
|
|
27
|
+
import { StoryElement } from './story-element.js';
|
|
28
|
+
import { mountStorybook } from './app.js';
|
|
29
|
+
import type { WebStoryModule } from './types.js';
|
|
30
|
+
|
|
31
|
+
/** A story deliberately taller than any host we mount it in. */
|
|
32
|
+
class TallStory extends StoryElement {
|
|
33
|
+
constructor() {
|
|
34
|
+
super(TallStory.getMetadata(), 'Default');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Part of the authoring contract the registry instantiates through. */
|
|
38
|
+
static getMetadata(): StoryMeta {
|
|
39
|
+
return { title: 'Probe/Tall', description: 'taller than its host on purpose', controls: [] };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
initialize(): void {
|
|
43
|
+
const filler = document.createElement('div');
|
|
44
|
+
filler.style.height = '1600px';
|
|
45
|
+
filler.style.width = '100%';
|
|
46
|
+
this.addContent(filler);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const HOST_HEIGHT = 600;
|
|
51
|
+
|
|
52
|
+
/** Mount the storybook in a fixed-height host and hand back its preview scroller. */
|
|
53
|
+
function mountTall(): { host: HTMLElement; scroller: HTMLElement } {
|
|
54
|
+
const host = document.createElement('div');
|
|
55
|
+
// A definite height, because that is the whole question: the real page gives the
|
|
56
|
+
// window one and the chain has to carry it down.
|
|
57
|
+
host.style.height = `${HOST_HEIGHT}px`;
|
|
58
|
+
host.style.width = '900px';
|
|
59
|
+
document.body.append(host);
|
|
60
|
+
|
|
61
|
+
const module: WebStoryModule = { stories: [TallStory] };
|
|
62
|
+
mountStorybook(host, { stories: [module], title: 'Probe' });
|
|
63
|
+
|
|
64
|
+
const scroller = host.querySelector('.sb-preview-scroll') as HTMLElement | null;
|
|
65
|
+
if (!scroller) throw new Error('no .sb-preview-scroll after mount — the chrome changed shape');
|
|
66
|
+
return { host, scroller };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const AdwStorybookPreviewScrollTest = async () => {
|
|
70
|
+
await describe('storybook preview scrolling', async () => {
|
|
71
|
+
await it('bounds the scroller by its host rather than by its content', () => {
|
|
72
|
+
const { host, scroller } = mountTall();
|
|
73
|
+
try {
|
|
74
|
+
// The defect in one assertion: the scroller was 1008px tall inside a
|
|
75
|
+
// 697px bound because the pane above it ignored the bound.
|
|
76
|
+
expect(scroller.clientHeight).toBeLessThan(HOST_HEIGHT);
|
|
77
|
+
expect(scroller.clientHeight).toBeGreaterThan(0);
|
|
78
|
+
} finally {
|
|
79
|
+
host.remove();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await it('can actually scroll when the story is taller than the host', () => {
|
|
84
|
+
const { host, scroller } = mountTall();
|
|
85
|
+
try {
|
|
86
|
+
expect(scroller.scrollHeight).toBeGreaterThan(scroller.clientHeight);
|
|
87
|
+
} finally {
|
|
88
|
+
host.remove();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await it('reaches the bottom of the story', () => {
|
|
93
|
+
// Being scrollable is not the same as being able to READ the end: an
|
|
94
|
+
// ancestor clipping at the fold satisfies the two assertions above while
|
|
95
|
+
// the last rows stay unreachable.
|
|
96
|
+
const { host, scroller } = mountTall();
|
|
97
|
+
try {
|
|
98
|
+
scroller.scrollTop = scroller.scrollHeight;
|
|
99
|
+
expect(scroller.scrollTop).toBeGreaterThan(0);
|
|
100
|
+
const reached = scroller.scrollTop + scroller.clientHeight;
|
|
101
|
+
expect(reached).toBeGreaterThan(scroller.scrollHeight - 2);
|
|
102
|
+
} finally {
|
|
103
|
+
host.remove();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await it('keeps every layer between the bound and the scroller collapsible', () => {
|
|
108
|
+
// The named cause, so a future regression says WHICH layer broke instead of
|
|
109
|
+
// only that scrolling stopped. min-height: auto on any of them re-creates
|
|
110
|
+
// the defect exactly.
|
|
111
|
+
const { host, scroller } = mountTall();
|
|
112
|
+
try {
|
|
113
|
+
let el: HTMLElement | null = scroller;
|
|
114
|
+
const offenders: string[] = [];
|
|
115
|
+
while (el && el !== host) {
|
|
116
|
+
const style = getComputedStyle(el);
|
|
117
|
+
if (style.minHeight !== '0px' && style.minHeight !== '0')
|
|
118
|
+
offenders.push(`${el.tagName.toLowerCase()}.${String(el.className || '-').split(' ')[0]}`);
|
|
119
|
+
el = el.parentElement;
|
|
120
|
+
}
|
|
121
|
+
expect(offenders).toStrictEqual([]);
|
|
122
|
+
} finally {
|
|
123
|
+
host.remove();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
};
|
package/src/story-element.ts
CHANGED
|
@@ -15,7 +15,7 @@ export type StoryArgsListener = (args: StoryArgs) => void;
|
|
|
15
15
|
* Base class for browser story elements.
|
|
16
16
|
*
|
|
17
17
|
* Provides default chrome — a clamped boxed group with a title + description above a
|
|
18
|
-
* centered,
|
|
18
|
+
* centered, tinted preview stage. Simple stories compose their preview by
|
|
19
19
|
* calling {@link addContent}; a subclass needing a bespoke layout passes its own root
|
|
20
20
|
* element to the constructor, and {@link addContent} is then a no-op.
|
|
21
21
|
*/
|
package/src/styles.ts
CHANGED
|
@@ -163,12 +163,51 @@ export const STORYBOOK_WEB_CSS = `
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
/* --- Preview area --- */
|
|
166
|
+
|
|
167
|
+
/* The slotted content pane has to PASS ITS BOUND DOWN, or the scroller below it has
|
|
168
|
+
nothing to scroll against.
|
|
169
|
+
|
|
170
|
+
Measured before this rule existed: .adw-osv-content bounded itself correctly at
|
|
171
|
+
697px while its content wanted 1008px, but this div — display: block,
|
|
172
|
+
min-height: auto, flex: 0 1 auto — ignored that and grew to 1008. So
|
|
173
|
+
.sb-preview-scroll inherited clientHeight === scrollHeight, had nothing to scroll,
|
|
174
|
+
and everything past the fold was clipped by the toolbar view's overflow: hidden:
|
|
175
|
+
a tall story just ended mid-page.
|
|
176
|
+
|
|
177
|
+
The sidebar never had this. Its scroller sits directly inside the toolbar view's own
|
|
178
|
+
flex content wrapper, so nothing breaks the chain there; this pane is the one place
|
|
179
|
+
a plain div sits between the bound and the scroller.
|
|
180
|
+
|
|
181
|
+
NOTE: no backticks in this file's comments — the whole stylesheet is a JS template
|
|
182
|
+
literal, so a backtick ends it and the parse error lands nine lines away. */
|
|
183
|
+
.sb-preview-pane {
|
|
184
|
+
display: flex;
|
|
185
|
+
flex-direction: column;
|
|
186
|
+
flex: 1 1 auto;
|
|
187
|
+
min-height: 0;
|
|
188
|
+
min-width: 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* The tint belongs to the whole preview AREA, not to a box inside it. On the website
|
|
192
|
+
it sits on the preview panel and the stage within is only centring; on the stage it
|
|
193
|
+
drew a grey rectangle floating in the pane with no defined edge, which is how it read.
|
|
194
|
+
|
|
195
|
+
HERE and not on .sb-preview-pane, measured: this element paints an OPAQUE
|
|
196
|
+
background-color, so a gradient one level up is covered completely. Sampling five
|
|
197
|
+
points across the pane returned the identical colour while the computed style still
|
|
198
|
+
reported a background-image — true and meaningless.
|
|
199
|
+
|
|
200
|
+
A background on a scroll container is painted against its padding box, so it stays
|
|
201
|
+
put while the content scrolls, the way a wallpaper does. */
|
|
166
202
|
.sb-preview-scroll {
|
|
167
203
|
flex: 1 1 auto;
|
|
168
204
|
min-height: 0;
|
|
169
205
|
min-width: 0;
|
|
170
206
|
overflow: auto;
|
|
171
207
|
background-color: var(--window-bg-color);
|
|
208
|
+
background-image:
|
|
209
|
+
radial-gradient(circle at 0% 0%, color-mix(in srgb, var(--accent-color) 7%, transparent), transparent 45%),
|
|
210
|
+
radial-gradient(circle at 100% 100%, color-mix(in srgb, #926ee4 6%, transparent), transparent 45%);
|
|
172
211
|
}
|
|
173
212
|
|
|
174
213
|
.sb-story-page {
|
|
@@ -203,7 +242,16 @@ export const STORYBOOK_WEB_CSS = `
|
|
|
203
242
|
color: var(--window-fg-color);
|
|
204
243
|
}
|
|
205
244
|
|
|
206
|
-
/*
|
|
245
|
+
/* Centring and breathing room only — the surface is the pane's.
|
|
246
|
+
|
|
247
|
+
The stage carried the tint first, and against the pane's own background that read
|
|
248
|
+
as a grey rectangle floating with no edge rather than as a surface. It has no
|
|
249
|
+
border-radius for the same reason: nothing here is a box any more.
|
|
250
|
+
|
|
251
|
+
The requirement the tint answers is unchanged — the preview area stays locatable
|
|
252
|
+
when the widget on it is transparent or empty — it is just answered by the AREA
|
|
253
|
+
now instead of by a rectangle inside it. Scheme handling moved with it, see
|
|
254
|
+
.sb-preview-pane. */
|
|
207
255
|
.story-stage {
|
|
208
256
|
display: flex;
|
|
209
257
|
flex-direction: column;
|
|
@@ -212,8 +260,6 @@ export const STORYBOOK_WEB_CSS = `
|
|
|
212
260
|
gap: var(--spacing-m);
|
|
213
261
|
padding: var(--spacing-l);
|
|
214
262
|
margin-top: var(--spacing-s);
|
|
215
|
-
border: 1px dashed rgba(127, 127, 127, 0.28);
|
|
216
|
-
border-radius: var(--card-radius);
|
|
217
263
|
min-height: 80px;
|
|
218
264
|
}
|
|
219
265
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Browser test entry for @gjsify/adwaita-storybook. Discovered and run by the
|
|
2
|
+
// tests/browser Playwright harness (see AGENTS.md → Browser tests), which drives
|
|
3
|
+
// Firefox in CI.
|
|
4
|
+
//
|
|
5
|
+
// This package had no test entry at all until the preview stopped scrolling — its
|
|
6
|
+
// output is layout, and layout is the one thing a state-and-class-names assertion
|
|
7
|
+
// cannot see.
|
|
8
|
+
import { run } from '@gjsify/unit';
|
|
9
|
+
|
|
10
|
+
import { AdwStorybookPreviewScrollTest } from './preview-scroll.spec.js';
|
|
11
|
+
|
|
12
|
+
run({ AdwStorybookPreviewScrollTest });
|