@dxos/react-ui-query 0.12.0 → 0.13.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/dist/lib/index.mjs +44 -6
- package/dist/lib/index.mjs.map +1 -1
- package/dist/lib/translations.mjs +13 -0
- package/dist/lib/translations.mjs.map +1 -0
- package/dist/types/src/components/QueryEditor/QueryEditor.d.ts +2 -2
- package/dist/types/src/components/QueryEditor/QueryEditor.d.ts.map +1 -1
- package/dist/types/src/components/QueryForm/Picker.d.ts.map +1 -1
- package/dist/types/src/components/QueryForm/QueryForm.d.ts +2 -2
- package/dist/types/src/components/QueryForm/QueryForm.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/usePersistentQuery.d.ts +20 -0
- package/dist/types/src/hooks/usePersistentQuery.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -17
- package/src/components/QueryEditor/QueryEditor.stories.tsx +1 -1
- package/src/components/QueryEditor/QueryEditor.tsx +5 -4
- package/src/components/QueryForm/Picker.tsx +3 -2
- package/src/components/QueryForm/QueryForm.stories.tsx +1 -1
- package/src/components/QueryForm/QueryForm.tsx +4 -4
- package/src/hooks/index.ts +1 -0
- package/src/hooks/usePersistentQuery.ts +57 -0
- package/src/translations.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-query",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Editors for ECHO queries: a query-language input and a type/tag form.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -38,39 +38,39 @@
|
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@codemirror/language": "^6.12.4",
|
|
41
|
-
"@codemirror/state": "^6.7.
|
|
42
|
-
"@codemirror/view": "^6.43.
|
|
41
|
+
"@codemirror/state": "^6.7.6",
|
|
42
|
+
"@codemirror/view": "^6.43.13",
|
|
43
43
|
"@lezer/common": "^1.5.2",
|
|
44
44
|
"@lezer/highlight": "^1.2.3",
|
|
45
45
|
"json5": "^2.2.3",
|
|
46
|
-
"@dxos/echo": "0.
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/ui-
|
|
50
|
-
"@dxos/ui": "0.
|
|
51
|
-
"@dxos/
|
|
46
|
+
"@dxos/echo": "0.13.0",
|
|
47
|
+
"@dxos/echo-query": "0.13.0",
|
|
48
|
+
"@dxos/keys": "0.13.0",
|
|
49
|
+
"@dxos/react-ui-attention": "0.13.0",
|
|
50
|
+
"@dxos/react-ui-editor": "0.13.0",
|
|
51
|
+
"@dxos/ui": "0.13.0",
|
|
52
|
+
"@dxos/ui-editor": "0.13.0"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
54
55
|
"@storybook/react-vite": "^10.6.0",
|
|
55
56
|
"@types/react": "~19.2.17",
|
|
56
57
|
"@types/react-dom": "~19.2.3",
|
|
57
58
|
"effect": "4.0.0",
|
|
58
|
-
"jsdom": "^29.1.1",
|
|
59
59
|
"react": "~19.2.7",
|
|
60
60
|
"react-dom": "~19.2.7",
|
|
61
61
|
"vite": "^8.2.2",
|
|
62
|
-
"@dxos/react-client": "0.
|
|
63
|
-
"@dxos/react-ui
|
|
64
|
-
"@dxos/react-ui": "0.
|
|
65
|
-
"@dxos/
|
|
66
|
-
"@dxos/
|
|
62
|
+
"@dxos/react-client": "0.13.0",
|
|
63
|
+
"@dxos/react-ui": "0.13.0",
|
|
64
|
+
"@dxos/react-ui-syntax-highlighter": "0.13.0",
|
|
65
|
+
"@dxos/types": "0.13.0",
|
|
66
|
+
"@dxos/ui-theme": "0.13.0"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"effect": "4.0.0",
|
|
70
70
|
"react": "~19.2.7",
|
|
71
71
|
"react-dom": "~19.2.7",
|
|
72
|
-
"@dxos/react-ui": "^0.
|
|
73
|
-
"@dxos/ui-theme": "^0.
|
|
72
|
+
"@dxos/react-ui": "^0.13.0",
|
|
73
|
+
"@dxos/ui-theme": "^0.13.0"
|
|
74
74
|
},
|
|
75
75
|
"publishConfig": {
|
|
76
76
|
"access": "public"
|
|
@@ -9,9 +9,9 @@ import { expect, userEvent, waitFor } from 'storybook/test';
|
|
|
9
9
|
|
|
10
10
|
import { type Filter, Tag } from '@dxos/echo';
|
|
11
11
|
import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
|
|
12
|
-
import { Toolbar } from '@dxos/react-ui';
|
|
13
12
|
import { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
14
13
|
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
14
|
+
import * as Toolbar from '@dxos/react-ui/Toolbar';
|
|
15
15
|
import { Employer, Organization, Person, Pipeline } from '@dxos/types';
|
|
16
16
|
|
|
17
17
|
import { translations } from '#translations';
|
|
@@ -6,7 +6,6 @@ import { type Extension, Prec } from '@codemirror/state';
|
|
|
6
6
|
import React, { forwardRef, useCallback, useMemo, useRef } from 'react';
|
|
7
7
|
|
|
8
8
|
import { type BuildResult, QueryBuilder } from '@dxos/echo-query';
|
|
9
|
-
import { type ThemedClassName, useThemeMode, useTranslation } from '@dxos/react-ui';
|
|
10
9
|
import {
|
|
11
10
|
Editor,
|
|
12
11
|
type EditorController,
|
|
@@ -15,6 +14,8 @@ import {
|
|
|
15
14
|
type UseEditorMenuProps,
|
|
16
15
|
createMenuGroup,
|
|
17
16
|
} from '@dxos/react-ui-editor';
|
|
17
|
+
import * as Hooks from '@dxos/react-ui/Hooks';
|
|
18
|
+
import type * as Util from '@dxos/react-ui/Util';
|
|
18
19
|
import { createBasicExtensions, createThemeExtensions, keymap } from '@dxos/ui-editor';
|
|
19
20
|
|
|
20
21
|
import { translationKey } from '#translations';
|
|
@@ -22,7 +23,7 @@ import { translationKey } from '#translations';
|
|
|
22
23
|
import { type CompletionOptions, completions } from './autocomplete.ts';
|
|
23
24
|
import { query } from './query-extension.ts';
|
|
24
25
|
|
|
25
|
-
export type QueryEditorProps = ThemedClassName<
|
|
26
|
+
export type QueryEditorProps = Util.ThemedClassName<
|
|
26
27
|
{
|
|
27
28
|
value?: string;
|
|
28
29
|
readonly?: boolean;
|
|
@@ -43,7 +44,7 @@ export type QueryEditorProps = ThemedClassName<
|
|
|
43
44
|
*/
|
|
44
45
|
export const QueryEditor = forwardRef<EditorController, QueryEditorProps>(
|
|
45
46
|
({ db, tags, value, readonly, numItems = 8, onChange, onFilterChange, ...props }, forwardedRef) => {
|
|
46
|
-
const { t } = useTranslation(translationKey);
|
|
47
|
+
const { t } = Hooks.useTranslation(translationKey);
|
|
47
48
|
|
|
48
49
|
const getOptions = useMemo(() => completions({ db, tags }), [db, tags]);
|
|
49
50
|
const getMenu = useCallback<NonNullable<UseEditorMenuProps['getMenu']>>(
|
|
@@ -78,7 +79,7 @@ export const QueryEditor = forwardRef<EditorController, QueryEditorProps>(
|
|
|
78
79
|
const tagsRef = useRef(tags);
|
|
79
80
|
tagsRef.current = tags;
|
|
80
81
|
|
|
81
|
-
const themeMode = useThemeMode();
|
|
82
|
+
const themeMode = Hooks.useThemeMode();
|
|
82
83
|
const extensions = useMemo<Extension[]>(
|
|
83
84
|
() => [
|
|
84
85
|
createBasicExtensions({
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { useMemo } from 'react';
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import * as Hooks from '@dxos/react-ui/Hooks';
|
|
8
|
+
import * as Select from '@dxos/react-ui/Select';
|
|
8
9
|
|
|
9
10
|
import { translationKey } from '#translations';
|
|
10
11
|
|
|
@@ -24,7 +25,7 @@ export const Picker = <T extends { value: string; label: string }>({
|
|
|
24
25
|
value,
|
|
25
26
|
onChange,
|
|
26
27
|
}: PickerProps<T>) => {
|
|
27
|
-
const { t } = useTranslation(translationKey);
|
|
28
|
+
const { t } = Hooks.useTranslation(translationKey);
|
|
28
29
|
const sorted = useMemo(() => values?.sort(({ label: a }, { label: b }) => a.localeCompare(b)) ?? [], [values]);
|
|
29
30
|
|
|
30
31
|
return (
|
|
@@ -8,9 +8,9 @@ import React, { useState } from 'react';
|
|
|
8
8
|
import { Filter, Query, Tag, Type } from '@dxos/echo';
|
|
9
9
|
import { EntityId } from '@dxos/keys';
|
|
10
10
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
11
|
-
import { Toolbar } from '@dxos/react-ui';
|
|
12
11
|
import { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
13
12
|
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
13
|
+
import * as Toolbar from '@dxos/react-ui/Toolbar';
|
|
14
14
|
import { Employer, Organization, Person, Pipeline } from '@dxos/types';
|
|
15
15
|
|
|
16
16
|
import { translations } from '#translations';
|
|
@@ -7,8 +7,8 @@ import React, { useCallback, useMemo, useState } from 'react';
|
|
|
7
7
|
|
|
8
8
|
import { Filter, Obj, Query, type QueryAST, type Tag } from '@dxos/echo';
|
|
9
9
|
import { URI } from '@dxos/keys';
|
|
10
|
-
import
|
|
11
|
-
import
|
|
10
|
+
import * as Hooks from '@dxos/react-ui/Hooks';
|
|
11
|
+
import type * as Util from '@dxos/react-ui/Util';
|
|
12
12
|
import { mx } from '@dxos/ui-theme';
|
|
13
13
|
|
|
14
14
|
import { translationKey } from '#translations';
|
|
@@ -16,7 +16,7 @@ import { translationKey } from '#translations';
|
|
|
16
16
|
import { Picker } from './Picker.tsx';
|
|
17
17
|
import { extractTag, extractTypename } from './query.ts';
|
|
18
18
|
|
|
19
|
-
export type QueryFormProps = ThemedClassName<{
|
|
19
|
+
export type QueryFormProps = Util.ThemedClassName<{
|
|
20
20
|
initialQuery?: QueryAST.Query;
|
|
21
21
|
types?: { value: URI.URI; label: string }[];
|
|
22
22
|
tags?: Tag.Tag[];
|
|
@@ -25,7 +25,7 @@ export type QueryFormProps = ThemedClassName<{
|
|
|
25
25
|
|
|
26
26
|
// TODO(wittjosiah): Support more complex queries and traversals.
|
|
27
27
|
export const QueryForm = ({ classNames, initialQuery, types, tags, onChange }: QueryFormProps) => {
|
|
28
|
-
const { t } = useTranslation(translationKey);
|
|
28
|
+
const { t } = Hooks.useTranslation(translationKey);
|
|
29
29
|
|
|
30
30
|
const initialType = initialQuery ? Option.getOrUndefined(extractTypename(initialQuery)) : undefined;
|
|
31
31
|
const initialTag = initialQuery ? Option.getOrUndefined(extractTag(initialQuery)) : undefined;
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type RefObject, useCallback, useEffect } from 'react';
|
|
6
|
+
|
|
7
|
+
import { useManagerOptional, useViewState, useViewStateActions } from '@dxos/react-ui-attention';
|
|
8
|
+
import { type ViewState } from '@dxos/react-ui-attention/types';
|
|
9
|
+
import { type EditorController } from '@dxos/react-ui-editor';
|
|
10
|
+
|
|
11
|
+
/** A view-state value that carries a {@link QueryEditor}'s text beside whatever else the view persists. */
|
|
12
|
+
export type QueryViewState = { query: string };
|
|
13
|
+
|
|
14
|
+
export type PersistentQuerySetter = (query: string | ((query: string) => string)) => void;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A list's filter query, held in a `ViewState` aspect (so it persists per device and per `contextId`)
|
|
18
|
+
* and mirrored into the {@link QueryEditor} behind `editorRef`.
|
|
19
|
+
*
|
|
20
|
+
* The editor takes its text once, as `initialValue`, so a write that did not come from typing — a
|
|
21
|
+
* picker over the same text, clear, another view of the same list, another tab — is pushed into it
|
|
22
|
+
* here. Pushed from the subscription rather than from a render effect: the subscription fires as the
|
|
23
|
+
* value is written, when the editor already holds whatever was just typed, whereas an effect can run
|
|
24
|
+
* for a render that trails fast typing and rewrite the document back to older text.
|
|
25
|
+
*/
|
|
26
|
+
export const usePersistentQuery = <T extends QueryViewState, Encoded = T>(
|
|
27
|
+
aspect: ViewState.Aspect<T, Encoded>,
|
|
28
|
+
contextId: string | undefined,
|
|
29
|
+
editorRef: RefObject<EditorController | null>,
|
|
30
|
+
): [string, PersistentQuerySetter] => {
|
|
31
|
+
const manager = useManagerOptional();
|
|
32
|
+
const { query } = useViewState(aspect, contextId);
|
|
33
|
+
const { update } = useViewStateActions(aspect, contextId);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (!manager || !contextId) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
return manager.subscribe(aspect, contextId, ({ query }) => {
|
|
39
|
+
const editor = editorRef.current;
|
|
40
|
+
if (editor && editor.getText() !== query) {
|
|
41
|
+
editor.setText(query);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}, [manager, aspect, contextId, editorRef]);
|
|
45
|
+
|
|
46
|
+
// Unchanged text keeps the same value, so the editor echoing a pushed text back is not a write.
|
|
47
|
+
const setQuery = useCallback<PersistentQuerySetter>(
|
|
48
|
+
(next) =>
|
|
49
|
+
update((view) => {
|
|
50
|
+
const query = typeof next === 'function' ? next(view.query) : next;
|
|
51
|
+
return view.query === query ? view : { ...view, query };
|
|
52
|
+
}),
|
|
53
|
+
[update],
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return [query, setQuery];
|
|
57
|
+
};
|
package/src/translations.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2026 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import type * as Theme from '@dxos/react-ui/Theme';
|
|
6
6
|
|
|
7
7
|
export const translationKey = '@dxos/react-ui-query';
|
|
8
8
|
|
|
@@ -19,4 +19,4 @@ export const translations = [
|
|
|
19
19
|
},
|
|
20
20
|
},
|
|
21
21
|
},
|
|
22
|
-
] as const satisfies Resource[];
|
|
22
|
+
] as const satisfies Theme.Resource[];
|