@dxos/plugin-sheet 0.7.2 → 0.7.3-main.2dd075e
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/{SheetContainer-YAMIOFC6.mjs → SheetContainer-KCLT6PEO.mjs} +8 -6
- package/dist/lib/browser/SheetContainer-KCLT6PEO.mjs.map +7 -0
- package/dist/lib/browser/{chunk-QHQFM7LV.mjs → chunk-E5WQ7U7G.mjs} +77 -78
- package/dist/lib/browser/chunk-E5WQ7U7G.mjs.map +7 -0
- package/dist/lib/browser/{chunk-VMSX6Z4X.mjs → chunk-F3HE6D3J.mjs} +76 -26
- package/dist/lib/browser/chunk-F3HE6D3J.mjs.map +7 -0
- package/dist/lib/browser/{chunk-BVUN7SHF.mjs → chunk-JXFPOYNA.mjs} +1 -1
- package/dist/lib/browser/chunk-JXFPOYNA.mjs.map +7 -0
- package/dist/lib/browser/{compute-graph-GGWUX644.mjs → compute-graph-SNUS7HOH.mjs} +3 -3
- package/dist/lib/browser/index.mjs +84 -32
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/types.mjs +1 -1
- package/dist/lib/node/{SheetContainer-BSDHHYXE.cjs → SheetContainer-VVVRYTQG.cjs} +32 -31
- package/dist/lib/node/SheetContainer-VVVRYTQG.cjs.map +7 -0
- package/dist/lib/node/{chunk-J5ZFWMOD.cjs → chunk-45YW2DX2.cjs} +96 -101
- package/dist/lib/node/chunk-45YW2DX2.cjs.map +7 -0
- package/dist/lib/node/{chunk-O7XR4R7Y.cjs → chunk-KSEEI5VC.cjs} +87 -27
- package/dist/lib/node/chunk-KSEEI5VC.cjs.map +7 -0
- package/dist/lib/node/{chunk-AWKOWDMI.cjs → chunk-OWH2EUHZ.cjs} +4 -4
- package/dist/lib/node/chunk-OWH2EUHZ.cjs.map +7 -0
- package/dist/lib/node/{compute-graph-KGWA2QLE.cjs → compute-graph-WILPHO4A.cjs} +20 -20
- package/dist/lib/node/{compute-graph-KGWA2QLE.cjs.map → compute-graph-WILPHO4A.cjs.map} +2 -2
- package/dist/lib/node/index.cjs +101 -47
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types.cjs +6 -6
- package/dist/lib/node/types.cjs.map +1 -1
- package/dist/lib/node-esm/{SheetContainer-T47T2NYJ.mjs → SheetContainer-LSBE6Q4X.mjs} +8 -6
- package/dist/lib/node-esm/SheetContainer-LSBE6Q4X.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-CR4K75EL.mjs → chunk-6JF2AHKO.mjs} +76 -26
- package/dist/lib/node-esm/chunk-6JF2AHKO.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-UIBWRHW7.mjs → chunk-BVS2IQRO.mjs} +1 -1
- package/dist/lib/node-esm/chunk-BVS2IQRO.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-6GSTEN7N.mjs → chunk-NYDNXI7L.mjs} +77 -78
- package/dist/lib/node-esm/chunk-NYDNXI7L.mjs.map +7 -0
- package/dist/lib/node-esm/{compute-graph-2SCZT7N5.mjs → compute-graph-S6CVN7RS.mjs} +3 -3
- package/dist/lib/node-esm/index.mjs +84 -32
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/types.mjs +1 -1
- package/dist/types/src/SheetPlugin.d.ts.map +1 -1
- package/dist/types/src/components/GridSheet/GridSheet.d.ts.map +1 -1
- package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts +1 -0
- package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts.map +1 -1
- package/dist/types/src/components/GridSheet/util.d.ts +0 -2
- package/dist/types/src/components/GridSheet/util.d.ts.map +1 -1
- package/dist/types/src/components/SheetContainer/SheetContainer.stories.d.ts +2 -0
- package/dist/types/src/components/SheetContainer/SheetContainer.stories.d.ts.map +1 -1
- package/dist/types/src/components/SheetObjectSettings.d.ts +7 -0
- package/dist/types/src/components/SheetObjectSettings.d.ts.map +1 -0
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/defs/sheet-range-types.d.ts.map +1 -1
- package/dist/types/src/defs/types.d.ts +2 -4
- package/dist/types/src/defs/types.d.ts.map +1 -1
- package/dist/types/src/defs/util.d.ts +8 -0
- package/dist/types/src/defs/util.d.ts.map +1 -1
- package/dist/types/src/extensions/editor/extension.d.ts +2 -1
- package/dist/types/src/extensions/editor/extension.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/integrations/thread-ranges.d.ts.map +1 -1
- package/dist/types/src/model/sheet-model.d.ts +0 -4
- package/dist/types/src/model/sheet-model.d.ts.map +1 -1
- package/dist/types/src/model/testing.d.ts +6 -0
- package/dist/types/src/model/testing.d.ts.map +1 -0
- package/dist/types/src/testing/data.d.ts +3 -0
- package/dist/types/src/testing/data.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/playwright/playwright.config.d.ts +3 -0
- package/dist/types/src/testing/playwright/playwright.config.d.ts.map +1 -0
- package/dist/types/src/testing/playwright/sheet-manager.d.ts +24 -0
- package/dist/types/src/testing/playwright/sheet-manager.d.ts.map +1 -0
- package/dist/types/src/testing/playwright/sheet.spec.d.ts +2 -0
- package/dist/types/src/testing/playwright/sheet.spec.d.ts.map +1 -0
- package/dist/types/src/testing/testing.d.ts +1 -2
- package/dist/types/src/testing/testing.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +61 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +2 -3
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +40 -37
- package/src/SheetPlugin.tsx +9 -21
- package/src/components/FunctionEditor/FunctionEditor.tsx +2 -2
- package/src/components/GridSheet/GridSheet.stories.tsx +2 -0
- package/src/components/GridSheet/GridSheet.tsx +18 -8
- package/src/components/GridSheet/util.ts +1 -9
- package/src/components/SheetContainer/SheetContainer.stories.tsx +44 -2
- package/src/components/SheetObjectSettings.tsx +38 -0
- package/src/components/Toolbar/Toolbar.tsx +7 -1
- package/src/components/index.ts +1 -0
- package/src/compute-graph/compute-graph.stories.tsx +1 -1
- package/src/compute-graph/compute-graph.test.ts +1 -1
- package/src/defs/sheet-range-types.ts +3 -0
- package/src/defs/types.ts +3 -1
- package/src/defs/util.ts +32 -1
- package/src/extensions/editor/extension.ts +3 -3
- package/src/index.ts +3 -0
- package/src/integrations/thread-ranges.ts +2 -0
- package/src/model/sheet-model.test.ts +35 -1
- package/src/model/sheet-model.ts +5 -17
- package/src/model/testing.ts +35 -0
- package/src/sanity.test.ts +1 -1
- package/src/testing/data.ts +33 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/playwright/playwright.config.ts +18 -0
- package/src/testing/playwright/sheet-manager.ts +91 -0
- package/src/testing/playwright/sheet.spec.ts +78 -0
- package/src/testing/testing.tsx +1 -31
- package/src/translations.ts +4 -0
- package/src/types.ts +1 -3
- package/dist/lib/browser/SheetContainer-YAMIOFC6.mjs.map +0 -7
- package/dist/lib/browser/chunk-BVUN7SHF.mjs.map +0 -7
- package/dist/lib/browser/chunk-QHQFM7LV.mjs.map +0 -7
- package/dist/lib/browser/chunk-VMSX6Z4X.mjs.map +0 -7
- package/dist/lib/node/SheetContainer-BSDHHYXE.cjs.map +0 -7
- package/dist/lib/node/chunk-AWKOWDMI.cjs.map +0 -7
- package/dist/lib/node/chunk-J5ZFWMOD.cjs.map +0 -7
- package/dist/lib/node/chunk-O7XR4R7Y.cjs.map +0 -7
- package/dist/lib/node-esm/SheetContainer-T47T2NYJ.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-6GSTEN7N.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-CR4K75EL.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-UIBWRHW7.mjs.map +0 -7
- /package/dist/lib/browser/{compute-graph-GGWUX644.mjs.map → compute-graph-SNUS7HOH.mjs.map} +0 -0
- /package/dist/lib/node-esm/{compute-graph-2SCZT7N5.mjs.map → compute-graph-S6CVN7RS.mjs.map} +0 -0
package/src/defs/types.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { invariant } from '@dxos/invariant';
|
|
6
|
+
import { type DxGridPlanePosition } from '@dxos/react-ui-grid';
|
|
6
7
|
|
|
7
8
|
export const DEFAULT_ROWS = 50;
|
|
8
9
|
export const DEFAULT_COLUMNS = 26;
|
|
@@ -10,7 +11,7 @@ export const DEFAULT_COLUMNS = 26;
|
|
|
10
11
|
export const MAX_ROWS = 500;
|
|
11
12
|
export const MAX_COLUMNS = 26 * 2;
|
|
12
13
|
|
|
13
|
-
export type CellAddress =
|
|
14
|
+
export type CellAddress = DxGridPlanePosition;
|
|
14
15
|
|
|
15
16
|
export type CellRange = { from: CellAddress; to?: CellAddress };
|
|
16
17
|
export type CompleteCellRange = { from: CellAddress; to: CellAddress };
|
|
@@ -76,6 +77,7 @@ export const inRange = (range: CellRange | undefined, cell: CellAddress): boolea
|
|
|
76
77
|
|
|
77
78
|
const { col: c1, row: r1 } = from;
|
|
78
79
|
const { col: c2, row: r2 } = to;
|
|
80
|
+
|
|
79
81
|
const cMin = Math.min(c1, c2);
|
|
80
82
|
const cMax = Math.max(c1, c2);
|
|
81
83
|
const rMin = Math.min(r1, r2);
|
package/src/defs/util.ts
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { randomBytes } from '@dxos/crypto';
|
|
6
|
-
import {
|
|
6
|
+
import { invariant } from '@dxos/invariant';
|
|
7
|
+
import { create } from '@dxos/live-object';
|
|
7
8
|
|
|
8
9
|
import {
|
|
9
10
|
addressFromA1Notation,
|
|
11
|
+
addressToA1Notation,
|
|
12
|
+
isFormula,
|
|
10
13
|
type CellAddress,
|
|
11
14
|
type CellRange,
|
|
12
15
|
type CompleteCellRange,
|
|
@@ -28,6 +31,8 @@ export class RangeException extends ApiError {
|
|
|
28
31
|
}
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
// TODO(burdon): Factor out to types lib.
|
|
35
|
+
|
|
31
36
|
/**
|
|
32
37
|
* With a string length of 8, the chance of a collision is 0.02% for a sheet with 10,000 strings.
|
|
33
38
|
*/
|
|
@@ -79,6 +84,10 @@ export const createSheet = ({ name, cells, ...size }: CreateSheetOptions = {}):
|
|
|
79
84
|
if (cells) {
|
|
80
85
|
Object.entries(cells).forEach(([key, { value }]) => {
|
|
81
86
|
const idx = addressToIndex(sheet, addressFromA1Notation(key));
|
|
87
|
+
if (isFormula(value)) {
|
|
88
|
+
value = mapFormulaRefsToIndices(sheet, value);
|
|
89
|
+
}
|
|
90
|
+
|
|
82
91
|
sheet.cells[idx] = { value };
|
|
83
92
|
});
|
|
84
93
|
}
|
|
@@ -134,3 +143,25 @@ export const compareIndexPositions = (sheet: SheetType, indexA: string, indexB:
|
|
|
134
143
|
return columnA - columnB;
|
|
135
144
|
}
|
|
136
145
|
};
|
|
146
|
+
|
|
147
|
+
// TODO(burdon): Tests.
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Map from A1 notation to indices.
|
|
151
|
+
*/
|
|
152
|
+
export const mapFormulaRefsToIndices = (sheet: SheetType, formula: string): string => {
|
|
153
|
+
invariant(isFormula(formula));
|
|
154
|
+
return formula.replace(/([a-zA-Z]+)([0-9]+)/g, (match) => {
|
|
155
|
+
return addressToIndex(sheet, addressFromA1Notation(match));
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Map from indices to A1 notation.
|
|
161
|
+
*/
|
|
162
|
+
export const mapFormulaIndicesToRefs = (sheet: SheetType, formula: string): string => {
|
|
163
|
+
invariant(isFormula(formula));
|
|
164
|
+
return formula.replace(/([a-zA-Z0-9]+)@([a-zA-Z0-9]+)/g, (idx) => {
|
|
165
|
+
return addressToA1Notation(addressFromIndex(sheet, idx));
|
|
166
|
+
});
|
|
167
|
+
};
|
|
@@ -64,6 +64,7 @@ const highlightStyles = HighlightStyle.define([
|
|
|
64
64
|
const languageFacet = singleValueFacet<Language>();
|
|
65
65
|
|
|
66
66
|
export type SheetExtensionOptions = {
|
|
67
|
+
debug?: boolean;
|
|
67
68
|
functions?: FunctionDefinition[];
|
|
68
69
|
};
|
|
69
70
|
|
|
@@ -74,7 +75,7 @@ export type SheetExtensionOptions = {
|
|
|
74
75
|
* https://github.com/codemirror/lang-example
|
|
75
76
|
* https://hyperformula.handsontable.com/guide/key-concepts.html#grammar
|
|
76
77
|
*/
|
|
77
|
-
export const sheetExtension = ({ functions = [] }: SheetExtensionOptions): Extension => {
|
|
78
|
+
export const sheetExtension = ({ debug, functions = [] }: SheetExtensionOptions): Extension => {
|
|
78
79
|
const { extension, language } = spreadsheet({ idiom: 'en-US', decimalSeparator: '.' });
|
|
79
80
|
|
|
80
81
|
const createCompletion = (name: string) => {
|
|
@@ -162,8 +163,7 @@ export const sheetExtension = ({ functions = [] }: SheetExtensionOptions): Exten
|
|
|
162
163
|
aboveCursor: false,
|
|
163
164
|
defaultKeymap: true,
|
|
164
165
|
activateOnTyping: true,
|
|
165
|
-
|
|
166
|
-
closeOnBlur: false,
|
|
166
|
+
closeOnBlur: !debug,
|
|
167
167
|
icons: false,
|
|
168
168
|
tooltipClass: () =>
|
|
169
169
|
mx(
|
package/src/index.ts
CHANGED
|
@@ -43,6 +43,8 @@ export const useUpdateFocusedCellOnThreadSelection = (grid: DxGridElement | null
|
|
|
43
43
|
// TODO(Zan): Everywhere we refer to the cursor in a thread context should change to `anchor`.
|
|
44
44
|
const range = parseThreadAnchorAsCellRange(data.cursor);
|
|
45
45
|
range && grid?.setFocus({ ...range.to, plane: 'grid' }, true);
|
|
46
|
+
|
|
47
|
+
return { data: true };
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
},
|
|
@@ -5,11 +5,19 @@
|
|
|
5
5
|
import { afterEach, beforeEach, describe, expect, onTestFinished, test } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { Trigger } from '@dxos/async';
|
|
8
|
+
import { log } from '@dxos/log';
|
|
8
9
|
import { FunctionType } from '@dxos/plugin-script/types';
|
|
9
10
|
|
|
10
11
|
import { SheetModel } from './sheet-model';
|
|
12
|
+
import { createTestGrid } from './testing';
|
|
11
13
|
import { TestBuilder, testFunctionPlugins } from '../compute-graph/testing';
|
|
12
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
addressFromA1Notation,
|
|
16
|
+
createSheet,
|
|
17
|
+
isFormula,
|
|
18
|
+
mapFormulaIndicesToRefs,
|
|
19
|
+
mapFormulaRefsToIndices,
|
|
20
|
+
} from '../defs';
|
|
13
21
|
import { type CellScalarValue } from '../types';
|
|
14
22
|
|
|
15
23
|
describe('SheetModel', () => {
|
|
@@ -56,4 +64,30 @@ describe('SheetModel', () => {
|
|
|
56
64
|
expect(v2).to.eq(100);
|
|
57
65
|
expect(graph.context.info.invocations.TEST).to.eq(1);
|
|
58
66
|
});
|
|
67
|
+
|
|
68
|
+
test('formula', async () => {
|
|
69
|
+
const space = await testBuilder.client.spaces.create();
|
|
70
|
+
const graph = testBuilder.registry.createGraph(space);
|
|
71
|
+
await graph.open();
|
|
72
|
+
|
|
73
|
+
const cols = 4;
|
|
74
|
+
const rows = 10;
|
|
75
|
+
const sheet = createTestGrid({ rows, cols });
|
|
76
|
+
const model = new SheetModel(graph, sheet);
|
|
77
|
+
await model.open();
|
|
78
|
+
|
|
79
|
+
for (let col = 1; col <= cols; col++) {
|
|
80
|
+
const cell = { col, row: rows };
|
|
81
|
+
const text = model.getCellText(cell);
|
|
82
|
+
const raw = model.getCellValue(cell);
|
|
83
|
+
const value = model.getValue(cell);
|
|
84
|
+
log('values', { text, raw, value });
|
|
85
|
+
|
|
86
|
+
expect(isFormula(text)).to.be.true;
|
|
87
|
+
expect(isFormula(raw)).to.be.true;
|
|
88
|
+
expect(typeof value).to.eq('number');
|
|
89
|
+
expect(mapFormulaRefsToIndices(sheet, text as string)).to.eq(raw);
|
|
90
|
+
expect(mapFormulaIndicesToRefs(sheet, raw as string)).to.eq(text);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
59
93
|
});
|
package/src/model/sheet-model.ts
CHANGED
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
ReadonlyException,
|
|
29
29
|
MAX_COLUMNS,
|
|
30
30
|
MAX_ROWS,
|
|
31
|
+
mapFormulaIndicesToRefs,
|
|
32
|
+
mapFormulaRefsToIndices,
|
|
31
33
|
} from '../defs';
|
|
32
34
|
import { type CellScalarValue, type CellValue, type SheetType, type RestoreAxis } from '../types';
|
|
33
35
|
|
|
@@ -142,7 +144,7 @@ export class SheetModel extends Resource {
|
|
|
142
144
|
invariant(this._node);
|
|
143
145
|
const { col, row } = addressFromIndex(this._sheet, key);
|
|
144
146
|
if (isFormula(value)) {
|
|
145
|
-
const binding = this._graph.mapFunctionBindingFromId(this.
|
|
147
|
+
const binding = this._graph.mapFunctionBindingFromId(mapFormulaIndicesToRefs(this._sheet, value));
|
|
146
148
|
if (binding) {
|
|
147
149
|
value = this._graph.mapFormulaToNative(binding);
|
|
148
150
|
} else {
|
|
@@ -315,7 +317,7 @@ export class SheetModel extends Resource {
|
|
|
315
317
|
}
|
|
316
318
|
|
|
317
319
|
if (isFormula(value)) {
|
|
318
|
-
return this._graph.mapFunctionBindingFromId(this.
|
|
320
|
+
return this._graph.mapFunctionBindingFromId(mapFormulaIndicesToRefs(this._sheet, value));
|
|
319
321
|
} else {
|
|
320
322
|
return String(value);
|
|
321
323
|
}
|
|
@@ -391,7 +393,7 @@ export class SheetModel extends Resource {
|
|
|
391
393
|
delete this._sheet.cells[idx];
|
|
392
394
|
} else {
|
|
393
395
|
if (isFormula(value)) {
|
|
394
|
-
value = this._graph.mapFunctionBindingToId(this.
|
|
396
|
+
value = this._graph.mapFunctionBindingToId(mapFormulaRefsToIndices(this._sheet, value));
|
|
395
397
|
}
|
|
396
398
|
|
|
397
399
|
this._sheet.cells[idx] = { value };
|
|
@@ -440,20 +442,6 @@ export class SheetModel extends Resource {
|
|
|
440
442
|
throw new Error('Not implemented');
|
|
441
443
|
}
|
|
442
444
|
|
|
443
|
-
//
|
|
444
|
-
// Indices.
|
|
445
|
-
//
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* Map from A1 notation to indices.
|
|
449
|
-
*/
|
|
450
|
-
mapFormulaRefsToIndices(formula: string): string {
|
|
451
|
-
invariant(isFormula(formula));
|
|
452
|
-
return formula.replace(/([a-zA-Z]+)([0-9]+)/g, (match) => {
|
|
453
|
-
return addressToIndex(this._sheet, addressFromA1Notation(match));
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
|
|
457
445
|
/**
|
|
458
446
|
* Map from indices to A1 notation.
|
|
459
447
|
*/
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { addressToA1Notation, createSheet } from '../defs';
|
|
6
|
+
import type { CellValue, SheetType } from '../types';
|
|
7
|
+
|
|
8
|
+
// TODO(burdon): Create testing endpoint.
|
|
9
|
+
// TODO(burdon): Move to react-ui-sheet.
|
|
10
|
+
export const createTestGrid = ({ cols = 4, rows = 10 }: { cols: number; rows: number }): SheetType => {
|
|
11
|
+
const year = new Date().getFullYear();
|
|
12
|
+
|
|
13
|
+
const cells: Record<string, CellValue> = {};
|
|
14
|
+
for (let col = 1; col <= cols; col++) {
|
|
15
|
+
for (let row = 1; row <= 10; row++) {
|
|
16
|
+
const cell = addressToA1Notation({ col, row });
|
|
17
|
+
if (row === 1) {
|
|
18
|
+
cells[cell] = { value: `${year} Q${col}` };
|
|
19
|
+
} else if (row === rows) {
|
|
20
|
+
const from = addressToA1Notation({ col, row: 2 });
|
|
21
|
+
const to = addressToA1Notation({ col, row: rows - 1 });
|
|
22
|
+
cells[cell] = { value: `=SUM(${from}:${to})` };
|
|
23
|
+
} else if (row > 2 && row < rows - 1) {
|
|
24
|
+
cells[cell] = { value: Math.floor(Math.random() * 10_000) };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const sheet = createSheet({
|
|
30
|
+
name: 'Test',
|
|
31
|
+
cells,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return sheet;
|
|
35
|
+
};
|
package/src/sanity.test.ts
CHANGED
|
@@ -34,7 +34,7 @@ describe('test', () => {
|
|
|
34
34
|
// - ERROR "process.nextTick is not a function"
|
|
35
35
|
// - ERROR "Identifier 'Buffer' has already been declared" if { nodeExternal: true }
|
|
36
36
|
const space = await client.spaces.create();
|
|
37
|
-
const fn = space.db.add(create(FunctionType, { name: 'test', version: 1, binding: 'HELLO' }));
|
|
37
|
+
const fn = space.db.add(create(FunctionType, { name: 'test', version: '0.0.1', binding: 'HELLO' }));
|
|
38
38
|
expect(fn).to.exist;
|
|
39
39
|
});
|
|
40
40
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type { CellValue } from '../types';
|
|
6
|
+
|
|
7
|
+
export const createTestCells = (testSheetName = 'test'): Record<string, CellValue> => ({
|
|
8
|
+
B1: { value: 'Qty2' },
|
|
9
|
+
B3: { value: 1 },
|
|
10
|
+
B4: { value: 2 },
|
|
11
|
+
B5: { value: 3 },
|
|
12
|
+
B7: { value: 'Total' },
|
|
13
|
+
|
|
14
|
+
C1: { value: 'Price' },
|
|
15
|
+
C3: { value: 2_000 },
|
|
16
|
+
C4: { value: 2_500 },
|
|
17
|
+
C5: { value: 3_000 },
|
|
18
|
+
C7: { value: '=SUMPRODUCT(B2:B6, C2:C6)' },
|
|
19
|
+
// C8: { value: '=C7*CRYPTO(D7)' },
|
|
20
|
+
C8: { value: '=C7*TEST()' },
|
|
21
|
+
|
|
22
|
+
D7: { value: 'USD' },
|
|
23
|
+
D8: { value: 'BTC' },
|
|
24
|
+
|
|
25
|
+
E3: { value: '=TODAY()' },
|
|
26
|
+
E4: { value: '=NOW()' },
|
|
27
|
+
|
|
28
|
+
F1: { value: `=${testSheetName}!A1` }, // Ref test sheet.
|
|
29
|
+
F3: { value: true },
|
|
30
|
+
F4: { value: false },
|
|
31
|
+
F5: { value: '8%' },
|
|
32
|
+
F6: { value: '$10000' },
|
|
33
|
+
});
|
package/src/testing/index.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { nxE2EPreset } from '@nx/playwright/preset';
|
|
6
|
+
import { defineConfig } from '@playwright/test';
|
|
7
|
+
|
|
8
|
+
import { e2ePreset } from '@dxos/test-utils/playwright';
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
...nxE2EPreset(__filename, { testDir: __dirname }),
|
|
12
|
+
...e2ePreset(__dirname),
|
|
13
|
+
webServer: {
|
|
14
|
+
command: 'pnpm -w nx storybook stories',
|
|
15
|
+
port: 9009,
|
|
16
|
+
reuseExistingServer: !process.env.CI,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Locator, type Page } from '@playwright/test';
|
|
6
|
+
|
|
7
|
+
import { DxGridManager } from '@dxos/lit-grid/testing';
|
|
8
|
+
import { type DxGridPosition, type DxGridAxis } from '@dxos/react-ui-grid';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Test helper for managing dx-grid interactions and assertions in Playwright tests.
|
|
12
|
+
* Provides utilities for cell selection, grid navigation, virtualization checks and event handling.
|
|
13
|
+
*/
|
|
14
|
+
export class SheetManager {
|
|
15
|
+
constructor(page: Page, grid?: Locator) {
|
|
16
|
+
this.grid = new DxGridManager(page, grid);
|
|
17
|
+
this.page = page;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
grid: DxGridManager;
|
|
21
|
+
page: Page;
|
|
22
|
+
|
|
23
|
+
async ready() {
|
|
24
|
+
await this.grid.ready();
|
|
25
|
+
return this.cellByText('Ready').waitFor({ state: 'visible' });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async fill(text: string) {
|
|
29
|
+
// TODO(thure): Do these timeouts help with test flakiness?
|
|
30
|
+
await this.page.waitForTimeout(200);
|
|
31
|
+
await this.cellEditor().fill(text);
|
|
32
|
+
await this.page.waitForTimeout(200);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async press(key: string) {
|
|
36
|
+
// TODO(thure): Does these timeouts help with test flakiness?
|
|
37
|
+
await this.page.waitForTimeout(200);
|
|
38
|
+
await this.page.keyboard.press(key);
|
|
39
|
+
await this.page.waitForTimeout(200);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async commit(key: string) {
|
|
43
|
+
// TODO(thure): Why do we need to wait? Enter is ignored otherwise…
|
|
44
|
+
await this.page.waitForTimeout(500);
|
|
45
|
+
await this.press(key);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
cellByText(text: string) {
|
|
49
|
+
return this.grid.grid.getByText(text);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async setFocusedCellValue(text: string, commitKey: string) {
|
|
53
|
+
const mode = await this.grid.mode();
|
|
54
|
+
if (mode === 'browse') {
|
|
55
|
+
await this.commit('Enter');
|
|
56
|
+
}
|
|
57
|
+
await this.fill(text);
|
|
58
|
+
await this.commit(commitKey);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async selectRange(start: DxGridPosition, end: DxGridPosition) {
|
|
62
|
+
const startCell = this.grid.cell(start.col, start.row, start.plane);
|
|
63
|
+
const endCell = this.grid.cell(end.col, end.row, end.plane);
|
|
64
|
+
const startBox = await startCell.boundingBox();
|
|
65
|
+
const endBox = await endCell.boundingBox();
|
|
66
|
+
await startCell.dragTo(endCell, {
|
|
67
|
+
sourcePosition: { x: startBox!.width / 2, y: startBox!.height / 2 },
|
|
68
|
+
targetPosition: { x: endBox!.width / 2, y: endBox!.height / 2 },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async deleteAxis(axis: DxGridAxis, position: number) {
|
|
73
|
+
const col = axis === 'row' ? 0 : position;
|
|
74
|
+
const row = axis === 'row' ? position : 0;
|
|
75
|
+
const plane = axis === 'row' ? 'frozenColsStart' : 'frozenRowsStart';
|
|
76
|
+
await this.grid.cell(col, row, plane).click({ button: 'right' });
|
|
77
|
+
await this.page.getByTestId(`grid.${axis}.drop`).click();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
toolbarAction(key: string, value: string) {
|
|
81
|
+
return this.page.getByTestId(`grid.toolbar.${key}.${value}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
cellEditor() {
|
|
85
|
+
return this.page.getByTestId('grid.cell-editor').getByRole('textbox');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
rangeInList(a1Coords: string) {
|
|
89
|
+
return this.page.getByTestId('grid.range-list').getByText(a1Coords);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2021 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { expect, test, type Page } from '@playwright/test';
|
|
6
|
+
|
|
7
|
+
import { faker } from '@dxos/random';
|
|
8
|
+
import { setupPage, storybookUrl } from '@dxos/test-utils/playwright';
|
|
9
|
+
|
|
10
|
+
import { SheetManager } from './sheet-manager';
|
|
11
|
+
|
|
12
|
+
test.describe('plugin-sheet', () => {
|
|
13
|
+
let page: Page;
|
|
14
|
+
let sheet: SheetManager;
|
|
15
|
+
|
|
16
|
+
test.beforeEach(async ({ browser }) => {
|
|
17
|
+
const setup = await setupPage(browser, {
|
|
18
|
+
url: storybookUrl('plugins-plugin-sheet-sheetcontainer--spec'),
|
|
19
|
+
});
|
|
20
|
+
page = setup.page;
|
|
21
|
+
sheet = new SheetManager(page);
|
|
22
|
+
await sheet.ready();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test.afterEach(async () => {
|
|
26
|
+
await page.close();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('basic interactions', async () => {
|
|
30
|
+
// Cell editor should initially be hidden
|
|
31
|
+
await expect(sheet.cellEditor()).not.toBeVisible();
|
|
32
|
+
// Click on cell to focus it
|
|
33
|
+
await sheet.grid.cell(0, 0, 'grid').click();
|
|
34
|
+
// Cell editor should still be hidden
|
|
35
|
+
await expect(sheet.cellEditor()).not.toBeVisible();
|
|
36
|
+
// Click again to edit it
|
|
37
|
+
await sheet.grid.cell(0, 0, 'grid').click();
|
|
38
|
+
// Confirm editor displays
|
|
39
|
+
await expect(sheet.cellEditor()).toBeVisible();
|
|
40
|
+
// Type in a value and press enter
|
|
41
|
+
const testString = faker.string.uuid();
|
|
42
|
+
await sheet.setFocusedCellValue(testString, 'Enter');
|
|
43
|
+
// Expect that value to now show in the grid
|
|
44
|
+
await expect(sheet.cellByText(testString)).toBeVisible();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('functions, row deletion, indexed positions', async () => {
|
|
48
|
+
const firstNumber = 123;
|
|
49
|
+
const secondNumber = 789;
|
|
50
|
+
const thirdNumber = 567;
|
|
51
|
+
// Input numbers
|
|
52
|
+
await sheet.grid.cell(0, 2, 'grid').click();
|
|
53
|
+
await sheet.setFocusedCellValue(`${firstNumber}`, 'Enter');
|
|
54
|
+
await sheet.setFocusedCellValue(`${secondNumber}`, 'Enter');
|
|
55
|
+
await sheet.setFocusedCellValue(`${thirdNumber}`, 'Enter');
|
|
56
|
+
|
|
57
|
+
// Test range input
|
|
58
|
+
await sheet.press('Enter');
|
|
59
|
+
await sheet.fill('=SUM(');
|
|
60
|
+
await sheet.selectRange({ col: 0, row: 2, plane: 'grid' }, { col: 0, row: 4, plane: 'grid' });
|
|
61
|
+
await sheet.press(')');
|
|
62
|
+
await sheet.commit('Enter');
|
|
63
|
+
// Check sum
|
|
64
|
+
await expect(sheet.grid.cell(0, 5, 'grid')).toHaveText(`${firstNumber + secondNumber + thirdNumber}`);
|
|
65
|
+
// Delete row of second number
|
|
66
|
+
await sheet.deleteAxis('row', 3);
|
|
67
|
+
// Check sum again, it should be one cell up and reflect the updated range.
|
|
68
|
+
await expect(sheet.grid.cell(0, 4, 'grid')).toHaveText(`${firstNumber + thirdNumber}`);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('ranges', async () => {
|
|
72
|
+
await sheet.selectRange({ col: 1, row: 0, plane: 'grid' }, { col: 1, row: 1, plane: 'grid' });
|
|
73
|
+
await sheet.toolbarAction('alignment', 'center').click();
|
|
74
|
+
await expect(await sheet.grid.cell(1, 0, 'grid')).toHaveAttribute('class', /text-center/);
|
|
75
|
+
await expect(await sheet.grid.cell(1, 1, 'grid')).toHaveAttribute('class', /text-center/);
|
|
76
|
+
await expect(sheet.rangeInList('B1:B2')).toBeVisible();
|
|
77
|
+
});
|
|
78
|
+
});
|
package/src/testing/testing.tsx
CHANGED
|
@@ -11,37 +11,7 @@ import { useAsyncState } from '@dxos/react-hooks';
|
|
|
11
11
|
import { ComputeGraphContextProvider } from '../components';
|
|
12
12
|
import { type ComputeGraph, type ComputeGraphOptions, ComputeGraphRegistry } from '../compute-graph';
|
|
13
13
|
import { createSheet } from '../defs';
|
|
14
|
-
import { type
|
|
15
|
-
|
|
16
|
-
const testSheetName = 'test';
|
|
17
|
-
|
|
18
|
-
export const createTestCells = (): Record<string, CellValue> => ({
|
|
19
|
-
B1: { value: 'Qty2' },
|
|
20
|
-
B3: { value: 1 },
|
|
21
|
-
B4: { value: 2 },
|
|
22
|
-
B5: { value: 3 },
|
|
23
|
-
B7: { value: 'Total' },
|
|
24
|
-
|
|
25
|
-
C1: { value: 'Price' },
|
|
26
|
-
C3: { value: 2_000 },
|
|
27
|
-
C4: { value: 2_500 },
|
|
28
|
-
C5: { value: 3_000 },
|
|
29
|
-
C7: { value: '=SUMPRODUCT(B2:B6, C2:C6)' },
|
|
30
|
-
// C8: { value: '=C7*CRYPTO(D7)' },
|
|
31
|
-
C8: { value: '=C7*TEST()' },
|
|
32
|
-
|
|
33
|
-
D7: { value: 'USD' },
|
|
34
|
-
D8: { value: 'BTC' },
|
|
35
|
-
|
|
36
|
-
E3: { value: '=TODAY()' },
|
|
37
|
-
E4: { value: '=NOW()' },
|
|
38
|
-
|
|
39
|
-
F1: { value: `=${testSheetName}!A1` }, // Ref test sheet.
|
|
40
|
-
F3: { value: true },
|
|
41
|
-
F4: { value: false },
|
|
42
|
-
F5: { value: '8%' },
|
|
43
|
-
F6: { value: '$10000' },
|
|
44
|
-
});
|
|
14
|
+
import { type CreateSheetOptions } from '../types';
|
|
45
15
|
|
|
46
16
|
export const useTestSheet = (space?: Space, graph?: ComputeGraph, options?: CreateSheetOptions) => {
|
|
47
17
|
const [sheet] = useAsyncState(async () => {
|
package/src/translations.ts
CHANGED
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { SHEET_PLUGIN } from './meta';
|
|
6
|
+
import { SheetType } from './types';
|
|
6
7
|
|
|
7
8
|
export default [
|
|
8
9
|
{
|
|
9
10
|
'en-US': {
|
|
11
|
+
[SheetType.typename]: {
|
|
12
|
+
'typename label': 'Sheet',
|
|
13
|
+
},
|
|
10
14
|
[SHEET_PLUGIN]: {
|
|
11
15
|
'plugin name': 'Sheets',
|
|
12
16
|
'sheet title placeholder': 'New sheet',
|
package/src/types.ts
CHANGED
|
@@ -11,9 +11,8 @@ import type {
|
|
|
11
11
|
IntentData,
|
|
12
12
|
} from '@dxos/app-framework';
|
|
13
13
|
import { ref, S, TypedObject } from '@dxos/echo-schema';
|
|
14
|
-
import { type SchemaProvides } from '@dxos/plugin-client';
|
|
15
14
|
import { type MarkdownExtensionProvides } from '@dxos/plugin-markdown';
|
|
16
|
-
import { type
|
|
15
|
+
import { type SchemaProvides } from '@dxos/plugin-space';
|
|
17
16
|
import { ThreadType } from '@dxos/plugin-space/types';
|
|
18
17
|
import { type StackProvides } from '@dxos/plugin-stack';
|
|
19
18
|
import { type DxGridAxis } from '@dxos/react-ui-grid';
|
|
@@ -61,7 +60,6 @@ export type SheetPluginProvides = SurfaceProvides &
|
|
|
61
60
|
MetadataRecordsProvides &
|
|
62
61
|
TranslationsProvides &
|
|
63
62
|
SchemaProvides &
|
|
64
|
-
SpaceInitProvides &
|
|
65
63
|
StackProvides &
|
|
66
64
|
ThreadProvides<SheetType>;
|
|
67
65
|
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/components/SheetContainer/SheetContainer.tsx", "../../../src/components/FunctionEditor/FunctionEditor.tsx", "../../../src/components/Toolbar/Toolbar.tsx", "../../../src/components/SheetContainer/index.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport React from 'react';\n\nimport { type Space } from '@dxos/react-client/echo';\nimport { StackItem } from '@dxos/react-ui-stack';\n\nimport { type SheetType } from '../../types';\nimport { useComputeGraph } from '../ComputeGraph';\nimport { FunctionEditor } from '../FunctionEditor';\nimport { GridSheet } from '../GridSheet';\nimport { SheetProvider } from '../SheetContext';\nimport { Toolbar } from '../Toolbar';\n\nexport const SheetContainer = ({\n space,\n sheet,\n role,\n ignoreAttention,\n}: {\n space: Space;\n sheet: SheetType;\n role?: string;\n ignoreAttention?: boolean;\n}) => {\n const graph = useComputeGraph(space);\n\n return graph ? (\n <SheetProvider sheet={sheet} graph={graph} ignoreAttention={ignoreAttention}>\n <StackItem.Content toolbar statusbar {...(role === 'section' && { classNames: 'aspect-video' })}>\n <Toolbar.Root role={role}>\n <Toolbar.Styles />\n <Toolbar.Alignment />\n <Toolbar.Separator />\n <Toolbar.Actions />\n </Toolbar.Root>\n <GridSheet />\n <FunctionEditor />\n </StackItem.Content>\n </SheetProvider>\n ) : null;\n};\n", "//\n// Copyright 2024 DXOS.org\n//\n\nimport React from 'react';\n\nimport { Icon } from '@dxos/react-ui';\n\nimport { addressToA1Notation, isFormula, rangeToA1Notation } from '../../defs';\nimport { useSheetContext } from '../SheetContext';\n\nexport const FunctionEditor = () => {\n const { model, cursor, range } = useSheetContext();\n\n let value;\n let formula = false;\n if (cursor) {\n value = model.getCellValue(cursor);\n if (isFormula(value)) {\n value = model.graph.mapFunctionBindingFromId(model.mapFormulaIndicesToRefs(value));\n formula = true;\n } else if (value != null) {\n value = String(value);\n }\n }\n\n return (\n <div className='flex shrink-0 justify-between items-center px-4 py-1 text-sm attention-surface border-bs !border-separator'>\n <div className='flex gap-4 items-center'>\n <div className='flex w-16 items-center font-mono'>\n {(range && rangeToA1Notation(range)) || (cursor && addressToA1Notation(cursor))}\n </div>\n <div className='flex gap-2 items-center'>\n <Icon icon='ph--function--regular' classNames={['text-greenText', formula ? 'visible' : 'invisible']} />\n <span className='font-mono'>{value}</span>\n </div>\n </div>\n </div>\n );\n};\n", "//\n// Copyright 2024 DXOS.org\n//\n\nimport { createContext } from '@radix-ui/react-context';\nimport React, { type PropsWithChildren, useCallback } from 'react';\n\nimport { useIntentDispatcher } from '@dxos/app-framework';\nimport {\n Icon,\n type ThemedClassName,\n Toolbar as NaturalToolbar,\n type ToolbarButtonProps as NaturalToolbarButtonProps,\n type ToolbarToggleGroupItemProps as NaturalToolbarToggleGroupItemProps,\n type ToolbarToggleProps as NaturalToolbarToggleProps,\n Tooltip,\n useTranslation,\n} from '@dxos/react-ui';\nimport { useAttention } from '@dxos/react-ui-attention';\nimport { nonNullable } from '@dxos/util';\n\nimport {\n alignKey,\n type AlignKey,\n type AlignValue,\n type CommentKey,\n type CommentValue,\n inRange,\n rangeFromIndex,\n rangeToIndex,\n styleKey,\n type StyleKey,\n type StyleValue,\n} from '../../defs';\nimport { completeCellRangeToThreadCursor } from '../../integrations';\nimport { SHEET_PLUGIN } from '../../meta';\nimport { type SheetType } from '../../types';\nimport { useSheetContext } from '../SheetContext';\n\n//\n// Buttons\n//\n\nconst buttonStyles = 'min-bs-0 p-2';\nconst tooltipProps = { side: 'bottom' as const, classNames: 'z-10' };\n\nconst ToolbarSeparator = () => <div role='separator' className='grow' />;\n\n//\n// ToolbarItem\n//\n\ntype ToolbarItemProps =\n | (NaturalToolbarButtonProps & { itemType: 'button'; icon: string })\n | (NaturalToolbarToggleGroupItemProps & { itemType: 'toggleGroupItem'; icon: string })\n | (NaturalToolbarToggleProps & { itemType: 'toggle'; icon: string });\n\nexport const ToolbarItem = ({ itemType, icon, children, ...props }: ToolbarItemProps) => {\n const Invoker =\n itemType === 'toggleGroupItem'\n ? NaturalToolbar.ToggleGroupItem\n : itemType === 'toggle'\n ? NaturalToolbar.Toggle\n : NaturalToolbar.Button;\n return (\n <Tooltip.Root>\n <Tooltip.Trigger asChild>\n {/* TODO(thure): type the props spread better. */}\n <Invoker variant='ghost' {...(props as any)} classNames={buttonStyles}>\n <Icon icon={icon} size={5} />\n <span className='sr-only'>{children}</span>\n </Invoker>\n </Tooltip.Trigger>\n <Tooltip.Portal>\n <Tooltip.Content {...tooltipProps}>\n {children}\n <Tooltip.Arrow />\n </Tooltip.Content>\n </Tooltip.Portal>\n </Tooltip.Root>\n );\n};\n\n//\n// Root\n//\n\ntype AlignAction = { key: AlignKey; value: AlignValue };\ntype CommentAction = { key: CommentKey; value: CommentValue; cellContent?: string };\ntype StyleAction = { key: StyleKey; value: StyleValue };\n\nexport type ToolbarAction = StyleAction | AlignAction | CommentAction;\nexport type ToolbarActionAnnotated = ToolbarAction & { unset?: boolean };\n\nexport type ToolbarActionType = ToolbarAction['key'];\n\nexport type ToolbarActionHandler = (action: ToolbarActionAnnotated) => void;\n\nexport type ToolbarProps = ThemedClassName<\n PropsWithChildren<{\n role?: string;\n }>\n>;\n\nconst [ToolbarContextProvider, useToolbarContext] = createContext<{\n onAction: (action: ToolbarActionAnnotated) => void;\n}>('Toolbar');\n\ntype Range = SheetType['ranges'][number];\n\nconst ToolbarRoot = ({ children, role, classNames }: ToolbarProps) => {\n const { id, model, cursorFallbackRange, cursor } = useSheetContext();\n const { hasAttention } = useAttention(id);\n const dispatch = useIntentDispatcher();\n\n // TODO(Zan): Externalize the toolbar action handler. E.g., Toolbar/keys should both fire events.\n const handleAction = useCallback(\n (action: ToolbarActionAnnotated) => {\n switch (action.key) {\n case 'alignment':\n if (cursorFallbackRange) {\n const index =\n model.sheet.ranges?.findIndex(\n (range) =>\n range.key === action.key &&\n inRange(rangeFromIndex(model.sheet, range.range), cursorFallbackRange.from),\n ) ?? -1;\n const nextRangeEntity = {\n range: rangeToIndex(model.sheet, cursorFallbackRange),\n key: action.key,\n value: action.value,\n };\n if (index < 0) {\n model.sheet.ranges?.push(nextRangeEntity);\n } else if (model.sheet.ranges![index].value === action.value) {\n model.sheet.ranges?.splice(index, 1);\n } else {\n model.sheet.ranges?.splice(index, 1, nextRangeEntity);\n }\n }\n break;\n case 'style':\n if (action.unset) {\n const index = model.sheet.ranges?.findIndex(\n (range) =>\n range.key === action.key &&\n cursorFallbackRange &&\n inRange(rangeFromIndex(model.sheet, range.range), cursorFallbackRange.from),\n );\n if (index >= 0) {\n model.sheet.ranges?.splice(index, 1);\n }\n } else if (cursorFallbackRange) {\n model.sheet.ranges?.push({\n range: rangeToIndex(model.sheet, cursorFallbackRange),\n key: action.key,\n value: action.value,\n });\n }\n break;\n case 'comment': {\n // TODO(Zan): We shouldn't hardcode the action ID.\n if (cursorFallbackRange) {\n void dispatch({\n action: 'dxos.org/plugin/thread/action/create',\n data: {\n cursor: completeCellRangeToThreadCursor(cursorFallbackRange),\n name: action.cellContent,\n subject: model.sheet,\n },\n });\n }\n }\n }\n },\n [model.sheet, cursorFallbackRange, cursor, dispatch],\n );\n\n return (\n <ToolbarContextProvider onAction={handleAction}>\n <NaturalToolbar.Root classNames={['pli-0.5 attention-surface', !hasAttention && 'opacity-20', classNames]}>\n {children}\n </NaturalToolbar.Root>\n </ToolbarContextProvider>\n );\n};\n\n// TODO(burdon): Generalize.\n// TODO(burdon): Detect and display current state.\ntype ButtonProps<T> = {\n value: T;\n icon: string;\n disabled?: (state: Range) => boolean;\n};\n\n//\n// Alignment\n//\n\nconst alignmentOptions: ButtonProps<AlignValue>[] = [\n { value: 'start', icon: 'ph--text-align-left--regular' },\n { value: 'center', icon: 'ph--text-align-center--regular' },\n { value: 'end', icon: 'ph--text-align-right--regular' },\n];\n\nconst Alignment = () => {\n const { cursor, model } = useSheetContext();\n const { onAction } = useToolbarContext('Alignment');\n const { t } = useTranslation(SHEET_PLUGIN);\n\n // TODO(thure): Can this O(n) call be memoized?\n const value = cursor\n ? model.sheet.ranges?.findLast(\n ({ range, key }) => key === alignKey && inRange(rangeFromIndex(model.sheet, range), cursor),\n )?.value\n : undefined;\n\n return (\n <NaturalToolbar.ToggleGroup\n type='single'\n value={\n // TODO(thure): providing `undefined` leaves the last item active which was active rather than showing none.\n value ?? 'never'\n }\n onValueChange={(value: AlignValue) => onAction?.({ key: alignKey, value })}\n >\n {alignmentOptions.map(({ value, icon }) => (\n <ToolbarItem itemType='toggleGroupItem' key={value} value={value} icon={icon}>\n {t('toolbar action label', {\n key: t(`range key ${alignKey} label`),\n value: t(`range value ${value} label`),\n })}\n </ToolbarItem>\n ))}\n </NaturalToolbar.ToggleGroup>\n );\n};\n\nconst styleOptions: ButtonProps<StyleValue>[] = [\n { value: 'highlight', icon: 'ph--highlighter--regular' },\n { value: 'softwrap', icon: 'ph--paragraph--regular' },\n];\n\nconst Styles = () => {\n const { cursorFallbackRange, model } = useSheetContext();\n const { onAction } = useToolbarContext('Styles');\n const { t } = useTranslation(SHEET_PLUGIN);\n\n // TODO(thure): Can this O(n) call be memoized?\n const activeValues = cursorFallbackRange\n ? model.sheet.ranges\n ?.filter(\n ({ range, key }) => key === 'style' && inRange(rangeFromIndex(model.sheet, range), cursorFallbackRange.from),\n )\n .reduce((acc, { value }) => {\n acc.add(value);\n return acc;\n }, new Set())\n : undefined;\n\n return (\n <>\n {styleOptions.map(({ value, icon }) => (\n <ToolbarItem\n itemType='toggle'\n key={value}\n pressed={activeValues?.has(value)}\n onPressedChange={(nextPressed: boolean) => {\n onAction?.({ key: 'style', value, unset: !nextPressed });\n }}\n icon={icon}\n >\n {t('toolbar action label', {\n key: t(`range key ${styleKey} label`),\n value: t(`range value ${value} label`),\n })}\n </ToolbarItem>\n ))}\n </>\n );\n};\n\n//\n// Actions\n//\n\nconst Actions = () => {\n const { onAction } = useToolbarContext('Actions');\n const { cursorFallbackRange, cursor, model } = useSheetContext();\n const { t } = useTranslation(SHEET_PLUGIN);\n\n // TODO(thure): Can this O(n) call be memoized?\n const overlapsCommentAnchor = (model.sheet.threads ?? [])\n .filter(nonNullable)\n .filter((thread) => thread.status !== 'resolved')\n .some((thread) => {\n if (!cursorFallbackRange) {\n return false;\n }\n return rangeToIndex(model.sheet, cursorFallbackRange) === thread.anchor;\n });\n\n const tooltipLabelKey = !cursor\n ? 'no cursor label'\n : overlapsCommentAnchor\n ? 'selection overlaps existing comment label'\n : 'comment label';\n\n return (\n <ToolbarItem\n itemType='button'\n value='comment'\n icon='ph--chat-text--regular'\n data-testid='editor.toolbar.comment'\n onClick={() => {\n if (!cursorFallbackRange) {\n return;\n }\n return onAction?.({\n key: 'comment',\n value: rangeToIndex(model.sheet, cursorFallbackRange),\n cellContent: model.getCellText(cursorFallbackRange.from),\n });\n }}\n disabled={!cursorFallbackRange || overlapsCommentAnchor}\n >\n {t(tooltipLabelKey)}\n </ToolbarItem>\n );\n};\n\nexport const Toolbar = {\n Root: ToolbarRoot,\n Separator: ToolbarSeparator,\n Alignment,\n Styles,\n Actions,\n};\n\nexport { useToolbarContext };\n", "//\n// Copyright 2024 DXOS.org\n//\n\nimport { SheetContainer } from './SheetContainer';\n\nexport default SheetContainer;\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["React", "StackItem", "React", "Icon", "FunctionEditor", "model", "cursor", "range", "useSheetContext", "value", "formula", "getCellValue", "isFormula", "graph", "mapFunctionBindingFromId", "mapFormulaIndicesToRefs", "String", "div", "className", "rangeToA1Notation", "addressToA1Notation", "Icon", "icon", "classNames", "span", "createContext", "React", "useCallback", "useIntentDispatcher", "Icon", "Toolbar", "NaturalToolbar", "Tooltip", "useTranslation", "useAttention", "nonNullable", "buttonStyles", "tooltipProps", "side", "classNames", "ToolbarSeparator", "React", "div", "role", "className", "ToolbarItem", "itemType", "icon", "children", "props", "Invoker", "NaturalToolbar", "ToggleGroupItem", "Toggle", "Button", "Tooltip", "Root", "Trigger", "asChild", "variant", "Icon", "size", "span", "Portal", "Content", "Arrow", "ToolbarContextProvider", "useToolbarContext", "createContext", "ToolbarRoot", "id", "model", "cursorFallbackRange", "cursor", "useSheetContext", "hasAttention", "useAttention", "dispatch", "useIntentDispatcher", "handleAction", "useCallback", "action", "key", "index", "sheet", "ranges", "findIndex", "range", "inRange", "rangeFromIndex", "from", "nextRangeEntity", "rangeToIndex", "value", "push", "splice", "unset", "data", "completeCellRangeToThreadCursor", "name", "cellContent", "subject", "onAction", "alignmentOptions", "Alignment", "t", "useTranslation", "SHEET_PLUGIN", "findLast", "alignKey", "undefined", "ToggleGroup", "type", "onValueChange", "map", "styleOptions", "Styles", "activeValues", "filter", "reduce", "acc", "add", "Set", "pressed", "has", "onPressedChange", "nextPressed", "styleKey", "Actions", "overlapsCommentAnchor", "threads", "nonNullable", "thread", "status", "some", "anchor", "tooltipLabelKey", "data-testid", "onClick", "getCellText", "disabled", "Toolbar", "Separator", "SheetContainer", "space", "sheet", "role", "ignoreAttention", "graph", "useComputeGraph", "React", "SheetProvider", "StackItem", "Content", "toolbar", "statusbar", "classNames", "Toolbar", "Root", "Styles", "Alignment", "Separator", "Actions", "GridSheet", "FunctionEditor", "SheetContainer"]
|
|
7
|
-
}
|