@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.
Files changed (136) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +180 -1
  3. package/dist/components/ActionMenu.d.ts +4 -2
  4. package/dist/components/ActionMenu.js +4 -8
  5. package/dist/components/Article.d.ts +60 -0
  6. package/dist/components/Article.js +42 -0
  7. package/dist/components/DescriptionList.d.ts +12 -2
  8. package/dist/components/DescriptionList.js +7 -3
  9. package/dist/components/DocumentViewer.d.ts +41 -0
  10. package/dist/components/DocumentViewer.js +72 -0
  11. package/dist/components/FileField.d.ts +55 -0
  12. package/dist/components/FileField.js +75 -0
  13. package/dist/components/FilterChips.d.ts +1 -1
  14. package/dist/components/FilterChips.js +1 -1
  15. package/dist/components/FilterChipsRow.d.ts +24 -0
  16. package/dist/components/FilterChipsRow.js +14 -0
  17. package/dist/components/ListRow.d.ts +31 -2
  18. package/dist/components/ListRow.js +19 -18
  19. package/dist/components/MarkdownView.d.ts +39 -0
  20. package/dist/components/MarkdownView.js +30 -0
  21. package/dist/components/NavCount.d.ts +15 -2
  22. package/dist/components/NavCount.js +16 -6
  23. package/dist/components/action-menu/assert-action-menu.d.ts +13 -0
  24. package/dist/components/action-menu/assert-action-menu.js +21 -0
  25. package/dist/components/chart/use-chart-width.js +2 -24
  26. package/dist/components/description-list-layout.d.ts +10 -0
  27. package/dist/components/description-list-layout.js +13 -0
  28. package/dist/components/document-viewer/document-viewer-labels.d.ts +23 -0
  29. package/dist/components/document-viewer/document-viewer-labels.js +23 -0
  30. package/dist/components/document-viewer/page-box.d.ts +8 -0
  31. package/dist/components/document-viewer/page-box.js +12 -0
  32. package/dist/components/document-viewer/pdf-document.d.ts +32 -0
  33. package/dist/components/document-viewer/pdf-document.js +66 -0
  34. package/dist/components/document-viewer/pdf-options.d.ts +36 -0
  35. package/dist/components/document-viewer/pdf-options.js +46 -0
  36. package/dist/components/document-viewer/render-pdf-page.d.ts +20 -0
  37. package/dist/components/document-viewer/render-pdf-page.js +38 -0
  38. package/dist/components/document-viewer/scroll-chain.d.ts +6 -0
  39. package/dist/components/document-viewer/scroll-chain.js +23 -0
  40. package/dist/components/document-viewer/use-error-report.d.ts +5 -0
  41. package/dist/components/document-viewer/use-error-report.js +13 -0
  42. package/dist/components/document-viewer/use-page-render.d.ts +21 -0
  43. package/dist/components/document-viewer/use-page-render.js +70 -0
  44. package/dist/components/document-viewer/use-pdf-document.d.ts +22 -0
  45. package/dist/components/document-viewer/use-pdf-document.js +47 -0
  46. package/dist/components/document-viewer/use-pinch-zoom.d.ts +12 -0
  47. package/dist/components/document-viewer/use-pinch-zoom.js +67 -0
  48. package/dist/components/document-viewer/use-zoom.d.ts +23 -0
  49. package/dist/components/document-viewer/use-zoom.js +70 -0
  50. package/dist/components/document-viewer/zoom.d.ts +29 -0
  51. package/dist/components/document-viewer/zoom.js +37 -0
  52. package/dist/components/file-field/file-field-checks.d.ts +13 -0
  53. package/dist/components/file-field/file-field-checks.js +23 -0
  54. package/dist/components/file-field/use-file-drop.d.ts +34 -0
  55. package/dist/components/file-field/use-file-drop.js +78 -0
  56. package/dist/components/file-field/use-swap-focus.d.ts +15 -0
  57. package/dist/components/file-field/use-swap-focus.js +40 -0
  58. package/dist/components/filter-chips/FilterChip.js +1 -1
  59. package/dist/components/filter-chips/options.d.ts +10 -0
  60. package/dist/components/filter-chips/options.js +8 -0
  61. package/dist/components/filter-chips/validate.d.ts +3 -3
  62. package/dist/components/filter-chips/validate.js +8 -4
  63. package/dist/components/list/ListRowContent.d.ts +9 -2
  64. package/dist/components/list/ListRowContent.js +6 -3
  65. package/dist/components/list/ListRowItem.d.ts +10 -0
  66. package/dist/components/list/ListRowItem.js +17 -0
  67. package/dist/components/list/row-naming.d.ts +17 -0
  68. package/dist/components/list/row-naming.js +19 -0
  69. package/dist/components/markdown/markdown-limits.d.ts +14 -0
  70. package/dist/components/markdown/markdown-limits.js +14 -0
  71. package/dist/components/markdown/markdown-model.d.ts +94 -0
  72. package/dist/components/markdown/markdown-model.js +8 -0
  73. package/dist/components/markdown/markdown-options.d.ts +18 -0
  74. package/dist/components/markdown/markdown-options.js +10 -0
  75. package/dist/components/markdown/markdown-rules.d.ts +28 -0
  76. package/dist/components/markdown/markdown-rules.js +81 -0
  77. package/dist/components/markdown/markdown-text.d.ts +17 -0
  78. package/dist/components/markdown/markdown-text.js +86 -0
  79. package/dist/components/markdown/parse-markdown.d.ts +4 -0
  80. package/dist/components/markdown/parse-markdown.js +21 -0
  81. package/dist/components/markdown/read-blocks.d.ts +8 -0
  82. package/dist/components/markdown/read-blocks.js +168 -0
  83. package/dist/components/markdown/read-inlines.d.ts +12 -0
  84. package/dist/components/markdown/read-inlines.js +126 -0
  85. package/dist/components/markdown/read-markdown.d.ts +9 -0
  86. package/dist/components/markdown/read-markdown.js +27 -0
  87. package/dist/components/markdown/reading-context.d.ts +18 -0
  88. package/dist/components/markdown/reading-context.js +35 -0
  89. package/dist/components/markdown/render-markdown.d.ts +10 -0
  90. package/dist/components/markdown/render-markdown.js +52 -0
  91. package/dist/components/slot.d.ts +9 -0
  92. package/dist/components/slot.js +13 -0
  93. package/dist/components/use-element-width.d.ts +11 -0
  94. package/dist/components/use-element-width.js +38 -0
  95. package/dist/css/css-action-menu-trigger.d.ts +6 -2
  96. package/dist/css/css-action-menu-trigger.js +50 -2
  97. package/dist/css/css-action-menu.js +4 -2
  98. package/dist/css/css-article.d.ts +2 -0
  99. package/dist/css/css-article.js +110 -0
  100. package/dist/css/css-components.js +15 -0
  101. package/dist/css/css-count-pill.js +14 -1
  102. package/dist/css/css-description-list.d.ts +1 -0
  103. package/dist/css/css-description-list.js +24 -0
  104. package/dist/css/css-document-viewer.d.ts +2 -0
  105. package/dist/css/css-document-viewer.js +186 -0
  106. package/dist/css/css-file-field.d.ts +2 -0
  107. package/dist/css/css-file-field.js +194 -0
  108. package/dist/css/css-filter-chip-tones.d.ts +3 -0
  109. package/dist/css/css-filter-chip-tones.js +28 -0
  110. package/dist/css/css-filter-chips-layout.d.ts +1 -0
  111. package/dist/css/css-filter-chips-layout.js +1 -1
  112. package/dist/css/css-filter-chips-row.d.ts +2 -0
  113. package/dist/css/css-filter-chips-row.js +79 -0
  114. package/dist/css/css-filter-chips.js +8 -0
  115. package/dist/css/css-font-mono.d.ts +3 -0
  116. package/dist/css/css-font-mono.js +23 -0
  117. package/dist/css/css-list-row-trailing.d.ts +2 -0
  118. package/dist/css/css-list-row-trailing.js +91 -0
  119. package/dist/css/css-list-row-truncate.d.ts +2 -0
  120. package/dist/css/css-list-row-truncate.js +29 -0
  121. package/dist/css/css-list.js +9 -1
  122. package/dist/css/css-markdown.d.ts +2 -0
  123. package/dist/css/css-markdown.js +144 -0
  124. package/dist/css/css-utilities.js +3 -0
  125. package/dist/css/css-variables.js +1 -0
  126. package/dist/css/spacing-classes.d.ts +1 -1
  127. package/dist/css/spacing-classes.js +1 -0
  128. package/dist/css/stylesheet.js +10 -0
  129. package/dist/document-viewer-worker.d.ts +9 -0
  130. package/dist/document-viewer-worker.js +9 -0
  131. package/dist/index.d.ts +11 -5
  132. package/dist/index.js +8 -3
  133. package/dist/list-options.d.ts +5 -2
  134. package/dist/list-options.js +7 -2
  135. package/dist/styles.css +649 -0
  136. 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', `![${token.content}](${attr(token, 'src') ?? ''})`);
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,2 @@
1
+ export declare function cssArticleClasses(): string;
2
+ export declare function articleClassCatalog(): string[];
@@ -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[];