@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
@@ -1 +1,7 @@
1
- export {};
1
+ /** How a negative count reads: in the danger colour, or plain text. */
2
+ export const denominationNegativeAppearances = ['danger', 'plain'];
3
+ /**
4
+ * The widths below which a strip is drawn transposed: the denominations as
5
+ * rows, the grid's rows as columns.
6
+ */
7
+ export const denominationTransposeBreakpoints = ['md', 'lg'];
@@ -0,0 +1,7 @@
1
+ import { type DenominationGridLayout, type DenominationGridRow } from './types.js';
2
+ /**
3
+ * The grid's later options: a known negative appearance per row, a known
4
+ * transpose breakpoint on a strip only, and a whole count of digits (3 to
5
+ * 6) for a grid with entry rows.
6
+ */
7
+ export declare function assertDenominationOptions(layout: DenominationGridLayout, rows: readonly DenominationGridRow[], transposeBelow: unknown, countDigits: unknown): void;
@@ -0,0 +1,27 @@
1
+ import { denominationNegativeAppearances, denominationTransposeBreakpoints, } from './types.js';
2
+ /** The digits an entry count can be given room for. */
3
+ const countDigitsRange = { min: 3, max: 6 };
4
+ /**
5
+ * The grid's later options: a known negative appearance per row, a known
6
+ * transpose breakpoint on a strip only, and a whole count of digits (3 to
7
+ * 6) for a grid with entry rows.
8
+ */
9
+ export function assertDenominationOptions(layout, rows, transposeBelow, countDigits) {
10
+ if (rows.some((row) => row.negativeAppearance !== undefined &&
11
+ !denominationNegativeAppearances.includes(row.negativeAppearance)))
12
+ throw new RangeError("negativeAppearance must be 'danger' or 'plain'");
13
+ if (transposeBelow !== undefined) {
14
+ if (!denominationTransposeBreakpoints.includes(transposeBelow))
15
+ throw new RangeError("transposeBelow must be 'md' or 'lg'");
16
+ if (layout !== 'strip')
17
+ throw new RangeError('transposeBelow needs the strip layout');
18
+ }
19
+ if (countDigits !== undefined) {
20
+ if (!Number.isInteger(countDigits) ||
21
+ countDigits < countDigitsRange.min ||
22
+ countDigits > countDigitsRange.max)
23
+ throw new RangeError('countDigits must be a whole number from 3 to 6');
24
+ if (!rows.some((row) => row.entry))
25
+ throw new RangeError('countDigits needs entry rows');
26
+ }
27
+ }
@@ -2,13 +2,14 @@ import { type ReactNode } from 'react';
2
2
  import { type ActionBarSummary } from '../action-bar/ActionBarSummary.js';
3
3
  type DialogFooterProps = {
4
4
  summary?: ActionBarSummary;
5
+ status?: ReactNode;
5
6
  children?: ReactNode;
6
7
  };
7
8
  /**
8
- * The dialog's pinned footer: the summary figure (in a polite live region,
9
- * as in ActionBar) at the start, the actions at the end. Rendered only when
10
- * the dialog has a footer or a summary, and then always with its status
11
- * region, so a new figure is announced.
9
+ * The dialog's pinned footer: the summary figure and the status line (in
10
+ * one polite live region, as in ActionBar) at the start, the actions at the
11
+ * end. Rendered only when the dialog has a footer, a summary or a status,
12
+ * and then always with its live region, so a new figure is announced.
12
13
  */
13
- export declare function DialogFooter({ children, summary }: DialogFooterProps): import("react").JSX.Element;
14
+ export declare function DialogFooter({ children, status, summary }: DialogFooterProps): import("react").JSX.Element;
14
15
  export {};
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ActionBarSummaryLine, } from '../action-bar/ActionBarSummary.js';
3
3
  /**
4
- * The dialog's pinned footer: the summary figure (in a polite live region,
5
- * as in ActionBar) at the start, the actions at the end. Rendered only when
6
- * the dialog has a footer or a summary, and then always with its status
7
- * region, so a new figure is announced.
4
+ * The dialog's pinned footer: the summary figure and the status line (in
5
+ * one polite live region, as in ActionBar) at the start, the actions at the
6
+ * end. Rendered only when the dialog has a footer, a summary or a status,
7
+ * and then always with its live region, so a new figure is announced.
8
8
  */
