Skip to content

Commit a9dbffd

Browse files
committed
WIP
1 parent 22592dd commit a9dbffd

File tree

16 files changed

+461
-31
lines changed

16 files changed

+461
-31
lines changed

packages/pluggableWidgets/gallery-web/src/components/GalleryContent.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@ import { observer } from "mobx-react-lite";
44
import { PropsWithChildren, ReactElement } from "react";
55
import { usePaginationConfig, usePaginationVM } from "../model/hooks/injection-hooks";
66

7-
export const GalleryContent = observer(function GalleryContent({
8-
children,
9-
}: PropsWithChildren): ReactElement {
7+
export const GalleryContent = observer(function GalleryContent({ children }: PropsWithChildren): ReactElement {
108
const paginationVM = usePaginationVM();
119
const isInfinite = usePaginationConfig().isLimitBased;
1210
const [trackScrolling, bodySize, containerRef] = useInfiniteControl({
1311
hasMoreItems: paginationVM.hasMoreItems,
14-
isInfinite: isInfinite,
12+
isInfinite,
1513
setPage: paginationVM.setPage.bind(paginationVM)
1614
});
1715

@@ -25,5 +23,4 @@ export const GalleryContent = observer(function GalleryContent({
2523
{children}
2624
</div>
2725
);
28-
}
29-
)
26+
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { KeyNavProvider } from "@mendix/widget-plugin-grid/keyboard-navigation/context";
2+
import { observer } from "mobx-react-lite";
3+
import { useGalleryConfig, useItems, useKeyNavFocus, useTextsService } from "../model/hooks/injection-hooks";
4+
import { ListBox } from "./ListBox";
5+
import { ListItem } from "./ListItem";
6+
7+
export const GalleryItems = observer(function GalleryItems() {
8+
const items = useItems().get();
9+
const config = useGalleryConfig();
10+
const texts = useTextsService();
11+
const focusController = useKeyNavFocus();
12+
const selectionHelper = null; // Placeholder for selection helper
13+
14+
if (items.length === 0) {
15+
return <div>Empty</div>;
16+
}
17+
18+
return (
19+
<ListBox
20+
lg={config.desktopItems}
21+
md={config.tabletItems}
22+
sm={config.phoneItems}
23+
selectionType={config.selectionType}
24+
aria-label={texts.listboxAriaLabel}
25+
>
26+
<KeyNavProvider focusController={focusController}>
27+
{items.map((item, index) => (
28+
<ListItem
29+
preview={false}
30+
key={item.id}
31+
helper={null}
32+
item={item}
33+
selectionHelper={selectionHelper}
34+
eventsController={null}
35+
getPosition={() => index}
36+
itemIndex={index}
37+
label={null}
38+
/>
39+
))}
40+
</KeyNavProvider>
41+
</ListBox>
42+
);
43+
});

packages/pluggableWidgets/gallery-web/src/components/GalleryWidget.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ReactElement } from "react";
2+
import { GalleryContent as Content } from "./GalleryContent";
23
import { GalleryFooter as Footer } from "./GalleryFooter";
34
import { GalleryFooterControls as FooterControls } from "./GalleryFooterControls";
45
import { GalleryHeader as Header } from "./GalleryHeader";
@@ -13,6 +14,7 @@ export function GalleryWidget(): ReactElement {
1314
<TopBarControls />
1415
</TopBar>
1516
<Header />
17+
<Content></Content>
1618
<Footer>
1719
<FooterControls />
1820
</Footer>
Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
1+
import { useFocusTargetProps } from "@mendix/widget-plugin-grid/keyboard-navigation/useFocusTargetProps";
2+
import { PositionInGrid } from "@mendix/widget-plugin-grid/selection";
13
import classNames from "classnames";
24
import { ObjectItem } from "mendix";
35
import { JSX, ReactElement, RefObject, useMemo } from "react";
4-
import { useFocusTargetProps } from "@mendix/widget-plugin-grid/keyboard-navigation/useFocusTargetProps";
5-
import { PositionInGrid, SelectActionHandler } from "@mendix/widget-plugin-grid/selection";
66
import { getAriaProps } from "../features/item-interaction/get-item-aria-props";
77

8-
import { GalleryItemHelper } from "../typings/GalleryItem";
8+
import { useGalleryItemVM, useSelectActions } from "../model/hooks/injection-hooks";
9+
910
import { ItemEventsController } from "../typings/ItemEventsController";
11+
import { ListItemButton } from "./ListItemButton";
1012

1113
type ListItemProps = Omit<JSX.IntrinsicElements["div"], "ref" | "role"> & {
1214
eventsController: ItemEventsController;
1315
getPosition: (index: number) => PositionInGrid;
14-
helper: GalleryItemHelper;
1516
item: ObjectItem;
1617
itemIndex: number;
17-
selectHelper: SelectActionHandler;
18+
1819
preview?: boolean;
19-
label?: string;
2020
};
2121

2222
export function ListItem(props: ListItemProps): ReactElement {
23-
const { eventsController, getPosition, helper, item, itemIndex, selectHelper, label, ...rest } = props;
24-
const clickable = helper.hasOnClick(item) || selectHelper.selectionType !== "None";
25-
const ariaProps = getAriaProps(item, selectHelper, label);
23+
const { eventsController, getPosition, item, itemIndex, ...rest } = props;
24+
const selectActions = useSelectActions();
25+
const itemVM = useGalleryItemVM();
26+
27+
const clickable = itemVM.hasOnClick(item) || selectActions.selectionType !== "None";
28+
const ariaProps = getAriaProps(item, selectActions, itemVM.label(item));
2629
const { columnIndex, rowIndex } = getPosition(itemIndex);
2730
const keyNavProps = useFocusTargetProps({ columnIndex: columnIndex ?? -1, rowIndex });
2831
const handlers = useMemo(() => eventsController.getProps(item), [eventsController, item]);
@@ -37,7 +40,7 @@ export function ListItem(props: ListItemProps): ReactElement {
3740
"widget-gallery-selected": ariaProps["aria-selected"],
3841
"widget-gallery-preview": props.preview
3942
},
40-
helper.itemClass(item)
43+
itemVM.class(item)
4144
)}
4245
{...ariaProps}
4346
onClick={handlers.onClick}
@@ -49,7 +52,11 @@ export function ListItem(props: ListItemProps): ReactElement {
4952
ref={keyNavProps.ref as RefObject<HTMLDivElement>}
5053
tabIndex={keyNavProps.tabIndex}
5154
>
52-
{helper.render(item)}
55+
{itemVM.hasOnClick(item) === true ? (
56+
<ListItemButton>{itemVM.content(item)}</ListItemButton>
57+
) : (
58+
itemVM.content(item)
59+
)}
5360
</div>
5461
);
5562
}

packages/pluggableWidgets/gallery-web/src/features/item-interaction/get-item-aria-props.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import { SelectActionsService } from "@mendix/widget-plugin-grid/interfaces/SelectActionsService";
12
import { ObjectItem } from "mendix";
2-
import { SelectActionHandler } from "@mendix/widget-plugin-grid/selection";
33

44
type ListItemRole = "option" | "listitem";
55

@@ -10,7 +10,7 @@ type ListItemAriaProps = {
1010
"aria-label": string | undefined;
1111
};
1212

13-
export function getAriaProps(item: ObjectItem, helper: SelectActionHandler, label?: string): ListItemAriaProps {
13+
export function getAriaProps(item: ObjectItem, helper: SelectActionsService, label?: string): ListItemAriaProps {
1414
if (helper.selectionType === "Single" || helper.selectionType === "Multi") {
1515
return {
1616
role: "option",

packages/pluggableWidgets/gallery-web/src/model/configs/Gallery.config.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ export interface GalleryConfig {
1515
autoSelect: boolean;
1616
// settings
1717
settingsStorageEnabled: boolean;
18+
// grid settings
19+
desktopItems: number;
20+
tabletItems: number;
21+
phoneItems: number;
1822
}
1923

2024
export function galleryConfig(props: GalleryContainerProps): GalleryConfig {
@@ -29,7 +33,10 @@ export function galleryConfig(props: GalleryContainerProps): GalleryConfig {
2933
selectionMode: props.itemSelectionMode,
3034
keepSelection: props.keepSelection,
3135
autoSelect: false,
32-
settingsStorageEnabled: false
36+
settingsStorageEnabled: false,
37+
desktopItems: props.desktopItems,
38+
tabletItems: props.tabletItems,
39+
phoneItems: props.phoneItems
3340
};
3441
}
3542

packages/pluggableWidgets/gallery-web/src/model/containers/Gallery.container.ts

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,42 @@
11
import { WidgetFilterAPI } from "@mendix/widget-plugin-filtering/context";
22
import { CombinedFilter } from "@mendix/widget-plugin-filtering/stores/generic/CombinedFilter";
33
import { CustomFilterHost } from "@mendix/widget-plugin-filtering/stores/generic/CustomFilterHost";
4-
import { createSelectionHelper, DatasourceService, SelectActionsProvider } from "@mendix/widget-plugin-grid/main";
4+
import {
5+
createFocusController,
6+
createSelectionHelper,
7+
DatasourceService,
8+
SelectActionsProvider
9+
} from "@mendix/widget-plugin-grid/main";
510
import { SelectionCounterViewModel } from "@mendix/widget-plugin-grid/selection-counter/SelectionCounter.viewModel-atoms";
611
import { DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/main";
712
import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid";
813
import { SortStoreHost } from "@mendix/widget-plugin-sorting/stores/SortStoreHost";
914
import { Container, injected } from "brandi";
1015
import { GalleryGateProps } from "../../typings/GalleryGateProps";
16+
import { GalleryItemViewModel } from "../../view-models/GalleryItem.viewModel";
1117
import { GalleryRootViewModel } from "../../view-models/GalleryRoot.viewModel";
1218
import { GalleryConfig } from "../configs/Gallery.config";
1319
import { galleryPaginationConfig } from "../configs/GalleryPagination.config";
20+
import { itemsAtom } from "../models/items.model";
21+
import { layoutAtom } from "../models/layout.model";
22+
import { LayoutService } from "../services/Layout.service";
1423
import { LoaderService } from "../services/Loader.service";
1524
import { QueryParamsService } from "../services/QueryParams.service";
1625
import { SelectionGate } from "../services/SelectionGate.service";
1726
import { CORE_TOKENS as CORE, GY_TOKENS as GY } from "../tokens";
27+
// import {
28+
// createClickActionHelper,
29+
// createFocusController,
30+
// createSelectionHelper,
31+
// createSetPageAction,
32+
// createSetPageSizeAction,
33+
// currentPageAtom,
34+
// DatasourceService,
35+
// layoutAtom,
36+
// pageSizeAtom,
37+
// SelectActionsProvider,
38+
// TaskProgressService
39+
// } from "@mendix/widget-plugin-grid/main";
1840
interface InitDependencies {
1941
props: GalleryGateProps;
2042
mainGate: DerivedPropsGate<GalleryGateProps>;
@@ -34,9 +56,13 @@ interface BindingGroup {
3456
}
3557

3658
const _01_coreBindings: BindingGroup = {
59+
inject() {
60+
injected(itemsAtom, CORE.mainGate);
61+
},
3762
init(container, { mainGate, config }) {
3863
container.bind(CORE.mainGate).toConstant(mainGate);
3964
container.bind(CORE.config).toConstant(config);
65+
container.bind(CORE.items).toInstance(itemsAtom).inTransientScope();
4066
}
4167
};
4268

@@ -83,7 +109,6 @@ const _03_filterBindings: BindingGroup = {
83109
const _04_sortBindings: BindingGroup = {
84110
inject() {
85111
injected(SortStoreHost, GY.sortHostConfig.optional);
86-
87112
},
88113
define(container) {
89114
container.bind(GY.sortHost).toInstance(SortStoreHost).inSingletonScope();
@@ -93,16 +118,18 @@ const _04_sortBindings: BindingGroup = {
93118
container.bind(GY.sortAPI).toConstant({
94119
version: 1,
95120
host: container.get(GY.sortHost) as SortStoreHost
96-
})
97-
},
121+
});
122+
}
98123
};
99124

100125
const _05_viewBindings: BindingGroup = {
101126
inject() {
102127
injected(GalleryRootViewModel, CORE.mainGate);
128+
injected(GalleryItemViewModel, CORE.mainGate);
103129
},
104130
define(container) {
105-
container.bind(GY.galleryRootVM).toInstance(GalleryRootViewModel).inSingletonScope();
131+
container.bind(GY.galleryRootVM).toInstance(GalleryRootViewModel).inTransientScope();
132+
container.bind(GY.galleryItemVM).toInstance(GalleryItemViewModel).inTransientScope();
106133
}
107134
};
108135

@@ -173,6 +200,21 @@ const _08_paginationBindings: BindingGroup = {
173200
}
174201
};
175202

203+
const _09_keyNavBindings: BindingGroup = {
204+
inject() {
205+
injected(createFocusController, CORE.setupService, GY.virtualLayout);
206+
injected(LayoutService, CORE.setupService, CORE.config, CORE.data.itemCount);
207+
injected(layoutAtom, GY.layoutService, GY.paging.pageSize);
208+
},
209+
define(container: Container) {
210+
container.bind(GY.virtualLayout).toInstance(layoutAtom).inTransientScope();
211+
container.bind(GY.keyNavFocusService).toInstance(createFocusController).inSingletonScope();
212+
},
213+
init(container, { mainGate }) {
214+
container.bind(CORE.mainGate).toConstant(mainGate);
215+
}
216+
};
217+
176218
const groups = [
177219
_01_coreBindings,
178220
_02_queryBindings,
@@ -181,7 +223,8 @@ const groups = [
181223
_05_viewBindings,
182224
_06_loaderBindings,
183225
_07_selectionBindings,
184-
_08_paginationBindings
226+
_08_paginationBindings,
227+
_09_keyNavBindings
185228
];
186229

187230
// Inject tokens from groups

packages/pluggableWidgets/gallery-web/src/model/hooks/injection-hooks.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { createInjectionHooks } from "brandi-react";
22
import { CORE_TOKENS as CORE, GY_TOKENS as GY } from "../tokens";
33

44
export const [useGalleryRootVM] = createInjectionHooks(GY.galleryRootVM);
5+
export const [useGalleryItemVM] = createInjectionHooks(GY.galleryItemVM);
6+
57
export const [useMainGate] = createInjectionHooks(CORE.mainGate);
8+
export const [useItems] = createInjectionHooks(CORE.items);
9+
export const [useGalleryConfig] = createInjectionHooks(CORE.config);
610

711
export const [useSelectActions] = createInjectionHooks(GY.selectActions);
812
export const [useSelectionHelper] = createInjectionHooks(GY.selectionHelper);
@@ -12,4 +16,8 @@ export const [usePaginationVM] = createInjectionHooks(GY.paging.paginationVM);
1216

1317
export const [useFilterAPI] = createInjectionHooks(GY.filterAPI);
1418

15-
export const [useSortAPI] = createInjectionHooks(GY.sortAPI);
19+
export const [useSortAPI] = createInjectionHooks(GY.sortAPI);
20+
21+
export const [useTextsService] = createInjectionHooks(CORE.texts);
22+
23+
export const [useKeyNavFocus] = createInjectionHooks(GY.keyNavFocusService);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { ComputedAtom, DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/main";
2+
import { ObjectItem } from "mendix";
3+
import { computed } from "mobx";
4+
import { GalleryGateProps } from "../../typings/GalleryGateProps";
5+
6+
/** @injectable */
7+
export function itemsAtom(gate: DerivedPropsGate<GalleryGateProps>): ComputedAtom<ObjectItem[]> {
8+
return computed(() => {
9+
return gate.props.datasource?.items ?? [];
10+
});
11+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { VirtualGridLayout } from "@mendix/widget-plugin-grid/keyboard-navigation/VirtualGridLayout";
2+
import { ComputedAtom } from "@mendix/widget-plugin-mobx-kit/main";
3+
import { computed } from "mobx";
4+
5+
/** @injectable */
6+
export function layoutAtom(
7+
layoutStore: {
8+
numberOfRows: number;
9+
numberOfColumns: number;
10+
},
11+
pageSize: ComputedAtom<number>
12+
): ComputedAtom<VirtualGridLayout> {
13+
return computed(() => new VirtualGridLayout(layoutStore.numberOfRows, layoutStore.numberOfColumns, pageSize.get()));
14+
}

0 commit comments

Comments
 (0)