@livestore/livestore 0.0.46-dev.2 → 0.0.46-dev.4
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/.tsbuildinfo +1 -1
- package/dist/__tests__/react/fixture.d.ts +1 -1
- package/dist/react/components/DiffableList.d.ts +20 -0
- package/dist/react/components/DiffableList.d.ts.map +1 -0
- package/dist/react/components/DiffableList.js +113 -0
- package/dist/react/components/DiffableList.js.map +1 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/package.json +7 -5
- package/src/react/components/DiffableList.tsx +192 -0
- package/src/react/index.ts +2 -0
- package/tsconfig.json +7 -1
|
@@ -204,8 +204,8 @@ export declare const schema: import("@livestore/common/dist/schema/index.js").Li
|
|
|
204
204
|
}, never>;
|
|
205
205
|
export declare const parseTodos: (rawRows: readonly any[]) => readonly {
|
|
206
206
|
text: string;
|
|
207
|
-
id: string;
|
|
208
207
|
completed: boolean;
|
|
208
|
+
id: string;
|
|
209
209
|
}[];
|
|
210
210
|
export declare const makeTodoMvc: ({ otelTracer, otelContext, useGlobalDbGraph, }?: {
|
|
211
211
|
otelTracer?: otel.Tracer | undefined;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { LiveQuery } from '../../reactiveQueries/base-class.js';
|
|
3
|
+
export type Props<TItem> = {
|
|
4
|
+
items$: LiveQuery<ReadonlyArray<TItem>>;
|
|
5
|
+
/**
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* renderContainer={(children) => <ul>{children}</ul>}
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
renderContainer: (ref: React.LegacyRef<any>) => React.ReactNode;
|
|
12
|
+
renderItem: (item: TItem, opts: {
|
|
13
|
+
index: number;
|
|
14
|
+
isInitialListRender: boolean;
|
|
15
|
+
}) => React.ReactNode;
|
|
16
|
+
getKey: (item: TItem, index: number) => string | number;
|
|
17
|
+
};
|
|
18
|
+
export declare const DiffableList_: <TItem>({ items$, renderContainer, renderItem, getKey, }: Props<TItem>) => React.ReactNode;
|
|
19
|
+
export declare const DiffableList: <TItem>({ items$, renderContainer, renderItem, getKey, }: Props<TItem>) => React.ReactNode;
|
|
20
|
+
//# sourceMappingURL=DiffableList.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DiffableList.d.ts","sourceRoot":"","sources":["../../../src/react/components/DiffableList.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qCAAqC,CAAA;AAIpE,MAAM,MAAM,KAAK,CAAC,KAAK,IAAI;IACzB,MAAM,EAAE,SAAS,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAA;IACvC;;;;;OAKG;IACH,eAAe,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,SAAS,CAAA;IAE/D,UAAU,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,OAAO,CAAA;KAAE,KAAK,KAAK,CAAC,SAAS,CAAA;IACnG,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAA;CACxD,CAAA;AAED,eAAO,MAAM,aAAa,4DAKvB,MAAM,KAAK,CAAC,KAAG,MAAM,SAyIvB,CAAA;AAED,eAAO,MAAM,YAAY,4DAKtB,MAAM,KAAK,CAAC,KAAG,MAAM,SAIvB,CAAA"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { FI } from '@livestore/fractional-index';
|
|
2
|
+
import { casesHandled } from '@livestore/utils';
|
|
3
|
+
import * as itsFine from 'its-fine';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import ReactDOM from 'react-dom/client';
|
|
6
|
+
import { computed } from '../../reactiveQueries/js.js';
|
|
7
|
+
import { useQuery } from '../useQuery.js';
|
|
8
|
+
export const DiffableList_ = ({ items$, renderContainer, renderItem, getKey, }) => {
|
|
9
|
+
const ref = React.useRef(null);
|
|
10
|
+
const container = renderContainer(ref);
|
|
11
|
+
const [hasMounted, setHasMounted] = React.useState(false);
|
|
12
|
+
React.useEffect(() => setHasMounted(true), []);
|
|
13
|
+
const keys$ = computed((get) => get(items$).map(getKey));
|
|
14
|
+
const elsRef = React.useRef([]);
|
|
15
|
+
const ContextBridge = itsFine.useContextBridge();
|
|
16
|
+
const renderListEl = React.useCallback((parentEl, index, item$) => {
|
|
17
|
+
const root = ReactDOM.createRoot(parentEl);
|
|
18
|
+
root.render(React.createElement(ContextBridge, null,
|
|
19
|
+
React.createElement(ItemWrapper, { "item$": item$, renderItem: renderItem, opts: { index, isInitialListRender: !hasMounted } })));
|
|
20
|
+
return root;
|
|
21
|
+
}, [ContextBridge, hasMounted, renderItem]);
|
|
22
|
+
React.useLayoutEffect(() => {
|
|
23
|
+
if (ref.current === null) {
|
|
24
|
+
throw new Error('ref.current is null');
|
|
25
|
+
}
|
|
26
|
+
const keys = keys$.run();
|
|
27
|
+
for (let index = 0; index < keys.length; index++) {
|
|
28
|
+
const parentEl = document.createElement('div');
|
|
29
|
+
ref.current.append(parentEl);
|
|
30
|
+
const item$ = computed((get) => get(items$)[index]);
|
|
31
|
+
const root = renderListEl(parentEl, index, item$);
|
|
32
|
+
elsRef.current.push({ el: parentEl, item$, root, id: keys[index] });
|
|
33
|
+
}
|
|
34
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
35
|
+
}, []);
|
|
36
|
+
React.useEffect(() => () => keys$.destroy(), [keys$]);
|
|
37
|
+
React.useEffect(() => {
|
|
38
|
+
// const keys = keys$.run()
|
|
39
|
+
return keys$.subscribe((keys) => {
|
|
40
|
+
const prevKeys = elsRef.current.map((el) => el.id);
|
|
41
|
+
let arrayIsEqual = true;
|
|
42
|
+
for (let i = 0; i < keys.length; i++) {
|
|
43
|
+
if (keys[i] !== prevKeys[i]) {
|
|
44
|
+
arrayIsEqual = false;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (arrayIsEqual)
|
|
49
|
+
return;
|
|
50
|
+
const previousAgg = FI.aggregateMake(prevKeys, FI.fractionalIndexImplNumber);
|
|
51
|
+
const { newEvents } = FI.getNewEvents(previousAgg, keys, FI.fractionalIndexImplNumber);
|
|
52
|
+
console.log('newEvents', newEvents);
|
|
53
|
+
for (const event of newEvents) {
|
|
54
|
+
switch (event.op) {
|
|
55
|
+
case 'remove': {
|
|
56
|
+
const { index } = event;
|
|
57
|
+
const el = elsRef.current[index];
|
|
58
|
+
el.root.unmount();
|
|
59
|
+
el.el.remove();
|
|
60
|
+
el.item$.destroy();
|
|
61
|
+
elsRef.current.splice(index, 1);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case 'add': {
|
|
65
|
+
const { index } = event;
|
|
66
|
+
const parentEl = document.createElement('div');
|
|
67
|
+
ref.current.append(parentEl);
|
|
68
|
+
const item$ = computed((get) => get(items$)[index]);
|
|
69
|
+
const root = renderListEl(parentEl, index, item$);
|
|
70
|
+
elsRef.current.splice(index, 0, { el: parentEl, item$, root, id: keys[index] });
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case 'move': {
|
|
74
|
+
// const { newIndex, previousIndex } = event
|
|
75
|
+
// const el = elsRef.current[previousIndex]!
|
|
76
|
+
// const item$ = el.item$
|
|
77
|
+
// const root = el.root
|
|
78
|
+
// const elEl = el.el
|
|
79
|
+
// elsRef.current.splice(previousIndex, 1)
|
|
80
|
+
// elsRef.current.splice(newIndex, 0, { el: elEl, item$, root })
|
|
81
|
+
// ref.current!.insertBefore(elEl, elsRef.current[newIndex + 1]?.el)
|
|
82
|
+
// // move dom element
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
default: {
|
|
86
|
+
casesHandled(event);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
// for (let index = 0; index < keys.length; index++) {
|
|
92
|
+
// if (prevKeys[index] === keys[index]) continue
|
|
93
|
+
// // check if `keys[index]` === `prevKeys[index + 1]`
|
|
94
|
+
// // which probably means that
|
|
95
|
+
// if (keys[index] === prevKeys[index + 1]) {
|
|
96
|
+
// // sp
|
|
97
|
+
// }
|
|
98
|
+
// prevKeys[index] = keys[index] as any
|
|
99
|
+
// }
|
|
100
|
+
// TODO in the future use a more efficient diffing algorithm that re-uses elements more optimally
|
|
101
|
+
// right now we're only looking one step ahead
|
|
102
|
+
// reconcile until `keys` and `prevKeys` are equal
|
|
103
|
+
// prevKeys = keys
|
|
104
|
+
}, [items$, keys$, renderListEl]);
|
|
105
|
+
return React.createElement(React.Fragment, null, container);
|
|
106
|
+
};
|
|
107
|
+
export const DiffableList = ({ items$, renderContainer, renderItem, getKey, }) => (React.createElement(itsFine.FiberProvider, null,
|
|
108
|
+
React.createElement(DiffableList_, { "items$": items$, renderContainer: renderContainer, renderItem: renderItem, getKey: getKey })));
|
|
109
|
+
const ItemWrapper = ({ item$, opts, renderItem, }) => {
|
|
110
|
+
const item = useQuery(item$);
|
|
111
|
+
return React.createElement(React.Fragment, null, renderItem(item, opts));
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=DiffableList.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DiffableList.js","sourceRoot":"","sources":["../../../src/react/components/DiffableList.tsx"],"names":[],"mappings":"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"}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -5,5 +5,6 @@ export { useTemporaryQuery } from './useTemporaryQuery.js';
|
|
|
5
5
|
export { useStackInfo } from './utils/stack-info.js';
|
|
6
6
|
export { useRow, type StateSetters, type SetStateAction, type Dispatch, type UseRowResult as UseStateResult, } from './useRow.js';
|
|
7
7
|
export { useAtom } from './useAtom.js';
|
|
8
|
+
export { DiffableList } from './components/DiffableList.js';
|
|
8
9
|
export type { TypedDocumentNode } from '@graphql-typed-document-node/core';
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EACL,MAAM,EACN,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,YAAY,IAAI,cAAc,GACpC,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EACL,MAAM,EACN,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,YAAY,IAAI,cAAc,GACpC,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAG3D,YAAY,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA"}
|
package/dist/react/index.js
CHANGED
|
@@ -5,4 +5,5 @@ export { useTemporaryQuery } from './useTemporaryQuery.js';
|
|
|
5
5
|
export { useStackInfo } from './utils/stack-info.js';
|
|
6
6
|
export { useRow, } from './useRow.js';
|
|
7
7
|
export { useAtom } from './useAtom.js';
|
|
8
|
+
export { DiffableList } from './components/DiffableList.js';
|
|
8
9
|
//# sourceMappingURL=index.js.map
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EACL,MAAM,GAKP,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EACL,MAAM,GAKP,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livestore/livestore",
|
|
3
|
-
"version": "0.0.46-dev.
|
|
3
|
+
"version": "0.0.46-dev.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -37,10 +37,12 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@graphql-typed-document-node/core": "^3.2.0",
|
|
39
39
|
"@opentelemetry/api": "^1.8.0",
|
|
40
|
+
"its-fine": "^1.1.2",
|
|
40
41
|
"lodash-es": "^4.17.21",
|
|
41
|
-
"@livestore/common": "0.0.46-dev.
|
|
42
|
-
"
|
|
43
|
-
"
|
|
42
|
+
"@livestore/common": "0.0.46-dev.4",
|
|
43
|
+
"effect-db-schema": "0.0.46-dev.4",
|
|
44
|
+
"@livestore/utils": "0.0.46-dev.4",
|
|
45
|
+
"@livestore/fractional-index": "0.0.46-dev.4"
|
|
44
46
|
},
|
|
45
47
|
"devDependencies": {
|
|
46
48
|
"@opentelemetry/sdk-trace-base": "1.22.0",
|
|
@@ -55,7 +57,7 @@
|
|
|
55
57
|
"typescript": "5.4.2",
|
|
56
58
|
"vite": "5.1.6",
|
|
57
59
|
"vitest": "^1.3.1",
|
|
58
|
-
"@livestore/web": "0.0.46-dev.
|
|
60
|
+
"@livestore/web": "0.0.46-dev.4"
|
|
59
61
|
},
|
|
60
62
|
"peerDependencies": {
|
|
61
63
|
"@tauri-apps/api": "^1.4.0",
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { FI } from '@livestore/fractional-index'
|
|
2
|
+
import { casesHandled } from '@livestore/utils'
|
|
3
|
+
import * as itsFine from 'its-fine'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import ReactDOM from 'react-dom/client'
|
|
6
|
+
|
|
7
|
+
import type { LiveQuery } from '../../reactiveQueries/base-class.js'
|
|
8
|
+
import { computed } from '../../reactiveQueries/js.js'
|
|
9
|
+
import { useQuery } from '../useQuery.js'
|
|
10
|
+
|
|
11
|
+
export type Props<TItem> = {
|
|
12
|
+
items$: LiveQuery<ReadonlyArray<TItem>>
|
|
13
|
+
/**
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* renderContainer={(children) => <ul>{children}</ul>}
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
renderContainer: (ref: React.LegacyRef<any>) => React.ReactNode
|
|
20
|
+
// TODO refactor render-flag to allow for transition animations on add/remove
|
|
21
|
+
renderItem: (item: TItem, opts: { index: number; isInitialListRender: boolean }) => React.ReactNode
|
|
22
|
+
getKey: (item: TItem, index: number) => string | number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const DiffableList_ = <TItem,>({
|
|
26
|
+
items$,
|
|
27
|
+
renderContainer,
|
|
28
|
+
renderItem,
|
|
29
|
+
getKey,
|
|
30
|
+
}: Props<TItem>): React.ReactNode => {
|
|
31
|
+
const ref = React.useRef<HTMLElement>(null)
|
|
32
|
+
const container = renderContainer(ref)
|
|
33
|
+
|
|
34
|
+
const [hasMounted, setHasMounted] = React.useState(false)
|
|
35
|
+
|
|
36
|
+
React.useEffect(() => setHasMounted(true), [])
|
|
37
|
+
|
|
38
|
+
const keys$ = computed((get) => get(items$).map(getKey))
|
|
39
|
+
type RefEl = {
|
|
40
|
+
id: string | number
|
|
41
|
+
el: HTMLElement
|
|
42
|
+
item$: LiveQuery<TItem>
|
|
43
|
+
root: ReactDOM.Root
|
|
44
|
+
}
|
|
45
|
+
const elsRef = React.useRef<RefEl[]>([])
|
|
46
|
+
|
|
47
|
+
const ContextBridge = itsFine.useContextBridge()
|
|
48
|
+
|
|
49
|
+
const renderListEl = React.useCallback(
|
|
50
|
+
(parentEl: HTMLElement, index: number, item$: LiveQuery<TItem>) => {
|
|
51
|
+
const root = ReactDOM.createRoot(parentEl)
|
|
52
|
+
root.render(
|
|
53
|
+
<ContextBridge>
|
|
54
|
+
<ItemWrapper item$={item$} renderItem={renderItem} opts={{ index, isInitialListRender: !hasMounted }} />
|
|
55
|
+
</ContextBridge>,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return root
|
|
59
|
+
},
|
|
60
|
+
[ContextBridge, hasMounted, renderItem],
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
React.useLayoutEffect(() => {
|
|
64
|
+
if (ref.current === null) {
|
|
65
|
+
throw new Error('ref.current is null')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const keys = keys$.run()
|
|
69
|
+
|
|
70
|
+
for (let index = 0; index < keys.length; index++) {
|
|
71
|
+
const parentEl = document.createElement('div')
|
|
72
|
+
ref.current!.append(parentEl)
|
|
73
|
+
const item$ = computed((get) => get(items$)[index]!) as LiveQuery<TItem>
|
|
74
|
+
const root = renderListEl(parentEl, index, item$)
|
|
75
|
+
elsRef.current.push({ el: parentEl, item$, root, id: keys[index]! })
|
|
76
|
+
}
|
|
77
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
78
|
+
}, [])
|
|
79
|
+
|
|
80
|
+
React.useEffect(() => () => keys$.destroy(), [keys$])
|
|
81
|
+
|
|
82
|
+
React.useEffect(() => {
|
|
83
|
+
// const keys = keys$.run()
|
|
84
|
+
|
|
85
|
+
return keys$.subscribe((keys) => {
|
|
86
|
+
const prevKeys = elsRef.current.map((el) => el.id)
|
|
87
|
+
|
|
88
|
+
let arrayIsEqual = true
|
|
89
|
+
for (let i = 0; i < keys.length; i++) {
|
|
90
|
+
if (keys[i] !== prevKeys[i]) {
|
|
91
|
+
arrayIsEqual = false
|
|
92
|
+
break
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (arrayIsEqual) return
|
|
96
|
+
|
|
97
|
+
const previousAgg = FI.aggregateMake(prevKeys, FI.fractionalIndexImplNumber)
|
|
98
|
+
const { newEvents } = FI.getNewEvents(previousAgg, keys, FI.fractionalIndexImplNumber)
|
|
99
|
+
|
|
100
|
+
console.log('newEvents', newEvents)
|
|
101
|
+
|
|
102
|
+
for (const event of newEvents) {
|
|
103
|
+
switch (event.op) {
|
|
104
|
+
case 'remove': {
|
|
105
|
+
const { index } = event
|
|
106
|
+
const el = elsRef.current[index]!
|
|
107
|
+
el.root.unmount()
|
|
108
|
+
el.el.remove()
|
|
109
|
+
el.item$.destroy()
|
|
110
|
+
elsRef.current.splice(index, 1)
|
|
111
|
+
break
|
|
112
|
+
}
|
|
113
|
+
case 'add': {
|
|
114
|
+
const { index } = event
|
|
115
|
+
const parentEl = document.createElement('div')
|
|
116
|
+
ref.current!.append(parentEl)
|
|
117
|
+
const item$ = computed((get) => get(items$)[index]!) as LiveQuery<TItem>
|
|
118
|
+
const root = renderListEl(parentEl, index, item$)
|
|
119
|
+
elsRef.current.splice(index, 0, { el: parentEl, item$, root, id: keys[index]! })
|
|
120
|
+
break
|
|
121
|
+
}
|
|
122
|
+
case 'move': {
|
|
123
|
+
// const { newIndex, previousIndex } = event
|
|
124
|
+
|
|
125
|
+
// const el = elsRef.current[previousIndex]!
|
|
126
|
+
// const item$ = el.item$
|
|
127
|
+
// const root = el.root
|
|
128
|
+
// const elEl = el.el
|
|
129
|
+
|
|
130
|
+
// elsRef.current.splice(previousIndex, 1)
|
|
131
|
+
// elsRef.current.splice(newIndex, 0, { el: elEl, item$, root })
|
|
132
|
+
|
|
133
|
+
// ref.current!.insertBefore(elEl, elsRef.current[newIndex + 1]?.el)
|
|
134
|
+
|
|
135
|
+
// // move dom element
|
|
136
|
+
|
|
137
|
+
break
|
|
138
|
+
}
|
|
139
|
+
default: {
|
|
140
|
+
casesHandled(event)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// for (let index = 0; index < keys.length; index++) {
|
|
147
|
+
// if (prevKeys[index] === keys[index]) continue
|
|
148
|
+
|
|
149
|
+
// // check if `keys[index]` === `prevKeys[index + 1]`
|
|
150
|
+
// // which probably means that
|
|
151
|
+
// if (keys[index] === prevKeys[index + 1]) {
|
|
152
|
+
// // sp
|
|
153
|
+
// }
|
|
154
|
+
|
|
155
|
+
// prevKeys[index] = keys[index] as any
|
|
156
|
+
// }
|
|
157
|
+
|
|
158
|
+
// TODO in the future use a more efficient diffing algorithm that re-uses elements more optimally
|
|
159
|
+
// right now we're only looking one step ahead
|
|
160
|
+
|
|
161
|
+
// reconcile until `keys` and `prevKeys` are equal
|
|
162
|
+
|
|
163
|
+
// prevKeys = keys
|
|
164
|
+
}, [items$, keys$, renderListEl])
|
|
165
|
+
|
|
166
|
+
return <>{container}</>
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export const DiffableList = <TItem,>({
|
|
170
|
+
items$,
|
|
171
|
+
renderContainer,
|
|
172
|
+
renderItem,
|
|
173
|
+
getKey,
|
|
174
|
+
}: Props<TItem>): React.ReactNode => (
|
|
175
|
+
<itsFine.FiberProvider>
|
|
176
|
+
<DiffableList_ items$={items$} renderContainer={renderContainer} renderItem={renderItem} getKey={getKey} />
|
|
177
|
+
</itsFine.FiberProvider>
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
const ItemWrapper = <TItem,>({
|
|
181
|
+
item$,
|
|
182
|
+
opts,
|
|
183
|
+
renderItem,
|
|
184
|
+
}: {
|
|
185
|
+
item$: LiveQuery<TItem>
|
|
186
|
+
opts: { index: number; isInitialListRender: boolean }
|
|
187
|
+
renderItem: (item: TItem, opts: { index: number; isInitialListRender: boolean }) => React.ReactNode
|
|
188
|
+
}) => {
|
|
189
|
+
const item = useQuery(item$)
|
|
190
|
+
|
|
191
|
+
return <>{renderItem(item, opts)}</>
|
|
192
|
+
}
|
package/src/react/index.ts
CHANGED
package/tsconfig.json
CHANGED
|
@@ -9,5 +9,11 @@
|
|
|
9
9
|
"tsBuildInfoFile": "./dist/.tsbuildinfo"
|
|
10
10
|
},
|
|
11
11
|
"include": ["./src"],
|
|
12
|
-
"references": [
|
|
12
|
+
"references": [
|
|
13
|
+
{ "path": "../../effect-db-schema" },
|
|
14
|
+
{ "path": "../common" },
|
|
15
|
+
{ "path": "../fractional-index" },
|
|
16
|
+
{ "path": "../web" },
|
|
17
|
+
{ "path": "../utils" }
|
|
18
|
+
]
|
|
13
19
|
}
|