@scalewing/react 1.41.0 → 1.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +180 -1
  3. package/dist/components/ActionMenu.d.ts +4 -2
  4. package/dist/components/ActionMenu.js +4 -8
  5. package/dist/components/Article.d.ts +60 -0
  6. package/dist/components/Article.js +42 -0
  7. package/dist/components/DescriptionList.d.ts +12 -2
  8. package/dist/components/DescriptionList.js +7 -3
  9. package/dist/components/DocumentViewer.d.ts +41 -0
  10. package/dist/components/DocumentViewer.js +72 -0
  11. package/dist/components/FileField.d.ts +55 -0
  12. package/dist/components/FileField.js +75 -0
  13. package/dist/components/FilterChips.d.ts +1 -1
  14. package/dist/components/FilterChips.js +1 -1
  15. package/dist/components/FilterChipsRow.d.ts +24 -0
  16. package/dist/components/FilterChipsRow.js +14 -0
  17. package/dist/components/ListRow.d.ts +31 -2
  18. package/dist/components/ListRow.js +19 -18
  19. package/dist/components/MarkdownView.d.ts +39 -0
  20. package/dist/components/MarkdownView.js +30 -0
  21. package/dist/components/NavCount.d.ts +15 -2
  22. package/dist/components/NavCount.js +16 -6
  23. package/dist/components/action-menu/assert-action-menu.d.ts +13 -0
  24. package/dist/components/action-menu/assert-action-menu.js +21 -0
  25. package/dist/components/chart/use-chart-width.js +2 -24
  26. package/dist/components/description-list-layout.d.ts +10 -0
  27. package/dist/components/description-list-layout.js +13 -0
  28. package/dist/components/document-viewer/document-viewer-labels.d.ts +23 -0
  29. package/dist/components/document-viewer/document-viewer-labels.js +23 -0
  30. package/dist/components/document-viewer/page-box.d.ts +8 -0
  31. package/dist/components/document-viewer/page-box.js +12 -0
  32. package/dist/components/document-viewer/pdf-document.d.ts +32 -0
  33. package/dist/components/document-viewer/pdf-document.js +66 -0
  34. package/dist/components/document-viewer/pdf-options.d.ts +36 -0
  35. package/dist/components/document-viewer/pdf-options.js +46 -0
  36. package/dist/components/document-viewer/render-pdf-page.d.ts +20 -0
  37. package/dist/components/document-viewer/render-pdf-page.js +38 -0
  38. package/dist/components/document-viewer/scroll-chain.d.ts +6 -0
  39. package/dist/components/document-viewer/scroll-chain.js +23 -0
  40. package/dist/components/document-viewer/use-error-report.d.ts +5 -0
  41. package/dist/components/document-viewer/use-error-report.js +13 -0
  42. package/dist/components/document-viewer/use-page-render.d.ts +21 -0
  43. package/dist/components/document-viewer/use-page-render.js +70 -0
  44. package/dist/components/document-viewer/use-pdf-document.d.ts +22 -0
  45. package/dist/components/document-viewer/use-pdf-document.js +47 -0
  46. package/dist/components/document-viewer/use-pinch-zoom.d.ts +12 -0
  47. package/dist/components/document-viewer/use-pinch-zoom.js +67 -0
  48. package/dist/components/document-viewer/use-zoom.d.ts +23 -0
  49. package/dist/components/document-viewer/use-zoom.js +70 -0
  50. package/dist/components/document-viewer/zoom.d.ts +29 -0
  51. package/dist/components/document-viewer/zoom.js +37 -0
  52. package/dist/components/file-field/file-field-checks.d.ts +13 -0
  53. package/dist/components/file-field/file-field-checks.js +23 -0
  54. package/dist/components/file-field/use-file-drop.d.ts +34 -0
  55. package/dist/components/file-field/use-file-drop.js +78 -0
  56. package/dist/components/file-field/use-swap-focus.d.ts +15 -0
  57. package/dist/components/file-field/use-swap-focus.js +40 -0
  58. package/dist/components/filter-chips/FilterChip.js +1 -1
  59. package/dist/components/filter-chips/options.d.ts +10 -0
  60. package/dist/components/filter-chips/options.js +8 -0
  61. package/dist/components/filter-chips/validate.d.ts +3 -3
  62. package/dist/components/filter-chips/validate.js +8 -4
  63. package/dist/components/list/ListRowContent.d.ts +9 -2
  64. package/dist/components/list/ListRowContent.js +6 -3
  65. package/dist/components/list/ListRowItem.d.ts +10 -0
  66. package/dist/components/list/ListRowItem.js +17 -0
  67. package/dist/components/list/row-naming.d.ts +17 -0
  68. package/dist/components/list/row-naming.js +19 -0
  69. package/dist/components/markdown/markdown-limits.d.ts +14 -0
  70. package/dist/components/markdown/markdown-limits.js +14 -0
  71. package/dist/components/markdown/markdown-model.d.ts +94 -0
  72. package/dist/components/markdown/markdown-model.js +8 -0
  73. package/dist/components/markdown/markdown-options.d.ts +18 -0
  74. package/dist/components/markdown/markdown-options.js +10 -0
  75. package/dist/components/markdown/markdown-rules.d.ts +28 -0
  76. package/dist/components/markdown/markdown-rules.js +81 -0
  77. package/dist/components/markdown/markdown-text.d.ts +17 -0
  78. package/dist/components/markdown/markdown-text.js +86 -0
  79. package/dist/components/markdown/parse-markdown.d.ts +4 -0
  80. package/dist/components/markdown/parse-markdown.js +21 -0
  81. package/dist/components/markdown/read-blocks.d.ts +8 -0
  82. package/dist/components/markdown/read-blocks.js +168 -0
  83. package/dist/components/markdown/read-inlines.d.ts +12 -0
  84. package/dist/components/markdown/read-inlines.js +126 -0
  85. package/dist/components/markdown/read-markdown.d.ts +9 -0
  86. package/dist/components/markdown/read-markdown.js +27 -0
  87. package/dist/components/markdown/reading-context.d.ts +18 -0
  88. package/dist/components/markdown/reading-context.js +35 -0
  89. package/dist/components/markdown/render-markdown.d.ts +10 -0
  90. package/dist/components/markdown/render-markdown.js +52 -0
  91. package/dist/components/slot.d.ts +9 -0
  92. package/dist/components/slot.js +13 -0
  93. package/dist/components/use-element-width.d.ts +11 -0
  94. package/dist/components/use-element-width.js +38 -0
  95. package/dist/css/css-action-menu-trigger.d.ts +6 -2
  96. package/dist/css/css-action-menu-trigger.js +50 -2
  97. package/dist/css/css-action-menu.js +4 -2
  98. package/dist/css/css-article.d.ts +2 -0
  99. package/dist/css/css-article.js +110 -0
  100. package/dist/css/css-components.js +15 -0
  101. package/dist/css/css-count-pill.js +14 -1
  102. package/dist/css/css-description-list.d.ts +1 -0
  103. package/dist/css/css-description-list.js +24 -0
  104. package/dist/css/css-document-viewer.d.ts +2 -0
  105. package/dist/css/css-document-viewer.js +186 -0
  106. package/dist/css/css-file-field.d.ts +2 -0
  107. package/dist/css/css-file-field.js +194 -0
  108. package/dist/css/css-filter-chip-tones.d.ts +3 -0
  109. package/dist/css/css-filter-chip-tones.js +28 -0
  110. package/dist/css/css-filter-chips-layout.d.ts +1 -0
  111. package/dist/css/css-filter-chips-layout.js +1 -1
  112. package/dist/css/css-filter-chips-row.d.ts +2 -0
  113. package/dist/css/css-filter-chips-row.js +79 -0
  114. package/dist/css/css-filter-chips.js +8 -0
  115. package/dist/css/css-font-mono.d.ts +3 -0
  116. package/dist/css/css-font-mono.js +23 -0
  117. package/dist/css/css-list-row-trailing.d.ts +2 -0
  118. package/dist/css/css-list-row-trailing.js +91 -0
  119. package/dist/css/css-list-row-truncate.d.ts +2 -0
  120. package/dist/css/css-list-row-truncate.js +29 -0
  121. package/dist/css/css-list.js +9 -1
  122. package/dist/css/css-markdown.d.ts +2 -0
  123. package/dist/css/css-markdown.js +144 -0
  124. package/dist/css/css-utilities.js +3 -0
  125. package/dist/css/css-variables.js +1 -0
  126. package/dist/css/spacing-classes.d.ts +1 -1
  127. package/dist/css/spacing-classes.js +1 -0
  128. package/dist/css/stylesheet.js +10 -0
  129. package/dist/document-viewer-worker.d.ts +9 -0
  130. package/dist/document-viewer-worker.js +9 -0
  131. package/dist/index.d.ts +11 -5
  132. package/dist/index.js +8 -3
  133. package/dist/list-options.d.ts +5 -2
  134. package/dist/list-options.js +7 -2
  135. package/dist/styles.css +649 -0
  136. package/package.json +10 -2