9
- export function DialogFooter({ children, summary }) {
10
- return (_jsxs("div", { className: "sw-dialog-footer", children: [_jsx("div", { className: "sw-dialog-footer-status", role: "status", children: summary ? _jsx(ActionBarSummaryLine, { summary: summary }) : null }), children ? (_jsx("div", { className: "sw-dialog-footer-actions", children: children })) : null] }));
9
+ export function DialogFooter({ children, status, summary }) {
10
+ return (_jsxs("div", { className: "sw-dialog-footer", children: [_jsxs("div", { className: "sw-dialog-footer-status", role: "status", children: [summary ? _jsx(ActionBarSummaryLine, { summary: summary }) : null, status !== undefined ? (_jsx("span", { className: "sw-dialog-footer-note", children: status })) : null] }), children ? (_jsx("div", { className: "sw-dialog-footer-actions", children: children })) : null] }));
11
11
  }
@@ -0,0 +1,24 @@
1
+ import { type Breakpoint } from '../../css/breakpoints.js';
2
+ import { type DialogAppearance } from '../../css/css-dialog-banded.js';
3
+ import { type ThemeCanvas } from '../../css/css-canvas-grouped.js';
4
+ import { type ActionBarSummary } from '../action-bar/ActionBarSummary.js';
5
+ export declare const dialogSizes: readonly ["md", "lg", "xl"];
6
+ /**
7
+ * `md` is the reading width (32rem) for a message or a short form; `lg`
8
+ * (56rem) holds a row of six fields or a data grid without folding it;
9
+ * `xl` (65rem) holds a grid of six count columns with its labels and total.
10
+ */
11
+ export type DialogSize = (typeof dialogSizes)[number];
12
+ /** `alertdialog` for a dialog that interrupts to warn, such as an idle sign-out. */
13
+ export type DialogRole = 'dialog' | 'alertdialog';
14
+ export type DialogChecks = {
15
+ size: DialogSize;
16
+ role: DialogRole | undefined;
17
+ description: string | undefined;
18
+ summary: ActionBarSummary | undefined;
19
+ appearance: DialogAppearance;
20
+ canvas: ThemeCanvas;
21
+ sheetBelow: Breakpoint | undefined;
22
+ sheetGrabber: boolean;
23
+ };
24
+ export declare function assertDialogProps({ appearance, canvas, description, role, sheetBelow, sheetGrabber, size, summary, }: DialogChecks): void;
@@ -0,0 +1,23 @@
1
+ import { dialogAppearances, } from '../../css/css-dialog-banded.js';
2
+ import { themeCanvases, } from '../../css/css-canvas-grouped.js';
3
+ import { assertActionBarSummary, } from '../action-bar/ActionBarSummary.js';
4
+ export const dialogSizes = ['md', 'lg', 'xl'];
5
+ const dialogRoles = ['dialog', 'alertdialog'];
6
+ function oneOf(values, value, prop) {
7
+ if (!values.includes(value))
8
+ throw new RangeError(`Unknown Dialog ${prop}: ${String(value)}`);
9
+ }
10
+ export function assertDialogProps({ appearance, canvas, description, role, sheetBelow, sheetGrabber, size, summary, }) {
11
+ oneOf(dialogSizes, size, 'size');
12
+ oneOf(dialogAppearances, appearance, 'appearance');
13
+ oneOf(themeCanvases, canvas, 'canvas');
14
+ if (role !== undefined)
15
+ oneOf(dialogRoles, role, 'role');
16
+ if (description !== undefined &&
17
+ (typeof description !== 'string' || !description.trim()))
18
+ throw new RangeError('Dialog description must not be empty');
19
+ if (sheetGrabber && !sheetBelow)
20
+ throw new RangeError('Dialog sheetGrabber needs sheetBelow');
21
+ if (summary)
22
+ assertActionBarSummary(summary);
23
+ }
@@ -0,0 +1,23 @@
1
+ /** The viewer's words, all in the consumer's language. */
2
+ export type DocumentViewerLabels = {
3
+ /** Names the step to the page before, such as "Previous page". */
4
+ previous: string;
5
+ /** Names the step to the page after, such as "Next page". */
6
+ next: string;
7
+ /** Where the reader is, such as `(page, count) => \`Page ${page} of ${count}\``. */
8
+ page: (page: number, pageCount: number) => string;
9
+ /** While the PDF opens, such as "Opening the PDF…". */
10
+ loading: string;
11
+ /** When it cannot be shown, such as "This PDF couldn't be shown". */
12
+ failed: string;
13
+ /** A line under `failed`, such as what the reader can do instead. */
14
+ failedDetail?: string;
15
+ };
16
+ /** Fails closed on missing words: the viewer has no words of its own. */
17
+ export declare function assertDocumentViewer({ label, labels, workerSrc, }: {
18
+ label: string;
19
+ labels: DocumentViewerLabels;
20
+ workerSrc: string;
21
+ }): void;
22
+ /** The page shown: the one asked for, kept between 1 and the last page. */
23
+ export declare function shownPage(page: number, pageCount: number): number;
@@ -0,0 +1,23 @@
1
+ function isBlank(text) {
2
+ return typeof text !== 'string' || text.trim() === '';
3
+ }
4
+ /** Fails closed on missing words: the viewer has no words of its own. */
5
+ export function assertDocumentViewer({ label, labels, workerSrc, }) {
6
+ if (isBlank(label))
7
+ throw new RangeError('DocumentViewer label must be non-empty text');
8
+ if (isBlank(workerSrc))
9
+ throw new RangeError('DocumentViewer workerSrc must be a URL');
10
+ for (const key of ['previous', 'next', 'loading', 'failed'])
11
+ if (isBlank(labels?.[key]))
12
+ throw new RangeError(`DocumentViewer labels.${key} must be non-empty text`);
13
+ if (typeof labels.page !== 'function')
14
+ throw new RangeError('DocumentViewer labels.page must be a function');
15
+ if (labels.failedDetail !== undefined && isBlank(labels.failedDetail))
16
+ throw new RangeError('DocumentViewer labels.failedDetail must be non-empty text');
17
+ }
18
+ /** The page shown: the one asked for, kept between 1 and the last page. */
19
+ export function shownPage(page, pageCount) {
20
+ if (!Number.isInteger(page))
21
+ return 1;
22
+ return Math.min(Math.max(page, 1), Math.max(pageCount, 1));
23
+ }
@@ -0,0 +1,8 @@
1
+ import { type CSSProperties } from 'react';
2
+ /** A US Letter page's height over its width, the box's shape until known. */
3
+ export declare const letterAspect: number;
4
+ /**
5
+ * The page box's size, read by the generated `sw-document-viewer-page`
6
+ * rule: its width in CSS pixels and its height over its width.
7
+ */
8
+ export declare function pageBoxStyle(width: number, aspect: number | null): CSSProperties;
@@ -0,0 +1,12 @@
1
+ /** A US Letter page's height over its width, the box's shape until known. */
2
+ export const letterAspect = 11 / 8.5;
3
+ /**
4
+ * The page box's size, read by the generated `sw-document-viewer-page`
5
+ * rule: its width in CSS pixels and its height over its width.
6
+ */
7
+ export function pageBoxStyle(width, aspect) {
8
+ return {
9
+ '--sw-document-page-width': `${Math.max(0, Math.round(width))}px`,
10
+ '--sw-document-page-aspect': String(aspect ?? letterAspect),
11
+ };
12
+ }
@@ -0,0 +1,32 @@
1
+ import { type PdfPageSize } from './render-pdf-page.js';
2
+ export type { PdfPageSize };
3
+ /** An open PDF, as DocumentViewer uses it. */
4
+ export type PdfDocument = {
5
+ pageCount: number;
6
+ /**
7
+ * Draws page `page` (from 1) `width` CSS pixels wide into `canvas`, and
8
+ * its text into `text` (transparent, for reading and selecting), and
9
+ * resolves with its size. `signal` cancels it.
10
+ */
11
+ renderPage: (options: {
12
+ page: number;
13
+ width: number;
14
+ canvas: HTMLCanvasElement;
15
+ text: HTMLElement;
16
+ signal: AbortSignal;
17
+ }) => Promise<PdfPageSize>;
18
+ /** The aspect (height over width) of page `page`, without drawing it. */
19
+ pageAspect: (page: number) => Promise<number>;
20
+ destroy: () => Promise<void>;
21
+ };
22
+ /** Whether an error is PDF.js reporting a cancelled render or text layer. */
23
+ export declare function isCancelled(error: unknown): boolean;
24
+ /**
25
+ * Opens a PDF from its bytes with PDF.js's legacy build (the modern one
26
+ * needs browsers newer than the ones stores run, such as for
27
+ * `Map.prototype.getOrInsertComputed`), loaded on first use (so a page
28
+ * without a viewer never downloads it), in a module worker started from
29
+ * `workerSrc` and ended with the document. Rejects when the bytes are not
30
+ * a PDF PDF.js can open.
31
+ */
32
+ export declare function openPdf(data: ArrayBuffer | Uint8Array, workerSrc: string): Promise<PdfDocument>;
@@ -0,0 +1,66 @@
1
+ import { copyPdfBytes, hardenedPdfOptions } from './pdf-options.js';
2
+ import { renderPdfPage } from './render-pdf-page.js';
3
+ /** Whether an error is PDF.js reporting a cancelled render or text layer. */
4
+ export function isCancelled(error) {
5
+ return (error instanceof Error &&
6
+ (error.name === 'RenderingCancelledException' ||
7
+ error.name === 'AbortException'));
8
+ }
9
+ /**
10
+ * Opens a PDF from its bytes with PDF.js's legacy build (the modern one
11
+ * needs browsers newer than the ones stores run, such as for
12
+ * `Map.prototype.getOrInsertComputed`), loaded on first use (so a page
13
+ * without a viewer never downloads it), in a module worker started from
14
+ * `workerSrc` and ended with the document. Rejects when the bytes are not
15
+ * a PDF PDF.js can open.
16
+ */
17
+ export async function openPdf(data, workerSrc) {
18
+ const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
19
+ const port = new Worker(workerSrc, { type: 'module' });
20
+ const worker = pdfjs.PDFWorker.create({
21
+ port,
22
+ verbosity: hardenedPdfOptions.verbosity,
23
+ });
24
+ const task = pdfjs.getDocument({
25
+ ...hardenedPdfOptions,
26
+ data: copyPdfBytes(data),
27
+ worker,
28
+ });
29
+ // The worker always ends, even when PDF.js fails to close the document.
30
+ async function destroy() {
31
+ try {
32
+ await task.destroy();
33
+ }
34
+ finally {
35
+ worker.destroy();
36
+ port.terminate();
37
+ }
38
+ }
39
+ let document;
40
+ try {
41
+ document = await task.promise;
42
+ }
43
+ catch (error) {
44
+ // The open error is the one reported, never a failure to close.
45
+ await destroy().catch(() => undefined);
46
+ throw error;
47
+ }
48
+ const loaded = document;
49
+ return {
50
+ pageCount: loaded.numPages,
51
+ destroy,
52
+ async pageAspect(page) {
53
+ const { width, height } = (await loaded.getPage(page)).getViewport({
54
+ scale: 1,
55
+ });
56
+ return height / width;
57
+ },
58
+ async renderPage({ page, ...target }) {
59
+ return renderPdfPage({
60
+ page: await loaded.getPage(page),
61
+ ...target,
62
+ createTextLayer: (options) => new pdfjs.TextLayer(options),
63
+ });
64
+ },
65
+ };
66
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The options every document opens with: nothing is fetched beyond the
3
+ * bytes given, no font, character map, colour profile or WebAssembly file
4
+ * (PDF.js falls back to system fonts and skips what it cannot decode), no
5
+ * XFA forms, no streaming or range requests, and no code evaluation.
6
+ *
7
+ * `isEvalSupported` is absent from PDF.js 6.4.299, whose bundles hold no
8
+ * `eval` or `new Function`; it stays here, set to `false`, so the
9
+ * hardening reads the same as the Teisoro API's and survives a downgrade.
10
+ */
11
+ export declare const hardenedPdfOptions: {
12
+ readonly isEvalSupported: false;
13
+ readonly useWorkerFetch: false;
14
+ readonly useWasm: false;
15
+ readonly useSystemFonts: true;
16
+ readonly disableFontFace: false;
17
+ readonly enableXfa: false;
18
+ readonly disableRange: true;
19
+ readonly disableStream: true;
20
+ readonly disableAutoFetch: true;
21
+ readonly stopAtErrors: false;
22
+ readonly verbosity: 0;
23
+ };
24
+ /** The largest canvas drawn, in device pixels: within every browser's limit. */
25
+ export declare const maxCanvasPixels = 16000000;
26
+ /**
27
+ * The device-pixel ratio a page of `width` by `height` CSS pixels is drawn
28
+ * at: the screen's, lowered so the canvas stays under `maxCanvasPixels`.
29
+ */
30
+ export declare function canvasRatio(width: number, height: number, deviceRatio: number): number;
31
+ /**
32
+ * A private copy of the bytes: PDF.js hands its data to the worker, which
33
+ * detaches the buffer, so the consumer's own bytes (kept for a download)
34
+ * are never touched.
35
+ */
36
+ export declare function copyPdfBytes(data: ArrayBuffer | Uint8Array): Uint8Array;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The options every document opens with: nothing is fetched beyond the
3
+ * bytes given, no font, character map, colour profile or WebAssembly file
4
+ * (PDF.js falls back to system fonts and skips what it cannot decode), no
5
+ * XFA forms, no streaming or range requests, and no code evaluation.
6
+ *
7
+ * `isEvalSupported` is absent from PDF.js 6.4.299, whose bundles hold no
8
+ * `eval` or `new Function`; it stays here, set to `false`, so the
9
+ * hardening reads the same as the Teisoro API's and survives a downgrade.
10
+ */
11
+ export const hardenedPdfOptions = {
12
+ isEvalSupported: false,
13
+ useWorkerFetch: false,
14
+ useWasm: false,
15
+ useSystemFonts: true,
16
+ disableFontFace: false,
17
+ enableXfa: false,
18
+ disableRange: true,
19
+ disableStream: true,
20
+ disableAutoFetch: true,
21
+ stopAtErrors: false,
22
+ verbosity: 0,
23
+ };
24
+ /** The largest canvas drawn, in device pixels: within every browser's limit. */
25
+ export const maxCanvasPixels = 16_000_000;
26
+ /**
27
+ * The device-pixel ratio a page of `width` by `height` CSS pixels is drawn
28
+ * at: the screen's, lowered so the canvas stays under `maxCanvasPixels`.
29
+ */
30
+ export function canvasRatio(width, height, deviceRatio) {
31
+ const ratio = deviceRatio > 0 ? deviceRatio : 1;
32
+ const area = width * height * ratio * ratio;
33
+ if (area <= maxCanvasPixels)
34
+ return ratio;
35
+ return Math.sqrt(maxCanvasPixels / (width * height));
36
+ }
37
+ /**
38
+ * A private copy of the bytes: PDF.js hands its data to the worker, which
39
+ * detaches the buffer, so the consumer's own bytes (kept for a download)
40
+ * are never touched.
41
+ */
42
+ export function copyPdfBytes(data) {
43
+ return data instanceof Uint8Array
44
+ ? data.slice()
45
+ : new Uint8Array(data.slice(0));
46
+ }
@@ -0,0 +1,20 @@
1
+ import { type PDFPageProxy, type TextLayer } from 'pdfjs-dist';
2
+ /** A page drawn: its CSS size at the width asked for. */
3
+ export type PdfPageSize = {
4
+ width: number;
5
+ height: number;
6
+ };
7
+ /**
8
+ * Draws one page `width` CSS pixels wide into `canvas`, at the screen's
9
+ * pixel ratio (capped), and its text into `text` (PDF.js's transparent
10
+ * text layer, at the scale the generated rules read). `signal` cancels
11
+ * both.
12
+ */
13
+ export declare function renderPdfPage({ page, width, canvas, text, signal, createTextLayer, }: {
14
+ page: PDFPageProxy;
15
+ width: number;
16
+ canvas: HTMLCanvasElement;
17
+ text: HTMLElement;
18
+ signal: AbortSignal;
19
+ createTextLayer: (options: ConstructorParameters<typeof TextLayer>[0]) => TextLayer;
20
+ }): Promise<PdfPageSize>;
@@ -0,0 +1,38 @@
1
+ import { canvasRatio } from './pdf-options.js';
2
+ /**
3
+ * Draws one page `width` CSS pixels wide into `canvas`, at the screen's
4
+ * pixel ratio (capped), and its text into `text` (PDF.js's transparent
5
+ * text layer, at the scale the generated rules read). `signal` cancels
6
+ * both.
7
+ */
8
+ export async function renderPdfPage({ page, width, canvas, text, signal, createTextLayer, }) {
9
+ const base = page.getViewport({ scale: 1 });
10
+ const viewport = page.getViewport({ scale: width / base.width });
11
+ const ratio = canvasRatio(viewport.width, viewport.height, window.devicePixelRatio);
12
+ canvas.width = Math.floor(viewport.width * ratio);
13
+ canvas.height = Math.floor(viewport.height * ratio);
14
+ const render = page.render({
15
+ canvas,
16
+ viewport,
17
+ transform: ratio === 1 ? undefined : [ratio, 0, 0, ratio, 0, 0],
18
+ });
19
+ text.replaceChildren();
20
+ text.style.setProperty('--total-scale-factor', String(viewport.scale));
21
+ const layer = createTextLayer({
22
+ textContentSource: page.streamTextContent(),
23
+ container: text,
24
+ viewport,
25
+ });
26
+ const cancel = () => {
27
+ render.cancel();
28
+ layer.cancel();
29
+ };
30
+ signal.addEventListener('abort', cancel, { once: true });
31
+ try {
32
+ await Promise.all([render.promise, layer.render()]);
33
+ }
34
+ finally {
35
+ signal.removeEventListener('abort', cancel);
36
+ }
37
+ return { width: viewport.width, height: viewport.height };
38
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Scrolls by `amount` pixels along one axis as a finger would: the
3
+ * element first, then each box around it that scrolls, up to the
4
+ * document, each taking what it can until the amount is spent.
5
+ */
6
+ export declare function scrollChainBy(start: Element, axis: 'x' | 'y', amount: number): void;
@@ -0,0 +1,23 @@
1
+ const scrolling = /^(auto|scroll|overlay)$/;
2
+ function scrolls(element, axis) {
3
+ if (element === element.ownerDocument.scrollingElement)
4
+ return true;
5
+ const style = getComputedStyle(element);
6
+ return scrolling.test(axis === 'x' ? style.overflowX : style.overflowY);
7
+ }
8
+ /**
9
+ * Scrolls by `amount` pixels along one axis as a finger would: the
10
+ * element first, then each box around it that scrolls, up to the
11
+ * document, each taking what it can until the amount is spent.
12
+ */
13
+ export function scrollChainBy(start, axis, amount) {
14
+ let left = amount;
15
+ for (let element = start; element && Math.abs(left) >= 0.5; element = element.parentElement) {
16
+ if (!scrolls(element, axis))
17
+ continue;
18
+ const key = axis === 'x' ? 'scrollLeft' : 'scrollTop';
19
+ const was = element[key];
20
+ element[key] = was + left;
21
+ left -= element[key] - was;
22
+ }
23
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * A stable function that reports an error to the consumer's latest
3
+ * `onError`, so a new callback each render never restarts a page's draw.
4
+ */
5
+ export declare function useErrorReport(onError: ((error: unknown) => void) | undefined): (error: unknown) => void;
@@ -0,0 +1,13 @@
1
+ 'use client';
2
+ import { useCallback, useEffect, useRef } from 'react';
3
+ /**
4
+ * A stable function that reports an error to the consumer's latest
5
+ * `onError`, so a new callback each render never restarts a page's draw.
6
+ */
7
+ export function useErrorReport(onError) {
8
+ const latest = useRef(onError);
9
+ useEffect(() => {
10
+ latest.current = onError;
11
+ });
12
+ return useCallback((error) => latest.current?.(error), []);
13
+ }
@@ -0,0 +1,21 @@
1
+ import { type RefObject } from 'react';
2
+ import { type PdfDocument } from './pdf-document.js';
3
+ export type PageRenderState = 'drawing' | 'drawn' | 'failed';
4
+ /**
5
+ * Draws the shown page at `width` CSS pixels into the canvas and text
6
+ * layer, again whenever the page or the width changes, cancelling a draw
7
+ * that is no longer wanted. The page is visible while it draws, so it
8
+ * appears as PDF.js paints it. `aspect` is its height over its width, for
9
+ * the page's box, known before its first draw ends.
10
+ */
11
+ export declare function usePageRender({ document, page, width, canvas, text, onError, }: {
12
+ document: PdfDocument | null;
13
+ page: number;
14
+ width: number;
15
+ canvas: RefObject<HTMLCanvasElement | null>;
16
+ text: RefObject<HTMLDivElement | null>;
17
+ onError: (error: unknown) => void;
18
+ }): {
19
+ state: PageRenderState;
20
+ aspect: number | null;
21
+ };
@@ -0,0 +1,70 @@
1
+ 'use client';
2
+ import { useEffect, useState } from 'react';
3
+ import { isCancelled } from './pdf-document.js';
4
+ /**
5
+ * Draws the shown page at `width` CSS pixels into the canvas and text
6
+ * layer, again whenever the page or the width changes, cancelling a draw
7
+ * that is no longer wanted. The page is visible while it draws, so it
8
+ * appears as PDF.js paints it. `aspect` is its height over its width, for
9
+ * the page's box, known before its first draw ends.
10
+ */
11
+ export function usePageRender({ document, page, width, canvas, text, onError, }) {
12
+ const [outcome, setOutcome] = useState(null);
13
+ const [shape, setShape] = useState(null);
14
+ useEffect(() => {
15
+ if (!document || !canvas.current || !text.current || width <= 0)
16
+ return;
17
+ const abort = new AbortController();
18
+ document
19
+ .renderPage({
20
+ page,
21
+ width,
22
+ canvas: canvas.current,
23
+ text: text.current,
24
+ signal: abort.signal,
25
+ })
26
+ .then((size) => {
27
+ if (abort.signal.aborted)
28
+ return;
29
+ setOutcome({
30
+ document,
31
+ page,
32
+ width,
33
+ state: 'drawn',
34
+ aspect: size.height / size.width,
35
+ });
36
+ }, (error) => {
37
+ if (abort.signal.aborted || isCancelled(error))
38
+ return;
39
+ setOutcome({ document, page, width, state: 'failed', aspect: null });
40
+ onError(error);
41
+ });
42
+ return () => abort.abort();
43
+ }, [document, page, width, canvas, text, onError]);
44
+ // The box takes the page's shape as soon as it is known.
45
+ useEffect(() => {
46
+ if (!document)
47
+ return;
48
+ let live = true;
49
+ document.pageAspect(page).then((aspect) => {
50
+ if (live)
51
+ setShape({ document, page, aspect });
52
+ }, () => undefined);
53
+ return () => {
54
+ live = false;
55
+ };
56
+ }, [document, page]);
57
+ const current = outcome &&
58
+ outcome.document === document &&
59
+ outcome.page === page &&
60
+ outcome.width === width
61
+ ? outcome
62
+ : null;
63
+ const known = shape && shape.document === document && shape.page === page
64
+ ? shape.aspect
65
+ : null;
66
+ return {
67
+ state: current?.state ?? 'drawing',
68
+ aspect: known ?? current?.aspect ?? null,
69
+ };
70
+ }
@@ -0,0 +1,22 @@
1
+ import { type PdfDocument } from './pdf-document.js';
2
+ export type PdfDocumentState = {
3
+ status: 'loading';
4
+ } | {
5
+ status: 'ready';
6
+ document: PdfDocument;
7
+ } | {
8
+ status: 'failed';
9
+ error: unknown;
10
+ };
11
+ /**
12
+ * Opens the PDF in `data` and closes it (and its worker) when the bytes
13
+ * change or the viewer goes away. New bytes start a new open and show as
14
+ * loading until it settles, so a consumer's Try again is new bytes (or a
15
+ * new `key`).
16
+ */
17
+ export declare function usePdfDocument(data: ArrayBuffer | Uint8Array, workerSrc: string, callbacks: {
18
+ onLoad?: (info: {
19
+ pageCount: number;
20
+ }) => void;
21
+ onError?: (error: unknown) => void;
22
+ }): PdfDocumentState;