@truedat/core 8.14.1 → 8.14.3
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/package.json +3 -3
- package/src/components/AiMenu.js +2 -1
- package/src/components/TrueModal.js +40 -2
- package/src/components/__tests__/AiMenu.spec.js +14 -0
- package/src/components/__tests__/TrueModal.spec.js +24 -1
- package/src/components/__tests__/__snapshots__/SideMenu.spec.js.snap +7 -0
- package/src/routes.js +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/core",
|
|
3
|
-
"version": "8.14.
|
|
3
|
+
"version": "8.14.3",
|
|
4
4
|
"description": "Truedat Web Core",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@testing-library/jest-dom": "^6.6.3",
|
|
58
58
|
"@testing-library/react": "^16.3.0",
|
|
59
59
|
"@testing-library/user-event": "^14.6.1",
|
|
60
|
-
"@truedat/test": "8.14.
|
|
60
|
+
"@truedat/test": "8.14.3",
|
|
61
61
|
"identity-obj-proxy": "^3.0.0",
|
|
62
62
|
"jest": "^29.7.0",
|
|
63
63
|
"redux-saga-test-plan": "^4.0.6"
|
|
@@ -99,5 +99,5 @@
|
|
|
99
99
|
"swr": "^2.3.3",
|
|
100
100
|
"turndown": "^7.2.2"
|
|
101
101
|
},
|
|
102
|
-
"gitHead": "
|
|
102
|
+
"gitHead": "ca5cc79e39ed78a1b5e317d825832e0676b011f6"
|
|
103
103
|
}
|
package/src/components/AiMenu.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { useIntl } from "react-intl";
|
|
2
2
|
import { useAuthorized } from "../hooks";
|
|
3
|
-
import { ACTIONS, PROMPTS, PROVIDERS } from "../routes";
|
|
3
|
+
import { ACTIONS, CUSTOM_INSTRUCTIONS, PROMPTS, PROVIDERS } from "../routes";
|
|
4
4
|
import Submenu from "./Submenu";
|
|
5
5
|
|
|
6
6
|
export const ITEMS = [
|
|
7
7
|
{ name: "actions", routes: [ACTIONS] },
|
|
8
8
|
{ name: "providers", routes: [PROVIDERS] },
|
|
9
|
+
{ name: "customInstructions", routes: [CUSTOM_INSTRUCTIONS] },
|
|
9
10
|
{ name: "prompts", routes: [PROMPTS] },
|
|
10
11
|
];
|
|
11
12
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useId } from "react";
|
|
1
|
+
import { useCallback, useId, useRef } from "react";
|
|
2
2
|
import PropTypes from "prop-types";
|
|
3
3
|
import { useIntl, FormattedMessage } from "react-intl";
|
|
4
4
|
import { Button, Icon, Modal } from "semantic-ui-react";
|
|
@@ -26,9 +26,44 @@ export const TrueModal = ({
|
|
|
26
26
|
onCancel,
|
|
27
27
|
onCreate,
|
|
28
28
|
className,
|
|
29
|
+
closeDisabled = false,
|
|
29
30
|
...modalProps
|
|
30
31
|
}) => {
|
|
31
32
|
const titleId = useId();
|
|
33
|
+
const headingRef = useRef(null);
|
|
34
|
+
const setHeading = useCallback((node) => {
|
|
35
|
+
headingRef.current = node;
|
|
36
|
+
if (!node) return undefined;
|
|
37
|
+
const frame = requestAnimationFrame(() => {
|
|
38
|
+
node.closest(".ui.modal")?.querySelector(".true-modal-close")?.focus();
|
|
39
|
+
});
|
|
40
|
+
return () => cancelAnimationFrame(frame);
|
|
41
|
+
}, []);
|
|
42
|
+
const handleKeyDown = (event) => {
|
|
43
|
+
modalProps.onKeyDown?.(event);
|
|
44
|
+
if (event.key !== "Tab" || event.defaultPrevented) return;
|
|
45
|
+
const dialog = headingRef.current?.closest(".ui.modal");
|
|
46
|
+
const controls = Array.from(
|
|
47
|
+
dialog?.querySelectorAll(
|
|
48
|
+
'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), [tabindex="0"]',
|
|
49
|
+
) || [],
|
|
50
|
+
).filter(
|
|
51
|
+
(node) =>
|
|
52
|
+
node.getClientRects().length &&
|
|
53
|
+
!node.closest("[aria-disabled=true], fieldset[disabled]"),
|
|
54
|
+
);
|
|
55
|
+
const first = controls[0];
|
|
56
|
+
const last = controls[controls.length - 1];
|
|
57
|
+
if (
|
|
58
|
+
first &&
|
|
59
|
+
(!dialog.contains(document.activeElement) ||
|
|
60
|
+
(event.shiftKey && document.activeElement === first) ||
|
|
61
|
+
(!event.shiftKey && document.activeElement === last))
|
|
62
|
+
) {
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
(event.shiftKey ? last : first).focus();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
32
67
|
const { formatMessage } = useIntl();
|
|
33
68
|
const defaultActions = _.compact([
|
|
34
69
|
onCancel && {
|
|
@@ -50,6 +85,7 @@ export const TrueModal = ({
|
|
|
50
85
|
<Modal
|
|
51
86
|
{..._.omit(["header", "content", "closeIcon"])(modalProps)}
|
|
52
87
|
className={_.compact(["true-modal", className]).join(" ")}
|
|
88
|
+
onKeyDown={handleKeyDown}
|
|
53
89
|
role="dialog"
|
|
54
90
|
aria-labelledby={
|
|
55
91
|
modalProps["aria-labelledby"] ||
|
|
@@ -58,6 +94,7 @@ export const TrueModal = ({
|
|
|
58
94
|
closeIcon={
|
|
59
95
|
<button
|
|
60
96
|
type="button"
|
|
97
|
+
disabled={closeDisabled}
|
|
61
98
|
className="true-modal-close"
|
|
62
99
|
style={{ color: headerTextColor }}
|
|
63
100
|
aria-label={formatMessage({
|
|
@@ -71,7 +108,7 @@ export const TrueModal = ({
|
|
|
71
108
|
>
|
|
72
109
|
<Modal.Header style={{ background: headerColor, color: headerTextColor }}>
|
|
73
110
|
{icon ? <Icon name={icon} size="large" /> : null}
|
|
74
|
-
<div className="true-modal-heading">
|
|
111
|
+
<div ref={setHeading} className="true-modal-heading">
|
|
75
112
|
<span id={titleId}>{title}</span>
|
|
76
113
|
{subtitle != null ? (
|
|
77
114
|
<p className="true-modal-subtitle">{subtitle}</p>
|
|
@@ -132,6 +169,7 @@ TrueModal.propTypes = {
|
|
|
132
169
|
onCancel: PropTypes.func,
|
|
133
170
|
onCreate: PropTypes.func,
|
|
134
171
|
className: PropTypes.string,
|
|
172
|
+
closeDisabled: PropTypes.bool,
|
|
135
173
|
};
|
|
136
174
|
|
|
137
175
|
export default TrueModal;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ITEMS } from "../AiMenu";
|
|
2
|
+
import { CUSTOM_INSTRUCTIONS, PROMPTS, PROVIDERS, ACTIONS } from "../../routes";
|
|
3
|
+
|
|
4
|
+
describe("AiMenu ITEMS", () => {
|
|
5
|
+
it("adds customInstructions without changing existing entries", () => {
|
|
6
|
+
expect(CUSTOM_INSTRUCTIONS).toBe("/custom-instructions");
|
|
7
|
+
expect(ITEMS).toEqual([
|
|
8
|
+
{ name: "actions", routes: [ACTIONS] },
|
|
9
|
+
{ name: "providers", routes: [PROVIDERS] },
|
|
10
|
+
{ name: "customInstructions", routes: [CUSTOM_INSTRUCTIONS] },
|
|
11
|
+
{ name: "prompts", routes: [PROMPTS] },
|
|
12
|
+
]);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { screen } from "@testing-library/react";
|
|
1
|
+
import { act, screen, waitFor } from "@testing-library/react";
|
|
2
2
|
import userEvent from "@testing-library/user-event";
|
|
3
3
|
import { render } from "@truedat/test/render";
|
|
4
4
|
import { TrueModal } from "../index";
|
|
@@ -190,4 +190,27 @@ describe("<TrueModal />", () => {
|
|
|
190
190
|
rerender(<TrueModal open={false} title="Title" />);
|
|
191
191
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
192
192
|
});
|
|
193
|
+
it("focuses the dialog and wraps keyboard focus within its controls", async () => {
|
|
194
|
+
const rects = jest
|
|
195
|
+
.spyOn(HTMLElement.prototype, "getClientRects")
|
|
196
|
+
.mockReturnValue([{}]);
|
|
197
|
+
renderModal({
|
|
198
|
+
children: <input aria-label="Name" />,
|
|
199
|
+
actions: [{ key: "save", content: "Save" }],
|
|
200
|
+
});
|
|
201
|
+
act(() => jest.advanceTimersByTime(20));
|
|
202
|
+
const close = screen.getByRole("button", { name: "Close" });
|
|
203
|
+
await waitFor(() => expect(close).toHaveFocus());
|
|
204
|
+
const user = setupUser();
|
|
205
|
+
await user.tab({ shift: true });
|
|
206
|
+
expect(screen.getByRole("button", { name: "Save" })).toHaveFocus();
|
|
207
|
+
await user.tab();
|
|
208
|
+
expect(close).toHaveFocus();
|
|
209
|
+
rects.mockRestore();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("exposes disabled semantics on the close control while an operation is pending", () => {
|
|
213
|
+
renderModal({ closeDisabled: true });
|
|
214
|
+
expect(screen.getByRole("button", { name: "Close" })).toBeDisabled();
|
|
215
|
+
});
|
|
193
216
|
});
|
|
@@ -443,6 +443,13 @@ exports[`<SideMenu /> matches the latest snapshot 1`] = `
|
|
|
443
443
|
>
|
|
444
444
|
providers
|
|
445
445
|
</a>
|
|
446
|
+
<a
|
|
447
|
+
class="link item"
|
|
448
|
+
data-discover="true"
|
|
449
|
+
href="/custom-instructions"
|
|
450
|
+
>
|
|
451
|
+
customInstructions
|
|
452
|
+
</a>
|
|
446
453
|
<a
|
|
447
454
|
class="link item"
|
|
448
455
|
data-discover="true"
|
package/src/routes.js
CHANGED
|
@@ -177,6 +177,7 @@ export const PROFILE_EXECUTION =
|
|
|
177
177
|
export const PROFILE_GROUP = "/profileGroups/:id";
|
|
178
178
|
export const PROMPTS = "/prompts";
|
|
179
179
|
export const PROVIDERS = "/providers";
|
|
180
|
+
export const CUSTOM_INSTRUCTIONS = "/custom-instructions";
|
|
180
181
|
export const QUALITY_CONTROLS = "/qualityControls";
|
|
181
182
|
export const QUALITY_CONTROLS_DEPRECATED = "/qualityControls/deprecated";
|
|
182
183
|
export const QUALITY_CONTROLS_DRAFTS = "/qualityControls/drafts";
|
|
@@ -434,6 +435,7 @@ const routes = {
|
|
|
434
435
|
PROFILE_GROUP,
|
|
435
436
|
PROMPTS,
|
|
436
437
|
PROVIDERS,
|
|
438
|
+
CUSTOM_INSTRUCTIONS,
|
|
437
439
|
QUALITY_CONTROLS,
|
|
438
440
|
QUALITY_CONTROLS_DEPRECATED,
|
|
439
441
|
QUALITY_CONTROLS_DRAFTS,
|