@@ -0,0 +1,70 @@
1
+ 'use client';
2
+ import { useEffect, useLayoutEffect, useRef, useState } from 'react';
3
+ import { scrollChainBy } from './scroll-chain.js';
4
+ import { anchorShift, clampZoom, minZoom } from './zoom.js';
5
+ /** The middle of the part of `element` inside the window, in client pixels. */
6
+ function visibleMiddle(element) {
7
+ const rect = element.getBoundingClientRect();
8
+ const view = element.ownerDocument.documentElement;
9
+ const top = Math.max(rect.top, 0);
10
+ const bottom = Math.min(rect.bottom, view.clientHeight || rect.bottom);
11
+ return {
12
+ x: rect.left + rect.width / 2,
13
+ y: bottom > top ? (top + bottom) / 2 : rect.top + rect.height / 2,
14
+ };
15
+ }
16
+ /** How long a zoom rests before the page is drawn again at its size. */
17
+ export const zoomSettleMs = 150;
18
+ /**
19
+ * The page's zoom (1 is fit width) and the scrolling box it is shown in.
20
+ * `zoomTo` changes it about a point on the screen (`at`, in client
21
+ * pixels; the middle of the box's visible part when left out) so the spot
22
+ * of the page under it stays put: once the bigger or smaller page is laid
23
+ * out, the box scrolls across and the boxes around it (the window at
24
+ * last) scroll down by what keeps that spot in place. `drawnZoom`
25
+ * follows `zoom` once it rests, so a pinch stretches the drawn page and
26
+ * only its end draws it again. Another `resetKey` (another page) is fit
27
+ * width again.
28
+ */
29
+ export function useZoom(resetKey) {
30
+ const box = useRef(null);
31
+ const page = useRef(null);
32
+ const [keyed, setKeyed] = useState({ key: resetKey, zoom: minZoom });
33
+ const [drawn, setDrawn] = useState({ key: resetKey, zoom: minZoom });
34
+ const zoom = keyed.key === resetKey ? keyed.zoom : minZoom;
35
+ const drawnZoom = drawn.key === resetKey ? drawn.zoom : minZoom;
36
+ const anchor = useRef(null);
37
+ const shown = useRef(zoom);
38
+ useLayoutEffect(() => {
39
+ const from = shown.current;
40
+ shown.current = zoom;
41
+ const element = box.current;
42
+ const sheet = page.current;
43
+ const spot = anchor.current;
44
+ anchor.current = null;
45
+ if (!element || !sheet || !spot || from === zoom)
46
+ return;
47
+ const shift = anchorShift(spot.before, sheet.getBoundingClientRect(), spot.point);
48
+ scrollChainBy(element, 'x', shift.x);
49
+ scrollChainBy(element, 'y', shift.y);
50
+ }, [zoom]);
51
+ useEffect(() => {
52
+ const timer = setTimeout(() => setDrawn({ key: resetKey, zoom }), zoomSettleMs);
53
+ return () => clearTimeout(timer);
54
+ }, [resetKey, zoom]);
55
+ function zoomTo(next, at) {
56
+ const element = box.current;
57
+ const sheet = page.current;
58
+ const target = clampZoom(next);
59
+ if (target === shown.current)
60
+ return;
61
+ if (element && sheet) {
62
+ anchor.current = {
63
+ point: at ?? visibleMiddle(element),
64
+ before: sheet.getBoundingClientRect(),
65
+ };
66
+ }
67
+ setKeyed({ key: resetKey, zoom: target });
68
+ }
69
+ return { box, page, zoom, drawnZoom, zoomTo };
70
+ }
@@ -0,0 +1,29 @@
1
+ /** Fit width is 1; a page can be drawn up to four times as wide. */
2
+ export declare const minZoom = 1;
3
+ export declare const maxZoom = 4;
4
+ /** One key press's step: a quarter of the fitted width. */
5
+ export declare const zoomStep = 0.25;
6
+ /** A zoom kept between fit width and the most, to two decimals. */
7
+ export declare function clampZoom(zoom: number): number;
8
+ /** The zoom a key asks for on the page: `+` or `=` in, `-` out, `0` fit. */
9
+ export declare function zoomForKey(key: string, zoom: number): number | null;
10
+ /** A box on the screen, as `getBoundingClientRect` gives it. */
11
+ export type ScreenBox = {
12
+ left: number;
13
+ top: number;
14
+ width: number;
15
+ height: number;
16
+ };
17
+ /**
18
+ * How far to scroll, across and down, so the spot of the page that was
19
+ * under `point` before a zoom (the page at `before`) is under it again
20
+ * after (the page at `after`). Both boxes are the page's own, so the
21
+ * stage's padding and where the page sits on the screen count.
22
+ */
23
+ export declare function anchorShift(before: ScreenBox, after: ScreenBox, point: {
24
+ x: number;
25
+ y: number;
26
+ }): {
27
+ x: number;
28
+ y: number;
29
+ };
@@ -0,0 +1,37 @@
1
+ /** Fit width is 1; a page can be drawn up to four times as wide. */
2
+ export const minZoom = 1;
3
+ export const maxZoom = 4;
4
+ /** One key press's step: a quarter of the fitted width. */
5
+ export const zoomStep = 0.25;
6
+ /** A zoom kept between fit width and the most, to two decimals. */
7
+ export function clampZoom(zoom) {
8
+ if (!Number.isFinite(zoom))
9
+ return minZoom;
10
+ return Math.round(Math.min(maxZoom, Math.max(minZoom, zoom)) * 100) / 100;
11
+ }
12
+ /** The zoom a key asks for on the page: `+` or `=` in, `-` out, `0` fit. */
13
+ export function zoomForKey(key, zoom) {
14
+ if (key === '+' || key === '=')
15
+ return clampZoom(zoom + zoomStep);
16
+ if (key === '-' || key === '_')
17
+ return clampZoom(zoom - zoomStep);
18
+ if (key === '0')
19
+ return minZoom;
20
+ return null;
21
+ }
22
+ /**
23
+ * How far to scroll, across and down, so the spot of the page that was
24
+ * under `point` before a zoom (the page at `before`) is under it again
25
+ * after (the page at `after`). Both boxes are the page's own, so the
26
+ * stage's padding and where the page sits on the screen count.
27
+ */
28
+ export function anchorShift(before, after, point) {
29
+ if (before.width <= 0 || before.height <= 0)
30
+ return { x: 0, y: 0 };
31
+ const across = (point.x - before.left) / before.width;
32
+ const down = (point.y - before.top) / before.height;
33
+ return {
34
+ x: after.left + across * after.width - point.x,
35
+ y: after.top + down * after.height - point.y,
36
+ };
37
+ }
@@ -0,0 +1,13 @@
1
+ import type { FileFieldValue } from '../FileField.js';
2
+ /**
3
+ * Fails closed on a FileField it cannot name or show: an empty label,
4
+ * choose or change words, a blank drop hint, or a chosen file with no name,
5
+ * a blank detail or a blank preview address.
6
+ */
7
+ export declare function assertFileField(props: {
8
+ label: string;
9
+ chooseLabel: string;
10
+ changeLabel: string;
11
+ dropLabel: string | undefined;
12
+ value: FileFieldValue | null;
13
+ }): void;
@@ -0,0 +1,23 @@
1
+ function isBlank(text) {
2
+ return typeof text !== 'string' || text.trim() === '';
3
+ }
4
+ /**
5
+ * Fails closed on a FileField it cannot name or show: an empty label,
6
+ * choose or change words, a blank drop hint, or a chosen file with no name,
7
+ * a blank detail or a blank preview address.
8
+ */
9
+ export function assertFileField(props) {
10
+ for (const key of ['label', 'chooseLabel', 'changeLabel'])
11
+ if (isBlank(props[key]))
12
+ throw new RangeError(`FileField ${key} must be non-empty text`);
13
+ if (props.dropLabel !== undefined && isBlank(props.dropLabel))
14
+ throw new RangeError('FileField dropLabel must be non-empty text');
15
+ if (props.value === null)
16
+ return;
17
+ if (isBlank(props.value.name))
18
+ throw new RangeError('FileField value name must be non-empty text');
19
+ if (props.value.detail !== undefined && isBlank(props.value.detail))
20
+ throw new RangeError('FileField value detail must be non-empty text');
21
+ if (props.value.preview !== undefined && isBlank(props.value.preview))
22
+ throw new RangeError('FileField value preview must be an image address');
23
+ }
@@ -0,0 +1,34 @@
1
+ import { type DragEvent } from 'react';
2
+ /**
3
+ * A disabled field still keeps the browser from opening a file dropped on
4
+ * it, and shows no copy cursor: the drop does nothing.
5
+ */
6
+ declare function refuse(event: DragEvent): void;
7
+ /**
8
+ * A drop target for one file: whether files are dragged over it, and the
9
+ * handlers that keep the browser from opening a dropped file and hand the
10
+ * first one to `onFile`. A counter follows the drag through the target's
11
+ * children, whose enter and leave events would otherwise flicker it. While
12
+ * `disabled`, a dropped file is refused and a drag in progress ends.
13
+ */
14
+ export declare function useFileDrop({ disabled, onFile, }: {
15
+ disabled: boolean;
16
+ onFile: (file: File) => void;
17
+ }): {
18
+ dragging: boolean;
19
+ handlers: {
20
+ onDragOver: typeof refuse;
21
+ onDragLeave: (event: DragEvent) => void;
22
+ onDrop: (event: DragEvent) => void;
23
+ onDragEnter?: undefined;
24
+ };
25
+ } | {
26
+ dragging: boolean;
27
+ handlers: {
28
+ onDragEnter: (event: DragEvent) => void;
29
+ onDragOver: (event: DragEvent) => void;
30
+ onDragLeave: (event: DragEvent) => void;
31
+ onDrop: (event: DragEvent) => void;
32
+ };
33
+ };
34
+ export {};
@@ -0,0 +1,78 @@
1
+ import { useRef, useState } from 'react';
2
+ /** Whether a drag carries files, not text or a link dragged from the page. */
3
+ function carriesFiles(event) {
4
+ return Array.from(event.dataTransfer?.types ?? []).includes('Files');
5
+ }
6
+ /**
7
+ * A disabled field still keeps the browser from opening a file dropped on
8
+ * it, and shows no copy cursor: the drop does nothing.
9
+ */
10
+ function refuse(event) {
11
+ if (!carriesFiles(event))
12
+ return;
13
+ event.preventDefault();
14
+ event.dataTransfer.dropEffect = 'none';
15
+ }
16
+ /**
17
+ * A drop target for one file: whether files are dragged over it, and the
18
+ * handlers that keep the browser from opening a dropped file and hand the
19
+ * first one to `onFile`. A counter follows the drag through the target's
20
+ * children, whose enter and leave events would otherwise flicker it. While
21
+ * `disabled`, a dropped file is refused and a drag in progress ends.
22
+ */
23
+ export function useFileDrop({ disabled, onFile, }) {
24
+ const depth = useRef(0);
25
+ const [dragging, setDragging] = useState(false);
26
+ // A drag that began before the field was disabled ends here too, so its
27
+ // tint is gone when the field is enabled again.
28
+ const stop = (event) => {
29
+ refuse(event);
30
+ depth.current = 0;
31
+ setDragging(false);
32
+ };
33
+ if (disabled)
34
+ return {
35
+ dragging: false,
36
+ handlers: {
37
+ onDragOver: refuse,
38
+ // Leaving or dropping ends a drag that began while enabled.
39
+ onDragLeave: stop,
40
+ onDrop: stop,
41
+ },
42
+ };
43
+ return {
44
+ dragging,
45
+ handlers: {
46
+ onDragEnter: (event) => {
47
+ if (!carriesFiles(event))
48
+ return;
49
+ event.preventDefault();
50
+ depth.current += 1;
51
+ setDragging(true);
52
+ },
53
+ onDragOver: (event) => {
54
+ if (!carriesFiles(event))
55
+ return;
56
+ event.preventDefault();
57
+ event.dataTransfer.dropEffect = 'copy';
58
+ },
59
+ onDragLeave: (event) => {
60
+ if (!carriesFiles(event))
61
+ return;
62
+ depth.current = Math.max(0, depth.current - 1);
63
+ if (depth.current === 0)
64
+ setDragging(false);
65
+ },
66
+ onDrop: (event) => {
67
+ if (!carriesFiles(event))
68
+ return;
69
+ event.preventDefault();
70
+ depth.current = 0;
71
+ setDragging(false);
72
+ const file = event.dataTransfer.files[0];
73
+ if (file)
74
+ onFile(file);
75
+ },
76
+ },
77
+ };
78
+ }
@@ -0,0 +1,15 @@
1
+ import { type FocusEvent, type RefObject } from 'react';
2
+ /**
3
+ * Keeps focus when the field swaps its zone for the chosen row (or back).
4
+ * The frame's focus handlers note whether focus is in the field. A blur
5
+ * that leaves the button in the page, with the window still focused, means
6
+ * the person moved on; one whose button was removed is the swap; and one
7
+ * while the window is away is the system picker. After a swap, if focus
8
+ * was in the field and is now nowhere, it moves to the button now there.
9
+ * Focus the person moved elsewhere (an answer that comes later) is never
10
+ * taken back.
11
+ */
12
+ export declare function useSwapFocus(button: RefObject<HTMLElement | null>, chosen: boolean): {
13
+ onFocus: () => void;
14
+ onBlur: (event: FocusEvent<HTMLElement>) => void;
15
+ };
@@ -0,0 +1,40 @@
1
+ 'use client';
2
+ import { useLayoutEffect, useRef, } from 'react';
3
+ /**
4
+ * Keeps focus when the field swaps its zone for the chosen row (or back).
5
+ * The frame's focus handlers note whether focus is in the field. A blur
6
+ * that leaves the button in the page, with the window still focused, means
7
+ * the person moved on; one whose button was removed is the swap; and one
8
+ * while the window is away is the system picker. After a swap, if focus
9
+ * was in the field and is now nowhere, it moves to the button now there.
10
+ * Focus the person moved elsewhere (an answer that comes later) is never
11
+ * taken back.
12
+ */
13
+ export function useSwapFocus(button, chosen) {
14
+ const inside = useRef(false);
15
+ useLayoutEffect(() => {
16
+ if (!inside.current)
17
+ return;
18
+ const active = document.activeElement;
19
+ if (active === null || active === document.body)
20
+ button.current?.focus();
21
+ }, [button, chosen]);
22
+ return {
23
+ onFocus: () => {
24
+ inside.current = true;
25
+ },
26
+ onBlur: (event) => {
27
+ const next = event.relatedTarget;
28
+ if (next instanceof Node && event.currentTarget.contains(next))
29
+ return;
30
+ const left = event.target;
31
+ // Removed by the swap, the button is gone by the next microtask; and
32
+ // while the system picker is open the window itself has lost focus,
33
+ // which is not the person leaving the field.
34
+ queueMicrotask(() => {
35
+ if (left.isConnected && document.hasFocus())
36
+ inside.current = false;
37
+ });
38
+ },
39
+ };
40
+ }
@@ -13,7 +13,7 @@ function ChipCount({ count, appearance, }) {
13
13
  }
