@oprt/react 0.2.1 → 0.4.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 +2 -1
- package/dist/ComboBox/ComboBox.js +9 -1
- package/dist/DataGrid/DataGrid.js +12 -2
- package/dist/DataGrid/DataGrid.manifest.js +2 -2
- package/dist/Popover/Popover.js +6 -5
- package/dist/Skeleton/Skeleton.js +36 -25
- package/dist/Skeleton/Skeleton.manifest.js +10 -2
- package/dist/Tabs/useTabsIndicator.js +1 -1
- package/dist/_internal/hydration.js +2 -2
- package/dist/_internal/loadingStatus.js +30 -0
- package/dist/_internal/motion.js +2 -2
- package/dist/index.cjs +389 -327
- package/dist/index.d.cts +27 -2
- package/dist/index.d.ts +27 -2
- package/dist/version.js +1 -1
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -35,7 +35,8 @@ import "@oprt/tokens";
|
|
|
35
35
|
import "./globals.css";
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
Load the tokens from
|
|
38
|
+
Load the tokens from
|
|
39
|
+
JavaScript or a `<link>`, not with an `@import` inside the Tailwind
|
|
39
40
|
stylesheet: the Tailwind CLI inlines an imported file without rewriting its font URLs, and the
|
|
40
41
|
bundled fonts would not load. Inside Tailwind Typography's `prose`, wrap components in an element
|
|
41
42
|
with the `not-prose` class.
|
|
@@ -179,7 +179,15 @@ var ComboBox = forwardRef((props, ref) => {
|
|
|
179
179
|
}
|
|
180
180
|
)
|
|
181
181
|
] }),
|
|
182
|
-
/* @__PURE__ */ jsx(
|
|
182
|
+
/* @__PURE__ */ jsx(
|
|
183
|
+
"p",
|
|
184
|
+
{
|
|
185
|
+
role: "status",
|
|
186
|
+
"data-react-aria-top-layer": "",
|
|
187
|
+
className: loading ? "m-0 text-oprt-text-dim" : "sr-only",
|
|
188
|
+
children: loading ? "Loading options." : null
|
|
189
|
+
}
|
|
190
|
+
),
|
|
183
191
|
/* @__PURE__ */ jsx(
|
|
184
192
|
MotionPopover,
|
|
185
193
|
{
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import { Select, SelectOption } from "../Select/Select.js";
|
|
19
19
|
import { sortedOrder } from "../DataTable/sort.js";
|
|
20
20
|
import { devValidate } from "../_internal/devValidate.js";
|
|
21
|
+
import { useLoadingStatusText } from "../_internal/loadingStatus.js";
|
|
21
22
|
import { DataGridManifest } from "./DataGrid.manifest.js";
|
|
22
23
|
import { PageControls } from "./PageControls.js";
|
|
23
24
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -76,7 +77,7 @@ var DataGrid = forwardRef((allProps, ref) => {
|
|
|
76
77
|
columns,
|
|
77
78
|
data,
|
|
78
79
|
getRowId,
|
|
79
|
-
caption
|
|
80
|
+
caption: captionProp,
|
|
80
81
|
selectedRowIds,
|
|
81
82
|
onSelectionChange,
|
|
82
83
|
sort,
|
|
@@ -106,6 +107,7 @@ var DataGrid = forwardRef((allProps, ref) => {
|
|
|
106
107
|
if (process.env.NODE_ENV !== "production") {
|
|
107
108
|
devValidate("DataGrid", DataGridManifest.DataGrid.props, allProps);
|
|
108
109
|
}
|
|
110
|
+
const caption = captionProp ?? "Data grid";
|
|
109
111
|
const [localSelection, setLocalSelection] = useState([]);
|
|
110
112
|
const [localSort, setLocalSort] = useState(null);
|
|
111
113
|
const [localPage, setLocalPage] = useState(1);
|
|
@@ -152,6 +154,13 @@ var DataGrid = forwardRef((allProps, ref) => {
|
|
|
152
154
|
const safePageSize = Number.isFinite(pageSize) && pageSize >= 1 ? Math.floor(pageSize) : 10;
|
|
153
155
|
const count = mode === "server" && rowCount !== void 0 && Number.isFinite(rowCount) ? Math.max(0, Math.floor(rowCount)) : data.length;
|
|
154
156
|
const pageCount = Math.max(1, Math.ceil(count / safePageSize));
|
|
157
|
+
const what = captionProp ?? "results";
|
|
158
|
+
const loadingNote = `Loading ${what}.`;
|
|
159
|
+
const loadingStatus = useLoadingStatusText({
|
|
160
|
+
loading,
|
|
161
|
+
loadingText: loadingNote,
|
|
162
|
+
loadedText: `${captionProp ?? "Results"} loaded. ${count} ${count === 1 ? "row" : "rows"}.`
|
|
163
|
+
});
|
|
155
164
|
const requestedPage = page ?? localPage;
|
|
156
165
|
const currentPage = Math.min(
|
|
157
166
|
pageCount,
|
|
@@ -431,7 +440,8 @@ var DataGrid = forwardRef((allProps, ref) => {
|
|
|
431
440
|
} : {}
|
|
432
441
|
}
|
|
433
442
|
),
|
|
434
|
-
/* @__PURE__ */ jsx("p", { role: "status", className:
|
|
443
|
+
/* @__PURE__ */ jsx("p", { role: "status", className: "sr-only", children: loadingStatus }),
|
|
444
|
+
loading && /* @__PURE__ */ jsx("p", { "aria-hidden": "true", className: "m-0 text-oprt-text-dim", children: loadingNote }),
|
|
435
445
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2u", children: [
|
|
436
446
|
/* @__PURE__ */ jsxs("p", { className: "m-0 font-mono tabular-nums text-oprt-text-dim", children: [
|
|
437
447
|
count,
|
|
@@ -34,7 +34,7 @@ var DataGridManifest = {
|
|
|
34
34
|
caption: {
|
|
35
35
|
type: "string",
|
|
36
36
|
default: "Data grid",
|
|
37
|
-
description: "Accessible native table caption and controls group name."
|
|
37
|
+
description: "Accessible native table caption and controls group name. Also names what is loading in the loading and loaded messages."
|
|
38
38
|
},
|
|
39
39
|
selectedRowIds: {
|
|
40
40
|
type: "array",
|
|
@@ -111,7 +111,7 @@ var DataGridManifest = {
|
|
|
111
111
|
loading: {
|
|
112
112
|
type: "boolean",
|
|
113
113
|
default: false,
|
|
114
|
-
description:
|
|
114
|
+
description: 'Marks the table busy and disables grid-owned controls and row activation while retaining supplied data. A load the person asks for (a reload, a page, a filter: `loading` turns on after the grid has rendered) says "Loading <caption>." once (or "Loading results." when no caption is given) and, when it ends, "<caption> loaded. N rows." once. The load that is under way when the grid first renders is busy but silent, as is the arrival of its rows.'
|
|
115
115
|
},
|
|
116
116
|
disabled: {
|
|
117
117
|
type: "boolean",
|
package/dist/Popover/Popover.js
CHANGED
|
@@ -69,11 +69,12 @@ var Popover = forwardRef(
|
|
|
69
69
|
const dialog = useRef(null);
|
|
70
70
|
const lastDialog = useRef(null);
|
|
71
71
|
const wasOpen = useRef(false);
|
|
72
|
-
const focusFrame = useRef();
|
|
72
|
+
const focusFrame = useRef(void 0);
|
|
73
73
|
const focusScrollClose = useRef(false);
|
|
74
74
|
const current = useRef({ isOpen, onCloseAutoFocus, returnFocusRef });
|
|
75
75
|
current.current = { isOpen, onCloseAutoFocus, returnFocusRef };
|
|
76
|
-
const
|
|
76
|
+
const triggerChild = children;
|
|
77
|
+
const childRef = parseInt(version, 10) < 19 ? triggerChild.ref : triggerChild.props.ref;
|
|
77
78
|
const rememberTrigger = useCallback((node) => {
|
|
78
79
|
trigger.current = node;
|
|
79
80
|
if (node) lastTrigger.current = node;
|
|
@@ -209,10 +210,10 @@ var Popover = forwardRef(
|
|
|
209
210
|
document.removeEventListener("pointercancel", cancel, true);
|
|
210
211
|
};
|
|
211
212
|
}, [isOpen, requestOpen]);
|
|
212
|
-
const triggerElement = cloneElement(
|
|
213
|
+
const triggerElement = cloneElement(triggerChild, {
|
|
213
214
|
ref: setTrigger,
|
|
214
|
-
disabled: disabled ||
|
|
215
|
-
"aria-disabled": disabled ||
|
|
215
|
+
disabled: disabled || triggerChild.props.disabled,
|
|
216
|
+
"aria-disabled": disabled || triggerChild.props["aria-disabled"],
|
|
216
217
|
"aria-controls": isOpen ? id : void 0
|
|
217
218
|
});
|
|
218
219
|
const pressable = /* @__PURE__ */ jsx(Pressable, { isDisabled: disabled, children: triggerElement });
|
|
@@ -19,26 +19,55 @@ var barStyles = cva(
|
|
|
19
19
|
defaultVariants: { variant: "block" }
|
|
20
20
|
}
|
|
21
21
|
);
|
|
22
|
+
var NOT_APPLIED = [
|
|
23
|
+
"aria-label",
|
|
24
|
+
"aria-labelledby",
|
|
25
|
+
"aria-describedby",
|
|
26
|
+
"role",
|
|
27
|
+
"aria-live",
|
|
28
|
+
"aria-busy"
|
|
29
|
+
];
|
|
30
|
+
var reportedNotApplied = /* @__PURE__ */ new Set();
|
|
31
|
+
function reportNotApplied(prop, received) {
|
|
32
|
+
if (process.env.NODE_ENV === "production" || reportedNotApplied.has(prop)) return;
|
|
33
|
+
reportedNotApplied.add(prop);
|
|
34
|
+
console.error(
|
|
35
|
+
JSON.stringify(
|
|
36
|
+
{
|
|
37
|
+
component: "Skeleton",
|
|
38
|
+
error: "PROP_NOT_APPLIED",
|
|
39
|
+
prop,
|
|
40
|
+
received: typeof received === "string" ? received : String(received),
|
|
41
|
+
suggestion: 'Skeleton is decorative: hidden from assistive technology, with no role or name, so it stays silent and a page of them does not say "Loading" once each. Mark the area that is loading aria-busy and say "Loading <what>" once in a status that is already on the page (see the Skeleton page).'
|
|
42
|
+
},
|
|
43
|
+
null,
|
|
44
|
+
2
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
22
48
|
var Skeleton = forwardRef((allProps, ref) => {
|
|
23
|
-
const { className, variant = "block", width, height, lines = 3, ...
|
|
49
|
+
const { className, variant = "block", width, height, lines = 3, ...rest } = allProps;
|
|
50
|
+
const props = { ...rest };
|
|
24
51
|
if (process.env.NODE_ENV !== "production") {
|
|
25
52
|
devValidate("Skeleton", SkeletonManifest.Skeleton.props, { ...allProps });
|
|
53
|
+
for (const name of NOT_APPLIED) {
|
|
54
|
+
if (props[name] !== void 0) reportNotApplied(name, props[name]);
|
|
55
|
+
}
|
|
26
56
|
}
|
|
57
|
+
for (const name of NOT_APPLIED) delete props[name];
|
|
58
|
+
delete props["aria-hidden"];
|
|
27
59
|
if (variant === "text") {
|
|
28
60
|
const count = Number.isFinite(lines) ? Math.max(1, Math.floor(lines)) : 3;
|
|
29
61
|
return /* @__PURE__ */ jsx(
|
|
30
62
|
"div",
|
|
31
63
|
{
|
|
64
|
+
...props,
|
|
32
65
|
ref,
|
|
33
|
-
|
|
34
|
-
"aria-busy": "true",
|
|
35
|
-
"aria-label": "Loading",
|
|
66
|
+
"aria-hidden": "true",
|
|
36
67
|
className: clsx("flex flex-col gap-1u", className),
|
|
37
|
-
...props,
|
|
38
68
|
children: Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsx(
|
|
39
69
|
"div",
|
|
40
70
|
{
|
|
41
|
-
"aria-hidden": "true",
|
|
42
71
|
className: clsx(barStyles({ variant: "text" }), i === count - 1 && "w-2/3")
|
|
43
72
|
},
|
|
44
73
|
i
|
|
@@ -50,25 +79,7 @@ var Skeleton = forwardRef((allProps, ref) => {
|
|
|
50
79
|
if (width !== void 0) style.width = width;
|
|
51
80
|
if (height !== void 0) style.height = height;
|
|
52
81
|
const hasStyle = width !== void 0 || height !== void 0;
|
|
53
|
-
return /* @__PURE__ */ jsx(
|
|
54
|
-
"div",
|
|
55
|
-
{
|
|
56
|
-
ref,
|
|
57
|
-
role: "status",
|
|
58
|
-
"aria-busy": "true",
|
|
59
|
-
"aria-label": "Loading",
|
|
60
|
-
className: clsx("flex flex-col", className),
|
|
61
|
-
...props,
|
|
62
|
-
children: /* @__PURE__ */ jsx(
|
|
63
|
-
"div",
|
|
64
|
-
{
|
|
65
|
-
"aria-hidden": "true",
|
|
66
|
-
className: barStyles({ variant: "block" }),
|
|
67
|
-
...hasStyle ? { style } : {}
|
|
68
|
-
}
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
);
|
|
82
|
+
return /* @__PURE__ */ jsx("div", { ...props, ref, "aria-hidden": "true", className: clsx("flex flex-col", className), children: /* @__PURE__ */ jsx("div", { className: barStyles({ variant: "block" }), ...hasStyle ? { style } : {} }) });
|
|
72
83
|
});
|
|
73
84
|
Skeleton.displayName = "Skeleton";
|
|
74
85
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/Skeleton/Skeleton.manifest.ts
|
|
2
2
|
var SkeletonManifest = {
|
|
3
3
|
Skeleton: {
|
|
4
|
-
description: "Loading
|
|
4
|
+
description: 'Loading placeholder, purely visual and silent: hidden from assistive technology, with no role, name or text, so a screen of placeholders does not say "Loading" once each. block renders one pulsing bar; text stacks `lines` bars with a shortened last line. Bars fill their container and never widen it, whatever width the caller asks for, and are drawn in a system color in forced-colors mode. The area that is loading says it: mark the area `aria-busy`, keep one polite status outside it (on the page before it has anything to say) that says "Loading <what>" and then that the content has arrived.',
|
|
5
5
|
props: {
|
|
6
6
|
variant: { type: "enum", values: ["block", "text"], default: "block" },
|
|
7
7
|
width: {
|
|
@@ -18,11 +18,19 @@ var SkeletonManifest = {
|
|
|
18
18
|
type: "number",
|
|
19
19
|
default: 3,
|
|
20
20
|
description: "Number of stacked bars in the text variant, rounded down with a minimum of 1. Non-finite values fall back to 3."
|
|
21
|
+
},
|
|
22
|
+
"aria-label": {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: 'Deprecated and not applied. A Skeleton used to be a status named "Loading" (or this label); it is decorative now. In development a structured error says so, once. Name the loading area instead. The same holds for aria-labelledby, aria-describedby, role, aria-live and aria-busy.'
|
|
25
|
+
},
|
|
26
|
+
role: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "Deprecated and not applied: a Skeleton is decorative and has no role. See aria-label."
|
|
21
29
|
}
|
|
22
30
|
},
|
|
23
31
|
composable_with: ["Card", "DataTable"],
|
|
24
32
|
never_inside: ["Button", "Badge"],
|
|
25
|
-
example: '<Skeleton variant="text" lines={3} />'
|
|
33
|
+
example: '<section aria-labelledby="tracks-title">\n <h2 id="tracks-title">Tracks</h2>\n <p role="status" className="sr-only">{note}</p>\n <div aria-busy={loading}>\n {loading ? <Skeleton variant="text" lines={3} /> : <TrackList tracks={tracks} />}\n </div>\n</section>'
|
|
26
34
|
}
|
|
27
35
|
};
|
|
28
36
|
export {
|
|
@@ -6,7 +6,7 @@ import { createResizeObserver } from "../_internal/useElementWidth.js";
|
|
|
6
6
|
import { useIsomorphicLayoutEffect } from "../_internal/useIsomorphicLayoutEffect.js";
|
|
7
7
|
function useTabsIndicator(list, vertical) {
|
|
8
8
|
const [geometry, setGeometry] = useState();
|
|
9
|
-
const selectedId = useRef();
|
|
9
|
+
const selectedId = useRef(void 0);
|
|
10
10
|
useIsomorphicLayoutEffect(() => {
|
|
11
11
|
const node = list.current;
|
|
12
12
|
if (!node) return;
|
|
@@ -35,11 +35,11 @@ function warnProbeMissing(origin) {
|
|
|
35
35
|
function useHydrationRoot(...forwardedIds) {
|
|
36
36
|
const probe = useId();
|
|
37
37
|
const hydrated = useSyncExternalStore(subscribeNoop, getClientSnapshot, getServerSnapshot);
|
|
38
|
-
const hydratingMount = useRef();
|
|
38
|
+
const hydratingMount = useRef(void 0);
|
|
39
39
|
if (hydratingMount.current === void 0) hydratingMount.current = !hydrated;
|
|
40
40
|
const [disagrees, setDisagrees] = useState(false);
|
|
41
41
|
const clientOnly = useRef(hydrated);
|
|
42
|
-
const origin = useRef();
|
|
42
|
+
const origin = useRef(void 0);
|
|
43
43
|
if (process.env.NODE_ENV !== "production" && origin.current === void 0) {
|
|
44
44
|
origin.current = callerOfHook();
|
|
45
45
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/_internal/loadingStatus.ts
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { useHoldBackNewText } from "./liveRegion.js";
|
|
6
|
+
function useLoadingStatusText({
|
|
7
|
+
loading,
|
|
8
|
+
loadingText,
|
|
9
|
+
loadedText
|
|
10
|
+
}) {
|
|
11
|
+
const held = useHoldBackNewText(true);
|
|
12
|
+
const [seen, setSeen] = useState({ loading, first: loading, arrival: "" });
|
|
13
|
+
let current = seen;
|
|
14
|
+
if (seen.loading !== loading) {
|
|
15
|
+
current = {
|
|
16
|
+
loading,
|
|
17
|
+
first: false,
|
|
18
|
+
arrival: loading || seen.first ? "" : loadedText
|
|
19
|
+
};
|
|
20
|
+
setSeen(current);
|
|
21
|
+
} else if (!loading && seen.arrival !== "" && seen.arrival !== loadedText) {
|
|
22
|
+
current = { ...seen, arrival: "" };
|
|
23
|
+
setSeen(current);
|
|
24
|
+
}
|
|
25
|
+
if (held || current.first) return "";
|
|
26
|
+
return loading ? loadingText : current.arrival;
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
useLoadingStatusText
|
|
30
|
+
};
|
package/dist/_internal/motion.js
CHANGED
|
@@ -25,8 +25,8 @@ function useMotion(mode = "auto", component) {
|
|
|
25
25
|
const [ready, setReady] = useState(false);
|
|
26
26
|
const [keyboard, setKeyboard] = useState(false);
|
|
27
27
|
const pointer = useRef(false);
|
|
28
|
-
const frame = useRef();
|
|
29
|
-
const contactCleanup = useRef();
|
|
28
|
+
const frame = useRef(void 0);
|
|
29
|
+
const contactCleanup = useRef(void 0);
|
|
30
30
|
const clearContact = useCallback(() => {
|
|
31
31
|
contactCleanup.current?.();
|
|
32
32
|
contactCleanup.current = void 0;
|