@scalewing/react 1.41.0 → 1.44.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/CHANGELOG.md +35 -0
- package/README.md +180 -1
- package/dist/components/ActionMenu.d.ts +4 -2
- package/dist/components/ActionMenu.js +4 -8
- package/dist/components/Article.d.ts +60 -0
- package/dist/components/Article.js +42 -0
- package/dist/components/DescriptionList.d.ts +12 -2
- package/dist/components/DescriptionList.js +7 -3
- package/dist/components/DocumentViewer.d.ts +41 -0
- package/dist/components/DocumentViewer.js +72 -0
- package/dist/components/FileField.d.ts +55 -0
- package/dist/components/FileField.js +75 -0
- package/dist/components/FilterChips.d.ts +1 -1
- package/dist/components/FilterChips.js +1 -1
- package/dist/components/FilterChipsRow.d.ts +24 -0
- package/dist/components/FilterChipsRow.js +14 -0
- package/dist/components/ListRow.d.ts +31 -2
- package/dist/components/ListRow.js +19 -18
- package/dist/components/MarkdownView.d.ts +39 -0
- package/dist/components/MarkdownView.js +30 -0
- package/dist/components/NavCount.d.ts +15 -2
- package/dist/components/NavCount.js +16 -6
- package/dist/components/action-menu/assert-action-menu.d.ts +13 -0
- package/dist/components/action-menu/assert-action-menu.js +21 -0
- package/dist/components/chart/use-chart-width.js +2 -24
- package/dist/components/description-list-layout.d.ts +10 -0
- package/dist/components/description-list-layout.js +13 -0
- package/dist/components/document-viewer/document-viewer-labels.d.ts +23 -0
- package/dist/components/document-viewer/document-viewer-labels.js +23 -0
- package/dist/components/document-viewer/page-box.d.ts +8 -0
- package/dist/components/document-viewer/page-box.js +12 -0
- package/dist/components/document-viewer/pdf-document.d.ts +32 -0
- package/dist/components/document-viewer/pdf-document.js +66 -0
- package/dist/components/document-viewer/pdf-options.d.ts +36 -0
- package/dist/components/document-viewer/pdf-options.js +46 -0
- package/dist/components/document-viewer/render-pdf-page.d.ts +20 -0
- package/dist/components/document-viewer/render-pdf-page.js +38 -0
- package/dist/components/document-viewer/scroll-chain.d.ts +6 -0
- package/dist/components/document-viewer/scroll-chain.js +23 -0
- package/dist/components/document-viewer/use-error-report.d.ts +5 -0
- package/dist/components/document-viewer/use-error-report.js +13 -0
- package/dist/components/document-viewer/use-page-render.d.ts +21 -0
- package/dist/components/document-viewer/use-page-render.js +70 -0
- package/dist/components/document-viewer/use-pdf-document.d.ts +22 -0
- package/dist/components/document-viewer/use-pdf-document.js +47 -0
- package/dist/components/document-viewer/use-pinch-zoom.d.ts +12 -0
- package/dist/components/document-viewer/use-pinch-zoom.js +67 -0
- package/dist/components/document-viewer/use-zoom.d.ts +23 -0
- package/dist/components/document-viewer/use-zoom.js +70 -0
- package/dist/components/document-viewer/zoom.d.ts +29 -0
- package/dist/components/document-viewer/zoom.js +37 -0
- package/dist/components/file-field/file-field-checks.d.ts +13 -0
- package/dist/components/file-field/file-field-checks.js +23 -0
- package/dist/components/file-field/use-file-drop.d.ts +34 -0
- package/dist/components/file-field/use-file-drop.js +78 -0
- package/dist/components/file-field/use-swap-focus.d.ts +15 -0
- package/dist/components/file-field/use-swap-focus.js +40 -0
- package/dist/components/filter-chips/FilterChip.js +1 -1
- package/dist/components/filter-chips/options.d.ts +10 -0
- package/dist/components/filter-chips/options.js +8 -0
- package/dist/components/filter-chips/validate.d.ts +3 -3
- package/dist/components/filter-chips/validate.js +8 -4
- package/dist/components/list/ListRowContent.d.ts +9 -2
- package/dist/components/list/ListRowContent.js +6 -3
- package/dist/components/list/ListRowItem.d.ts +10 -0
- package/dist/components/list/ListRowItem.js +17 -0
- package/dist/components/list/row-naming.d.ts +17 -0
- package/dist/components/list/row-naming.js +19 -0
- package/dist/components/markdown/markdown-limits.d.ts +14 -0
- package/dist/components/markdown/markdown-limits.js +14 -0
- package/dist/components/markdown/markdown-model.d.ts +94 -0
- package/dist/components/markdown/markdown-model.js +8 -0
- package/dist/components/markdown/markdown-options.d.ts +18 -0
- package/dist/components/markdown/markdown-options.js +10 -0
- package/dist/components/markdown/markdown-rules.d.ts +28 -0
- package/dist/components/markdown/markdown-rules.js +81 -0
- package/dist/components/markdown/markdown-text.d.ts +17 -0
- package/dist/components/markdown/markdown-text.js +86 -0
- package/dist/components/markdown/parse-markdown.d.ts +4 -0
- package/dist/components/markdown/parse-markdown.js +21 -0
- package/dist/components/markdown/read-blocks.d.ts +8 -0
- package/dist/components/markdown/read-blocks.js +168 -0
- package/dist/components/markdown/read-inlines.d.ts +12 -0
- package/dist/components/markdown/read-inlines.js +126 -0
- package/dist/components/markdown/read-markdown.d.ts +9 -0
- package/dist/components/markdown/read-markdown.js +27 -0
- package/dist/components/markdown/reading-context.d.ts +18 -0
- package/dist/components/markdown/reading-context.js +35 -0
- package/dist/components/markdown/render-markdown.d.ts +10 -0
- package/dist/components/markdown/render-markdown.js +52 -0
- package/dist/components/slot.d.ts +9 -0
- package/dist/components/slot.js +13 -0
- package/dist/components/use-element-width.d.ts +11 -0
- package/dist/components/use-element-width.js +38 -0
- package/dist/css/css-action-menu-trigger.d.ts +6 -2
- package/dist/css/css-action-menu-trigger.js +50 -2
- package/dist/css/css-action-menu.js +4 -2
- package/dist/css/css-article.d.ts +2 -0
- package/dist/css/css-article.js +110 -0
- package/dist/css/css-components.js +15 -0
- package/dist/css/css-count-pill.js +14 -1
- package/dist/css/css-description-list.d.ts +1 -0
- package/dist/css/css-description-list.js +24 -0
- package/dist/css/css-document-viewer.d.ts +2 -0
- package/dist/css/css-document-viewer.js +186 -0
- package/dist/css/css-file-field.d.ts +2 -0
- package/dist/css/css-file-field.js +194 -0
- package/dist/css/css-filter-chip-tones.d.ts +3 -0
- package/dist/css/css-filter-chip-tones.js +28 -0
- package/dist/css/css-filter-chips-layout.d.ts +1 -0
- package/dist/css/css-filter-chips-layout.js +1 -1
- package/dist/css/css-filter-chips-row.d.ts +2 -0
- package/dist/css/css-filter-chips-row.js +79 -0
- package/dist/css/css-filter-chips.js +8 -0
- package/dist/css/css-font-mono.d.ts +3 -0
- package/dist/css/css-font-mono.js +23 -0
- package/dist/css/css-list-row-trailing.d.ts +2 -0
- package/dist/css/css-list-row-trailing.js +91 -0
- package/dist/css/css-list-row-truncate.d.ts +2 -0
- package/dist/css/css-list-row-truncate.js +29 -0
- package/dist/css/css-list.js +9 -1
- package/dist/css/css-markdown.d.ts +2 -0
- package/dist/css/css-markdown.js +144 -0
- package/dist/css/css-utilities.js +3 -0
- package/dist/css/css-variables.js +1 -0
- package/dist/css/spacing-classes.d.ts +1 -1
- package/dist/css/spacing-classes.js +1 -0
- package/dist/css/stylesheet.js +10 -0
- package/dist/document-viewer-worker.d.ts +9 -0
- package/dist/document-viewer-worker.js +9 -0
- package/dist/index.d.ts +11 -5
- package/dist/index.js +8 -3
- package/dist/list-options.d.ts +5 -2
- package/dist/list-options.js +7 -2
- package/dist/styles.css +649 -0
- package/package.json +10 -2
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { maxExcerpt, maxNesting } from './markdown-limits.js';
|
|
2
|
+
import { safeLinkHref } from './markdown-rules.js';
|
|
3
|
+
import { plainText } from './markdown-text.js';
|
|
4
|
+
/** A token's attribute, read without its methods (tokens may be copies). */
|
|
5
|
+
export function attr(token, name) {
|
|
6
|
+
return token.attrs?.find(([key]) => key === name)?.[1] ?? null;
|
|
7
|
+
}
|
|
8
|
+
function close(context, frame, parent) {
|
|
9
|
+
if (frame.kind === 'root')
|
|
10
|
+
return;
|
|
11
|
+
if (frame.kind !== 'link') {
|
|
12
|
+
parent.children.push({ kind: frame.kind, children: frame.children });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const href = safeLinkHref(frame.href ?? '');
|
|
16
|
+
if (href !== null) {
|
|
17
|
+
parent.children.push({ kind: 'link', href, children: frame.children });
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
context.omit('link', `[${plainText(frame.children)}](${frame.href ?? ''})`);
|
|
21
|
+
// Links never hold links, so each word moves up once.
|
|
22
|
+
for (const child of frame.children)
|
|
23
|
+
parent.children.push(child);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* One block's inline tokens as the view's inlines: text, breaks, bold,
|
|
27
|
+
* italic and links. A link's address must pass `safeLinkHref`, or its
|
|
28
|
+
* words stay as text; raw HTML and images here are left out; code is its
|
|
29
|
+
* words; marks nested past `maxNesting` are their words.
|
|
30
|
+
*/
|
|
31
|
+
export function readInlines(context, tokens) {
|
|
32
|
+
const root = { kind: 'root', href: null, children: [] };
|
|
33
|
+
const stack = [root];
|
|
34
|
+
// Marks past `maxNesting` open no frame: their words go straight into
|
|
35
|
+
// the deepest drawn one, so each word is placed once at any depth.
|
|
36
|
+
let flat = 0;
|
|
37
|
+
let flatWords = '';
|
|
38
|
+
// Links never hold links: the open one's address, quoted when it shuts.
|
|
39
|
+
const flatLinks = [];
|
|
40
|
+
const quote = (text) => {
|
|
41
|
+
if (flatWords.length < maxExcerpt)
|
|
42
|
+
flatWords += text;
|
|
43
|
+
};
|
|
44
|
+
const open = (kind, href) => {
|
|
45
|
+
if (flat > 0 || stack.length > maxNesting) {
|
|
46
|
+
flat += 1;
|
|
47
|
+
// A link this deep is drawn as its words; the run's one omission
|
|
48
|
+
// quotes it as `[words](address)`.
|
|
49
|
+
flatLinks.push(kind === 'link' ? (href ?? '') : null);
|
|
50
|
+
if (kind === 'link')
|
|
51
|
+
quote('[');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
stack.push({ kind, href, children: [] });
|
|
55
|
+
};
|
|
56
|
+
const shut = () => {
|
|
57
|
+
if (flat > 0) {
|
|
58
|
+
flat -= 1;
|
|
59
|
+
const link = flatLinks.pop();
|
|
60
|
+
if (link !== null && link !== undefined)
|
|
61
|
+
quote(`](${link})`);
|
|
62
|
+
if (flat === 0) {
|
|
63
|
+
context.omit('limit', flatWords);
|
|
64
|
+
flatWords = '';
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (stack.length > 1)
|
|
69
|
+
close(context, stack.pop(), stack[stack.length - 1]);
|
|
70
|
+
};
|
|
71
|
+
const words = (value) => {
|
|
72
|
+
stack[stack.length - 1].children.push({ kind: 'text', value });
|
|
73
|
+
if (flat > 0)
|
|
74
|
+
quote(value);
|
|
75
|
+
};
|
|
76
|
+
for (const token of tokens) {
|
|
77
|
+
const top = stack[stack.length - 1];
|
|
78
|
+
switch (token.type) {
|
|
79
|
+
case 'text':
|
|
80
|
+
case 'text_special':
|
|
81
|
+
words(token.content);
|
|
82
|
+
break;
|
|
83
|
+
case 'softbreak':
|
|
84
|
+
words('\n');
|
|
85
|
+
break;
|
|
86
|
+
case 'hardbreak':
|
|
87
|
+
top.children.push({ kind: 'break' });
|
|
88
|
+
break;
|
|
89
|
+
case 'code_inline':
|
|
90
|
+
context.omit('code', `\`${token.content}\``);
|
|
91
|
+
words(token.content);
|
|
92
|
+
break;
|
|
93
|
+
case 'html_inline':
|
|
94
|
+
context.omit('html', token.content);
|
|
95
|
+
break;
|
|
96
|
+
case 'image':
|
|
97
|
+
// An image in a link, a mark or a heading: never drawn there.
|
|
98
|
+
context.omit('image', ` ?? ''})`);
|
|
99
|
+
break;
|
|
100
|
+
case 'strong_open':
|
|
101
|
+
open('strong', null);
|
|
102
|
+
break;
|
|
103
|
+
case 'em_open':
|
|
104
|
+
open('emphasis', null);
|
|
105
|
+
break;
|
|
106
|
+
case 'link_open':
|
|
107
|
+
open('link', attr(token, 'href'));
|
|
108
|
+
break;
|
|
109
|
+
case 'strong_close':
|
|
110
|
+
case 'em_close':
|
|
111
|
+
case 'link_close':
|
|
112
|
+
shut();
|
|
113
|
+
break;
|
|
114
|
+
default:
|
|
115
|
+
// Nothing else is parsed (no strikethrough, no footnotes); any
|
|
116
|
+
// other token's words, if it has them, stay as text.
|
|
117
|
+
if (token.content)
|
|
118
|
+
words(token.content);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
while (flat > 0)
|
|
122
|
+
shut();
|
|
123
|
+
while (stack.length > 1)
|
|
124
|
+
close(context, stack.pop(), stack[stack.length - 1]);
|
|
125
|
+
return root.children;
|
|
126
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type MarkdownReading } from './markdown-model.js';
|
|
2
|
+
import { type MarkdownOptions } from './markdown-options.js';
|
|
3
|
+
/**
|
|
4
|
+
* Reads Markdown into what `MarkdownView` draws: its blocks, its headings
|
|
5
|
+
* (with the ids the view gives them) and what it leaves out. Pure: the
|
|
6
|
+
* same source and options always give the same reading. Refuses a source
|
|
7
|
+
* that is not text and options the view refuses.
|
|
8
|
+
*/
|
|
9
|
+
export declare function readMarkdown(source: string, options?: MarkdownOptions, owner?: string): MarkdownReading;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { maxParseNesting } from './markdown-limits.js';
|
|
2
|
+
import { assertMarkdownInput, } from './markdown-options.js';
|
|
3
|
+
import { parseMarkdown } from './parse-markdown.js';
|
|
4
|
+
import { readBlocks } from './read-blocks.js';
|
|
5
|
+
import { ReadingContext } from './reading-context.js';
|
|
6
|
+
/**
|
|
7
|
+
* Reads Markdown into what `MarkdownView` draws: its blocks, its headings
|
|
8
|
+
* (with the ids the view gives them) and what it leaves out. Pure: the
|
|
9
|
+
* same source and options always give the same reading. Refuses a source
|
|
10
|
+
* that is not text and options the view refuses.
|
|
11
|
+
*/
|
|
12
|
+
export function readMarkdown(source, options = {}, owner = 'readMarkdown') {
|
|
13
|
+
assertMarkdownInput(owner, source, options);
|
|
14
|
+
const tokens = parseMarkdown(source);
|
|
15
|
+
const context = new ReadingContext(source, options);
|
|
16
|
+
// The parser stops reading a block nested deeper than it reads.
|
|
17
|
+
const cut = tokens.find((token) => token.level >= maxParseNesting - 1);
|
|
18
|
+
if (cut)
|
|
19
|
+
context.omitBlock('limit', cut);
|
|
20
|
+
const blocks = readBlocks(context, tokens);
|
|
21
|
+
return {
|
|
22
|
+
blocks,
|
|
23
|
+
headings: context.headings,
|
|
24
|
+
omissions: context.omissions,
|
|
25
|
+
omissionCounts: context.omissionCounts,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type MarkdownHeading, type MarkdownOmission, type MarkdownOmissionCounts, type MarkdownOmissionKind } from './markdown-model.js';
|
|
2
|
+
import { type MarkdownOptions } from './markdown-options.js';
|
|
3
|
+
import { HeadingIds } from './markdown-rules.js';
|
|
4
|
+
import { type Token } from './parse-markdown.js';
|
|
5
|
+
/** One document's reading so far: its headings and what it left out. */
|
|
6
|
+
export declare class ReadingContext {
|
|
7
|
+
readonly options: MarkdownOptions;
|
|
8
|
+
readonly headings: MarkdownHeading[];
|
|
9
|
+
readonly omissions: MarkdownOmission[];
|
|
10
|
+
readonly omissionCounts: MarkdownOmissionCounts;
|
|
11
|
+
readonly ids: HeadingIds;
|
|
12
|
+
private readonly lines;
|
|
13
|
+
constructor(source: string, options: MarkdownOptions);
|
|
14
|
+
/** Notes something left out, by its words. */
|
|
15
|
+
omit(kind: MarkdownOmissionKind, text: string): void;
|
|
16
|
+
/** Notes a block left out, by the source lines it came from. */
|
|
17
|
+
omitBlock(kind: MarkdownOmissionKind, token: Token): void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { maxOmissionsListed } from './markdown-limits.js';
|
|
2
|
+
import { HeadingIds } from './markdown-rules.js';
|
|
3
|
+
import { excerpt } from './markdown-text.js';
|
|
4
|
+
/** One document's reading so far: its headings and what it left out. */
|
|
5
|
+
export class ReadingContext {
|
|
6
|
+
options;
|
|
7
|
+
headings = [];
|
|
8
|
+
omissions = [];
|
|
9
|
+
omissionCounts = {
|
|
10
|
+
html: 0,
|
|
11
|
+
table: 0,
|
|
12
|
+
image: 0,
|
|
13
|
+
link: 0,
|
|
14
|
+
code: 0,
|
|
15
|
+
limit: 0,
|
|
16
|
+
};
|
|
17
|
+
ids;
|
|
18
|
+
lines;
|
|
19
|
+
constructor(source, options) {
|
|
20
|
+
this.options = options;
|
|
21
|
+
this.ids = new HeadingIds(options.idPrefix ?? '');
|
|
22
|
+
this.lines = source.split(/\r\n?|\n/);
|
|
23
|
+
}
|
|
24
|
+
/** Notes something left out, by its words. */
|
|
25
|
+
omit(kind, text) {
|
|
26
|
+
this.omissionCounts[kind] += 1;
|
|
27
|
+
if (this.omissions.length < maxOmissionsListed)
|
|
28
|
+
this.omissions.push({ kind, source: excerpt(text) });
|
|
29
|
+
}
|
|
30
|
+
/** Notes a block left out, by the source lines it came from. */
|
|
31
|
+
omitBlock(kind, token) {
|
|
32
|
+
const [start, end] = token.map ?? [0, 0];
|
|
33
|
+
this.omit(kind, this.lines.slice(start, end).join('\n'));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type MarkdownBlock, type MarkdownInline } from './markdown-model.js';
|
|
3
|
+
/** What every drawn link carries besides its own words. */
|
|
4
|
+
export type LinkOptions = {
|
|
5
|
+
newTabLabel: string;
|
|
6
|
+
};
|
|
7
|
+
/** Draws a run of text as React elements: never HTML from a string. */
|
|
8
|
+
export declare function renderInlines(inlines: readonly MarkdownInline[], links: LinkOptions): ReactNode[];
|
|
9
|
+
/** Draws blocks in order. */
|
|
10
|
+
export declare function renderBlocks(blocks: readonly MarkdownBlock[], links: LinkOptions): ReactNode[];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Separator } from '../Separator.js';
|
|
3
|
+
const headingElements = { 1: 'h2', 2: 'h3', 3: 'h4' };
|
|
4
|
+
/** Draws a run of text as React elements: never HTML from a string. */
|
|
5
|
+
export function renderInlines(inlines, links) {
|
|
6
|
+
return inlines.map((inline, index) => {
|
|
7
|
+
switch (inline.kind) {
|
|
8
|
+
case 'text':
|
|
9
|
+
return inline.value;
|
|
10
|
+
case 'break':
|
|
11
|
+
return _jsx("br", {}, index);
|
|
12
|
+
case 'strong':
|
|
13
|
+
return (_jsx("strong", { children: renderInlines(inline.children, links) }, index));
|
|
14
|
+
case 'emphasis':
|
|
15
|
+
return _jsx("em", { children: renderInlines(inline.children, links) }, index);
|
|
16
|
+
case 'link':
|
|
17
|
+
return (_jsxs("a", { className: "sw-markdown-link", href: inline.href, rel: "noopener noreferrer",
|
|
18
|
+
// Safari leaves a link out of the Tab order without one.
|
|
19
|
+
tabIndex: 0, target: "_blank", children: [renderInlines(inline.children, links), _jsxs("span", { className: "sw-sr-only", children: [" ", links.newTabLabel] })] }, index));
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/** Draws a list item: a tight list's paragraphs are its words alone. */
|
|
24
|
+
function renderItem(blocks, tight, links) {
|
|
25
|
+
if (!tight)
|
|
26
|
+
return renderBlocks(blocks, links);
|
|
27
|
+
return blocks.map((block, index) => block.kind === 'paragraph' ? (_jsx("span", { className: "sw-markdown-item-text", children: renderInlines(block.children, links) }, index)) : (renderBlock(block, index, links)));
|
|
28
|
+
}
|
|
29
|
+
function renderBlock(block, key, links) {
|
|
30
|
+
switch (block.kind) {
|
|
31
|
+
case 'paragraph':
|
|
32
|
+
return (_jsx("p", { className: "sw-markdown-paragraph", children: renderInlines(block.children, links) }, key));
|
|
33
|
+
case 'heading': {
|
|
34
|
+
const Heading = headingElements[block.level];
|
|
35
|
+
return (_jsx(Heading, { className: `sw-markdown-heading sw-markdown-heading-${block.level}`, id: block.id, children: renderInlines(block.children, links) }, key));
|
|
36
|
+
}
|
|
37
|
+
case 'list': {
|
|
38
|
+
const items = block.items.map((item, index) => (_jsx("li", { children: renderItem(item, block.tight, links) }, index)));
|
|
39
|
+
return block.ordered ? (_jsx("ol", { className: "sw-markdown-list", start: block.start === null || block.start === 1 ? undefined : block.start, children: items }, key)) : (_jsx("ul", { className: "sw-markdown-list", children: items }, key));
|
|
40
|
+
}
|
|
41
|
+
case 'quote':
|
|
42
|
+
return (_jsx("blockquote", { className: "sw-markdown-quote", children: renderBlocks(block.children, links) }, key));
|
|
43
|
+
case 'figure':
|
|
44
|
+
return (_jsxs("figure", { className: "sw-markdown-figure", children: [_jsx("img", { alt: block.alt, className: "sw-markdown-image", src: block.src }), block.caption ? (_jsx("figcaption", { className: "sw-markdown-caption", children: block.caption })) : null] }, key));
|
|
45
|
+
case 'rule':
|
|
46
|
+
return _jsx(Separator, {}, key);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Draws blocks in order. */
|
|
50
|
+
export function renderBlocks(blocks, links) {
|
|
51
|
+
return blocks.map((block, index) => renderBlock(block, index, links));
|
|
52
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Whether a component's slot (a row's `leading`, `tag` or `action`, a
|
|
4
|
+
* chips row's `end`) holds something to draw. React renders nothing for
|
|
5
|
+
* `undefined`, `null`, a boolean (the `false` of
|
|
6
|
+
* `canArchive && <ActionMenu …/>`) or an empty string, so those are an
|
|
7
|
+
* empty slot and add no box and no gap.
|
|
8
|
+
*/
|
|
9
|
+
export declare function hasSlot(node: ReactNode): boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a component's slot (a row's `leading`, `tag` or `action`, a
|
|
3
|
+
* chips row's `end`) holds something to draw. React renders nothing for
|
|
4
|
+
* `undefined`, `null`, a boolean (the `false` of
|
|
5
|
+
* `canArchive && <ActionMenu …/>`) or an empty string, so those are an
|
|
6
|
+
* empty slot and add no box and no gap.
|
|
7
|
+
*/
|
|
8
|
+
export function hasSlot(node) {
|
|
9
|
+
return (node !== undefined &&
|
|
10
|
+
node !== null &&
|
|
11
|
+
node !== '' &&
|
|
12
|
+
typeof node !== 'boolean');
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Follows the width an element is given, in whole CSS pixels: on mount,
|
|
4
|
+
* before the first paint, and whenever its box changes size. Before that,
|
|
5
|
+
* and on the server, it reports `fallback`; a browser with no
|
|
6
|
+
* ResizeObserver measures on window resizes instead. `content` measures
|
|
7
|
+
* the content box (inside the padding) rather than the border box.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useElementWidth(ref: RefObject<HTMLElement | null>, fallback: number, { content }?: {
|
|
10
|
+
content?: boolean;
|
|
11
|
+
}): number;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useLayoutEffect, useState } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Follows the width an element is given, in whole CSS pixels: on mount,
|
|
5
|
+
* before the first paint, and whenever its box changes size. Before that,
|
|
6
|
+
* and on the server, it reports `fallback`; a browser with no
|
|
7
|
+
* ResizeObserver measures on window resizes instead. `content` measures
|
|
8
|
+
* the content box (inside the padding) rather than the border box.
|
|
9
|
+
*/
|
|
10
|
+
export function useElementWidth(ref, fallback, { content = false } = {}) {
|
|
11
|
+
const [width, setWidth] = useState(fallback);
|
|
12
|
+
useLayoutEffect(() => {
|
|
13
|
+
const box = ref.current;
|
|
14
|
+
if (!box)
|
|
15
|
+
return;
|
|
16
|
+
function measure() {
|
|
17
|
+
if (!box)
|
|
18
|
+
return;
|
|
19
|
+
const style = getComputedStyle(box);
|
|
20
|
+
const padding = content
|
|
21
|
+
? (parseFloat(style.paddingLeft) || 0) +
|
|
22
|
+
(parseFloat(style.paddingRight) || 0)
|
|
23
|
+
: 0;
|
|
24
|
+
const next = Math.round(box.getBoundingClientRect().width - padding);
|
|
25
|
+
if (next > 0)
|
|
26
|
+
setWidth(next);
|
|
27
|
+
}
|
|
28
|
+
measure();
|
|
29
|
+
if (typeof ResizeObserver === 'undefined') {
|
|
30
|
+
window.addEventListener('resize', measure);
|
|
31
|
+
return () => window.removeEventListener('resize', measure);
|
|
32
|
+
}
|
|
33
|
+
const observer = new ResizeObserver(measure);
|
|
34
|
+
observer.observe(box);
|
|
35
|
+
return () => observer.disconnect();
|
|
36
|
+
}, [ref, content]);
|
|
37
|
+
return width;
|
|
38
|
+
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
export declare const actionMenuTriggerAppearances: readonly ["framed", "text"];
|
|
1
|
+
export declare const actionMenuTriggerAppearances: readonly ["framed", "text", "icon"];
|
|
2
2
|
/**
|
|
3
3
|
* How an ActionMenu's trigger looks: `framed` (the default) is a small
|
|
4
4
|
* outlined control; `text` is its words in the text color with a chevron
|
|
5
5
|
* after them and no fill or outline until hovered or open, such as a
|
|
6
|
-
* person's name that opens their account menu in a header
|
|
6
|
+
* person's name that opens their account menu in a header; `icon` is the
|
|
7
|
+
* consumer's glyph alone in a round quiet button, no chevron and no
|
|
8
|
+
* outline until hovered or open, such as a row's ••• menu.
|
|
7
9
|
*/
|
|
8
10
|
export type ActionMenuTriggerAppearance = (typeof actionMenuTriggerAppearances)[number];
|
|
9
11
|
export declare const actionMenuTriggerTextClass = "sw-action-menu-trigger-text";
|
|
12
|
+
export declare const actionMenuTriggerIconClass = "sw-action-menu-trigger-icon";
|
|
10
13
|
export declare function cssActionMenuTriggerClasses(): string;
|
|
14
|
+
export declare function cssActionMenuTriggerIconClasses(): string;
|
|
11
15
|
export declare function actionMenuTriggerClassCatalog(): string[];
|
|
@@ -3,9 +3,11 @@ import { chevronStroke } from './chevron.js';
|
|
|
3
3
|
import { quietFill } from './quiet-fill.js';
|
|
4
4
|
import { coarsePointerQuery, touchTarget } from './touch-target.js';
|
|
5
5
|
const label = typographyVariants.label;
|
|
6
|
-
export const actionMenuTriggerAppearances = ['framed', 'text'];
|
|
6
|
+
export const actionMenuTriggerAppearances = ['framed', 'text', 'icon'];
|
|
7
7
|
export const actionMenuTriggerTextClass = 'sw-action-menu-trigger-text';
|
|
8
|
+
export const actionMenuTriggerIconClass = 'sw-action-menu-trigger-icon';
|
|
8
9
|
const text = `.sw-action-menu-trigger.${actionMenuTriggerTextClass}`;
|
|
10
|
+
const icon = `.sw-action-menu-trigger.${actionMenuTriggerIconClass}`;
|
|
9
11
|
/*
|
|
10
12
|
* A text trigger: no fill and a transparent border (kept, so forced colors
|
|
11
13
|
* can draw one), its words in label type at medium weight in the text
|
|
@@ -61,6 +63,52 @@ ${text}[aria-expanded='true'] {
|
|
|
61
63
|
${text}[aria-expanded='true'] { border-color: Highlight; }
|
|
62
64
|
}`;
|
|
63
65
|
}
|
|
66
|
+
/*
|
|
67
|
+
* An icon trigger: the consumer's glyph alone, centred in a round button
|
|
68
|
+
* as wide as it is tall (the sm control height, the 44 px target on a
|
|
69
|
+
* coarse pointer), in the muted colour, with no fill, no chevron and a
|
|
70
|
+
* transparent border (kept for forced colors). Like the text trigger it
|
|
71
|
+
* takes the quiet fill while open and under a hovering pointer. Its name
|
|
72
|
+
* is the menu's label, so the glyph says nothing on its own.
|
|
73
|
+
*/
|
|
74
|
+
export function cssActionMenuTriggerIconClasses() {
|
|
75
|
+
return `${icon} {
|
|
76
|
+
align-items: center;
|
|
77
|
+
background: transparent;
|
|
78
|
+
border-color: transparent;
|
|
79
|
+
border-radius: var(--sw-radius-pill);
|
|
80
|
+
color: var(--sw-color-muted);
|
|
81
|
+
display: inline-flex;
|
|
82
|
+
justify-content: center;
|
|
83
|
+
min-height: var(--sw-control-sm-min-height);
|
|
84
|
+
min-width: var(--sw-control-sm-min-height);
|
|
85
|
+
padding: 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
${icon}[aria-expanded='true'] {
|
|
89
|
+
background: ${quietFill};
|
|
90
|
+
color: var(--sw-color-text);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@media (hover: hover) {
|
|
94
|
+
${icon}:hover:not(:disabled) {
|
|
95
|
+
background: ${quietFill};
|
|
96
|
+
color: var(--sw-color-text);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@media ${coarsePointerQuery} {
|
|
101
|
+
${icon} {
|
|
102
|
+
min-height: ${touchTarget};
|
|
103
|
+
min-width: ${touchTarget};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@media (forced-colors: active) {
|
|
108
|
+
${icon} { color: ButtonText; }
|
|
109
|
+
${icon}[aria-expanded='true'] { border-color: Highlight; }
|
|
110
|
+
}`;
|
|
111
|
+
}
|
|
64
112
|
export function actionMenuTriggerClassCatalog() {
|
|
65
|
-
return [actionMenuTriggerTextClass];
|
|
113
|
+
return [actionMenuTriggerTextClass, actionMenuTriggerIconClass];
|
|
66
114
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
import { chevronStroke } from './chevron.js';
|
|
3
|
-
import { actionMenuTriggerClassCatalog, cssActionMenuTriggerClasses, } from './css-action-menu-trigger.js';
|
|
3
|
+
import { actionMenuTriggerClassCatalog, cssActionMenuTriggerClasses, cssActionMenuTriggerIconClasses, } from './css-action-menu-trigger.js';
|
|
4
4
|
import { zIndex } from './stacking.js';
|
|
5
5
|
import { coarsePointerQuery, touchTarget } from './touch-target.js';
|
|
6
6
|
import { quietFill } from './quiet-fill.js';
|
|
@@ -166,7 +166,9 @@ export function cssActionMenuClasses() {
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
${cssActionMenuTriggerClasses()}
|
|
169
|
+
${cssActionMenuTriggerClasses()}
|
|
170
|
+
|
|
171
|
+
${cssActionMenuTriggerIconClasses()}`;
|
|
170
172
|
}
|
|
171
173
|
export function actionMenuClassCatalog() {
|
|
172
174
|
return [
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
import { breakpointQuery } from './breakpoints.js';
|
|
3
|
+
const { caption, label } = typographyVariants;
|
|
4
|
+
/*
|
|
5
|
+
* An article is a card at the reading width (--sw-reading-max): its cover
|
|
6
|
+
* photo edge to edge across the top at 3:1 (2:1 on a phone), rounded with
|
|
7
|
+
* the card's top corners, then its words a space-6 below and above and a
|
|
8
|
+
* space-8 in from the sides (space-5 all round on a phone). The eyebrow
|
|
9
|
+
* is caption type at the label's weight, muted, with the actions at its
|
|
10
|
+
* row's end (when the row is too narrow they wrap under it, at its
|
|
11
|
+
* start); the title a space-3 under it; the meta line muted caption type
|
|
12
|
+
* a space-3 under the title; a hairline a space-3 under that, and the
|
|
13
|
+
* body a space-4 under the hairline. The card sits at the start of a
|
|
14
|
+
* wider column, as a page's main column does.
|
|
15
|
+
*/
|
|
16
|
+
export function cssArticleClasses() {
|
|
17
|
+
return `.sw-article {
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
max-inline-size: var(--sw-reading-max);
|
|
22
|
+
min-width: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.sw-article-cover {
|
|
26
|
+
aspect-ratio: 3 / 1;
|
|
27
|
+
background: var(--sw-color-subtle);
|
|
28
|
+
border-start-end-radius: inherit;
|
|
29
|
+
border-start-start-radius: inherit;
|
|
30
|
+
display: block;
|
|
31
|
+
inline-size: 100%;
|
|
32
|
+
object-fit: cover;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.sw-article-body {
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
min-width: 0;
|
|
39
|
+
padding: var(--sw-space-6) var(--sw-space-8);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.sw-article-header {
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
margin-block-end: var(--sw-space-3);
|
|
46
|
+
min-width: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.sw-article-top {
|
|
50
|
+
align-items: center;
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-wrap: wrap;
|
|
53
|
+
gap: var(--sw-space-3);
|
|
54
|
+
justify-content: space-between;
|
|
55
|
+
margin-block-end: var(--sw-space-3);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.sw-article-eyebrow {
|
|
59
|
+
color: var(--sw-color-muted);
|
|
60
|
+
font-size: ${caption.fontSize}px;
|
|
61
|
+
font-weight: ${label.fontWeight};
|
|
62
|
+
letter-spacing: ${caption.letterSpacing}px;
|
|
63
|
+
line-height: ${caption.lineHeight}px;
|
|
64
|
+
min-width: 0;
|
|
65
|
+
overflow-wrap: anywhere;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.sw-article-actions {
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-wrap: wrap;
|
|
71
|
+
gap: var(--sw-space-2);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.sw-article-actions:only-child { margin-inline-start: auto; }
|
|
75
|
+
|
|
76
|
+
.sw-article-title { overflow-wrap: anywhere; }
|
|
77
|
+
|
|
78
|
+
.sw-article-meta {
|
|
79
|
+
color: var(--sw-color-muted);
|
|
80
|
+
font-size: ${caption.fontSize}px;
|
|
81
|
+
letter-spacing: ${caption.letterSpacing}px;
|
|
82
|
+
line-height: ${caption.lineHeight}px;
|
|
83
|
+
margin-block-start: var(--sw-space-3);
|
|
84
|
+
overflow-wrap: anywhere;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.sw-article-content {
|
|
88
|
+
margin-block-start: var(--sw-space-4);
|
|
89
|
+
min-width: 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@media ${breakpointQuery('below', 'md')} {
|
|
93
|
+
.sw-article-cover { aspect-ratio: 2 / 1; }
|
|
94
|
+
.sw-article-body { padding: var(--sw-space-5); }
|
|
95
|
+
}`;
|
|
96
|
+
}
|
|
97
|
+
export function articleClassCatalog() {
|
|
98
|
+
return [
|
|
99
|
+
'sw-article',
|
|
100
|
+
'sw-article-cover',
|
|
101
|
+
'sw-article-body',
|
|
102
|
+
'sw-article-header',
|
|
103
|
+
'sw-article-top',
|
|
104
|
+
'sw-article-eyebrow',
|
|
105
|
+
'sw-article-actions',
|
|
106
|
+
'sw-article-title',
|
|
107
|
+
'sw-article-meta',
|
|
108
|
+
'sw-article-content',
|
|
109
|
+
];
|
|
110
|
+
}
|
|
@@ -5,7 +5,11 @@ import { cssAccordionClasses } from './css-accordion.js';
|
|
|
5
5
|
import { cssButtonClasses } from './css-button.js';
|
|
6
6
|
import { cssCalendarButtonClasses } from './css-calendar-button.js';
|
|
7
7
|
import { cssDateRangeClasses } from './css-date-range.js';
|
|
8
|
+
import { cssFileFieldClasses } from './css-file-field.js';
|
|
9
|
+
import { cssArticleClasses } from './css-article.js';
|
|
10
|
+
import { cssDocumentViewerClasses } from './css-document-viewer.js';
|
|
8
11
|
import { cssListClasses } from './css-list.js';
|
|
12
|
+
import { cssMarkdownClasses } from './css-markdown.js';
|
|
9
13
|
import { cssCountPillClasses } from './css-count-pill.js';
|
|
10
14
|
import { cssBarChartClasses } from './css-bar-chart.js';
|
|
11
15
|
import { cssChartLegendClasses } from './css-chart-legend.js';
|
|
@@ -32,6 +36,7 @@ import { cssMultiSelectClasses } from './css-multi-select.js';
|
|
|
32
36
|
import { cssSplitClasses } from './css-split.js';
|
|
33
37
|
import { cssSwitchClasses } from './css-switch.js';
|
|
34
38
|
import { cssTextClasses } from './css-text.js';
|
|
39
|
+
import { cssFontMonoClasses } from './css-font-mono.js';
|
|
35
40
|
import { cssSpinnerClasses } from './css-spinner.js';
|
|
36
41
|
import { cssProgressClasses } from './css-progress.js';
|
|
37
42
|
import { cssNoticeClasses } from './css-notice.js';
|
|
@@ -130,6 +135,8 @@ ${cssPopupHostRules()}
|
|
|
130
135
|
|
|
131
136
|
${cssTextClasses()}
|
|
132
137
|
|
|
138
|
+
${cssFontMonoClasses()}
|
|
139
|
+
|
|
133
140
|
${cssButtonClasses()}
|
|
134
141
|
|
|
135
142
|
${cssCheckboxClasses()}
|
|
@@ -150,6 +157,14 @@ ${cssDateRangeClasses()}
|
|
|
150
157
|
|
|
151
158
|
${cssListClasses()}
|
|
152
159
|
|
|
160
|
+
${cssFileFieldClasses()}
|
|
161
|
+
|
|
162
|
+
${cssArticleClasses()}
|
|
163
|
+
|
|
164
|
+
${cssDocumentViewerClasses()}
|
|
165
|
+
|
|
166
|
+
${cssMarkdownClasses()}
|
|
167
|
+
|
|
153
168
|
${cssCountPillClasses()}
|
|
154
169
|
|
|
155
170
|
${cssBarChartClasses()}
|
|
@@ -2,6 +2,8 @@ import { typographyVariants } from '@scalewing/tokens';
|
|
|
2
2
|
const caption = typographyVariants.caption;
|
|
3
3
|
/** The counts drawn as the pill: SectionNav's and a Nav word's. */
|
|
4
4
|
const pills = ['sw-section-nav-count', 'sw-nav-count'];
|
|
5
|
+
/** NavCount's news tone, a modifier of its pill. */
|
|
6
|
+
const accentPill = 'sw-nav-count-accent';
|
|
5
7
|
/*
|
|
6
8
|
* A count after a destination's words (a number waiting there, such as
|
|
7
9
|
* tasks to do) is a filled pill: the danger colour with its on-colour
|
|
@@ -30,6 +32,17 @@ export function cssCountPillClasses() {
|
|
|
30
32
|
padding-inline: var(--sw-space-1);
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
/*
|
|
36
|
+
* NavCount's \`tone="accent"\`: a count that is news, not work waiting (what
|
|
37
|
+
* was added since the person last looked), is the accent fill with its
|
|
38
|
+
* on-colour figures (4.5:1 in every palette, as a primary Button's label).
|
|
39
|
+
* One class more specific than the pill, so it wins in any order.
|
|
40
|
+
*/
|
|
41
|
+
[data-theme] .sw-nav-count.${accentPill} {
|
|
42
|
+
background: var(--sw-color-accent);
|
|
43
|
+
color: var(--sw-color-onAccent);
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
/*
|
|
34
47
|
* A Nav link holding a count lays out as SectionNav's link does: the words
|
|
35
48
|
* and the pill centred on one line a space-2 apart (the space text node
|
|
@@ -50,5 +63,5 @@ export function cssCountPillClasses() {
|
|
|
50
63
|
}`;
|
|
51
64
|
}
|
|
52
65
|
export function countPillClassCatalog() {
|
|
53
|
-
return [...pills];
|
|
66
|
+
return [...pills, accentPill];
|
|
54
67
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* a term sits over its detail. A term and a detail are columns of lines:
|
|
8
8
|
* each child is its own line with its own line height, so a caption term
|
|
9
9
|
* starts level with a caption detail instead of on the list's taller line.
|
|
10
|
+
* `spread` puts each detail at its row's end, beside its term at every width.
|
|
10
11
|
*/
|
|
11
12
|
export declare function cssDescriptionListClasses(): string;
|
|
12
13
|
export declare function descriptionListClassCatalog(): string[];
|