14
14
  /** One chip: a native radio or checkbox under its face. */
15
15
  export function FilterChip({ option, name, type, checked, required, tabStopHere, countAppearance, onCheckedChange, }) {
16
- return (_jsxs("label", { className: cx('sw-filter-chip', option.count === 0 && 'sw-filter-chip-quiet'), children: [_jsx("input", { checked: checked, className: "sw-filter-chip-input", disabled: option.disabled, name: name, onChange: (event) => {
16
+ return (_jsxs("label", { className: cx('sw-filter-chip', option.count === 0 && 'sw-filter-chip-quiet', option.tone === 'warning' && 'sw-filter-chip-warning'), children: [_jsx("input", { checked: checked, className: "sw-filter-chip-input", disabled: option.disabled, name: name, onChange: (event) => {
17
17
  if (!option.disabled)
18
18
  onCheckedChange(event.currentTarget.checked);
19
19
  }, required: required, tabIndex: tabStopHere ? tabStop : undefined, type: type, value: option.value }), _jsxs("span", { className: "sw-filter-chip-face", children: [option.label, option.count === undefined ? null : (_jsx(ChipCount, { appearance: countAppearance, count: option.count }))] })] }));
@@ -1,9 +1,19 @@
1
1
  import { type RequiredMark } from '../FieldLabelText.js';
2
+ /**
3
+ * A chip's colour while it is not chosen: `neutral` (the default), the
4
+ * glass chip; `warning`, the warning tint with its words in the warning
5
+ * ink, for a choice that shows what needs a look ("Missing a
6
+ * translation"). Its words must say so; the tint only draws the eye. A
7
+ * chosen chip is the accent, whatever its tone.
8
+ */
9
+ export declare const filterChipTones: readonly ["neutral", "warning"];
10
+ export type FilterChipTone = (typeof filterChipTones)[number];
2
11
  export type FilterChipOption = {
3
12
  value: string;
4
13
  label: string;
5
14
  count?: number;
6
15
  disabled?: boolean;
16
+ tone?: FilterChipTone;
7
17
  };
8
18
  /**
9
19
  * How a chip's count is drawn: `figure` (the default), a muted tabular
@@ -1,3 +1,11 @@
1
+ /**
2
+ * A chip's colour while it is not chosen: `neutral` (the default), the
3
+ * glass chip; `warning`, the warning tint with its words in the warning
4
+ * ink, for a choice that shows what needs a look ("Missing a
5
+ * translation"). Its words must say so; the tint only draws the eye. A
6
+ * chosen chip is the accent, whatever its tone.
7
+ */
8
+ export const filterChipTones = ['neutral', 'warning'];
1
9
  /**
2
10
  * How a chip's count is drawn: `figure` (the default), a muted tabular
3
11
  * figure after a space; `inline`, the label's own words and type, after a
@@ -2,8 +2,8 @@ import { type FilterChipsProps } from './options.js';
2
2
  /**
3
3
  * Fails closed on chips it cannot draw: an empty label, no options, blank
4
4
  * or repeated values, a chosen value that is no option, a count that is
5
- * not a whole number from 0, an unknown count appearance or overflow, and
6
- * a multiple choice asked to be required or given a value that is not a
7
- * list.
5
+ * not a whole number from 0, an unknown chip tone, count appearance or
6
+ * overflow, and a multiple choice asked to be required or given a value
7
+ * that is not a list.
8
8
  */
9
9
  export declare function assertFilterChips(props: FilterChipsProps): void;
@@ -1,5 +1,5 @@
1
1
  import { assertRequiredMark } from '../FieldLabelText.js';
2
- import { filterChipCountAppearances, filterChipsOverflows, } from './options.js';
2
+ import { filterChipCountAppearances, filterChipsOverflows, filterChipTones, } from './options.js';
3
3
  function isCount(count) {
4
4
  return count === undefined || (Number.isInteger(count) && count >= 0);
5
5
  }
@@ -11,9 +11,9 @@ function chosenValues(props) {
11
11
  /**
12
12
  * Fails closed on chips it cannot draw: an empty label, no options, blank
13
13
  * or repeated values, a chosen value that is no option, a count that is
14
- * not a whole number from 0, an unknown count appearance or overflow, and
15
- * a multiple choice asked to be required or given a value that is not a
16
- * list.
14
+ * not a whole number from 0, an unknown chip tone, count appearance or
15
+ * overflow, and a multiple choice asked to be required or given a value
16
+ * that is not a list.
17
17
  */
18
18
  export function assertFilterChips(props) {
19
19
  const { label, options } = props;
@@ -39,6 +39,10 @@ export function assertFilterChips(props) {
39
39
  : 'value must match an option or be empty');
40
40
  if (options.some((option) => !isCount(option.count)))
41
41
  throw new RangeError('count must be a non-negative integer');
42
+ const unknownTone = options.find((option) => option.tone !== undefined &&
43
+ !filterChipTones.includes(option.tone));
44
+ if (unknownTone)
45
+ throw new RangeError(`Unknown FilterChips option tone: ${String(unknownTone.tone)}`);
42
46
  const countAppearance = props.countAppearance ?? 'figure';
43
47
  if (!filterChipCountAppearances.includes(countAppearance))
44
48
  throw new RangeError(`Unknown FilterChips countAppearance: ${String(countAppearance)}`);
@@ -14,9 +14,16 @@ export type ListRowContentProps = {
14
14
  tone: ListRowTone;
15
15
  titleAppearance: ListRowTitleAppearance;
16
16
  accessory: ListRowAccessory;
17
+ /** The unread words; the dot shows while they are given. */
18
+ unreadLabel?: string;
19
+ tag?: ReactNode;
20
+ /** One line each for the title and detail, cut with "…", the words in a tooltip. */
21
+ truncate: boolean;
17
22
  };
18
23
  /**
19
24
  * A list row's parts in reading order: the hidden leading mark, the title
20
- * over its detail, the value over its detail, then the chevron or check.
25
+ * (with the unread dot and its hidden words) over its detail, the value
26
+ * over its detail, the tag, then the chevron or check. A truncated row
27
+ * sets its title's words in their own box, so the "…" never hides the dot.
21
28
  */
22
- export declare function ListRowContent({ id, title, detail, value, valueDetail, valueTone, valueBadge, leading, tone, titleAppearance, accessory, }: ListRowContentProps): import("react").JSX.Element;
29
+ export declare function ListRowContent({ id, title, detail, value, valueDetail, valueTone, valueBadge, leading, tone, titleAppearance, accessory, unreadLabel, tag, truncate, }: ListRowContentProps): import("react").JSX.Element;
@@ -2,10 +2,13 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { cx } from '../../class-names.js';
3
3
  import { Badge } from '../Badge.js';
4
4
  import { ListRowAccessoryMark } from './ListRowAccessoryMark.js';
5
+ import { hasSlot } from '../slot.js';
5
6
  /**
6
7
  * A list row's parts in reading order: the hidden leading mark, the title
7
- * over its detail, the value over its detail, then the chevron or check.
8
+ * (with the unread dot and its hidden words) over its detail, the value
9
+ * over its detail, the tag, then the chevron or check. A truncated row
10
+ * sets its title's words in their own box, so the "…" never hides the dot.
8
11
  */
9
- export function ListRowContent({ id, title, detail, value, valueDetail, valueTone, valueBadge, leading, tone, titleAppearance, accessory, }) {
10
- return (_jsxs(_Fragment, { children: [leading === undefined ? null : (_jsx("span", { "aria-hidden": "true", className: "sw-list-row-leading", children: leading })), _jsxs("span", { className: "sw-list-row-label", children: [_jsx("span", { className: cx('sw-list-row-title', titleAppearance === 'plain' && 'sw-list-row-title-plain', tone === 'danger' && 'sw-list-row-title-danger'), id: `${id}-title`, children: title }), detail ? (_jsx("span", { className: "sw-list-row-detail", id: `${id}-detail`, children: detail })) : null] }), value === undefined ? null : (_jsxs("span", { className: "sw-list-row-end", children: [_jsx("span", { className: cx('sw-list-row-value', valueTone && `sw-list-row-value-${valueTone}`), id: `${id}-value`, children: valueBadge ? _jsx(Badge, { tone: valueBadge, children: value }) : value }), valueDetail ? (_jsx("span", { className: "sw-list-row-value-detail", id: `${id}-value-detail`, children: valueDetail })) : null] })), _jsx(ListRowAccessoryMark, { kind: accessory })] }));
12
+ export function ListRowContent({ id, title, detail, value, valueDetail, valueTone, valueBadge, leading, tone, titleAppearance, accessory, unreadLabel, tag, truncate, }) {
13
+ return (_jsxs(_Fragment, { children: [!hasSlot(leading) ? null : (_jsx("span", { "aria-hidden": "true", className: "sw-list-row-leading", children: leading })), _jsxs("span", { className: "sw-list-row-label", children: [_jsxs("span", { className: cx('sw-list-row-title', titleAppearance === 'plain' && 'sw-list-row-title-plain', tone === 'danger' && 'sw-list-row-title-danger'), id: `${id}-title`, children: [truncate ? (_jsx("span", { className: "sw-list-row-title-text", title: title, children: title })) : (title), unreadLabel === undefined ? null : (_jsx("span", { "aria-hidden": "true", className: "sw-list-row-unread" }))] }), unreadLabel === undefined ? null : (_jsx("span", { className: "sw-sr-only", id: `${id}-unread`, children: unreadLabel })), detail ? (_jsx("span", { className: "sw-list-row-detail", id: `${id}-detail`, title: truncate ? detail : undefined, children: detail })) : null] }), value === undefined ? null : (_jsxs("span", { className: "sw-list-row-end", children: [_jsx("span", { className: cx('sw-list-row-value', valueTone && `sw-list-row-value-${valueTone}`), id: `${id}-value`, children: valueBadge ? _jsx(Badge, { tone: valueBadge, children: value }) : value }), valueDetail ? (_jsx("span", { className: "sw-list-row-value-detail", id: `${id}-value-detail`, children: valueDetail })) : null] })), !hasSlot(tag) ? null : (_jsx("span", { className: "sw-list-row-tag", id: `${id}-tag`, children: tag })), _jsx(ListRowAccessoryMark, { kind: accessory })] }));
11
14
  }
@@ -0,0 +1,10 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * A row's list item: an `li` in a ListGroup's list, a `div` elsewhere. A
4
+ * row with an `action` holds that control after the row, a sibling of the
5
+ * row's button or link, so it is never inside the row's press area.
6
+ */
7
+ export declare function ListRowItem({ action, children, }: {
8
+ action: ReactNode;
9
+ children: ReactNode;
10
+ }): import("react").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useContext } from 'react';
3
+ import { cx } from '../../class-names.js';
4
+ import { ListRowContainer } from './list-row-container.js';
5
+ import { hasSlot } from '../slot.js';
6
+ /**
7
+ * A row's list item: an `li` in a ListGroup's list, a `div` elsewhere. A
8
+ * row with an `action` holds that control after the row, a sibling of the
9
+ * row's button or link, so it is never inside the row's press area.
10
+ */
11
+ export function ListRowItem({ action, children, }) {
12
+ const container = useContext(ListRowContainer);
13
+ const hasAction = hasSlot(action);
14
+ const content = (_jsxs(_Fragment, { children: [children, hasAction ? _jsx("div", { className: "sw-list-row-action", children: action }) : null] }));
15
+ const className = cx('sw-list-item', hasAction && 'sw-list-item-action');
16
+ return container === 'li' ? (_jsx("li", { className: className, children: content })) : (_jsx("div", { className: className, children: content }));
17
+ }
@@ -0,0 +1,17 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * A pressable row's name (its title, the unread words and its value) and
4
+ * description (its detail lines and its tag), by the ids its parts carry.
5
+ */
6
+ export declare function rowNaming(id: string, row: {
7
+ 'aria-label'?: string;
8
+ value?: string;
9
+ detail?: string;
10
+ valueDetail?: string;
11
+ unread: boolean;
12
+ tag?: ReactNode;
13
+ }): {
14
+ 'aria-label': string | undefined;
15
+ 'aria-labelledby': string | undefined;
16
+ 'aria-describedby': string | undefined;
17
+ };
@@ -0,0 +1,19 @@
1
+ import { hasSlot } from '../slot.js';
2
+ function ids(...parts) {
3
+ const joined = parts.filter(Boolean).join(' ');
4
+ return joined === '' ? undefined : joined;
5
+ }
6
+ /**
7
+ * A pressable row's name (its title, the unread words and its value) and
8
+ * description (its detail lines and its tag), by the ids its parts carry.
9
+ */
10
+ export function rowNaming(id, row) {
11
+ const hasTag = hasSlot(row.tag);
12
+ return {
13
+ 'aria-label': row['aria-label'],
14
+ 'aria-labelledby': row['aria-label']
15
+ ? undefined
16
+ : ids(`${id}-title`, row.unread && `${id}-unread`, row.value !== undefined && `${id}-value`),
17
+ 'aria-describedby': ids(Boolean(row.detail) && `${id}-detail`, Boolean(row.valueDetail) && `${id}-value-detail`, hasTag && `${id}-tag`),
18
+ };
19
+ }
@@ -0,0 +1,14 @@
1
+ /** How many quotes and lists the view draws inside one another. */
2
+ export declare const maxNesting = 16;
3
+ /**
4
+ * How deep the parser reads (a quote is one level, a list and its item
5
+ * two). markdown-it stops reading a block that nests deeper (its words are
6
+ * lost, and reported as `limit`). Its work on runs of link and image
7
+ * openers grows with this cap, so it stays low: 128 keeps 60-deep lists
8
+ * and reads 25,000 `![` in about 0.1 s.
9
+ */
10
+ export declare const maxParseNesting = 128;
11
+ /** The longest excerpt an omission keeps. */
12
+ export declare const maxExcerpt = 80;
13
+ /** How many omissions the reading lists; the counts keep the rest. */
14
+ export declare const maxOmissionsListed = 100;
@@ -0,0 +1,14 @@
1
+ /** How many quotes and lists the view draws inside one another. */
2
+ export const maxNesting = 16;
3
+ /**
4
+ * How deep the parser reads (a quote is one level, a list and its item
5
+ * two). markdown-it stops reading a block that nests deeper (its words are
6
+ * lost, and reported as `limit`). Its work on runs of link and image
7
+ * openers grows with this cap, so it stays low: 128 keeps 60-deep lists
8
+ * and reads 25,000 `![` in about 0.1 s.
9
+ */
10
+ export const maxParseNesting = 128;
11
+ /** The longest excerpt an omission keeps. */
12
+ export const maxExcerpt = 80;
13
+ /** How many omissions the reading lists; the counts keep the rest. */
14
+ export const maxOmissionsListed = 100;