shelving 1.200.1 → 1.202.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.
- package/api/provider/APIProvider.d.ts +2 -1
- package/api/provider/APIProvider.js +4 -0
- package/api/provider/CachedAPIProvider.d.ts +2 -1
- package/api/provider/CachedAPIProvider.js +6 -0
- package/api/provider/ThroughAPIProvider.d.ts +1 -0
- package/api/provider/ThroughAPIProvider.js +5 -0
- package/db/provider/CacheDBProvider.d.ts +1 -0
- package/db/provider/CacheDBProvider.js +7 -0
- package/db/provider/ChangesDBProvider.d.ts +4 -1
- package/db/provider/ChangesDBProvider.js +4 -1
- package/db/provider/DBProvider.d.ts +2 -1
- package/db/provider/DBProvider.js +7 -0
- package/db/provider/MemoryDBProvider.d.ts +1 -1
- package/db/provider/MemoryDBProvider.js +1 -1
- package/db/provider/ThroughDBProvider.d.ts +1 -0
- package/db/provider/ThroughDBProvider.js +5 -0
- package/markup/render.d.ts +4 -4
- package/markup/render.js +4 -4
- package/markup/rule/unordered.d.ts +2 -2
- package/markup/util/rule.d.ts +4 -4
- package/package.json +4 -2
- package/ui/app/App.d.ts +2 -2
- package/ui/app/App.js +4 -6
- package/ui/app/App.tsx +6 -8
- package/ui/block/Video.js +1 -1
- package/ui/block/Video.tsx +1 -1
- package/ui/layout/SidebarLayout.d.ts +17 -0
- package/ui/layout/SidebarLayout.js +15 -0
- package/ui/layout/SidebarLayout.module.css +47 -0
- package/ui/layout/SidebarLayout.tsx +36 -0
- package/ui/layout/index.d.ts +1 -0
- package/ui/layout/index.js +1 -0
- package/ui/layout/index.tsx +1 -0
- package/ui/misc/Meta.d.ts +2 -2
- package/ui/misc/Meta.tsx +2 -2
- package/ui/page/HTML.d.ts +10 -0
- package/ui/page/HTML.js +11 -0
- package/ui/page/HTML.tsx +20 -0
- package/ui/page/Head.d.ts +0 -5
- package/ui/page/Head.js +4 -2
- package/ui/page/Head.tsx +3 -8
- package/ui/page/Page.d.ts +4 -3
- package/ui/page/Page.js +2 -1
- package/ui/page/Page.tsx +4 -3
- package/ui/page/index.d.ts +1 -0
- package/ui/page/index.js +1 -0
- package/ui/page/index.ts +1 -0
- package/ui/router/Router.d.ts +13 -11
- package/ui/router/Router.js +13 -3
- package/ui/router/Router.tsx +19 -14
- package/ui/router/RouterStore.d.ts +1 -1
- package/ui/router/RouterStore.js +2 -2
- package/ui/router/RouterStore.tsx +2 -2
- package/ui/util/meta.d.ts +8 -8
- package/ui/util/meta.js +5 -5
- package/ui/util/meta.ts +11 -11
- package/util/element.d.ts +32 -0
- package/util/element.js +41 -0
- package/util/index.d.ts +1 -1
- package/util/index.js +1 -1
- package/util/jsx.d.ts +0 -32
- package/util/jsx.js +0 -41
|
@@ -2,7 +2,7 @@ import type { AnyCaller } from "../../util/function.js";
|
|
|
2
2
|
import type { RequestOptions } from "../../util/http.js";
|
|
3
3
|
import type { Endpoint } from "../endpoint/Endpoint.js";
|
|
4
4
|
/** Provider for API endpoints rooted at a common base URL. */
|
|
5
|
-
export declare abstract class APIProvider<P = unknown, R = unknown> {
|
|
5
|
+
export declare abstract class APIProvider<P = unknown, R = unknown> implements AsyncDisposable {
|
|
6
6
|
/** The base URL for this API. */
|
|
7
7
|
abstract readonly url: URL;
|
|
8
8
|
/**
|
|
@@ -43,4 +43,5 @@ export declare abstract class APIProvider<P = unknown, R = unknown> {
|
|
|
43
43
|
abstract parseResponse<PP extends P, RR extends R>(_endpoint: Endpoint<PP, RR>, response: Response, caller?: AnyCaller): Promise<RR>;
|
|
44
44
|
/** Send a payload to an `Endpoint` and retrieve the result. */
|
|
45
45
|
call<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, options?: RequestOptions, caller?: AnyCaller): Promise<RR>;
|
|
46
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
46
47
|
}
|
|
@@ -13,7 +13,7 @@ import { ThroughAPIProvider } from "./ThroughAPIProvider.js";
|
|
|
13
13
|
* - Note: This is not used for `refresh()` calls — when you call `refresh()` you likely mean "do it now".
|
|
14
14
|
* - When we are using `call()` on a cache, the entire point of the cache is to "cache", so the default isn't `0` like it is for `refresh()`
|
|
15
15
|
*/
|
|
16
|
-
export declare class CachedAPIProvider<P, R> extends ThroughAPIProvider<P, R> {
|
|
16
|
+
export declare class CachedAPIProvider<P, R> extends ThroughAPIProvider<P, R> implements AsyncDisposable {
|
|
17
17
|
readonly maxAge: number | undefined;
|
|
18
18
|
private readonly _cache;
|
|
19
19
|
constructor(source: APIProvider<P, R>, maxAge?: number);
|
|
@@ -22,4 +22,5 @@ export declare class CachedAPIProvider<P, R> extends ThroughAPIProvider<P, R> {
|
|
|
22
22
|
invalidateAll<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>): void;
|
|
23
23
|
refresh<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP): void;
|
|
24
24
|
refreshAll<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>): void;
|
|
25
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
25
26
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AVOID_REFRESH } from "../../store/FetchStore.js";
|
|
2
|
+
import { awaitDispose } from "../../util/dispose.js";
|
|
2
3
|
import { APICache } from "../cache/APICache.js";
|
|
3
4
|
import { ThroughAPIProvider } from "./ThroughAPIProvider.js";
|
|
4
5
|
/**
|
|
@@ -35,4 +36,9 @@ export class CachedAPIProvider extends ThroughAPIProvider {
|
|
|
35
36
|
refreshAll(endpoint) {
|
|
36
37
|
this._cache.refreshAll(endpoint, this.maxAge);
|
|
37
38
|
}
|
|
39
|
+
// Implement `AsyncDisposable`
|
|
40
|
+
async [Symbol.asyncDispose]() {
|
|
41
|
+
await awaitDispose(this._cache, // Dispose the cache.
|
|
42
|
+
super[Symbol.asyncDispose]());
|
|
43
|
+
}
|
|
38
44
|
}
|
|
@@ -15,4 +15,5 @@ export declare class ThroughAPIProvider<P, R> extends APIProvider<P, R> implemen
|
|
|
15
15
|
getRequest<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, options?: RequestOptions, caller?: AnyCaller): Request;
|
|
16
16
|
parseResponse<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, response: Response, caller?: AnyCaller): Promise<RR>;
|
|
17
17
|
fetch(request: Request): Promise<Response>;
|
|
18
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
18
19
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { awaitDispose } from "../../util/dispose.js";
|
|
1
2
|
import { APIProvider } from "./APIProvider.js";
|
|
2
3
|
/**
|
|
3
4
|
* Provider wrapper that delegates API operations to a source provider.
|
|
@@ -24,4 +25,8 @@ export class ThroughAPIProvider extends APIProvider {
|
|
|
24
25
|
fetch(request) {
|
|
25
26
|
return this.source.fetch(request);
|
|
26
27
|
}
|
|
28
|
+
// Implement `AsyncDisposable`
|
|
29
|
+
async [Symbol.asyncDispose]() {
|
|
30
|
+
await awaitDispose(this.source);
|
|
31
|
+
}
|
|
27
32
|
}
|
|
@@ -23,4 +23,5 @@ export declare class CacheDBProvider<I extends Identifier, T extends Data> exten
|
|
|
23
23
|
setQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>, data: TT): Promise<void>;
|
|
24
24
|
updateQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>, updates: Updates<TT>): Promise<void>;
|
|
25
25
|
deleteQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>): Promise<void>;
|
|
26
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
26
27
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { awaitDispose } from "../../util/dispose.js";
|
|
1
2
|
import { DBProvider } from "./DBProvider.js";
|
|
2
3
|
import { MemoryDBProvider } from "./MemoryDBProvider.js";
|
|
3
4
|
/** Keep a copy of asynchronous remote data in a local synchronous cache. */
|
|
@@ -58,4 +59,10 @@ export class CacheDBProvider extends DBProvider {
|
|
|
58
59
|
await this.source.deleteQuery(collection, query);
|
|
59
60
|
this.memory.getTable(collection).deleteQuery(query);
|
|
60
61
|
}
|
|
62
|
+
// Implement `AsyncDisposable`
|
|
63
|
+
async [Symbol.asyncDispose]() {
|
|
64
|
+
await awaitDispose(this.source, // Dispose the source API provider.
|
|
65
|
+
this.memory, // Dispose the source API provider.
|
|
66
|
+
super[Symbol.asyncDispose]());
|
|
67
|
+
}
|
|
61
68
|
}
|
|
@@ -14,7 +14,10 @@ export type DBChange<I extends Identifier> = {
|
|
|
14
14
|
readonly data?: unknown;
|
|
15
15
|
readonly updates?: unknown;
|
|
16
16
|
};
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Database provider that keeps a log of any written changes to its `.changes` property.
|
|
19
|
+
* - This is useful if you want to hook into a database to build out audit logging functionality.
|
|
20
|
+
*/
|
|
18
21
|
export declare class ChangesDBProvider<I extends Identifier, T extends Data> extends ThroughDBProvider<I, T> {
|
|
19
22
|
get changes(): ReadonlyArray<DBChange<I>>;
|
|
20
23
|
readonly _changes: MutableArray<DBChange<I>>;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { ThroughDBProvider } from "./ThroughDBProvider.js";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Database provider that keeps a log of any written changes to its `.changes` property.
|
|
4
|
+
* - This is useful if you want to hook into a database to build out audit logging functionality.
|
|
5
|
+
*/
|
|
3
6
|
export class ChangesDBProvider extends ThroughDBProvider {
|
|
4
7
|
get changes() {
|
|
5
8
|
return this._changes;
|
|
@@ -4,7 +4,7 @@ import type { Query } from "../../util/query.js";
|
|
|
4
4
|
import type { Updates } from "../../util/update.js";
|
|
5
5
|
import type { Collection } from "../collection/Collection.js";
|
|
6
6
|
/** Provider with a fully asynchronous interface for database access. */
|
|
7
|
-
export declare abstract class DBProvider<I extends Identifier = Identifier, T extends Data = Data> {
|
|
7
|
+
export declare abstract class DBProvider<I extends Identifier = Identifier, T extends Data = Data> implements AsyncDisposable {
|
|
8
8
|
abstract getItem<II extends I, TT extends T>(collection: Collection<string, II, TT>, id: II): Promise<OptionalItem<II, TT>>;
|
|
9
9
|
requireItem<II extends I, TT extends T>(collection: Collection<string, II, TT>, id: II): Promise<Item<II, TT>>;
|
|
10
10
|
abstract getItemSequence<II extends I, TT extends T>(collection: Collection<string, II, TT>, id: II): OptionalItemSequence<II, TT>;
|
|
@@ -20,4 +20,5 @@ export declare abstract class DBProvider<I extends Identifier = Identifier, T ex
|
|
|
20
20
|
abstract deleteQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>): Promise<void>;
|
|
21
21
|
getFirst<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>): Promise<OptionalItem<II, TT>>;
|
|
22
22
|
requireFirst<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>): Promise<Item<II, TT>>;
|
|
23
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
23
24
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { RequiredError } from "../../error/RequiredError.js";
|
|
2
2
|
import { countArray, getFirst } from "../../util/array.js";
|
|
3
|
+
import { awaitDispose } from "../../util/dispose.js";
|
|
3
4
|
/** Provider with a fully asynchronous interface for database access. */
|
|
4
5
|
export class DBProvider {
|
|
5
6
|
async requireItem(collection, id) {
|
|
@@ -30,4 +31,10 @@ export class DBProvider {
|
|
|
30
31
|
});
|
|
31
32
|
return first;
|
|
32
33
|
}
|
|
34
|
+
// Implement `AsyncDisposable`
|
|
35
|
+
async [Symbol.asyncDispose]() {
|
|
36
|
+
await awaitDispose(
|
|
37
|
+
// Empty by default.
|
|
38
|
+
);
|
|
39
|
+
}
|
|
33
40
|
}
|
|
@@ -8,7 +8,7 @@ import { DBProvider } from "./DBProvider.js";
|
|
|
8
8
|
/**
|
|
9
9
|
* Fast in-memory store for data.
|
|
10
10
|
* - Extremely fast (ideal for caching!), but does not persist data after the browser window is closed.
|
|
11
|
-
* - `
|
|
11
|
+
* - `getItem()` etc return the exact same instance of an object that's passed into `setItem()`
|
|
12
12
|
*/
|
|
13
13
|
export declare class MemoryDBProvider<I extends Identifier = Identifier, T extends Data = Data> extends DBProvider<I, T> {
|
|
14
14
|
/** List of tables in `{ name: MemoryTable }` format. */
|
|
@@ -11,7 +11,7 @@ import { DBProvider } from "./DBProvider.js";
|
|
|
11
11
|
/**
|
|
12
12
|
* Fast in-memory store for data.
|
|
13
13
|
* - Extremely fast (ideal for caching!), but does not persist data after the browser window is closed.
|
|
14
|
-
* - `
|
|
14
|
+
* - `getItem()` etc return the exact same instance of an object that's passed into `setItem()`
|
|
15
15
|
*/
|
|
16
16
|
export class MemoryDBProvider extends DBProvider {
|
|
17
17
|
/** List of tables in `{ name: MemoryTable }` format. */
|
|
@@ -24,4 +24,5 @@ export declare class ThroughDBProvider<I extends Identifier, T extends Data> imp
|
|
|
24
24
|
deleteQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>): Promise<void>;
|
|
25
25
|
getFirst<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>): Promise<OptionalItem<II, TT>>;
|
|
26
26
|
requireFirst<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>): Promise<Item<II, TT>>;
|
|
27
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
27
28
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { awaitDispose } from "../../util/dispose.js";
|
|
1
2
|
/** A provider that passes through to an asynchronous source. */
|
|
2
3
|
export class ThroughDBProvider {
|
|
3
4
|
source;
|
|
@@ -49,4 +50,8 @@ export class ThroughDBProvider {
|
|
|
49
50
|
requireFirst(collection, query) {
|
|
50
51
|
return this.source.requireFirst(collection, query);
|
|
51
52
|
}
|
|
53
|
+
// Implement `AsyncDisposable`
|
|
54
|
+
async [Symbol.asyncDispose]() {
|
|
55
|
+
await awaitDispose(this.source);
|
|
56
|
+
}
|
|
52
57
|
}
|
package/markup/render.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Elements } from "../util/element.js";
|
|
2
2
|
import type { MarkupOptions } from "./util/options.js";
|
|
3
3
|
/**
|
|
4
|
-
* Parse a text string as Markdownish syntax and render it as
|
|
4
|
+
* Parse a text string as Markdownish syntax and render it as elements.
|
|
5
5
|
* - Syntax is not defined by this code, but by the rules supplied to it.
|
|
6
6
|
*
|
|
7
7
|
* @param input The string content possibly containing markup syntax, e.g. "This is a *bold* string.
|
|
8
8
|
* @param options An options object for the render.
|
|
9
9
|
* @param context The context to render in (defaults to `"block"`).
|
|
10
10
|
*
|
|
11
|
-
* @returns
|
|
11
|
+
* @returns Elements, i.e. either a complete `Element`, `null`, `undefined`, `string`, or an array of zero or more of those.
|
|
12
12
|
*/
|
|
13
|
-
export declare function renderMarkup(input: string, options: MarkupOptions, context?: string):
|
|
13
|
+
export declare function renderMarkup(input: string, options: MarkupOptions, context?: string): Elements;
|
package/markup/render.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Parse a text string as Markdownish syntax and render it as
|
|
2
|
+
* Parse a text string as Markdownish syntax and render it as elements.
|
|
3
3
|
* - Syntax is not defined by this code, but by the rules supplied to it.
|
|
4
4
|
*
|
|
5
5
|
* @param input The string content possibly containing markup syntax, e.g. "This is a *bold* string.
|
|
6
6
|
* @param options An options object for the render.
|
|
7
7
|
* @param context The context to render in (defaults to `"block"`).
|
|
8
8
|
*
|
|
9
|
-
* @returns
|
|
9
|
+
* @returns Elements, i.e. either a complete `Element`, `null`, `undefined`, `string`, or an array of zero or more of those.
|
|
10
10
|
*/
|
|
11
11
|
export function renderMarkup(input, options, context = "block") {
|
|
12
12
|
const arr = Array.from(_parseString(input, options, context));
|
|
13
13
|
return !arr.length ? null : arr.length === 1 ? arr[0] : arr;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Parse a string to its corresponding
|
|
17
|
-
* - This code is heavily inspired by `simple-markdown`, but intends to be even simpler (and faster) by always producing
|
|
16
|
+
* Parse a string to its corresponding elements in a given context.
|
|
17
|
+
* - This code is heavily inspired by `simple-markdown`, but intends to be even simpler (and faster) by always producing elements.
|
|
18
18
|
*/
|
|
19
19
|
function* _parseString(
|
|
20
20
|
/** The input string. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Element } from "../../util/element.js";
|
|
2
2
|
import type { MarkupOptions } from "../util/options.js";
|
|
3
3
|
export declare const UNORDERED_REGEXP: import("../../index.js").NamedRegExp<{
|
|
4
4
|
list?: string;
|
|
@@ -13,4 +13,4 @@ export declare const UNORDERED_REGEXP: import("../../index.js").NamedRegExp<{
|
|
|
13
13
|
*/
|
|
14
14
|
export declare const UNORDERED_RULE: import("../util/rule.js").MarkupRule;
|
|
15
15
|
/** Parse a markdown list into a set of items elements. */
|
|
16
|
-
export declare function _getItems(list: string, options: MarkupOptions): Iterable<
|
|
16
|
+
export declare function _getItems(list: string, options: MarkupOptions): Iterable<Element>;
|
package/markup/util/rule.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Element } from "../../util/element.js";
|
|
2
2
|
import type { NamedRegExp, NamedRegExpExecArray } from "../../util/regexp.js";
|
|
3
3
|
import type { MarkupOptions } from "./options.js";
|
|
4
4
|
export type MarkupContexts = [string, ...string[]];
|
|
5
5
|
export interface MarkupRule {
|
|
6
6
|
/** Regular expression used for matching the rule. */
|
|
7
7
|
regexp: RegExp;
|
|
8
|
-
/** Use the matched data to render
|
|
9
|
-
render(match: RegExpExecArray, options: MarkupOptions, key: string):
|
|
8
|
+
/** Use the matched data to render an element. */
|
|
9
|
+
render(match: RegExpExecArray, options: MarkupOptions, key: string): Element;
|
|
10
10
|
/** One or more contexts this rule should render in. */
|
|
11
11
|
contexts: MarkupContexts;
|
|
12
12
|
/** Priority for this rule (higher priority rules override lower priority rules). */
|
|
@@ -14,4 +14,4 @@ export interface MarkupRule {
|
|
|
14
14
|
}
|
|
15
15
|
export type MarkupRules = readonly MarkupRule[];
|
|
16
16
|
/** Helper to make it easier to create typed `MarkupRule` instances using `NamedRegExp` regular expressions. */
|
|
17
|
-
export declare function getMarkupRule<T extends NamedRegExp | RegExp>(regexp: T, render: T extends NamedRegExp<infer X> ? (match: NamedRegExpExecArray<X>, options: MarkupOptions, key: string) =>
|
|
17
|
+
export declare function getMarkupRule<T extends NamedRegExp | RegExp>(regexp: T, render: T extends NamedRegExp<infer X> ? (match: NamedRegExpExecArray<X>, options: MarkupOptions, key: string) => Element : (match: RegExpExecArray, options: MarkupOptions, key: string) => Element, contexts: MarkupContexts, priority?: number): MarkupRule;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shelving",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.202.0",
|
|
4
4
|
"author": "Dave Houlbrooke <dave@shax.com>",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"@typescript/native-preview": "^7.0.0-dev.20260502.1",
|
|
19
19
|
"firebase": "^12.12.1",
|
|
20
20
|
"react": "canary",
|
|
21
|
-
"react-dom": "canary"
|
|
21
|
+
"react-dom": "canary",
|
|
22
|
+
"typescript": "^5"
|
|
22
23
|
},
|
|
23
24
|
"peerDependencies": {
|
|
24
25
|
"@google-cloud/firestore": ">=7.0.0",
|
|
@@ -69,6 +70,7 @@
|
|
|
69
70
|
"test:unit": "bun test --concurrent --only-failures",
|
|
70
71
|
"fix": "bun run --sequential fix:*",
|
|
71
72
|
"fix:0:lint": "biome check --write .",
|
|
73
|
+
"docs": "bun ./scripts/docs.tsx",
|
|
72
74
|
"build": "bun run --sequential build:*",
|
|
73
75
|
"build:0:setup": "rm -rf ./dist && mkdir -p ./dist",
|
|
74
76
|
"build:1:copy": "cp package.json dist/package.json && cp LICENSE.md dist/LICENSE.md && cp README.md dist/README.md && cp .npmignore dist/.npmignore && cp -r modules/ui dist/ui",
|
package/ui/app/App.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ReactElement, type ReactNode } from "react";
|
|
2
|
-
import type {
|
|
3
|
-
export interface AppProps extends
|
|
2
|
+
import type { PossibleMeta } from "../util/meta.js";
|
|
3
|
+
export interface AppProps extends PossibleMeta {
|
|
4
4
|
children: ReactNode;
|
|
5
5
|
}
|
|
6
6
|
/**
|
package/ui/app/App.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useEffect } from "react";
|
|
3
3
|
import { Meta } from "../misc/Meta.js";
|
|
4
4
|
import APP_CSS from "./App.module.css";
|
|
5
|
-
const
|
|
5
|
+
const APP_CLASS = APP_CSS.app;
|
|
6
6
|
/**
|
|
7
7
|
* Root component for an application.
|
|
8
8
|
* - Adds the theme CSS class (which sets CSS token variables on `:root`) to `document.body` on mount and removes it on unmount.
|
|
@@ -10,12 +10,10 @@ const _appClass = APP_CSS.app;
|
|
|
10
10
|
*/
|
|
11
11
|
export function App({ children, ...metadata }) {
|
|
12
12
|
useEffect(() => {
|
|
13
|
-
if (!
|
|
13
|
+
if (!APP_CLASS)
|
|
14
14
|
return;
|
|
15
|
-
document.body.classList.add(
|
|
16
|
-
return () =>
|
|
17
|
-
document.body.classList.remove(_appClass);
|
|
18
|
-
};
|
|
15
|
+
document.body.classList.add(APP_CLASS);
|
|
16
|
+
return () => document.body.classList.remove(APP_CLASS);
|
|
19
17
|
}, []);
|
|
20
18
|
return _jsx(Meta, { ...metadata, children: children });
|
|
21
19
|
}
|
package/ui/app/App.tsx
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { type ReactElement, type ReactNode, useEffect } from "react";
|
|
2
2
|
import { Meta } from "../misc/Meta.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { PossibleMeta } from "../util/meta.js";
|
|
4
4
|
import APP_CSS from "./App.module.css";
|
|
5
5
|
|
|
6
|
-
export interface AppProps extends
|
|
6
|
+
export interface AppProps extends PossibleMeta {
|
|
7
7
|
children: ReactNode;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const APP_CLASS = APP_CSS.app;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Root component for an application.
|
|
@@ -16,11 +16,9 @@ const _appClass = APP_CSS.app;
|
|
|
16
16
|
*/
|
|
17
17
|
export function App({ children, ...metadata }: AppProps): ReactElement {
|
|
18
18
|
useEffect(() => {
|
|
19
|
-
if (!
|
|
20
|
-
document.body.classList.add(
|
|
21
|
-
return () =>
|
|
22
|
-
document.body.classList.remove(_appClass);
|
|
23
|
-
};
|
|
19
|
+
if (!APP_CLASS) return;
|
|
20
|
+
document.body.classList.add(APP_CLASS);
|
|
21
|
+
return () => document.body.classList.remove(APP_CLASS);
|
|
24
22
|
}, []);
|
|
25
23
|
return <Meta {...metadata}>{children}</Meta>;
|
|
26
24
|
}
|
package/ui/block/Video.js
CHANGED
|
@@ -22,7 +22,7 @@ export function VideoButton({ children, title, onClick, disabled, ...variants })
|
|
|
22
22
|
}
|
|
23
23
|
/** Button to make a video element go fullscreen. */
|
|
24
24
|
export function FullscreenVideoButton() {
|
|
25
|
-
const [isFull, setFull] = useState(!!document.fullscreenElement);
|
|
25
|
+
const [isFull, setFull] = useState(() => typeof document !== "undefined" && !!document.fullscreenElement);
|
|
26
26
|
useEffect(() => {
|
|
27
27
|
const onChange = () => setFull(!!document.fullscreenElement);
|
|
28
28
|
document.addEventListener("fullscreenchange", onChange);
|
package/ui/block/Video.tsx
CHANGED
|
@@ -63,7 +63,7 @@ export interface FullscreenVideoButtonProps {
|
|
|
63
63
|
|
|
64
64
|
/** Button to make a video element go fullscreen. */
|
|
65
65
|
export function FullscreenVideoButton(): ReactElement | null {
|
|
66
|
-
const [isFull, setFull] = useState(!!document.fullscreenElement);
|
|
66
|
+
const [isFull, setFull] = useState(() => typeof document !== "undefined" && !!document.fullscreenElement);
|
|
67
67
|
|
|
68
68
|
useEffect(() => {
|
|
69
69
|
const onChange = () => setFull(!!document.fullscreenElement);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
import SIDEBAR_LAYOUT_CSS from "./SidebarLayout.module.css";
|
|
3
|
+
export interface SidebarLayoutProps {
|
|
4
|
+
/** Content rendered in the fixed-width side column. */
|
|
5
|
+
sidebar: ReactNode;
|
|
6
|
+
/** Main content rendered in the scrollable content column. */
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
/** Render the sidebar on the right rather than the left. */
|
|
9
|
+
right?: boolean | undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Layout with a fixed-width side column (typically navigation) next to a scrollable main content column.
|
|
13
|
+
* - The sidebar collapses above the main content on narrow viewports.
|
|
14
|
+
* - Use the `--sidebar-layout-width` and `--sidebar-layout-bg` custom properties to override defaults.
|
|
15
|
+
*/
|
|
16
|
+
export declare function SidebarLayout({ sidebar, children, right }: SidebarLayoutProps): ReactElement;
|
|
17
|
+
export { SIDEBAR_LAYOUT_CSS };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { getClass } from "../util/css.js";
|
|
3
|
+
import { LAYOUT_CSS } from "./Layout.js";
|
|
4
|
+
import SIDEBAR_LAYOUT_CSS from "./SidebarLayout.module.css";
|
|
5
|
+
/**
|
|
6
|
+
* Layout with a fixed-width side column (typically navigation) next to a scrollable main content column.
|
|
7
|
+
* - The sidebar collapses above the main content on narrow viewports.
|
|
8
|
+
* - Use the `--sidebar-layout-width` and `--sidebar-layout-bg` custom properties to override defaults.
|
|
9
|
+
*/
|
|
10
|
+
export function SidebarLayout({ sidebar, children, right = false }) {
|
|
11
|
+
const sidebarEl = (_jsx("aside", { className: SIDEBAR_LAYOUT_CSS.sidebar, children: sidebar }, "sidebar"));
|
|
12
|
+
const contentEl = (_jsx("div", { className: SIDEBAR_LAYOUT_CSS.content, children: _jsx("div", { className: SIDEBAR_LAYOUT_CSS.contentInner, children: children }) }, "content"));
|
|
13
|
+
return (_jsx("main", { className: getClass(SIDEBAR_LAYOUT_CSS.main, LAYOUT_CSS.layout), children: right ? [contentEl, sidebarEl] : [sidebarEl, contentEl] }));
|
|
14
|
+
}
|
|
15
|
+
export { SIDEBAR_LAYOUT_CSS };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Sidebar layout: a fixed-width column on the left, scrollable main content on the right.
|
|
3
|
+
*
|
|
4
|
+
* The outer `.layout` class (from Layout.module.css) owns the overall page scroll,
|
|
5
|
+
* but here we override that: the sidebar and the main column each scroll independently.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.main {
|
|
9
|
+
display: grid;
|
|
10
|
+
grid-template-columns: var(--sidebar-layout-width, 17.5rem) 1fr;
|
|
11
|
+
gap: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.sidebar {
|
|
17
|
+
overflow-y: auto;
|
|
18
|
+
overscroll-behavior: contain;
|
|
19
|
+
padding: var(--spacing-block);
|
|
20
|
+
background: var(--sidebar-layout-bg, var(--color-surface));
|
|
21
|
+
border-right: 1px solid var(--color-border);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.content {
|
|
25
|
+
overflow-y: auto;
|
|
26
|
+
overscroll-behavior: contain;
|
|
27
|
+
padding: var(--spacing-block) var(--spacing-spacious);
|
|
28
|
+
min-width: 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.contentInner {
|
|
32
|
+
width: 100%;
|
|
33
|
+
max-width: var(--width-wide);
|
|
34
|
+
margin: 0 auto;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* On narrow viewports collapse to a single column with the sidebar above. */
|
|
38
|
+
@media (max-width: 48rem) {
|
|
39
|
+
.main {
|
|
40
|
+
grid-template-columns: 1fr;
|
|
41
|
+
}
|
|
42
|
+
.sidebar {
|
|
43
|
+
border-right: none;
|
|
44
|
+
border-bottom: 1px solid var(--color-border);
|
|
45
|
+
max-height: 50vh;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
import { getClass } from "../util/css.js";
|
|
3
|
+
import { LAYOUT_CSS } from "./Layout.js";
|
|
4
|
+
import SIDEBAR_LAYOUT_CSS from "./SidebarLayout.module.css";
|
|
5
|
+
|
|
6
|
+
export interface SidebarLayoutProps {
|
|
7
|
+
/** Content rendered in the fixed-width side column. */
|
|
8
|
+
sidebar: ReactNode;
|
|
9
|
+
/** Main content rendered in the scrollable content column. */
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
/** Render the sidebar on the right rather than the left. */
|
|
12
|
+
right?: boolean | undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Layout with a fixed-width side column (typically navigation) next to a scrollable main content column.
|
|
17
|
+
* - The sidebar collapses above the main content on narrow viewports.
|
|
18
|
+
* - Use the `--sidebar-layout-width` and `--sidebar-layout-bg` custom properties to override defaults.
|
|
19
|
+
*/
|
|
20
|
+
export function SidebarLayout({ sidebar, children, right = false }: SidebarLayoutProps): ReactElement {
|
|
21
|
+
const sidebarEl = (
|
|
22
|
+
<aside key="sidebar" className={SIDEBAR_LAYOUT_CSS.sidebar}>
|
|
23
|
+
{sidebar}
|
|
24
|
+
</aside>
|
|
25
|
+
);
|
|
26
|
+
const contentEl = (
|
|
27
|
+
<div key="content" className={SIDEBAR_LAYOUT_CSS.content}>
|
|
28
|
+
<div className={SIDEBAR_LAYOUT_CSS.contentInner}>{children}</div>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
return (
|
|
32
|
+
<main className={getClass(SIDEBAR_LAYOUT_CSS.main, LAYOUT_CSS.layout)}>{right ? [contentEl, sidebarEl] : [sidebarEl, contentEl]}</main>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { SIDEBAR_LAYOUT_CSS };
|
package/ui/layout/index.d.ts
CHANGED
package/ui/layout/index.js
CHANGED
package/ui/layout/index.tsx
CHANGED
package/ui/misc/Meta.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import { type MetaData, type
|
|
3
|
-
export interface MetaProps extends
|
|
2
|
+
import { type MetaData, type PossibleMeta } from "../util/meta.js";
|
|
3
|
+
export interface MetaProps extends PossibleMeta {
|
|
4
4
|
children: ReactNode;
|
|
5
5
|
}
|
|
6
6
|
/** Create or update the current meta context. */
|
package/ui/misc/Meta.tsx
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createContext, type ReactNode, use } from "react";
|
|
2
|
-
import { type MetaData, mergeMeta, type
|
|
2
|
+
import { type MetaData, mergeMeta, type PossibleMeta } from "../util/meta.js";
|
|
3
3
|
|
|
4
4
|
/** Context to store the `Config` object. */
|
|
5
5
|
const _MetaContext = createContext<MetaData>({});
|
|
6
6
|
_MetaContext.displayName = "MetaContext";
|
|
7
7
|
|
|
8
|
-
export interface MetaProps extends
|
|
8
|
+
export interface MetaProps extends PossibleMeta {
|
|
9
9
|
children: ReactNode;
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
export interface HTMLProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Output a `<html>` element wrapping `<body id="root">`.
|
|
7
|
+
* - No `<head>` element is rendered. Head tags (`<title>`, `<meta>`, `<link>`, `<script>`) are emitted inline by `<Page>` / `<Head>` lower in the tree, and React 19 hoists them automatically — to the document `<head>` on the client, and to a generated `<head>` element during `renderToString` SSR.
|
|
8
|
+
* - This means the same component tree works for both modes without any shell-aware logic.
|
|
9
|
+
*/
|
|
10
|
+
export declare function HTML({ children }: HTMLProps): ReactElement;
|
package/ui/page/HTML.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { requireMeta } from "../misc/Meta.js";
|
|
3
|
+
/**
|
|
4
|
+
* Output a `<html>` element wrapping `<body id="root">`.
|
|
5
|
+
* - No `<head>` element is rendered. Head tags (`<title>`, `<meta>`, `<link>`, `<script>`) are emitted inline by `<Page>` / `<Head>` lower in the tree, and React 19 hoists them automatically — to the document `<head>` on the client, and to a generated `<head>` element during `renderToString` SSR.
|
|
6
|
+
* - This means the same component tree works for both modes without any shell-aware logic.
|
|
7
|
+
*/
|
|
8
|
+
export function HTML({ children }) {
|
|
9
|
+
const { language } = requireMeta();
|
|
10
|
+
return (_jsx("html", { lang: language, children: _jsx("body", { id: "root", children: children }) }));
|
|
11
|
+
}
|
package/ui/page/HTML.tsx
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
import { requireMeta } from "../misc/Meta.js";
|
|
3
|
+
|
|
4
|
+
export interface HTMLProps {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Output a `<html>` element wrapping `<body id="root">`.
|
|
10
|
+
* - No `<head>` element is rendered. Head tags (`<title>`, `<meta>`, `<link>`, `<script>`) are emitted inline by `<Page>` / `<Head>` lower in the tree, and React 19 hoists them automatically — to the document `<head>` on the client, and to a generated `<head>` element during `renderToString` SSR.
|
|
11
|
+
* - This means the same component tree works for both modes without any shell-aware logic.
|
|
12
|
+
*/
|
|
13
|
+
export function HTML({ children }: HTMLProps): ReactElement {
|
|
14
|
+
const { language } = requireMeta();
|
|
15
|
+
return (
|
|
16
|
+
<html lang={language}>
|
|
17
|
+
<body id="root">{children}</body>
|
|
18
|
+
</html>
|
|
19
|
+
);
|
|
20
|
+
}
|
package/ui/page/Head.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
1
|
import { type ReactElement } from "react";
|
|
2
|
-
declare const _componentProps: unique symbol;
|
|
3
|
-
export interface HeadProps {
|
|
4
|
-
readonly [_componentProps]?: never;
|
|
5
|
-
}
|
|
6
2
|
/** Use the details from the current page data context to set the document `<title>`, meta tags, and history state. */
|
|
7
3
|
export declare function Head(): ReactElement;
|
|
8
|
-
export {};
|