@tensor_1/react 2.47.0 → 2.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +470 -1
- package/dist/{SchedulingCalendar-B6TakO2h.d.ts → SchedulingCalendar-WWyFL3Eo.d.ts} +51 -2
- package/dist/announce.d.ts +10 -0
- package/dist/announce.js +30 -0
- package/dist/chart-analytic.cjs +33 -25
- package/dist/chart-preset.cjs +23 -15
- package/dist/chart.cjs +23 -15
- package/dist/combobox-model.d.ts +10 -1
- package/dist/combobox-model.js +15 -0
- package/dist/command-palette-model.d.ts +44 -0
- package/dist/command-palette-model.js +36 -0
- package/dist/components/AccountMenu.d.ts +44 -0
- package/dist/components/AccountMenu.js +43 -0
- package/dist/components/AppShell.d.ts +45 -2
- package/dist/components/AppShell.js +116 -5
- package/dist/components/AuditLog.d.ts +128 -0
- package/dist/components/AuditLog.js +196 -0
- package/dist/components/BrandProfiles.js +4 -3
- package/dist/components/Breadcrumbs.d.ts +9 -2
- package/dist/components/Breadcrumbs.js +3 -2
- package/dist/components/Button.d.ts +12 -5
- package/dist/components/Button.js +7 -3
- package/dist/components/Card.d.ts +18 -1
- package/dist/components/Card.js +5 -1
- package/dist/components/ChangeHighlight.d.ts +30 -0
- package/dist/components/ChangeHighlight.js +21 -0
- package/dist/components/ChartCore.js +2 -1
- package/dist/components/Checkbox.d.ts +27 -1
- package/dist/components/Checkbox.js +31 -2
- package/dist/components/CodeDisplay.d.ts +65 -0
- package/dist/components/CodeDisplay.js +124 -0
- package/dist/components/Combobox.d.ts +27 -6
- package/dist/components/Combobox.js +195 -58
- package/dist/components/CommandPalette.d.ts +36 -1
- package/dist/components/CommandPalette.js +254 -2
- package/dist/components/ContactCard.d.ts +66 -0
- package/dist/components/ContactCard.js +74 -0
- package/dist/components/Count.d.ts +24 -0
- package/dist/components/Count.js +32 -0
- package/dist/components/DataWorkspace.d.ts +167 -0
- package/dist/components/DataWorkspace.js +383 -0
- package/dist/components/DateFieldParts.d.ts +2 -1
- package/dist/components/DateFieldParts.js +7 -0
- package/dist/components/DatePicker.d.ts +9 -2
- package/dist/components/DatePicker.js +67 -28
- package/dist/components/DateRangePicker.d.ts +12 -2
- package/dist/components/DateRangePicker.js +13 -12
- package/dist/components/DetailPage.d.ts +7 -1
- package/dist/components/DetailPage.js +2 -1
- package/dist/components/Dialogs.d.ts +71 -10
- package/dist/components/Dialogs.js +130 -30
- package/dist/components/DiffViewer.d.ts +23 -3
- package/dist/components/DiffViewer.js +26 -12
- package/dist/components/DiscardPrompt.js +16 -0
- package/dist/components/DisplayPreferences.d.ts +83 -0
- package/dist/components/DisplayPreferences.js +185 -0
- package/dist/components/EditorialHome.d.ts +23 -3
- package/dist/components/EditorialHome.js +16 -11
- package/dist/components/EntityPicker.d.ts +20 -3
- package/dist/components/EntityPicker.js +32 -13
- package/dist/components/FilterEditor.d.ts +18 -4
- package/dist/components/FilterEditor.js +141 -19
- package/dist/components/InfoTip.js +11 -12
- package/dist/components/InlineEdit.d.ts +87 -20
- package/dist/components/InlineEdit.js +197 -54
- package/dist/components/Input.d.ts +12 -2
- package/dist/components/Input.js +5 -3
- package/dist/components/Kbd.d.ts +6 -1
- package/dist/components/Kbd.js +6 -6
- package/dist/components/Lightbox.d.ts +41 -5
- package/dist/components/Lightbox.js +95 -9
- package/dist/components/MatchReview.d.ts +40 -1
- package/dist/components/MatchReview.js +130 -4
- package/dist/components/MetricInput.js +3 -2
- package/dist/components/Modal.d.ts +2 -1
- package/dist/components/Modal.js +13 -4
- package/dist/components/NavFavourites.d.ts +5 -0
- package/dist/components/NavFavourites.js +7 -7
- package/dist/components/OtpInput.d.ts +33 -0
- package/dist/components/OtpInput.js +123 -0
- package/dist/components/PageHeader.d.ts +7 -1
- package/dist/components/PageHeader.js +2 -2
- package/dist/components/Pagination.js +6 -5
- package/dist/components/PasswordInput.d.ts +34 -0
- package/dist/components/PasswordInput.js +101 -0
- package/dist/components/Peek.js +3 -3
- package/dist/components/Popover.js +2 -0
- package/dist/components/PrintLayout.d.ts +63 -0
- package/dist/components/PrintLayout.js +114 -0
- package/dist/components/QrCode.d.ts +47 -0
- package/dist/components/QrCode.js +55 -0
- package/dist/components/QrScanner.d.ts +95 -0
- package/dist/components/QrScanner.js +266 -0
- package/dist/components/RelationshipGraph.d.ts +32 -1
- package/dist/components/RelationshipGraph.js +18 -5
- package/dist/components/RelationshipGraphLayered.d.ts +1 -1
- package/dist/components/RelationshipGraphLayered.js +190 -115
- package/dist/components/RelationshipGraphList.js +33 -6
- package/dist/components/RuleBuilder.js +39 -7
- package/dist/components/SchedulingAgenda.d.ts +3 -1
- package/dist/components/SchedulingCalendar.d.ts +3 -1
- package/dist/components/SchedulingCalendar.js +43 -4
- package/dist/components/SchedulingResourceDay.d.ts +57 -0
- package/dist/components/SchedulingResourceDay.js +287 -0
- package/dist/components/SchedulingTimeGrid.d.ts +3 -1
- package/dist/components/SchedulingTimeline.d.ts +3 -1
- package/dist/components/SearchSuggest.d.ts +23 -3
- package/dist/components/SearchSuggest.js +48 -17
- package/dist/components/Segmented.d.ts +39 -4
- package/dist/components/Segmented.js +31 -3
- package/dist/components/Select.d.ts +3 -2
- package/dist/components/Select.js +1 -1
- package/dist/components/ShortcutHint.d.ts +11 -10
- package/dist/components/ShortcutHint.js +44 -38
- package/dist/components/Sidebar.d.ts +36 -4
- package/dist/components/Sidebar.js +37 -12
- package/dist/components/SortableList.d.ts +45 -0
- package/dist/components/SortableList.js +134 -0
- package/dist/components/Stat.d.ts +28 -3
- package/dist/components/Stat.js +13 -3
- package/dist/components/StatusPill.d.ts +14 -3
- package/dist/components/StatusPill.js +11 -3
- package/dist/components/Steps.js +2 -1
- package/dist/components/Table.d.ts +9 -2
- package/dist/components/Table.js +4 -2
- package/dist/components/TableControls.js +18 -58
- package/dist/components/TableOverflowProbe.d.ts +12 -0
- package/dist/components/TableOverflowProbe.js +27 -0
- package/dist/components/Tabs.d.ts +2 -1
- package/dist/components/Tabs.js +1 -0
- package/dist/components/TagInput.d.ts +67 -0
- package/dist/components/TagInput.js +342 -0
- package/dist/components/Textarea.d.ts +5 -0
- package/dist/components/Textarea.js +2 -2
- package/dist/components/TimePicker.d.ts +80 -0
- package/dist/components/TimePicker.js +347 -0
- package/dist/components/Toast.js +3 -2
- package/dist/components/Tooltip.d.ts +8 -4
- package/dist/components/Tooltip.js +24 -21
- package/dist/components/Tree.d.ts +8 -0
- package/dist/components/Tree.js +11 -5
- package/dist/display-preferences-model.d.ts +53 -0
- package/dist/display-preferences-model.js +64 -0
- package/dist/emoji.cjs +28 -35
- package/dist/filter-model.d.ts +59 -3
- package/dist/filter-model.js +67 -2
- package/dist/hooks/useChangeHighlight.d.ts +33 -0
- package/dist/hooks/useChangeHighlight.js +44 -0
- package/dist/hooks/useDialog.d.ts +5 -0
- package/dist/hooks/useDialog.js +3 -2
- package/dist/hooks/useOptimisticValue.d.ts +51 -0
- package/dist/hooks/useOptimisticValue.js +61 -0
- package/dist/hooks/useReducedMotion.d.ts +4 -1
- package/dist/hooks/useReducedMotion.js +7 -1
- package/dist/hooks/useResolvedFilterLabels.d.ts +15 -0
- package/dist/hooks/useResolvedFilterLabels.js +34 -0
- package/dist/hooks/useVirtualRows.d.ts +21 -2
- package/dist/hooks/useVirtualRows.js +30 -3
- package/dist/icons/Icon.d.ts +1 -1
- package/dist/index.cjs +18644 -13210
- package/dist/index.d.cts +3003 -699
- package/dist/index.d.ts +42 -16
- package/dist/index.js +139 -7
- package/dist/labels-context.d.ts +90 -3
- package/dist/overlay-stack.d.ts +6 -0
- package/dist/overlay-stack.js +13 -0
- package/dist/qr-model.d.ts +65 -0
- package/dist/qr-model.js +323 -0
- package/dist/relationship-graph-model.d.ts +78 -3
- package/dist/relationship-graph-model.js +112 -5
- package/dist/reorder.d.ts +30 -0
- package/dist/reorder.js +74 -0
- package/dist/rich-text.cjs +4 -2
- package/dist/scheduling-resource-model.d.ts +76 -0
- package/dist/scheduling-resource-model.js +74 -0
- package/dist/search-suggest-model.js +3 -1
- package/dist/shell-context.d.ts +11 -0
- package/dist/shell-context.js +7 -0
- package/dist/time-field-model.d.ts +46 -0
- package/dist/time-field-model.js +152 -0
- package/dist/toast-store.d.ts +17 -1
- package/dist/toast-store.js +23 -7
- package/package.json +2 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/** The words of a `QrScanner` (HAR-1254). */
|
|
5
|
+
interface QrScannerLabels {
|
|
6
|
+
/** The viewfinder region's name. */
|
|
7
|
+
viewfinder: string;
|
|
8
|
+
/** Starts the camera (the browser asks for permission after it). */
|
|
9
|
+
start: string;
|
|
10
|
+
/** Stops the camera. */
|
|
11
|
+
stop: string;
|
|
12
|
+
/** While the browser's permission prompt is open. */
|
|
13
|
+
asking: string;
|
|
14
|
+
/** While the camera runs and nothing was read yet. */
|
|
15
|
+
scanning: string;
|
|
16
|
+
/** Camera access was refused. */
|
|
17
|
+
deniedTitle: string;
|
|
18
|
+
/** How to allow it again: the browser keeps the refusal until the person changes it. */
|
|
19
|
+
deniedBody: string;
|
|
20
|
+
/** No camera API (an old browser, or a page not served over HTTPS). */
|
|
21
|
+
unsupportedTitle: string;
|
|
22
|
+
unsupportedBody: string;
|
|
23
|
+
/** No camera found, or it is in use by another app. */
|
|
24
|
+
noCameraTitle: string;
|
|
25
|
+
noCameraBody: string;
|
|
26
|
+
/** The camera started but sends no picture. */
|
|
27
|
+
noFrameTitle: string;
|
|
28
|
+
noFrameBody: string;
|
|
29
|
+
/** Shown after `hintAfter` ms of frames without a code. */
|
|
30
|
+
unreadable: string;
|
|
31
|
+
/** Tries the camera again. */
|
|
32
|
+
retry: string;
|
|
33
|
+
/** The torch toggle's name. */
|
|
34
|
+
torch: string;
|
|
35
|
+
/** Announced once when a code was read; `{value}` is the text. */
|
|
36
|
+
read: string;
|
|
37
|
+
}
|
|
38
|
+
declare const DEFAULT_QR_SCANNER_LABELS: QrScannerLabels;
|
|
39
|
+
/**
|
|
40
|
+
* What the scanner is doing. `idle` shows the explainer and the start button; `asking` waits for
|
|
41
|
+
* the browser's prompt; `scanning` shows the picture; `denied`, `unsupported`, `no-camera` and
|
|
42
|
+
* `no-frame` say what went wrong and how to go on.
|
|
43
|
+
*/
|
|
44
|
+
type QrScannerStatus = 'idle' | 'asking' | 'scanning' | 'denied' | 'unsupported' | 'no-camera' | 'no-frame';
|
|
45
|
+
/** One camera frame for the consumer's decoder. It never leaves the device unless the decoder sends it. */
|
|
46
|
+
interface QrScannerFrame {
|
|
47
|
+
/** The frame's pixels, at most `maxFrameSize` on the long side. */
|
|
48
|
+
imageData: ImageData;
|
|
49
|
+
/** The live video element, for a decoder that reads it directly (`BarcodeDetector.detect(video)`). */
|
|
50
|
+
video: HTMLVideoElement;
|
|
51
|
+
}
|
|
52
|
+
/** Returns the text it read, or nothing. Throwing counts as nothing read. */
|
|
53
|
+
type QrScannerDecoder = (frame: QrScannerFrame) => string | null | undefined | Promise<string | null | undefined>;
|
|
54
|
+
interface QrScannerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onError'> {
|
|
55
|
+
/** Your decoder (a WASM or JS QR reader, or `BarcodeDetector`). The kit ships none and calls it with frames only. */
|
|
56
|
+
decode: QrScannerDecoder;
|
|
57
|
+
/** Called with each new text read. The same text again is ignored for `repeatAfter` ms. */
|
|
58
|
+
onResult: (value: string) => void;
|
|
59
|
+
/** Why the page wants the camera, shown before the browser's own prompt (and after a refusal). */
|
|
60
|
+
explainer?: ReactNode;
|
|
61
|
+
/** The manual entry (a field and a button): always rendered, in every state, after the viewfinder. */
|
|
62
|
+
manualEntry?: ReactNode;
|
|
63
|
+
/**
|
|
64
|
+
* Starts the camera without the start button when the browser says permission was already
|
|
65
|
+
* given (`navigator.permissions`). Default `true`; the explainer is still shown first otherwise.
|
|
66
|
+
*/
|
|
67
|
+
autoStart?: boolean;
|
|
68
|
+
/** Called on every status change, e.g. to log it or to move focus to the manual field after a refusal. */
|
|
69
|
+
onStatusChange?: (status: QrScannerStatus) => void;
|
|
70
|
+
/** Milliseconds between decode attempts. Default 200. */
|
|
71
|
+
interval?: number;
|
|
72
|
+
/** The long side of the frame handed to the decoder, in pixels. Default 640. */
|
|
73
|
+
maxFrameSize?: number;
|
|
74
|
+
/** The same text read again within this many milliseconds is not reported twice. Default 2000. */
|
|
75
|
+
repeatAfter?: number;
|
|
76
|
+
/** Milliseconds of frames without a code before the "still nothing read" hint. Default 8000. */
|
|
77
|
+
hintAfter?: number;
|
|
78
|
+
/** Milliseconds after the camera started without a single frame before `no-frame`. Default 5000. */
|
|
79
|
+
frameTimeout?: number;
|
|
80
|
+
/** `environment` (default, the back camera) or `user`. */
|
|
81
|
+
facingMode?: 'environment' | 'user';
|
|
82
|
+
/** The words. Default: `UixLabelsProvider` `qrScanner`, then English. */
|
|
83
|
+
labels?: Partial<QrScannerLabels>;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A camera viewfinder that reads QR codes with a decoder the product supplies (HAR-1254; SHOPx
|
|
87
|
+
* SHOP-07 receipt scan, SHOP-09 till; MOTUS circulation intake). It explains why it wants the
|
|
88
|
+
* camera before the browser asks, says plainly when access is blocked, missing or unsupported
|
|
89
|
+
* and how to go on, and always keeps the manual entry next to it. Frames go to `decode` and
|
|
90
|
+
* nowhere else: the component makes no network request. The camera stops on unmount, on Stop
|
|
91
|
+
* and while the page is hidden.
|
|
92
|
+
*/
|
|
93
|
+
declare function QrScanner({ decode, onResult, explainer, manualEntry, autoStart, onStatusChange, interval, maxFrameSize, repeatAfter, hintAfter, frameTimeout, facingMode, labels: labelsProp, className, ...props }: QrScannerProps): react.JSX.Element;
|
|
94
|
+
|
|
95
|
+
export { DEFAULT_QR_SCANNER_LABELS, QrScanner, type QrScannerDecoder, type QrScannerFrame, type QrScannerLabels, type QrScannerProps, type QrScannerStatus };
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
|
+
import { cx } from "../cx.js";
|
|
5
|
+
import { fillLabel } from "../fill-label.js";
|
|
6
|
+
import { useUixLabels } from "../labels-context.js";
|
|
7
|
+
import { Button } from "./Button.js";
|
|
8
|
+
const DEFAULT_QR_SCANNER_LABELS = {
|
|
9
|
+
viewfinder: "Camera viewfinder",
|
|
10
|
+
start: "Use the camera",
|
|
11
|
+
stop: "Stop the camera",
|
|
12
|
+
asking: "Allow the camera in your browser\u2019s prompt.",
|
|
13
|
+
scanning: "Point the camera at the code.",
|
|
14
|
+
deniedTitle: "The camera is blocked",
|
|
15
|
+
deniedBody: "Allow the camera for this site in the browser\u2019s address bar or site settings, then try again. You can also type the code.",
|
|
16
|
+
unsupportedTitle: "This browser cannot use the camera here",
|
|
17
|
+
unsupportedBody: "Type the code instead, or open this page in a current browser over a secure connection.",
|
|
18
|
+
noCameraTitle: "No camera is available",
|
|
19
|
+
noCameraBody: "Connect a camera or close the app that is using it, then try again. You can also type the code.",
|
|
20
|
+
noFrameTitle: "The camera sends no picture",
|
|
21
|
+
noFrameBody: "Check that nothing covers the lens, then try again. You can also type the code.",
|
|
22
|
+
unreadable: "Still nothing read. Hold the code flat and steady, in good light, or type it.",
|
|
23
|
+
retry: "Try again",
|
|
24
|
+
torch: "Torch",
|
|
25
|
+
read: "Code read: {value}"
|
|
26
|
+
};
|
|
27
|
+
const ERROR_STATUS = {
|
|
28
|
+
NotAllowedError: "denied",
|
|
29
|
+
SecurityError: "denied",
|
|
30
|
+
PermissionDeniedError: "denied",
|
|
31
|
+
NotFoundError: "no-camera",
|
|
32
|
+
DevicesNotFoundError: "no-camera",
|
|
33
|
+
NotReadableError: "no-camera",
|
|
34
|
+
TrackStartError: "no-camera",
|
|
35
|
+
OverconstrainedError: "no-camera"
|
|
36
|
+
};
|
|
37
|
+
const useIsomorphicLayoutEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
|
|
38
|
+
const cameraApi = () => typeof navigator !== "undefined" && typeof navigator.mediaDevices?.getUserMedia === "function";
|
|
39
|
+
function QrScanner({
|
|
40
|
+
decode,
|
|
41
|
+
onResult,
|
|
42
|
+
explainer,
|
|
43
|
+
manualEntry,
|
|
44
|
+
autoStart = true,
|
|
45
|
+
onStatusChange,
|
|
46
|
+
interval = 200,
|
|
47
|
+
maxFrameSize = 640,
|
|
48
|
+
repeatAfter = 2e3,
|
|
49
|
+
hintAfter = 8e3,
|
|
50
|
+
frameTimeout = 5e3,
|
|
51
|
+
facingMode = "environment",
|
|
52
|
+
labels: labelsProp,
|
|
53
|
+
className,
|
|
54
|
+
...props
|
|
55
|
+
}) {
|
|
56
|
+
const uixLabels = useUixLabels();
|
|
57
|
+
const labels = { ...DEFAULT_QR_SCANNER_LABELS, ...uixLabels.qrScanner, ...labelsProp };
|
|
58
|
+
const [status, setStatusState] = useState("idle");
|
|
59
|
+
const [hint, setHint] = useState(false);
|
|
60
|
+
const [torch, setTorch] = useState(null);
|
|
61
|
+
const [read, setRead] = useState(null);
|
|
62
|
+
const videoRef = useRef(null);
|
|
63
|
+
const streamRef = useRef(null);
|
|
64
|
+
const runRef = useRef(0);
|
|
65
|
+
const resumeRef = useRef(false);
|
|
66
|
+
const live = useRef({ decode, onResult, onStatusChange, interval, maxFrameSize, repeatAfter, hintAfter, frameTimeout });
|
|
67
|
+
live.current = { decode, onResult, onStatusChange, interval, maxFrameSize, repeatAfter, hintAfter, frameTimeout };
|
|
68
|
+
const statusRef = useRef(status);
|
|
69
|
+
const setStatus = useCallback((next) => {
|
|
70
|
+
if (statusRef.current === next) return;
|
|
71
|
+
statusRef.current = next;
|
|
72
|
+
setStatusState(next);
|
|
73
|
+
live.current.onStatusChange?.(next);
|
|
74
|
+
}, []);
|
|
75
|
+
const release = useCallback(() => {
|
|
76
|
+
runRef.current += 1;
|
|
77
|
+
streamRef.current?.getTracks().forEach((track) => track.stop());
|
|
78
|
+
streamRef.current = null;
|
|
79
|
+
if (videoRef.current) videoRef.current.srcObject = null;
|
|
80
|
+
setTorch(null);
|
|
81
|
+
setHint(false);
|
|
82
|
+
setRead(null);
|
|
83
|
+
}, []);
|
|
84
|
+
const start = useCallback(async () => {
|
|
85
|
+
if (!cameraApi()) {
|
|
86
|
+
setStatus("unsupported");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
release();
|
|
90
|
+
const run = runRef.current;
|
|
91
|
+
setStatus("asking");
|
|
92
|
+
let stream;
|
|
93
|
+
try {
|
|
94
|
+
stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: { ideal: facingMode } }, audio: false });
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (run !== runRef.current) return;
|
|
97
|
+
setStatus(ERROR_STATUS[error?.name ?? ""] ?? "no-camera");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (run !== runRef.current) {
|
|
101
|
+
stream.getTracks().forEach((track2) => track2.stop());
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
streamRef.current = stream;
|
|
105
|
+
const track = stream.getVideoTracks()[0];
|
|
106
|
+
setTorch(track?.getCapabilities?.()?.torch ? false : null);
|
|
107
|
+
track?.addEventListener("ended", () => {
|
|
108
|
+
if (run === runRef.current) {
|
|
109
|
+
release();
|
|
110
|
+
setStatus("no-frame");
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
setStatus("scanning");
|
|
114
|
+
}, [facingMode, release, setStatus]);
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (status !== "scanning") return void 0;
|
|
117
|
+
const video = videoRef.current;
|
|
118
|
+
const stream = streamRef.current;
|
|
119
|
+
if (!video || !stream) return void 0;
|
|
120
|
+
const run = runRef.current;
|
|
121
|
+
video.srcObject = stream;
|
|
122
|
+
void video.play?.()?.catch?.(() => void 0);
|
|
123
|
+
const canvas = document.createElement("canvas");
|
|
124
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
125
|
+
const startedAt = Date.now();
|
|
126
|
+
let lastHit = startedAt;
|
|
127
|
+
let lastValue = null;
|
|
128
|
+
let timer;
|
|
129
|
+
let sawFrame = false;
|
|
130
|
+
const step = async () => {
|
|
131
|
+
if (run !== runRef.current) return;
|
|
132
|
+
const { decode: read2, maxFrameSize: max, frameTimeout: timeout, hintAfter: hintMs, repeatAfter: repeat, interval: wait } = live.current;
|
|
133
|
+
const width = video.videoWidth;
|
|
134
|
+
const height = video.videoHeight;
|
|
135
|
+
if (width > 0 && height > 0 && video.readyState >= 2 && context) {
|
|
136
|
+
sawFrame = true;
|
|
137
|
+
const scale = Math.min(1, max / Math.max(width, height));
|
|
138
|
+
canvas.width = Math.round(width * scale);
|
|
139
|
+
canvas.height = Math.round(height * scale);
|
|
140
|
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
141
|
+
let value;
|
|
142
|
+
try {
|
|
143
|
+
value = await read2({ imageData: context.getImageData(0, 0, canvas.width, canvas.height), video });
|
|
144
|
+
} catch {
|
|
145
|
+
value = null;
|
|
146
|
+
}
|
|
147
|
+
if (run !== runRef.current) return;
|
|
148
|
+
const now = Date.now();
|
|
149
|
+
if (value) {
|
|
150
|
+
lastHit = now;
|
|
151
|
+
setHint(false);
|
|
152
|
+
if (!lastValue || lastValue.value !== value || now - lastValue.at > repeat) {
|
|
153
|
+
lastValue = { value, at: now };
|
|
154
|
+
setRead({ value, at: now });
|
|
155
|
+
live.current.onResult(value);
|
|
156
|
+
}
|
|
157
|
+
} else if (now - lastHit > hintMs) setHint(true);
|
|
158
|
+
} else if (!sawFrame && Date.now() - startedAt > timeout) {
|
|
159
|
+
release();
|
|
160
|
+
setStatus("no-frame");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
timer = setTimeout(() => {
|
|
164
|
+
void step();
|
|
165
|
+
}, wait);
|
|
166
|
+
};
|
|
167
|
+
timer = setTimeout(() => {
|
|
168
|
+
void step();
|
|
169
|
+
}, 0);
|
|
170
|
+
return () => clearTimeout(timer);
|
|
171
|
+
}, [status, release, setStatus]);
|
|
172
|
+
const startRef = useRef(start);
|
|
173
|
+
startRef.current = start;
|
|
174
|
+
useIsomorphicLayoutEffect(() => {
|
|
175
|
+
if (!cameraApi()) setStatus("unsupported");
|
|
176
|
+
}, [setStatus]);
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
if (!cameraApi()) return void 0;
|
|
179
|
+
if (status !== "idle" || typeof navigator === "undefined" || !navigator.permissions?.query) return void 0;
|
|
180
|
+
let cancelled = false;
|
|
181
|
+
navigator.permissions.query({ name: "camera" }).then((result) => {
|
|
182
|
+
if (cancelled) return;
|
|
183
|
+
if (result.state === "denied") setStatus("denied");
|
|
184
|
+
else if (result.state === "granted" && autoStart) void startRef.current();
|
|
185
|
+
}, () => void 0);
|
|
186
|
+
return () => {
|
|
187
|
+
cancelled = true;
|
|
188
|
+
};
|
|
189
|
+
}, []);
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
const onVisibility = () => {
|
|
192
|
+
if (document.hidden && streamRef.current) {
|
|
193
|
+
release();
|
|
194
|
+
setStatus("idle");
|
|
195
|
+
resumeRef.current = true;
|
|
196
|
+
} else if (!document.hidden && resumeRef.current) {
|
|
197
|
+
resumeRef.current = false;
|
|
198
|
+
void startRef.current();
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
202
|
+
return () => document.removeEventListener("visibilitychange", onVisibility);
|
|
203
|
+
}, [release, setStatus]);
|
|
204
|
+
useEffect(() => () => release(), [release]);
|
|
205
|
+
const facingRef = useRef(facingMode);
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
if (facingRef.current === facingMode) return;
|
|
208
|
+
facingRef.current = facingMode;
|
|
209
|
+
if (statusRef.current === "scanning" || statusRef.current === "asking") void start();
|
|
210
|
+
}, [facingMode, start]);
|
|
211
|
+
useEffect(() => {
|
|
212
|
+
if (!read) return void 0;
|
|
213
|
+
const timer = setTimeout(() => setRead((current) => current === read ? { ...current, at: -1 } : current), 1200);
|
|
214
|
+
return () => clearTimeout(timer);
|
|
215
|
+
}, [read]);
|
|
216
|
+
const toggleTorch = async () => {
|
|
217
|
+
const track = streamRef.current?.getVideoTracks()[0];
|
|
218
|
+
if (!track || torch === null) return;
|
|
219
|
+
try {
|
|
220
|
+
await track.applyConstraints({ advanced: [{ torch: !torch }] });
|
|
221
|
+
setTorch(!torch);
|
|
222
|
+
} catch {
|
|
223
|
+
setTorch(null);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
const problem = status === "denied" ? [labels.deniedTitle, labels.deniedBody] : status === "unsupported" ? [labels.unsupportedTitle, labels.unsupportedBody] : status === "no-camera" ? [labels.noCameraTitle, labels.noCameraBody] : status === "no-frame" ? [labels.noFrameTitle, labels.noFrameBody] : null;
|
|
227
|
+
const message = status === "asking" ? labels.asking : status === "scanning" ? hint ? labels.unreadable : labels.scanning : null;
|
|
228
|
+
const flash = read !== null && read.at >= 0;
|
|
229
|
+
return /* @__PURE__ */ jsxs("div", { className: cx("uix-qr-scanner", className), "data-status": status, ...props, children: [
|
|
230
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-qr-scanner__frame", role: "group", "aria-label": labels.viewfinder, "data-read": flash || void 0, children: [
|
|
231
|
+
/* @__PURE__ */ jsx("video", { ref: videoRef, className: "uix-qr-scanner__video", muted: true, playsInline: true, "aria-hidden": "true", hidden: status !== "scanning" }),
|
|
232
|
+
status === "scanning" && /* @__PURE__ */ jsx("span", { className: "uix-qr-scanner__target", "aria-hidden": "true" }),
|
|
233
|
+
(status === "idle" || status === "asking" || problem) && /* @__PURE__ */ jsxs("div", { className: "uix-qr-scanner__panel", children: [
|
|
234
|
+
problem ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
235
|
+
/* @__PURE__ */ jsx("p", { className: "uix-qr-scanner__title", children: problem[0] }),
|
|
236
|
+
/* @__PURE__ */ jsx("p", { className: "uix-qr-scanner__body", children: problem[1] }),
|
|
237
|
+
status === "denied" && explainer != null && /* @__PURE__ */ jsx("div", { className: "uix-qr-scanner__explainer", children: explainer })
|
|
238
|
+
] }) : explainer != null && /* @__PURE__ */ jsx("div", { className: "uix-qr-scanner__explainer", children: explainer }),
|
|
239
|
+
status === "idle" && /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: () => {
|
|
240
|
+
void start();
|
|
241
|
+
}, children: labels.start }),
|
|
242
|
+
status === "asking" && /* @__PURE__ */ jsx(Button, { variant: "primary", loading: true, children: labels.start }),
|
|
243
|
+
(status === "denied" || status === "no-camera" || status === "no-frame") && /* @__PURE__ */ jsx(Button, { onClick: () => {
|
|
244
|
+
void start();
|
|
245
|
+
}, children: labels.retry })
|
|
246
|
+
] })
|
|
247
|
+
] }),
|
|
248
|
+
status === "scanning" && /* @__PURE__ */ jsxs("div", { className: "uix-qr-scanner__bar", children: [
|
|
249
|
+
/* @__PURE__ */ jsx("span", { className: "uix-qr-scanner__message", "data-hint": hint || void 0, children: message }),
|
|
250
|
+
torch !== null && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", "aria-pressed": torch, onClick: () => {
|
|
251
|
+
void toggleTorch();
|
|
252
|
+
}, children: labels.torch }),
|
|
253
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", onClick: () => {
|
|
254
|
+
release();
|
|
255
|
+
setStatus("idle");
|
|
256
|
+
}, children: labels.stop })
|
|
257
|
+
] }),
|
|
258
|
+
status === "asking" && /* @__PURE__ */ jsx("p", { className: "uix-qr-scanner__message", children: message }),
|
|
259
|
+
/* @__PURE__ */ jsx("div", { role: "status", className: "uix-visually-hidden", children: read ? fillLabel(labels.read, { value: read.value }) : problem ? problem[0] : "" }),
|
|
260
|
+
manualEntry != null && /* @__PURE__ */ jsx("div", { className: "uix-qr-scanner__manual", children: manualEntry })
|
|
261
|
+
] });
|
|
262
|
+
}
|
|
263
|
+
export {
|
|
264
|
+
DEFAULT_QR_SCANNER_LABELS,
|
|
265
|
+
QrScanner
|
|
266
|
+
};
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { RelationshipGraphNode, RelationshipGraphEdge, RelationshipGraphCluster } from '../relationship-graph-model.js';
|
|
3
|
+
import { RelationshipGraphNode, RelationshipGraphEdge, RelationshipGraphTone, RelationshipGraphCluster } from '../relationship-graph-model.js';
|
|
4
4
|
|
|
5
5
|
interface RelationshipGraphLegendItem {
|
|
6
6
|
id: string;
|
|
7
7
|
label: string;
|
|
8
|
+
/** Layered: the swatch takes the tone the matching nodes or edges take (HAR-1639). */
|
|
9
|
+
tone?: RelationshipGraphTone;
|
|
10
|
+
/** Layered: the swatch is a line (`edge`, default) or a node box (`node`). */
|
|
11
|
+
shape?: 'edge' | 'node';
|
|
12
|
+
/** Layered: a dashed line swatch for an edge `variant="dashed"`. */
|
|
13
|
+
variant?: 'solid' | 'dashed';
|
|
8
14
|
}
|
|
9
15
|
/** Every visible or announced string. Pass translations; English defaults fill the gaps. */
|
|
10
16
|
interface RelationshipGraphLabels {
|
|
@@ -37,7 +43,17 @@ interface RelationshipGraphLabels {
|
|
|
37
43
|
graphSummary: (nodes: number, edges: number) => string;
|
|
38
44
|
relationship: string;
|
|
39
45
|
conflict: string;
|
|
46
|
+
/** @deprecated No longer read: the list names a node's neighbours under `connectedHeading`. A
|
|
47
|
+
* `connectedTo` supplied without `connectedHeading` still sets that heading, from its words. */
|
|
40
48
|
connectedTo: (names: string) => string;
|
|
49
|
+
/** The relationship list: the heading of a node's neighbours, e.g. "Connected to". */
|
|
50
|
+
connectedHeading: string;
|
|
51
|
+
/** The relationship list: shows every neighbour of a node with more than eight. */
|
|
52
|
+
showAllRelations: (count: number) => string;
|
|
53
|
+
/** The relationship list: back to the first eight neighbours. */
|
|
54
|
+
showFewerRelations: string;
|
|
55
|
+
/** Layered: ends a hub's description, which names only its first five relationships. */
|
|
56
|
+
moreRelations: (count: number, inList: boolean) => string;
|
|
41
57
|
noConnections: string;
|
|
42
58
|
expandNeighbors: string;
|
|
43
59
|
openDetails: string;
|
|
@@ -54,6 +70,10 @@ interface RelationshipGraphLabels {
|
|
|
54
70
|
retry: string;
|
|
55
71
|
emptyTitle: string;
|
|
56
72
|
emptyHint: string;
|
|
73
|
+
/** Layered: a node's outgoing relationship in its description (HAR-1639), e.g. "depends on Payments DB". */
|
|
74
|
+
relationTo: (relation: string, target: string) => string;
|
|
75
|
+
/** Layered: a node's incoming relationship in its description, e.g. "depends on, from Checkout". */
|
|
76
|
+
relationFrom: (relation: string, source: string) => string;
|
|
57
77
|
}
|
|
58
78
|
declare const DEFAULT_RELATIONSHIP_GRAPH_LABELS: RelationshipGraphLabels;
|
|
59
79
|
interface RelationshipGraphProps {
|
|
@@ -120,6 +140,17 @@ interface RelationshipGraphProps {
|
|
|
120
140
|
/** Layered: controlled roving focus. */
|
|
121
141
|
focusId?: string;
|
|
122
142
|
onFocusChange?: (id: string) => void;
|
|
143
|
+
/**
|
|
144
|
+
* Layered: brings this node into view without moving focus (HAR-1639), e.g. the row picked in
|
|
145
|
+
* a table beside the map while the keyboard stays in the table. Smooth, or at once with reduced
|
|
146
|
+
* motion. Changing it again scrolls again; `focusId` focuses as well.
|
|
147
|
+
*/
|
|
148
|
+
revealId?: string;
|
|
149
|
+
/**
|
|
150
|
+
* Layered: draws the root with a heavier border. Default: when `rootId` is given. A map with
|
|
151
|
+
* several starting points (no `rootId`) has no one root, so nothing is marked (HAR-1639).
|
|
152
|
+
*/
|
|
153
|
+
markRoot?: boolean;
|
|
123
154
|
}
|
|
124
155
|
/** Bounded, deterministic SVG relationship view with an equivalent accessible list. */
|
|
125
156
|
declare function RelationshipGraph(props: RelationshipGraphProps): react.JSX.Element;
|
|
@@ -33,6 +33,10 @@ const DEFAULT_RELATIONSHIP_GRAPH_LABELS = {
|
|
|
33
33
|
relationship: "Relationship",
|
|
34
34
|
conflict: "conflict",
|
|
35
35
|
connectedTo: (names) => `Connected to ${names}`,
|
|
36
|
+
connectedHeading: "Connected to",
|
|
37
|
+
showAllRelations: (count) => `Show all ${count}`,
|
|
38
|
+
showFewerRelations: "Show fewer",
|
|
39
|
+
moreRelations: (count, inList) => `and ${count} more${inList ? ", listed in the relationship list" : ""}`,
|
|
36
40
|
noConnections: "No visible connections",
|
|
37
41
|
expandNeighbors: "Expand neighbors",
|
|
38
42
|
openDetails: "Open details",
|
|
@@ -47,7 +51,9 @@ const DEFAULT_RELATIONSHIP_GRAPH_LABELS = {
|
|
|
47
51
|
loading: "Loading relationships\u2026",
|
|
48
52
|
retry: "Try again",
|
|
49
53
|
emptyTitle: "No relationships to display.",
|
|
50
|
-
emptyHint: "Adjust the neighborhood or filters to include a node."
|
|
54
|
+
emptyHint: "Adjust the neighborhood or filters to include a node.",
|
|
55
|
+
relationTo: (relation, target) => `${relation} ${target}`,
|
|
56
|
+
relationFrom: (relation, source) => `${relation}, from ${source}`
|
|
51
57
|
};
|
|
52
58
|
const EMPTY_IDS = /* @__PURE__ */ new Set();
|
|
53
59
|
const EMPTY_LEGEND = [];
|
|
@@ -70,7 +76,13 @@ function radialViewBox(nodes) {
|
|
|
70
76
|
return `${round(minX)} ${round(minY)} ${width} ${height}`;
|
|
71
77
|
}
|
|
72
78
|
function RelationshipGraph(props) {
|
|
73
|
-
const labels = useMemo(() =>
|
|
79
|
+
const labels = useMemo(() => {
|
|
80
|
+
const merged = { ...DEFAULT_RELATIONSHIP_GRAPH_LABELS, ...props.labels };
|
|
81
|
+
if (props.labels?.connectedTo && props.labels.connectedHeading === void 0) {
|
|
82
|
+
merged.connectedHeading = props.labels.connectedTo("").replace(/[\s:,]+$/u, "");
|
|
83
|
+
}
|
|
84
|
+
return merged;
|
|
85
|
+
}, [props.labels]);
|
|
74
86
|
const stateClass = cx("uix-relationship-graph uix-relationship-graph--state", props.className);
|
|
75
87
|
if (props.loading) return /* @__PURE__ */ jsx("div", { className: stateClass, role: "status", children: labels.loading });
|
|
76
88
|
if (props.error) {
|
|
@@ -102,7 +114,8 @@ function RadialRelationshipGraph({
|
|
|
102
114
|
legend = EMPTY_LEGEND,
|
|
103
115
|
className,
|
|
104
116
|
labels,
|
|
105
|
-
showList = true
|
|
117
|
+
showList = true,
|
|
118
|
+
controls = "full"
|
|
106
119
|
}) {
|
|
107
120
|
const bounded = useMemo(() => boundRelationshipGraph(nodes, edges, maxNodes), [nodes, edges, maxNodes]);
|
|
108
121
|
const positioned = useMemo(() => layoutRelationshipGraph(bounded.nodes, bounded.edges, selectedId), [bounded, selectedId]);
|
|
@@ -138,7 +151,7 @@ function RadialRelationshipGraph({
|
|
|
138
151
|
setPan({ x: 0, y: 0 });
|
|
139
152
|
};
|
|
140
153
|
return /* @__PURE__ */ jsxs("section", { className: cx("uix-relationship-graph", `uix-relationship-graph--${density}`, className), "aria-label": labels.graph, children: [
|
|
141
|
-
/* @__PURE__ */ jsxs("div", { className: "uix-relationship-graph__toolbar", "aria-label": labels.controls, children: [
|
|
154
|
+
controls !== "none" && /* @__PURE__ */ jsxs("div", { className: "uix-relationship-graph__toolbar", "aria-label": labels.controls, children: [
|
|
142
155
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setPan((value) => ({ ...value, x: value.x - 24 })), "aria-label": labels.panLeft, children: labels.left }),
|
|
143
156
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setPan((value) => ({ ...value, y: value.y - 24 })), "aria-label": labels.panUp, children: labels.up }),
|
|
144
157
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setPan((value) => ({ ...value, y: value.y + 24 })), "aria-label": labels.panDown, children: labels.down }),
|
|
@@ -153,7 +166,7 @@ function RadialRelationshipGraph({
|
|
|
153
166
|
bounded.edges.map((edge) => {
|
|
154
167
|
const source = positionedById.get(edge.source);
|
|
155
168
|
const target = positionedById.get(edge.target);
|
|
156
|
-
return /* @__PURE__ */ jsx("line", { x1: source.x * RADIAL_SCALE_X, y1: source.y * RADIAL_SCALE_Y, x2: target.x * RADIAL_SCALE_X, y2: target.y * RADIAL_SCALE_Y, className: "uix-relationship-graph__edge", "data-typed": edge.type != null && typeColor.has(edge.type) || void 0, style: edge.type != null && typeColor.has(edge.type) ? { "--uix-graph-type-color": typeColor.get(edge.type) } : void 0, "data-highlighted": highlightedEdgeIds.has(edge.id) || void 0, "data-conflicted": conflictedEdgeIds.has(edge.id) || void 0, children: /* @__PURE__ */ jsx("title", { children: edge.label ?? edge.type ?? labels.relationship }) }, edge.id);
|
|
169
|
+
return /* @__PURE__ */ jsx("line", { x1: source.x * RADIAL_SCALE_X, y1: source.y * RADIAL_SCALE_Y, x2: target.x * RADIAL_SCALE_X, y2: target.y * RADIAL_SCALE_Y, className: "uix-relationship-graph__edge", "data-typed": edge.type != null && typeColor.has(edge.type) || void 0, style: edge.type != null && typeColor.has(edge.type) ? { "--uix-graph-type-color": typeColor.get(edge.type) } : void 0, "data-highlighted": highlightedEdgeIds.has(edge.id) || void 0, "data-conflicted": conflictedEdgeIds.has(edge.id) || void 0, "data-tone": edge.tone, "data-variant": edge.variant === "dashed" ? "dashed" : void 0, children: /* @__PURE__ */ jsx("title", { children: edge.label ?? edge.type ?? labels.relationship }) }, edge.id);
|
|
157
170
|
}),
|
|
158
171
|
positioned.map((node) => /* @__PURE__ */ jsxs(
|
|
159
172
|
"g",
|
|
@@ -3,6 +3,6 @@ import { ResolvedRelationshipGraphProps } from './RelationshipGraph.js';
|
|
|
3
3
|
import '../relationship-graph-model.js';
|
|
4
4
|
|
|
5
5
|
/** `RelationshipGraph layout="layered"` (ADR-0003). Internal — reached through RelationshipGraph. */
|
|
6
|
-
declare function LayeredRelationshipGraph({ nodes, edges, selectedId, onSelect, onExpandNeighbors, onOpenDetails, maxNodes, density, highlightedNodeIds, conflictedNodeIds, highlightedEdgeIds, conflictedEdgeIds, legend, className, labels, rootId, renderNode, nodeAriaLabel, renderCluster, clusterThreshold, expandedClusterIds, onToggleCluster, dimmedNodeIds, edgeLabels, showList, height, focusId, onFocusChange, controls, initialView: initialViewMode, }: ResolvedRelationshipGraphProps): react.JSX.Element;
|
|
6
|
+
declare function LayeredRelationshipGraph({ nodes, edges, selectedId, onSelect, onExpandNeighbors, onOpenDetails, maxNodes, density, highlightedNodeIds, conflictedNodeIds, highlightedEdgeIds, conflictedEdgeIds, legend, className, labels, rootId, renderNode, nodeAriaLabel, renderCluster, clusterThreshold, expandedClusterIds, onToggleCluster, dimmedNodeIds, edgeLabels, showList, height, focusId, onFocusChange, controls, initialView: initialViewMode, revealId, markRoot, }: ResolvedRelationshipGraphProps): react.JSX.Element;
|
|
7
7
|
|
|
8
8
|
export { LayeredRelationshipGraph };
|