shelving 1.212.0 → 1.214.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.
Files changed (47) hide show
  1. package/extract/Extractor.js +0 -2
  2. package/index.d.ts +0 -1
  3. package/index.js +1 -1
  4. package/markup/render.d.ts +3 -3
  5. package/markup/render.js +1 -1
  6. package/markup/rule/blockquote.js +2 -7
  7. package/markup/rule/code.js +2 -7
  8. package/markup/rule/fenced.js +2 -17
  9. package/markup/rule/heading.js +6 -7
  10. package/markup/rule/inline.js +5 -7
  11. package/markup/rule/linebreak.js +6 -7
  12. package/markup/rule/link.js +4 -11
  13. package/markup/rule/ordered.js +3 -19
  14. package/markup/rule/paragraph.js +2 -7
  15. package/markup/rule/separator.js +2 -7
  16. package/markup/rule/table.js +19 -28
  17. package/markup/rule/unordered.d.ts +2 -2
  18. package/markup/rule/unordered.js +3 -18
  19. package/markup/util/rule.d.ts +3 -3
  20. package/package.json +2 -1
  21. package/ui/README.md +83 -0
  22. package/ui/app/README.md +32 -0
  23. package/ui/block/README.md +115 -0
  24. package/ui/dialog/README.md +80 -0
  25. package/ui/docs/README.md +65 -0
  26. package/ui/form/README.md +165 -0
  27. package/ui/inline/README.md +86 -0
  28. package/ui/layout/README.md +71 -0
  29. package/ui/layout/SidebarLayout.d.ts +2 -2
  30. package/ui/layout/SidebarLayout.js +16 -8
  31. package/ui/layout/SidebarLayout.module.css +6 -45
  32. package/ui/layout/SidebarLayout.tsx +25 -16
  33. package/ui/menu/README.md +33 -0
  34. package/ui/misc/README.md +122 -0
  35. package/ui/notice/README.md +76 -22
  36. package/ui/page/HTML.d.ts +1 -1
  37. package/ui/page/HTML.js +2 -2
  38. package/ui/page/HTML.tsx +2 -2
  39. package/ui/page/README.md +56 -0
  40. package/ui/router/README.md +4 -4
  41. package/ui/transition/README.md +80 -0
  42. package/ui/tree/README.md +74 -0
  43. package/ui/util/README.md +140 -0
  44. package/ui/util/meta.d.ts +10 -3
  45. package/ui/util/meta.js +7 -0
  46. package/ui/util/meta.ts +14 -3
  47. package/util/element.d.ts +8 -7
@@ -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
@@ -6,7 +6,6 @@
6
6
  export * from "./api/index.js";
7
7
  export * from "./db/index.js";
8
8
  export * from "./error/index.js";
9
- export * from "./markup/index.js";
10
9
  export * from "./schema/index.js";
11
10
  export * from "./sequence/index.js";
12
11
  export * from "./store/index.js";
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.
@@ -1,4 +1,4 @@
1
- import type { Elements } from "../util/element.js";
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 Elements, i.e. either a complete `Element`, `null`, `undefined`, `string`, or an array of zero or more of those.
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): Elements;
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 Elements, i.e. either a complete `Element`, `null`, `undefined`, `string`, or an array of zero or more of those.
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"]);
@@ -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);
@@ -1,4 +1,4 @@
1
- import { REACT_ELEMENT_TYPE } from "../util/internal.js";
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);
@@ -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
- key,
14
- $$typeof: REACT_ELEMENT_TYPE,
15
- type: `h${prefix.length}`,
16
- props: { children: renderMarkup(heading.trim(), options, "inline") },
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"]);
@@ -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
- key,
23
- $$typeof: REACT_ELEMENT_TYPE,
24
- type: INLINE_CHARS[char],
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"]);
@@ -1,4 +1,4 @@
1
- import { REACT_ELEMENT_TYPE } from "../util/internal.js";
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
- key,
15
- $$typeof: REACT_ELEMENT_TYPE,
16
- type: "br",
17
- props: {},
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
+ ]);
@@ -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"]);
@@ -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
  }
