@oprt/react 0.2.0 → 0.2.1

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.
@@ -8,6 +8,7 @@ import { Badge } from "../Badge/Badge.js";
8
8
  import { Button } from "../Button/Button.js";
9
9
  import { useActionFocus } from "../_internal/useActionFocus.js";
10
10
  import { devValidate } from "../_internal/devValidate.js";
11
+ import { useHoldBackNewText } from "../_internal/liveRegion.js";
11
12
  import { ActionStatusManifest } from "./ActionStatus.manifest.js";
12
13
  import { jsx, jsxs } from "react/jsx-runtime";
13
14
  var statusStyles = cva(
@@ -34,6 +35,8 @@ function ActionStatusLine({
34
35
  requestId,
35
36
  children
36
37
  }) {
38
+ const spoken = [`${label}, ${state}.`, requestId && `Request: ${requestId}.`, message].filter(Boolean).join(" ");
39
+ const holdBack = useHoldBackNewText(true);
37
40
  return /* @__PURE__ */ jsxs(
38
41
  "div",
39
42
  {
@@ -42,13 +45,21 @@ function ActionStatusLine({
42
45
  "aria-atomic": "true",
43
46
  className: "flex min-w-0 flex-col gap-1u",
44
47
  children: [
45
- /* @__PURE__ */ jsx("span", { className: "min-w-0 oprt-wrap-anywhere font-medium uppercase tracking-oprt-caps", children: label }),
46
- /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Badge, { variant: variants[state], indicator: "dot", children: state }) }),
47
- requestId && /* @__PURE__ */ jsxs("span", { className: "min-w-0 oprt-wrap-anywhere font-mono", children: [
48
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: holdBack ? null : spoken }),
49
+ /* @__PURE__ */ jsx(
50
+ "span",
51
+ {
52
+ "aria-hidden": "true",
53
+ className: "min-w-0 oprt-wrap-anywhere font-medium uppercase tracking-oprt-caps",
54
+ children: label
55
+ }
56
+ ),
57
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", children: /* @__PURE__ */ jsx(Badge, { variant: variants[state], indicator: "dot", children: state }) }),
58
+ requestId && /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "min-w-0 oprt-wrap-anywhere font-mono", children: [
48
59
  "Request: ",
49
60
  requestId
50
61
  ] }),
51
- message && /* @__PURE__ */ jsx("p", { className: "m-0 min-w-0 oprt-wrap-anywhere", children: message }),
62
+ message && /* @__PURE__ */ jsx("p", { "aria-hidden": "true", className: "m-0 min-w-0 oprt-wrap-anywhere", children: message }),
52
63
  children
53
64
  ]
54
65
  }
@@ -7,6 +7,7 @@ import { forwardRef } from "react";
7
7
  import clsx from "clsx";
8
8
  import { AlertManifest } from "./Alert.manifest.js";
9
9
  import { devValidate } from "../_internal/devValidate.js";
10
+ import { useHoldBackNewText } from "../_internal/liveRegion.js";
10
11
  import { jsx, jsxs } from "react/jsx-runtime";
11
12
  var ICONS = {
12
13
  info: Info,
@@ -48,6 +49,7 @@ var Alert = forwardRef((allProps, ref) => {
48
49
  ...props
49
50
  } = allProps;
50
51
  const role = variant === "error" || variant === "warning" ? "alert" : "status";
52
+ const holdBack = useHoldBackNewText(role === "status");
51
53
  const Icon = ICONS[variant];
52
54
  const hasBody = children != null && typeof children !== "boolean" && children !== "";
53
55
  if (process.env.NODE_ENV !== "production") {
@@ -55,19 +57,26 @@ var Alert = forwardRef((allProps, ref) => {
55
57
  }
56
58
  return /* @__PURE__ */ jsxs("div", { ref, role, className: clsx(containerStyles({ variant }), className), ...props, children: [
57
59
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: clsx("shrink-0", accentStyles({ variant })), children: /* @__PURE__ */ jsx(Icon, { size: 16 }) }),
58
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1u oprt-wrap-anywhere", children: [
59
- title && /* @__PURE__ */ jsx(
60
- "p",
61
- {
62
- className: clsx(
63
- "m-0 text-oprt-sm uppercase tracking-oprt-sm",
64
- accentStyles({ variant })
60
+ /* @__PURE__ */ jsxs(
61
+ "div",
62
+ {
63
+ className: "flex min-w-0 flex-1 flex-col gap-1u oprt-wrap-anywhere",
64
+ ...holdBack ? { "aria-hidden": true } : {},
65
+ children: [
66
+ title && /* @__PURE__ */ jsx(
67
+ "p",
68
+ {
69
+ className: clsx(
70
+ "m-0 text-oprt-sm uppercase tracking-oprt-sm",
71
+ accentStyles({ variant })
72
+ ),
73
+ children: title
74
+ }
65
75
  ),
66
- children: title
67
- }
68
- ),
69
- hasBody && /* @__PURE__ */ jsx("div", { className: "text-oprt-base normal-case text-oprt-text", children })
70
- ] }),
76
+ hasBody && /* @__PURE__ */ jsx("div", { className: "text-oprt-base normal-case text-oprt-text", children })
77
+ ]
78
+ }
79
+ ),
71
80
  dismissible && /* @__PURE__ */ jsx(
72
81
  "button",
73
82
  {
@@ -77,18 +77,21 @@ var AttachmentPreview = forwardRef(
77
77
  /* @__PURE__ */ jsx("span", { className: "oprt-wrap-anywhere", children: attachment.mediaType })
78
78
  ] })
79
79
  ] }),
80
- preview.kind === "image" && (imageError ? /* @__PURE__ */ jsx("p", { role: "status", className: "m-0 text-oprt-error", children: imageError }) : /* @__PURE__ */ jsx(
81
- "img",
82
- {
83
- src: preview.src,
84
- alt: preview.alt,
85
- onError: () => setImageState(
86
- (current) => current.key === imageKey ? { key: imageKey, failed: true } : current
87
- ),
88
- className: clsx(PREVIEW_HEIGHT, "max-w-full self-start object-contain")
89
- },
90
- imageKey
91
- )),
80
+ preview.kind === "image" && /* @__PURE__ */ jsxs(Fragment, { children: [
81
+ /* @__PURE__ */ jsx("p", { role: "status", className: imageError ? "m-0 text-oprt-error" : "sr-only", children: imageError }),
82
+ !imageError && /* @__PURE__ */ jsx(
83
+ "img",
84
+ {
85
+ src: preview.src,
86
+ alt: preview.alt,
87
+ onError: () => setImageState(
88
+ (current) => current.key === imageKey ? { key: imageKey, failed: true } : current
89
+ ),
90
+ className: clsx(PREVIEW_HEIGHT, "max-w-full self-start object-contain")
91
+ },
92
+ imageKey
93
+ )
94
+ ] }),
92
95
  preview.kind === "text" && /* @__PURE__ */ jsxs(Fragment, { children: [
93
96
  /* @__PURE__ */ jsx(
94
97
  "pre",
@@ -874,7 +874,15 @@ var Chart = forwardRef((allProps, ref) => {
874
874
  onBlur: () => setInspectionFocused(false)
875
875
  }
876
876
  ),
877
- empty ? /* @__PURE__ */ jsx("p", { className: "m-0 py-6u text-oprt-base text-oprt-text-dim", role: "status", children: empty }) : /* @__PURE__ */ jsxs(
877
+ /* @__PURE__ */ jsx(
878
+ "p",
879
+ {
880
+ role: "status",
881
+ className: empty ? "m-0 py-6u text-oprt-base text-oprt-text-dim" : "sr-only",
882
+ children: empty
883
+ }
884
+ ),
885
+ empty ? null : /* @__PURE__ */ jsxs(
878
886
  "svg",
879
887
  {
880
888
  role: "img",
@@ -172,7 +172,15 @@ var EventTimeline = forwardRef(
172
172
  "aria-label": label,
173
173
  className: clsx(timelineStyles(), className),
174
174
  children: [
175
- !validRange ? /* @__PURE__ */ jsx("p", { role: "status", className: "m-0 text-oprt-base text-oprt-text-dim", children: "Choose a valid time range with start before end." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
175
+ /* @__PURE__ */ jsx(
176
+ "p",
177
+ {
178
+ role: "status",
179
+ className: !validRange ? "m-0 text-oprt-base text-oprt-text-dim" : "sr-only",
180
+ children: !validRange ? "Choose a valid time range with start before end." : ""
181
+ }
182
+ ),
183
+ !validRange ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
176
184
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap justify-between gap-1u font-mono text-oprt-base text-oprt-text-dim", children: [
177
185
  /* @__PURE__ */ jsx("time", { dateTime: new Date(start).toISOString(), children: utc(start) }),
178
186
  /* @__PURE__ */ jsx("time", { dateTime: new Date(end).toISOString(), children: utc(end) })
@@ -165,7 +165,7 @@ var Toast = forwardRef((allProps, ref) => {
165
165
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 select-text flex-col gap-1u oprt-wrap-anywhere", children: [
166
166
  /* @__PURE__ */ jsx(RadixToast.Title, { className: titleStyles({ variant: severity }), children: title }),
167
167
  description && /* @__PURE__ */ jsxs(RadixToast.Description, { className: "m-0 text-oprt-base normal-case text-oprt-text", children: [
168
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: ". " }),
168
+ /* @__PURE__ */ jsx("span", { "data-radix-toast-announce-exclude": "", "data-radix-toast-announce-alt": ". " }),
169
169
  description
170
170
  ] })
171
171
  ] }),
@@ -0,0 +1,27 @@
1
+ "use client";
2
+
3
+ // src/_internal/liveRegion.ts
4
+ import { useEffect, useRef, useState, useSyncExternalStore } from "react";
5
+ var neverChanges = () => () => {
6
+ };
7
+ var LIVE_REGION_LEAD_MS = 100;
8
+ function useHoldBackNewText(enabled) {
9
+ const hydrating = useSyncExternalStore(
10
+ neverChanges,
11
+ () => false,
12
+ () => true
13
+ );
14
+ const lateArrival = useRef(null);
15
+ if (lateArrival.current === null) lateArrival.current = !hydrating;
16
+ const [held, setHeld] = useState(Boolean(lateArrival.current));
17
+ useEffect(() => {
18
+ if (!held) return;
19
+ const timer = window.setTimeout(() => setHeld(false), LIVE_REGION_LEAD_MS);
20
+ return () => window.clearTimeout(timer);
21
+ }, [held]);
22
+ return enabled && held;
23
+ }
24
+ export {
25
+ LIVE_REGION_LEAD_MS,
26
+ useHoldBackNewText
27
+ };