mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Remove reaction and replace with ObservableCrate
- This new abstraction allows us to hook into the transitions between values without having to resort to reactions. Allowing us to be explicit in when we want code to execute and also be defensive against new code paths Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
5bb25390bf
commit
4c039da15e
@ -3,13 +3,6 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* A function that does nothing
|
|
||||||
*/
|
|
||||||
export function noop<T extends any[]>(...args: T): void {
|
|
||||||
return void args;
|
|
||||||
}
|
|
||||||
|
|
||||||
export * from "./abort-controller";
|
export * from "./abort-controller";
|
||||||
export * from "./app-version";
|
export * from "./app-version";
|
||||||
export * from "./autobind";
|
export * from "./autobind";
|
||||||
@ -27,6 +20,8 @@ export * from "./formatDuration";
|
|||||||
export * from "./getRandId";
|
export * from "./getRandId";
|
||||||
export * from "./hash-set";
|
export * from "./hash-set";
|
||||||
export * from "./n-fircate";
|
export * from "./n-fircate";
|
||||||
|
export * from "./noop";
|
||||||
|
export * from "./observable-crate/impl";
|
||||||
export * from "./openBrowser";
|
export * from "./openBrowser";
|
||||||
export * from "./paths";
|
export * from "./paths";
|
||||||
export * from "./promise-exec";
|
export * from "./promise-exec";
|
||||||
|
|||||||
10
src/common/utils/noop.ts
Normal file
10
src/common/utils/noop.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* A function that does nothing
|
||||||
|
*/
|
||||||
|
export function noop<T extends any[]>(...args: T): void {
|
||||||
|
return void args;
|
||||||
|
}
|
||||||
53
src/common/utils/observable-crate/impl.ts
Normal file
53
src/common/utils/observable-crate/impl.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { observable, runInAction } from "mobx";
|
||||||
|
import { getOrInsertMap } from "../collection-functions";
|
||||||
|
import { noop } from "../noop";
|
||||||
|
|
||||||
|
export interface ObservableCrate<T> {
|
||||||
|
get(): T;
|
||||||
|
set(value: T): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ObservableCrateFactory {
|
||||||
|
<T>(initialValue: T, transitionHandlers?: ObservableCrateTransitionHandlers<T>): ObservableCrate<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ObservableCrateTransitionHandler<T> {
|
||||||
|
from: T;
|
||||||
|
to: T;
|
||||||
|
onTransition: () => void;
|
||||||
|
}
|
||||||
|
export type ObservableCrateTransitionHandlers<T> = ObservableCrateTransitionHandler<T>[];
|
||||||
|
|
||||||
|
function convertToHandlersMap<T>(handlers: ObservableCrateTransitionHandlers<T>): Map<T, Map<T, () => void>> {
|
||||||
|
const res: ReturnType<typeof convertToHandlersMap<T>> = new Map();
|
||||||
|
|
||||||
|
for (const { from, to, onTransition } of handlers) {
|
||||||
|
getOrInsertMap(res, from).set(to, onTransition);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const observableCrate = ((initialValue, transitionHandlers = []) => {
|
||||||
|
const crate = observable.box(initialValue);
|
||||||
|
const handlers = convertToHandlersMap(transitionHandlers);
|
||||||
|
|
||||||
|
return {
|
||||||
|
get() {
|
||||||
|
return crate.get();
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
const onTransition = handlers.get(crate.get())?.get(value) ?? noop;
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
crate.set(value);
|
||||||
|
onTransition();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}) as ObservableCrateFactory;
|
||||||
86
src/common/utils/observable-crate/observable-crate.test.ts
Normal file
86
src/common/utils/observable-crate/observable-crate.test.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ObservableCrate } from "./impl";
|
||||||
|
import { observableCrate } from "./impl";
|
||||||
|
|
||||||
|
describe("observable-crate", () => {
|
||||||
|
it("can be constructed with initial value", () => {
|
||||||
|
expect(() => observableCrate(0).build()).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has a definite type if the initial value is provided", () => {
|
||||||
|
expect (() => {
|
||||||
|
const res: ObservableCrate<number> = observableCrate(0).build();
|
||||||
|
|
||||||
|
void res;
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepts a map of transitionHandlers", () => {
|
||||||
|
expect(() => observableCrate(0).withHandlers(new Map())).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("with a crate over an enum, and some transition handlers", () => {
|
||||||
|
enum Test {
|
||||||
|
Start,
|
||||||
|
T1,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
let crate: ObservableCrate<Test>;
|
||||||
|
let correctHandler: jest.MockedFunction<() => void>;
|
||||||
|
let incorrectHandler: jest.MockedFunction<() => void>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
correctHandler = jest.fn();
|
||||||
|
incorrectHandler = jest.fn();
|
||||||
|
crate = observableCrate(Test.Start).withHandlers(new Map([
|
||||||
|
[Test.Start, new Map([
|
||||||
|
[Test.Start, incorrectHandler],
|
||||||
|
[Test.T1, correctHandler],
|
||||||
|
[Test.End, incorrectHandler],
|
||||||
|
])],
|
||||||
|
[Test.T1, new Map([
|
||||||
|
[Test.Start, incorrectHandler],
|
||||||
|
[Test.T1, incorrectHandler],
|
||||||
|
[Test.End, incorrectHandler],
|
||||||
|
])],
|
||||||
|
[Test.End, new Map([
|
||||||
|
[Test.Start, incorrectHandler],
|
||||||
|
[Test.T1, incorrectHandler],
|
||||||
|
[Test.End, incorrectHandler],
|
||||||
|
])],
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial value is available", () => {
|
||||||
|
expect(crate.get()).toBe(Test.Start);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not call any transition handler", () => {
|
||||||
|
expect(correctHandler).not.toBeCalled();
|
||||||
|
expect(incorrectHandler).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when setting a new value", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
crate.set(Test.T1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls the associated transition handler", () => {
|
||||||
|
expect(correctHandler).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not call any other transition handler", () => {
|
||||||
|
expect(incorrectHandler).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("new value is available", () => {
|
||||||
|
expect(crate.get()).toBe(Test.T1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type React from "react";
|
||||||
|
import isMacInjectable from "../../../../common/vars/is-mac.injectable";
|
||||||
|
|
||||||
|
export type IsMultiSelectionKey = (event: React.KeyboardEvent) => boolean;
|
||||||
|
|
||||||
|
const isMultiSelectionKeyInjectable = getInjectable({
|
||||||
|
id: "is-multi-selection-key",
|
||||||
|
instantiate: (di): IsMultiSelectionKey => {
|
||||||
|
const isMac = di.inject(isMacInjectable);
|
||||||
|
|
||||||
|
return isMac
|
||||||
|
? ({ key }) => key === "Meta"
|
||||||
|
: ({ key }) => key === "Control";
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default isMultiSelectionKeyInjectable;
|
||||||
@ -2,17 +2,17 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { NamespaceSelectFilterModel } from "./namespace-select-filter-model";
|
import { namespaceSelectFilterModelFor } from "./namespace-select-filter-model";
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import namespaceStoreInjectable from "../store.injectable";
|
import namespaceStoreInjectable from "../store.injectable";
|
||||||
import isMacInjectable from "../../../../common/vars/is-mac.injectable";
|
import isMultiSelectionKeyInjectable from "./is-selection-key.injectable";
|
||||||
|
|
||||||
const namespaceSelectFilterModelInjectable = getInjectable({
|
const namespaceSelectFilterModelInjectable = getInjectable({
|
||||||
id: "namespace-select-filter-model",
|
id: "namespace-select-filter-model",
|
||||||
|
|
||||||
instantiate: (di) => new NamespaceSelectFilterModel({
|
instantiate: (di) => namespaceSelectFilterModelFor({
|
||||||
namespaceStore: di.inject(namespaceStoreInjectable),
|
namespaceStore: di.inject(namespaceStoreInjectable),
|
||||||
isMac: di.inject(isMacInjectable),
|
isMultiSelectionKey: di.inject(isMultiSelectionKeyInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -3,16 +3,18 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observable, action, computed, makeObservable, comparer, reaction } from "mobx";
|
import type { IComputedValue } from "mobx";
|
||||||
|
import { observable, action, computed, comparer } from "mobx";
|
||||||
import type { NamespaceStore } from "../store";
|
import type { NamespaceStore } from "../store";
|
||||||
import type { ActionMeta } from "react-select";
|
import type { ActionMeta, MultiValue } from "react-select";
|
||||||
import { Icon } from "../../icon";
|
import { Icon } from "../../icon";
|
||||||
import type { SelectOption } from "../../select";
|
import type { SelectOption } from "../../select";
|
||||||
import { autoBind } from "../../../utils";
|
import { observableCrate } from "../../../utils";
|
||||||
|
import type { IsMultiSelectionKey } from "./is-selection-key.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
readonly namespaceStore: NamespaceStore;
|
namespaceStore: NamespaceStore;
|
||||||
readonly isMac: boolean;
|
isMultiSelectionKey: IsMultiSelectionKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const selectAllNamespaces = Symbol("all-namespaces-selected");
|
export const selectAllNamespaces = Symbol("all-namespaces-selected");
|
||||||
@ -20,50 +22,71 @@ export const selectAllNamespaces = Symbol("all-namespaces-selected");
|
|||||||
export type SelectAllNamespaces = typeof selectAllNamespaces;
|
export type SelectAllNamespaces = typeof selectAllNamespaces;
|
||||||
export type NamespaceSelectFilterOption = SelectOption<string | SelectAllNamespaces>;
|
export type NamespaceSelectFilterOption = SelectOption<string | SelectAllNamespaces>;
|
||||||
|
|
||||||
export class NamespaceSelectFilterModel {
|
export function formatOptionLabel({ value, isSelected }: NamespaceSelectFilterOption) {
|
||||||
private isSelectionKey = (event: React.KeyboardEvent): boolean => {
|
if (value === selectAllNamespaces) {
|
||||||
if (this.dependencies.isMac) {
|
return <>All Namespaces</>;
|
||||||
return event.key === "Meta";
|
|
||||||
}
|
|
||||||
|
|
||||||
return event.key === "Control"; // windows or linux
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(private readonly dependencies: Dependencies) {
|
|
||||||
makeObservable(this);
|
|
||||||
autoBind(this);
|
|
||||||
|
|
||||||
reaction(
|
|
||||||
() => this.menuIsOpen.get(),
|
|
||||||
(isOpen) => {
|
|
||||||
if (!isOpen) { // falling edge of menu being open
|
|
||||||
this.optionsSortingSelected.replace(this.selectedNames.get());
|
|
||||||
this.didToggle = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly menuIsOpen = observable.box(false);
|
return (
|
||||||
|
<div className="flex gaps align-center">
|
||||||
|
<Icon small material="layers" />
|
||||||
|
<span>{value}</span>
|
||||||
|
{isSelected && (
|
||||||
|
<Icon
|
||||||
|
small
|
||||||
|
material="check"
|
||||||
|
className="box right"
|
||||||
|
data-testid={`namespace-select-filter-option-${value}-selected`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private readonly selectedNames = computed(() => new Set(this.dependencies.namespaceStore.contextNamespaces), {
|
export interface NamespaceSelectFilterModel {
|
||||||
|
readonly options: IComputedValue<readonly NamespaceSelectFilterOption[]>;
|
||||||
|
readonly menu: {
|
||||||
|
open: () => void;
|
||||||
|
close: () => void;
|
||||||
|
readonly isOpen: boolean;
|
||||||
|
};
|
||||||
|
onChange: (newValue: MultiValue<NamespaceSelectFilterOption>, actionMeta: ActionMeta<NamespaceSelectFilterOption>) => void;
|
||||||
|
onClick: () => void;
|
||||||
|
onKeyDown: React.KeyboardEventHandler;
|
||||||
|
onKeyUp: React.KeyboardEventHandler;
|
||||||
|
reset: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SelectMenuState {
|
||||||
|
Close = "close",
|
||||||
|
Open = "open",
|
||||||
|
}
|
||||||
|
|
||||||
|
export function namespaceSelectFilterModelFor(dependencies: Dependencies): NamespaceSelectFilterModel {
|
||||||
|
const { isMultiSelectionKey, namespaceStore } = dependencies;
|
||||||
|
|
||||||
|
let didToggle = false;
|
||||||
|
let isMultiSelection = false;
|
||||||
|
const menuState = observableCrate(SelectMenuState.Close, [{
|
||||||
|
from: SelectMenuState.Close,
|
||||||
|
to: SelectMenuState.Open,
|
||||||
|
onTransition: () => {
|
||||||
|
optionsSortingSelected.replace(selectedNames.get());
|
||||||
|
didToggle = false;
|
||||||
|
},
|
||||||
|
}]);
|
||||||
|
const selectedNames = computed(() => new Set(namespaceStore.contextNamespaces), {
|
||||||
equals: comparer.structural,
|
equals: comparer.structural,
|
||||||
});
|
});
|
||||||
|
const optionsSortingSelected = observable.set(selectedNames.get());
|
||||||
/**
|
const options = computed((): readonly NamespaceSelectFilterOption[] => {
|
||||||
* This set is only updated on the falling edge of the menu being open. That way while the menu is
|
const baseOptions = namespaceStore.items.map(ns => ns.getName());
|
||||||
* open the order of the items doesn't change
|
const namespaces = selectedNames.get();
|
||||||
*/
|
|
||||||
private readonly optionsSortingSelected = observable.set<string>(this.selectedNames.get());
|
|
||||||
|
|
||||||
readonly options = computed((): readonly NamespaceSelectFilterOption[] => {
|
|
||||||
const baseOptions = this.dependencies.namespaceStore.items.map(ns => ns.getName());
|
|
||||||
const selectedNames = this.selectedNames.get();
|
|
||||||
|
|
||||||
baseOptions.sort((
|
baseOptions.sort((
|
||||||
(left, right) =>
|
(left, right) =>
|
||||||
+this.optionsSortingSelected.has(right)
|
+optionsSortingSelected.has(right)
|
||||||
- +this.optionsSortingSelected.has(left)
|
- +optionsSortingSelected.has(left)
|
||||||
));
|
));
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -77,113 +100,78 @@ export class NamespaceSelectFilterModel {
|
|||||||
value: namespace,
|
value: namespace,
|
||||||
label: namespace,
|
label: namespace,
|
||||||
id: namespace,
|
id: namespace,
|
||||||
isSelected: selectedNames.has(namespace),
|
isSelected: namespaces.has(namespace),
|
||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
const menuIsOpen = computed(() => menuState.get() === SelectMenuState.Open);
|
||||||
|
|
||||||
formatOptionLabel({ value, isSelected }: NamespaceSelectFilterOption) {
|
const model: NamespaceSelectFilterModel = {
|
||||||
if (value === selectAllNamespaces) {
|
options,
|
||||||
return "All Namespaces";
|
menu: {
|
||||||
}
|
close: action(() => {
|
||||||
|
menuState.set(SelectMenuState.Close);
|
||||||
return (
|
}),
|
||||||
<div className="flex gaps align-center">
|
open: action(() => {
|
||||||
<Icon small material="layers" />
|
menuState.set(SelectMenuState.Open);
|
||||||
<span>{value}</span>
|
}),
|
||||||
{isSelected && (
|
get isOpen() {
|
||||||
<Icon
|
return menuIsOpen.get();
|
||||||
small
|
},
|
||||||
material="check"
|
},
|
||||||
className="box right"
|
onChange: (_, action) => {
|
||||||
data-testid={`namespace-select-filter-option-${value}-selected`}
|
switch (action.action) {
|
||||||
/>
|
case "clear":
|
||||||
)}
|
namespaceStore.selectAll();
|
||||||
</div>
|
break;
|
||||||
);
|
case "deselect-option":
|
||||||
}
|
if (typeof action.option === "string") {
|
||||||
|
didToggle = true;
|
||||||
@action
|
namespaceStore.toggleSingle(action.option);
|
||||||
closeMenu() {
|
|
||||||
this.menuIsOpen.set(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
openMenu(){
|
|
||||||
this.menuIsOpen.set(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
isSelected(namespace: string | string[]) {
|
|
||||||
return this.dependencies.namespaceStore.hasContext(namespace);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectSingle(namespace: string) {
|
|
||||||
this.dependencies.namespaceStore.selectSingle(namespace);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectAll() {
|
|
||||||
this.dependencies.namespaceStore.selectAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange(namespace: unknown, action: ActionMeta<NamespaceSelectFilterOption>) {
|
|
||||||
switch (action.action) {
|
|
||||||
case "clear":
|
|
||||||
this.dependencies.namespaceStore.selectAll();
|
|
||||||
break;
|
|
||||||
case "deselect-option":
|
|
||||||
if (typeof action.option === "string") {
|
|
||||||
this.didToggle = true;
|
|
||||||
this.dependencies.namespaceStore.toggleSingle(action.option);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "select-option":
|
|
||||||
if (action.option?.value === selectAllNamespaces) {
|
|
||||||
this.didToggle = true;
|
|
||||||
this.dependencies.namespaceStore.selectAll();
|
|
||||||
} else if (action.option) {
|
|
||||||
this.didToggle = true;
|
|
||||||
|
|
||||||
if (this.isMultiSelection) {
|
|
||||||
this.dependencies.namespaceStore.toggleSingle(action.option.value);
|
|
||||||
} else {
|
|
||||||
this.dependencies.namespaceStore.selectSingle(action.option.value);
|
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
break;
|
case "select-option":
|
||||||
}
|
if (action.option?.value === selectAllNamespaces) {
|
||||||
}
|
didToggle = true;
|
||||||
|
namespaceStore.selectAll();
|
||||||
|
} else if (action.option) {
|
||||||
|
didToggle = true;
|
||||||
|
|
||||||
onClick() {
|
if (isMultiSelection) {
|
||||||
if (!this.menuIsOpen.get()) {
|
namespaceStore.toggleSingle(action.option.value);
|
||||||
this.openMenu();
|
} else {
|
||||||
} else if (!this.isMultiSelection) {
|
namespaceStore.selectSingle(action.option.value);
|
||||||
this.closeMenu();
|
}
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
|
|
||||||
private isMultiSelection = false;
|
|
||||||
|
|
||||||
onKeyDown(event: React.KeyboardEvent) {
|
|
||||||
if (this.isSelectionKey(event)) {
|
|
||||||
this.isMultiSelection = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private didToggle = false;
|
|
||||||
|
|
||||||
onKeyUp(event: React.KeyboardEvent) {
|
|
||||||
if (this.isSelectionKey(event)) {
|
|
||||||
this.isMultiSelection = false;
|
|
||||||
|
|
||||||
if (this.didToggle) {
|
|
||||||
this.closeMenu();
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
onClick: () => {
|
||||||
|
if (!menuIsOpen.get()) {
|
||||||
|
model.menu.open();
|
||||||
|
} else if (!isMultiSelection) {
|
||||||
|
model.menu.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onKeyDown: (event) => {
|
||||||
|
if (isMultiSelectionKey(event)) {
|
||||||
|
isMultiSelection = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onKeyUp: (event) => {
|
||||||
|
if (isMultiSelectionKey(event)) {
|
||||||
|
isMultiSelection = false;
|
||||||
|
|
||||||
@action
|
if (didToggle) {
|
||||||
reset() {
|
model.menu.close();
|
||||||
this.isMultiSelection = false;
|
}
|
||||||
this.closeMenu();
|
}
|
||||||
}
|
},
|
||||||
|
reset: action(() => {
|
||||||
|
isMultiSelection = false;
|
||||||
|
model.menu.close();
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return model;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import type { NamespaceStore } from "./store";
|
|||||||
import { Select } from "../select";
|
import { Select } from "../select";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import type { NamespaceSelectFilterModel, NamespaceSelectFilterOption, SelectAllNamespaces } from "./namespace-select-filter-model/namespace-select-filter-model";
|
import type { NamespaceSelectFilterModel, NamespaceSelectFilterOption, SelectAllNamespaces } from "./namespace-select-filter-model/namespace-select-filter-model";
|
||||||
|
import { formatOptionLabel } from "./namespace-select-filter-model/namespace-select-filter-model";
|
||||||
import namespaceSelectFilterModelInjectable from "./namespace-select-filter-model/namespace-select-filter-model.injectable";
|
import namespaceSelectFilterModelInjectable from "./namespace-select-filter-model/namespace-select-filter-model.injectable";
|
||||||
import namespaceStoreInjectable from "./store.injectable";
|
import namespaceStoreInjectable from "./store.injectable";
|
||||||
|
|
||||||
@ -36,13 +37,13 @@ const NonInjectedNamespaceSelectFilter = observer(({ model, id }: Dependencies &
|
|||||||
id={id}
|
id={id}
|
||||||
isMulti={true}
|
isMulti={true}
|
||||||
isClearable={false}
|
isClearable={false}
|
||||||
menuIsOpen={model.menuIsOpen.get()}
|
menuIsOpen={model.menu.isOpen}
|
||||||
components={{ Placeholder }}
|
components={{ Placeholder }}
|
||||||
closeMenuOnSelect={false}
|
closeMenuOnSelect={false}
|
||||||
controlShouldRenderValue={false}
|
controlShouldRenderValue={false}
|
||||||
onChange={model.onChange}
|
onChange={model.onChange}
|
||||||
onBlur={model.reset}
|
onBlur={model.reset}
|
||||||
formatOptionLabel={model.formatOptionLabel}
|
formatOptionLabel={formatOptionLabel}
|
||||||
options={model.options.get()}
|
options={model.options.get()}
|
||||||
className="NamespaceSelect NamespaceSelectFilter"
|
className="NamespaceSelect NamespaceSelectFilter"
|
||||||
menuClass="NamespaceSelectFilterMenu"
|
menuClass="NamespaceSelectFilterMenu"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user