@scalewing/react 1.41.0 → 1.42.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 +14 -0
- package/README.md +120 -1
- package/dist/components/Article.d.ts +60 -0
- package/dist/components/Article.js +42 -0
- package/dist/components/DocumentViewer.d.ts +41 -0
- package/dist/components/DocumentViewer.js +72 -0
- package/dist/components/FileField.d.ts +44 -0
- package/dist/components/FileField.js +66 -0
- package/dist/components/ListRow.d.ts +23 -2
- package/dist/components/ListRow.js +17 -17
- 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/chart/use-chart-width.js +2 -24
- 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 +21 -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/list/ListRowContent.d.ts +6 -2
- package/dist/components/list/ListRowContent.js +5 -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/list/row-slot.d.ts +8 -0
- package/dist/components/list/row-slot.js +12 -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/use-element-width.d.ts +11 -0
- package/dist/components/use-element-width.js +38 -0
- package/dist/css/css-article.d.ts +2 -0
- package/dist/css/css-article.js +110 -0
- package/dist/css/css-components.js +12 -0
- package/dist/css/css-count-pill.js +14 -1
- 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 +180 -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.js +5 -1
- package/dist/css/css-markdown.d.ts +2 -0
- package/dist/css/css-markdown.js +144 -0
- package/dist/css/css-variables.js +1 -0
- package/dist/css/stylesheet.js +8 -0
- package/dist/document-viewer-worker.d.ts +9 -0
- package/dist/document-viewer-worker.js +9 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/list-options.d.ts +5 -2
- package/dist/list-options.js +7 -2
- package/dist/styles.css +504 -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,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
|
+
}
|
|
@@ -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';
|
|
@@ -150,6 +154,14 @@ ${cssDateRangeClasses()}
|
|
|
150
154
|
|
|
151
155
|
${cssListClasses()}
|
|
152
156
|
|
|
157
|
+
${cssFileFieldClasses()}
|
|
158
|
+
|
|
159
|
+
${cssArticleClasses()}
|
|
160
|
+
|
|
161
|
+
${cssDocumentViewerClasses()}
|
|
162
|
+
|
|
163
|
+
${cssMarkdownClasses()}
|
|
164
|
+
|
|
153
165
|
${cssCountPillClasses()}
|
|
154
166
|
|
|
155
167
|
${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
|
}
|