@scalewing/react 1.40.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 +38 -0
- package/README.md +274 -7
- package/dist/calendar-labels.d.ts +2 -0
- package/dist/calendar-labels.js +7 -0
- package/dist/components/Article.d.ts +60 -0
- package/dist/components/Article.js +42 -0
- package/dist/components/BarChart.d.ts +20 -4
- package/dist/components/BarChart.js +13 -6
- package/dist/components/ButtonGroup.d.ts +8 -1
- package/dist/components/ButtonGroup.js +11 -2
- package/dist/components/DateField.d.ts +14 -2
- package/dist/components/DateField.js +14 -11
- package/dist/components/DenominationGrid.d.ts +3 -2
- package/dist/components/DenominationGrid.js +21 -3
- package/dist/components/Dialog.d.ts +29 -13
- package/dist/components/Dialog.js +22 -20
- package/dist/components/DocumentViewer.d.ts +41 -0
- package/dist/components/DocumentViewer.js +72 -0
- package/dist/components/Field.d.ts +13 -2
- package/dist/components/Field.js +14 -38
- package/dist/components/FileField.d.ts +44 -0
- package/dist/components/FileField.js +66 -0
- package/dist/components/FilterChips.d.ts +7 -37
- package/dist/components/FilterChips.js +32 -24
- package/dist/components/ListRow.d.ts +33 -3
- package/dist/components/ListRow.js +18 -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/SegmentedControl.d.ts +17 -1
- package/dist/components/SegmentedControl.js +11 -3
- package/dist/components/Switch.d.ts +16 -1
- package/dist/components/Switch.js +17 -6
- package/dist/components/action-bar/ActionBarSummary.d.ts +10 -2
- package/dist/components/action-bar/ActionBarSummary.js +11 -2
- package/dist/components/chart/use-chart-width.js +2 -24
- package/dist/components/date-field/CalendarEntryButton.d.ts +24 -0
- package/dist/components/date-field/CalendarEntryButton.js +16 -0
- package/dist/components/date-field/TypedEntry.d.ts +26 -0
- package/dist/components/date-field/TypedEntry.js +13 -0
- package/dist/components/denomination-grid/CountCell.d.ts +16 -0
- package/dist/components/denomination-grid/CountCell.js +14 -0
- package/dist/components/denomination-grid/DenominationStrip.d.ts +4 -1
- package/dist/components/denomination-grid/DenominationStrip.js +2 -2
- package/dist/components/denomination-grid/EntryCell.d.ts +3 -1
- package/dist/components/denomination-grid/EntryCell.js +2 -2
- package/dist/components/denomination-grid/StripRow.js +1 -10
- package/dist/components/denomination-grid/TransposedStrip.d.ts +17 -0
- package/dist/components/denomination-grid/TransposedStrip.js +43 -0
- package/dist/components/denomination-grid/types.d.ts +31 -0
- package/dist/components/denomination-grid/types.js +7 -1
- package/dist/components/denomination-grid/validate-options.d.ts +7 -0
- package/dist/components/denomination-grid/validate-options.js +27 -0
- package/dist/components/dialog/DialogFooter.d.ts +6 -5
- package/dist/components/dialog/DialogFooter.js +6 -6
- package/dist/components/dialog/validate.d.ts +24 -0
- package/dist/components/dialog/validate.js +23 -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/field/field-checks.d.ts +34 -0
- package/dist/components/field/field-checks.js +42 -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/filter-chips/FilterChip.d.ts +16 -0
- package/dist/components/filter-chips/FilterChip.js +20 -0
- package/dist/components/filter-chips/options.d.ts +75 -0
- package/dist/components/filter-chips/options.js +11 -0
- package/dist/components/filter-chips/validate.d.ts +9 -0
- package/dist/components/filter-chips/validate.js +48 -0
- package/dist/components/list/ListRowContent.d.ts +8 -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/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/chart-tones.d.ts +2 -0
- package/dist/css/chart-tones.js +5 -1
- package/dist/css/css-article.d.ts +2 -0
- package/dist/css/css-article.js +110 -0
- package/dist/css/css-bar-chart.d.ts +17 -2
- package/dist/css/css-bar-chart.js +38 -7
- package/dist/css/css-button-group-attached.d.ts +3 -0
- package/dist/css/css-button-group-attached.js +18 -1
- package/dist/css/css-canvas-grouped.d.ts +17 -0
- package/dist/css/css-canvas-grouped.js +40 -0
- package/dist/css/css-components.js +18 -0
- package/dist/css/css-count-pill.js +14 -1
- package/dist/css/css-date-field-calendar-entry.d.ts +11 -0
- package/dist/css/css-date-field-calendar-entry.js +50 -0
- package/dist/css/css-date-field.js +4 -0
- package/dist/css/css-denomination-grid.js +4 -0
- package/dist/css/css-denomination-transposed.d.ts +4 -0
- package/dist/css/css-denomination-transposed.js +178 -0
- package/dist/css/css-dialog-canvas.d.ts +3 -0
- package/dist/css/css-dialog-canvas.js +20 -0
- package/dist/css/css-dialog-frame.js +19 -2
- package/dist/css/css-dialog-grabber.d.ts +4 -0
- package/dist/css/css-dialog-grabber.js +56 -0
- package/dist/css/css-dialog.js +3 -2
- package/dist/css/css-document-viewer.d.ts +2 -0
- package/dist/css/css-document-viewer.js +186 -0
- package/dist/css/css-document.js +3 -0
- package/dist/css/css-field-width.d.ts +12 -0
- package/dist/css/css-field-width.js +49 -0
- package/dist/css/css-field.js +4 -0
- package/dist/css/css-file-field.d.ts +2 -0
- package/dist/css/css-file-field.js +180 -0
- package/dist/css/css-filter-chips-layout.d.ts +2 -0
- package/dist/css/css-filter-chips-layout.js +42 -0
- package/dist/css/css-filter-chips.js +4 -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.d.ts +6 -0
- package/dist/css/css-list-row.js +19 -3
- 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-segmented-sizing.d.ts +13 -0
- package/dist/css/css-segmented-sizing.js +66 -0
- package/dist/css/css-segmented.js +4 -0
- package/dist/css/css-switch.d.ts +6 -0
- package/dist/css/css-switch.js +39 -0
- package/dist/css/css-variables.js +1 -0
- package/dist/css/stacking.d.ts +1 -0
- package/dist/css/stacking.js +4 -1
- package/dist/css/stylesheet.js +12 -0
- package/dist/date-field-labels.d.ts +5 -0
- package/dist/date-field-labels.js +1 -0
- package/dist/document-viewer-worker.d.ts +9 -0
- package/dist/document-viewer-worker.js +9 -0
- package/dist/index.d.ts +15 -9
- package/dist/index.js +13 -7
- package/dist/list-options.d.ts +11 -7
- package/dist/list-options.js +20 -8
- package/dist/styles.css +889 -44
- package/dist/theme/ThemeProvider.d.ts +11 -1
- package/dist/theme/ThemeProvider.js +4 -2
- package/package.json +10 -2
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useRef, useState, } from 'react';
|
|
4
|
+
import { Button } from './Button.js';
|
|
5
|
+
import { Spinner } from './Spinner.js';
|
|
6
|
+
import { Text } from './Text.js';
|
|
7
|
+
import { assertDocumentViewer, shownPage, } from './document-viewer/document-viewer-labels.js';
|
|
8
|
+
import { pageBoxStyle } from './document-viewer/page-box.js';
|
|
9
|
+
import { useErrorReport } from './document-viewer/use-error-report.js';
|
|
10
|
+
import { usePageRender } from './document-viewer/use-page-render.js';
|
|
11
|
+
import { usePdfDocument } from './document-viewer/use-pdf-document.js';
|
|
12
|
+
import { usePinchZoom } from './document-viewer/use-pinch-zoom.js';
|
|
13
|
+
import { useZoom } from './document-viewer/use-zoom.js';
|
|
14
|
+
import { zoomForKey } from './document-viewer/zoom.js';
|
|
15
|
+
import { useElementWidth } from './use-element-width.js';
|
|
16
|
+
/**
|
|
17
|
+
* A PDF drawn one page at a time from its bytes with PDF.js, fitted to the
|
|
18
|
+
* width it is given, with previous and next steps and "Page n of m" in a
|
|
19
|
+
* polite live region. Two fingers, or `+`, `-` and `0` on the focused
|
|
20
|
+
* page, zoom it. The page's text sits over the drawing, transparent, so it
|
|
21
|
+
* can be read by assistive technology and selected. It opens nothing from
|
|
22
|
+
* the network beyond the worker, and has no download or print of its own.
|
|
23
|
+
*/
|
|
24
|
+
export function DocumentViewer({ data, workerSrc, label, labels, page: pageProp, onPageChange, failedAction, onLoad, onError, }) {
|
|
25
|
+
assertDocumentViewer({ label, labels, workerSrc });
|
|
26
|
+
const statusId = useId();
|
|
27
|
+
// The viewer's own page belongs to its bytes: new bytes start at page 1.
|
|
28
|
+
const [own, setOwn] = useState({ data, page: 1 });
|
|
29
|
+
const ownPage = own.data === data ? own.page : 1;
|
|
30
|
+
const canvas = useRef(null);
|
|
31
|
+
const text = useRef(null);
|
|
32
|
+
const opened = usePdfDocument(data, workerSrc, { onLoad, onError });
|
|
33
|
+
const reportError = useErrorReport(onError);
|
|
34
|
+
const document = opened.status === 'ready' ? opened.document : null;
|
|
35
|
+
const pageCount = document?.pageCount ?? 0;
|
|
36
|
+
const page = shownPage(pageProp ?? ownPage, pageCount);
|
|
37
|
+
const { box: stage, page: sheet, zoom, drawnZoom, zoomTo, } = useZoom(`${page}`);
|
|
38
|
+
const fitWidth = useElementWidth(stage, 0, { content: true });
|
|
39
|
+
usePinchZoom(stage, zoom, zoomTo);
|
|
40
|
+
const drawn = usePageRender({
|
|
41
|
+
document,
|
|
42
|
+
page,
|
|
43
|
+
width: Math.round(fitWidth * drawnZoom),
|
|
44
|
+
canvas,
|
|
45
|
+
text,
|
|
46
|
+
onError: reportError,
|
|
47
|
+
});
|
|
48
|
+
// A page that cannot be drawn keeps the steps, so another can be read.
|
|
49
|
+
const failed = opened.status === 'failed' || drawn.state === 'failed';
|
|
50
|
+
const open = document !== null;
|
|
51
|
+
function go(target) {
|
|
52
|
+
if (target < 1 || target > pageCount || target === page)
|
|
53
|
+
return;
|
|
54
|
+
setOwn({ data, page: target });
|
|
55
|
+
onPageChange?.(target);
|
|
56
|
+
}
|
|
57
|
+
function onKeyDown(event) {
|
|
58
|
+
if (event.altKey || event.ctrlKey || event.metaKey)
|
|
59
|
+
return;
|
|
60
|
+
const next = zoomForKey(event.key, zoom);
|
|
61
|
+
if (next === null)
|
|
62
|
+
return;
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
zoomTo(next);
|
|
65
|
+
}
|
|
66
|
+
const step = (direction) => {
|
|
67
|
+
const target = direction === 'previous' ? page - 1 : page + 1;
|
|
68
|
+
const off = target < 1 || target > pageCount;
|
|
69
|
+
return (_jsx(Button, { "aria-disabled": off || undefined, "aria-label": labels[direction], className: "sw-document-viewer-step", disabledAppearance: "neutral", onPress: () => go(target), variant: "neutral", children: _jsx("span", { "aria-hidden": "true", className: `sw-pagination-glyph sw-pagination-glyph-${direction}` }) }));
|
|
70
|
+
};
|
|
71
|
+
return (_jsxs("section", { "aria-label": label, className: "sw-document-viewer", children: [_jsxs("div", { "aria-labelledby": open && !failed ? statusId : undefined, className: "sw-document-viewer-stage", onKeyDown: open && !failed ? onKeyDown : undefined, ref: stage, role: open && !failed ? 'group' : undefined, tabIndex: open && !failed ? 0 : undefined, children: [failed ? (_jsxs("div", { className: "sw-document-viewer-message", role: "alert", children: [_jsx(Text, { as: "p", variant: "title", children: labels.failed }), labels.failedDetail ? (_jsx(Text, { as: "p", children: labels.failedDetail })) : null, failedAction ? (_jsx("div", { className: "sw-document-viewer-actions", children: failedAction })) : null] })) : null, opened.status === 'loading' ? (_jsxs("div", { className: "sw-document-viewer-message", children: [_jsx(Spinner, { label: labels.loading }), _jsx(Text, { "aria-hidden": true, as: "p", children: labels.loading })] })) : null, open ? (_jsxs("div", { className: "sw-document-viewer-page", "data-zoomed": zoom > 1 || undefined, hidden: failed, ref: sheet, style: pageBoxStyle(fitWidth * zoom, drawn.aspect), children: [_jsx("canvas", { "aria-hidden": "true", ref: canvas }), _jsx("div", { className: "sw-document-viewer-text", ref: text })] })) : null] }), open ? (_jsxs("div", { className: "sw-document-viewer-bar", children: [pageCount > 1 ? step('previous') : null, _jsx("span", { "aria-atomic": "true", "aria-live": "polite", className: "sw-document-viewer-status", id: statusId, children: labels.page(page, pageCount) }), pageCount > 1 ? step('next') : null] })) : null] }));
|
|
72
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type SpacingStep } from '@scalewing/tokens';
|
|
2
2
|
import { type ReactNode } from 'react';
|
|
3
3
|
import { type RequiredMark } from './FieldLabelText.js';
|
|
4
|
+
import { type FieldWidth } from '../css/css-field-width.js';
|
|
5
|
+
import { fieldVariants, type FieldVariant } from './field/field-checks.js';
|
|
4
6
|
export type FieldSize = 'xs' | 'md';
|
|
5
|
-
export
|
|
6
|
-
export type FieldVariant = (typeof fieldVariants)[number];
|
|
7
|
+
export { fieldVariants, type FieldVariant };
|
|
7
8
|
export type FieldProps = {
|
|
8
9
|
children: ReactNode;
|
|
9
10
|
gap?: SpacingStep;
|
|
@@ -60,5 +61,15 @@ export type FieldProps = {
|
|
|
60
61
|
clearLabel?: string;
|
|
61
62
|
/** Called by a search field's clear button; the consumer empties the value. */
|
|
62
63
|
onClear?: () => void;
|
|
64
|
+
/**
|
|
65
|
+
* Caps the control's width at a Table column size (`xs` 4rem, `sm` 6rem,
|
|
66
|
+
* `md` 8rem, `lg` 12rem, `xl` 16rem), at the start of the column and
|
|
67
|
+
* never wider than it, so a short value such as a rate or a count does
|
|
68
|
+
* not stretch across a form. The label and messages keep the column's
|
|
69
|
+
* width. Left out, the control fills the column. It needs one native
|
|
70
|
+
* text input, select or textarea child (a `TypeError` otherwise); an
|
|
71
|
+
* unknown width throws a `RangeError`.
|
|
72
|
+
*/
|
|
73
|
+
width?: FieldWidth;
|
|
63
74
|
};
|
|
64
75
|
export declare function Field(props: FieldProps): import("react").JSX.Element;
|
package/dist/components/Field.js
CHANGED
|
@@ -4,45 +4,14 @@ import { cloneElement, isValidElement, useId, } from 'react';
|
|
|
4
4
|
import { adornedControlAria, FieldAdornment, } from './FieldAdornment.js';
|
|
5
5
|
import { FieldErrorRegion } from './FieldErrorRegion.js';
|
|
6
6
|
import { assertRequiredMark, FieldLabelText, } from './FieldLabelText.js';
|
|
7
|
+
import { fieldWidthClass } from '../css/css-field-width.js';
|
|
7
8
|
import { SearchFrame } from './field/SearchFrame.js';
|
|
9
|
+
import { assertFieldProps, fieldVariants, passesInvalid, } from './field/field-checks.js';
|
|
8
10
|
import { Stack } from './Stack.js';
|
|
9
|
-
export
|
|
10
|
-
function assertSearch(props, child) {
|
|
11
|
-
const { variant = 'outlined' } = props;
|
|
12
|
-
if (!fieldVariants.includes(variant))
|
|
13
|
-
throw new RangeError(`Unknown Field variant: ${String(variant)}`);
|
|
14
|
-
if (variant !== 'search') {
|
|
15
|
-
if (props.leading !== undefined ||
|
|
16
|
-
props.clearLabel !== undefined ||
|
|
17
|
-
props.onClear !== undefined)
|
|
18
|
-
throw new TypeError('Field leading, clearLabel and onClear are for variant="search"');
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
if (!props.clearLabel?.trim())
|
|
22
|
-
throw new RangeError('A search Field needs a nonempty clearLabel');
|
|
23
|
-
if (typeof props.onClear !== 'function')
|
|
24
|
-
throw new TypeError('A search Field needs onClear');
|
|
25
|
-
if (props.prefix || props.suffix)
|
|
26
|
-
throw new TypeError('A search Field takes leading, not prefix or suffix');
|
|
27
|
-
if (!isValidElement(child) || child.type !== 'input')
|
|
28
|
-
throw new TypeError('A search Field needs one native input child');
|
|
29
|
-
if (child.props.value === undefined)
|
|
30
|
-
throw new TypeError('A search Field needs a controlled input: pass value (and onChange)');
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* `invalid` was passed, even as `false`, but not as `undefined` (a wrapper
|
|
34
|
-
* forwarding an optional prop passes nothing): such a field needs one native
|
|
35
|
-
* control from its first render, so a form learns it on the happy path
|
|
36
|
-
* instead of on its first failed submit. `error` keeps its older rule (it
|
|
37
|
-
* requires the control only while it holds a message), so a composed
|
|
38
|
-
* child beside `error={undefined}` still renders.
|
|
39
|
-
*/
|
|
40
|
-
function passesInvalid(props) {
|
|
41
|
-
return props.invalid !== undefined || props.changed !== undefined;
|
|
42
|
-
}
|
|
11
|
+
export { fieldVariants };
|
|
43
12
|
export function Field(props) {
|
|
44
|
-
const { children, gap = 1, label, labelVisuallyHidden = false, size = 'md', description, error, invalid = false, changed = false, required = false, requiredMark = 'visible', prefix, suffix, variant = 'outlined', leading, clearLabel = '', onClear, } = props;
|
|
45
|
-
|
|
13
|
+
const { children, gap = 1, label, labelVisuallyHidden = false, size = 'md', description, error, invalid = false, changed = false, required = false, requiredMark = 'visible', prefix, suffix, variant = 'outlined', leading, clearLabel = '', onClear, width, } = props;
|
|
14
|
+
assertFieldProps(props, children);
|
|
46
15
|
const search = variant === 'search';
|
|
47
16
|
const controlId = useId();
|
|
48
17
|
const descriptionId = useId();
|
|
@@ -54,13 +23,20 @@ export function Field(props) {
|
|
|
54
23
|
const message = error || description;
|
|
55
24
|
const marksInvalid = Boolean(error) || invalid;
|
|
56
25
|
const adorned = Boolean(prefix || suffix);
|
|
57
|
-
const needsControl = Boolean(message ||
|
|
26
|
+
const needsControl = Boolean(message ||
|
|
27
|
+
marksInvalid ||
|
|
28
|
+
passesInvalid(props) ||
|
|
29
|
+
required ||
|
|
30
|
+
adorned ||
|
|
31
|
+
Boolean(width));
|
|
58
32
|
const className = [
|
|
59
33
|
'sw-field',
|
|
60
34
|
size === 'xs' && 'sw-field-xs',
|
|
61
35
|
marksInvalid && 'sw-field-invalid',
|
|
62
36
|
search && 'sw-field-search-field',
|
|
63
37
|
changed && !marksInvalid && 'sw-field-changed',
|
|
38
|
+
width && 'sw-field-sized',
|
|
39
|
+
width && fieldWidthClass(width),
|
|
64
40
|
]
|
|
65
41
|
.filter(Boolean)
|
|
66
42
|
.join(' ');
|
|
@@ -71,7 +47,7 @@ export function Field(props) {
|
|
|
71
47
|
typeof children.type !== 'string' ||
|
|
72
48
|
!['input', 'select', 'textarea'].includes(children.type)) {
|
|
73
49
|
if (needsControl)
|
|
74
|
-
throw new TypeError('Field validation and
|
|
50
|
+
throw new TypeError('Field validation, adornments and width require one native input, select, or textarea child');
|
|
75
51
|
return (_jsxs(Stack, { as: "label", className: className, gap: labelHidden ? 0 : gap, children: [labelText, children] }));
|
|
76
52
|
}
|
|
77
53
|
if (adorned && children.type !== 'input')
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/** The chosen file as the field shows it: its name and a line about it. */
|
|
3
|
+
export type FileFieldValue = {
|
|
4
|
+
name: string;
|
|
5
|
+
/** A muted line under the name, such as "4 pages · 1.6 MB". */
|
|
6
|
+
detail?: string;
|
|
7
|
+
};
|
|
8
|
+
export type FileFieldProps = {
|
|
9
|
+
/** The field's label, drawn above it as Field's, and part of its button's name. */
|
|
10
|
+
label: string;
|
|
11
|
+
labelVisuallyHidden?: boolean;
|
|
12
|
+
/** A muted hint under the field, such as "Up to 25 MB". */
|
|
13
|
+
description?: string;
|
|
14
|
+
/** Replaces the hint with an error, in a polite live region. */
|
|
15
|
+
error?: string;
|
|
16
|
+
/**
|
|
17
|
+
* The file types the picker offers, as the native `accept` attribute
|
|
18
|
+
* (".pdf,application/pdf"). A dropped file is not filtered by it: check
|
|
19
|
+
* what `onChange` receives and say why in `error`.
|
|
20
|
+
*/
|
|
21
|
+
accept?: string;
|
|
22
|
+
/** The chosen file to show, or nothing chosen (`null` or left out). */
|
|
23
|
+
value?: FileFieldValue | null;
|
|
24
|
+
/** Called with the file the person chose or dropped. Nothing is uploaded. */
|
|
25
|
+
onChange: (file: File) => void;
|
|
26
|
+
/** The empty field's words, such as "Choose a PDF". */
|
|
27
|
+
chooseLabel: string;
|
|
28
|
+
/** The button beside a chosen file, such as "Choose another". */
|
|
29
|
+
changeLabel: string;
|
|
30
|
+
/** Muted words after `chooseLabel` for a fine pointer, such as "or drop it here". */
|
|
31
|
+
dropLabel?: string;
|
|
32
|
+
/** A consumer mark before the chosen file's name (Lucide), hidden from assistive technology. */
|
|
33
|
+
icon?: ReactNode;
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
/** Prefixes the ids the field gives its parts (its button is `${id}-button`). */
|
|
36
|
+
id?: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* One file chosen by a press or dropped on the field: empty, a wide zone
|
|
40
|
+
* that opens the system picker; chosen, the file's name and detail with a
|
|
41
|
+
* button to choose another. It hands the `File` to the consumer and shows
|
|
42
|
+
* what the consumer passes back as `value`; it never reads or uploads it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function FileField({ label, labelVisuallyHidden, description, error, accept, value, onChange, chooseLabel, changeLabel, dropLabel, icon, disabled, id, }: FileFieldProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useRef } from 'react';
|
|
4
|
+
import { cx } from '../class-names.js';
|
|
5
|
+
import { tabStop } from '../tab-stop.js';
|
|
6
|
+
import { Button } from './Button.js';
|
|
7
|
+
import { FieldErrorRegion } from './FieldErrorRegion.js';
|
|
8
|
+
import { FieldLabelText } from './FieldLabelText.js';
|
|
9
|
+
import { assertFileField } from './file-field/file-field-checks.js';
|
|
10
|
+
import { useFileDrop } from './file-field/use-file-drop.js';
|
|
11
|
+
import { useSwapFocus } from './file-field/use-swap-focus.js';
|
|
12
|
+
import { Stack } from './Stack.js';
|
|
13
|
+
/** Whether the consumer passed a mark to draw (not empty JSX). */
|
|
14
|
+
function hasMark(icon) {
|
|
15
|
+
return (icon !== undefined &&
|
|
16
|
+
icon !== null &&
|
|
17
|
+
icon !== '' &&
|
|
18
|
+
typeof icon !== 'boolean');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* One file chosen by a press or dropped on the field: empty, a wide zone
|
|
22
|
+
* that opens the system picker; chosen, the file's name and detail with a
|
|
23
|
+
* button to choose another. It hands the `File` to the consumer and shows
|
|
24
|
+
* what the consumer passes back as `value`; it never reads or uploads it.
|
|
25
|
+
*/
|
|
26
|
+
export function FileField({ label, labelVisuallyHidden = false, description, error, accept, value = null, onChange, chooseLabel, changeLabel, dropLabel, icon, disabled = false, id, }) {
|
|
27
|
+
assertFileField({ label, chooseLabel, changeLabel, dropLabel, value });
|
|
28
|
+
const ownId = useId();
|
|
29
|
+
const baseId = id ?? ownId;
|
|
30
|
+
const inputRef = useRef(null);
|
|
31
|
+
const buttonRef = useRef(null);
|
|
32
|
+
const chosen = value !== null;
|
|
33
|
+
const drop = useFileDrop({ disabled, onFile: onChange });
|
|
34
|
+
const focusHandlers = useSwapFocus(buttonRef, chosen);
|
|
35
|
+
function onInput(event) {
|
|
36
|
+
const file = event.currentTarget.files?.[0];
|
|
37
|
+
// Cleared, so choosing the same file again is a change too.
|
|
38
|
+
event.currentTarget.value = '';
|
|
39
|
+
if (file)
|
|
40
|
+
onChange(file);
|
|
41
|
+
}
|
|
42
|
+
const ids = {
|
|
43
|
+
label: `${baseId}-label`,
|
|
44
|
+
button: `${baseId}-button`,
|
|
45
|
+
name: `${baseId}-name`,
|
|
46
|
+
detail: `${baseId}-detail`,
|
|
47
|
+
hint: `${baseId}-hint`,
|
|
48
|
+
error: `${baseId}-error`,
|
|
49
|
+
};
|
|
50
|
+
// The error is in the button's description and the polite region; the
|
|
51
|
+
// button role takes no aria-invalid.
|
|
52
|
+
const naming = {
|
|
53
|
+
'aria-labelledby': `${ids.label} ${ids.button}`,
|
|
54
|
+
'aria-describedby': [
|
|
55
|
+
chosen && ids.name,
|
|
56
|
+
chosen && value.detail && ids.detail,
|
|
57
|
+
description && !error && ids.hint,
|
|
58
|
+
error && ids.error,
|
|
59
|
+
]
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.join(' ') || undefined,
|
|
62
|
+
id: ids.button,
|
|
63
|
+
};
|
|
64
|
+
const open = () => inputRef.current?.click();
|
|
65
|
+
return (_jsxs(Stack, { className: cx('sw-field', 'sw-file-field', error && 'sw-field-invalid'), gap: labelVisuallyHidden ? 0 : 1, children: [_jsx("span", { id: ids.label, children: _jsx(FieldLabelText, { label: label, visuallyHidden: labelVisuallyHidden }) }), _jsxs("div", { className: cx('sw-file-field-frame', chosen ? 'sw-file-field-chosen' : 'sw-file-field-empty', drop.dragging && 'sw-file-field-dragging', disabled && 'sw-file-field-disabled'), ...drop.handlers, ...focusHandlers, children: [_jsx("input", { accept: accept, "aria-label": label, disabled: disabled, hidden: true, onChange: onInput, ref: inputRef, tabIndex: -1, type: "file" }), chosen ? (_jsxs(_Fragment, { children: [!hasMark(icon) ? null : (_jsx("span", { "aria-hidden": "true", className: "sw-file-field-icon", children: icon })), _jsxs("span", { className: "sw-file-field-file", children: [_jsx("span", { className: "sw-file-field-name", id: ids.name, children: value.name }), value.detail ? (_jsx("span", { className: "sw-file-field-detail", id: ids.detail, children: value.detail })) : null] }), _jsx(Button, { ...naming, disabled: disabled, disabledAppearance: "neutral", onPress: open, ref: buttonRef, variant: "neutral", children: changeLabel })] })) : (_jsxs("button", { ...naming, className: "sw-file-field-zone", disabled: disabled, onClick: open, ref: buttonRef, tabIndex: tabStop, type: "button", children: [_jsx("span", { className: "sw-file-field-choose", children: chooseLabel }), dropLabel ? ' ' : null, dropLabel ? (_jsx("span", { className: "sw-file-field-drop", children: dropLabel })) : null] }))] }), description && !error ? (_jsx("span", { className: "sw-field-description", id: ids.hint, children: description })) : null, _jsx(FieldErrorRegion, { className: "sw-field-error", id: ids.error, message: error })] }));
|
|
66
|
+
}
|
|
@@ -1,37 +1,7 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
export type FilterChipOption
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export type FilterChipsProps = {
|
|
9
|
-
label: string;
|
|
10
|
-
/** The chosen option's value, or `''` while nothing is chosen yet. */
|
|
11
|
-
value: string;
|
|
12
|
-
onChange: (value: string) => void;
|
|
13
|
-
options: readonly FilterChipOption[];
|
|
14
|
-
/**
|
|
15
|
-
* A choice must be made, such as a reason: each radio is `required` and
|
|
16
|
-
* the label shows the required mark (hidden from assistive technology,
|
|
17
|
-
* which reads the radios' own state).
|
|
18
|
-
*/
|
|
19
|
-
required?: boolean;
|
|
20
|
-
/**
|
|
21
|
-
* `hidden` leaves out the label's " *" while the control stays required
|
|
22
|
-
* (and announced so); `visible` (default) draws it.
|
|
23
|
-
* When hidden, say in visible text which fields must be filled; a
|
|
24
|
-
* placeholder alone is not enough.
|
|
25
|
-
*/
|
|
26
|
-
requiredMark?: RequiredMark;
|
|
27
|
-
/**
|
|
28
|
-
* A message under the chips, such as "Pick a reason": a polite live
|
|
29
|
-
* region linked to the group by `aria-describedby`; the group is marked
|
|
30
|
-
* invalid and every chip takes the danger edge. An empty string is no
|
|
31
|
-
* error.
|
|
32
|
-
*/
|
|
33
|
-
error?: string;
|
|
34
|
-
/** More ids that describe the group, such as a hint beside it. */
|
|
35
|
-
'aria-describedby'?: string;
|
|
36
|
-
};
|
|
37
|
-
export declare function FilterChips({ label, value, onChange, options, required, requiredMark, error, 'aria-describedby': describedByProp, }: FilterChipsProps): import("react").JSX.Element;
|
|
1
|
+
import { type FilterChipsProps } from './filter-chips/options.js';
|
|
2
|
+
export { filterChipCountAppearances, filterChipsOverflows, type FilterChipCountAppearance, type FilterChipOption, type FilterChipsOverflow, type FilterChipsProps, } from './filter-chips/options.js';
|
|
3
|
+
/**
|
|
4
|
+
* A labelled row of chips: one choice (radios), or several at once
|
|
5
|
+
* (`multiple`, checkboxes), each chip optionally counted.
|
|
6
|
+
*/
|
|
7
|
+
export declare function FilterChips(props: FilterChipsProps): import("react").JSX.Element;
|
|
@@ -1,35 +1,43 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsxs as _jsxs, jsx as _jsx
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from 'react';
|
|
4
4
|
import { cx } from '../class-names.js';
|
|
5
|
+
import { radioTabStopValue } from '../tab-stop.js';
|
|
5
6
|
import { FieldErrorRegion } from './FieldErrorRegion.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import { showsRequiredMark } from './FieldLabelText.js';
|
|
8
|
+
import { FilterChip } from './filter-chips/FilterChip.js';
|
|
9
|
+
import { assertFilterChips } from './filter-chips/validate.js';
|
|
10
|
+
export { filterChipCountAppearances, filterChipsOverflows, } from './filter-chips/options.js';
|
|
11
|
+
/** The values a change leaves on, in the options' order. */
|
|
12
|
+
function toggled(options, chosen, value, checked) {
|
|
13
|
+
return options
|
|
14
|
+
.map((option) => option.value)
|
|
15
|
+
.filter((each) => (each === value ? checked : chosen.includes(each)));
|
|
10
16
|
}
|
|
11
|
-
|
|
17
|
+
/**
|
|
18
|
+
* A labelled row of chips: one choice (radios), or several at once
|
|
19
|
+
* (`multiple`, checkboxes), each chip optionally counted.
|
|
20
|
+
*/
|
|
21
|
+
export function FilterChips(props) {
|
|
22
|
+
const { label, options, labelVisuallyHidden = false, requiredMark = 'visible', countAppearance = 'figure', overflow = 'wrap', error, 'aria-describedby': describedByProp, } = props;
|
|
12
23
|
const name = useId();
|
|
13
24
|
const errorId = useId();
|
|
25
|
+
assertFilterChips(props);
|
|
26
|
+
const required = props.multiple ? false : (props.required ?? false);
|
|
14
27
|
const describedBy = [describedByProp?.trim() || null, error ? errorId : null]
|
|
15
28
|
.filter((id) => id !== null)
|
|
16
29
|
.join(' ');
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const entry = radioTabStopValue(options, value);
|
|
31
|
-
return (_jsxs("fieldset", { "aria-describedby": describedBy || undefined, "aria-invalid": error ? true : undefined, className: "sw-filter-chips", children: [_jsxs("legend", { className: "sw-filter-chips-legend", children: [label, showsRequiredMark(required, requiredMark) ? (_jsxs("span", { "aria-hidden": "true", className: "sw-field-required", children: [' ', "*"] })) : null] }), _jsx("span", { className: "sw-filter-chips-options", children: options.map((option) => (_jsxs("label", { className: cx('sw-filter-chip', option.count === 0 && 'sw-filter-chip-quiet'), children: [_jsx("input", { checked: option.value === value, className: "sw-filter-chip-input", disabled: option.disabled, name: name, onChange: (event) => {
|
|
32
|
-
if (event.currentTarget.checked && !option.disabled)
|
|
33
|
-
onChange(option.value);
|
|
34
|
-
}, required: required, tabIndex: option.value === entry ? tabStop : undefined, type: "radio", value: option.value }), _jsxs("span", { className: "sw-filter-chip-face", children: [option.label, option.count === undefined ? null : (_jsxs(_Fragment, { children: [' ', _jsx("span", { className: "sw-filter-chip-count", children: option.count })] }))] })] }, option.value))) }), _jsx(FieldErrorRegion, { className: "sw-filter-chips-error", id: errorId, message: error })] }));
|
|
30
|
+
const entry = props.multiple
|
|
31
|
+
? undefined
|
|
32
|
+
: radioTabStopValue(options, props.value);
|
|
33
|
+
function isChecked(value) {
|
|
34
|
+
return props.multiple ? props.value.includes(value) : props.value === value;
|
|
35
|
+
}
|
|
36
|
+
function change(value, checked) {
|
|
37
|
+
if (props.multiple)
|
|
38
|
+
props.onChange(toggled(options, props.value, value, checked));
|
|
39
|
+
else if (checked)
|
|
40
|
+
props.onChange(value);
|
|
41
|
+
}
|
|
42
|
+
return (_jsxs("fieldset", { "aria-describedby": describedBy || undefined, "aria-invalid": error ? true : undefined, className: cx('sw-filter-chips', overflow === 'scroll' && 'sw-filter-chips-scroll'), children: [_jsxs("legend", { className: cx('sw-filter-chips-legend', labelVisuallyHidden && 'sw-sr-only'), children: [label, showsRequiredMark(required, requiredMark) ? (_jsxs("span", { "aria-hidden": "true", className: "sw-field-required", children: [' ', "*"] })) : null] }), _jsx("span", { className: "sw-filter-chips-options", children: options.map((option) => (_jsx(FilterChip, { checked: isChecked(option.value), countAppearance: countAppearance, name: name, onCheckedChange: (checked) => change(option.value, checked), option: option, required: required, tabStopHere: props.multiple ? true : option.value === entry, type: props.multiple ? 'checkbox' : 'radio' }, option.value))) }), _jsx(FieldErrorRegion, { className: "sw-filter-chips-error", id: errorId, message: error })] }));
|
|
35
43
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
+
import { type BadgeTone } from '../css/css-data.js';
|
|
2
3
|
import { type ListRowTitleAppearance, type ListRowTone, type ListRowValueTone } from '../list-options.js';
|
|
3
4
|
export type ListRowProps = {
|
|
4
5
|
title: string;
|
|
@@ -8,8 +9,16 @@ export type ListRowProps = {
|
|
|
8
9
|
value?: string;
|
|
9
10
|
/** A muted line under the value, such as "checks $3,427". */
|
|
10
11
|
valueDetail?: string;
|
|
11
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Colours the value only, as a table cell's tone, or `warning` for a
|
|
14
|
+
* figure that needs a look; the words say why.
|
|
15
|
+
*/
|
|
12
16
|
valueTone?: ListRowValueTone;
|
|
17
|
+
/**
|
|
18
|
+
* Draws the value as a `Badge` in this tone, such as a status ("Open",
|
|
19
|
+
* "Blocked") at the row's end. Not with `valueTone`.
|
|
20
|
+
*/
|
|
21
|
+
valueBadge?: BadgeTone;
|
|
13
22
|
/**
|
|
14
23
|
* A consumer mark at the row's start, such as a status dot or a Lucide
|
|
15
24
|
* glyph (ADR 0008). It is hidden from assistive technology, so the words
|
|
@@ -35,6 +44,26 @@ export type ListRowProps = {
|
|
|
35
44
|
tone?: ListRowTone;
|
|
36
45
|
/** `plain` sets the title at the body weight, for a list of plain statements. */
|
|
37
46
|
titleAppearance?: ListRowTitleAppearance;
|
|
47
|
+
/**
|
|
48
|
+
* Marks the row as new for the person, such as a document they have not
|
|
49
|
+
* opened: an accent dot after the title, read as `unreadLabel`.
|
|
50
|
+
*/
|
|
51
|
+
unread?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* What the dot means in the consumer's words ("New"), read after the
|
|
54
|
+
* title (in a pressable row's name). Required while `unread`.
|
|
55
|
+
*/
|
|
56
|
+
unreadLabel?: string;
|
|
57
|
+
/**
|
|
58
|
+
* A short status before the accessory, such as a `Badge` ("Only in
|
|
59
|
+
* English"); a pressable row is described by it.
|
|
60
|
+
*/
|
|
61
|
+
tag?: ReactNode;
|
|
62
|
+
/**
|
|
63
|
+
* A control after the row and outside its press area, such as an
|
|
64
|
+
* `ActionMenu` of the row's own actions. It needs its own name.
|
|
65
|
+
*/
|
|
66
|
+
action?: ReactNode;
|
|
38
67
|
/** `none` hides the chevron of a pressable row that acts in place. */
|
|
39
68
|
accessory?: 'chevron' | 'none';
|
|
40
69
|
/**
|
|
@@ -46,6 +75,7 @@ export type ListRowProps = {
|
|
|
46
75
|
/**
|
|
47
76
|
* One row of a `ListGroup`: it opens something (`onPress` or `href`, with
|
|
48
77
|
* a chevron, the whole row pressing), picks one option in place
|
|
49
|
-
* (`selected`), or only states something (neither).
|
|
78
|
+
* (`selected`), or only states something (neither). `unread` marks it new,
|
|
79
|
+
* `tag` adds a status before its end, and `action` a control beside it.
|
|
50
80
|
*/
|
|
51
|
-
export declare function ListRow({ title, detail, value, valueDetail, valueTone, leading, onPress, href, selected, disabled, tone, titleAppearance, accessory, 'aria-label': ariaLabel, }: ListRowProps): import("react").JSX.Element;
|
|
81
|
+
export declare function ListRow({ title, detail, value, valueDetail, valueTone, valueBadge, leading, onPress, href, selected, disabled, tone, titleAppearance, accessory, unread, unreadLabel, tag, action, 'aria-label': ariaLabel, }: ListRowProps): import("react").JSX.Element;
|
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { useId } from 'react';
|
|
4
4
|
import { cx } from '../class-names.js';
|
|
5
5
|
import { assertListRow, listRowAccessory, } from '../list-options.js';
|
|
6
6
|
import { tabStop } from '../tab-stop.js';
|
|
7
7
|
import { plainPress } from './link-press.js';
|
|
8
8
|
import { ListRowContent } from './list/ListRowContent.js';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
const joined = parts.filter(Boolean).join(' ');
|
|
12
|
-
return joined === '' ? undefined : joined;
|
|
13
|
-
}
|
|
9
|
+
import { ListRowItem } from './list/ListRowItem.js';
|
|
10
|
+
import { rowNaming } from './list/row-naming.js';
|
|
14
11
|
/**
|
|
15
12
|
* One row of a `ListGroup`: it opens something (`onPress` or `href`, with
|
|
16
13
|
* a chevron, the whole row pressing), picks one option in place
|
|
17
|
-
* (`selected`), or only states something (neither).
|
|
14
|
+
* (`selected`), or only states something (neither). `unread` marks it new,
|
|
15
|
+
* `tag` adds a status before its end, and `action` a control beside it.
|
|
18
16
|
*/
|
|
19
|
-
export function ListRow({ title, detail, value, valueDetail, valueTone, leading, onPress, href, selected, disabled = false, tone = 'default', titleAppearance = 'strong', accessory = 'chevron', 'aria-label': ariaLabel, }) {
|
|
17
|
+
export function ListRow({ title, detail, value, valueDetail, valueTone, valueBadge, leading, onPress, href, selected, disabled = false, tone = 'default', titleAppearance = 'strong', accessory = 'chevron', unread = false, unreadLabel, tag, action, 'aria-label': ariaLabel, }) {
|
|
20
18
|
const pressable = onPress !== undefined || href !== undefined;
|
|
21
19
|
assertListRow({
|
|
22
20
|
title,
|
|
@@ -26,21 +24,24 @@ export function ListRow({ title, detail, value, valueDetail, valueTone, leading,
|
|
|
26
24
|
accessory,
|
|
27
25
|
value,
|
|
28
26
|
valueDetail,
|
|
27
|
+
valueBadge,
|
|
29
28
|
selected,
|
|
30
29
|
pressable,
|
|
30
|
+
unread,
|
|
31
|
+
unreadLabel,
|
|
31
32
|
});
|
|
32
|
-
const container = useContext(ListRowContainer);
|
|
33
33
|
const id = useId();
|
|
34
34
|
const kind = listRowAccessory({ pressable, selected, accessory });
|
|
35
|
-
const content = (_jsx(ListRowContent, { accessory: kind, detail: detail, id: id, leading: leading, title: title, titleAppearance: titleAppearance, tone: tone, value: value, valueDetail: valueDetail, valueTone: valueTone }));
|
|
35
|
+
const content = (_jsx(ListRowContent, { accessory: kind, detail: detail, id: id, leading: leading, tag: tag, title: title, titleAppearance: titleAppearance, tone: tone, unreadLabel: unread ? unreadLabel : undefined, value: value, valueBadge: valueBadge, valueDetail: valueDetail, valueTone: valueTone }));
|
|
36
36
|
const className = cx('sw-list-row', pressable && 'sw-list-row-pressable', disabled && 'sw-list-row-disabled');
|
|
37
|
-
const naming = {
|
|
37
|
+
const naming = rowNaming(id, {
|
|
38
38
|
'aria-label': ariaLabel,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
value,
|
|
40
|
+
detail,
|
|
41
|
+
valueDetail,
|
|
42
|
+
unread,
|
|
43
|
+
tag,
|
|
44
|
+
});
|
|
44
45
|
let row;
|
|
45
46
|
if (href !== undefined)
|
|
46
47
|
row = (_jsx("a", { ...naming, "aria-current": selected ? 'true' : undefined, "aria-disabled": disabled || undefined, className: className, href: disabled ? undefined : href, onClick: onPress && !disabled
|
|
@@ -55,5 +56,5 @@ export function ListRow({ title, detail, value, valueDetail, valueTone, leading,
|
|
|
55
56
|
row = (_jsx("button", { ...naming, "aria-pressed": selected, className: className, disabled: disabled, onClick: onPress, tabIndex: tabStop, type: "button", children: content }));
|
|
56
57
|
else
|
|
57
58
|
row = (_jsx("div", { "aria-label": ariaLabel, className: className, role: ariaLabel ? 'group' : undefined, children: content }));
|
|
58
|
-
return
|
|
59
|
+
return _jsx(ListRowItem, { action: action, children: row });
|
|
59
60
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type MarkdownHeading, type MarkdownOmission, type MarkdownOmissionCounts } from './markdown/markdown-model.js';
|
|
2
|
+
import { type MarkdownOptions } from './markdown/markdown-options.js';
|
|
3
|
+
export type { MarkdownHeading, MarkdownImage, MarkdownImageResolver, MarkdownOmission, MarkdownOmissionCounts, MarkdownOmissionKind, } from './markdown/markdown-model.js';
|
|
4
|
+
export { markdownOmissionKinds } from './markdown/markdown-model.js';
|
|
5
|
+
export type { MarkdownOptions } from './markdown/markdown-options.js';
|
|
6
|
+
export type MarkdownViewProps = MarkdownOptions & {
|
|
7
|
+
/** The Markdown, as written or pasted. */
|
|
8
|
+
source: string;
|
|
9
|
+
/**
|
|
10
|
+
* Hidden words after every link, which opens in a new tab, such as
|
|
11
|
+
* "(opens in a new tab)".
|
|
12
|
+
*/
|
|
13
|
+
newTabLabel: string;
|
|
14
|
+
};
|
|
15
|
+
/** What `inspectMarkdown` finds. */
|
|
16
|
+
export type MarkdownInspection = {
|
|
17
|
+
/** The headings, with the ids the view gives them. */
|
|
18
|
+
headings: MarkdownHeading[];
|
|
19
|
+
/** The first 100 things left out, in order. */
|
|
20
|
+
omissions: MarkdownOmission[];
|
|
21
|
+
/** How many of each kind were left out, past the first 100 too. */
|
|
22
|
+
omissionCounts: MarkdownOmissionCounts;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* What a source's view shows in its headings (for an "On this page" list,
|
|
26
|
+
* with the ids the view gives them) and what it leaves out (for an editor
|
|
27
|
+
* to say so before saving). Pure; pass the same options as the view, which
|
|
28
|
+
* it refuses the same way.
|
|
29
|
+
*/
|
|
30
|
+
export declare function inspectMarkdown(source: string, options?: MarkdownOptions): MarkdownInspection;
|
|
31
|
+
/**
|
|
32
|
+
* Markdown drawn in Scalewing's type as React elements, never as HTML:
|
|
33
|
+
* paragraphs, headings (`#` drawn at the page's second level, an `h2`),
|
|
34
|
+
* lists, bold, italic, links (http, https and mailto only, in a new tab),
|
|
35
|
+
* block quotes as a highlighted box, line breaks and `photo:` images with
|
|
36
|
+
* their captions. Raw HTML, tables, other images and other link schemes
|
|
37
|
+
* are left out (a link's words stay); `inspectMarkdown` lists them.
|
|
38
|
+
*/
|
|
39
|
+
export declare function MarkdownView(props: MarkdownViewProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { readMarkdown } from './markdown/read-markdown.js';
|
|
4
|
+
import { renderBlocks } from './markdown/render-markdown.js';
|
|
5
|
+
export { markdownOmissionKinds } from './markdown/markdown-model.js';
|
|
6
|
+
/**
|
|
7
|
+
* What a source's view shows in its headings (for an "On this page" list,
|
|
8
|
+
* with the ids the view gives them) and what it leaves out (for an editor
|
|
9
|
+
* to say so before saving). Pure; pass the same options as the view, which
|
|
10
|
+
* it refuses the same way.
|
|
11
|
+
*/
|
|
12
|
+
export function inspectMarkdown(source, options = {}) {
|
|
13
|
+
const { headings, omissions, omissionCounts } = readMarkdown(source, options, 'inspectMarkdown');
|
|
14
|
+
return { headings, omissions, omissionCounts };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Markdown drawn in Scalewing's type as React elements, never as HTML:
|
|
18
|
+
* paragraphs, headings (`#` drawn at the page's second level, an `h2`),
|
|
19
|
+
* lists, bold, italic, links (http, https and mailto only, in a new tab),
|
|
20
|
+
* block quotes as a highlighted box, line breaks and `photo:` images with
|
|
21
|
+
* their captions. Raw HTML, tables, other images and other link schemes
|
|
22
|
+
* are left out (a link's words stay); `inspectMarkdown` lists them.
|
|
23
|
+
*/
|
|
24
|
+
export function MarkdownView(props) {
|
|
25
|
+
const { source, resolveImage, idPrefix, newTabLabel } = props;
|
|
26
|
+
if (typeof newTabLabel !== 'string' || newTabLabel.trim() === '')
|
|
27
|
+
throw new RangeError('MarkdownView newTabLabel must be non-empty text');
|
|
28
|
+
const reading = useMemo(() => readMarkdown(source, { resolveImage, idPrefix }, 'MarkdownView'), [source, resolveImage, idPrefix]);
|
|
29
|
+
return (_jsx("div", { className: "sw-markdown", children: renderBlocks(reading.blocks, { newTabLabel }) }));
|
|
30
|
+
}
|