@dxos/plugin-sheet 0.6.12-main.78ddbdf → 0.6.12-main.89e9959
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-V4GCCZTX.mjs → SheetContainer-LG77O4RM.mjs} +14 -13
- package/dist/lib/browser/SheetContainer-LG77O4RM.mjs.map +7 -0
- package/dist/lib/browser/{chunk-U2JHW3L6.mjs → chunk-CHQAW4F4.mjs} +206 -53
- package/dist/lib/browser/chunk-CHQAW4F4.mjs.map +7 -0
- package/dist/lib/browser/{chunk-6ZMQVB4Z.mjs → chunk-GSV5QNLD.mjs} +220 -177
- package/dist/lib/browser/chunk-GSV5QNLD.mjs.map +7 -0
- package/dist/lib/browser/{chunk-T3NJFTD4.mjs → chunk-WZMOZKQZ.mjs} +2 -2
- package/dist/lib/browser/{chunk-T3NJFTD4.mjs.map → chunk-WZMOZKQZ.mjs.map} +3 -3
- package/dist/lib/browser/graph-M4IQ76QX.mjs +33 -0
- package/dist/lib/browser/index.mjs +45 -21
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/types.mjs +1 -1
- package/dist/lib/node/{SheetContainer-3ZY7MPWJ.cjs → SheetContainer-OZ7DHH4L.cjs} +21 -20
- package/dist/lib/node/SheetContainer-OZ7DHH4L.cjs.map +7 -0
- package/dist/lib/node/{chunk-OTTD7FBK.cjs → chunk-5FTFZL5W.cjs} +224 -70
- package/dist/lib/node/chunk-5FTFZL5W.cjs.map +7 -0
- package/dist/lib/node/{chunk-DD6FIXWC.cjs → chunk-5XPK2V4A.cjs} +222 -175
- package/dist/lib/node/chunk-5XPK2V4A.cjs.map +7 -0
- package/dist/lib/node/{chunk-Q3HBHPRL.cjs → chunk-AOP42UAA.cjs} +5 -5
- package/dist/lib/node/{chunk-Q3HBHPRL.cjs.map → chunk-AOP42UAA.cjs.map} +3 -3
- package/dist/lib/node/graph-Q3N2X26H.cjs +55 -0
- package/dist/lib/node/graph-Q3N2X26H.cjs.map +7 -0
- package/dist/lib/node/index.cjs +51 -30
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types.cjs +8 -8
- package/dist/lib/node/types.cjs.map +1 -1
- package/dist/lib/node-esm/{SheetContainer-PXSJX6XK.mjs → SheetContainer-4XS2G25Z.mjs} +14 -13
- package/dist/lib/node-esm/SheetContainer-4XS2G25Z.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-D6KU5MI7.mjs → chunk-5WPZCXNS.mjs} +220 -177
- package/dist/lib/node-esm/chunk-5WPZCXNS.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-7HVSOTGA.mjs → chunk-KK3XL37M.mjs} +206 -53
- package/dist/lib/node-esm/chunk-KK3XL37M.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-BMNA27EX.mjs → chunk-RR2AO4SM.mjs} +2 -2
- package/dist/lib/node-esm/{chunk-BMNA27EX.mjs.map → chunk-RR2AO4SM.mjs.map} +3 -3
- package/dist/lib/node-esm/graph-SMPUMOV2.mjs +34 -0
- package/dist/lib/node-esm/index.mjs +45 -21
- package/dist/lib/node-esm/index.mjs.map +4 -4
- 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/CellEditor/CellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/CellEditor/extension.d.ts.map +1 -1
- package/dist/types/src/components/GridSheet/GridSheet.d.ts +3 -3
- package/dist/types/src/components/GridSheet/GridSheet.d.ts.map +1 -1
- package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts +1 -1
- package/dist/types/src/components/GridSheet/util.d.ts +9 -0
- package/dist/types/src/components/GridSheet/util.d.ts.map +1 -1
- package/dist/types/src/components/Sheet/Sheet.d.ts.map +1 -1
- package/dist/types/src/components/Sheet/Sheet.stories.d.ts.map +1 -1
- package/dist/types/src/components/Sheet/sheet-context.d.ts +3 -3
- package/dist/types/src/components/Sheet/sheet-context.d.ts.map +1 -1
- package/dist/types/src/components/SheetContainer.d.ts +1 -1
- package/dist/types/src/components/SheetContainer.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/defs/types.d.ts.map +1 -1
- package/dist/types/src/defs/util.d.ts +1 -1
- package/dist/types/src/defs/util.d.ts.map +1 -1
- package/dist/types/src/extensions/compute.d.ts +3 -2
- package/dist/types/src/extensions/compute.d.ts.map +1 -1
- package/dist/types/src/extensions/compute.stories.d.ts.map +1 -1
- package/dist/types/src/graph/compute-graph-registry.d.ts +34 -0
- package/dist/types/src/graph/compute-graph-registry.d.ts.map +1 -0
- package/dist/types/src/graph/compute-graph.d.ts +17 -34
- package/dist/types/src/graph/compute-graph.d.ts.map +1 -1
- package/dist/types/src/graph/compute-graph.stories.d.ts.map +1 -1
- package/dist/types/src/graph/compute-graph.test.d.ts +2 -0
- package/dist/types/src/graph/compute-graph.test.d.ts.map +1 -0
- package/dist/types/src/graph/compute-node.d.ts +9 -2
- package/dist/types/src/graph/compute-node.d.ts.map +1 -1
- package/dist/types/src/graph/{async-function.d.ts → functions/async-function.d.ts} +13 -4
- package/dist/types/src/graph/functions/async-function.d.ts.map +1 -0
- package/dist/types/src/graph/functions/edge-function.d.ts +21 -0
- package/dist/types/src/graph/functions/edge-function.d.ts.map +1 -0
- package/dist/types/src/graph/functions/function-defs.d.ts.map +1 -0
- package/dist/types/src/graph/functions/index.d.ts +4 -0
- package/dist/types/src/graph/functions/index.d.ts.map +1 -0
- package/dist/types/src/graph/index.d.ts +2 -1
- package/dist/types/src/graph/index.d.ts.map +1 -1
- package/dist/types/src/graph/testing/index.d.ts +3 -0
- package/dist/types/src/graph/testing/index.d.ts.map +1 -0
- package/dist/types/src/graph/testing/test-builder.d.ts +15 -0
- package/dist/types/src/graph/testing/test-builder.d.ts.map +1 -0
- package/dist/types/src/graph/testing/test-plugin.d.ts +36 -0
- package/dist/types/src/graph/testing/test-plugin.d.ts.map +1 -0
- package/dist/types/src/hooks/useComputeGraph.d.ts.map +1 -1
- package/dist/types/src/hooks/useSheetModel.d.ts +2 -2
- package/dist/types/src/hooks/useSheetModel.d.ts.map +1 -1
- package/dist/types/src/model/sheet-model.d.ts +3 -3
- package/dist/types/src/model/sheet-model.d.ts.map +1 -1
- package/dist/types/src/model/sheet-model.test.d.ts +2 -0
- package/dist/types/src/model/sheet-model.test.d.ts.map +1 -0
- package/dist/types/src/testing/testing.d.ts +4 -5
- package/dist/types/src/testing/testing.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +4 -3
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +40 -39
- package/src/SheetPlugin.tsx +19 -15
- package/src/components/CellEditor/CellEditor.stories.tsx +2 -3
- package/src/components/CellEditor/extension.test.ts +0 -1
- package/src/components/CellEditor/extension.ts +4 -3
- package/src/components/GridSheet/GridSheet.stories.tsx +3 -3
- package/src/components/GridSheet/GridSheet.tsx +26 -8
- package/src/components/GridSheet/util.ts +61 -21
- package/src/components/Sheet/Sheet.stories.tsx +21 -20
- package/src/components/Sheet/Sheet.tsx +30 -14
- package/src/components/Sheet/sheet-context.tsx +4 -4
- package/src/components/SheetContainer.tsx +13 -15
- package/src/defs/types.ts +1 -0
- package/src/defs/util.ts +19 -3
- package/src/extensions/compute.stories.tsx +20 -20
- package/src/extensions/compute.ts +91 -42
- package/src/graph/compute-graph-registry.ts +90 -0
- package/src/graph/compute-graph.stories.tsx +4 -3
- package/src/graph/compute-graph.test.ts +87 -0
- package/src/graph/compute-graph.ts +73 -121
- package/src/graph/compute-node.ts +17 -5
- package/src/graph/{async-function.ts → functions/async-function.ts} +23 -15
- package/src/graph/{edge-function.ts → functions/edge-function.ts} +14 -13
- package/src/graph/functions/index.ts +7 -0
- package/src/graph/hyperformula.test.ts +1 -2
- package/src/graph/index.ts +2 -1
- package/src/graph/testing/index.ts +6 -0
- package/src/graph/testing/test-builder.ts +54 -0
- package/src/graph/{custom-function.ts → testing/test-plugin.ts} +43 -9
- package/src/hooks/hooks.stories.tsx +3 -3
- package/src/hooks/useComputeGraph.ts +9 -1
- package/src/hooks/useSheetModel.ts +4 -7
- package/src/model/sheet-model.test.ts +59 -0
- package/src/model/sheet-model.ts +47 -30
- package/src/testing/testing.tsx +17 -15
- package/src/types.ts +3 -3
- package/dist/lib/browser/SheetContainer-V4GCCZTX.mjs.map +0 -7
- package/dist/lib/browser/chunk-6ZMQVB4Z.mjs.map +0 -7
- package/dist/lib/browser/chunk-U2JHW3L6.mjs.map +0 -7
- package/dist/lib/browser/graph-T27BOBOV.mjs +0 -21
- package/dist/lib/node/SheetContainer-3ZY7MPWJ.cjs.map +0 -7
- package/dist/lib/node/chunk-DD6FIXWC.cjs.map +0 -7
- package/dist/lib/node/chunk-OTTD7FBK.cjs.map +0 -7
- package/dist/lib/node/graph-SPKGX7W4.cjs +0 -43
- package/dist/lib/node/graph-SPKGX7W4.cjs.map +0 -7
- package/dist/lib/node-esm/SheetContainer-PXSJX6XK.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-7HVSOTGA.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-D6KU5MI7.mjs.map +0 -7
- package/dist/lib/node-esm/graph-U67IO4UC.mjs +0 -22
- package/dist/types/src/graph/async-function.d.ts.map +0 -1
- package/dist/types/src/graph/compute-graph.browser.test.d.ts +0 -2
- package/dist/types/src/graph/compute-graph.browser.test.d.ts.map +0 -1
- package/dist/types/src/graph/custom-function.d.ts +0 -21
- package/dist/types/src/graph/custom-function.d.ts.map +0 -1
- package/dist/types/src/graph/edge-function.d.ts +0 -20
- package/dist/types/src/graph/edge-function.d.ts.map +0 -1
- package/dist/types/src/graph/function-defs.d.ts.map +0 -1
- package/src/graph/compute-graph.browser.test.ts +0 -104
- /package/dist/lib/browser/{graph-T27BOBOV.mjs.map → graph-M4IQ76QX.mjs.map} +0 -0
- /package/dist/lib/node-esm/{graph-U67IO4UC.mjs.map → graph-SMPUMOV2.mjs.map} +0 -0
- /package/dist/types/src/graph/{function-defs.d.ts → functions/function-defs.d.ts} +0 -0
- /package/src/graph/{function-defs.ts → functions/function-defs.ts} +0 -0
|
@@ -25,6 +25,7 @@ import { Resizable, type ResizeCallback, type ResizeStartCallback } from 're-res
|
|
|
25
25
|
import React, {
|
|
26
26
|
type CSSProperties,
|
|
27
27
|
type DOMAttributes,
|
|
28
|
+
type KeyboardEventHandler,
|
|
28
29
|
type PropsWithChildren,
|
|
29
30
|
forwardRef,
|
|
30
31
|
useEffect,
|
|
@@ -40,7 +41,7 @@ import { debounce } from '@dxos/async';
|
|
|
40
41
|
import { fullyQualifiedId, createDocAccessor } from '@dxos/client/echo';
|
|
41
42
|
import { log } from '@dxos/log';
|
|
42
43
|
import { type ThemedClassName } from '@dxos/react-ui';
|
|
43
|
-
import {
|
|
44
|
+
import { ATTENABLE_ATTRIBUTE, useAttendableAttributes, useAttention, useAttentionPath } from '@dxos/react-ui-attention';
|
|
44
45
|
import { mx } from '@dxos/react-ui-theme';
|
|
45
46
|
|
|
46
47
|
import {
|
|
@@ -861,10 +862,8 @@ const SheetGrid = forwardRef<HTMLDivElement, SheetGridProps>(
|
|
|
861
862
|
columnSizes,
|
|
862
863
|
});
|
|
863
864
|
|
|
864
|
-
// TODO(burdon): Prevent scroll if not attended.
|
|
865
865
|
const id = fullyQualifiedId(model.sheet);
|
|
866
|
-
const
|
|
867
|
-
const hasAttention = useHasAttention(id);
|
|
866
|
+
const { hasAttention } = useAttention(id);
|
|
868
867
|
|
|
869
868
|
return (
|
|
870
869
|
<div ref={containerRef} role='grid' className='relative flex grow overflow-hidden'>
|
|
@@ -872,6 +871,7 @@ const SheetGrid = forwardRef<HTMLDivElement, SheetGridProps>(
|
|
|
872
871
|
<div className={mx('z-20 absolute inset-0 border border-gridLine pointer-events-none')} />
|
|
873
872
|
|
|
874
873
|
{/* Grid scroll container. */}
|
|
874
|
+
{/* NOTE: Prevents scroll if not attended. */}
|
|
875
875
|
<div ref={scrollerRef} className={mx('grow', hasAttention && 'overflow-auto scrollbar-thin')}>
|
|
876
876
|
{/* Scroll content. */}
|
|
877
877
|
<div
|
|
@@ -952,21 +952,37 @@ const SheetGrid = forwardRef<HTMLDivElement, SheetGridProps>(
|
|
|
952
952
|
</div>
|
|
953
953
|
|
|
954
954
|
{/* Hidden input for key navigation. */}
|
|
955
|
-
{createPortal(
|
|
956
|
-
<input
|
|
957
|
-
ref={inputRef}
|
|
958
|
-
autoFocus
|
|
959
|
-
className='absolute w-[1px] h-[1px] bg-transparent outline-none border-none caret-transparent'
|
|
960
|
-
onKeyDown={handleKeyDown}
|
|
961
|
-
{...attendableAttrs}
|
|
962
|
-
/>,
|
|
963
|
-
document.body,
|
|
964
|
-
)}
|
|
955
|
+
{createPortal(<SheetInput ref={inputRef} id={id} onKeyDown={handleKeyDown} />, document.body)}
|
|
965
956
|
</div>
|
|
966
957
|
);
|
|
967
958
|
},
|
|
968
959
|
);
|
|
969
960
|
|
|
961
|
+
type SheetInputProps = {
|
|
962
|
+
id: string;
|
|
963
|
+
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
|
|
964
|
+
};
|
|
965
|
+
|
|
966
|
+
const SheetInput = forwardRef<HTMLInputElement, SheetInputProps>(({ id, onKeyDown }, forwardedRef) => {
|
|
967
|
+
const path = useAttentionPath();
|
|
968
|
+
const attendableAttrs = useAttendableAttributes(id);
|
|
969
|
+
|
|
970
|
+
// TODO(wittjosiah): Consider factoring out as an attention util.
|
|
971
|
+
// Wrap input in attendable divs for each part of the path.
|
|
972
|
+
// This ensures that the sheet stays attended when the input is focused.
|
|
973
|
+
return path.toReversed().reduce(
|
|
974
|
+
(acc, part) => {
|
|
975
|
+
return <div {...{ [ATTENABLE_ATTRIBUTE]: part }}>{acc}</div>;
|
|
976
|
+
},
|
|
977
|
+
<input
|
|
978
|
+
ref={forwardedRef}
|
|
979
|
+
className='absolute w-[1px] h-[1px] bg-transparent outline-none border-none caret-transparent'
|
|
980
|
+
onKeyDown={onKeyDown}
|
|
981
|
+
{...attendableAttrs}
|
|
982
|
+
/>,
|
|
983
|
+
);
|
|
984
|
+
});
|
|
985
|
+
|
|
970
986
|
//
|
|
971
987
|
// Selection
|
|
972
988
|
//
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
import React, { type PropsWithChildren, createContext, useContext, useMemo, useState } from 'react';
|
|
6
6
|
|
|
7
7
|
import { invariant } from '@dxos/invariant';
|
|
8
|
-
import { type Space } from '@dxos/react-client/echo';
|
|
9
8
|
|
|
10
9
|
import { createDecorations } from './decorations';
|
|
11
10
|
import { type CellAddress, type CellRange } from '../../defs';
|
|
11
|
+
import { type ComputeGraph } from '../../graph';
|
|
12
12
|
import { useSheetModel, useFormattingModel } from '../../hooks';
|
|
13
13
|
import { type FormattingModel, type SheetModel } from '../../model';
|
|
14
14
|
import { type SheetType } from '../../types';
|
|
@@ -45,19 +45,19 @@ export const useSheetContext = (): SheetContextType => {
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
export type SheetContextProps = {
|
|
48
|
+
graph: ComputeGraph;
|
|
48
49
|
sheet: SheetType;
|
|
49
|
-
space: Space;
|
|
50
50
|
readonly?: boolean;
|
|
51
51
|
} & Pick<SheetContextType, 'onInfo'>;
|
|
52
52
|
|
|
53
53
|
export const SheetContextProvider = ({
|
|
54
54
|
children,
|
|
55
|
+
graph,
|
|
55
56
|
sheet,
|
|
56
|
-
space,
|
|
57
57
|
readonly,
|
|
58
58
|
onInfo,
|
|
59
59
|
}: PropsWithChildren<SheetContextProps>) => {
|
|
60
|
-
const model = useSheetModel(
|
|
60
|
+
const model = useSheetModel(graph, sheet, { readonly });
|
|
61
61
|
const formatting = useFormattingModel(model);
|
|
62
62
|
|
|
63
63
|
// TODO(Zan): Impl. set range and set cursor that scrolls to that cell or range if it is not visible.
|
|
@@ -6,27 +6,22 @@ import React, { useCallback } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { useIntentDispatcher } from '@dxos/app-framework';
|
|
8
8
|
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
9
|
-
import {
|
|
9
|
+
import { useAttendableAttributes, useAttention } from '@dxos/react-ui-attention';
|
|
10
10
|
import { focusRing, mx } from '@dxos/react-ui-theme';
|
|
11
11
|
|
|
12
12
|
import { Sheet, type SheetRootProps } from './Sheet';
|
|
13
13
|
import { Toolbar, type ToolbarAction } from './Toolbar';
|
|
14
14
|
|
|
15
|
-
// TODO(Zan): Factor out, copied this from MarkdownPlugin.
|
|
16
|
-
const attentionFragment = mx(
|
|
17
|
-
'group-focus-within/editor:attention-surface group-[[aria-current]]/editor:attention-surface',
|
|
18
|
-
'group-focus-within/editor:border-separator',
|
|
19
|
-
);
|
|
20
|
-
|
|
21
15
|
// TODO(Zan): Factor out, copied this from MarkdownPlugin.
|
|
22
16
|
export const sectionToolbarLayout =
|
|
23
17
|
'bs-[--rail-action] bg-[--sticky-bg] sticky block-start-0 __-block-start-px transition-opacity';
|
|
24
18
|
|
|
25
|
-
const SheetContainer = ({
|
|
19
|
+
const SheetContainer = ({ graph, sheet, role }: SheetRootProps & { role?: string }) => {
|
|
26
20
|
const dispatch = useIntentDispatcher();
|
|
27
21
|
|
|
28
22
|
const id = fullyQualifiedId(sheet);
|
|
29
|
-
const
|
|
23
|
+
const attendableAttrs = useAttendableAttributes(id);
|
|
24
|
+
const { hasAttention } = useAttention(id);
|
|
30
25
|
|
|
31
26
|
// TODO(Zan): Centralise the toolbar action handler. Current implementation in stories.
|
|
32
27
|
const handleAction = useCallback(
|
|
@@ -49,15 +44,19 @@ const SheetContainer = ({ sheet, space, role }: SheetRootProps & { role?: string
|
|
|
49
44
|
);
|
|
50
45
|
|
|
51
46
|
return (
|
|
52
|
-
<div
|
|
53
|
-
|
|
47
|
+
<div
|
|
48
|
+
role='none'
|
|
49
|
+
className={role === 'article' ? 'row-span-2 grid grid-rows-subgrid' : undefined}
|
|
50
|
+
{...(role === 'article' && attendableAttrs)}
|
|
51
|
+
>
|
|
52
|
+
<Sheet.Root graph={graph} sheet={sheet}>
|
|
54
53
|
<div role='none' className={mx('flex flex-0 justify-center overflow-x-auto')}>
|
|
55
54
|
<Toolbar.Root
|
|
56
55
|
onAction={handleAction}
|
|
57
56
|
classNames={mx(
|
|
58
57
|
role === 'section'
|
|
59
|
-
? ['z-[2] group-focus-within/section:visible', !
|
|
60
|
-
: '
|
|
58
|
+
? ['z-[2] group-focus-within/section:visible', !hasAttention && 'invisible', sectionToolbarLayout]
|
|
59
|
+
: 'attention-surface',
|
|
61
60
|
)}
|
|
62
61
|
>
|
|
63
62
|
{/* TODO(Zan): Restore some of this functionality */}
|
|
@@ -73,9 +72,8 @@ const SheetContainer = ({ sheet, space, role }: SheetRootProps & { role?: string
|
|
|
73
72
|
className={mx(
|
|
74
73
|
role === 'section' && 'aspect-square border-is border-bs border-be border-separator',
|
|
75
74
|
role === 'article' &&
|
|
76
|
-
'flex is-full overflow-hidden focus-visible:ring-inset row-span-1 data-[toolbar=disabled]:pbs-2 data-[toolbar=disabled]:row-span-2 border-bs border-separator',
|
|
75
|
+
'flex is-full overflow-hidden focus-visible:ring-inset row-span-1 data-[toolbar=disabled]:pbs-2 data-[toolbar=disabled]:row-span-2 border-bs border-separator attention-surface',
|
|
77
76
|
focusRing,
|
|
78
|
-
attentionFragment,
|
|
79
77
|
)}
|
|
80
78
|
>
|
|
81
79
|
<Sheet.Main />
|
package/src/defs/types.ts
CHANGED
|
@@ -34,6 +34,7 @@ export const addressToA1Notation = ({ col, row }: CellAddress): string => {
|
|
|
34
34
|
return `${columnLetter(col)}${row + 1}`;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
// TODO(burdon): See simpleCellAddressFromString
|
|
37
38
|
export const addressFromA1Notation = (ref: string): CellAddress => {
|
|
38
39
|
const match = ref.match(/([A-Z]+)(\d+)/);
|
|
39
40
|
invariant(match, `Invalid notation: ${ref}`);
|
package/src/defs/util.ts
CHANGED
|
@@ -5,7 +5,15 @@
|
|
|
5
5
|
import { randomBytes } from '@dxos/crypto';
|
|
6
6
|
import { create } from '@dxos/echo-schema';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
addressFromA1Notation,
|
|
10
|
+
type CellAddress,
|
|
11
|
+
type CellRange,
|
|
12
|
+
DEFAULT_COLUMNS,
|
|
13
|
+
DEFAULT_ROWS,
|
|
14
|
+
MAX_COLUMNS,
|
|
15
|
+
MAX_ROWS,
|
|
16
|
+
} from './types';
|
|
9
17
|
import { type CreateSheetOptions, type SheetSize, SheetType } from '../types';
|
|
10
18
|
|
|
11
19
|
// TODO(burdon): Factor out from dxos/protocols to new common package.
|
|
@@ -52,9 +60,9 @@ export const initialize = (
|
|
|
52
60
|
}
|
|
53
61
|
};
|
|
54
62
|
|
|
55
|
-
export const createSheet = ({
|
|
63
|
+
export const createSheet = ({ name, cells, ...size }: CreateSheetOptions = {}): SheetType => {
|
|
56
64
|
const sheet = create(SheetType, {
|
|
57
|
-
|
|
65
|
+
name,
|
|
58
66
|
cells: {},
|
|
59
67
|
rows: [],
|
|
60
68
|
columns: [],
|
|
@@ -64,6 +72,14 @@ export const createSheet = ({ title, ...size }: CreateSheetOptions = {}): SheetT
|
|
|
64
72
|
});
|
|
65
73
|
|
|
66
74
|
initialize(sheet, size);
|
|
75
|
+
|
|
76
|
+
if (cells) {
|
|
77
|
+
Object.entries(cells).forEach(([key, { value }]) => {
|
|
78
|
+
const idx = addressToIndex(sheet, addressFromA1Notation(key));
|
|
79
|
+
sheet.cells[idx] = { value };
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
67
83
|
return sheet;
|
|
68
84
|
};
|
|
69
85
|
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
|
-
import React, { useEffect,
|
|
6
|
+
import React, { useEffect, useMemo } from 'react';
|
|
7
7
|
|
|
8
|
+
import { PublicKey } from '@dxos/keys';
|
|
8
9
|
import { useSpace } from '@dxos/react-client/echo';
|
|
9
10
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
10
11
|
import { useThemeContext } from '@dxos/react-ui';
|
|
@@ -13,16 +14,16 @@ import {
|
|
|
13
14
|
createMarkdownExtensions,
|
|
14
15
|
createThemeExtensions,
|
|
15
16
|
decorateMarkdown,
|
|
17
|
+
documentId,
|
|
16
18
|
useTextEditor,
|
|
17
19
|
} from '@dxos/react-ui-editor';
|
|
18
20
|
import { withTheme, withLayout } from '@dxos/storybook-utils';
|
|
19
21
|
import { nonNullable } from '@dxos/util';
|
|
20
22
|
|
|
21
|
-
import { compute } from './compute';
|
|
23
|
+
import { compute, computeGraphFacet } from './compute';
|
|
22
24
|
import { Sheet } from '../components';
|
|
23
|
-
import { type ComputeNode } from '../graph';
|
|
24
25
|
import { useComputeGraph, useSheetModel } from '../hooks';
|
|
25
|
-
import { useTestSheet,
|
|
26
|
+
import { useTestSheet, withComputeGraphDecorator } from '../testing';
|
|
26
27
|
import { SheetType } from '../types';
|
|
27
28
|
|
|
28
29
|
const str = (...lines: string[]) => lines.join('\n');
|
|
@@ -34,20 +35,15 @@ type EditorProps = {
|
|
|
34
35
|
// TODO(burdon): Implement named expressions.
|
|
35
36
|
// https://hyperformula.handsontable.com/guide/cell-references.html
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
// TODO(burdon): Inline Adobe eCharts.
|
|
39
|
+
|
|
38
40
|
const SHEET_NAME = 'Test Sheet';
|
|
39
41
|
|
|
40
42
|
const Editor = ({ text }: EditorProps) => {
|
|
43
|
+
const id = useMemo(() => PublicKey.random(), []);
|
|
41
44
|
const { themeMode } = useThemeContext();
|
|
42
45
|
const space = useSpace();
|
|
43
|
-
const
|
|
44
|
-
const [node, setNode] = useState<ComputeNode>();
|
|
45
|
-
// TODO(burdon): Virtualize SheetModel.
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
if (graph) {
|
|
48
|
-
setNode(graph.getOrCreateNode(DOC_NAME));
|
|
49
|
-
}
|
|
50
|
-
}, [graph]);
|
|
46
|
+
const computeGraph = useComputeGraph(space);
|
|
51
47
|
const { parentRef, focusAttributes } = useTextEditor(
|
|
52
48
|
() => ({
|
|
53
49
|
initialValue: text,
|
|
@@ -55,11 +51,13 @@ const Editor = ({ text }: EditorProps) => {
|
|
|
55
51
|
createBasicExtensions(),
|
|
56
52
|
createMarkdownExtensions({ themeMode }),
|
|
57
53
|
createThemeExtensions({ themeMode, syntaxHighlighting: true }),
|
|
58
|
-
|
|
54
|
+
documentId.of(id.toHex()),
|
|
55
|
+
computeGraph && computeGraphFacet.of(computeGraph),
|
|
56
|
+
compute(),
|
|
59
57
|
decorateMarkdown(),
|
|
60
58
|
].filter(nonNullable),
|
|
61
59
|
}),
|
|
62
|
-
[
|
|
60
|
+
[computeGraph, themeMode],
|
|
63
61
|
);
|
|
64
62
|
|
|
65
63
|
return <div className='w-[40rem] overflow-hidden' ref={parentRef} {...focusAttributes} />;
|
|
@@ -68,21 +66,21 @@ const Editor = ({ text }: EditorProps) => {
|
|
|
68
66
|
const Grid = () => {
|
|
69
67
|
const space = useSpace();
|
|
70
68
|
const graph = useComputeGraph(space);
|
|
71
|
-
const sheet = useTestSheet(space, graph, {
|
|
72
|
-
const model = useSheetModel(
|
|
69
|
+
const sheet = useTestSheet(space, graph, { name: SHEET_NAME });
|
|
70
|
+
const model = useSheetModel(graph, sheet);
|
|
73
71
|
useEffect(() => {
|
|
74
72
|
if (model) {
|
|
75
73
|
model.setValues({ A1: { value: 100 }, A2: { value: 200 }, A3: { value: 300 }, A5: { value: '=SUM(A1:A3)' } });
|
|
76
74
|
}
|
|
77
75
|
}, [model]);
|
|
78
76
|
|
|
79
|
-
if (!
|
|
77
|
+
if (!graph || !sheet) {
|
|
80
78
|
return null;
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
return (
|
|
84
82
|
<div className='flex w-[40rem] overflow-hidden'>
|
|
85
|
-
<Sheet.Root
|
|
83
|
+
<Sheet.Root graph={graph} sheet={sheet}>
|
|
86
84
|
<Sheet.Main classNames='border border-separator' />
|
|
87
85
|
</Sheet.Root>
|
|
88
86
|
</div>
|
|
@@ -102,13 +100,14 @@ export default {
|
|
|
102
100
|
title: 'plugin-sheet/extensions',
|
|
103
101
|
decorators: [
|
|
104
102
|
withClientProvider({ types: [SheetType], createIdentity: true, createSpace: true }),
|
|
105
|
-
|
|
103
|
+
withComputeGraphDecorator(),
|
|
106
104
|
withTheme,
|
|
107
105
|
withLayout({ fullscreen: true, classNames: 'justify-center' }),
|
|
108
106
|
],
|
|
109
107
|
parameters: { layout: 'fullscreen' },
|
|
110
108
|
};
|
|
111
109
|
|
|
110
|
+
// TODO(burdon): Inline formulae.
|
|
112
111
|
export const Default = {
|
|
113
112
|
render: Editor,
|
|
114
113
|
args: {
|
|
@@ -146,6 +145,7 @@ export const Graph = {
|
|
|
146
145
|
`="${SHEET_NAME}"!A5`,
|
|
147
146
|
'```',
|
|
148
147
|
'',
|
|
148
|
+
'',
|
|
149
149
|
),
|
|
150
150
|
},
|
|
151
151
|
};
|
|
@@ -14,85 +14,134 @@ import {
|
|
|
14
14
|
} from '@codemirror/state';
|
|
15
15
|
import { Decoration, EditorView, ViewPlugin, WidgetType } from '@codemirror/view';
|
|
16
16
|
|
|
17
|
-
import { type
|
|
17
|
+
import { type UnsubscribeCallback, debounce } from '@dxos/async';
|
|
18
|
+
import { invariant } from '@dxos/invariant';
|
|
19
|
+
import { documentId, singleValueFacet } from '@dxos/react-ui-editor/state';
|
|
20
|
+
|
|
21
|
+
import { type CellAddress } from '../defs';
|
|
22
|
+
import { type ComputeGraph, type ComputeNode, createSheetName } from '../graph';
|
|
23
|
+
import { type CellScalarValue } from '../types';
|
|
18
24
|
|
|
19
25
|
const LANGUAGE_TAG = 'dx';
|
|
20
26
|
|
|
21
27
|
// TODO(burdon): Create marker just for our decorator?
|
|
22
28
|
const updateAllDecorations = StateEffect.define<void>();
|
|
23
29
|
|
|
30
|
+
export const computeGraphFacet = singleValueFacet<ComputeGraph>();
|
|
31
|
+
|
|
24
32
|
export type ComputeOptions = {};
|
|
25
33
|
|
|
26
|
-
export const compute = (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
34
|
+
export const compute = (options: ComputeOptions = {}): Extension => {
|
|
35
|
+
let computeNode: ComputeNode | undefined;
|
|
36
|
+
|
|
37
|
+
const update = (state: EditorState, current?: RangeSet<Decoration>) => {
|
|
38
|
+
const builder = new RangeSetBuilder<Decoration>();
|
|
39
|
+
if (computeNode) {
|
|
40
|
+
computeNode.clear();
|
|
41
|
+
syntaxTree(state).iterate({
|
|
42
|
+
enter: (node) => {
|
|
43
|
+
switch (node.name) {
|
|
44
|
+
case 'FencedCode': {
|
|
45
|
+
const cursor = state.selection.main.head;
|
|
46
|
+
if (state.readOnly || cursor < node.from || cursor > node.to) {
|
|
47
|
+
const info = node.node.getChild('CodeInfo');
|
|
48
|
+
if (info) {
|
|
49
|
+
const type = state.sliceDoc(info.from, info.to);
|
|
50
|
+
const text = node.node.getChild('CodeText');
|
|
51
|
+
if (type === LANGUAGE_TAG && text) {
|
|
52
|
+
const formula = state.sliceDoc(text.from, text.to);
|
|
53
|
+
|
|
54
|
+
const iter = current?.iter(node.node.from);
|
|
55
|
+
if (iter?.value && iter?.value.spec.formula === formula) {
|
|
56
|
+
// Add existing widget.
|
|
57
|
+
builder.add(node.from, node.to, iter.value);
|
|
58
|
+
} else {
|
|
59
|
+
// TODO(burdon): Create ordered list of cells on each decoration run.
|
|
60
|
+
const cell: CellAddress = { col: node.node.from, row: 0 };
|
|
61
|
+
invariant(computeNode);
|
|
62
|
+
// NOTE: This triggers re-render (below).
|
|
63
|
+
computeNode.setValue(cell, formula);
|
|
64
|
+
const value = computeNode.getValue(cell);
|
|
65
|
+
builder.add(
|
|
66
|
+
node.from,
|
|
67
|
+
node.to,
|
|
68
|
+
Decoration.replace({
|
|
69
|
+
widget: new ComputeWidget(formula, value),
|
|
70
|
+
formula,
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
50
76
|
}
|
|
77
|
+
|
|
78
|
+
break;
|
|
51
79
|
}
|
|
52
80
|
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
56
84
|
|
|
57
85
|
return builder.finish();
|
|
58
86
|
};
|
|
59
87
|
|
|
60
88
|
return [
|
|
61
|
-
// Graph subscription.
|
|
62
89
|
ViewPlugin.fromClass(
|
|
63
90
|
class {
|
|
64
|
-
|
|
91
|
+
// Graph subscription.
|
|
92
|
+
private _subscription?: UnsubscribeCallback;
|
|
65
93
|
constructor(view: EditorView) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
94
|
+
const id = view.state.facet(documentId);
|
|
95
|
+
const computeGraph = view.state.facet(computeGraphFacet);
|
|
96
|
+
if (id && computeGraph) {
|
|
97
|
+
queueMicrotask(async () => {
|
|
98
|
+
computeNode = computeGraph.getOrCreateNode(createSheetName({ type: '', id }));
|
|
99
|
+
await computeNode.open();
|
|
100
|
+
|
|
101
|
+
// Trigger re-render if values updated.
|
|
102
|
+
// TODO(burdon): Trigger only if formula value updated (currently triggered during render).
|
|
103
|
+
this._subscription = computeNode.update.on(
|
|
104
|
+
debounce(({ type, ...rest }) => {
|
|
105
|
+
if (type === 'valuesUpdated') {
|
|
106
|
+
view.dispatch({
|
|
107
|
+
effects: updateAllDecorations.of(),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}, 250),
|
|
111
|
+
);
|
|
69
112
|
});
|
|
70
|
-
}
|
|
113
|
+
}
|
|
71
114
|
}
|
|
72
115
|
|
|
73
116
|
destroy() {
|
|
74
|
-
this._subscription();
|
|
117
|
+
this._subscription?.();
|
|
118
|
+
void computeNode?.close();
|
|
119
|
+
computeNode = undefined;
|
|
75
120
|
}
|
|
76
121
|
},
|
|
77
122
|
),
|
|
78
123
|
|
|
79
|
-
|
|
80
|
-
StateField.define<RangeSet<any>>({
|
|
124
|
+
StateField.define<RangeSet<Decoration>>({
|
|
81
125
|
create: (state) => update(state),
|
|
82
|
-
update: (
|
|
126
|
+
update: (rangeSet: RangeSet<Decoration>, tr: Transaction) => update(tr.state, rangeSet),
|
|
83
127
|
provide: (field) => EditorView.decorations.from(field),
|
|
84
128
|
}),
|
|
85
129
|
];
|
|
86
130
|
};
|
|
87
131
|
|
|
88
|
-
|
|
89
|
-
|
|
132
|
+
// TODO(burdon): Click to edit.
|
|
133
|
+
class ComputeWidget extends WidgetType {
|
|
134
|
+
constructor(
|
|
135
|
+
private readonly formula: string,
|
|
136
|
+
private readonly value: CellScalarValue,
|
|
137
|
+
) {
|
|
90
138
|
super();
|
|
91
139
|
}
|
|
92
140
|
|
|
93
|
-
override toDOM(
|
|
141
|
+
override toDOM(_view: EditorView) {
|
|
94
142
|
const div = document.createElement('div');
|
|
95
|
-
div.
|
|
143
|
+
div.setAttribute('title', this.formula);
|
|
144
|
+
div.innerText = String(this.value);
|
|
96
145
|
return div;
|
|
97
146
|
}
|
|
98
147
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type { FunctionPluginDefinition } from 'hyperformula';
|
|
6
|
+
import type { ConfigParams } from 'hyperformula/typings/ConfigParams';
|
|
7
|
+
import type { FunctionTranslationsPackage } from 'hyperformula/typings/interpreter';
|
|
8
|
+
import defaultsDeep from 'lodash.defaultsdeep';
|
|
9
|
+
|
|
10
|
+
import { type SpaceId, type Space } from '@dxos/client/echo';
|
|
11
|
+
import { Resource } from '@dxos/context';
|
|
12
|
+
import { invariant } from '@dxos/invariant';
|
|
13
|
+
import { log } from '@dxos/log';
|
|
14
|
+
|
|
15
|
+
import { HyperFormula } from '#hyperformula';
|
|
16
|
+
import { ComputeGraph } from './compute-graph';
|
|
17
|
+
import { EdgeFunctionPlugin, EdgeFunctionPluginTranslations, type FunctionContextOptions } from './functions';
|
|
18
|
+
|
|
19
|
+
export type ComputeGraphPlugin = {
|
|
20
|
+
plugin: FunctionPluginDefinition;
|
|
21
|
+
translations: FunctionTranslationsPackage;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type ComputeGraphOptions = {
|
|
25
|
+
plugins?: ComputeGraphPlugin[];
|
|
26
|
+
} & Partial<FunctionContextOptions> &
|
|
27
|
+
Partial<ConfigParams>;
|
|
28
|
+
|
|
29
|
+
export const defaultOptions: ComputeGraphOptions = {
|
|
30
|
+
licenseKey: 'gpl-v3',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const defaultPlugins: ComputeGraphPlugin[] = [
|
|
34
|
+
{
|
|
35
|
+
plugin: EdgeFunctionPlugin,
|
|
36
|
+
translations: EdgeFunctionPluginTranslations,
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Manages a collection of ComputeGraph instances for each space.
|
|
42
|
+
*
|
|
43
|
+
* [ComputePlugin] => [ComputeGraphRegistry] => [ComputeGraph(Space)] => [ComputeNode(Object)]
|
|
44
|
+
*
|
|
45
|
+
* NOTE: The ComputeGraphRegistry manages the hierarchy of resources via its root Context.
|
|
46
|
+
* NOTE: The package.json file defines the packaged #hyperformula module.
|
|
47
|
+
*/
|
|
48
|
+
// TODO(burdon): Move graph into separate plugin; isolate HF deps.
|
|
49
|
+
export class ComputeGraphRegistry extends Resource {
|
|
50
|
+
private readonly _graphs = new Map<SpaceId, ComputeGraph>();
|
|
51
|
+
|
|
52
|
+
private readonly _options: ComputeGraphOptions;
|
|
53
|
+
|
|
54
|
+
constructor(options: ComputeGraphOptions = { plugins: defaultPlugins }) {
|
|
55
|
+
super();
|
|
56
|
+
this._options = defaultsDeep({}, options, defaultOptions);
|
|
57
|
+
this._options.plugins?.forEach(({ plugin, translations }) => {
|
|
58
|
+
HyperFormula.registerFunctionPlugin(plugin, translations);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getGraph(spaceId: SpaceId): ComputeGraph | undefined {
|
|
63
|
+
return this._graphs.get(spaceId);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getOrCreateGraph(space: Space): ComputeGraph {
|
|
67
|
+
let graph = this._graphs.get(space.id);
|
|
68
|
+
if (!graph) {
|
|
69
|
+
log('create graph', { space: space.id });
|
|
70
|
+
graph = this.createGraph(space);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return graph;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
createGraph(space: Space): ComputeGraph {
|
|
77
|
+
invariant(!this._graphs.has(space.id), `ComputeGraph already exists for space: ${space.id}`);
|
|
78
|
+
const hf = HyperFormula.buildEmpty(this._options);
|
|
79
|
+
const graph = new ComputeGraph(hf, space, this._options);
|
|
80
|
+
this._graphs.set(space.id, graph);
|
|
81
|
+
return graph;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
protected override async _close() {
|
|
85
|
+
for (const graph of this._graphs.values()) {
|
|
86
|
+
await graph.close();
|
|
87
|
+
}
|
|
88
|
+
this._graphs.clear();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -13,9 +13,10 @@ import { Toolbar, Button, Input } from '@dxos/react-ui';
|
|
|
13
13
|
import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
14
14
|
import { withTheme } from '@dxos/storybook-utils';
|
|
15
15
|
|
|
16
|
+
import { testFunctionPlugins } from './testing';
|
|
16
17
|
import { createSheet } from '../defs';
|
|
17
18
|
import { useComputeGraph, useSheetModel } from '../hooks';
|
|
18
|
-
import {
|
|
19
|
+
import { withComputeGraphDecorator } from '../testing';
|
|
19
20
|
import { SheetType } from '../types';
|
|
20
21
|
|
|
21
22
|
const FUNCTION_NAME = 'TEST';
|
|
@@ -26,7 +27,7 @@ const Story = () => {
|
|
|
26
27
|
const [sheet, setSheet] = useState<SheetType>();
|
|
27
28
|
const [text, setText] = useState(`${FUNCTION_NAME}(100)`);
|
|
28
29
|
const [result, setResult] = useState<any>();
|
|
29
|
-
const model = useSheetModel(
|
|
30
|
+
const model = useSheetModel(graph, sheet);
|
|
30
31
|
useEffect(() => {
|
|
31
32
|
if (space) {
|
|
32
33
|
const sheet = space.db.add(createSheet());
|
|
@@ -83,7 +84,7 @@ export default {
|
|
|
83
84
|
title: 'plugin-sheet/functions',
|
|
84
85
|
decorators: [
|
|
85
86
|
withClientProvider({ types: [FunctionType, SheetType], createIdentity: true, createSpace: true }),
|
|
86
|
-
|
|
87
|
+
withComputeGraphDecorator({ plugins: testFunctionPlugins }),
|
|
87
88
|
withTheme,
|
|
88
89
|
],
|
|
89
90
|
render: (args: any) => <Story {...args} />,
|