@@ -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);
@@ -1,4 +1,4 @@
1
- import { REACT_ELEMENT_TYPE } from "../util/internal.js";
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"]);
@@ -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
- // First section is `<thead>`; the last is `<tfoot>` when there are 3+ sections; sections in between are each a `<tbody>`.
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 children = sections.map((rows, s) => {
47
- const type = s === 0 ? "thead" : s === last && last >= 2 ? "tfoot" : "tbody";
48
- return {
49
- $$typeof: REACT_ELEMENT_TYPE,
50
- type,
51
- key: `${type}-${s}`,
52
- props: { children: Array.from(_renderRows(rows, s === 0 ? "th" : "td", aligns, options)) },
53
- };
54
- });
55
- return { key, $$typeof: REACT_ELEMENT_TYPE, type: "table", props: { children } };
56
- }
57
- /** Render the rows of one section into `<tr>` elements of `<th>` or `<td>` cells. */
58
- function* _renderRows(rows, cell, aligns, options) {
59
- let r = 0;
60
- for (const row of rows) {
61
- const values = _splitRow(row);
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 { Element } from "../../util/element.js";
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<Element>;
16
+ export declare function _getItems(list: string, options: MarkupOptions): Iterable<ReactElement>;
@@ -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
  }
@@ -1,4 +1,4 @@
1
- import type { Element } from "../../util/element.js";
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): Element;
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) => Element : (match: RegExpExecArray, options: MarkupOptions, key: string) => Element, contexts: MarkupContexts, priority?: number): MarkupRule;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.212.0",
3
+ "version": "1.214.0",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",
@@ -35,6 +35,7 @@
35
35
  "./cloudflare": "./cloudflare/index.js",
36
36
  "./db": "./db/index.js",
37
37
  "./error": "./error/index.js",
38
+ "./extract": "./extract/index.js",
38
39
  "./firestore/client": "./firestore/client/index.js",
39
40
  "./firestore/lite": "./firestore/lite/index.js",
40
41
  "./firestore/server": "./firestore/server/index.js",
