@dxos/react-ui-attention 0.8.2-staging.7ac8446 → 0.8.2
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/browser/chunk-4I5OVUE3.mjs +592 -0
- package/dist/lib/browser/chunk-4I5OVUE3.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +5 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +1 -1
- package/dist/lib/node/chunk-EGMHOB43.cjs +635 -0
- package/dist/lib/node/chunk-EGMHOB43.cjs.map +7 -0
- package/dist/lib/node/index.cjs +25 -23
- package/dist/lib/node/index.cjs.map +2 -2
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +2 -2
- package/dist/lib/node-esm/chunk-GBD6BNIC.mjs +594 -0
- package/dist/lib/node-esm/chunk-GBD6BNIC.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +5 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +1 -1
- package/dist/types/src/attention.d.ts +9 -9
- package/dist/types/src/attention.d.ts.map +1 -1
- package/dist/types/src/components/AttentionProvider.d.ts +1 -1
- package/dist/types/src/components/AttentionProvider.d.ts.map +1 -1
- package/dist/types/src/components/SelectionProvider.d.ts +12 -4
- package/dist/types/src/components/SelectionProvider.d.ts.map +1 -1
- package/dist/types/src/selection.d.ts +37 -6
- package/dist/types/src/selection.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -12
- package/src/attention.ts +15 -15
- package/src/components/AttentionGlyph.tsx +2 -2
- package/src/components/SelectionProvider.tsx +48 -12
- package/src/selection.ts +98 -23
- package/dist/lib/browser/chunk-DX37PZNT.mjs +0 -410
- package/dist/lib/browser/chunk-DX37PZNT.mjs.map +0 -7
- package/dist/lib/node/chunk-KNNJ735O.cjs +0 -452
- package/dist/lib/node/chunk-KNNJ735O.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-XLBVDYC3.mjs +0 -412
- package/dist/lib/node-esm/chunk-XLBVDYC3.mjs.map +0 -7
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { createContext } from '@radix-ui/react-context';
|
|
6
|
+
import { Match } from 'effect';
|
|
6
7
|
import React, { useCallback, useMemo, type PropsWithChildren } from 'react';
|
|
7
8
|
|
|
8
9
|
import { useDefaultValue } from '@dxos/react-ui';
|
|
9
10
|
|
|
10
|
-
import { SelectionManager } from '../selection';
|
|
11
|
+
import { defaultSelection, SelectionManager, type Selection, type SelectionMode } from '../selection';
|
|
11
12
|
|
|
12
13
|
const SELECTION_NAME = 'Selection';
|
|
13
14
|
|
|
@@ -22,7 +23,6 @@ const [SelectionContextProvider, useSelectionContext] = createContext<SelectionC
|
|
|
22
23
|
/**
|
|
23
24
|
* Manages selection state across the app for multiple contexts.
|
|
24
25
|
*/
|
|
25
|
-
// TODO(burdon): Review/remove this.
|
|
26
26
|
// TODO(burdon): When is the selection removed?
|
|
27
27
|
export const SelectionProvider = ({
|
|
28
28
|
children,
|
|
@@ -32,25 +32,61 @@ export const SelectionProvider = ({
|
|
|
32
32
|
return <SelectionContextProvider selection={selection}>{children}</SelectionContextProvider>;
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
export const useSelected = <T extends SelectionMode>(
|
|
36
|
+
contextId?: string,
|
|
37
|
+
mode: T = 'multi' as T,
|
|
38
|
+
): T extends 'single'
|
|
39
|
+
? string | undefined
|
|
40
|
+
: T extends 'multi'
|
|
41
|
+
? string[]
|
|
42
|
+
: T extends 'range'
|
|
43
|
+
? { from: string; to: string } | undefined
|
|
44
|
+
: T extends 'multi-range'
|
|
45
|
+
? { from: string; to: string }[]
|
|
46
|
+
: never => {
|
|
39
47
|
const { selection } = useSelectionContext(SELECTION_NAME);
|
|
40
|
-
|
|
48
|
+
if (contextId) {
|
|
49
|
+
return selection.getSelected(contextId, mode);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return Match.type<Selection>().pipe(
|
|
53
|
+
Match.when({ mode: 'single' }, (s) => s.id),
|
|
54
|
+
Match.when({ mode: 'multi' }, (s) => s.ids),
|
|
55
|
+
Match.when({ mode: 'range' }, (s) => (s.from && s.to ? { from: s.from, to: s.to } : undefined)),
|
|
56
|
+
Match.when({ mode: 'multi-range' }, (s) => s.ranges),
|
|
57
|
+
Match.exhaustive,
|
|
58
|
+
)(defaultSelection(mode)) as any;
|
|
41
59
|
};
|
|
42
60
|
|
|
43
61
|
/**
|
|
44
62
|
* Provides functions to manage the selection state for multiple contexts.
|
|
45
63
|
*/
|
|
46
|
-
export const useSelectionActions = (contextIds: string[]) => {
|
|
47
|
-
const stableContextIds = useMemo(() => contextIds, [JSON.stringify(contextIds)]);
|
|
64
|
+
export const useSelectionActions = (contextIds: string[], mode: SelectionMode = 'multi') => {
|
|
65
|
+
const stableContextIds = useMemo(() => contextIds, [JSON.stringify(contextIds)]); // TODO(burdon): Avoid stringify.
|
|
48
66
|
const { selection } = useSelectionContext(SELECTION_NAME);
|
|
49
67
|
|
|
50
|
-
const
|
|
68
|
+
const singleSelect = useCallback(
|
|
69
|
+
(id: string) => {
|
|
70
|
+
for (const contextId of stableContextIds) {
|
|
71
|
+
selection.updateSingle(contextId, id);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
[selection, stableContextIds],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const multiSelect = useCallback(
|
|
51
78
|
(ids: string[]) => {
|
|
52
79
|
for (const contextId of stableContextIds) {
|
|
53
|
-
selection.
|
|
80
|
+
selection.updateMulti(contextId, ids);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
[selection, stableContextIds],
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const rangeSelect = useCallback(
|
|
87
|
+
(from: string, to: string) => {
|
|
88
|
+
for (const contextId of stableContextIds) {
|
|
89
|
+
selection.updateRange(contextId, from, to);
|
|
54
90
|
}
|
|
55
91
|
},
|
|
56
92
|
[selection, stableContextIds],
|
|
@@ -71,5 +107,5 @@ export const useSelectionActions = (contextIds: string[]) => {
|
|
|
71
107
|
}
|
|
72
108
|
}, [selection, stableContextIds]);
|
|
73
109
|
|
|
74
|
-
return {
|
|
110
|
+
return { singleSelect, multiSelect, rangeSelect, toggle, clear };
|
|
75
111
|
};
|
package/src/selection.ts
CHANGED
|
@@ -3,18 +3,35 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { untracked } from '@preact/signals-core';
|
|
6
|
+
import { Match } from 'effect';
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
+
import { invariant } from '@dxos/invariant';
|
|
9
|
+
import { live, type Live } from '@dxos/live-object';
|
|
10
|
+
|
|
11
|
+
export type SelectionMode = 'single' | 'multi' | 'range' | 'multi-range';
|
|
12
|
+
|
|
13
|
+
export type Selection =
|
|
14
|
+
| { mode: 'single'; id?: string }
|
|
15
|
+
| { mode: 'multi'; ids: string[] }
|
|
16
|
+
| { mode: 'range'; from?: string; to?: string }
|
|
17
|
+
| { mode: 'multi-range'; ranges: { from: string; to: string }[] };
|
|
18
|
+
|
|
19
|
+
export const defaultSelection = Match.type<SelectionMode>().pipe(
|
|
20
|
+
Match.when('single', () => ({ mode: 'single' }) as Selection),
|
|
21
|
+
Match.when('multi', () => ({ mode: 'multi', ids: [] }) as Selection),
|
|
22
|
+
Match.when('range', () => ({ mode: 'range' }) as Selection),
|
|
23
|
+
Match.when('multi-range', () => ({ mode: 'multi-range', ranges: [] }) as Selection),
|
|
24
|
+
Match.exhaustive,
|
|
25
|
+
);
|
|
8
26
|
|
|
9
27
|
/**
|
|
10
28
|
* Manages selection state for different contexts.
|
|
11
|
-
* Each context maintains its own
|
|
29
|
+
* Each context maintains its own selection mode and state.
|
|
12
30
|
*/
|
|
13
|
-
// TODO(burdon): Review/remove this abstraction. Selection should be managed by specific components.
|
|
14
31
|
export class SelectionManager {
|
|
15
|
-
private readonly _state =
|
|
32
|
+
private readonly _state = live<{ selections: Record<string, Selection> }>({ selections: {} });
|
|
16
33
|
|
|
17
|
-
constructor(initial: Record<string,
|
|
34
|
+
constructor(initial: Record<string, Selection> = {}) {
|
|
18
35
|
if (Object.keys(initial).length > 0) {
|
|
19
36
|
untracked(() => {
|
|
20
37
|
this._state.selections = initial;
|
|
@@ -22,38 +39,96 @@ export class SelectionManager {
|
|
|
22
39
|
}
|
|
23
40
|
}
|
|
24
41
|
|
|
25
|
-
|
|
26
|
-
|
|
42
|
+
getSelection<T extends SelectionMode | undefined>(
|
|
43
|
+
contextId: string,
|
|
44
|
+
mode: T = undefined as T,
|
|
45
|
+
): T extends undefined ? Live<Selection> | undefined : Live<Selection> {
|
|
46
|
+
const selection = untracked(() => this._state.selections[contextId]);
|
|
47
|
+
if (!mode || selection) {
|
|
48
|
+
return selection;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return untracked(() => {
|
|
52
|
+
this._state.selections[contextId] = defaultSelection(mode);
|
|
53
|
+
return this._state.selections[contextId];
|
|
54
|
+
});
|
|
27
55
|
}
|
|
28
56
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
57
|
+
getSelected<T extends SelectionMode>(
|
|
58
|
+
contextId: string,
|
|
59
|
+
mode: T = 'multi' as T,
|
|
60
|
+
): T extends 'single'
|
|
61
|
+
? string | undefined
|
|
62
|
+
: T extends 'multi'
|
|
63
|
+
? string[]
|
|
64
|
+
: T extends 'range'
|
|
65
|
+
? { from: string; to: string } | undefined
|
|
66
|
+
: T extends 'multi-range'
|
|
67
|
+
? { from: string; to: string }[]
|
|
68
|
+
: never {
|
|
69
|
+
const selection = this.getSelection(contextId, mode);
|
|
70
|
+
invariant(selection?.mode === mode, 'Selection mode mismatch');
|
|
71
|
+
return Match.type<Selection>().pipe(
|
|
72
|
+
Match.when({ mode: 'single' }, (s) => s.id),
|
|
73
|
+
Match.when({ mode: 'multi' }, (s) => s.ids),
|
|
74
|
+
Match.when({ mode: 'range' }, (s) => (s.from && s.to ? { from: s.from, to: s.to } : undefined)),
|
|
75
|
+
Match.when({ mode: 'multi-range' }, (s) => s.ranges),
|
|
76
|
+
Match.exhaustive,
|
|
77
|
+
)(selection) as any;
|
|
36
78
|
}
|
|
37
79
|
|
|
38
|
-
|
|
80
|
+
updateSingle(contextId: string, id: string): void {
|
|
39
81
|
untracked(() => {
|
|
40
|
-
this.
|
|
82
|
+
const selection = this.getSelection(contextId, 'single');
|
|
83
|
+
invariant(selection?.mode === 'single', 'Selection mode is not single');
|
|
84
|
+
selection.id = id;
|
|
41
85
|
});
|
|
42
86
|
}
|
|
43
87
|
|
|
44
|
-
|
|
88
|
+
updateMulti(contextId: string, ids: string[]) {
|
|
45
89
|
untracked(() => {
|
|
46
|
-
this.
|
|
90
|
+
const selection = this.getSelection(contextId, 'multi');
|
|
91
|
+
invariant(selection?.mode === 'multi', 'Selection mode is not multi');
|
|
92
|
+
selection.ids = ids;
|
|
47
93
|
});
|
|
48
94
|
}
|
|
49
95
|
|
|
50
|
-
|
|
96
|
+
updateRange(contextId: string, from: string, to: string) {
|
|
51
97
|
untracked(() => {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
98
|
+
const selection = this.getSelection(contextId, 'range');
|
|
99
|
+
invariant(selection?.mode === 'range', 'Selection mode is not range');
|
|
100
|
+
selection.from = from;
|
|
101
|
+
selection.to = to;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
updateMultiRange(contextId: string, ranges: { from: string; to: string }[]) {
|
|
106
|
+
untracked(() => {
|
|
107
|
+
const selection = this.getSelection(contextId, 'multi-range');
|
|
108
|
+
invariant(selection?.mode === 'multi-range', 'Selection mode is not multi-range');
|
|
109
|
+
selection.ranges = ranges;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
clearSelection(contextId: string): void {
|
|
114
|
+
untracked(() => {
|
|
115
|
+
const selection = this.getSelection(contextId);
|
|
116
|
+
if (selection) {
|
|
117
|
+
this._state.selections[contextId] = defaultSelection(selection.mode);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
toggleSelection(contextId: string, id: string): void {
|
|
123
|
+
untracked(() => {
|
|
124
|
+
const selection = this.getSelection(contextId, 'multi');
|
|
125
|
+
invariant(selection?.mode === 'multi', 'Selection mode is not multi');
|
|
126
|
+
|
|
127
|
+
if (selection.ids.includes(id)) {
|
|
128
|
+
const index = selection.ids.indexOf(id);
|
|
129
|
+
selection.ids.splice(index, 1);
|
|
55
130
|
} else {
|
|
56
|
-
|
|
131
|
+
selection.ids.push(id);
|
|
57
132
|
}
|
|
58
133
|
});
|
|
59
134
|
}
|
|
@@ -1,410 +0,0 @@
|
|
|
1
|
-
// packages/ui/react-ui-attention/src/attention.ts
|
|
2
|
-
import { untracked } from "@preact/signals-core";
|
|
3
|
-
import { S } from "@dxos/echo-schema";
|
|
4
|
-
import { create } from "@dxos/live-object";
|
|
5
|
-
import { ComplexMap } from "@dxos/util";
|
|
6
|
-
var ATTENDABLE_PATH_SEPARATOR = "~";
|
|
7
|
-
var AttentionSchema = S.mutable(S.Struct({
|
|
8
|
-
hasAttention: S.Boolean,
|
|
9
|
-
isAncestor: S.Boolean,
|
|
10
|
-
isRelated: S.Boolean
|
|
11
|
-
}));
|
|
12
|
-
var stringKey = (key) => key.join(",");
|
|
13
|
-
var AttentionManager = class {
|
|
14
|
-
constructor(initial = []) {
|
|
15
|
-
// Each attention path is associated with an attention object.
|
|
16
|
-
// The lookup is not a reactive object to ensure that attention for each path is subscribable independently.
|
|
17
|
-
this._map = new ComplexMap(stringKey);
|
|
18
|
-
this._state = create({
|
|
19
|
-
current: []
|
|
20
|
-
});
|
|
21
|
-
if (initial.length > 0) {
|
|
22
|
-
this.update(initial);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Currently attended element.
|
|
27
|
-
*
|
|
28
|
-
* @reactive
|
|
29
|
-
*/
|
|
30
|
-
get current() {
|
|
31
|
-
return this._state.current;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* All attention paths.
|
|
35
|
-
*/
|
|
36
|
-
keys() {
|
|
37
|
-
return Array.from(this._map.keys());
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Get the attention state for a given path.
|
|
41
|
-
*/
|
|
42
|
-
get(key) {
|
|
43
|
-
const object = this._map.get(key);
|
|
44
|
-
if (object) {
|
|
45
|
-
return object;
|
|
46
|
-
}
|
|
47
|
-
const newObject = create(AttentionSchema, {
|
|
48
|
-
hasAttention: false,
|
|
49
|
-
isAncestor: false,
|
|
50
|
-
isRelated: false
|
|
51
|
-
});
|
|
52
|
-
this._map.set(key, newObject);
|
|
53
|
-
return newObject;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Update the currently attended element.
|
|
57
|
-
*
|
|
58
|
-
* @internal
|
|
59
|
-
*/
|
|
60
|
-
update(nextKey) {
|
|
61
|
-
const currentKey = untracked(() => this.current);
|
|
62
|
-
const currentRelatedTo = currentKey[0];
|
|
63
|
-
const nextRelatedTo = nextKey[0];
|
|
64
|
-
this.get([
|
|
65
|
-
nextRelatedTo
|
|
66
|
-
]);
|
|
67
|
-
const currentRelatedKeys = this.keys().filter((key) => key[0] === currentRelatedTo);
|
|
68
|
-
const nextRelatedKeys = this.keys().filter((key) => key[0] === nextRelatedTo && stringKey(key) !== stringKey(nextKey));
|
|
69
|
-
const prevRelatedKeys = currentRelatedKeys.filter((curr) => !nextRelatedKeys.find((next) => stringKey(curr) === stringKey(next)));
|
|
70
|
-
const currentAncestorKeys = currentKey.slice(1).map((_, i) => currentKey.slice(i + 1, currentKey.length));
|
|
71
|
-
const nextAncestorKeys = nextKey.slice(1).map((_, i) => nextKey.slice(i + 1, nextKey.length));
|
|
72
|
-
const prevAncestorKeys = currentAncestorKeys.filter((curr) => !nextAncestorKeys.find((next) => stringKey(curr) === stringKey(next)));
|
|
73
|
-
this._state.current = nextKey;
|
|
74
|
-
prevRelatedKeys.forEach((key) => this._set(key, {}));
|
|
75
|
-
prevAncestorKeys.forEach((key) => this._set(key, {}));
|
|
76
|
-
nextRelatedKeys.forEach((key) => this._set(key, {
|
|
77
|
-
isRelated: true
|
|
78
|
-
}));
|
|
79
|
-
nextAncestorKeys.forEach((key) => this._set(key, {
|
|
80
|
-
isAncestor: true
|
|
81
|
-
}));
|
|
82
|
-
this._set(nextKey, {
|
|
83
|
-
hasAttention: true
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
_set(key, attention) {
|
|
87
|
-
const object = this.get(key);
|
|
88
|
-
object.hasAttention = attention.hasAttention ?? false;
|
|
89
|
-
object.isAncestor = attention.isAncestor ?? false;
|
|
90
|
-
object.isRelated = attention.isRelated ?? false;
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
var getAttendables = (selector, cursor, acc = []) => {
|
|
94
|
-
const closestAttendable = cursor.closest(selector);
|
|
95
|
-
if (closestAttendable) {
|
|
96
|
-
const attendableId = closestAttendable.getAttribute("data-attendable-id");
|
|
97
|
-
if (!attendableId) {
|
|
98
|
-
const trigger = document.querySelector(`[aria-controls="${closestAttendable.getAttribute("id")}"]`);
|
|
99
|
-
if (!trigger) {
|
|
100
|
-
return acc;
|
|
101
|
-
} else {
|
|
102
|
-
return getAttendables(selector, trigger, acc);
|
|
103
|
-
}
|
|
104
|
-
} else {
|
|
105
|
-
acc.push(...attendableId.split(ATTENDABLE_PATH_SEPARATOR));
|
|
106
|
-
return !closestAttendable.parentElement ? acc : getAttendables(selector, closestAttendable.parentElement, acc);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return [
|
|
110
|
-
...new Set(acc)
|
|
111
|
-
];
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
// packages/ui/react-ui-attention/src/components/AttentionGlyph.tsx
|
|
115
|
-
import React, { forwardRef, useMemo } from "react";
|
|
116
|
-
import { Icon } from "@dxos/react-ui";
|
|
117
|
-
import { mx } from "@dxos/react-ui-theme";
|
|
118
|
-
var attentionGlyphStyles = "inline-block rounded-sm is-3 bs-3 bg-transparent text-accentText transition-colors [[data-contains-attended=true]_&]:bg-attentionRelated [[data-attention=true]_&]:bg-accentSurface [[data-attention=true]_&]:text-inverse [[aria-current][data-attention=true]_&]:bg-accentSurface [[aria-current][data-attention=true]_&]:text-inverse";
|
|
119
|
-
var presenceIconStyles = "is-3 bs-3 group-[[aria-current]_&:hover]/attentionGlyphButton:hidden group-[[aria-current]_&:focus]/attentionGlyphButton:hidden group-[[data-attention=true]_&:hover]/attentionGlyphButton:hidden group-[[data-attention=true]_&:focus]/attentionGlyphButton:hidden group-[[aria-current][data-attention=true]_&:hover]/attentionGlyphButton:hidden group-[[aria-current][data-attention=true]_&:focus]/attentionGlyphButton:hidden";
|
|
120
|
-
var PresenceOne = () => {
|
|
121
|
-
return /* @__PURE__ */ React.createElement("svg", {
|
|
122
|
-
width: "12",
|
|
123
|
-
height: "12",
|
|
124
|
-
viewBox: "0 0 12 12",
|
|
125
|
-
className: presenceIconStyles,
|
|
126
|
-
fill: "none",
|
|
127
|
-
xmlns: "http://www.w3.org/2000/svg"
|
|
128
|
-
}, /* @__PURE__ */ React.createElement("circle", {
|
|
129
|
-
cx: "6",
|
|
130
|
-
cy: "6",
|
|
131
|
-
r: "2.5",
|
|
132
|
-
fill: "currentColor"
|
|
133
|
-
}));
|
|
134
|
-
};
|
|
135
|
-
var PresenceMany = () => {
|
|
136
|
-
return /* @__PURE__ */ React.createElement("svg", {
|
|
137
|
-
width: "12",
|
|
138
|
-
height: "12",
|
|
139
|
-
viewBox: "0 0 12 12",
|
|
140
|
-
className: presenceIconStyles,
|
|
141
|
-
fill: "none",
|
|
142
|
-
xmlns: "http://www.w3.org/2000/svg"
|
|
143
|
-
}, /* @__PURE__ */ React.createElement("path", {
|
|
144
|
-
d: "M6.75 8.27311C7.38815 7.72296 7.79212 6.90866 7.79212 6C7.79212 5.09134 7.38815 4.27704 6.75 3.72689C7.06722 3.58122 7.42019 3.5 7.79212 3.5C9.17283 3.5 10.2921 4.61929 10.2921 6C10.2921 7.38071 9.17283 8.5 7.79212 8.5C7.42019 8.5 7.06723 8.41878 6.75 8.27311Z",
|
|
145
|
-
fill: "currentColor"
|
|
146
|
-
}), /* @__PURE__ */ React.createElement("circle", {
|
|
147
|
-
cx: "4.25",
|
|
148
|
-
cy: "6",
|
|
149
|
-
r: "2.5",
|
|
150
|
-
fill: "currentColor"
|
|
151
|
-
}));
|
|
152
|
-
};
|
|
153
|
-
var Syncing = () => {
|
|
154
|
-
const animationProps = useMemo(() => ({
|
|
155
|
-
// Synchronize animations.
|
|
156
|
-
animationDelay: `-${Date.now() % 2e3}ms`
|
|
157
|
-
}), []);
|
|
158
|
-
return /* @__PURE__ */ React.createElement("div", {
|
|
159
|
-
role: "status",
|
|
160
|
-
className: "flex items-center"
|
|
161
|
-
}, /* @__PURE__ */ React.createElement(Icon, {
|
|
162
|
-
icon: "ph--circle-notch--regular",
|
|
163
|
-
size: 3,
|
|
164
|
-
style: animationProps,
|
|
165
|
-
classNames: "text-subdued animate-[spin_2s_linear_infinite]"
|
|
166
|
-
}));
|
|
167
|
-
};
|
|
168
|
-
var AttentionGlyph = /* @__PURE__ */ forwardRef(({ presence, attended, syncing, containsAttended, classNames, ...props }, forwardedRef) => {
|
|
169
|
-
const icon = syncing ? /* @__PURE__ */ React.createElement(Syncing, null) : presence === "many" ? /* @__PURE__ */ React.createElement(PresenceMany, null) : presence === "one" ? /* @__PURE__ */ React.createElement(PresenceOne, null) : null;
|
|
170
|
-
return /* @__PURE__ */ React.createElement("div", {
|
|
171
|
-
role: "none",
|
|
172
|
-
className: "flex",
|
|
173
|
-
"data-attention": attended,
|
|
174
|
-
"data-contains-attended": containsAttended
|
|
175
|
-
}, /* @__PURE__ */ React.createElement("span", {
|
|
176
|
-
role: "none",
|
|
177
|
-
...props,
|
|
178
|
-
className: mx(attentionGlyphStyles, classNames),
|
|
179
|
-
ref: forwardedRef
|
|
180
|
-
}, icon));
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// packages/ui/react-ui-attention/src/components/AttentionProvider.tsx
|
|
184
|
-
import { createContext } from "@radix-ui/react-context";
|
|
185
|
-
import { Primitive } from "@radix-ui/react-primitive";
|
|
186
|
-
import { Slot } from "@radix-ui/react-slot";
|
|
187
|
-
import React2, { useMemo as useMemo2, forwardRef as forwardRef2 } from "react";
|
|
188
|
-
import { useDefaultValue } from "@dxos/react-ui";
|
|
189
|
-
import { mx as mx2 } from "@dxos/react-ui-theme";
|
|
190
|
-
var ATTENTION_NAME = "Attention";
|
|
191
|
-
var ATTENABLE_ATTRIBUTE = "data-attendable-id";
|
|
192
|
-
var ATTENTION_SOURCE_ATTRIBUTE = "data-is-attention-source";
|
|
193
|
-
var [AttentionContextProvider, useAttentionContext] = createContext(ATTENTION_NAME, {
|
|
194
|
-
attention: new AttentionManager(),
|
|
195
|
-
path: []
|
|
196
|
-
});
|
|
197
|
-
var UNKNOWN_ATTENDABLE = {
|
|
198
|
-
hasAttention: false,
|
|
199
|
-
isAncestor: false,
|
|
200
|
-
isRelated: false
|
|
201
|
-
};
|
|
202
|
-
var useAttention = (attendableId) => {
|
|
203
|
-
const { attention, path } = useAttentionContext(ATTENTION_NAME);
|
|
204
|
-
if (!attendableId) {
|
|
205
|
-
return UNKNOWN_ATTENDABLE;
|
|
206
|
-
}
|
|
207
|
-
const current = [
|
|
208
|
-
...attendableId.split(ATTENDABLE_PATH_SEPARATOR),
|
|
209
|
-
...path
|
|
210
|
-
];
|
|
211
|
-
return attention.get(current);
|
|
212
|
-
};
|
|
213
|
-
var useAttended = () => {
|
|
214
|
-
const { attention } = useAttentionContext(ATTENTION_NAME);
|
|
215
|
-
return attention.current;
|
|
216
|
-
};
|
|
217
|
-
var useAttendableAttributes = (attendableId) => {
|
|
218
|
-
const { hasAttention } = useAttention(attendableId);
|
|
219
|
-
return useMemo2(() => {
|
|
220
|
-
const attributes = {
|
|
221
|
-
[ATTENABLE_ATTRIBUTE]: attendableId
|
|
222
|
-
};
|
|
223
|
-
if (hasAttention) {
|
|
224
|
-
attributes[ATTENTION_SOURCE_ATTRIBUTE] = "true";
|
|
225
|
-
}
|
|
226
|
-
return attributes;
|
|
227
|
-
}, [
|
|
228
|
-
attendableId,
|
|
229
|
-
hasAttention
|
|
230
|
-
]);
|
|
231
|
-
};
|
|
232
|
-
var useAttentionPath = () => {
|
|
233
|
-
const { path } = useAttentionContext(ATTENTION_NAME);
|
|
234
|
-
return path;
|
|
235
|
-
};
|
|
236
|
-
var RootAttentionProvider = ({ children, attention: propsAttention, onChange }) => {
|
|
237
|
-
const attention = useDefaultValue(propsAttention, () => new AttentionManager());
|
|
238
|
-
const handleFocus = (event) => {
|
|
239
|
-
const selector = [
|
|
240
|
-
"[data-attendable-id]",
|
|
241
|
-
...Array.from(document.querySelectorAll("[aria-controls]")).map((el) => `[id="${el.getAttribute("aria-controls")}"]`)
|
|
242
|
-
].join(",");
|
|
243
|
-
const prev = attention.current;
|
|
244
|
-
const next = getAttendables(selector, event.target);
|
|
245
|
-
if (next.length > 0 && (prev.length !== next.length || !!prev.find((id, index) => next[index] !== id))) {
|
|
246
|
-
attention.update(next);
|
|
247
|
-
onChange?.(next);
|
|
248
|
-
}
|
|
249
|
-
};
|
|
250
|
-
return /* @__PURE__ */ React2.createElement(AttentionContextProvider, {
|
|
251
|
-
attention,
|
|
252
|
-
path: []
|
|
253
|
-
}, /* @__PURE__ */ React2.createElement("div", {
|
|
254
|
-
role: "none",
|
|
255
|
-
className: "contents",
|
|
256
|
-
onFocusCapture: handleFocus
|
|
257
|
-
}, children));
|
|
258
|
-
};
|
|
259
|
-
var AttentionProvider = ({ id, children }) => {
|
|
260
|
-
const { attention, path } = useAttentionContext(ATTENTION_NAME);
|
|
261
|
-
const nextPath = useMemo2(() => [
|
|
262
|
-
id,
|
|
263
|
-
...path
|
|
264
|
-
], [
|
|
265
|
-
id,
|
|
266
|
-
path
|
|
267
|
-
]);
|
|
268
|
-
return /* @__PURE__ */ React2.createElement(AttentionContextProvider, {
|
|
269
|
-
attention,
|
|
270
|
-
path: nextPath
|
|
271
|
-
}, children);
|
|
272
|
-
};
|
|
273
|
-
var AttendableContainer = /* @__PURE__ */ forwardRef2(({ id, classNames, children, asChild, ...props }, forwardedRef) => {
|
|
274
|
-
const attendableAttrs = useAttendableAttributes(id);
|
|
275
|
-
const Root = asChild ? Slot : Primitive.div;
|
|
276
|
-
return /* @__PURE__ */ React2.createElement(Root, {
|
|
277
|
-
role: "none",
|
|
278
|
-
...attendableAttrs,
|
|
279
|
-
...props,
|
|
280
|
-
className: mx2("attention-surface", props.tabIndex === 0 && "dx-focus-ring-inset-over-all", classNames),
|
|
281
|
-
ref: forwardedRef
|
|
282
|
-
}, children);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
// packages/ui/react-ui-attention/src/selection.ts
|
|
286
|
-
import { untracked as untracked2 } from "@preact/signals-core";
|
|
287
|
-
import { create as create2 } from "@dxos/live-object";
|
|
288
|
-
var SelectionManager = class {
|
|
289
|
-
constructor(initial = {}) {
|
|
290
|
-
this._state = create2({
|
|
291
|
-
selections: {}
|
|
292
|
-
});
|
|
293
|
-
if (Object.keys(initial).length > 0) {
|
|
294
|
-
untracked2(() => {
|
|
295
|
-
this._state.selections = initial;
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
get selections() {
|
|
300
|
-
return this._state.selections;
|
|
301
|
-
}
|
|
302
|
-
getSelection(contextId) {
|
|
303
|
-
if (!this.selections[contextId]) {
|
|
304
|
-
untracked2(() => {
|
|
305
|
-
this._state.selections[contextId] = /* @__PURE__ */ new Set();
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
return this.selections[contextId];
|
|
309
|
-
}
|
|
310
|
-
updateSelection(contextId, selectedIds) {
|
|
311
|
-
untracked2(() => {
|
|
312
|
-
this._state.selections[contextId] = selectedIds instanceof Set ? selectedIds : new Set(selectedIds);
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
clearSelection(contextId) {
|
|
316
|
-
untracked2(() => {
|
|
317
|
-
this._state.selections[contextId] = /* @__PURE__ */ new Set();
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
toggleSelection(contextId, id) {
|
|
321
|
-
untracked2(() => {
|
|
322
|
-
const current = this.getSelection(contextId);
|
|
323
|
-
if (current.has(id)) {
|
|
324
|
-
current.delete(id);
|
|
325
|
-
} else {
|
|
326
|
-
current.add(id);
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
// packages/ui/react-ui-attention/src/components/SelectionProvider.tsx
|
|
333
|
-
import { createContext as createContext2 } from "@radix-ui/react-context";
|
|
334
|
-
import React3, { useCallback, useMemo as useMemo3 } from "react";
|
|
335
|
-
import { useDefaultValue as useDefaultValue2 } from "@dxos/react-ui";
|
|
336
|
-
var SELECTION_NAME = "Selection";
|
|
337
|
-
var [SelectionContextProvider, useSelectionContext] = createContext2(SELECTION_NAME, {
|
|
338
|
-
selection: new SelectionManager()
|
|
339
|
-
});
|
|
340
|
-
var SelectionProvider = ({ children, selection: propsSelection }) => {
|
|
341
|
-
const selection = useDefaultValue2(propsSelection, () => new SelectionManager());
|
|
342
|
-
return /* @__PURE__ */ React3.createElement(SelectionContextProvider, {
|
|
343
|
-
selection
|
|
344
|
-
}, children);
|
|
345
|
-
};
|
|
346
|
-
var emptySet = /* @__PURE__ */ new Set();
|
|
347
|
-
var useSelectedItems = (contextId) => {
|
|
348
|
-
const { selection } = useSelectionContext(SELECTION_NAME);
|
|
349
|
-
return contextId ? selection.getSelection(contextId) : emptySet;
|
|
350
|
-
};
|
|
351
|
-
var useSelectionActions = (contextIds) => {
|
|
352
|
-
const stableContextIds = useMemo3(() => contextIds, [
|
|
353
|
-
JSON.stringify(contextIds)
|
|
354
|
-
]);
|
|
355
|
-
const { selection } = useSelectionContext(SELECTION_NAME);
|
|
356
|
-
const select = useCallback((ids) => {
|
|
357
|
-
for (const contextId of stableContextIds) {
|
|
358
|
-
selection.updateSelection(contextId, ids);
|
|
359
|
-
}
|
|
360
|
-
}, [
|
|
361
|
-
selection,
|
|
362
|
-
stableContextIds
|
|
363
|
-
]);
|
|
364
|
-
const toggle = useCallback((id) => {
|
|
365
|
-
for (const contextId of stableContextIds) {
|
|
366
|
-
selection.toggleSelection(contextId, id);
|
|
367
|
-
}
|
|
368
|
-
}, [
|
|
369
|
-
selection,
|
|
370
|
-
stableContextIds
|
|
371
|
-
]);
|
|
372
|
-
const clear = useCallback(() => {
|
|
373
|
-
for (const contextId of stableContextIds) {
|
|
374
|
-
selection.clearSelection(contextId);
|
|
375
|
-
}
|
|
376
|
-
}, [
|
|
377
|
-
selection,
|
|
378
|
-
stableContextIds
|
|
379
|
-
]);
|
|
380
|
-
return {
|
|
381
|
-
select,
|
|
382
|
-
toggle,
|
|
383
|
-
clear
|
|
384
|
-
};
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
export {
|
|
388
|
-
ATTENDABLE_PATH_SEPARATOR,
|
|
389
|
-
AttentionSchema,
|
|
390
|
-
AttentionManager,
|
|
391
|
-
getAttendables,
|
|
392
|
-
Syncing,
|
|
393
|
-
AttentionGlyph,
|
|
394
|
-
ATTENTION_NAME,
|
|
395
|
-
ATTENABLE_ATTRIBUTE,
|
|
396
|
-
ATTENTION_SOURCE_ATTRIBUTE,
|
|
397
|
-
useAttentionContext,
|
|
398
|
-
useAttention,
|
|
399
|
-
useAttended,
|
|
400
|
-
useAttendableAttributes,
|
|
401
|
-
useAttentionPath,
|
|
402
|
-
RootAttentionProvider,
|
|
403
|
-
AttentionProvider,
|
|
404
|
-
AttendableContainer,
|
|
405
|
-
SelectionManager,
|
|
406
|
-
SelectionProvider,
|
|
407
|
-
useSelectedItems,
|
|
408
|
-
useSelectionActions
|
|
409
|
-
};
|
|
410
|
-
//# sourceMappingURL=chunk-DX37PZNT.mjs.map
|