Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { cssPropertiesToCss } from "@odoo/o-spreadsheet-engine/components/helpers/css";
import { Token } from "@odoo/o-spreadsheet-engine/formulas/tokenizer";
import { SpreadsheetChildEnv } from "@odoo/o-spreadsheet-engine/types/spreadsheet_env";
import { Component } from "@odoo/owl";
import { Component, onMounted } from "@odoo/owl";
import { AutoCompleteProviderDefinition } from "../../../registries/auto_completes";
import { Store, useLocalStore, useStore } from "../../../store_engine";
import { Color, ComposerFocusType, UID } from "../../../types/index";
Expand All @@ -21,6 +21,7 @@ interface Props {
title?: string;
class?: string;
invalid?: boolean;
autofocus?: boolean;
getContextualColoredSymbolToken?: (token: Token) => Color;
}

Expand All @@ -36,6 +37,7 @@ export class StandaloneComposer extends Component<Props, SpreadsheetChildEnv> {
title: { type: String, optional: true },
class: { type: String, optional: true },
invalid: { type: Boolean, optional: true },
autofocus: { type: Boolean, optional: true },
getContextualColoredSymbolToken: { type: Function, optional: true },
};
static components = { Composer };
Expand Down Expand Up @@ -69,6 +71,12 @@ export class StandaloneComposer extends Component<Props, SpreadsheetChildEnv> {
setCurrentContent: this.standaloneComposerStore.setCurrentContent,
stopEdition: this.standaloneComposerStore.stopEdition,
};
onMounted(() => {
if (this.props.autofocus && this.focus === "inactive") {
this.composerFocusStore.focusComposer(this.composerInterface, {});
this.composerFocusStore.activeComposer.editionMode;
}
});
}