package/ui/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # ui
2
+
3
+ A React component library for building Shelving apps — forms, content, layout, routing, dialogs, and the documentation-site components, all in one place.
4
+
5
+ The `ui` module exists so an app never hand-rolls the same form field, card, or router twice. Every component picks up its look from shared CSS and exposes its variations as plain boolean props. You build a screen by composing these pieces, and reach for a custom-styled element only when nothing here fits.
6
+
7
+ `ui` is consumed as source — it ships `.tsx` and `.module.css` files and needs a bundler that understands CSS Modules and JSX. It is not part of the root `shelving` package; import it from `shelving/ui`.
8
+
9
+ ## How components work
10
+
11
+ A few conventions run through every component (see also the React Components section of `AGENTS.md`):
12
+
13
+ - **Variants, not CSS.** Visual options are boolean props — `<Button small primary>`, `<Section narrow>`. Each maps to a class in the component's CSS Module. You never pass `style` or raw `className`.
14
+ - **Composition.** Higher-level components — a `*Page`, a `*Card` — take their identity from library components like `Card`, `Section`, `Button`, and `Tag` rather than shipping their own styling.
15
+ - **Sentence case.** Titles, headings, and button labels capitalise only the first word.
16
+ - **Theming via CSS variables.** Colour and spacing come from CSS custom properties with fallback chains, so a theme is a small set of variable overrides.
17
+
18
+ ## Module map
19
+
20
+ ### Content
21
+
22
+ | Folder | What's inside |
23
+ |---|---|
24
+ | [block](/ui/block) | Block-level content — `Card`, `Section`, `Heading`, `Table`, `List`, `Prose`, `Figure`, `Flex` |
25
+ | [inline](/ui/inline) | Inline content — `Code`, `Strong`, `Emphasis`, `Link`, `Mark`, `Small` |
26
+ | [misc](/ui/misc) | Cross-cutting pieces — `Markup`, `Tag`, `Status`, `Loading`, `Color`, `Catcher`, `Mapper` |
27
+
28
+ ### Structure
29
+
30
+ | Folder | What's inside |
31
+ |---|---|
32
+ | [app](/ui/app) | The `<App>` root component |
33
+ | [page](/ui/page) | Document-level components — `<HTML>`, `<Head>`, `<Page>` |
34
+ | [layout](/ui/layout) | Page layouts — `SidebarLayout`, `CenteredLayout` |
35
+ | [router](/ui/router) | Client-side routing — `<Navigation>`, `<Router>` |
36
+
37
+ ### Interaction
38
+
39
+ | Folder | What's inside |
40
+ |---|---|
41
+ | [form](/ui/form) | Forms and inputs — `<Form>`, `<Field>`, typed inputs, `<Button>`, `FormStore` |
42
+ | [dialog](/ui/dialog) | `<Dialog>` and `<Modal>` overlays |
43
+ | [menu](/ui/menu) | `<Menu>` and `<MenuItem>` |
44
+ | [notice](/ui/notice) | Inline and global notices |
45
+ | [transition](/ui/transition) | CSS enter / leave transitions |
46
+
47
+ ### Documentation site
48
+
49
+ | Folder | What's inside |
50
+ |---|---|
51
+ | [tree](/ui/tree) | `<TreeApp>` and the components that turn a tree into a site |
52
+ | [docs](/ui/docs) | Page and card renderers for directories, files, and code symbols |
53
+ | [util](/ui/util) | UI helper functions — context, meta, CSS class composition |
54
+
55
+ ## Quick start
56
+
57
+ A minimal single-screen app:
58
+
59
+ ```tsx
60
+ import { App, CenteredLayout, Section, Heading, Paragraph } from "shelving/ui";
61
+
62
+ function HelloApp() {
63
+ return (
64
+ <App app="My app">
65
+ <CenteredLayout>
66
+ <Section narrow>
67
+ <Heading>Hello</Heading>
68
+ <Paragraph>Welcome to the app.</Paragraph>
69
+ </Section>
70
+ </CenteredLayout>
71
+ </App>
72
+ );
73
+ }
74
+ ```
75
+
76
+ For a routed, multi-page app, wrap the tree in [`<Navigation>` and `<Router>`](/ui/router). For a documentation site, hand an extracted tree to [`<TreeApp>`](/ui/tree) — see the [extract](/extract) guide.
77
+
78
+ ## See also
79
+
80
+ - [extract](/extract) — builds the tree that the documentation components render
81
+ - [markup](/markup) — Markdown rendering used by `<Markup>` and `<Prose>`
82
+ - [store](/store) — reactive state behind `FormStore`, `NavigationStore`, and notices
83
+ - [react](/react) — store and provider hooks used alongside these components
@@ -0,0 +1,32 @@
1
+ # App
2
+
3
+ Root component for a client-side Shelving app. `<App>` applies the theme CSS class to `document.body` and provides a `Meta` context so every descendant can read or update page metadata.
4
+
5
+ Use `<App>` when mounting into an existing HTML page on the client. For server-side rendering where you need the full `<html>` document shell, use [`<HTML>`](/ui/page) instead.
6
+
7
+ ## Usage
8
+
9
+ ```tsx
10
+ import { App, Navigation, Router } from "shelving/ui";
11
+
12
+ export function MyApp() {
13
+ return (
14
+ <App app="My App" root="https://example.com/" url="/">
15
+ <Navigation>
16
+ <Router routes={{
17
+ "/": HomePage,
18
+ "/about": AboutPage,
19
+ }} />
20
+ </Navigation>
21
+ </App>
22
+ );
23
+ }
24
+ ```
25
+
26
+ `<App>` accepts all `PossibleMeta` props (`app`, `root`, `url`, `title`, `language`, `tags`, etc.) and merges them into the context it provides to children. On mount it adds the theme class to `document.body`, which activates the CSS custom property tokens defined in `App.module.css`; on unmount it removes it.
27
+
28
+ ## See also
29
+
30
+ - [`ui/page`](/ui/page) — `<HTML>` and `<Page>` for the document shell and per-page metadata
31
+ - [`ui/layout`](/ui/layout) — `SidebarLayout` and `CenteredLayout`
32
+ - [`ui/router`](/ui/router) — `<Navigation>` and `<Router>` for client-side routing