@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.
Files changed (191) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +274 -7
  3. package/dist/calendar-labels.d.ts +2 -0
  4. package/dist/calendar-labels.js +7 -0
  5. package/dist/components/Article.d.ts +60 -0
  6. package/dist/components/Article.js +42 -0
  7. package/dist/components/BarChart.d.ts +20 -4
  8. package/dist/components/BarChart.js +13 -6
  9. package/dist/components/ButtonGroup.d.ts +8 -1
  10. package/dist/components/ButtonGroup.js +11 -2
  11. package/dist/components/DateField.d.ts +14 -2
  12. package/dist/components/DateField.js +14 -11
  13. package/dist/components/DenominationGrid.d.ts +3 -2
  14. package/dist/components/DenominationGrid.js +21 -3
  15. package/dist/components/Dialog.d.ts +29 -13
  16. package/dist/components/Dialog.js +22 -20
  17. package/dist/components/DocumentViewer.d.ts +41 -0
  18. package/dist/components/DocumentViewer.js +72 -0
  19. package/dist/components/Field.d.ts +13 -2
  20. package/dist/components/Field.js +14 -38
  21. package/dist/components/FileField.d.ts +44 -0
  22. package/dist/components/FileField.js +66 -0
  23. package/dist/components/FilterChips.d.ts +7 -37
  24. package/dist/components/FilterChips.js +32 -24
  25. package/dist/components/ListRow.d.ts +33 -3
  26. package/dist/components/ListRow.js +18 -17
  27. package/dist/components/MarkdownView.d.ts +39 -0
  28. package/dist/components/MarkdownView.js +30 -0
  29. package/dist/components/NavCount.d.ts +15 -2
  30. package/dist/components/NavCount.js +16 -6
  31. package/dist/components/SegmentedControl.d.ts +17 -1
  32. package/dist/components/SegmentedControl.js +11 -3
  33. package/dist/components/Switch.d.ts +16 -1
  34. package/dist/components/Switch.js +17 -6
  35. package/dist/components/action-bar/ActionBarSummary.d.ts +10 -2
  36. package/dist/components/action-bar/ActionBarSummary.js +11 -2
  37. package/dist/components/chart/use-chart-width.js +2 -24
  38. package/dist/components/date-field/CalendarEntryButton.d.ts +24 -0
  39. package/dist/components/date-field/CalendarEntryButton.js +16 -0
  40. package/dist/components/date-field/TypedEntry.d.ts +26 -0
  41. package/dist/components/date-field/TypedEntry.js +13 -0
  42. package/dist/components/denomination-grid/CountCell.d.ts +16 -0
  43. package/dist/components/denomination-grid/CountCell.js +14 -0
  44. package/dist/components/denomination-grid/DenominationStrip.d.ts +4 -1
  45. package/dist/components/denomination-grid/DenominationStrip.js +2 -2
  46. package/dist/components/denomination-grid/EntryCell.d.ts +3 -1
  47. package/dist/components/denomination-grid/EntryCell.js +2 -2
  48. package/dist/components/denomination-grid/StripRow.js +1 -10
  49. package/dist/components/denomination-grid/TransposedStrip.d.ts +17 -0
  50. package/dist/components/denomination-grid/TransposedStrip.js +43 -0
  51. package/dist/components/denomination-grid/types.d.ts +31 -0
  52. package/dist/components/denomination-grid/types.js +7 -1
  53. package/dist/components/denomination-grid/validate-options.d.ts +7 -0
  54. package/dist/components/denomination-grid/validate-options.js +27 -0
  55. package/dist/components/dialog/DialogFooter.d.ts +6 -5
  56. package/dist/components/dialog/DialogFooter.js +6 -6
  57. package/dist/components/dialog/validate.d.ts +24 -0
  58. package/dist/components/dialog/validate.js +23 -0
  59. package/dist/components/document-viewer/document-viewer-labels.d.ts +23 -0
  60. package/dist/components/document-viewer/document-viewer-labels.js +23 -0
  61. package/dist/components/document-viewer/page-box.d.ts +8 -0
  62. package/dist/components/document-viewer/page-box.js +12 -0
  63. package/dist/components/document-viewer/pdf-document.d.ts +32 -0
  64. package/dist/components/document-viewer/pdf-document.js +66 -0
  65. package/dist/components/document-viewer/pdf-options.d.ts +36 -0
  66. package/dist/components/document-viewer/pdf-options.js +46 -0
  67. package/dist/components/document-viewer/render-pdf-page.d.ts +20 -0
  68. package/dist/components/document-viewer/render-pdf-page.js +38 -0
  69. package/dist/components/document-viewer/scroll-chain.d.ts +6 -0
  70. package/dist/components/document-viewer/scroll-chain.js +23 -0
  71. package/dist/components/document-viewer/use-error-report.d.ts +5 -0
  72. package/dist/components/document-viewer/use-error-report.js +13 -0
  73. package/dist/components/document-viewer/use-page-render.d.ts +21 -0
  74. package/dist/components/document-viewer/use-page-render.js +70 -0
  75. package/dist/components/document-viewer/use-pdf-document.d.ts +22 -0
  76. package/dist/components/document-viewer/use-pdf-document.js +47 -0
  77. package/dist/components/document-viewer/use-pinch-zoom.d.ts +12 -0
  78. package/dist/components/document-viewer/use-pinch-zoom.js +67 -0
  79. package/dist/components/document-viewer/use-zoom.d.ts +23 -0
  80. package/dist/components/document-viewer/use-zoom.js +70 -0
  81. package/dist/components/document-viewer/zoom.d.ts +29 -0
  82. package/dist/components/document-viewer/zoom.js +37 -0
  83. package/dist/components/field/field-checks.d.ts +34 -0
  84. package/dist/components/field/field-checks.js +42 -0
  85. package/dist/components/file-field/file-field-checks.d.ts +13 -0
  86. package/dist/components/file-field/file-field-checks.js +21 -0
  87. package/dist/components/file-field/use-file-drop.d.ts +34 -0
  88. package/dist/components/file-field/use-file-drop.js +78 -0
  89. package/dist/components/file-field/use-swap-focus.d.ts +15 -0
  90. package/dist/components/file-field/use-swap-focus.js +40 -0
  91. package/dist/components/filter-chips/FilterChip.d.ts +16 -0
  92. package/dist/components/filter-chips/FilterChip.js +20 -0
  93. package/dist/components/filter-chips/options.d.ts +75 -0
  94. package/dist/components/filter-chips/options.js +11 -0
  95. package/dist/components/filter-chips/validate.d.ts +9 -0
  96. package/dist/components/filter-chips/validate.js +48 -0
  97. package/dist/components/list/ListRowContent.d.ts +8 -2
  98. package/dist/components/list/ListRowContent.js +6 -3
  99. package/dist/components/list/ListRowItem.d.ts +10 -0
  100. package/dist/components/list/ListRowItem.js +17 -0
  101. package/dist/components/list/row-naming.d.ts +17 -0
  102. package/dist/components/list/row-naming.js +19 -0
  103. package/dist/components/list/row-slot.d.ts +8 -0
  104. package/dist/components/list/row-slot.js +12 -0
  105. package/dist/components/markdown/markdown-limits.d.ts +14 -0
  106. package/dist/components/markdown/markdown-limits.js +14 -0
  107. package/dist/components/markdown/markdown-model.d.ts +94 -0
  108. package/dist/components/markdown/markdown-model.js +8 -0
  109. package/dist/components/markdown/markdown-options.d.ts +18 -0
  110. package/dist/components/markdown/markdown-options.js +10 -0
  111. package/dist/components/markdown/markdown-rules.d.ts +28 -0
  112. package/dist/components/markdown/markdown-rules.js +81 -0
  113. package/dist/components/markdown/markdown-text.d.ts +17 -0
  114. package/dist/components/markdown/markdown-text.js +86 -0
  115. package/dist/components/markdown/parse-markdown.d.ts +4 -0
  116. package/dist/components/markdown/parse-markdown.js +21 -0
  117. package/dist/components/markdown/read-blocks.d.ts +8 -0
  118. package/dist/components/markdown/read-blocks.js +168 -0
  119. package/dist/components/markdown/read-inlines.d.ts +12 -0
  120. package/dist/components/markdown/read-inlines.js +126 -0
  121. package/dist/components/markdown/read-markdown.d.ts +9 -0
  122. package/dist/components/markdown/read-markdown.js +27 -0
  123. package/dist/components/markdown/reading-context.d.ts +18 -0
  124. package/dist/components/markdown/reading-context.js +35 -0
  125. package/dist/components/markdown/render-markdown.d.ts +10 -0
  126. package/dist/components/markdown/render-markdown.js +52 -0
  127. package/dist/components/use-element-width.d.ts +11 -0
  128. package/dist/components/use-element-width.js +38 -0
  129. package/dist/css/chart-tones.d.ts +2 -0
  130. package/dist/css/chart-tones.js +5 -1
  131. package/dist/css/css-article.d.ts +2 -0
  132. package/dist/css/css-article.js +110 -0
  133. package/dist/css/css-bar-chart.d.ts +17 -2
  134. package/dist/css/css-bar-chart.js +38 -7
  135. package/dist/css/css-button-group-attached.d.ts +3 -0
  136. package/dist/css/css-button-group-attached.js +18 -1
  137. package/dist/css/css-canvas-grouped.d.ts +17 -0
  138. package/dist/css/css-canvas-grouped.js +40 -0
  139. package/dist/css/css-components.js +18 -0
  140. package/dist/css/css-count-pill.js +14 -1
  141. package/dist/css/css-date-field-calendar-entry.d.ts +11 -0
  142. package/dist/css/css-date-field-calendar-entry.js +50 -0
  143. package/dist/css/css-date-field.js +4 -0
  144. package/dist/css/css-denomination-grid.js +4 -0
  145. package/dist/css/css-denomination-transposed.d.ts +4 -0
  146. package/dist/css/css-denomination-transposed.js +178 -0
  147. package/dist/css/css-dialog-canvas.d.ts +3 -0
  148. package/dist/css/css-dialog-canvas.js +20 -0
  149. package/dist/css/css-dialog-frame.js +19 -2
  150. package/dist/css/css-dialog-grabber.d.ts +4 -0
  151. package/dist/css/css-dialog-grabber.js +56 -0
  152. package/dist/css/css-dialog.js +3 -2
  153. package/dist/css/css-document-viewer.d.ts +2 -0
  154. package/dist/css/css-document-viewer.js +186 -0
  155. package/dist/css/css-document.js +3 -0
  156. package/dist/css/css-field-width.d.ts +12 -0
  157. package/dist/css/css-field-width.js +49 -0
  158. package/dist/css/css-field.js +4 -0
  159. package/dist/css/css-file-field.d.ts +2 -0
  160. package/dist/css/css-file-field.js +180 -0
  161. package/dist/css/css-filter-chips-layout.d.ts +2 -0
  162. package/dist/css/css-filter-chips-layout.js +42 -0
  163. package/dist/css/css-filter-chips.js +4 -0
  164. package/dist/css/css-list-row-trailing.d.ts +2 -0
  165. package/dist/css/css-list-row-trailing.js +91 -0
  166. package/dist/css/css-list-row.d.ts +6 -0
  167. package/dist/css/css-list-row.js +19 -3
  168. package/dist/css/css-list.js +5 -1
  169. package/dist/css/css-markdown.d.ts +2 -0
  170. package/dist/css/css-markdown.js +144 -0
  171. package/dist/css/css-segmented-sizing.d.ts +13 -0
  172. package/dist/css/css-segmented-sizing.js +66 -0
  173. package/dist/css/css-segmented.js +4 -0
  174. package/dist/css/css-switch.d.ts +6 -0
  175. package/dist/css/css-switch.js +39 -0
  176. package/dist/css/css-variables.js +1 -0
  177. package/dist/css/stacking.d.ts +1 -0
  178. package/dist/css/stacking.js +4 -1
  179. package/dist/css/stylesheet.js +12 -0
  180. package/dist/date-field-labels.d.ts +5 -0
  181. package/dist/date-field-labels.js +1 -0
  182. package/dist/document-viewer-worker.d.ts +9 -0
  183. package/dist/document-viewer-worker.js +9 -0
  184. package/dist/index.d.ts +15 -9
  185. package/dist/index.js +13 -7
  186. package/dist/list-options.d.ts +11 -7
  187. package/dist/list-options.js +20 -8
  188. package/dist/styles.css +889 -44
  189. package/dist/theme/ThemeProvider.d.ts +11 -1
  190. package/dist/theme/ThemeProvider.js +4 -2
  191. 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 declare const fieldVariants: readonly ["outlined", "search"];
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;
@@ -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 const fieldVariants = ['outlined', 'search'];
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
- assertSearch(props, children);
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 || marksInvalid || passesInvalid(props) || required || adorned);
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 adornments require one native input, select, or textarea child');
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 RequiredMark } from './FieldLabelText.js';
2
- export type FilterChipOption = {
3
- value: string;
4
- label: string;
5
- count?: number;
6
- disabled?: boolean;
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, Fragment as _Fragment } from "react/jsx-runtime";
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 { assertRequiredMark, showsRequiredMark, } from './FieldLabelText.js';
7
- import { radioTabStopValue, tabStop } from '../tab-stop.js';
8
- function isCount(count) {
9
- return count === undefined || (Number.isInteger(count) && count >= 0);
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
- export function FilterChips({ label, value, onChange, options, required = false, requiredMark = 'visible', error, 'aria-describedby': describedByProp, }) {
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 values = options.map((option) => option.value);
18
- if (!label.trim())
19
- throw new RangeError('label must not be empty');
20
- assertRequiredMark(requiredMark);
21
- if (options.length === 0 ||
22
- options.some((option) => !option.value.trim() || !option.label.trim()))
23
- throw new RangeError('options must have nonempty values and labels');
24
- if (new Set(values).size !== values.length)
25
- throw new RangeError('options must have unique values');
26
- if (value !== '' && !values.includes(value))
27
- throw new RangeError('value must match an option or be empty');
28
- if (options.some((option) => !isCount(option.count)))
29
- throw new RangeError('count must be a non-negative integer');
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
- /** Colours the value only, as a table cell's tone; the words say why. */
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 { useContext, useId } from 'react';
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 { ListRowContainer } from './list/list-row-container.js';
10
- function ids(...parts) {
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
- 'aria-labelledby': ariaLabel
40
- ? undefined
41
- : ids(`${id}-title`, value !== undefined && `${id}-value`),
42
- 'aria-describedby': ids(Boolean(detail) && `${id}-detail`, Boolean(valueDetail) && `${id}-value-detail`),
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 container === 'li' ? (_jsx("li", { className: "sw-list-item", children: row })) : (_jsx("div", { className: "sw-list-item", children: row }));
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
+ }