get focus(): ComposerFocusType {
Expand Down
5 changes: 5 additions & 0 deletions src/components/selection_input/selection_input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface Props {
ranges: string[];
hasSingleRange?: boolean;
required?: boolean;
autofocus?: boolean;
isInvalid?: boolean;
class?: string;
onSelectionChanged?: (ranges: string[]) => void;
Expand Down Expand Up @@ -50,6 +51,7 @@ export class SelectionInput extends Component<Props, SpreadsheetChildEnv> {
ranges: Array,
hasSingleRange: { type: Boolean, optional: true },
required: { type: Boolean, optional: true },
autofocus: { type: Boolean, optional: true },
isInvalid: { type: Boolean, optional: true },
class: { type: String, optional: true },
onSelectionChanged: { type: Function, optional: true },
Expand Down Expand Up @@ -105,6 +107,9 @@ export class SelectionInput extends Component<Props, SpreadsheetChildEnv> {
this.props.colors,
this.props.disabledRanges
);
if (this.props.autofocus) {
this.store.focusById(this.store.selectionInputs[0]?.id);
}
onWillUpdateProps((nextProps) => {
if (nextProps.ranges.join() !== this.store.selectionInputValues.join()) {
this.triggerChange();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
t-key="state.rules.cellIs.operator"
criterion="genericCriterion"
onCriterionChanged.bind="onRuleValuesChanged"
autofocus="this.state.hasEditedCf"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it makes sense to add it for other CF as well ?
For instance ColorScale has a set iof inputs -
image
and so does Icon set (but that one is a bit far fetched

image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from a functional POV, probably yes it makes sense. From a technical POV, is it worth it ? I don't know...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's keep it as it is for now then

/>

<div class="o-section-subtitle pt-3">Formatting style</div>
Expand Down
8 changes: 5 additions & 3 deletions src/components/side_panel/criterion_form/criterion_form.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SpreadsheetChildEnv } from "@odoo/o-spreadsheet-engine/types/spreadsheet_env";
import { Component, onMounted } from "@odoo/owl";
import { Component } from "@odoo/owl";
import { useStore } from "../../../store_engine";
import { GenericCriterion } from "../../../types";
import { ComposerFocusStore } from "../../composer/composer_focus_store";
Expand All @@ -8,6 +8,7 @@ interface Props<T extends GenericCriterion> {
criterion: T;
onCriterionChanged: (criterion: T) => void;
disableFormulas?: boolean;
autofocus?: boolean;
}

export abstract class CriterionForm<
Expand All @@ -17,12 +18,13 @@ export abstract class CriterionForm<
criterion: Object,
onCriterionChanged: Function,
disableFormulas: { type: Boolean, optional: true },
autofocus: { type: Boolean, optional: true },
};
setup() {
const composerFocusStore = useStore(ComposerFocusStore);
onMounted(() => {
if (composerFocusStore.activeComposer.editionMode !== "inactive") {
composerFocusStore.activeComposer.stopEdition();
});
}
}

updateCriterion(criterion: Partial<T>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export class CriterionInput extends Component<Props, SpreadsheetChildEnv> {
setup() {
useEffect(
() => {
if (this.props.focused) {
this.inputRef.el!.focus();
if (this.props.focused && this.inputRef.el) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... can the input ref really be undefiend at useEffect ? Or is it just to avoid ! ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, if the input isn't displayed because it's a composer instead

this.inputRef.el.focus();
}
},
() => [this.props.focused, this.inputRef.el]
Expand Down Expand Up @@ -94,6 +94,7 @@ export class CriterionInput extends Component<Props, SpreadsheetChildEnv> {
defaultRangeSheetId: this.env.model.getters.getActiveSheetId(),
invalid: this.state.shouldDisplayError && !!this.errorMessage,
defaultStatic: true,
autofocus: this.props.focused,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
onValueChanged.bind="onValueChanged"
criterionType="props.criterion.type"
disableFormulas="props.disableFormulas"
focused="props.autofocus"
/>
</t>
</templates>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
onValueChanged.bind="onFirstValueChanged"
criterionType="props.criterion.type"
disableFormulas="props.disableFormulas"
focused="props.autofocus"
/>
<CriterionInput
value="props.criterion.values[1]"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
onValueChanged.bind="onValueChanged"
criterionType="props.criterion.type"
disableFormulas="props.disableFormulas"
focused="props.autofocus"
/>
</t>
</templates>
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class ListCriterionForm extends CriterionForm<IsValueInListCriterion> {

state = useState<State>({
numberOfValues: Math.max(this.props.criterion.values.length, 2),
focusedValueIndex: this.props.autofocus ? 0 : undefined,
});

setup() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
onSelectionChanged="(ranges) => this.onRangeChanged(ranges[0])"
required="true"
hasSingleRange="true"
autofocus="props.autofocus"
/>
<t t-foreach="values" t-as="value" t-key="value_index">
<div class="o-dv-list-values p-1 d-flex align-items-center">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface Props {
interface State {
rule: DataValidationRuleData;
errors: CancelledReason[];
isTypeUpdated: boolean;
}

export class DataValidationEditor extends Component<Props, SpreadsheetChildEnv> {
Expand All @@ -44,7 +45,11 @@ export class DataValidationEditor extends Component<Props, SpreadsheetChildEnv>
onCloseSidePanel: { type: Function, optional: true },
};

state = useState<State>({ rule: this.defaultDataValidationRule, errors: [] });
state = useState<State>({
rule: this.defaultDataValidationRule,
errors: [],
isTypeUpdated: false,
});
private editingSheetId!: UID;

setup() {
Expand All @@ -62,6 +67,7 @@ export class DataValidationEditor extends Component<Props, SpreadsheetChildEnv>

onCriterionTypeChanged(type: DataValidationCriterionType) {
this.state.rule.criterion.type = type;
this.state.isTypeUpdated = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably have the same behaviour for CFs and in the data filter menu, no ? To confirm with frgi, but I don't see why implement it in DVs but not CFs

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's done 😉

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm blind

}

onRangesChanged(ranges: string[]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
t-key="state.rule.criterion.type"
criterion="state.rule.criterion"
onCriterionChanged.bind="onCriterionChanged"
autofocus="state.isTypeUpdated"
/>
</div>
</Section>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,9 @@ describe("UI of conditional formats", () => {

// change every value
setInputValueAndTrigger(selectors.ruleEditor.range, "A1:A3");
expect(".o-composer").not.toHaveClass("active");
await changeRuleOperatorType(fixture, "beginsWithText");
expect(".o-composer").toHaveClass("active");
editStandaloneComposer(selectors.ruleEditor.editor.valueInput, "3");

await click(fixture, selectors.ruleEditor.editor.bold);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,15 @@ describe("data validation sidePanel component", () => {
]);
});

test("date input is focused when criterion type is changed", async () => {
await simulateClick(".o-dv-add");
expect(document.activeElement).not.toBe(fixture.querySelector(".o-dv-input .o-composer"));
await changeCriterionType("dateIs");
setInputValueAndTrigger(".o-dv-date-value", "exactDate");
expect(".o-dv-input .o-composer").toHaveClass("active");
expect(document.activeElement).toBe(fixture.querySelector(".o-dv-input .o-composer"));
});

test("Invalid range", async () => {
await simulateClick(".o-dv-add");
await nextTick();
Expand Down Expand Up @@ -248,6 +257,28 @@ describe("data validation sidePanel component", () => {
);
});

test("single input is focused when changing type", async () => {
await simulateClick(".o-dv-add");
await nextTick();
expect(".o-dv-input .o-composer.active").toHaveCount(0);
await changeCriterionType("isEqualText");
expect(".o-dv-input .o-composer.active").toHaveCount(1);
expect(document.activeElement).toBe(fixture.querySelector(".o-dv-input .o-composer"));
expect(".o-dv-input .o-composer").toHaveClass("active");
});

test("first input of two is focused when changing type", async () => {
await simulateClick(".o-dv-add");
await nextTick();
expect(".o-dv-input .o-composer.active").toHaveCount(0);
await changeCriterionType("isBetween");
expect(".o-dv-input .o-composer.active").toHaveCount(1);
const composers = fixture.querySelectorAll(".o-dv-input .o-composer");
expect(document.activeElement).toBe(composers[0]);
expect(composers[0]).toHaveClass("active");
expect(composers[1]).not.toHaveClass("active");
});

test("Can make the rule blocking", async () => {
await simulateClick(".o-dv-add");
await nextTick();
Expand Down
18 changes: 18 additions & 0 deletions tests/data_validation/data_validation_list_component.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ describe("Edit criterion in side panel", () => {
expect(inputs[0].innerText).toBe("hola");
});

test("first input is focused when criterion type is changed", async () => {
expect(document.activeElement).not.toBe(fixture.querySelector(".o-dv-input input"));
await click(fixture, ".o-dv-type");
await click(fixture, `.o-menu-item[data-name="containsText"]`);
await click(fixture, ".o-dv-type");
await click(fixture, `.o-menu-item[data-name="isValueInList"]`);
expect(document.activeElement).toBe(fixture.querySelector(".o-dv-input input"));
});

test("Can add a new value", async () => {
await click(fixture, ".o-dv-list-add-value");
const inputs = fixture.querySelectorAll<HTMLInputElement>(".o-dv-list-values .o-input");
Expand Down Expand Up @@ -202,6 +211,15 @@ describe("Edit criterion in side panel", () => {
expect(getDataValidationRules(model)[0].criterion.values).toEqual(["B1:B9"]);
});

test("range input is focused when criterion type is changed", async () => {
expect(".o-dv-settings .o-selection-input input").not.toHaveClass("o-focused");
await click(fixture, ".o-dv-type");
await click(fixture, `.o-menu-item[data-name="containsText"]`);
await click(fixture, ".o-dv-type");
await click(fixture, `.o-menu-item[data-name="isValueInRange"]`);
expect(".o-dv-settings .o-selection-input input").toHaveClass("o-focused");
});

test("Can change display style", () => {
const displayStyleInput = fixture.querySelector<HTMLInputElement>(".o-dv-display-style");
setInputValueAndTrigger(displayStyleInput, "plainText");
Expand Down
30 changes: 20 additions & 10 deletions tests/setup/jest_extend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,16 +268,26 @@ CancelledReasons: ${this.utils.printReceived(dispatchResult.reasons)}
return { pass: false, message: () => message };
}
const pass = element.classList.contains(expectedClass);
const message = () =>
pass
? ""
: `expect(target).toHaveClass(expected);\n\n${this.utils.printDiffOrStringify(
expectedClass,
element.className,
"Expected class",
"Received class",
false
)}`;
const message = () => {
if (this.isNot && pass) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we do something generic ? All of the other custom matchers are also wrong ...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's do this in another task

return `expect(target).not.toHaveClass(expected);\n\n${this.utils.printDiffOrStringify(
expectedClass,
element.className,
"Unexpected class",
"Received class",
false
)}`;
} else if (!pass) {
return `expect(target).toHaveClass(expected);\n\n${this.utils.printDiffOrStringify(
expectedClass,
element.className,
"Expected class",
"Received class",
false
)}`;
}
return "";
};
return { pass, message };
},
toHaveAttribute(target: DOMTarget, attribute: string, expectedValue: string) {
Expand Down