[IMP] web: DomainSelector: enhance user experience
The purpose of this commit is to improve the domain selector UX with multiple little changes: - The model field selector popover is now autofocused when opened - Removal of a div that appeared during a hover on "add leaf" and "ellipsis button" - Caret icon next to the root node selector - Minor CSS changes task-2861497 closes odoo/odoo#99321 Signed-off-by: Géry Debongnie <ged@odoo.com>
This commit is contained in:
@@ -17,4 +17,8 @@ DomainSelectorBranchOperator.template = "web.DomainSelectorBranchOperator";
|
||||
DomainSelectorBranchOperator.props = {
|
||||
node: Object,
|
||||
readonly: Boolean,
|
||||
showCaret: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,23 +3,23 @@
|
||||
|
||||
<t t-name="web.DomainSelectorBranchOperator" owl="1">
|
||||
<t t-if="!props.readonly">
|
||||
<div class="btn-group o_domain_tree_operator_selector" aria-atomic="true">
|
||||
<Dropdown togglerClass="'btn btn-sm btn-primary o_domain_tree_operator_caret'">
|
||||
<div class="d-inline-flex o_domain_tree_operator_selector" aria-atomic="true">
|
||||
<Dropdown togglerClass="'btn btn-link btn-sm btn-primary py-0 px-1 o_domain_tree_operator_caret'" showCaret="props.showCaret">
|
||||
<t t-set-slot="toggler">
|
||||
<t t-if="props.node.operator === '&'">All</t>
|
||||
<t t-elif="props.node.operator === '|'">Any</t>
|
||||
<t t-elif="props.node.operator === '!'">None</t>
|
||||
<t t-if="props.node.operator === '&'">all</t>
|
||||
<t t-elif="props.node.operator === '|'">any</t>
|
||||
<t t-elif="props.node.operator === '!'">none</t>
|
||||
</t>
|
||||
<DropdownItem onSelected="() => this.onOperatorSelected('&')">All</DropdownItem>
|
||||
<DropdownItem onSelected="() => this.onOperatorSelected('|')">Any</DropdownItem>
|
||||
<DropdownItem onSelected="() => this.onOperatorSelected('&')">all</DropdownItem>
|
||||
<DropdownItem onSelected="() => this.onOperatorSelected('|')">any</DropdownItem>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<strong>
|
||||
<t t-if="props.node.operator === '&'">ALL</t>
|
||||
<t t-elif="props.node.operator === '|'">ANY</t>
|
||||
<t t-elif="props.node.operator === '!'">NONE</t>
|
||||
<t t-if="props.node.operator === '&'">all</t>
|
||||
<t t-elif="props.node.operator === '|'">any</t>
|
||||
<t t-elif="props.node.operator === '!'">none</t>
|
||||
</strong>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<t t-set="field" t-value="getFieldComponent(fieldInfo.type)" />
|
||||
<t t-if="field">
|
||||
<div>
|
||||
<select class="o_domain_leaf_operator_select o_input" t-on-change="onOperatorChange">
|
||||
<select class="o_domain_leaf_operator_select o_input text-truncate pe-1" t-on-change="onOperatorChange">
|
||||
<t t-foreach="field.getOperators()" t-as="operator" t-key="operator.value">
|
||||
<option
|
||||
t-att-value="operator_index"
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span>Match records with </span>
|
||||
<DomainSelectorBranchOperator node="node" readonly="props.readonly" />
|
||||
<DomainSelectorBranchOperator node="node" readonly="props.readonly" showCaret="true" />
|
||||
<span> of the following rules:</span>
|
||||
<div class="o_domain_node_children_container">
|
||||
<t t-foreach="node.operands" t-as="subNode" t-key="subNode.id">
|
||||
|
||||
@@ -91,6 +91,7 @@ export class ModelFieldSelector extends Component {
|
||||
},
|
||||
{
|
||||
closeOnClickAway: true,
|
||||
popoverClass: "o-popover-no-arrow",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { sortBy } from "../utils/arrays";
|
||||
import { useModelField } from "./model_field_hook";
|
||||
|
||||
import { fuzzyLookup } from "@web/core/utils/search";
|
||||
import { useAutofocus } from "../utils/hooks";
|
||||
|
||||
const { Component, onWillStart } = owl;
|
||||
|
||||
@@ -15,6 +16,9 @@ export class ModelFieldSelectorPopover extends Component {
|
||||
this.fieldKeys = [];
|
||||
this.searchValue = "";
|
||||
this.fullFieldName = this.fieldNameChain.join(".");
|
||||
if (!this.env.isSmall) {
|
||||
useAutofocus();
|
||||
}
|
||||
|
||||
onWillStart(async () => {
|
||||
await this.loadFields();
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<t t-if="props.showSearchInput">
|
||||
<div class="o_field_selector_search mt-2">
|
||||
<input type="text" placeholder='Search...' class="o_input" t-att-value="searchValue" t-on-input="onSearch" />
|
||||
<input type="text" placeholder='Search...' class="o_input" t-att-value="searchValue" t-on-input="onSearch" t-ref="autofocus" />
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
@@ -66,5 +66,13 @@
|
||||
&.o-popover--le .popover-arrow, &.o-popover--re .popover-arrow {
|
||||
bottom: $popover-border-radius;
|
||||
}
|
||||
|
||||
&.o-popover-no-arrow > .popover-arrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.o-popover-no-arrow {
|
||||
border: 0px;
|
||||
}
|
||||
}
|
||||
/*!rtl:end:ignore*/
|
||||
|
||||
@@ -42,13 +42,12 @@
|
||||
&.o_domain_tree {
|
||||
> .o_domain_tree_header {
|
||||
position: relative;
|
||||
|
||||
.o_domain_tree_operator_caret::after {
|
||||
@include o-caret-down;
|
||||
}
|
||||
}
|
||||
.o_domain_tree_operator_caret::after {
|
||||
@include o-caret-down;
|
||||
}
|
||||
|
||||
> .o_domain_node_children_container {
|
||||
& .o_domain_node_children_container {
|
||||
padding-left: $o-domain-selector-indent;
|
||||
}
|
||||
|
||||
@@ -180,21 +179,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.o_hover_btns {
|
||||
background: darken(white, 3%);
|
||||
}
|
||||
|
||||
&.o_hover_add_node {
|
||||
margin-bottom: $o-domain-animation-bar-height;
|
||||
transition: margin .15s ease .5s;
|
||||
|
||||
&::after {
|
||||
max-height: $o-domain-animation-bar-height;
|
||||
transition: max-height .15s ease .5s;
|
||||
}
|
||||
&.o_hover_add_inset_node::after {
|
||||
left: $o-domain-selector-indent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ QUnit.module("Components", (hooks) => {
|
||||
QUnit.module("ModelFieldSelector");
|
||||
|
||||
QUnit.test("creating a field chain from scratch", async (assert) => {
|
||||
assert.expect(14);
|
||||
assert.expect(15);
|
||||
|
||||
function getValueFromDOM(el) {
|
||||
return [...el.querySelectorAll(".o_field_selector_chain_part")]
|
||||
@@ -101,6 +101,11 @@ QUnit.module("Components", (hooks) => {
|
||||
|
||||
// Focusing the field selector input should open a field selector popover
|
||||
await click(target, ".o_field_selector");
|
||||
assert.strictEqual(
|
||||
target.querySelector("input.o_input[placeholder='Search...']"),
|
||||
document.activeElement,
|
||||
"the field selector input should be focused"
|
||||
);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_selector_popover",
|
||||
|
||||
Reference in New Issue
Block a user