@furystack/shades-common-components 13.4.0 → 13.5.0
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/CHANGELOG.md +42 -0
- package/esm/components/alert.d.ts.map +1 -1
- package/esm/components/alert.js +7 -6
- package/esm/components/alert.js.map +1 -1
- package/esm/components/cache-view.spec.js +171 -170
- package/esm/components/cache-view.spec.js.map +1 -1
- package/esm/components/command-palette/command-palette-suggestion-list.js +1 -1
- package/esm/components/command-palette/command-palette-suggestion-list.js.map +1 -1
- package/esm/components/inputs/input.d.ts.map +1 -1
- package/esm/components/inputs/input.js +2 -0
- package/esm/components/inputs/input.js.map +1 -1
- package/esm/components/inputs/select.d.ts.map +1 -1
- package/esm/components/inputs/select.js +3 -1
- package/esm/components/inputs/select.js.map +1 -1
- package/esm/components/page-container/index.d.ts.map +1 -1
- package/esm/components/page-container/index.js +1 -1
- package/esm/components/page-container/index.js.map +1 -1
- package/esm/components/page-container/index.spec.js +5 -4
- package/esm/components/page-container/index.spec.js.map +1 -1
- package/esm/components/page-layout/index.d.ts +6 -0
- package/esm/components/page-layout/index.d.ts.map +1 -1
- package/esm/components/page-layout/index.js +28 -13
- package/esm/components/page-layout/index.js.map +1 -1
- package/esm/components/page-layout/index.spec.js +119 -0
- package/esm/components/page-layout/index.spec.js.map +1 -1
- package/esm/components/result.d.ts +4 -2
- package/esm/components/result.d.ts.map +1 -1
- package/esm/components/result.js +11 -10
- package/esm/components/result.js.map +1 -1
- package/esm/components/suggest/index.d.ts.map +1 -1
- package/esm/components/suggest/index.js +7 -3
- package/esm/components/suggest/index.js.map +1 -1
- package/esm/components/tree/tree.d.ts.map +1 -1
- package/esm/components/tree/tree.js +1 -0
- package/esm/components/tree/tree.js.map +1 -1
- package/package.json +6 -6
- package/src/components/alert.tsx +9 -6
- package/src/components/cache-view.spec.tsx +235 -219
- package/src/components/command-palette/command-palette-suggestion-list.tsx +1 -1
- package/src/components/inputs/input.tsx +2 -0
- package/src/components/inputs/select.tsx +3 -1
- package/src/components/page-container/index.spec.tsx +5 -4
- package/src/components/page-container/index.tsx +7 -1
- package/src/components/page-layout/index.spec.tsx +173 -0
- package/src/components/page-layout/index.tsx +35 -13
- package/src/components/result.tsx +17 -10
- package/src/components/suggest/index.tsx +18 -15
- package/src/components/tree/tree.tsx +1 -0
|
@@ -879,4 +879,177 @@ describe('PageLayout component', () => {
|
|
|
879
879
|
})
|
|
880
880
|
})
|
|
881
881
|
})
|
|
882
|
+
|
|
883
|
+
describe('Contained Mode', () => {
|
|
884
|
+
it('should set data-contained attribute on host when contained is true', async () => {
|
|
885
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
886
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
887
|
+
|
|
888
|
+
initializeShadeRoot({
|
|
889
|
+
injector,
|
|
890
|
+
rootElement,
|
|
891
|
+
jsxElement: (
|
|
892
|
+
<PageLayout contained>
|
|
893
|
+
<div>Content</div>
|
|
894
|
+
</PageLayout>
|
|
895
|
+
),
|
|
896
|
+
})
|
|
897
|
+
|
|
898
|
+
await flushUpdates()
|
|
899
|
+
const pageLayout = document.querySelector('shade-page-layout')
|
|
900
|
+
expect(pageLayout?.hasAttribute('data-contained')).toBe(true)
|
|
901
|
+
})
|
|
902
|
+
})
|
|
903
|
+
|
|
904
|
+
it('should not set data-contained attribute when contained is not set', async () => {
|
|
905
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
906
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
907
|
+
|
|
908
|
+
initializeShadeRoot({
|
|
909
|
+
injector,
|
|
910
|
+
rootElement,
|
|
911
|
+
jsxElement: (
|
|
912
|
+
<PageLayout>
|
|
913
|
+
<div>Content</div>
|
|
914
|
+
</PageLayout>
|
|
915
|
+
),
|
|
916
|
+
})
|
|
917
|
+
|
|
918
|
+
await flushUpdates()
|
|
919
|
+
const pageLayout = document.querySelector('shade-page-layout')
|
|
920
|
+
expect(pageLayout?.hasAttribute('data-contained')).toBe(false)
|
|
921
|
+
})
|
|
922
|
+
})
|
|
923
|
+
|
|
924
|
+
it('should have absolute positioning when contained', async () => {
|
|
925
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
926
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
927
|
+
|
|
928
|
+
initializeShadeRoot({
|
|
929
|
+
injector,
|
|
930
|
+
rootElement,
|
|
931
|
+
jsxElement: (
|
|
932
|
+
<PageLayout contained>
|
|
933
|
+
<div>Content</div>
|
|
934
|
+
</PageLayout>
|
|
935
|
+
),
|
|
936
|
+
})
|
|
937
|
+
|
|
938
|
+
await flushUpdates()
|
|
939
|
+
const pageLayout = document.querySelector('shade-page-layout') as HTMLElement
|
|
940
|
+
const computedStyle = window.getComputedStyle(pageLayout)
|
|
941
|
+
expect(computedStyle.position).toBe('absolute')
|
|
942
|
+
})
|
|
943
|
+
})
|
|
944
|
+
|
|
945
|
+
it('should work with AppBar and drawers in contained mode', async () => {
|
|
946
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
947
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
948
|
+
|
|
949
|
+
initializeShadeRoot({
|
|
950
|
+
injector,
|
|
951
|
+
rootElement,
|
|
952
|
+
jsxElement: (
|
|
953
|
+
<PageLayout
|
|
954
|
+
contained
|
|
955
|
+
appBar={{
|
|
956
|
+
variant: 'permanent',
|
|
957
|
+
component: <div>AppBar</div>,
|
|
958
|
+
}}
|
|
959
|
+
drawer={{
|
|
960
|
+
left: {
|
|
961
|
+
variant: 'collapsible',
|
|
962
|
+
component: <div>Left Drawer</div>,
|
|
963
|
+
},
|
|
964
|
+
}}
|
|
965
|
+
>
|
|
966
|
+
<div>Content</div>
|
|
967
|
+
</PageLayout>
|
|
968
|
+
),
|
|
969
|
+
})
|
|
970
|
+
|
|
971
|
+
await flushUpdates()
|
|
972
|
+
|
|
973
|
+
const pageLayout = document.querySelector('shade-page-layout') as HTMLElement & { injector: Injector }
|
|
974
|
+
expect(pageLayout.hasAttribute('data-contained')).toBe(true)
|
|
975
|
+
expect(document.body.innerHTML).toContain('page-layout-appbar')
|
|
976
|
+
expect(document.body.innerHTML).toContain('page-layout-drawer-left')
|
|
977
|
+
expect(document.body.innerHTML).toContain('page-layout-content')
|
|
978
|
+
|
|
979
|
+
const layoutService = pageLayout.injector.getInstance(LayoutService)
|
|
980
|
+
expect(layoutService.drawerState.getValue().left?.open).toBe(true)
|
|
981
|
+
})
|
|
982
|
+
})
|
|
983
|
+
|
|
984
|
+
it('should support drawer toggle in contained mode', async () => {
|
|
985
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
986
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
987
|
+
|
|
988
|
+
initializeShadeRoot({
|
|
989
|
+
injector,
|
|
990
|
+
rootElement,
|
|
991
|
+
jsxElement: (
|
|
992
|
+
<PageLayout
|
|
993
|
+
contained
|
|
994
|
+
drawer={{
|
|
995
|
+
left: {
|
|
996
|
+
variant: 'collapsible',
|
|
997
|
+
component: <div>Left Drawer</div>,
|
|
998
|
+
},
|
|
999
|
+
}}
|
|
1000
|
+
>
|
|
1001
|
+
<div>Content</div>
|
|
1002
|
+
</PageLayout>
|
|
1003
|
+
),
|
|
1004
|
+
})
|
|
1005
|
+
|
|
1006
|
+
await flushUpdates()
|
|
1007
|
+
|
|
1008
|
+
const pageLayout = document.querySelector('shade-page-layout') as HTMLElement & { injector: Injector }
|
|
1009
|
+
const layoutService = pageLayout.injector.getInstance(LayoutService)
|
|
1010
|
+
|
|
1011
|
+
expect(pageLayout.hasAttribute('data-drawer-left-closed')).toBe(false)
|
|
1012
|
+
|
|
1013
|
+
layoutService.setDrawerOpen('left', false)
|
|
1014
|
+
await flushUpdates()
|
|
1015
|
+
|
|
1016
|
+
expect(pageLayout.hasAttribute('data-drawer-left-closed')).toBe(true)
|
|
1017
|
+
})
|
|
1018
|
+
})
|
|
1019
|
+
|
|
1020
|
+
it('should support temporary drawer backdrop click in contained mode', async () => {
|
|
1021
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
1022
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
1023
|
+
|
|
1024
|
+
initializeShadeRoot({
|
|
1025
|
+
injector,
|
|
1026
|
+
rootElement,
|
|
1027
|
+
jsxElement: (
|
|
1028
|
+
<PageLayout
|
|
1029
|
+
contained
|
|
1030
|
+
drawer={{
|
|
1031
|
+
left: {
|
|
1032
|
+
variant: 'temporary',
|
|
1033
|
+
defaultOpen: true,
|
|
1034
|
+
component: <div>Temporary Drawer</div>,
|
|
1035
|
+
},
|
|
1036
|
+
}}
|
|
1037
|
+
>
|
|
1038
|
+
<div>Content</div>
|
|
1039
|
+
</PageLayout>
|
|
1040
|
+
),
|
|
1041
|
+
})
|
|
1042
|
+
|
|
1043
|
+
await flushUpdates()
|
|
1044
|
+
const pageLayout = document.querySelector('shade-page-layout')
|
|
1045
|
+
expect(pageLayout?.hasAttribute('data-backdrop-visible')).toBe(true)
|
|
1046
|
+
|
|
1047
|
+
const backdrop = document.querySelector('.page-layout-drawer-backdrop') as HTMLElement
|
|
1048
|
+
backdrop.click()
|
|
1049
|
+
await flushUpdates()
|
|
1050
|
+
|
|
1051
|
+
expect(pageLayout?.hasAttribute('data-drawer-left-closed')).toBe(true)
|
|
1052
|
+
})
|
|
1053
|
+
})
|
|
1054
|
+
})
|
|
882
1055
|
})
|
|
@@ -46,6 +46,12 @@ export type PageLayoutProps = {
|
|
|
46
46
|
topGap?: string
|
|
47
47
|
/** Gap between the drawers and the content area (CSS value). Default: '0px' */
|
|
48
48
|
sideGap?: string
|
|
49
|
+
/**
|
|
50
|
+
* When true, uses `position: absolute` instead of `position: fixed` so the
|
|
51
|
+
* layout fills its nearest positioned ancestor rather than the viewport.
|
|
52
|
+
* This enables nesting PageLayout instances (e.g. in a showcase grid).
|
|
53
|
+
*/
|
|
54
|
+
contained?: boolean
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
const DEFAULT_APPBAR_HEIGHT = '48px'
|
|
@@ -102,8 +108,8 @@ export const PageLayout = Shade<PageLayoutProps>({
|
|
|
102
108
|
margin: '0',
|
|
103
109
|
},
|
|
104
110
|
|
|
105
|
-
// AppBar container
|
|
106
|
-
'& .page-layout-appbar': {
|
|
111
|
+
// AppBar container (> * > scopes to the wrapper div to prevent bleeding into nested PageLayouts)
|
|
112
|
+
'& > * > .page-layout-appbar': {
|
|
107
113
|
position: 'fixed',
|
|
108
114
|
top: '0',
|
|
109
115
|
left: '0',
|
|
@@ -114,18 +120,18 @@ export const PageLayout = Shade<PageLayoutProps>({
|
|
|
114
120
|
},
|
|
115
121
|
|
|
116
122
|
// Auto-hide AppBar styles (controlled via host data attributes)
|
|
117
|
-
'&[data-appbar-auto-hide] .page-layout-appbar': {
|
|
123
|
+
'&[data-appbar-auto-hide] > * > .page-layout-appbar': {
|
|
118
124
|
top: 'calc(-1 * var(--layout-appbar-height, 48px))',
|
|
119
125
|
},
|
|
120
|
-
'&[data-appbar-auto-hide] .page-layout-appbar:hover': {
|
|
126
|
+
'&[data-appbar-auto-hide] > * > .page-layout-appbar:hover': {
|
|
121
127
|
top: '0',
|
|
122
128
|
},
|
|
123
|
-
'&[data-appbar-auto-hide][data-appbar-visible] .page-layout-appbar': {
|
|
129
|
+
'&[data-appbar-auto-hide][data-appbar-visible] > * > .page-layout-appbar': {
|
|
124
130
|
top: '0',
|
|
125
131
|
},
|
|
126
132
|
|
|
127
133
|
// Drawer containers - use CSS transitions
|
|
128
|
-
'& .page-layout-drawer': {
|
|
134
|
+
'& > * > .page-layout-drawer': {
|
|
129
135
|
position: 'fixed',
|
|
130
136
|
top: 'var(--layout-appbar-height, 48px)',
|
|
131
137
|
bottom: '0',
|
|
@@ -135,13 +141,13 @@ export const PageLayout = Shade<PageLayoutProps>({
|
|
|
135
141
|
backgroundImage: cssVariableTheme.background.paperImage,
|
|
136
142
|
transition: `transform ${cssVariableTheme.transitions.duration.slow} ${cssVariableTheme.transitions.easing.easeInOut}`,
|
|
137
143
|
},
|
|
138
|
-
'& .page-layout-drawer-left': {
|
|
144
|
+
'& > * > .page-layout-drawer-left': {
|
|
139
145
|
left: '0',
|
|
140
146
|
width: 'var(--layout-drawer-left-configured-width, 240px)',
|
|
141
147
|
borderRight: `1px solid ${cssVariableTheme.divider}`,
|
|
142
148
|
transform: 'translateX(0)',
|
|
143
149
|
},
|
|
144
|
-
'& .page-layout-drawer-right': {
|
|
150
|
+
'& > * > .page-layout-drawer-right': {
|
|
145
151
|
right: '0',
|
|
146
152
|
width: 'var(--layout-drawer-right-configured-width, 240px)',
|
|
147
153
|
borderLeft: `1px solid ${cssVariableTheme.divider}`,
|
|
@@ -149,17 +155,17 @@ export const PageLayout = Shade<PageLayoutProps>({
|
|
|
149
155
|
},
|
|
150
156
|
|
|
151
157
|
// Drawer closed states (controlled via host data attributes)
|
|
152
|
-
'&[data-drawer-left-closed] .page-layout-drawer-left': {
|
|
158
|
+
'&[data-drawer-left-closed] > * > .page-layout-drawer-left': {
|
|
153
159
|
transform: 'translateX(-100%)',
|
|
154
160
|
pointerEvents: 'none',
|
|
155
161
|
},
|
|
156
|
-
'&[data-drawer-right-closed] .page-layout-drawer-right': {
|
|
162
|
+
'&[data-drawer-right-closed] > * > .page-layout-drawer-right': {
|
|
157
163
|
transform: 'translateX(100%)',
|
|
158
164
|
pointerEvents: 'none',
|
|
159
165
|
},
|
|
160
166
|
|
|
161
167
|
// Temporary drawer backdrop
|
|
162
|
-
'& .page-layout-drawer-backdrop': {
|
|
168
|
+
'& > * > .page-layout-drawer-backdrop': {
|
|
163
169
|
position: 'fixed',
|
|
164
170
|
top: '0',
|
|
165
171
|
left: '0',
|
|
@@ -171,13 +177,28 @@ export const PageLayout = Shade<PageLayoutProps>({
|
|
|
171
177
|
pointerEvents: 'none',
|
|
172
178
|
transition: `opacity ${cssVariableTheme.transitions.duration.slow} ${cssVariableTheme.transitions.easing.easeInOut}`,
|
|
173
179
|
},
|
|
174
|
-
'&[data-backdrop-visible] .page-layout-drawer-backdrop': {
|
|
180
|
+
'&[data-backdrop-visible] > * > .page-layout-drawer-backdrop': {
|
|
175
181
|
opacity: '1',
|
|
176
182
|
pointerEvents: 'auto',
|
|
177
183
|
},
|
|
178
184
|
|
|
185
|
+
// Contained mode - use absolute positioning instead of fixed so the layout
|
|
186
|
+
// fills its nearest positioned ancestor rather than the viewport
|
|
187
|
+
'&[data-contained]': {
|
|
188
|
+
position: 'absolute',
|
|
189
|
+
},
|
|
190
|
+
'&[data-contained] > * > .page-layout-appbar': {
|
|
191
|
+
position: 'absolute',
|
|
192
|
+
},
|
|
193
|
+
'&[data-contained] > * > .page-layout-drawer': {
|
|
194
|
+
position: 'absolute',
|
|
195
|
+
},
|
|
196
|
+
'&[data-contained] > * > .page-layout-drawer-backdrop': {
|
|
197
|
+
position: 'absolute',
|
|
198
|
+
},
|
|
199
|
+
|
|
179
200
|
// Content area - uses CSS variables for positioning
|
|
180
|
-
'& .page-layout-content': {
|
|
201
|
+
'& > * > .page-layout-content': {
|
|
181
202
|
position: 'absolute',
|
|
182
203
|
top: '0',
|
|
183
204
|
bottom: '0',
|
|
@@ -327,6 +348,7 @@ export const PageLayout = Shade<PageLayoutProps>({
|
|
|
327
348
|
const rightContentMargin = layoutService.getContentMarginForPosition('right')
|
|
328
349
|
|
|
329
350
|
useHostProps({
|
|
351
|
+
...(props.contained ? { 'data-contained': '' } : {}),
|
|
330
352
|
...(!isLeftOpen ? { 'data-drawer-left-closed': '' } : {}),
|
|
331
353
|
...(!isRightOpen ? { 'data-drawer-right-closed': '' } : {}),
|
|
332
354
|
...(props.appBar?.variant === 'auto-hide' ? { 'data-appbar-auto-hide': '' } : {}),
|
|
@@ -40,16 +40,19 @@ const statusColorMap: Record<ResultStatus, string> = {
|
|
|
40
40
|
'500': paletteMainColors.error.main,
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
const
|
|
44
|
-
success:
|
|
45
|
-
error:
|
|
46
|
-
warning:
|
|
47
|
-
info:
|
|
48
|
-
'403':
|
|
49
|
-
'404':
|
|
50
|
-
'500':
|
|
43
|
+
const defaultIconDefs: Record<ResultStatus, typeof checkCircle> = {
|
|
44
|
+
success: checkCircle,
|
|
45
|
+
error: errorCircle,
|
|
46
|
+
warning: warningIcon,
|
|
47
|
+
info: infoIcon,
|
|
48
|
+
'403': forbidden,
|
|
49
|
+
'404': searchOff,
|
|
50
|
+
'500': serverError,
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
const getDefaultIcon = (status: ResultStatus): JSX.Element =>
|
|
54
|
+
(<Icon icon={defaultIconDefs[status]} size={64} />) as unknown as JSX.Element
|
|
55
|
+
|
|
53
56
|
const defaultTitles: Record<ResultStatus, string> = {
|
|
54
57
|
success: 'Success',
|
|
55
58
|
error: 'Error',
|
|
@@ -114,7 +117,7 @@ export const Result = Shade<ResultProps>({
|
|
|
114
117
|
render: ({ props, children, useHostProps }) => {
|
|
115
118
|
const { status, title, subtitle, icon, style } = props
|
|
116
119
|
|
|
117
|
-
const displayIcon = icon ??
|
|
120
|
+
const displayIcon = icon ?? getDefaultIcon(status)
|
|
118
121
|
const statusColor = statusColorMap[status]
|
|
119
122
|
|
|
120
123
|
useHostProps({
|
|
@@ -146,4 +149,8 @@ export const Result = Shade<ResultProps>({
|
|
|
146
149
|
},
|
|
147
150
|
})
|
|
148
151
|
|
|
149
|
-
export {
|
|
152
|
+
export {
|
|
153
|
+
getDefaultIcon as resultGetDefaultIcon,
|
|
154
|
+
defaultIconDefs as resultDefaultIconDefs,
|
|
155
|
+
defaultTitles as resultDefaultTitles,
|
|
156
|
+
}
|
|
@@ -83,21 +83,23 @@ export const Suggest: <T>(props: SuggestProps<T>, children: ChildrenList) => JSX
|
|
|
83
83
|
useHostProps({
|
|
84
84
|
'data-opened': isOpened ? '' : undefined,
|
|
85
85
|
})
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
86
|
+
useDisposable('isLoadingSubscription', () =>
|
|
87
|
+
manager.isLoading.subscribe((isLoading) => {
|
|
88
|
+
const loader = loaderRef.current
|
|
89
|
+
if (!loader) return
|
|
90
|
+
if (isLoading) {
|
|
91
|
+
void promisifyAnimation(loader, [{ opacity: 0 }, { opacity: 1 }], {
|
|
92
|
+
duration: 100,
|
|
93
|
+
fill: 'forwards',
|
|
94
|
+
})
|
|
95
|
+
} else {
|
|
96
|
+
void promisifyAnimation(loader, [{ opacity: 1 }, { opacity: 0 }], {
|
|
97
|
+
duration: 100,
|
|
98
|
+
fill: 'forwards',
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
}),
|
|
102
|
+
)
|
|
101
103
|
useDisposable('onSelectSuggestion', () =>
|
|
102
104
|
manager.subscribe('onSelectSuggestion', props.onSelectSuggestion as (entry: unknown) => void),
|
|
103
105
|
)
|
|
@@ -133,6 +135,7 @@ export const Suggest: <T>(props: SuggestProps<T>, children: ChildrenList) => JSX
|
|
|
133
135
|
<div className="post-controls">
|
|
134
136
|
<span ref={loaderRef} style={{ display: 'inline-flex' }}>
|
|
135
137
|
<Loader
|
|
138
|
+
// eslint-disable-next-line furystack/no-direct-get-value-in-render -- Initial opacity only; animated transitions handled by isLoadingSubscription via DOM
|
|
136
139
|
style={{ width: '20px', height: '20px', opacity: manager.isLoading.getValue() ? '1' : '0' }}
|
|
137
140
|
delay={0}
|
|
138
141
|
borderWidth={4}
|
|
@@ -89,6 +89,7 @@ export const Tree: <T>(props: TreeProps<T>, children: ChildrenList) => JSX.Eleme
|
|
|
89
89
|
|
|
90
90
|
const [flattenedNodes] = useObservable('flattenedNodes', props.treeService.flattenedNodes)
|
|
91
91
|
|
|
92
|
+
// eslint-disable-next-line furystack/require-use-observable-for-render -- Used as persistent ref, not reactive state; read and written synchronously in same render cycle
|
|
92
93
|
const previousItemsRef = useDisposable('previousTreeItems', () => new ObservableValue<Set<unknown>>(new Set()))
|
|
93
94
|
const previousItems = previousItemsRef.getValue()
|
|
94
95
|
const currentItems = new Set<unknown>(flattenedNodes.map((n) => n.item))
|