shelving 1.212.0 → 1.213.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/extract/Extractor.js +0 -2
- package/index.d.ts +0 -1
- package/index.js +1 -1
- package/markup/render.d.ts +3 -3
- package/markup/render.js +1 -1
- package/markup/rule/blockquote.js +2 -7
- package/markup/rule/code.js +2 -7
- package/markup/rule/fenced.js +2 -17
- package/markup/rule/heading.js +6 -7
- package/markup/rule/inline.js +5 -7
- package/markup/rule/linebreak.js +6 -7
- package/markup/rule/link.js +4 -11
- package/markup/rule/ordered.js +3 -19
- package/markup/rule/paragraph.js +2 -7
- package/markup/rule/separator.js +2 -7
- package/markup/rule/table.js +19 -28
- package/markup/rule/unordered.d.ts +2 -2
- package/markup/rule/unordered.js +3 -18
- package/markup/util/rule.d.ts +3 -3
- package/package.json +1 -1
- package/ui/layout/SidebarLayout.d.ts +2 -2
- package/ui/layout/SidebarLayout.js +16 -8
- package/ui/layout/SidebarLayout.module.css +6 -45
- package/ui/layout/SidebarLayout.tsx +25 -16
- package/ui/page/HTML.d.ts +1 -1
- package/ui/page/HTML.js +2 -2
- package/ui/page/HTML.tsx +2 -2
- package/ui/util/meta.d.ts +10 -3
- package/ui/util/meta.js +7 -0
- package/ui/util/meta.ts +14 -3
- package/util/element.d.ts +8 -7
package/extract/Extractor.js
CHANGED
|
@@ -20,8 +20,6 @@ export function mergeTreeElements(primary, secondary) {
|
|
|
20
20
|
key: primary.key,
|
|
21
21
|
props: {
|
|
22
22
|
...primary.props,
|
|
23
|
-
title: primary.props.title,
|
|
24
|
-
source: primary.props.source,
|
|
25
23
|
description: primary.props.description ?? secondary.props.description,
|
|
26
24
|
content: _mergeContent(primary.props.content, secondary.props.content),
|
|
27
25
|
children: mergeElements(primary.props.children, secondary.props.children),
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -9,7 +9,7 @@ export * from "./error/index.js";
|
|
|
9
9
|
// export * from "./firestore/client/index.js"; // Not exported.
|
|
10
10
|
// export * from "./firestore/lite/index.js"; // Not exported.
|
|
11
11
|
// export * from "./firestore/server/index.js"; // Not exported.
|
|
12
|
-
export * from "./markup/index.js";
|
|
12
|
+
// export * from "./markup/index.js"; // Not exported — `shelving/markup` now compiles JSX and imports React's JSX runtime.
|
|
13
13
|
// export * from "./react/index.js"; // Not exported.
|
|
14
14
|
export * from "./schema/index.js";
|
|
15
15
|
// export * from "./ui/index.js"; // Not exported — shelving/ui requires a bundler (CSS Modules, JSX) and is consumed as source.
|
package/markup/render.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
2
|
import type { MarkupOptions } from "./util/options.js";
|
|
3
3
|
/**
|
|
4
4
|
* Parse a text string as Markdownish syntax and render it as elements.
|
|
@@ -8,6 +8,6 @@ import type { MarkupOptions } from "./util/options.js";
|
|
|
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 A React node — an element, a string, `null`, 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): ReactNode;
|
package/markup/render.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
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 A React node — an element, a string, `null`, 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));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
2
|
import { renderMarkup } from "../render.js";
|
|
2
|
-
import { REACT_ELEMENT_TYPE } from "../util/internal.js";
|
|
3
3
|
import { BLOCK_CONTENT_REGEXP, createBlockRegExp } from "../util/regexp.js";
|
|
4
4
|
import { createMarkupRule } from "../util/rule.js";
|
|
5
5
|
const PREFIX = ">";
|
|
@@ -11,9 +11,4 @@ export const BLOCKQUOTE_REGEXP = createBlockRegExp(`${PREFIX}${BLOCK_CONTENT_REG
|
|
|
11
11
|
* - No spaces can appear before the `>` quote character.
|
|
12
12
|
* - Quote block is only broken by `\n\n` two newline characters.
|
|
13
13
|
*/
|
|
14
|
-
export const BLOCKQUOTE_RULE = createMarkupRule(BLOCKQUOTE_REGEXP, ([quote], options, key) => ({
|
|
15
|
-
key,
|
|
16
|
-
$$typeof: REACT_ELEMENT_TYPE,
|
|
17
|
-
type: "blockquote",
|
|
18
|
-
props: { children: renderMarkup(quote.replace(INDENT, ""), options, "block") },
|
|
19
|
-
}), ["block", "list"]);
|
|
14
|
+
export const BLOCKQUOTE_RULE = createMarkupRule(BLOCKQUOTE_REGEXP, ([quote], options, key) => _jsx("blockquote", { children: renderMarkup(quote.replace(INDENT, ""), options, "block") }, key), ["block", "list"]);
|
package/markup/rule/code.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
2
|
import { getRegExp } from "../../util/regexp.js";
|
|
2
|
-
import { REACT_ELEMENT_TYPE } from "../util/internal.js";
|
|
3
3
|
import { BLOCK_CONTENT_REGEXP } from "../util/regexp.js";
|
|
4
4
|
import { createMarkupRule } from "../util/rule.js";
|
|
5
5
|
const CODE_REGEXP = getRegExp(`(?<fence>\`+)(?<code>${BLOCK_CONTENT_REGEXP})\\k<fence>`);
|
|
@@ -10,9 +10,4 @@ const CODE_REGEXP = getRegExp(`(?<fence>\`+)(?<code>${BLOCK_CONTENT_REGEXP})\\k<
|
|
|
10
10
|
* - Closing characters must exactly match opening characters.
|
|
11
11
|
* - Same as Markdown syntax.
|
|
12
12
|
*/
|
|
13
|
-
export const CODE_RULE = createMarkupRule(CODE_REGEXP, ({ groups: { code } }, _options, key) => ({
|
|
14
|
-
key,
|
|
15
|
-
$$typeof: REACT_ELEMENT_TYPE,
|
|
16
|
-
type: "code",
|
|
17
|
-
props: { children: code },
|
|
18
|
-
}), ["inline", "list"], 10);
|
|
13
|
+
export const CODE_RULE = createMarkupRule(CODE_REGEXP, ({ groups: { code } }, _options, key) => _jsx("code", { children: code }, key), ["inline", "list"], 10);
|
package/markup/rule/fenced.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { BLOCK_CONTENT_REGEXP, BLOCK_START_REGEXP, createBlockRegExp, LINE_CONTENT_REGEXP, LINE_SPACE_REGEXP } from "../util/regexp.js";
|
|
3
3
|
import { createMarkupRule } from "../util/rule.js";
|
|
4
4
|
const FENCE = "`{3,}|~{3,}";
|
|
@@ -16,19 +16,4 @@ const FENCED_REGEXP = createBlockRegExp(`(?<code>${BLOCK_CONTENT_REGEXP})`,
|
|
|
16
16
|
* - If there's no closing fence the code block will run to the end of the current string.
|
|
17
17
|
* - Markdown-style four-space indent syntax is not supported (only fenced code since it's less confusing and more common).
|
|
18
18
|
*/
|
|
19
|
-
export const FENCED_RULE = createMarkupRule(FENCED_REGEXP, ({ groups: { title, code } }, _options, key) => ({
|
|
20
|
-
key,
|
|
21
|
-
$$typeof: REACT_ELEMENT_TYPE,
|
|
22
|
-
type: "pre",
|
|
23
|
-
props: {
|
|
24
|
-
children: {
|
|
25
|
-
$$typeof: REACT_ELEMENT_TYPE,
|
|
26
|
-
type: "code",
|
|
27
|
-
key: null,
|
|
28
|
-
props: {
|
|
29
|
-
title: title?.trim() || undefined,
|
|
30
|
-
children: code.trim(),
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
}), ["block", "list"], 10);
|
|
19
|
+
export const FENCED_RULE = createMarkupRule(FENCED_REGEXP, ({ groups: { title, code } }, _options, key) => (_jsx("pre", { children: _jsx("code", { title: title?.trim() || undefined, children: code.trim() }) }, key)), ["block", "list"], 10);
|
package/markup/rule/heading.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
2
|
import { renderMarkup } from "../render.js";
|
|
2
|
-
import { REACT_ELEMENT_TYPE } from "../util/internal.js";
|
|
3
3
|
import { createLineRegExp, LINE_CONTENT_REGEXP, LINE_SPACE_REGEXP } from "../util/regexp.js";
|
|
4
4
|
import { createMarkupRule } from "../util/rule.js";
|
|
5
5
|
const HEADING_REGEXP = createLineRegExp(`(?<prefix>#{1,6})(?:${LINE_SPACE_REGEXP}+(?<heading>${LINE_CONTENT_REGEXP}))?`);
|
|
@@ -9,9 +9,8 @@ const HEADING_REGEXP = createLineRegExp(`(?<prefix>#{1,6})(?:${LINE_SPACE_REGEXP
|
|
|
9
9
|
* - `#` must be the first character on the line.
|
|
10
10
|
* - Markdown's underline syntax is not supported (for simplification).
|
|
11
11
|
*/
|
|
12
|
-
export const HEADING_RULE = createMarkupRule(HEADING_REGEXP, ({ groups: { prefix, heading = "" } }, options, key) =>
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}), ["block"]);
|
|
12
|
+
export const HEADING_RULE = createMarkupRule(HEADING_REGEXP, ({ groups: { prefix, heading = "" } }, options, key) => {
|
|
13
|
+
// The hash count picks the heading level; cast the dynamic tag to the known `h1`–`h6` set.
|
|
14
|
+
const Heading = `h${prefix.length}`;
|
|
15
|
+
return _jsx(Heading, { children: renderMarkup(heading.trim(), options, "inline") }, key);
|
|
16
|
+
}, ["block"]);
|
package/markup/rule/inline.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
2
|
import { renderMarkup } from "../render.js";
|
|
2
|
-
import { REACT_ELEMENT_TYPE } from "../util/internal.js";
|
|
3
3
|
import { createWordRegExp } from "../util/regexp.js";
|
|
4
4
|
import { createMarkupRule } from "../util/rule.js";
|
|
5
5
|
/** Map characters, e.g. `*`, to their coresponding HTML tag, e.g. `strong` */
|
|
@@ -18,9 +18,7 @@ const INLINE_REGEXP = createWordRegExp(`(?<wrap>(?<char>[${Object.keys(INLINE_CH
|
|
|
18
18
|
* - Closing characters must exactly match opening characters.
|
|
19
19
|
* - Different to Markdown: strong is always surrounded by `*asterisks*` and emphasis is always surrounded by `_underscores_` (strong isn't 'double emphasis').
|
|
20
20
|
*/
|
|
21
|
-
export const INLINE_RULE = createMarkupRule(INLINE_REGEXP, ({ groups: { char, text } }, options, key) =>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
props: { children: renderMarkup(text, options, "inline") },
|
|
26
|
-
}), ["inline", "list", "link"]);
|
|
21
|
+
export const INLINE_RULE = createMarkupRule(INLINE_REGEXP, ({ groups: { char, text } }, options, key) => {
|
|
22
|
+
const Inline = INLINE_CHARS[char];
|
|
23
|
+
return _jsx(Inline, { children: renderMarkup(text, options, "inline") }, key);
|
|
24
|
+
}, ["inline", "list", "link"]);
|
package/markup/rule/linebreak.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createMarkupRule } from "../util/rule.js";
|
|
3
3
|
/**
|
|
4
4
|
* Hard linebreak (`<br />` tag).
|
|
@@ -10,9 +10,8 @@ import { createMarkupRule } from "../util/rule.js";
|
|
|
10
10
|
* - This is more intuitive (a linebreak becomes a linebreak is isn't silently ignored).
|
|
11
11
|
* - This works better with textareas that wrap text (since manually breaking up long lines is no longer necessary).
|
|
12
12
|
*/
|
|
13
|
-
export const LINEBREAK_RULE = createMarkupRule(/[^\n\S]*\n[^\n\S]*/, (_match, _options, key) => ({
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}), ["inline", "list", "link"]);
|
|
13
|
+
export const LINEBREAK_RULE = createMarkupRule(/[^\n\S]*\n[^\n\S]*/, (_match, _options, key) => _jsx("br", {}, key), [
|
|
14
|
+
"inline",
|
|
15
|
+
"list",
|
|
16
|
+
"link",
|
|
17
|
+
]);
|
package/markup/rule/link.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
2
|
import { formatURI } from "../../util/format.js";
|
|
2
3
|
import { getLink } from "../../util/link.js";
|
|
3
4
|
import { getRegExp } from "../../util/regexp.js";
|
|
4
5
|
import { HTTP_SCHEMES } from "../../util/uri.js";
|
|
5
6
|
import { renderMarkup } from "../render.js";
|
|
6
|
-
import { REACT_ELEMENT_TYPE } from "../util/internal.js";
|
|
7
7
|
import { createMarkupRule } from "../util/rule.js";
|
|
8
8
|
/** Render `<a href="">` if the link is a valid one, or `<a>` (with no `href`) if it isn't. */
|
|
9
9
|
function renderLinkMarkupRule({ groups: { title, href: unsafeHref } }, options, key) {
|
|
@@ -11,12 +11,7 @@ function renderLinkMarkupRule({ groups: { title, href: unsafeHref } }, options,
|
|
|
11
11
|
const link = getLink(unsafeHref, url, root);
|
|
12
12
|
const href = link && schemes.includes(link.protocol) ? link?.href : undefined;
|
|
13
13
|
const children = title ? renderMarkup(title, options, "link") : link ? formatURI(link) : "";
|
|
14
|
-
return {
|
|
15
|
-
key,
|
|
16
|
-
$$typeof: REACT_ELEMENT_TYPE,
|
|
17
|
-
type: "a",
|
|
18
|
-
props: { href, rel, children },
|
|
19
|
-
};
|
|
14
|
+
return (_jsx("a", { href: href, rel: rel, children: children }, key));
|
|
20
15
|
}
|
|
21
16
|
export const LINK_REGEXP = getRegExp(/\[(?<title>[^\]\n]*?)\]\((?<href>[^)\n]*?)\)/);
|
|
22
17
|
/**
|
|
@@ -27,8 +22,7 @@ export const LINK_REGEXP = getRegExp(/\[(?<title>[^\]\n]*?)\]\((?<href>[^)\n]*?)
|
|
|
27
22
|
* - If link is not valid (using `new URL(url)` then unparsed text will be returned.
|
|
28
23
|
* - For security only `http://` or `https://` links will work (if invalid the unparsed text will be returned).
|
|
29
24
|
*/
|
|
30
|
-
export const LINK_RULE = createMarkupRule(LINK_REGEXP,
|
|
31
|
-
renderLinkMarkupRule, ["inline", "list"]);
|
|
25
|
+
export const LINK_RULE = createMarkupRule(LINK_REGEXP, renderLinkMarkupRule, ["inline", "list"]);
|
|
32
26
|
export const AUTOLINK_REGEXP = getRegExp(/(?<href>[a-z]{2,}:\S+)(?: +(?:\((?<title>[^)\n]*?)\)))?/);
|
|
33
27
|
/**
|
|
34
28
|
* Autolinked URL starts with `scheme://` (any scheme in `options.schemes`) and matches an unlimited number of non-space characters.
|
|
@@ -37,5 +31,4 @@ export const AUTOLINK_REGEXP = getRegExp(/(?<href>[a-z]{2,}:\S+)(?: +(?:\((?<tit
|
|
|
37
31
|
* - If link is not valid (using `new URL(url)` then unparsed text will be returned.
|
|
38
32
|
* - For security only schemes that appear in `options.schemes` will match (defaults to `http:` and `https:`).
|
|
39
33
|
*/
|
|
40
|
-
export const AUTOLINK_RULE = createMarkupRule(AUTOLINK_REGEXP,
|
|
41
|
-
renderLinkMarkupRule, ["inline", "list"]);
|
|
34
|
+
export const AUTOLINK_RULE = createMarkupRule(AUTOLINK_REGEXP, renderLinkMarkupRule, ["inline", "list"]);
|
package/markup/rule/ordered.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
2
|
import { renderMarkup } from "../render.js";
|
|
2
|
-
import { REACT_ELEMENT_TYPE } from "../util/internal.js";
|
|
3
3
|
import { BLOCK_CONTENT_REGEXP, BLOCK_SPACE_REGEXP, createBlockRegExp, LINE_SPACE_REGEXP } from "../util/regexp.js";
|
|
4
4
|
import { createMarkupRule } from "../util/rule.js";
|
|
5
5
|
const INDENT = /^\t/gm; // Nesting is recognised with tabs only.
|
|
@@ -13,27 +13,11 @@ export const ORDERED_REGEXP = createBlockRegExp(`(?<list>${NUMBER}(?:${LINE_SPAC
|
|
|
13
13
|
* - Second-level list can be created by indenting with `\t` one tab.
|
|
14
14
|
* - Sparse lists are not supported.
|
|
15
15
|
*/
|
|
16
|
-
export const ORDERED_RULE = createMarkupRule(ORDERED_REGEXP, ({ groups: { list } }, options, key) => ({
|
|
17
|
-
key,
|
|
18
|
-
$$typeof: REACT_ELEMENT_TYPE,
|
|
19
|
-
type: "ol",
|
|
20
|
-
props: {
|
|
21
|
-
children: Array.from(_getOrderedItems(list, options)),
|
|
22
|
-
},
|
|
23
|
-
}), ["block", "list"]);
|
|
16
|
+
export const ORDERED_RULE = createMarkupRule(ORDERED_REGEXP, ({ groups: { list } }, options, key) => _jsx("ol", { children: Array.from(_getOrderedItems(list, options)) }, key), ["block", "list"]);
|
|
24
17
|
/** Parse a markdown list into a set of items elements. */
|
|
25
18
|
function* _getOrderedItems(list, options) {
|
|
26
19
|
let key = 0;
|
|
27
20
|
for (const [_unused, number = "", item = ""] of list.matchAll(ITEM)) {
|
|
28
|
-
yield {
|
|
29
|
-
$$typeof: REACT_ELEMENT_TYPE,
|
|
30
|
-
type: "li",
|
|
31
|
-
props: {
|
|
32
|
-
value: Number.parseInt(number, 10),
|
|
33
|
-
children: renderMarkup(item.replace(INDENT, ""), options, "list"),
|
|
34
|
-
},
|
|
35
|
-
key: key.toString(),
|
|
36
|
-
};
|
|
37
|
-
key++;
|
|
21
|
+
yield (_jsx("li", { value: Number.parseInt(number, 10), children: renderMarkup(item.replace(INDENT, ""), options, "list") }, key++));
|
|
38
22
|
}
|
|
39
23
|
}
|
package/markup/rule/paragraph.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
2
|
import { renderMarkup } from "../render.js";
|
|
2
|
-
import { REACT_ELEMENT_TYPE } from "../util/internal.js";
|
|
3
3
|
import { BLOCK_SPACE_REGEXP, BLOCK_START_REGEXP, createBlockRegExp } from "../util/regexp.js";
|
|
4
4
|
import { createMarkupRule } from "../util/rule.js";
|
|
5
5
|
export const PARAGRAPH_REGEXP = createBlockRegExp("(?<paragraph>(?:(?=\\S)[\\s\\S]*?\\S))",
|
|
@@ -11,9 +11,4 @@ export const PARAGRAPH_REGEXP = createBlockRegExp("(?<paragraph>(?:(?=\\S)[\\s\\
|
|
|
11
11
|
* - Any run of non-whitespace.
|
|
12
12
|
* - Leading and trailing whitespace is trimmed.
|
|
13
13
|
*/
|
|
14
|
-
export const PARAGRAPH_RULE = createMarkupRule(PARAGRAPH_REGEXP, ({ groups: { paragraph } }, options, key) => ({
|
|
15
|
-
key,
|
|
16
|
-
$$typeof: REACT_ELEMENT_TYPE,
|
|
17
|
-
type: "p",
|
|
18
|
-
props: { children: renderMarkup(paragraph, options, "inline") },
|
|
19
|
-
}), ["block"], -10);
|
|
14
|
+
export const PARAGRAPH_RULE = createMarkupRule(PARAGRAPH_REGEXP, ({ groups: { paragraph } }, options, key) => _jsx("p", { children: renderMarkup(paragraph, options, "inline") }, key), ["block"], -10);
|
package/markup/rule/separator.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createLineRegExp } from "../util/regexp.js";
|
|
3
3
|
import { createMarkupRule } from "../util/rule.js";
|
|
4
4
|
const SEPARATOR_REGEXP = createLineRegExp("([-*•+_=])(?: *\\1){2,}");
|
|
@@ -9,9 +9,4 @@ const SEPARATOR_REGEXP = createLineRegExp("([-*•+_=])(?: *\\1){2,}");
|
|
|
9
9
|
* - Character must be the same every time (can't mix)
|
|
10
10
|
* - Might have infinite number of spaces between the characters.
|
|
11
11
|
*/
|
|
12
|
-
export const SEPARATOR_RULE = createMarkupRule(SEPARATOR_REGEXP, (_match, _options, key) => ({
|
|
13
|
-
key,
|
|
14
|
-
$$typeof: REACT_ELEMENT_TYPE,
|
|
15
|
-
type: "hr",
|
|
16
|
-
props: {},
|
|
17
|
-
}), ["block"]);
|
|
12
|
+
export const SEPARATOR_RULE = createMarkupRule(SEPARATOR_REGEXP, (_match, _options, key) => _jsx("hr", {}, key), ["block"]);
|
package/markup/rule/table.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
2
|
import { renderMarkup } from "../render.js";
|
|
2
|
-
import { REACT_ELEMENT_TYPE } from "../util/internal.js";
|
|
3
3
|
import { createBlockRegExp, LINE_SPACE_REGEXP } from "../util/regexp.js";
|
|
4
4
|
import { createMarkupRule } from "../util/rule.js";
|
|
5
5
|
// Constants.
|
|
@@ -41,35 +41,26 @@ function _renderTable(table, options, key) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
sections.push(section);
|
|
44
|
-
//
|
|
44
|
+
// Build the table with explicit loops — markup elements are static and positional, so the loop index is the natural key.
|
|
45
45
|
const last = sections.length - 1;
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const cells = aligns.map((align, c) => {
|
|
63
|
-
const children = renderMarkup(values[c] ?? "", options, "inline");
|
|
64
|
-
return {
|
|
65
|
-
$$typeof: REACT_ELEMENT_TYPE,
|
|
66
|
-
type: cell,
|
|
67
|
-
key: c.toString(),
|
|
68
|
-
props: align ? { align, children } : { children },
|
|
69
|
-
};
|
|
70
|
-
});
|
|
71
|
-
yield { $$typeof: REACT_ELEMENT_TYPE, type: "tr", key: (r++).toString(), props: { children: cells } };
|
|
46
|
+
const body = [];
|
|
47
|
+
for (let s = 0; s < sections.length; s++) {
|
|
48
|
+
// First section is `<thead>`; the last is `<tfoot>` with 3+ sections; sections in between are each a `<tbody>`.
|
|
49
|
+
const Section = s === 0 ? "thead" : s === last && last >= 2 ? "tfoot" : "tbody";
|
|
50
|
+
const Cell = s === 0 ? "th" : "td";
|
|
51
|
+
const rowLines = sections[s] ?? [];
|
|
52
|
+
const rows = [];
|
|
53
|
+
for (let r = 0; r < rowLines.length; r++) {
|
|
54
|
+
const values = _splitRow(rowLines[r] ?? "");
|
|
55
|
+
const cells = [];
|
|
56
|
+
for (let c = 0; c < aligns.length; c++) {
|
|
57
|
+
cells.push(_jsx(Cell, { align: aligns[c], children: renderMarkup(values[c] ?? "", options, "inline") }, c));
|
|
58
|
+
}
|
|
59
|
+
rows.push(_jsx("tr", { children: cells }, r));
|
|
60
|
+
}
|
|
61
|
+
body.push(_jsx(Section, { children: rows }, s));
|
|
72
62
|
}
|
|
63
|
+
return _jsx("table", { children: body }, key);
|
|
73
64
|
}
|
|
74
65
|
/** Split a table row into trimmed cell strings, honouring `\|` escaped pipes. */
|
|
75
66
|
function _splitRow(row) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ReactElement } from "react";
|
|
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<ReactElement>;
|
package/markup/rule/unordered.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
2
|
import { renderMarkup } from "../render.js";
|
|
2
|
-
import { REACT_ELEMENT_TYPE } from "../util/internal.js";
|
|
3
3
|
import { BLOCK_CONTENT_REGEXP, BLOCK_SPACE_REGEXP, createBlockRegExp, LINE_SPACE_REGEXP } from "../util/regexp.js";
|
|
4
4
|
import { createMarkupRule } from "../util/rule.js";
|
|
5
5
|
const INDENT = /^\t/gm; // Nesting is recognised with tabs only.
|
|
@@ -14,26 +14,11 @@ export const UNORDERED_REGEXP = createBlockRegExp(`(?<list>${BULLET}(?:${LINE_SP
|
|
|
14
14
|
* - List block is ended by `\n\n` two newline characters.
|
|
15
15
|
* - Sparse lists are not supported.
|
|
16
16
|
*/
|
|
17
|
-
export const UNORDERED_RULE = createMarkupRule(UNORDERED_REGEXP, ({ groups: { list = "" } }, options, key) => ({
|
|
18
|
-
key,
|
|
19
|
-
$$typeof: REACT_ELEMENT_TYPE,
|
|
20
|
-
type: "ul",
|
|
21
|
-
props: {
|
|
22
|
-
children: Array.from(_getItems(list, options)),
|
|
23
|
-
},
|
|
24
|
-
}), ["block", "list"]);
|
|
17
|
+
export const UNORDERED_RULE = createMarkupRule(UNORDERED_REGEXP, ({ groups: { list = "" } }, options, key) => _jsx("ul", { children: Array.from(_getItems(list, options)) }, key), ["block", "list"]);
|
|
25
18
|
/** Parse a markdown list into a set of items elements. */
|
|
26
19
|
export function* _getItems(list, options) {
|
|
27
20
|
let key = 0;
|
|
28
21
|
for (const [_unused, item = ""] of list.matchAll(ITEM)) {
|
|
29
|
-
yield {
|
|
30
|
-
$$typeof: REACT_ELEMENT_TYPE,
|
|
31
|
-
type: "li",
|
|
32
|
-
props: {
|
|
33
|
-
children: renderMarkup(item.replace(INDENT, ""), options, "list"),
|
|
34
|
-
},
|
|
35
|
-
key: key.toString(),
|
|
36
|
-
};
|
|
37
|
-
key++;
|
|
22
|
+
yield _jsx("li", { children: renderMarkup(item.replace(INDENT, ""), options, "list") }, key++);
|
|
38
23
|
}
|
|
39
24
|
}
|
package/markup/util/rule.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ReactElement } from "react";
|
|
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[]];
|
|
@@ -6,7 +6,7 @@ export interface MarkupRule {
|
|
|
6
6
|
/** Regular expression used for matching the rule. */
|
|
7
7
|
regexp: RegExp;
|
|
8
8
|
/** Use the matched data to render an element. */
|
|
9
|
-
render(match: RegExpExecArray, options: MarkupOptions, key: string):
|
|
9
|
+
render(match: RegExpExecArray, options: MarkupOptions, key: string): ReactElement;
|
|
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 createMarkupRule<T extends NamedRegExp | RegExp>(regexp: T, render: T extends NamedRegExp<infer X> ? (match: NamedRegExpExecArray<X>, options: MarkupOptions, key: string) =>
|
|
17
|
+
export declare function createMarkupRule<T extends NamedRegExp | RegExp>(regexp: T, render: T extends NamedRegExp<infer X> ? (match: NamedRegExpExecArray<X>, options: MarkupOptions, key: string) => ReactElement : (match: RegExpExecArray, options: MarkupOptions, key: string) => ReactElement, contexts: MarkupContexts, priority?: number): MarkupRule;
|
package/package.json
CHANGED
|
@@ -11,8 +11,8 @@ export interface SidebarLayoutProps {
|
|
|
11
11
|
/**
|
|
12
12
|
* Layout with a fixed-width side column (typically navigation) next to a scrollable main content column.
|
|
13
13
|
* - The sidebar is rendered as `<nav>` — it almost always contains the page's primary navigation.
|
|
14
|
-
* - On narrow viewports the sidebar
|
|
15
|
-
* -
|
|
14
|
+
* - On narrow viewports the sidebar becomes an off-canvas drawer toggled by the "show menu" / "close" buttons.
|
|
15
|
+
* - Inside a `<Navigation>` the drawer closes itself whenever the route changes (e.g. tapping a sidebar link).
|
|
16
16
|
* - Use the `--sidebar-layout-width` and `--sidebar-layout-bg` custom properties to override defaults.
|
|
17
17
|
*/
|
|
18
18
|
export declare function SidebarLayout({ sidebar, children, right }: SidebarLayoutProps): ReactElement;
|
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/solid";
|
|
3
|
-
import {
|
|
3
|
+
import { use, useEffect, useState } from "react";
|
|
4
|
+
import { useStore } from "../../react/useStore.js";
|
|
5
|
+
import { Button } from "../form/Button.js";
|
|
6
|
+
import { NavigationContext } from "../router/NavigationContext.js";
|
|
4
7
|
import { getClass } from "../util/css.js";
|
|
5
8
|
import { LAYOUT_CSS } from "./Layout.js";
|
|
6
9
|
import SIDEBAR_LAYOUT_CSS from "./SidebarLayout.module.css";
|
|
7
10
|
/**
|
|
8
11
|
* Layout with a fixed-width side column (typically navigation) next to a scrollable main content column.
|
|
9
12
|
* - The sidebar is rendered as `<nav>` — it almost always contains the page's primary navigation.
|
|
10
|
-
* - On narrow viewports the sidebar
|
|
11
|
-
* -
|
|
13
|
+
* - On narrow viewports the sidebar becomes an off-canvas drawer toggled by the "show menu" / "close" buttons.
|
|
14
|
+
* - Inside a `<Navigation>` the drawer closes itself whenever the route changes (e.g. tapping a sidebar link).
|
|
12
15
|
* - Use the `--sidebar-layout-width` and `--sidebar-layout-bg` custom properties to override defaults.
|
|
13
16
|
*/
|
|
14
17
|
export function SidebarLayout({ sidebar, children, right = false }) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const [open, setOpen] = useState(false);
|
|
19
|
+
// Close the drawer whenever navigation changes the URL — covers tapping a link inside the sidebar.
|
|
20
|
+
const href = useStore(use(NavigationContext))?.href;
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (href)
|
|
23
|
+
setOpen(false);
|
|
24
|
+
}, [href]);
|
|
25
|
+
const sidebarEl = (_jsxs("nav", { className: getClass(SIDEBAR_LAYOUT_CSS.sidebar, open && SIDEBAR_LAYOUT_CSS.open), children: [_jsx("div", { className: SIDEBAR_LAYOUT_CSS.close, children: _jsx(Button, { plain: true, fit: true, title: "Close menu", onClick: () => setOpen(false), children: _jsx(XMarkIcon, {}) }) }), sidebar] }, "sidebar"));
|
|
26
|
+
const contentEl = (_jsxs("div", { className: getClass(LAYOUT_CSS.layout, SIDEBAR_LAYOUT_CSS.content), children: [_jsx("div", { className: SIDEBAR_LAYOUT_CSS.show, children: _jsx(Button, { plain: true, fit: true, title: "Show menu", onClick: () => setOpen(true), children: _jsx(Bars3Icon, {}) }) }), _jsx("div", { className: SIDEBAR_LAYOUT_CSS.contentInner, children: children })] }, "content"));
|
|
27
|
+
return (_jsx("main", { className: getClass(SIDEBAR_LAYOUT_CSS.main, LAYOUT_CSS.layout), children: right ? [contentEl, sidebarEl] : [sidebarEl, contentEl] }));
|
|
20
28
|
}
|
|
21
29
|
export { SIDEBAR_LAYOUT_CSS };
|
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
* The inner `.content` element reuses the `.layout` class too so it picks up the standard
|
|
7
7
|
* layout padding, safe-area insets, and `overflow: hidden auto` scroll behaviour.
|
|
8
8
|
*
|
|
9
|
-
* On narrow viewports the sidebar becomes an off-canvas drawer
|
|
10
|
-
* screen
|
|
11
|
-
* Driving the drawer with a checkbox + CSS means it works even when the page ships no client-side JS.
|
|
9
|
+
* On narrow viewports the sidebar becomes an off-canvas drawer that slides off the left edge of the
|
|
10
|
+
* screen; the `.show` / `.close` buttons toggle it (both are hidden entirely on wide viewports).
|
|
12
11
|
*/
|
|
13
12
|
|
|
14
13
|
.main {
|
|
@@ -38,38 +37,16 @@
|
|
|
38
37
|
margin: 0 auto;
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
/*
|
|
42
|
-
.toggle {
|
|
43
|
-
display: none;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/* Menu toggle buttons — hidden by default, only shown on narrow viewports (see media query below). */
|
|
40
|
+
/* Wrappers for the menu toggle buttons — hidden on wide viewports, shown on narrow ones (see media query). */
|
|
47
41
|
.show,
|
|
48
42
|
.close {
|
|
49
43
|
display: none;
|
|
50
|
-
|
|
51
|
-
margin: 0;
|
|
52
|
-
align-items: center;
|
|
53
|
-
justify-content: center;
|
|
54
|
-
padding: var(--space-small);
|
|
55
|
-
border-radius: var(--radius-xsmall);
|
|
56
|
-
color: var(--color-text);
|
|
57
|
-
cursor: pointer;
|
|
58
|
-
|
|
59
|
-
& svg {
|
|
60
|
-
width: var(--size-icon);
|
|
61
|
-
height: var(--size-icon);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
&:hover {
|
|
65
|
-
background: var(--color-surface);
|
|
66
|
-
}
|
|
44
|
+
margin-bottom: var(--space-xsmall);
|
|
67
45
|
}
|
|
68
46
|
|
|
69
47
|
/* The close button sits at the top of the sidebar, aligned to its trailing edge. */
|
|
70
48
|
.close {
|
|
71
|
-
|
|
72
|
-
margin-bottom: var(--space-xsmall);
|
|
49
|
+
justify-content: flex-end;
|
|
73
50
|
}
|
|
74
51
|
|
|
75
52
|
/* On narrow viewports the sidebar becomes an off-canvas drawer that slides in from the left. */
|
|
@@ -87,28 +64,12 @@
|
|
|
87
64
|
transform: translateX(-100%);
|
|
88
65
|
transition: transform var(--duration-normal) ease-in-out;
|
|
89
66
|
}
|
|
90
|
-
|
|
91
|
-
.toggle:checked ~ .sidebar {
|
|
67
|
+
.sidebar.open {
|
|
92
68
|
transform: translateX(0);
|
|
93
69
|
box-shadow: 0 0 1.5rem var(--color-overlay);
|
|
94
70
|
}
|
|
95
|
-
/* Keep the checkbox visually hidden but still focusable for keyboard users. */
|
|
96
|
-
.toggle {
|
|
97
|
-
display: block;
|
|
98
|
-
position: absolute;
|
|
99
|
-
width: 1px;
|
|
100
|
-
height: 1px;
|
|
101
|
-
overflow: hidden;
|
|
102
|
-
clip-path: inset(50%);
|
|
103
|
-
}
|
|
104
71
|
.show,
|
|
105
72
|
.close {
|
|
106
73
|
display: flex;
|
|
107
74
|
}
|
|
108
|
-
/* Forward the checkbox's keyboard focus ring to whichever toggle button is on screen. */
|
|
109
|
-
.toggle:focus-visible ~ .content .show,
|
|
110
|
-
.toggle:focus-visible ~ .sidebar .close {
|
|
111
|
-
outline: var(--stroke-focus) solid var(--color-focus);
|
|
112
|
-
outline-offset: 2px;
|
|
113
|
-
}
|
|
114
75
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/solid";
|
|
2
|
-
import { type ReactElement, type ReactNode,
|
|
2
|
+
import { type ReactElement, type ReactNode, use, useEffect, useState } from "react";
|
|
3
|
+
import { useStore } from "../../react/useStore.js";
|
|
4
|
+
import { Button } from "../form/Button.js";
|
|
5
|
+
import { NavigationContext } from "../router/NavigationContext.js";
|
|
3
6
|
import { getClass } from "../util/css.js";
|
|
4
7
|
import { LAYOUT_CSS } from "./Layout.js";
|
|
5
8
|
import SIDEBAR_LAYOUT_CSS from "./SidebarLayout.module.css";
|
|
@@ -16,35 +19,41 @@ export interface SidebarLayoutProps {
|
|
|
16
19
|
/**
|
|
17
20
|
* Layout with a fixed-width side column (typically navigation) next to a scrollable main content column.
|
|
18
21
|
* - The sidebar is rendered as `<nav>` — it almost always contains the page's primary navigation.
|
|
19
|
-
* - On narrow viewports the sidebar
|
|
20
|
-
* -
|
|
22
|
+
* - On narrow viewports the sidebar becomes an off-canvas drawer toggled by the "show menu" / "close" buttons.
|
|
23
|
+
* - Inside a `<Navigation>` the drawer closes itself whenever the route changes (e.g. tapping a sidebar link).
|
|
21
24
|
* - Use the `--sidebar-layout-width` and `--sidebar-layout-bg` custom properties to override defaults.
|
|
22
25
|
*/
|
|
23
26
|
export function SidebarLayout({ sidebar, children, right = false }: SidebarLayoutProps): ReactElement {
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
const [open, setOpen] = useState(false);
|
|
28
|
+
|
|
29
|
+
// Close the drawer whenever navigation changes the URL — covers tapping a link inside the sidebar.
|
|
30
|
+
const href = useStore(use(NavigationContext))?.href;
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (href) setOpen(false);
|
|
33
|
+
}, [href]);
|
|
26
34
|
|
|
27
35
|
const sidebarEl = (
|
|
28
|
-
<nav key="sidebar" className={SIDEBAR_LAYOUT_CSS.sidebar}>
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
|
|
36
|
+
<nav key="sidebar" className={getClass(SIDEBAR_LAYOUT_CSS.sidebar, open && SIDEBAR_LAYOUT_CSS.open)}>
|
|
37
|
+
<div className={SIDEBAR_LAYOUT_CSS.close}>
|
|
38
|
+
<Button plain fit title="Close menu" onClick={() => setOpen(false)}>
|
|
39
|
+
<XMarkIcon />
|
|
40
|
+
</Button>
|
|
41
|
+
</div>
|
|
32
42
|
{sidebar}
|
|
33
43
|
</nav>
|
|
34
44
|
);
|
|
35
45
|
const contentEl = (
|
|
36
46
|
<div key="content" className={getClass(LAYOUT_CSS.layout, SIDEBAR_LAYOUT_CSS.content)}>
|
|
37
|
-
<
|
|
38
|
-
<
|
|
39
|
-
|
|
47
|
+
<div className={SIDEBAR_LAYOUT_CSS.show}>
|
|
48
|
+
<Button plain fit title="Show menu" onClick={() => setOpen(true)}>
|
|
49
|
+
<Bars3Icon />
|
|
50
|
+
</Button>
|
|
51
|
+
</div>
|
|
40
52
|
<div className={SIDEBAR_LAYOUT_CSS.contentInner}>{children}</div>
|
|
41
53
|
</div>
|
|
42
54
|
);
|
|
43
55
|
return (
|
|
44
|
-
<main className={getClass(SIDEBAR_LAYOUT_CSS.main, LAYOUT_CSS.layout)}>
|
|
45
|
-
<input type="checkbox" id={id} className={SIDEBAR_LAYOUT_CSS.toggle} aria-label="Show or hide menu" />
|
|
46
|
-
{right ? [contentEl, sidebarEl] : [sidebarEl, contentEl]}
|
|
47
|
-
</main>
|
|
56
|
+
<main className={getClass(SIDEBAR_LAYOUT_CSS.main, LAYOUT_CSS.layout)}>{right ? [contentEl, sidebarEl] : [sidebarEl, contentEl]}</main>
|
|
48
57
|
);
|
|
49
58
|
}
|
|
50
59
|
|
package/ui/page/HTML.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface HTMLProps extends PossibleMeta {
|
|
|
4
4
|
children: ReactNode;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
* Output a `<html>` element wrapping `<head>` (via `<Head>`) and `<body
|
|
7
|
+
* Output a `<html>` element wrapping `<head>` (via `<Head>`) and `<body>`.
|
|
8
8
|
* - `<Head>` renders the literal `<head>` with `<base>` and other shell-level metadata; per-page hoistable elements (title, meta, links, stylesheets, scripts) come from `<PageHead>` inside `<Page>` and are hoisted into this `<head>` by React 19.
|
|
9
9
|
*/
|
|
10
10
|
export declare function HTML({ children, ...meta }: HTMLProps): ReactElement;
|
package/ui/page/HTML.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { MetaContext, requireMeta } from "../misc/MetaContext.js";
|
|
3
3
|
/**
|
|
4
|
-
* Output a `<html>` element wrapping `<head>` (via `<Head>`) and `<body
|
|
4
|
+
* Output a `<html>` element wrapping `<head>` (via `<Head>`) and `<body>`.
|
|
5
5
|
* - `<Head>` renders the literal `<head>` with `<base>` and other shell-level metadata; per-page hoistable elements (title, meta, links, stylesheets, scripts) come from `<PageHead>` inside `<Page>` and are hoisted into this `<head>` by React 19.
|
|
6
6
|
*/
|
|
7
7
|
export function HTML({ children, ...meta }) {
|
|
8
8
|
const merged = requireMeta(meta);
|
|
9
9
|
const { language, root: base, app } = merged;
|
|
10
|
-
return (_jsxs("html", { lang: language, children: [_jsxs("head", { children: [_jsx("meta", { charSet: "utf-8" }), base && _jsx("base", { href: base.href }), app && _jsx("title", { children: app })] }), _jsx("body", {
|
|
10
|
+
return (_jsxs("html", { lang: language, children: [_jsxs("head", { children: [_jsx("meta", { charSet: "utf-8" }), base && _jsx("base", { href: base.href }), app && _jsx("title", { children: app })] }), _jsx("body", { children: _jsx(MetaContext, { value: merged, children: children }) })] }));
|
|
11
11
|
}
|
package/ui/page/HTML.tsx
CHANGED
|
@@ -7,7 +7,7 @@ export interface HTMLProps extends PossibleMeta {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Output a `<html>` element wrapping `<head>` (via `<Head>`) and `<body
|
|
10
|
+
* Output a `<html>` element wrapping `<head>` (via `<Head>`) and `<body>`.
|
|
11
11
|
* - `<Head>` renders the literal `<head>` with `<base>` and other shell-level metadata; per-page hoistable elements (title, meta, links, stylesheets, scripts) come from `<PageHead>` inside `<Page>` and are hoisted into this `<head>` by React 19.
|
|
12
12
|
*/
|
|
13
13
|
export function HTML({ children, ...meta }: HTMLProps): ReactElement {
|
|
@@ -20,7 +20,7 @@ export function HTML({ children, ...meta }: HTMLProps): ReactElement {
|
|
|
20
20
|
{base && <base href={base.href} />}
|
|
21
21
|
{app && <title>{app}</title>}
|
|
22
22
|
</head>
|
|
23
|
-
<body
|
|
23
|
+
<body>
|
|
24
24
|
<MetaContext value={merged}>{children}</MetaContext>
|
|
25
25
|
</body>
|
|
26
26
|
</html>
|
package/ui/util/meta.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { AnyCaller } from "../../util/function.js";
|
|
|
4
4
|
import { type PossibleLink } from "../../util/link.js";
|
|
5
5
|
import type { Nullish } from "../../util/null.js";
|
|
6
6
|
import { type ImmutableURI, type PossibleURI, type PossibleURIParams } from "../../util/uri.js";
|
|
7
|
-
import { type ImmutableURL } from "../../util/url.js";
|
|
7
|
+
import { type ImmutableURL, type PossibleURL } from "../../util/url.js";
|
|
8
8
|
/** Set of named meta `<meta />` tags in `{ name: content }` format. */
|
|
9
9
|
export type MetaTags = ImmutableDictionary<string | boolean | null | undefined>;
|
|
10
10
|
/** Set of named meta `<link />` tags in `{ rel: href }` format. */
|
|
@@ -42,7 +42,9 @@ export interface Meta {
|
|
|
42
42
|
readonly stylesheets?: MetaAssets | undefined;
|
|
43
43
|
}
|
|
44
44
|
/** Input metadata that can be parsed and converted to proper metadata. */
|
|
45
|
-
export interface PossibleMeta extends Omit<Meta, "url" | "links" | "scripts" | "modules" | "stylesheets"> {
|
|
45
|
+
export interface PossibleMeta extends Omit<Meta, "root" | "url" | "links" | "scripts" | "modules" | "stylesheets"> {
|
|
46
|
+
/** Base URL for the app — accepts a string or `URL`, resolved with `requireURL()`. */
|
|
47
|
+
readonly root?: PossibleURL | undefined;
|
|
46
48
|
/**
|
|
47
49
|
* New URL for the page.
|
|
48
50
|
* - Resolved using `requireURL()` if set relative to `root`
|
|
@@ -70,11 +72,16 @@ export declare function joinTitles(...titles: (string | undefined)[]): string;
|
|
|
70
72
|
* - `stylesheets` and `links` hrefs newly set in `meta2` are absolutified against the merged `url`/`base`, so they stay correct no matter where they are later rendered.
|
|
71
73
|
*/
|
|
72
74
|
export declare function mergeMeta(meta1: Meta, meta2: PossibleMeta, caller?: AnyCaller): Meta;
|
|
75
|
+
/**
|
|
76
|
+
* Create a fully-formed `Meta` from a `PossibleMeta`.
|
|
77
|
+
* - Like `mergeMeta()` but with no previous `Meta` to merge into — initialises meta from scratch.
|
|
78
|
+
*/
|
|
79
|
+
export declare function createMeta(meta: PossibleMeta, caller?: AnyCaller): Meta;
|
|
73
80
|
/**
|
|
74
81
|
* Merge two metadata URLs.
|
|
75
82
|
* - New URL is resolved relative to: current URL, new base URL, current base URL
|
|
76
83
|
*/
|
|
77
|
-
export declare function mergeMetaURL(base: ImmutableURL | undefined, current: ImmutableURL | undefined, next:
|
|
84
|
+
export declare function mergeMetaURL(base: ImmutableURL | undefined, current: ImmutableURL | undefined, next: PossibleURL | undefined, params: PossibleURIParams | undefined, caller?: AnyCaller): ImmutableURL | undefined;
|
|
78
85
|
/**
|
|
79
86
|
* Merge two metadata tags.
|
|
80
87
|
* - New assets are resolved relative to current URL (relative paths) and root URL (absolute paths).
|
package/ui/util/meta.js
CHANGED
|
@@ -37,6 +37,13 @@ export function mergeMeta(meta1, meta2, caller = mergeMeta) {
|
|
|
37
37
|
stylesheets: mergeMetaAssets(meta1.stylesheets, meta2.stylesheets, url, root, caller),
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Create a fully-formed `Meta` from a `PossibleMeta`.
|
|
42
|
+
* - Like `mergeMeta()` but with no previous `Meta` to merge into — initialises meta from scratch.
|
|
43
|
+
*/
|
|
44
|
+
export function createMeta(meta, caller = createMeta) {
|
|
45
|
+
return mergeMeta({}, meta, caller);
|
|
46
|
+
}
|
|
40
47
|
/**
|
|
41
48
|
* Merge two metadata URLs.
|
|
42
49
|
* - New URL is resolved relative to: current URL, new base URL, current base URL
|
package/ui/util/meta.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { AnyCaller } from "../../util/function.js";
|
|
|
4
4
|
import { type PossibleLink, requireLink } from "../../util/link.js";
|
|
5
5
|
import type { Nullish } from "../../util/null.js";
|
|
6
6
|
import { type ImmutableURI, type PossibleURI, type PossibleURIParams, withURIParams } from "../../util/uri.js";
|
|
7
|
-
import { type ImmutableURL, requireURL } from "../../util/url.js";
|
|
7
|
+
import { type ImmutableURL, type PossibleURL, requireURL } from "../../util/url.js";
|
|
8
8
|
|
|
9
9
|
/** Set of named meta `<meta />` tags in `{ name: content }` format. */
|
|
10
10
|
export type MetaTags = ImmutableDictionary<string | boolean | null | undefined>;
|
|
@@ -52,7 +52,10 @@ export interface Meta {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/** Input metadata that can be parsed and converted to proper metadata. */
|
|
55
|
-
export interface PossibleMeta extends Omit<Meta, "url" | "links" | "scripts" | "modules" | "stylesheets"> {
|
|
55
|
+
export interface PossibleMeta extends Omit<Meta, "root" | "url" | "links" | "scripts" | "modules" | "stylesheets"> {
|
|
56
|
+
/** Base URL for the app — accepts a string or `URL`, resolved with `requireURL()`. */
|
|
57
|
+
readonly root?: PossibleURL | undefined;
|
|
58
|
+
|
|
56
59
|
/**
|
|
57
60
|
* New URL for the page.
|
|
58
61
|
* - Resolved using `requireURL()` if set relative to `root`
|
|
@@ -111,6 +114,14 @@ export function mergeMeta(meta1: Meta, meta2: PossibleMeta, caller: AnyCaller =
|
|
|
111
114
|
};
|
|
112
115
|
}
|
|
113
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Create a fully-formed `Meta` from a `PossibleMeta`.
|
|
119
|
+
* - Like `mergeMeta()` but with no previous `Meta` to merge into — initialises meta from scratch.
|
|
120
|
+
*/
|
|
121
|
+
export function createMeta(meta: PossibleMeta, caller: AnyCaller = createMeta): Meta {
|
|
122
|
+
return mergeMeta({}, meta, caller);
|
|
123
|
+
}
|
|
124
|
+
|
|
114
125
|
/**
|
|
115
126
|
* Merge two metadata URLs.
|
|
116
127
|
* - New URL is resolved relative to: current URL, new base URL, current base URL
|
|
@@ -118,7 +129,7 @@ export function mergeMeta(meta1: Meta, meta2: PossibleMeta, caller: AnyCaller =
|
|
|
118
129
|
export function mergeMetaURL(
|
|
119
130
|
base: ImmutableURL | undefined,
|
|
120
131
|
current: ImmutableURL | undefined,
|
|
121
|
-
next:
|
|
132
|
+
next: PossibleURL | undefined,
|
|
122
133
|
params: PossibleURIParams | undefined,
|
|
123
134
|
caller: AnyCaller = mergeMetaURL,
|
|
124
135
|
): ImmutableURL | undefined {
|
package/util/element.d.ts
CHANGED
|
@@ -2,18 +2,19 @@ import type { ImmutableArray } from "./array.js";
|
|
|
2
2
|
import type { AbsolutePath } from "./path.js";
|
|
3
3
|
import type { Query } from "./query.js";
|
|
4
4
|
/** Set of valid props for an element. */
|
|
5
|
-
export
|
|
6
|
-
readonly [key: string]: unknown;
|
|
5
|
+
export type ElementProps = {
|
|
7
6
|
readonly children?: Elements;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Element with a type, props, and optional key (compatible with `React.ReactElement`).
|
|
10
|
+
* - Declared as a `type`, not an `interface`, so its implicit index signature lets it satisfy `Data` — `queryElements()` runs elements through `queryItems<T extends Data>`.
|
|
11
|
+
*/
|
|
12
|
+
export type Element<P extends ElementProps = ElementProps> = {
|
|
12
13
|
readonly type: string | ((props: P) => Elements | null);
|
|
13
14
|
readonly props: P;
|
|
14
15
|
readonly key: string | null;
|
|
15
16
|
readonly $$typeof?: symbol;
|
|
16
|
-
}
|
|
17
|
+
};
|
|
17
18
|
/** Collection of elements (compatible with `React.ReactNode`). */
|
|
18
19
|
export type Elements<T extends Element = Element> = undefined | null | string | T | Iterable<Elements<T>>;
|
|
19
20
|
/** Props for a tree element — must have a `tree-` prefixed type. */
|