@dxos/react-ui-attention 0.8.4-main.ead640a → 0.8.4-main.ef1bc66f44
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-PAZBZOO2.mjs +7 -0
- package/dist/lib/browser/chunk-PAZBZOO2.mjs.map +7 -0
- package/dist/lib/browser/chunk-W5HPVX5Q.mjs +705 -0
- package/dist/lib/browser/chunk-W5HPVX5Q.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +75 -8
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +21 -5
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/types.mjs +7 -0
- package/dist/lib/browser/types.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-NFJ45DDP.mjs +706 -0
- package/dist/lib/node-esm/chunk-NFJ45DDP.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-PMGNPL4M.mjs +9 -0
- package/dist/lib/node-esm/chunk-PMGNPL4M.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +75 -8
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +21 -5
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/types.mjs +8 -0
- package/dist/lib/node-esm/types.mjs.map +7 -0
- package/dist/types/src/attention.d.ts +22 -17
- package/dist/types/src/attention.d.ts.map +1 -1
- package/dist/types/src/components/AttentionProvider.d.ts +5 -4
- package/dist/types/src/components/AttentionProvider.d.ts.map +1 -1
- package/dist/types/src/components/SelectionProvider.d.ts +5 -4
- package/dist/types/src/components/SelectionProvider.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/selection.d.ts +23 -3
- package/dist/types/src/selection.d.ts.map +1 -1
- package/dist/types/src/testing/decorators/withAttention.d.ts +5 -1
- package/dist/types/src/testing/decorators/withAttention.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +10 -0
- package/dist/types/src/types.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +35 -23
- package/src/attention.test.ts +12 -7
- package/src/attention.ts +64 -42
- package/src/components/AttentionGlyph.stories.tsx +2 -2
- package/src/components/AttentionGlyph.tsx +1 -1
- package/src/components/AttentionProvider.stories.tsx +1 -1
- package/src/components/AttentionProvider.tsx +72 -65
- package/src/components/SelectionProvider.tsx +62 -15
- package/src/index.ts +1 -0
- package/src/selection.ts +111 -71
- package/src/testing/decorators/withAttention.ts +21 -4
- package/src/types.ts +16 -0
- package/dist/lib/browser/chunk-HELLA3BH.mjs +0 -675
- package/dist/lib/browser/chunk-HELLA3BH.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-FBZV7QG7.mjs +0 -677
- package/dist/lib/node-esm/chunk-FBZV7QG7.mjs.map +0 -7
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';const require = createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
ATTENDABLE_PATH_SEPARATOR
|
|
4
|
+
} from "./chunk-PMGNPL4M.mjs";
|
|
5
|
+
|
|
6
|
+
// src/attention.ts
|
|
7
|
+
import { Atom } from "@effect-atom/atom-react";
|
|
8
|
+
import { ComplexMap } from "@dxos/util";
|
|
9
|
+
var stringKey = (key) => key.join(",");
|
|
10
|
+
var AttentionManager = class {
|
|
11
|
+
_registry;
|
|
12
|
+
// Each attention path is associated with an attention atom.
|
|
13
|
+
// The lookup is not a reactive object to ensure that attention for each path is subscribable independently.
|
|
14
|
+
_map = new ComplexMap(stringKey);
|
|
15
|
+
_currentAtom;
|
|
16
|
+
constructor(_registry, initial = []) {
|
|
17
|
+
this._registry = _registry;
|
|
18
|
+
this._currentAtom = Atom.make([]);
|
|
19
|
+
if (initial.length > 0) {
|
|
20
|
+
this.update(initial);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Atom for the currently attended element path.
|
|
25
|
+
*/
|
|
26
|
+
get current() {
|
|
27
|
+
return this._currentAtom;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Gets the currently attended element path.
|
|
31
|
+
*/
|
|
32
|
+
getCurrent() {
|
|
33
|
+
return this._registry.get(this._currentAtom);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Subscribe to changes in the current attention path.
|
|
37
|
+
*/
|
|
38
|
+
subscribeCurrent(cb) {
|
|
39
|
+
this._registry.get(this._currentAtom);
|
|
40
|
+
return this._registry.subscribe(this._currentAtom, () => {
|
|
41
|
+
cb(this._registry.get(this._currentAtom));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* All attention paths.
|
|
46
|
+
*/
|
|
47
|
+
keys() {
|
|
48
|
+
return Array.from(this._map.keys());
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get the attention state for a given path.
|
|
52
|
+
*/
|
|
53
|
+
get(key) {
|
|
54
|
+
const atom = this._getAtom(key);
|
|
55
|
+
return this._registry.get(atom);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Subscribe to changes in the attention state for a given path.
|
|
59
|
+
*/
|
|
60
|
+
subscribe(key, cb) {
|
|
61
|
+
const atom = this._getAtom(key);
|
|
62
|
+
this._registry.get(atom);
|
|
63
|
+
return this._registry.subscribe(atom, () => {
|
|
64
|
+
cb(this._registry.get(atom));
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
_getAtom(key) {
|
|
68
|
+
const existing = this._map.get(key);
|
|
69
|
+
if (existing) {
|
|
70
|
+
return existing;
|
|
71
|
+
}
|
|
72
|
+
const newAtom = Atom.make({
|
|
73
|
+
hasAttention: false,
|
|
74
|
+
isAncestor: false,
|
|
75
|
+
isRelated: false
|
|
76
|
+
});
|
|
77
|
+
this._map.set(key, newAtom);
|
|
78
|
+
return newAtom;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Update the currently attended element.
|
|
82
|
+
*
|
|
83
|
+
* @internal
|
|
84
|
+
*/
|
|
85
|
+
update(nextKey) {
|
|
86
|
+
const currentKey = this.getCurrent();
|
|
87
|
+
const currentRelatedTo = currentKey[0];
|
|
88
|
+
const nextRelatedTo = nextKey[0];
|
|
89
|
+
this._getAtom([
|
|
90
|
+
nextRelatedTo
|
|
91
|
+
]);
|
|
92
|
+
const currentRelatedKeys = this.keys().filter((key) => key[0] === currentRelatedTo);
|
|
93
|
+
const nextRelatedKeys = this.keys().filter((key) => key[0] === nextRelatedTo && stringKey(key) !== stringKey(nextKey));
|
|
94
|
+
const prevRelatedKeys = currentRelatedKeys.filter((curr) => !nextRelatedKeys.find((next) => stringKey(curr) === stringKey(next)));
|
|
95
|
+
const currentAncestorKeys = currentKey.slice(1).map((_, i) => currentKey.slice(i + 1, currentKey.length));
|
|
96
|
+
const nextAncestorKeys = nextKey.slice(1).map((_, i) => nextKey.slice(i + 1, nextKey.length));
|
|
97
|
+
const prevAncestorKeys = currentAncestorKeys.filter((curr) => !nextAncestorKeys.find((next) => stringKey(curr) === stringKey(next)));
|
|
98
|
+
this._registry.set(this._currentAtom, nextKey);
|
|
99
|
+
prevRelatedKeys.forEach((key) => this._set(key, {}));
|
|
100
|
+
prevAncestorKeys.forEach((key) => this._set(key, {}));
|
|
101
|
+
nextRelatedKeys.forEach((key) => this._set(key, {
|
|
102
|
+
isRelated: true
|
|
103
|
+
}));
|
|
104
|
+
nextAncestorKeys.forEach((key) => this._set(key, {
|
|
105
|
+
isAncestor: true
|
|
106
|
+
}));
|
|
107
|
+
this._set(nextKey, {
|
|
108
|
+
hasAttention: true
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
_set(key, attention) {
|
|
112
|
+
const atom = this._getAtom(key);
|
|
113
|
+
this._registry.set(atom, {
|
|
114
|
+
hasAttention: attention.hasAttention ?? false,
|
|
115
|
+
isAncestor: attention.isAncestor ?? false,
|
|
116
|
+
isRelated: attention.isRelated ?? false
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
var getAttendables = (selector, cursor, acc = []) => {
|
|
121
|
+
const closestAttendable = cursor.closest(selector);
|
|
122
|
+
if (closestAttendable) {
|
|
123
|
+
const attendableId = closestAttendable.getAttribute("data-attendable-id");
|
|
124
|
+
if (!attendableId) {
|
|
125
|
+
const trigger = document.querySelector(`[aria-controls="${closestAttendable.getAttribute("id")}"]`);
|
|
126
|
+
if (!trigger) {
|
|
127
|
+
return acc;
|
|
128
|
+
} else {
|
|
129
|
+
return getAttendables(selector, trigger, acc);
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
acc.push(...attendableId.split(ATTENDABLE_PATH_SEPARATOR));
|
|
133
|
+
return !closestAttendable.parentElement ? acc : getAttendables(selector, closestAttendable.parentElement, acc);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return [
|
|
137
|
+
...new Set(acc)
|
|
138
|
+
];
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// src/components/AttentionProvider.tsx
|
|
142
|
+
import { RegistryContext } from "@effect-atom/atom-react";
|
|
143
|
+
import { createContext } from "@radix-ui/react-context";
|
|
144
|
+
import { Primitive } from "@radix-ui/react-primitive";
|
|
145
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
146
|
+
import React, { forwardRef, useContext, useEffect, useMemo, useState } from "react";
|
|
147
|
+
import { log } from "@dxos/log";
|
|
148
|
+
import { useDefaultValue } from "@dxos/react-hooks";
|
|
149
|
+
import { mx } from "@dxos/ui-theme";
|
|
150
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-attention/src/components/AttentionProvider.tsx";
|
|
151
|
+
var ATTENTION_NAME = "Attention";
|
|
152
|
+
var ATTENDABLE_ATTRIBUTE = "data-attendable-id";
|
|
153
|
+
var ATTENTION_SOURCE_ATTRIBUTE = "data-is-attention-source";
|
|
154
|
+
var [AttentionContextProvider, useAttentionContext] = createContext(ATTENTION_NAME, {
|
|
155
|
+
attention: void 0,
|
|
156
|
+
path: []
|
|
157
|
+
});
|
|
158
|
+
var UNKNOWN_ATTENDABLE = {
|
|
159
|
+
hasAttention: false,
|
|
160
|
+
isAncestor: false,
|
|
161
|
+
isRelated: false
|
|
162
|
+
};
|
|
163
|
+
var useAttention = (attendableId) => {
|
|
164
|
+
const { attention, path } = useAttentionContext(ATTENTION_NAME);
|
|
165
|
+
const [state, setState] = useState(UNKNOWN_ATTENDABLE);
|
|
166
|
+
const key = useMemo(() => {
|
|
167
|
+
if (!attendableId) {
|
|
168
|
+
return void 0;
|
|
169
|
+
}
|
|
170
|
+
return [
|
|
171
|
+
...attendableId.split(ATTENDABLE_PATH_SEPARATOR),
|
|
172
|
+
...path
|
|
173
|
+
];
|
|
174
|
+
}, [
|
|
175
|
+
attendableId,
|
|
176
|
+
path
|
|
177
|
+
]);
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
if (!key || !attention) {
|
|
180
|
+
setState(UNKNOWN_ATTENDABLE);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
setState(attention.get(key));
|
|
184
|
+
return attention.subscribe(key, (newState) => {
|
|
185
|
+
setState(newState);
|
|
186
|
+
});
|
|
187
|
+
}, [
|
|
188
|
+
attention,
|
|
189
|
+
key
|
|
190
|
+
]);
|
|
191
|
+
return state;
|
|
192
|
+
};
|
|
193
|
+
var useAttended = () => {
|
|
194
|
+
const { attention } = useAttentionContext(ATTENTION_NAME);
|
|
195
|
+
const [current, setCurrent] = useState([]);
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
if (!attention) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
setCurrent(attention.getCurrent());
|
|
201
|
+
return attention.subscribeCurrent((newCurrent) => {
|
|
202
|
+
setCurrent(newCurrent);
|
|
203
|
+
});
|
|
204
|
+
}, [
|
|
205
|
+
attention
|
|
206
|
+
]);
|
|
207
|
+
return current;
|
|
208
|
+
};
|
|
209
|
+
var useAttentionAttributes = (attendableId) => {
|
|
210
|
+
const { hasAttention } = useAttention(attendableId);
|
|
211
|
+
return useMemo(() => {
|
|
212
|
+
const attributes = {
|
|
213
|
+
[ATTENDABLE_ATTRIBUTE]: attendableId
|
|
214
|
+
};
|
|
215
|
+
if (hasAttention) {
|
|
216
|
+
attributes[ATTENTION_SOURCE_ATTRIBUTE] = "true";
|
|
217
|
+
}
|
|
218
|
+
return attributes;
|
|
219
|
+
}, [
|
|
220
|
+
attendableId,
|
|
221
|
+
hasAttention
|
|
222
|
+
]);
|
|
223
|
+
};
|
|
224
|
+
var useAttentionPath = () => {
|
|
225
|
+
const { path } = useAttentionContext(ATTENTION_NAME);
|
|
226
|
+
return path;
|
|
227
|
+
};
|
|
228
|
+
var RootAttentionProvider = ({ children, attention: propsAttention, onChange }) => {
|
|
229
|
+
const registry = useContext(RegistryContext);
|
|
230
|
+
const attention = useDefaultValue(propsAttention, () => new AttentionManager(registry));
|
|
231
|
+
const handleFocus = (event) => {
|
|
232
|
+
log("focus", {
|
|
233
|
+
related: event.relatedTarget,
|
|
234
|
+
target: event.target
|
|
235
|
+
}, {
|
|
236
|
+
F: __dxlog_file,
|
|
237
|
+
L: 130,
|
|
238
|
+
S: void 0,
|
|
239
|
+
C: (f, a) => f(...a)
|
|
240
|
+
});
|
|
241
|
+
const selector = [
|
|
242
|
+
"[data-attendable-id]",
|
|
243
|
+
...Array.from(document.querySelectorAll("[aria-controls]")).map((el) => `[id="${el.getAttribute("aria-controls")}"]`)
|
|
244
|
+
].join(",");
|
|
245
|
+
const prev = attention.getCurrent();
|
|
246
|
+
const next = getAttendables(selector, event.target);
|
|
247
|
+
if (next.length > 0 && (prev.length !== next.length || !!prev.find((id, index) => next[index] !== id))) {
|
|
248
|
+
attention.update(next);
|
|
249
|
+
onChange?.(next);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
return /* @__PURE__ */ React.createElement(AttentionContextProvider, {
|
|
253
|
+
attention,
|
|
254
|
+
path: []
|
|
255
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
256
|
+
role: "none",
|
|
257
|
+
className: "contents",
|
|
258
|
+
onFocusCapture: handleFocus
|
|
259
|
+
}, children));
|
|
260
|
+
};
|
|
261
|
+
var AttentionProvider = ({ id, children }) => {
|
|
262
|
+
const { attention, path } = useAttentionContext(ATTENTION_NAME);
|
|
263
|
+
const nextPath = useMemo(() => [
|
|
264
|
+
id,
|
|
265
|
+
...path
|
|
266
|
+
], [
|
|
267
|
+
id,
|
|
268
|
+
path
|
|
269
|
+
]);
|
|
270
|
+
return /* @__PURE__ */ React.createElement(AttentionContextProvider, {
|
|
271
|
+
attention,
|
|
272
|
+
path: nextPath
|
|
273
|
+
}, children);
|
|
274
|
+
};
|
|
275
|
+
var AttendableContainer = /* @__PURE__ */ forwardRef(({ id, classNames, children, asChild, ...props }, forwardedRef) => {
|
|
276
|
+
const attentionAttrs = useAttentionAttributes(id);
|
|
277
|
+
const Root = asChild ? Slot : Primitive.div;
|
|
278
|
+
return /* @__PURE__ */ React.createElement(Root, {
|
|
279
|
+
role: "none",
|
|
280
|
+
...attentionAttrs,
|
|
281
|
+
...props,
|
|
282
|
+
className: mx("attention-surface", props.tabIndex === 0 && "dx-focus-ring-inset-over-all", classNames),
|
|
283
|
+
ref: forwardedRef
|
|
284
|
+
}, children);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// src/selection.ts
|
|
288
|
+
import { Atom as Atom2 } from "@effect-atom/atom-react";
|
|
289
|
+
import * as Match from "effect/Match";
|
|
290
|
+
import * as Schema from "effect/Schema";
|
|
291
|
+
import { invariant } from "@dxos/invariant";
|
|
292
|
+
var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/react-ui-attention/src/selection.ts";
|
|
293
|
+
var SelectionSchema = Schema.Union(Schema.Struct({
|
|
294
|
+
mode: Schema.Literal("single"),
|
|
295
|
+
id: Schema.optional(Schema.String)
|
|
296
|
+
}).pipe(Schema.mutable), Schema.Struct({
|
|
297
|
+
mode: Schema.Literal("multi"),
|
|
298
|
+
ids: Schema.Array(Schema.String).pipe(Schema.mutable)
|
|
299
|
+
}).pipe(Schema.mutable), Schema.Struct({
|
|
300
|
+
mode: Schema.Literal("range"),
|
|
301
|
+
from: Schema.optional(Schema.String),
|
|
302
|
+
to: Schema.optional(Schema.String)
|
|
303
|
+
}).pipe(Schema.mutable), Schema.Struct({
|
|
304
|
+
mode: Schema.Literal("multi-range"),
|
|
305
|
+
ranges: Schema.Array(Schema.Struct({
|
|
306
|
+
from: Schema.String,
|
|
307
|
+
to: Schema.String
|
|
308
|
+
})).pipe(Schema.mutable)
|
|
309
|
+
}).pipe(Schema.mutable)).pipe(Schema.mutable);
|
|
310
|
+
var defaultSelection = Match.type().pipe(Match.withReturnType(), Match.when("single", () => ({
|
|
311
|
+
mode: "single"
|
|
312
|
+
})), Match.when("multi", () => ({
|
|
313
|
+
mode: "multi",
|
|
314
|
+
ids: []
|
|
315
|
+
})), Match.when("range", () => ({
|
|
316
|
+
mode: "range"
|
|
317
|
+
})), Match.when("multi-range", () => ({
|
|
318
|
+
mode: "multi-range",
|
|
319
|
+
ranges: []
|
|
320
|
+
})), Match.exhaustive);
|
|
321
|
+
var getSelectionSet = (selectionManager, contextId) => {
|
|
322
|
+
const ids = new Set(contextId ? [
|
|
323
|
+
contextId
|
|
324
|
+
] : []);
|
|
325
|
+
for (const context of selectionManager.getSelectionContexts()) {
|
|
326
|
+
const selection = selectionManager.getSelection(context);
|
|
327
|
+
if (selection?.mode === "multi") {
|
|
328
|
+
for (const id of selection.ids) {
|
|
329
|
+
ids.add(id);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return ids;
|
|
334
|
+
};
|
|
335
|
+
var SelectionManager = class {
|
|
336
|
+
_registry;
|
|
337
|
+
_state;
|
|
338
|
+
constructor(_registry, initial = {}) {
|
|
339
|
+
this._registry = _registry;
|
|
340
|
+
this._state = Atom2.make({
|
|
341
|
+
selections: {
|
|
342
|
+
...initial
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Get the state atom for reactive access in the graph system.
|
|
348
|
+
*/
|
|
349
|
+
get state() {
|
|
350
|
+
return this._state;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Gets the current state.
|
|
354
|
+
*/
|
|
355
|
+
getState() {
|
|
356
|
+
return this._registry.get(this._state);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Subscribe to changes in the selection state.
|
|
360
|
+
*/
|
|
361
|
+
subscribe(cb) {
|
|
362
|
+
this._registry.get(this._state);
|
|
363
|
+
return this._registry.subscribe(this._state, () => {
|
|
364
|
+
cb(this._registry.get(this._state));
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
getSelectionContexts() {
|
|
368
|
+
return Object.keys(this._registry.get(this._state).selections);
|
|
369
|
+
}
|
|
370
|
+
getSelection(contextId, mode = void 0) {
|
|
371
|
+
const state = this._registry.get(this._state);
|
|
372
|
+
const selection = state.selections[contextId];
|
|
373
|
+
if (!mode || selection) {
|
|
374
|
+
return selection;
|
|
375
|
+
}
|
|
376
|
+
const newSelection = defaultSelection(mode);
|
|
377
|
+
this._registry.update(this._state, (state2) => ({
|
|
378
|
+
selections: {
|
|
379
|
+
...state2.selections,
|
|
380
|
+
[contextId]: newSelection
|
|
381
|
+
}
|
|
382
|
+
}));
|
|
383
|
+
return newSelection;
|
|
384
|
+
}
|
|
385
|
+
// TODO(burdon): Disambiguate with getSelection?
|
|
386
|
+
getSelected(contextId, mode = "multi") {
|
|
387
|
+
const selection = this.getSelection(contextId, mode);
|
|
388
|
+
invariant(selection?.mode === mode, "Selection mode mismatch", {
|
|
389
|
+
F: __dxlog_file2,
|
|
390
|
+
L: 134,
|
|
391
|
+
S: this,
|
|
392
|
+
A: [
|
|
393
|
+
"selection?.mode === mode",
|
|
394
|
+
"'Selection mode mismatch'"
|
|
395
|
+
]
|
|
396
|
+
});
|
|
397
|
+
return Match.type().pipe(Match.when({
|
|
398
|
+
mode: "single"
|
|
399
|
+
}, (s) => s.id), Match.when({
|
|
400
|
+
mode: "multi"
|
|
401
|
+
}, (s) => s.ids), Match.when({
|
|
402
|
+
mode: "range"
|
|
403
|
+
}, (s) => s.from && s.to ? {
|
|
404
|
+
from: s.from,
|
|
405
|
+
to: s.to
|
|
406
|
+
} : void 0), Match.when({
|
|
407
|
+
mode: "multi-range"
|
|
408
|
+
}, (s) => s.ranges), Match.exhaustive)(selection);
|
|
409
|
+
}
|
|
410
|
+
updateSingle(contextId, id) {
|
|
411
|
+
const selection = this.getSelection(contextId, "single");
|
|
412
|
+
invariant(selection?.mode === "single", "Selection mode is not single", {
|
|
413
|
+
F: __dxlog_file2,
|
|
414
|
+
L: 147,
|
|
415
|
+
S: this,
|
|
416
|
+
A: [
|
|
417
|
+
"selection?.mode === 'single'",
|
|
418
|
+
"'Selection mode is not single'"
|
|
419
|
+
]
|
|
420
|
+
});
|
|
421
|
+
this._registry.update(this._state, (state) => ({
|
|
422
|
+
selections: {
|
|
423
|
+
...state.selections,
|
|
424
|
+
[contextId]: {
|
|
425
|
+
...selection,
|
|
426
|
+
id
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}));
|
|
430
|
+
}
|
|
431
|
+
updateMulti(contextId, ids) {
|
|
432
|
+
const selection = this.getSelection(contextId, "multi");
|
|
433
|
+
invariant(selection?.mode === "multi", "Selection mode is not multi", {
|
|
434
|
+
F: __dxlog_file2,
|
|
435
|
+
L: 158,
|
|
436
|
+
S: this,
|
|
437
|
+
A: [
|
|
438
|
+
"selection?.mode === 'multi'",
|
|
439
|
+
"'Selection mode is not multi'"
|
|
440
|
+
]
|
|
441
|
+
});
|
|
442
|
+
this._registry.update(this._state, (state) => ({
|
|
443
|
+
selections: {
|
|
444
|
+
...state.selections,
|
|
445
|
+
[contextId]: {
|
|
446
|
+
...selection,
|
|
447
|
+
ids: [
|
|
448
|
+
...ids
|
|
449
|
+
]
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}));
|
|
453
|
+
}
|
|
454
|
+
updateRange(contextId, from, to) {
|
|
455
|
+
const selection = this.getSelection(contextId, "range");
|
|
456
|
+
invariant(selection?.mode === "range", "Selection mode is not range", {
|
|
457
|
+
F: __dxlog_file2,
|
|
458
|
+
L: 169,
|
|
459
|
+
S: this,
|
|
460
|
+
A: [
|
|
461
|
+
"selection?.mode === 'range'",
|
|
462
|
+
"'Selection mode is not range'"
|
|
463
|
+
]
|
|
464
|
+
});
|
|
465
|
+
this._registry.update(this._state, (state) => ({
|
|
466
|
+
selections: {
|
|
467
|
+
...state.selections,
|
|
468
|
+
[contextId]: {
|
|
469
|
+
...selection,
|
|
470
|
+
from,
|
|
471
|
+
to
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}));
|
|
475
|
+
}
|
|
476
|
+
updateMultiRange(contextId, ranges) {
|
|
477
|
+
const selection = this.getSelection(contextId, "multi-range");
|
|
478
|
+
invariant(selection?.mode === "multi-range", "Selection mode is not multi-range", {
|
|
479
|
+
F: __dxlog_file2,
|
|
480
|
+
L: 180,
|
|
481
|
+
S: this,
|
|
482
|
+
A: [
|
|
483
|
+
"selection?.mode === 'multi-range'",
|
|
484
|
+
"'Selection mode is not multi-range'"
|
|
485
|
+
]
|
|
486
|
+
});
|
|
487
|
+
this._registry.update(this._state, (state) => ({
|
|
488
|
+
selections: {
|
|
489
|
+
...state.selections,
|
|
490
|
+
[contextId]: {
|
|
491
|
+
...selection,
|
|
492
|
+
ranges: [
|
|
493
|
+
...ranges
|
|
494
|
+
]
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}));
|
|
498
|
+
}
|
|
499
|
+
clearSelection(contextId) {
|
|
500
|
+
const selection = this.getSelection(contextId);
|
|
501
|
+
if (!selection) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const clearedSelection = Match.type().pipe(Match.when({
|
|
505
|
+
mode: "single"
|
|
506
|
+
}, (s) => ({
|
|
507
|
+
...s,
|
|
508
|
+
id: void 0
|
|
509
|
+
})), Match.when({
|
|
510
|
+
mode: "multi"
|
|
511
|
+
}, (s) => ({
|
|
512
|
+
...s,
|
|
513
|
+
ids: []
|
|
514
|
+
})), Match.when({
|
|
515
|
+
mode: "range"
|
|
516
|
+
}, (s) => ({
|
|
517
|
+
...s,
|
|
518
|
+
from: void 0,
|
|
519
|
+
to: void 0
|
|
520
|
+
})), Match.when({
|
|
521
|
+
mode: "multi-range"
|
|
522
|
+
}, (s) => ({
|
|
523
|
+
...s,
|
|
524
|
+
ranges: []
|
|
525
|
+
})), Match.exhaustive)(selection);
|
|
526
|
+
this._registry.update(this._state, (state) => ({
|
|
527
|
+
selections: {
|
|
528
|
+
...state.selections,
|
|
529
|
+
[contextId]: clearedSelection
|
|
530
|
+
}
|
|
531
|
+
}));
|
|
532
|
+
}
|
|
533
|
+
toggleSelection(contextId, id) {
|
|
534
|
+
const selection = this.getSelection(contextId, "multi");
|
|
535
|
+
invariant(selection?.mode === "multi", "Selection mode is not multi", {
|
|
536
|
+
F: __dxlog_file2,
|
|
537
|
+
L: 213,
|
|
538
|
+
S: this,
|
|
539
|
+
A: [
|
|
540
|
+
"selection?.mode === 'multi'",
|
|
541
|
+
"'Selection mode is not multi'"
|
|
542
|
+
]
|
|
543
|
+
});
|
|
544
|
+
const newIds = selection.ids.includes(id) ? selection.ids.filter((existingId) => existingId !== id) : [
|
|
545
|
+
...selection.ids,
|
|
546
|
+
id
|
|
547
|
+
];
|
|
548
|
+
this._registry.update(this._state, (state) => ({
|
|
549
|
+
selections: {
|
|
550
|
+
...state.selections,
|
|
551
|
+
[contextId]: {
|
|
552
|
+
...selection,
|
|
553
|
+
ids: newIds
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}));
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
// src/components/SelectionProvider.tsx
|
|
561
|
+
import { RegistryContext as RegistryContext2 } from "@effect-atom/atom-react";
|
|
562
|
+
import { createContext as createContext2 } from "@radix-ui/react-context";
|
|
563
|
+
import * as Match2 from "effect/Match";
|
|
564
|
+
import React2, { useCallback, useContext as useContext2, useEffect as useEffect2, useMemo as useMemo2, useState as useState2 } from "react";
|
|
565
|
+
import { useDefaultValue as useDefaultValue2 } from "@dxos/react-hooks";
|
|
566
|
+
var SELECTION_NAME = "Selection";
|
|
567
|
+
var [SelectionContextProvider, useSelectionContext] = createContext2(SELECTION_NAME, {
|
|
568
|
+
selection: void 0
|
|
569
|
+
});
|
|
570
|
+
var SelectionProvider = ({ children, selection: propsSelection }) => {
|
|
571
|
+
const registry = useContext2(RegistryContext2);
|
|
572
|
+
const selection = useDefaultValue2(propsSelection, () => new SelectionManager(registry));
|
|
573
|
+
return /* @__PURE__ */ React2.createElement(SelectionContextProvider, {
|
|
574
|
+
selection
|
|
575
|
+
}, children);
|
|
576
|
+
};
|
|
577
|
+
var useSelectionManager = () => {
|
|
578
|
+
const { selection } = useSelectionContext(SELECTION_NAME);
|
|
579
|
+
return selection;
|
|
580
|
+
};
|
|
581
|
+
var getDefaultResult = (mode) => {
|
|
582
|
+
return Match2.type().pipe(Match2.when({
|
|
583
|
+
mode: "single"
|
|
584
|
+
}, (selection) => selection.id), Match2.when({
|
|
585
|
+
mode: "multi"
|
|
586
|
+
}, (selection) => selection.ids), Match2.when({
|
|
587
|
+
mode: "range"
|
|
588
|
+
}, (selection) => selection.from && selection.to ? {
|
|
589
|
+
from: selection.from,
|
|
590
|
+
to: selection.to
|
|
591
|
+
} : void 0), Match2.when({
|
|
592
|
+
mode: "multi-range"
|
|
593
|
+
}, (selection) => selection.ranges), Match2.exhaustive)(defaultSelection(mode));
|
|
594
|
+
};
|
|
595
|
+
var useSelected = (contextId, mode = "multi") => {
|
|
596
|
+
const { selection } = useSelectionContext(SELECTION_NAME);
|
|
597
|
+
const [state, setState] = useState2(() => contextId && selection ? selection.getSelected(contextId, mode) : getDefaultResult(mode));
|
|
598
|
+
useEffect2(() => {
|
|
599
|
+
if (!contextId || !selection) {
|
|
600
|
+
setState(getDefaultResult(mode));
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
setState(selection.getSelected(contextId, mode));
|
|
604
|
+
return selection.subscribe(() => {
|
|
605
|
+
setState(selection.getSelected(contextId, mode));
|
|
606
|
+
});
|
|
607
|
+
}, [
|
|
608
|
+
selection,
|
|
609
|
+
contextId,
|
|
610
|
+
mode
|
|
611
|
+
]);
|
|
612
|
+
return state;
|
|
613
|
+
};
|
|
614
|
+
var useSelectionActions = (contextIds, mode = "multi") => {
|
|
615
|
+
const stableContextIds = useMemo2(() => contextIds, [
|
|
616
|
+
JSON.stringify(contextIds)
|
|
617
|
+
]);
|
|
618
|
+
const { selection } = useSelectionContext(SELECTION_NAME);
|
|
619
|
+
const singleSelect = useCallback((id) => {
|
|
620
|
+
if (!selection) {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
for (const contextId of stableContextIds) {
|
|
624
|
+
selection.updateSingle(contextId, id);
|
|
625
|
+
}
|
|
626
|
+
}, [
|
|
627
|
+
selection,
|
|
628
|
+
stableContextIds
|
|
629
|
+
]);
|
|
630
|
+
const multiSelect = useCallback((ids) => {
|
|
631
|
+
if (!selection) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
for (const contextId of stableContextIds) {
|
|
635
|
+
selection.updateMulti(contextId, ids);
|
|
636
|
+
}
|
|
637
|
+
}, [
|
|
638
|
+
selection,
|
|
639
|
+
stableContextIds
|
|
640
|
+
]);
|
|
641
|
+
const rangeSelect = useCallback((from, to) => {
|
|
642
|
+
if (!selection) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
for (const contextId of stableContextIds) {
|
|
646
|
+
selection.updateRange(contextId, from, to);
|
|
647
|
+
}
|
|
648
|
+
}, [
|
|
649
|
+
selection,
|
|
650
|
+
stableContextIds
|
|
651
|
+
]);
|
|
652
|
+
const toggle = useCallback((id) => {
|
|
653
|
+
if (!selection) {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
for (const contextId of stableContextIds) {
|
|
657
|
+
selection.toggleSelection(contextId, id);
|
|
658
|
+
}
|
|
659
|
+
}, [
|
|
660
|
+
selection,
|
|
661
|
+
stableContextIds
|
|
662
|
+
]);
|
|
663
|
+
const clear = useCallback(() => {
|
|
664
|
+
if (!selection) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
for (const contextId of stableContextIds) {
|
|
668
|
+
selection.clearSelection(contextId);
|
|
669
|
+
}
|
|
670
|
+
}, [
|
|
671
|
+
selection,
|
|
672
|
+
stableContextIds
|
|
673
|
+
]);
|
|
674
|
+
return {
|
|
675
|
+
singleSelect,
|
|
676
|
+
multiSelect,
|
|
677
|
+
rangeSelect,
|
|
678
|
+
toggle,
|
|
679
|
+
clear
|
|
680
|
+
};
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
export {
|
|
684
|
+
AttentionManager,
|
|
685
|
+
getAttendables,
|
|
686
|
+
ATTENTION_NAME,
|
|
687
|
+
ATTENDABLE_ATTRIBUTE,
|
|
688
|
+
ATTENTION_SOURCE_ATTRIBUTE,
|
|
689
|
+
useAttentionContext,
|
|
690
|
+
useAttention,
|
|
691
|
+
useAttended,
|
|
692
|
+
useAttentionAttributes,
|
|
693
|
+
useAttentionPath,
|
|
694
|
+
RootAttentionProvider,
|
|
695
|
+
AttentionProvider,
|
|
696
|
+
AttendableContainer,
|
|
697
|
+
SelectionSchema,
|
|
698
|
+
defaultSelection,
|
|
699
|
+
getSelectionSet,
|
|
700
|
+
SelectionManager,
|
|
701
|
+
SelectionProvider,
|
|
702
|
+
useSelectionManager,
|
|
703
|
+
useSelected,
|
|
704
|
+
useSelectionActions
|
|
705
|
+
};
|
|
706
|
+
//# sourceMappingURL=chunk-NFJ45DDP.mjs.map
|