shelving 1.253.0 → 1.253.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/extract/TypescriptExtractor.d.ts +2 -2
- package/extract/TypescriptExtractor.js +53 -73
- package/package.json +1 -1
- package/ui/block/Card.module.css +2 -1
- package/ui/docs/DocumentationDescription.d.ts +8 -5
- package/ui/docs/DocumentationDescription.js +12 -10
- package/ui/docs/DocumentationDescription.tsx +26 -12
- package/ui/docs/DocumentationPage.d.ts +5 -5
- package/ui/docs/DocumentationPage.js +8 -9
- package/ui/docs/DocumentationPage.test.tsx +36 -10
- package/ui/docs/DocumentationPage.tsx +9 -8
- package/ui/docs/DocumentationParams.js +8 -22
- package/ui/docs/DocumentationParams.tsx +15 -29
- package/ui/docs/DocumentationProperties.d.ts +26 -0
- package/ui/docs/DocumentationProperties.js +32 -0
- package/ui/docs/DocumentationProperties.tsx +83 -0
- package/ui/docs/DocumentationType.d.ts +33 -0
- package/ui/docs/DocumentationType.js +32 -0
- package/ui/docs/DocumentationType.tsx +48 -0
- package/ui/docs/index.d.ts +2 -0
- package/ui/docs/index.js +2 -0
- package/ui/docs/index.ts +2 -0
- package/ui/layout/CenteredLayout.js +2 -3
- package/ui/layout/CenteredLayout.md +1 -1
- package/ui/layout/CenteredLayout.module.css +61 -1
- package/ui/layout/CenteredLayout.tsx +2 -3
- package/ui/layout/Layout.d.ts +0 -7
- package/ui/layout/Layout.js +0 -9
- package/ui/layout/Layout.ts +0 -11
- package/ui/layout/SidebarLayout.js +2 -3
- package/ui/layout/SidebarLayout.md +1 -1
- package/ui/layout/SidebarLayout.module.css +44 -5
- package/ui/layout/SidebarLayout.tsx +2 -3
- package/ui/misc/MetaContext.test.tsx +10 -0
- package/ui/misc/StatusIcon.md +10 -1
- package/ui/misc/StatusIcon.module.css +4 -0
- package/ui/misc/Tag.module.css +1 -1
- package/ui/router/Router.test.tsx +9 -0
- package/util/tree.d.ts +4 -2
- package/util/url.d.ts +3 -1
- package/util/url.js +4 -1
- package/ui/layout/Layout.module.css +0 -73
|
@@ -53,6 +53,6 @@ useEffect(useSafeKeyboardArea, []);
|
|
|
53
53
|
| `--sidebar-layout-content-background` | Main content column fill | `var(--tint-100)` |
|
|
54
54
|
| `--sidebar-layout-border` | Divider between sidebar and content | `var(--stroke-normal) solid var(--tint-80)` |
|
|
55
55
|
|
|
56
|
-
The content
|
|
56
|
+
The sidebar and content columns own their own scroll behaviour directly (this layout no longer composes a shared `.layout` class). `useSafeKeyboardArea()` still writes `--layout-inset-bottom` for layouts that pad to the safe area.
|
|
57
57
|
|
|
58
58
|
**Global tokens it reads** — the tint ladder `--tint-80` / `--tint-90` / `--tint-100`, plus `--space-normal`, `--stroke-normal`, `--duration-normal`, and `--color-shadow`.
|
|
@@ -8,10 +8,9 @@
|
|
|
8
8
|
/*
|
|
9
9
|
* Sidebar layout: a fixed-width column on the left, a scrollable main content column on the right.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* layout padding, safe-area insets, and `overflow: hidden auto` scroll behaviour.
|
|
11
|
+
* This layout owns its page styling completely: `.main` is the body-filling grid container, and the
|
|
12
|
+
* inner `.content` column owns the scroll/safe-area behaviour. The `html`/`body` rules below lock the
|
|
13
|
+
* document so `.main` is the only scroll container.
|
|
15
14
|
*
|
|
16
15
|
* On narrow viewports the sidebar becomes an off-canvas drawer that slides off the left edge of the
|
|
17
16
|
* screen; the `.toggle` button opens/closes it and the `.overlay` dims the page behind the open
|
|
@@ -19,12 +18,44 @@
|
|
|
19
18
|
*/
|
|
20
19
|
|
|
21
20
|
@layer components {
|
|
21
|
+
/*
|
|
22
|
+
* Lock the document and let `.main` own the page scroll.
|
|
23
|
+
* - <html> and <body> are pinned to the viewport (no rubber-band on iOS).
|
|
24
|
+
* - <body> stacks its children vertically (flex column).
|
|
25
|
+
* - The `.main` element grows to fill the body.
|
|
26
|
+
*/
|
|
27
|
+
html:has(.main) {
|
|
28
|
+
/* Box */
|
|
29
|
+
margin: 0;
|
|
30
|
+
padding: 0;
|
|
31
|
+
height: 100vh;
|
|
32
|
+
height: 100dvh; /* Make the height dynamically expand/contract as the address bar shows/hides on iOS. */
|
|
33
|
+
width: 100vw;
|
|
34
|
+
width: 100dvw;
|
|
35
|
+
|
|
36
|
+
/* Contents */
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
overflow-wrap: break-word; /* Break overlong words only as a last resort. */
|
|
39
|
+
}
|
|
40
|
+
|
|
22
41
|
body:has(.main) {
|
|
42
|
+
/* Box */
|
|
43
|
+
margin: 0;
|
|
44
|
+
padding: 0;
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
|
|
48
|
+
/* Lock body to prevent iOS Safari triggering viewport scrolling on focus change. */
|
|
49
|
+
position: fixed;
|
|
50
|
+
inset: 0;
|
|
51
|
+
|
|
23
52
|
/* Style */
|
|
24
53
|
background: var(--sidebar-layout-background, var(--tint-100));
|
|
25
54
|
}
|
|
26
55
|
|
|
27
56
|
.main {
|
|
57
|
+
/* Grow to fill the body (it is the body's flex child). */
|
|
58
|
+
flex: 1;
|
|
28
59
|
display: grid;
|
|
29
60
|
grid-template-columns: var(--sidebar-layout-width, 17.5rem) 1fr;
|
|
30
61
|
gap: 0;
|
|
@@ -47,10 +78,18 @@
|
|
|
47
78
|
}
|
|
48
79
|
|
|
49
80
|
.content {
|
|
50
|
-
/*
|
|
81
|
+
/* Owns the page scroll for the content column (previously inherited from the shared `.layout`). */
|
|
51
82
|
min-width: 0;
|
|
52
83
|
padding: 0;
|
|
53
84
|
|
|
85
|
+
/* Scrolling */
|
|
86
|
+
display: flow-root;
|
|
87
|
+
overflow-x: hidden;
|
|
88
|
+
overflow-y: auto;
|
|
89
|
+
overscroll-behavior-x: auto;
|
|
90
|
+
overscroll-behavior-y: contain;
|
|
91
|
+
scroll-behavior: smooth;
|
|
92
|
+
|
|
54
93
|
/* Style */
|
|
55
94
|
background: var(--sidebar-layout-content-background, var(--tint-100));
|
|
56
95
|
}
|
|
@@ -5,7 +5,6 @@ import { requireMetaURL } from "../misc/MetaContext.js";
|
|
|
5
5
|
import { RouteCache } from "../router/RouteCache.js";
|
|
6
6
|
import { getClass, getModuleClass } from "../util/css.js";
|
|
7
7
|
import type { OptionalChildProps } from "../util/props.js";
|
|
8
|
-
import { LAYOUT_CLASS } from "./Layout.js";
|
|
9
8
|
import SIDEBAR_LAYOUT_CSS from "./SidebarLayout.module.css";
|
|
10
9
|
|
|
11
10
|
/**
|
|
@@ -60,7 +59,7 @@ export function SidebarLayout({ sidebar, children, right = false }: SidebarLayou
|
|
|
60
59
|
// duplicated nor remounted as the URL changes.
|
|
61
60
|
const contentEl = (
|
|
62
61
|
<RouteCache key="content">
|
|
63
|
-
<div className={
|
|
62
|
+
<div className={getModuleClass(SIDEBAR_LAYOUT_CSS, "content")}>
|
|
64
63
|
<div className={getModuleClass(SIDEBAR_LAYOUT_CSS, "toggle")}>
|
|
65
64
|
<Button title={open ? "Close menu" : "Show menu"} onClick={() => setOpen(o => !o)}>
|
|
66
65
|
{open ? <XMarkIcon /> : <Bars3Icon />}
|
|
@@ -80,7 +79,7 @@ export function SidebarLayout({ sidebar, children, right = false }: SidebarLayou
|
|
|
80
79
|
/>
|
|
81
80
|
);
|
|
82
81
|
return (
|
|
83
|
-
<main className={
|
|
82
|
+
<main className={getModuleClass(SIDEBAR_LAYOUT_CSS, "main")}>
|
|
84
83
|
{right ? [contentEl, sidebarEl, overlayEl] : [sidebarEl, contentEl, overlayEl]}
|
|
85
84
|
</main>
|
|
86
85
|
);
|
|
@@ -29,6 +29,16 @@ describe("requireMetaURL", () => {
|
|
|
29
29
|
expect(html).toBe("/");
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
test("normalizes a trailing slash on the url away", () => {
|
|
33
|
+
// `/enquiry/loan/` resolves to the same path as `/enquiry/loan`, so trailing-slash URLs match the same route.
|
|
34
|
+
const html = renderToStaticMarkup(
|
|
35
|
+
<MetaContext value={createMeta({ root: "http://x.com/", url: "http://x.com/enquiry/loan/" })}>
|
|
36
|
+
<Probe />
|
|
37
|
+
</MetaContext>,
|
|
38
|
+
);
|
|
39
|
+
expect(html).toBe("/enquiry/loan");
|
|
40
|
+
});
|
|
41
|
+
|
|
32
42
|
test("throws RequiredError when url is unset", () => {
|
|
33
43
|
expect(() => renderToStaticMarkup(<Probe />)).toThrow(RequiredError);
|
|
34
44
|
});
|
package/ui/misc/StatusIcon.md
CHANGED
|
@@ -6,7 +6,7 @@ Renders the icon for a given status, coloured to match. Picks a heroicon per sta
|
|
|
6
6
|
|
|
7
7
|
- `status` defaults to `"info"` (an info icon) when unset.
|
|
8
8
|
- Size it via the `size` prop (`"small"`, `"normal"`, `"large"`, `"xlarge"`, or `"xxlarge"`); defaults to the current line height.
|
|
9
|
-
-
|
|
9
|
+
- Paints from its status tint by default, so the icon colour matches the `status` — override `--status-icon-color` to force a specific colour.
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
@@ -17,3 +17,12 @@ import { StatusIcon } from "shelving/ui";
|
|
|
17
17
|
<StatusIcon status="error" />
|
|
18
18
|
<StatusIcon status="loading" size="small" />
|
|
19
19
|
```
|
|
20
|
+
|
|
21
|
+
## Styling
|
|
22
|
+
|
|
23
|
+
| Variable | Styles | Default |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `--status-icon-color` | Icon colour | `var(--tint-50)` (the status tint, e.g. `--color-failure` for `status="error"`) |
|
|
26
|
+
| `--status-icon-size` | Icon width / height | `1lh` (current line height) |
|
|
27
|
+
|
|
28
|
+
**Global tokens it reads** — the status tint anchor `--tint-50`, plus the size tokens `--size-small` / `--size-normal` / `--size-large` / `--size-xlarge` / `--size-xxlarge` used by the `size` prop.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
@import "../style/layers.css";
|
|
2
2
|
@import "../style/Size.module.css";
|
|
3
|
+
@import "../style/Tint.module.css";
|
|
3
4
|
|
|
4
5
|
@layer components {
|
|
5
6
|
.icon {
|
|
@@ -8,6 +9,9 @@
|
|
|
8
9
|
width: var(--status-icon-size, 1lh);
|
|
9
10
|
height: var(--status-icon-size, 1lh);
|
|
10
11
|
|
|
12
|
+
/* Paint from the status tint anchor so e.g. `status="error"` renders red, `success` green, etc. */
|
|
13
|
+
color: var(--status-icon-color, var(--tint-50));
|
|
14
|
+
|
|
11
15
|
/* Variants */
|
|
12
16
|
&.small {
|
|
13
17
|
width: var(--status-icon-small-size, var(--size-small));
|
package/ui/misc/Tag.module.css
CHANGED
|
@@ -3,10 +3,12 @@ import { renderToStaticMarkup } from "react-dom/server";
|
|
|
3
3
|
import { MetaContext } from "../misc/MetaContext.js";
|
|
4
4
|
import { createMeta } from "../util/meta.js";
|
|
5
5
|
import { Router } from "./Router.js";
|
|
6
|
+
import type { RouteProps } from "./Routes.js";
|
|
6
7
|
|
|
7
8
|
const ROUTES = {
|
|
8
9
|
"/": () => <main>Home</main>,
|
|
9
10
|
"/about": () => <main>About</main>,
|
|
11
|
+
"/enquiry/{form}": ({ form }: RouteProps) => <main>Enquiry {form}</main>,
|
|
10
12
|
} as const;
|
|
11
13
|
|
|
12
14
|
function render(url: string) {
|
|
@@ -25,4 +27,11 @@ describe("Router", () => {
|
|
|
25
27
|
test("throws when no route matches and no fallback is given", () => {
|
|
26
28
|
expect(() => render("./missing")).toThrow();
|
|
27
29
|
});
|
|
30
|
+
|
|
31
|
+
test("matches a route when the url has a trailing slash", () => {
|
|
32
|
+
// A trailing slash on the url resolves to the same route as the slash-less form.
|
|
33
|
+
expect(render("http://x.com/about/")).toContain("About");
|
|
34
|
+
expect(render("http://x.com/enquiry/loan")).toContain("Enquiry loan");
|
|
35
|
+
expect(render("http://x.com/enquiry/loan/")).toContain("Enquiry loan");
|
|
36
|
+
});
|
|
28
37
|
});
|
package/util/tree.d.ts
CHANGED
|
@@ -79,6 +79,8 @@ export interface DocumentationParam {
|
|
|
79
79
|
readonly optional?: boolean | undefined;
|
|
80
80
|
/** Default-value expression from the parameter's initializer (e.g. `"false"`, `"{}"`), or `undefined` when the parameter has none. */
|
|
81
81
|
readonly default?: string | undefined;
|
|
82
|
+
/** Whether this is a read-only data member (a `readonly` property, or a getter with no matching setter). Only meaningful for `properties`, not function params. */
|
|
83
|
+
readonly readonly?: boolean | undefined;
|
|
82
84
|
}
|
|
83
85
|
/**
|
|
84
86
|
* A single `@returns` entry — multiple allowed to document union return types separately.
|
|
@@ -137,8 +139,8 @@ export interface DocumentationElementProps extends TreeElementProps {
|
|
|
137
139
|
*/
|
|
138
140
|
readonly types?: ImmutableArray<string> | undefined;
|
|
139
141
|
/**
|
|
140
|
-
* Structured member list for
|
|
141
|
-
* - Reuses the `DocumentationParam` shape so
|
|
142
|
+
* Structured data-member list for a `class`, `interface`, or object-literal `type` — each property's `name`, `type`, optionality, `default`, and `description`. Methods stay as child elements; only data members live here.
|
|
143
|
+
* - Reuses the `DocumentationParam` shape so it can both render the type's own Properties table and be flattened into an options-bag parameter's individual fields at render time.
|
|
142
144
|
*/
|
|
143
145
|
readonly properties?: ImmutableArray<DocumentationParam> | undefined;
|
|
144
146
|
}
|
package/util/url.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AnyCaller } from "./function.js";
|
|
2
2
|
import type { Nullish } from "./null.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type AbsolutePath } from "./path.js";
|
|
4
4
|
import type { ImmutableURI } from "./uri.js";
|
|
5
5
|
/**
|
|
6
6
|
* A URL string has a protocol and a `//`.
|
|
@@ -133,6 +133,7 @@ export declare function requireURL(target: PossibleURL, base?: PossibleURL, call
|
|
|
133
133
|
* - Need to be valid _URLs_ not just _URIs_, i.e. needs to have `protocol://` at the start.
|
|
134
134
|
* - Origins need to match, i.e. `http://localhost` !== `http://localhost:4020`
|
|
135
135
|
* - Relative targets are resolved against the normalized base URL.
|
|
136
|
+
* - The resolved target pathname is normalised with `cleanPath()` — runs of slashes are collapsed and a trailing slash is stripped (the root `/` is preserved) — so `/foo` and `/foo/` resolve to the same path. This mirrors the path-based sibling `matchPathPrefix()`.
|
|
136
137
|
*
|
|
137
138
|
* @param target URL to match against `base` — if this is a relative path it will be resolved against `base`
|
|
138
139
|
* @param base Base URL the `target` is matched against.
|
|
@@ -142,6 +143,7 @@ export declare function requireURL(target: PossibleURL, base?: PossibleURL, call
|
|
|
142
143
|
* @throws RequiredError If `target` or `base` cannot be resolved to a true URL.
|
|
143
144
|
*
|
|
144
145
|
* @example matchURLPrefix("http://x.com/a/b", "http://x.com/a/"); // `/b`
|
|
146
|
+
* @example matchURLPrefix("http://x.com/a/b/", "http://x.com/a/"); // `/b` (trailing slash normalised away)
|
|
145
147
|
*
|
|
146
148
|
* @see https://dhoulb.github.io/shelving/util/url/matchURLPrefix
|
|
147
149
|
*/
|
package/util/url.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { RequiredError } from "../error/RequiredError.js";
|
|
2
|
+
import { cleanPath } from "./path.js";
|
|
2
3
|
export const ImmutableURL = URL;
|
|
3
4
|
/**
|
|
4
5
|
* Is an unknown value a URL object?
|
|
@@ -106,6 +107,7 @@ export function requireURL(target, base, caller = requireURL) {
|
|
|
106
107
|
* - Need to be valid _URLs_ not just _URIs_, i.e. needs to have `protocol://` at the start.
|
|
107
108
|
* - Origins need to match, i.e. `http://localhost` !== `http://localhost:4020`
|
|
108
109
|
* - Relative targets are resolved against the normalized base URL.
|
|
110
|
+
* - The resolved target pathname is normalised with `cleanPath()` — runs of slashes are collapsed and a trailing slash is stripped (the root `/` is preserved) — so `/foo` and `/foo/` resolve to the same path. This mirrors the path-based sibling `matchPathPrefix()`.
|
|
109
111
|
*
|
|
110
112
|
* @param target URL to match against `base` — if this is a relative path it will be resolved against `base`
|
|
111
113
|
* @param base Base URL the `target` is matched against.
|
|
@@ -115,6 +117,7 @@ export function requireURL(target, base, caller = requireURL) {
|
|
|
115
117
|
* @throws RequiredError If `target` or `base` cannot be resolved to a true URL.
|
|
116
118
|
*
|
|
117
119
|
* @example matchURLPrefix("http://x.com/a/b", "http://x.com/a/"); // `/b`
|
|
120
|
+
* @example matchURLPrefix("http://x.com/a/b/", "http://x.com/a/"); // `/b` (trailing slash normalised away)
|
|
118
121
|
*
|
|
119
122
|
* @see https://dhoulb.github.io/shelving/util/url/matchURLPrefix
|
|
120
123
|
*/
|
|
@@ -126,7 +129,7 @@ export function matchURLPrefix(target, base, caller = matchURLPrefix) {
|
|
|
126
129
|
if (targetURL.origin !== baseURL.origin)
|
|
127
130
|
return;
|
|
128
131
|
const basePath = baseURL.pathname;
|
|
129
|
-
const targetPath = targetURL.pathname;
|
|
132
|
+
const targetPath = cleanPath(targetURL.pathname);
|
|
130
133
|
if (basePath === "/")
|
|
131
134
|
return targetPath;
|
|
132
135
|
// `basePath` may or may not have a trailing slash, so strip it and re-assert the directory boundary explicitly.
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
@import "../style/layers.css";
|
|
2
|
-
@import "../style/Space.module.css";
|
|
3
|
-
@import "../style/Tint.module.css";
|
|
4
|
-
|
|
5
|
-
/* Low-priority padding/flex defaults live in `@layer defaults` so consumers can override them */
|
|
6
|
-
/* with normal-specificity selectors without needing `:where()` tricks. */
|
|
7
|
-
@layer defaults {
|
|
8
|
-
.layout {
|
|
9
|
-
/* Top/bottom use `layout-space` and left/right use `layout-padding` */
|
|
10
|
-
padding: var(--layout-space, var(--space-normal)) var(--layout-padding, var(--space-normal));
|
|
11
|
-
|
|
12
|
-
/* Default page padding (with safe-area additions). */
|
|
13
|
-
padding-top: calc(var(--layout-space, var(--space-normal)) + max(var(--layout-inset-top, 0px), env(safe-area-inset-top, 0px)));
|
|
14
|
-
padding-bottom: calc(var(--layout-space, var(--space-normal)) + max(var(--layout-inset-bottom, 0px), env(safe-area-inset-bottom, 0px)));
|
|
15
|
-
padding-left: calc(var(--layout-padding, var(--space-normal)) + max(var(--layout-inset-left, 0px), env(safe-area-inset-left, 0px)));
|
|
16
|
-
padding-right: calc(var(--layout-padding, var(--space-normal)) + max(var(--layout-inset-right, 0px), env(safe-area-inset-right, 0px)));
|
|
17
|
-
|
|
18
|
-
/* Grow to fill the body. */
|
|
19
|
-
flex: 1;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
@layer components {
|
|
24
|
-
/*
|
|
25
|
-
* Lock the document and let .layout own the page scroll.
|
|
26
|
-
*
|
|
27
|
-
* `.layout` says "this element is the main layout container, not <body>".
|
|
28
|
-
* When .layout appears anywhere in the page:
|
|
29
|
-
* - <html> and <body> are pinned to the viewport (no rubber-band on iOS).
|
|
30
|
-
* - <body> stacks its children vertically (flex column).
|
|
31
|
-
* - The .layout element grows to fill the body (flex: 1) and is the only scroll container.
|
|
32
|
-
*/
|
|
33
|
-
html:has(.layout) {
|
|
34
|
-
/* Box */
|
|
35
|
-
margin: 0;
|
|
36
|
-
padding: 0;
|
|
37
|
-
height: 100vh;
|
|
38
|
-
height: 100dvh; /** Make the height dynamically expand/contract as the address bar shows/hides on iOS. */
|
|
39
|
-
width: 100vw;
|
|
40
|
-
width: 100dvw;
|
|
41
|
-
|
|
42
|
-
/* Contents */
|
|
43
|
-
overflow: hidden;
|
|
44
|
-
overflow-wrap: break-word; /* Break overlong words only as a last resort; `anywhere` would also break mid-word to fit narrow table/flex columns. */
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
body:has(.layout) {
|
|
48
|
-
/* Box */
|
|
49
|
-
margin: 0;
|
|
50
|
-
padding: 0;
|
|
51
|
-
display: flex;
|
|
52
|
-
flex-direction: column;
|
|
53
|
-
|
|
54
|
-
/* Lock body to prevent iOS Safari triggering viewport scrolling on focus change. */
|
|
55
|
-
position: fixed;
|
|
56
|
-
inset: 0;
|
|
57
|
-
|
|
58
|
-
/* Layout-specific bg hook. Typography and page bg come from the body baseline in the style/ token modules. */
|
|
59
|
-
background: var(--layout-body-bg, var(--tint-100));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.layout {
|
|
63
|
-
/* Box */
|
|
64
|
-
display: flow-root;
|
|
65
|
-
|
|
66
|
-
/* Scrolling */
|
|
67
|
-
overflow-x: hidden;
|
|
68
|
-
overflow-y: auto;
|
|
69
|
-
overscroll-behavior-x: auto;
|
|
70
|
-
overscroll-behavior-y: contain;
|
|
71
|
-
scroll-behavior: smooth;
|
|
72
|
-
}
|
|
73
|
-
}
|