shelving 1.252.0 → 1.253.1
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/DocumentationKind.d.ts +1 -1
- package/ui/docs/DocumentationKind.js +12 -12
- package/ui/docs/DocumentationKind.tsx +12 -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/StatusIcon.md +10 -1
- package/ui/misc/StatusIcon.module.css +4 -0
- package/ui/misc/Tag.module.css +1 -1
- package/util/tree.d.ts +4 -2
- package/ui/layout/Layout.module.css +0 -73
|
@@ -16,12 +16,13 @@ import { TreeMarkup } from "../tree/TreeMarkup.js";
|
|
|
16
16
|
import { DocumentationButtons } from "./DocumentationButtons.js";
|
|
17
17
|
import { DocumentationKind, getDocumentationKindColor } from "./DocumentationKind.js";
|
|
18
18
|
import { DocumentationParams } from "./DocumentationParams.js";
|
|
19
|
+
import { DocumentationProperties } from "./DocumentationProperties.js";
|
|
19
20
|
import { DocumentationReferences } from "./DocumentationReferences.js";
|
|
20
21
|
import { DocumentationReturns } from "./DocumentationReturns.js";
|
|
21
22
|
import { DocumentationSignatures } from "./DocumentationSignatures.js";
|
|
22
23
|
import { DocumentationThrows } from "./DocumentationThrows.js";
|
|
23
24
|
|
|
24
|
-
/** Documentation `kind`s grouped into card sections, in display order — pluralised, sentence-case headings. */
|
|
25
|
+
/** Documentation `kind`s grouped into card sections, in display order — pluralised, sentence-case headings. Data members (properties) are not child elements — they render as the Properties table instead. */
|
|
25
26
|
const KIND_SECTIONS = {
|
|
26
27
|
component: "Components",
|
|
27
28
|
function: "Functions",
|
|
@@ -30,9 +31,7 @@ const KIND_SECTIONS = {
|
|
|
30
31
|
type: "Types",
|
|
31
32
|
constant: "Constants",
|
|
32
33
|
"static method": "Static methods",
|
|
33
|
-
"static property": "Static properties",
|
|
34
34
|
method: "Methods",
|
|
35
|
-
property: "Properties",
|
|
36
35
|
};
|
|
37
36
|
|
|
38
37
|
/** Render a list of tree elements grouped into kind-based card sections, in `KIND_SECTIONS` order. */
|
|
@@ -68,11 +67,11 @@ function DocumentationChildren({ elements }: { readonly elements?: TreeElements
|
|
|
68
67
|
|
|
69
68
|
/**
|
|
70
69
|
* Page renderer for a `tree-documentation` element — the full detail page for a documented symbol.
|
|
71
|
-
* - Renders breadcrumbs, title (with kind + `readonly` tags), relational links (`member of`, `extends`, `implements`), signatures (one per overload), content, parameters, returns, throws, referenced types, and examples.
|
|
72
|
-
* - In the Parameters / Returns / Throws tables the `Type` column links each type to its documented page via `TreeLink` (exact-match only; compound or builtin types stay plain text), and a row with no hand-written description falls back to the referenced type's own `description`. A
|
|
73
|
-
* -
|
|
70
|
+
* - Renders breadcrumbs, title (with kind + `readonly` tags), relational links (`member of`, `extends`, `implements`), signatures (one per overload), content, parameters, returns, throws, properties, referenced types, and examples.
|
|
71
|
+
* - In the Parameters / Returns / Throws / Properties tables the `Type` column links each type to its documented page via `TreeLink` (exact-match only; compound or builtin types stay plain text), and a row with no hand-written description falls back to the referenced type's own `description`. A default renders as a `Defaults to …` note (linking the value when it's a documented token) rather than in a dedicated column.
|
|
72
|
+
* - A class/interface/object-literal type's data members render as the Properties table (see `DocumentationProperties`); an options-bag parameter whose type resolves to one is flattened into indented child rows from the same structured list.
|
|
74
73
|
* - A `type` alias's referenced type names render as a linked `Type` table, each row carrying the resolved element's `description` (exact-match only).
|
|
75
|
-
* - Child symbols are grouped by `kind` into card sections
|
|
74
|
+
* - Child symbols (functions, classes, methods — not data members) are grouped by `kind` into card sections, each under its own heading.
|
|
76
75
|
* - All sections are conditional — only render when they have entries.
|
|
77
76
|
*
|
|
78
77
|
* @kind component
|
|
@@ -90,6 +89,7 @@ export function DocumentationPage({
|
|
|
90
89
|
params,
|
|
91
90
|
returns,
|
|
92
91
|
throws,
|
|
92
|
+
properties,
|
|
93
93
|
types,
|
|
94
94
|
examples,
|
|
95
95
|
children,
|
|
@@ -110,12 +110,13 @@ export function DocumentationPage({
|
|
|
110
110
|
<DocumentationButtons {...props} />
|
|
111
111
|
</Header>
|
|
112
112
|
</Panel>
|
|
113
|
-
{signatures?.length || params?.length || returns?.length || throws?.length || types?.length ? (
|
|
113
|
+
{signatures?.length || params?.length || returns?.length || throws?.length || properties?.length || types?.length ? (
|
|
114
114
|
<Section>
|
|
115
115
|
<DocumentationSignatures signatures={signatures} />
|
|
116
116
|
<DocumentationParams params={params} />
|
|
117
117
|
<DocumentationReturns returns={returns} />
|
|
118
118
|
<DocumentationThrows throws={throws} />
|
|
119
|
+
<DocumentationProperties properties={properties} />
|
|
119
120
|
<DocumentationReferences types={types} />
|
|
120
121
|
</Section>
|
|
121
122
|
) : null}
|
|
@@ -6,28 +6,13 @@ import { Scroll } from "../style/Scroll.js";
|
|
|
6
6
|
import { Cell } from "../table/Cell.js";
|
|
7
7
|
import { Table } from "../table/Table.js";
|
|
8
8
|
import { getTreeElement, useTreeMap } from "../tree/TreeContext.js";
|
|
9
|
-
import { TreeLink } from "../tree/TreeLink.js";
|
|
10
9
|
import { DocumentationDescription } from "./DocumentationDescription.js";
|
|
10
|
+
import { DocumentationType, splitType } from "./DocumentationType.js";
|
|
11
11
|
const DEFAULT_TYPE = "unknown";
|
|
12
12
|
/** Indentation (non-breaking spaces, so HTML doesn't collapse it) prefixed to a flattened sub-property's name so it reads as nested under its parent param. */
|
|
13
13
|
const SUBPARAM_INDENT = "\u00A0\u00A0\u00A0\u00A0";
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
* - An `undefined` member is dropped from display and instead flags the value as optional — we often write `| undefined` explicitly (e.g. for `exactOptionalPropertyTypes`, or to allow an explicit `undefined` to trigger a default), which reads as noise in the docs.
|
|
17
|
-
* - When nothing but `undefined` is left, the members are kept as-is rather than emptied.
|
|
18
|
-
*/
|
|
19
|
-
function _splitType(type) {
|
|
20
|
-
const parts = type
|
|
21
|
-
.split(" | ")
|
|
22
|
-
.map(part => part.trim())
|
|
23
|
-
.filter(Boolean);
|
|
24
|
-
const members = parts.filter(part => part !== "undefined");
|
|
25
|
-
return members.length ? { members, optional: members.length !== parts.length } : { members: parts, optional: false };
|
|
26
|
-
}
|
|
27
|
-
/** Render a type expression as one linked `Type`-column token per union member, each stacked on its own line (an `undefined` member is dropped — see `_splitType`). */
|
|
28
|
-
function _renderType(members) {
|
|
29
|
-
return members.map((member, index) => (_jsxs(Fragment, { children: [index > 0 && _jsx("br", {}), _jsx(TreeLink, { name: member, nowrap: true })] }, member)));
|
|
30
|
-
}
|
|
14
|
+
/** Kinds whose `properties` represent an options-bag worth flattening into a param's child rows — interfaces and object-literal `type` aliases, not full classes. */
|
|
15
|
+
const FLATTEN_KINDS = new Set(["interface", "type"]);
|
|
31
16
|
/**
|
|
32
17
|
* Render a documented symbol's parameters as a scrollable table — one row per parameter.
|
|
33
18
|
* - Self-contained: pulls its own copy of the tree map from `useTreeMap()` so the `Type` column can link each type to its documented page via `TreeLink` (exact-match only; compound or builtin types stay plain text).
|
|
@@ -46,14 +31,15 @@ export function DocumentationParams({ params }) {
|
|
|
46
31
|
if (!params?.length)
|
|
47
32
|
return null;
|
|
48
33
|
return (_jsx(Section, { children: _jsx(Scroll, { horizontal: true, children: _jsxs(Table, { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx(Cell, { header: true, width: "fit", children: "Param" }), _jsx(Cell, { header: true, width: "fit", children: "Type" }), _jsx(Cell, { header: true, width: "xxnarrow", grow: true })] }) }), _jsx("tbody", { children: params.map(({ name, type = DEFAULT_TYPE, description, default: def, optional }) => {
|
|
49
|
-
const { members, optional: typeOptional } =
|
|
34
|
+
const { members, optional: typeOptional } = splitType(type);
|
|
50
35
|
// An options-bag param whose (single, concrete) type resolves to a documented interface/object type is flattened into its individual fields as indented child rows.
|
|
51
36
|
const single = members.length === 1 ? members[0] : undefined;
|
|
52
37
|
const resolved = single ? getTreeElement(map, single)?.props : undefined;
|
|
53
|
-
|
|
54
|
-
|
|
38
|
+
const properties = resolved && FLATTEN_KINDS.has(resolved.kind ?? "") ? resolved.properties : undefined;
|
|
39
|
+
return (_jsxs(Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { children: _jsx(Code, { nowrap: true, children: name }) }), _jsx("td", { children: _jsx(DocumentationType, { members: members }) }), _jsx("td", { children: _jsx(DocumentationDescription, { description: description || resolved?.description, default: def, optional: !!optional || typeOptional }) })] }), properties?.map(({ name: propName, type: propType = DEFAULT_TYPE, description: propDescription, default: propDef, optional: propOptional, readonly: propReadonly, }) => {
|
|
40
|
+
const { members: propMembers, optional: propTypeOptional } = splitType(propType);
|
|
55
41
|
const propSingle = propMembers[0] ?? DEFAULT_TYPE;
|
|
56
|
-
return (_jsxs("tr", { children: [_jsxs(Cell, { nowrap: true, children: [SUBPARAM_INDENT, _jsx(Code, { nowrap: true, children: `.${propName}` })] }), _jsx("td", { children:
|
|
42
|
+
return (_jsxs("tr", { children: [_jsxs(Cell, { nowrap: true, children: [SUBPARAM_INDENT, _jsx(Code, { nowrap: true, children: `.${propName}` })] }), _jsx("td", { children: _jsx(DocumentationType, { members: propMembers }) }), _jsx("td", { children: _jsx(DocumentationDescription, { description: propDescription || getTreeElement(map, propSingle)?.props.description, default: propDef, optional: !!propOptional || propTypeOptional, readonly: propReadonly }) })] }, `${propName}-${propType}-${propDescription}`));
|
|
57
43
|
})] }, `${name}-${type}`));
|
|
58
44
|
}) })] }) }) }));
|
|
59
45
|
}
|
|
@@ -7,37 +7,16 @@ import { Scroll } from "../style/Scroll.js";
|
|
|
7
7
|
import { Cell } from "../table/Cell.js";
|
|
8
8
|
import { Table } from "../table/Table.js";
|
|
9
9
|
import { getTreeElement, useTreeMap } from "../tree/TreeContext.js";
|
|
10
|
-
import { TreeLink } from "../tree/TreeLink.js";
|
|
11
10
|
import { DocumentationDescription } from "./DocumentationDescription.js";
|
|
11
|
+
import { DocumentationType, splitType } from "./DocumentationType.js";
|
|
12
12
|
|
|
13
13
|
const DEFAULT_TYPE = "unknown";
|
|
14
14
|
|
|
15
15
|
/** Indentation (non-breaking spaces, so HTML doesn't collapse it) prefixed to a flattened sub-property's name so it reads as nested under its parent param. */
|
|
16
16
|
const SUBPARAM_INDENT = "\u00A0\u00A0\u00A0\u00A0";
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
* - An `undefined` member is dropped from display and instead flags the value as optional — we often write `| undefined` explicitly (e.g. for `exactOptionalPropertyTypes`, or to allow an explicit `undefined` to trigger a default), which reads as noise in the docs.
|
|
21
|
-
* - When nothing but `undefined` is left, the members are kept as-is rather than emptied.
|
|
22
|
-
*/
|
|
23
|
-
function _splitType(type: string): { readonly members: readonly string[]; readonly optional: boolean } {
|
|
24
|
-
const parts = type
|
|
25
|
-
.split(" | ")
|
|
26
|
-
.map(part => part.trim())
|
|
27
|
-
.filter(Boolean);
|
|
28
|
-
const members = parts.filter(part => part !== "undefined");
|
|
29
|
-
return members.length ? { members, optional: members.length !== parts.length } : { members: parts, optional: false };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** Render a type expression as one linked `Type`-column token per union member, each stacked on its own line (an `undefined` member is dropped — see `_splitType`). */
|
|
33
|
-
function _renderType(members: readonly string[]): ReactNode {
|
|
34
|
-
return members.map((member, index) => (
|
|
35
|
-
<Fragment key={member}>
|
|
36
|
-
{index > 0 && <br />}
|
|
37
|
-
<TreeLink name={member} nowrap />
|
|
38
|
-
</Fragment>
|
|
39
|
-
));
|
|
40
|
-
}
|
|
18
|
+
/** Kinds whose `properties` represent an options-bag worth flattening into a param's child rows — interfaces and object-literal `type` aliases, not full classes. */
|
|
19
|
+
const FLATTEN_KINDS = new Set(["interface", "type"]);
|
|
41
20
|
|
|
42
21
|
/**
|
|
43
22
|
* Props for `DocumentationParams` — the parameter list to render, one row per parameter.
|
|
@@ -82,17 +61,20 @@ export function DocumentationParams({ params }: DocumentationParamsProps): React
|
|
|
82
61
|
</thead>
|
|
83
62
|
<tbody>
|
|
84
63
|
{params.map(({ name, type = DEFAULT_TYPE, description, default: def, optional }) => {
|
|
85
|
-
const { members, optional: typeOptional } =
|
|
64
|
+
const { members, optional: typeOptional } = splitType(type);
|
|
86
65
|
// An options-bag param whose (single, concrete) type resolves to a documented interface/object type is flattened into its individual fields as indented child rows.
|
|
87
66
|
const single = members.length === 1 ? members[0] : undefined;
|
|
88
67
|
const resolved = single ? (getTreeElement(map, single)?.props as DocumentationElementProps | undefined) : undefined;
|
|
68
|
+
const properties = resolved && FLATTEN_KINDS.has(resolved.kind ?? "") ? resolved.properties : undefined;
|
|
89
69
|
return (
|
|
90
70
|
<Fragment key={`${name}-${type}`}>
|
|
91
71
|
<tr>
|
|
92
72
|
<td>
|
|
93
73
|
<Code nowrap>{name}</Code>
|
|
94
74
|
</td>
|
|
95
|
-
<td>
|
|
75
|
+
<td>
|
|
76
|
+
<DocumentationType members={members} />
|
|
77
|
+
</td>
|
|
96
78
|
<td>
|
|
97
79
|
<DocumentationDescription
|
|
98
80
|
description={description || resolved?.description}
|
|
@@ -101,15 +83,16 @@ export function DocumentationParams({ params }: DocumentationParamsProps): React
|
|
|
101
83
|
/>
|
|
102
84
|
</td>
|
|
103
85
|
</tr>
|
|
104
|
-
{
|
|
86
|
+
{properties?.map(
|
|
105
87
|
({
|
|
106
88
|
name: propName,
|
|
107
89
|
type: propType = DEFAULT_TYPE,
|
|
108
90
|
description: propDescription,
|
|
109
91
|
default: propDef,
|
|
110
92
|
optional: propOptional,
|
|
93
|
+
readonly: propReadonly,
|
|
111
94
|
}) => {
|
|
112
|
-
const { members: propMembers, optional: propTypeOptional } =
|
|
95
|
+
const { members: propMembers, optional: propTypeOptional } = splitType(propType);
|
|
113
96
|
const propSingle = propMembers[0] ?? DEFAULT_TYPE;
|
|
114
97
|
return (
|
|
115
98
|
<tr key={`${propName}-${propType}-${propDescription}`}>
|
|
@@ -117,12 +100,15 @@ export function DocumentationParams({ params }: DocumentationParamsProps): React
|
|
|
117
100
|
{SUBPARAM_INDENT}
|
|
118
101
|
<Code nowrap>{`.${propName}`}</Code>
|
|
119
102
|
</Cell>
|
|
120
|
-
<td>
|
|
103
|
+
<td>
|
|
104
|
+
<DocumentationType members={propMembers} />
|
|
105
|
+
</td>
|
|
121
106
|
<td>
|
|
122
107
|
<DocumentationDescription
|
|
123
108
|
description={propDescription || getTreeElement(map, propSingle)?.props.description}
|
|
124
109
|
default={propDef}
|
|
125
110
|
optional={!!propOptional || propTypeOptional}
|
|
111
|
+
readonly={propReadonly}
|
|
126
112
|
/>
|
|
127
113
|
</td>
|
|
128
114
|
</tr>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { ImmutableArray } from "../../util/array.js";
|
|
3
|
+
import type { DocumentationParam } from "../../util/tree.js";
|
|
4
|
+
/**
|
|
5
|
+
* Props for `DocumentationProperties` — the data members of a class/interface/object-literal type to render, one row each.
|
|
6
|
+
*
|
|
7
|
+
* @see https://dhoulb.github.io/shelving/ui/docs/DocumentationProperties/DocumentationPropertiesProps
|
|
8
|
+
*/
|
|
9
|
+
export interface DocumentationPropertiesProps {
|
|
10
|
+
/** Properties to render — one row each. */
|
|
11
|
+
readonly properties?: ImmutableArray<DocumentationParam> | undefined;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Render a documented type's data members (properties, getters/setters) as a scrollable table — one row per property.
|
|
15
|
+
* - Self-contained: pulls its own copy of the tree map from [`useTreeMap()`](/ui/useTreeMap) so the `Type` column can link each type to its documented page via [`TreeLink`](/ui/TreeLink) (exact-match only; compound or builtin types stay plain text).
|
|
16
|
+
* - Each property name carries a leading `.` (e.g. `.caller`); a union type renders one linked token per member, each on its own line, with a `| undefined` member dropped and marking the property optional.
|
|
17
|
+
* - Descriptions render as inline markup, with a trailing `Defaults to …` (linked when documented) or `Required.` note — see [`DocumentationDescription`](/ui/DocumentationDescription). A row with no hand-written description falls back to the referenced type's own `description`.
|
|
18
|
+
* - These are the same structured entries an options-bag parameter is flattened from in [`DocumentationParams`](/ui/DocumentationParams), so the two stay in sync by construction.
|
|
19
|
+
* - Renders nothing when there are no properties.
|
|
20
|
+
*
|
|
21
|
+
* @kind component
|
|
22
|
+
* @returns A [`<Section>`](/ui/Section) containing the properties table, or `null` when there are none.
|
|
23
|
+
* @example <DocumentationProperties properties={properties} />
|
|
24
|
+
* @see https://dhoulb.github.io/shelving/ui/docs/DocumentationProperties/DocumentationProperties
|
|
25
|
+
*/
|
|
26
|
+
export declare function DocumentationProperties({ properties }: DocumentationPropertiesProps): ReactNode;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Section } from "../block/Section.js";
|
|
3
|
+
import { Code } from "../inline/Code.js";
|
|
4
|
+
import { Scroll } from "../style/Scroll.js";
|
|
5
|
+
import { Cell } from "../table/Cell.js";
|
|
6
|
+
import { Table } from "../table/Table.js";
|
|
7
|
+
import { getTreeElement, useTreeMap } from "../tree/TreeContext.js";
|
|
8
|
+
import { DocumentationDescription } from "./DocumentationDescription.js";
|
|
9
|
+
import { DocumentationType, splitType } from "./DocumentationType.js";
|
|
10
|
+
const DEFAULT_TYPE = "unknown";
|
|
11
|
+
/**
|
|
12
|
+
* Render a documented type's data members (properties, getters/setters) as a scrollable table — one row per property.
|
|
13
|
+
* - Self-contained: pulls its own copy of the tree map from [`useTreeMap()`](/ui/useTreeMap) so the `Type` column can link each type to its documented page via [`TreeLink`](/ui/TreeLink) (exact-match only; compound or builtin types stay plain text).
|
|
14
|
+
* - Each property name carries a leading `.` (e.g. `.caller`); a union type renders one linked token per member, each on its own line, with a `| undefined` member dropped and marking the property optional.
|
|
15
|
+
* - Descriptions render as inline markup, with a trailing `Defaults to …` (linked when documented) or `Required.` note — see [`DocumentationDescription`](/ui/DocumentationDescription). A row with no hand-written description falls back to the referenced type's own `description`.
|
|
16
|
+
* - These are the same structured entries an options-bag parameter is flattened from in [`DocumentationParams`](/ui/DocumentationParams), so the two stay in sync by construction.
|
|
17
|
+
* - Renders nothing when there are no properties.
|
|
18
|
+
*
|
|
19
|
+
* @kind component
|
|
20
|
+
* @returns A [`<Section>`](/ui/Section) containing the properties table, or `null` when there are none.
|
|
21
|
+
* @example <DocumentationProperties properties={properties} />
|
|
22
|
+
* @see https://dhoulb.github.io/shelving/ui/docs/DocumentationProperties/DocumentationProperties
|
|
23
|
+
*/
|
|
24
|
+
export function DocumentationProperties({ properties }) {
|
|
25
|
+
const map = useTreeMap();
|
|
26
|
+
if (!properties?.length)
|
|
27
|
+
return null;
|
|
28
|
+
return (_jsx(Section, { children: _jsx(Scroll, { horizontal: true, children: _jsxs(Table, { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx(Cell, { header: true, width: "fit", children: "Property" }), _jsx(Cell, { header: true, width: "fit", children: "Type" }), _jsx(Cell, { header: true, width: "xxnarrow", grow: true })] }) }), _jsx("tbody", { children: properties.map(({ name, type = DEFAULT_TYPE, description, default: def, optional, readonly }) => {
|
|
29
|
+
const { members, optional: typeOptional } = splitType(type);
|
|
30
|
+
return (_jsxs("tr", { children: [_jsx(Cell, { nowrap: true, children: _jsx(Code, { nowrap: true, children: `.${name}` }) }), _jsx("td", { children: _jsx(DocumentationType, { members: members }) }), _jsx("td", { children: _jsx(DocumentationDescription, { description: description || getTreeElement(map, members[0] ?? DEFAULT_TYPE)?.props.description, default: def, optional: !!optional || typeOptional, readonly: readonly }) })] }, `${name}-${type}`));
|
|
31
|
+
}) })] }) }) }));
|
|
32
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { ImmutableArray } from "../../util/array.js";
|
|
3
|
+
import type { DocumentationParam } from "../../util/tree.js";
|
|
4
|
+
import { Section } from "../block/Section.js";
|
|
5
|
+
import { Code } from "../inline/Code.js";
|
|
6
|
+
import { Scroll } from "../style/Scroll.js";
|
|
7
|
+
import { Cell } from "../table/Cell.js";
|
|
8
|
+
import { Table } from "../table/Table.js";
|
|
9
|
+
import { getTreeElement, useTreeMap } from "../tree/TreeContext.js";
|
|
10
|
+
import { DocumentationDescription } from "./DocumentationDescription.js";
|
|
11
|
+
import { DocumentationType, splitType } from "./DocumentationType.js";
|
|
12
|
+
|
|
13
|
+
const DEFAULT_TYPE = "unknown";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Props for `DocumentationProperties` — the data members of a class/interface/object-literal type to render, one row each.
|
|
17
|
+
*
|
|
18
|
+
* @see https://dhoulb.github.io/shelving/ui/docs/DocumentationProperties/DocumentationPropertiesProps
|
|
19
|
+
*/
|
|
20
|
+
export interface DocumentationPropertiesProps {
|
|
21
|
+
/** Properties to render — one row each. */
|
|
22
|
+
readonly properties?: ImmutableArray<DocumentationParam> | undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Render a documented type's data members (properties, getters/setters) as a scrollable table — one row per property.
|
|
27
|
+
* - Self-contained: pulls its own copy of the tree map from [`useTreeMap()`](/ui/useTreeMap) so the `Type` column can link each type to its documented page via [`TreeLink`](/ui/TreeLink) (exact-match only; compound or builtin types stay plain text).
|
|
28
|
+
* - Each property name carries a leading `.` (e.g. `.caller`); a union type renders one linked token per member, each on its own line, with a `| undefined` member dropped and marking the property optional.
|
|
29
|
+
* - Descriptions render as inline markup, with a trailing `Defaults to …` (linked when documented) or `Required.` note — see [`DocumentationDescription`](/ui/DocumentationDescription). A row with no hand-written description falls back to the referenced type's own `description`.
|
|
30
|
+
* - These are the same structured entries an options-bag parameter is flattened from in [`DocumentationParams`](/ui/DocumentationParams), so the two stay in sync by construction.
|
|
31
|
+
* - Renders nothing when there are no properties.
|
|
32
|
+
*
|
|
33
|
+
* @kind component
|
|
34
|
+
* @returns A [`<Section>`](/ui/Section) containing the properties table, or `null` when there are none.
|
|
35
|
+
* @example <DocumentationProperties properties={properties} />
|
|
36
|
+
* @see https://dhoulb.github.io/shelving/ui/docs/DocumentationProperties/DocumentationProperties
|
|
37
|
+
*/
|
|
38
|
+
export function DocumentationProperties({ properties }: DocumentationPropertiesProps): ReactNode {
|
|
39
|
+
const map = useTreeMap();
|
|
40
|
+
if (!properties?.length) return null;
|
|
41
|
+
return (
|
|
42
|
+
<Section>
|
|
43
|
+
<Scroll horizontal>
|
|
44
|
+
<Table>
|
|
45
|
+
<thead>
|
|
46
|
+
<tr>
|
|
47
|
+
<Cell header width="fit">
|
|
48
|
+
Property
|
|
49
|
+
</Cell>
|
|
50
|
+
<Cell header width="fit">
|
|
51
|
+
Type
|
|
52
|
+
</Cell>
|
|
53
|
+
<Cell header width="xxnarrow" grow />
|
|
54
|
+
</tr>
|
|
55
|
+
</thead>
|
|
56
|
+
<tbody>
|
|
57
|
+
{properties.map(({ name, type = DEFAULT_TYPE, description, default: def, optional, readonly }) => {
|
|
58
|
+
const { members, optional: typeOptional } = splitType(type);
|
|
59
|
+
return (
|
|
60
|
+
<tr key={`${name}-${type}`}>
|
|
61
|
+
<Cell nowrap>
|
|
62
|
+
<Code nowrap>{`.${name}`}</Code>
|
|
63
|
+
</Cell>
|
|
64
|
+
<td>
|
|
65
|
+
<DocumentationType members={members} />
|
|
66
|
+
</td>
|
|
67
|
+
<td>
|
|
68
|
+
<DocumentationDescription
|
|
69
|
+
description={description || getTreeElement(map, members[0] ?? DEFAULT_TYPE)?.props.description}
|
|
70
|
+
default={def}
|
|
71
|
+
optional={!!optional || typeOptional}
|
|
72
|
+
readonly={readonly}
|
|
73
|
+
/>
|
|
74
|
+
</td>
|
|
75
|
+
</tr>
|
|
76
|
+
);
|
|
77
|
+
})}
|
|
78
|
+
</tbody>
|
|
79
|
+
</Table>
|
|
80
|
+
</Scroll>
|
|
81
|
+
</Section>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Split a type expression on ` | ` into its individual union members.
|
|
4
|
+
* - An `undefined` member is dropped from display and instead flags the value as optional — we often write `| undefined` explicitly (e.g. for `exactOptionalPropertyTypes`, or to allow an explicit `undefined` to trigger a default), which reads as noise in the docs.
|
|
5
|
+
* - When nothing but `undefined` is left, the members are kept as-is rather than emptied.
|
|
6
|
+
*
|
|
7
|
+
* @param type The type expression to split (e.g. `"Schemas<T> | DataSchema<T>"`).
|
|
8
|
+
* @returns The non-`undefined` members, plus whether an `undefined` member was dropped.
|
|
9
|
+
* @see https://dhoulb.github.io/shelving/ui/docs/DocumentationType/splitType
|
|
10
|
+
*/
|
|
11
|
+
export declare function splitType(type: string): {
|
|
12
|
+
readonly members: readonly string[];
|
|
13
|
+
readonly optional: boolean;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Props for `DocumentationType` — the union members to render (see [`splitType`](/ui/splitType)).
|
|
17
|
+
*
|
|
18
|
+
* @see https://dhoulb.github.io/shelving/ui/docs/DocumentationType/DocumentationTypeProps
|
|
19
|
+
*/
|
|
20
|
+
export interface DocumentationTypeProps {
|
|
21
|
+
/** Union members to render — one linked token each, stacked on their own line. */
|
|
22
|
+
readonly members: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Render a documentation table's `Type` column — one linked token per union member, each stacked on its own line.
|
|
26
|
+
* - Each member links to its documented page via [`TreeLink`](/ui/TreeLink) (exact-match only; compound or builtin types stay plain text).
|
|
27
|
+
*
|
|
28
|
+
* @kind component
|
|
29
|
+
* @returns One [`TreeLink`](/ui/TreeLink) per member, separated by line breaks.
|
|
30
|
+
* @example <DocumentationType members={["Schemas<T>", "DataSchema<T>"]} />
|
|
31
|
+
* @see https://dhoulb.github.io/shelving/ui/docs/DocumentationType/DocumentationType
|
|
32
|
+
*/
|
|
33
|
+
export declare function DocumentationType({ members }: DocumentationTypeProps): ReactNode;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment } from "react";
|
|
3
|
+
import { TreeLink } from "../tree/TreeLink.js";
|
|
4
|
+
/**
|
|
5
|
+
* Split a type expression on ` | ` into its individual union members.
|
|
6
|
+
* - An `undefined` member is dropped from display and instead flags the value as optional — we often write `| undefined` explicitly (e.g. for `exactOptionalPropertyTypes`, or to allow an explicit `undefined` to trigger a default), which reads as noise in the docs.
|
|
7
|
+
* - When nothing but `undefined` is left, the members are kept as-is rather than emptied.
|
|
8
|
+
*
|
|
9
|
+
* @param type The type expression to split (e.g. `"Schemas<T> | DataSchema<T>"`).
|
|
10
|
+
* @returns The non-`undefined` members, plus whether an `undefined` member was dropped.
|
|
11
|
+
* @see https://dhoulb.github.io/shelving/ui/docs/DocumentationType/splitType
|
|
12
|
+
*/
|
|
13
|
+
export function splitType(type) {
|
|
14
|
+
const parts = type
|
|
15
|
+
.split(" | ")
|
|
16
|
+
.map(part => part.trim())
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
const members = parts.filter(part => part !== "undefined");
|
|
19
|
+
return members.length ? { members, optional: members.length !== parts.length } : { members: parts, optional: false };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Render a documentation table's `Type` column — one linked token per union member, each stacked on its own line.
|
|
23
|
+
* - Each member links to its documented page via [`TreeLink`](/ui/TreeLink) (exact-match only; compound or builtin types stay plain text).
|
|
24
|
+
*
|
|
25
|
+
* @kind component
|
|
26
|
+
* @returns One [`TreeLink`](/ui/TreeLink) per member, separated by line breaks.
|
|
27
|
+
* @example <DocumentationType members={["Schemas<T>", "DataSchema<T>"]} />
|
|
28
|
+
* @see https://dhoulb.github.io/shelving/ui/docs/DocumentationType/DocumentationType
|
|
29
|
+
*/
|
|
30
|
+
export function DocumentationType({ members }) {
|
|
31
|
+
return members.map((member, index) => (_jsxs(Fragment, { children: [index > 0 && _jsx("br", {}), _jsx(TreeLink, { name: member, nowrap: true })] }, member)));
|
|
32
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Fragment, type ReactNode } from "react";
|
|
2
|
+
import { TreeLink } from "../tree/TreeLink.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Split a type expression on ` | ` into its individual union members.
|
|
6
|
+
* - An `undefined` member is dropped from display and instead flags the value as optional — we often write `| undefined` explicitly (e.g. for `exactOptionalPropertyTypes`, or to allow an explicit `undefined` to trigger a default), which reads as noise in the docs.
|
|
7
|
+
* - When nothing but `undefined` is left, the members are kept as-is rather than emptied.
|
|
8
|
+
*
|
|
9
|
+
* @param type The type expression to split (e.g. `"Schemas<T> | DataSchema<T>"`).
|
|
10
|
+
* @returns The non-`undefined` members, plus whether an `undefined` member was dropped.
|
|
11
|
+
* @see https://dhoulb.github.io/shelving/ui/docs/DocumentationType/splitType
|
|
12
|
+
*/
|
|
13
|
+
export function splitType(type: string): { readonly members: readonly string[]; readonly optional: boolean } {
|
|
14
|
+
const parts = type
|
|
15
|
+
.split(" | ")
|
|
16
|
+
.map(part => part.trim())
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
const members = parts.filter(part => part !== "undefined");
|
|
19
|
+
return members.length ? { members, optional: members.length !== parts.length } : { members: parts, optional: false };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Props for `DocumentationType` — the union members to render (see [`splitType`](/ui/splitType)).
|
|
24
|
+
*
|
|
25
|
+
* @see https://dhoulb.github.io/shelving/ui/docs/DocumentationType/DocumentationTypeProps
|
|
26
|
+
*/
|
|
27
|
+
export interface DocumentationTypeProps {
|
|
28
|
+
/** Union members to render — one linked token each, stacked on their own line. */
|
|
29
|
+
readonly members: readonly string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Render a documentation table's `Type` column — one linked token per union member, each stacked on its own line.
|
|
34
|
+
* - Each member links to its documented page via [`TreeLink`](/ui/TreeLink) (exact-match only; compound or builtin types stay plain text).
|
|
35
|
+
*
|
|
36
|
+
* @kind component
|
|
37
|
+
* @returns One [`TreeLink`](/ui/TreeLink) per member, separated by line breaks.
|
|
38
|
+
* @example <DocumentationType members={["Schemas<T>", "DataSchema<T>"]} />
|
|
39
|
+
* @see https://dhoulb.github.io/shelving/ui/docs/DocumentationType/DocumentationType
|
|
40
|
+
*/
|
|
41
|
+
export function DocumentationType({ members }: DocumentationTypeProps): ReactNode {
|
|
42
|
+
return members.map((member, index) => (
|
|
43
|
+
<Fragment key={member}>
|
|
44
|
+
{index > 0 && <br />}
|
|
45
|
+
<TreeLink name={member} nowrap />
|
|
46
|
+
</Fragment>
|
|
47
|
+
));
|
|
48
|
+
}
|
package/ui/docs/index.d.ts
CHANGED
|
@@ -5,7 +5,9 @@ export * from "./DocumentationHomePage.js";
|
|
|
5
5
|
export * from "./DocumentationKind.js";
|
|
6
6
|
export * from "./DocumentationPage.js";
|
|
7
7
|
export * from "./DocumentationParams.js";
|
|
8
|
+
export * from "./DocumentationProperties.js";
|
|
8
9
|
export * from "./DocumentationReferences.js";
|
|
9
10
|
export * from "./DocumentationReturns.js";
|
|
10
11
|
export * from "./DocumentationSignatures.js";
|
|
11
12
|
export * from "./DocumentationThrows.js";
|
|
13
|
+
export * from "./DocumentationType.js";
|
package/ui/docs/index.js
CHANGED
|
@@ -5,7 +5,9 @@ export * from "./DocumentationHomePage.js";
|
|
|
5
5
|
export * from "./DocumentationKind.js";
|
|
6
6
|
export * from "./DocumentationPage.js";
|
|
7
7
|
export * from "./DocumentationParams.js";
|
|
8
|
+
export * from "./DocumentationProperties.js";
|
|
8
9
|
export * from "./DocumentationReferences.js";
|
|
9
10
|
export * from "./DocumentationReturns.js";
|
|
10
11
|
export * from "./DocumentationSignatures.js";
|
|
11
12
|
export * from "./DocumentationThrows.js";
|
|
13
|
+
export * from "./DocumentationType.js";
|
package/ui/docs/index.ts
CHANGED
|
@@ -5,7 +5,9 @@ export * from "./DocumentationHomePage.js";
|
|
|
5
5
|
export * from "./DocumentationKind.js";
|
|
6
6
|
export * from "./DocumentationPage.js";
|
|
7
7
|
export * from "./DocumentationParams.js";
|
|
8
|
+
export * from "./DocumentationProperties.js";
|
|
8
9
|
export * from "./DocumentationReferences.js";
|
|
9
10
|
export * from "./DocumentationReturns.js";
|
|
10
11
|
export * from "./DocumentationSignatures.js";
|
|
11
12
|
export * from "./DocumentationThrows.js";
|
|
13
|
+
export * from "./DocumentationType.js";
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { RouteCache } from "../router/RouteCache.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getModuleClass } from "../util/css.js";
|
|
4
4
|
import CENTERED_LAYOUT_CSS from "./CenteredLayout.module.css";
|
|
5
|
-
import { LAYOUT_CLASS } from "./Layout.js";
|
|
6
5
|
/**
|
|
7
6
|
* Layout that centres its content with no header/footer and a narrow max-width.
|
|
8
7
|
* - Used for e.g. login/register/error/form pages where the content is the only focus.
|
|
@@ -17,5 +16,5 @@ import { LAYOUT_CLASS } from "./Layout.js";
|
|
|
17
16
|
export function CenteredLayout({ children, fullWidth = false }) {
|
|
18
17
|
// Wrap the scrolling `<main>` in `<RouteCache>` so recently-visited pages stay mounted but hidden,
|
|
19
18
|
// keeping their scroll position and state intact across back/forward navigation.
|
|
20
|
-
return (_jsx(RouteCache, { children: _jsx("main", { className:
|
|
19
|
+
return (_jsx(RouteCache, { children: _jsx("main", { className: getModuleClass(CENTERED_LAYOUT_CSS, "main"), children: _jsx("div", { className: getModuleClass(CENTERED_LAYOUT_CSS, "mainInner"), style: fullWidth ? { maxWidth: "none" } : undefined, children: children }) }) }));
|
|
21
20
|
}
|
|
@@ -27,6 +27,6 @@ Layouts compose naturally as `<Router>` route values — wrap a group of routes
|
|
|
27
27
|
|
|
28
28
|
## Styling
|
|
29
29
|
|
|
30
|
-
This layout exposes no own `--centered-layout-*` hooks. The inner column is capped at the global `--width-wide` token (dropped when `fullWidth` is set), and the outer element
|
|
30
|
+
This layout exposes no own `--centered-layout-*` hooks. The inner column is capped at the global `--width-wide` token (dropped when `fullWidth` is set), and the outer element owns its scroll, padding, and safe-area behaviour directly — reading the layout hooks `--layout-space`, `--layout-padding`, `--layout-body-bg`, and `--layout-inset-top` / `-bottom` / `-left` / `-right`.
|
|
31
31
|
|
|
32
32
|
**Global tokens it reads** — `--width-wide`.
|
|
@@ -1,13 +1,73 @@
|
|
|
1
1
|
@import "../style/layers.css";
|
|
2
|
+
@import "../style/Space.module.css";
|
|
3
|
+
@import "../style/Tint.module.css";
|
|
2
4
|
@import "../style/Width.module.css";
|
|
3
5
|
|
|
6
|
+
/* Low-priority padding defaults live in `@layer defaults` so consumers can override them */
|
|
7
|
+
/* with normal-specificity selectors without needing `:where()` tricks. */
|
|
8
|
+
@layer defaults {
|
|
9
|
+
.main {
|
|
10
|
+
/* Top/bottom use `layout-space` and left/right use `layout-padding`, plus safe-area additions. */
|
|
11
|
+
padding: var(--layout-space, var(--space-normal)) var(--layout-padding, var(--space-normal));
|
|
12
|
+
padding-top: calc(var(--layout-space, var(--space-normal)) + max(var(--layout-inset-top, 0px), env(safe-area-inset-top, 0px)));
|
|
13
|
+
padding-bottom: calc(var(--layout-space, var(--space-normal)) + max(var(--layout-inset-bottom, 0px), env(safe-area-inset-bottom, 0px)));
|
|
14
|
+
padding-left: calc(var(--layout-padding, var(--space-normal)) + max(var(--layout-inset-left, 0px), env(safe-area-inset-left, 0px)));
|
|
15
|
+
padding-right: calc(var(--layout-padding, var(--space-normal)) + max(var(--layout-inset-right, 0px), env(safe-area-inset-right, 0px)));
|
|
16
|
+
|
|
17
|
+
/* Grow to fill the body. */
|
|
18
|
+
flex: 1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
4
22
|
@layer components {
|
|
23
|
+
/*
|
|
24
|
+
* Lock the document and let `.main` own the page scroll.
|
|
25
|
+
* - <html> and <body> are pinned to the viewport (no rubber-band on iOS).
|
|
26
|
+
* - <body> stacks its children vertically (flex column).
|
|
27
|
+
* - The `.main` element grows to fill the body and is the only scroll container.
|
|
28
|
+
*/
|
|
29
|
+
html:has(.main) {
|
|
30
|
+
/* Box */
|
|
31
|
+
margin: 0;
|
|
32
|
+
padding: 0;
|
|
33
|
+
height: 100vh;
|
|
34
|
+
height: 100dvh; /* Make the height dynamically expand/contract as the address bar shows/hides on iOS. */
|
|
35
|
+
width: 100vw;
|
|
36
|
+
width: 100dvw;
|
|
37
|
+
|
|
38
|
+
/* Contents */
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
overflow-wrap: break-word; /* Break overlong words only as a last resort. */
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
body:has(.main) {
|
|
44
|
+
/* Box */
|
|
45
|
+
margin: 0;
|
|
46
|
+
padding: 0;
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-direction: column;
|
|
49
|
+
|
|
50
|
+
/* Lock body to prevent iOS Safari triggering viewport scrolling on focus change. */
|
|
51
|
+
position: fixed;
|
|
52
|
+
inset: 0;
|
|
53
|
+
|
|
54
|
+
/* Layout-specific bg hook. */
|
|
55
|
+
background: var(--layout-body-bg, var(--tint-100));
|
|
56
|
+
}
|
|
57
|
+
|
|
5
58
|
.main {
|
|
6
|
-
/* Content */
|
|
59
|
+
/* Content — a flex column whose inner column centres itself with `margin: auto` (both axes). */
|
|
7
60
|
display: flex;
|
|
8
61
|
flex-direction: column;
|
|
9
62
|
align-items: center;
|
|
10
63
|
justify-content: start;
|
|
64
|
+
|
|
65
|
+
/* Scrolling */
|
|
66
|
+
overflow-x: hidden;
|
|
67
|
+
overflow-y: auto;
|
|
68
|
+
overscroll-behavior-x: auto;
|
|
69
|
+
overscroll-behavior-y: contain;
|
|
70
|
+
scroll-behavior: smooth;
|
|
11
71
|
}
|
|
12
72
|
|
|
13
73
|
.mainInner {
|