[FIX] web: restore correct m2m_tags style
This commit completes the style of the tags of m2m_tags field. Tags did not have correct colors and had border. The commit also fixes the m2m_tags_avatar field for the same reason as well as a traceback when clicking on the remove tag button. closes odoo/odoo#96654 Related: odoo/enterprise#30012 Signed-off-by: Bruno Boi (boi) <boi@odoo.com>
This commit is contained in:
@@ -174,8 +174,8 @@ export class Many2ManyTagsField extends Component {
|
||||
}
|
||||
|
||||
focusTag(index) {
|
||||
const tagListEl = this.autoCompleteRef.el.previousElementSibling;
|
||||
const tags = tagListEl.getElementsByClassName("badge");
|
||||
const autoCompleteParent = this.autoCompleteRef.el.parentElement;
|
||||
const tags = autoCompleteParent.getElementsByClassName("badge");
|
||||
if (tags.length) {
|
||||
if (index === undefined) {
|
||||
tags[tags.length - 1].focus();
|
||||
@@ -235,8 +235,8 @@ export class Many2ManyTagsField extends Component {
|
||||
return;
|
||||
}
|
||||
const hotkey = getActiveHotkey(ev);
|
||||
const tagListEl = this.autoCompleteRef.el.previousElementSibling;
|
||||
const tags = [...tagListEl.getElementsByClassName("badge")];
|
||||
const autoCompleteParent = this.autoCompleteRef.el.parentElement;
|
||||
const tags = [...autoCompleteParent.getElementsByClassName("badge")];
|
||||
const closestTag = ev.target.closest(".badge");
|
||||
const tagIndex = tags.indexOf(closestTag);
|
||||
const input = this.autoCompleteRef.el.querySelector(".o-autocomplete--input");
|
||||
|
||||
@@ -6,10 +6,75 @@
|
||||
line-height: $o-line-height-base;
|
||||
}
|
||||
|
||||
.o_field_widget.o_field_many2many_tags .o_input {
|
||||
height: 100%;
|
||||
.o_field_widget.o_field_many2many_tags {
|
||||
flex-flow: row wrap;
|
||||
|
||||
.o_tags_input {
|
||||
padding: 1px 0;
|
||||
|
||||
.o_tag {
|
||||
padding-left: 0.6em;
|
||||
padding-right: 0.6em;
|
||||
}
|
||||
}
|
||||
|
||||
.o_field_many2many_selection {
|
||||
flex: 1 0 100px;
|
||||
|
||||
.o_input {
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
flex: 0 0 auto;
|
||||
margin: 1px 2px 1px 0;
|
||||
border: none;
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
|
||||
&.dropdown {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.o_badge_text, .o_tag_badge_text {
|
||||
@include o-text-overflow(inline-block);
|
||||
max-width: 200px;
|
||||
color: inherit;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.o_delete {
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
padding-left: 4px;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_list_view .o_field_widget.o_field_many2many_tags .o_field_many2many_selection {
|
||||
flex-basis: 40px;
|
||||
.o_list_view .o_field_widget.o_field_many2many_tags {
|
||||
.o_tags_input {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.o_field_many2many_selection {
|
||||
flex-basis: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_form_view .o_group .o_field_tags {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.o_form_statusbar .o_field_tags {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@@ -2,22 +2,27 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.Many2ManyTagsField" owl="1">
|
||||
<TagsList tags="tags" t-on-keydown="onTagsKeydown"/>
|
||||
<div t-if="showM2OSelectionField" class="o_field_many2many_selection d-inline-block mw-100" t-on-keydown="onAutoCompleteKeydown" t-ref="autoComplete">
|
||||
<Many2XAutocomplete
|
||||
id="props.id"
|
||||
placeholder="props.placeholder"
|
||||
resModel="props.relation"
|
||||
autoSelect="true"
|
||||
fieldString="string"
|
||||
activeActions="activeActions"
|
||||
update="update"
|
||||
quickCreate="activeActions.canCreate ? quickCreate : null"
|
||||
context="context"
|
||||
getDomain.bind="getDomain"
|
||||
isToMany="true"
|
||||
nameCreateField="props.nameCreateField"
|
||||
/>
|
||||
<div
|
||||
class="o_field_tags d-inline-flex flex-wrap mw-100"
|
||||
t-att-class="{'o_tags_input o_input': !props.readonly}"
|
||||
>
|
||||
<TagsList tags="tags" t-on-keydown="onTagsKeydown"/>
|
||||
<div t-if="showM2OSelectionField" class="o_field_many2many_selection d-inline-flex w-100" t-on-keydown="onAutoCompleteKeydown" t-ref="autoComplete">
|
||||
<Many2XAutocomplete
|
||||
id="props.id"
|
||||
placeholder="props.placeholder"
|
||||
resModel="props.relation"
|
||||
autoSelect="true"
|
||||
fieldString="string"
|
||||
activeActions="activeActions"
|
||||
update="update"
|
||||
quickCreate="activeActions.canCreate ? quickCreate : null"
|
||||
context="context"
|
||||
getDomain.bind="getDomain"
|
||||
isToMany="true"
|
||||
nameCreateField="props.nameCreateField"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -1,49 +1,66 @@
|
||||
.o_tags_list {
|
||||
gap: 2px;
|
||||
.o_tag {
|
||||
user-select: none;
|
||||
|
||||
.o_tag {
|
||||
user-select: none;
|
||||
|
||||
.o_badge_text, a {
|
||||
color: inherit;
|
||||
.o_badge_text, a {
|
||||
color: inherit;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.o_tag_badge_text {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 1.6em;
|
||||
aspect-ratio: 1/1;
|
||||
|
||||
+ .o_tag_badge_text {
|
||||
padding: 0.25em 0;
|
||||
}
|
||||
|
||||
.o_tag_badge_text {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 1.6em;
|
||||
aspect-ratio: 1/1;
|
||||
|
||||
+ .o_tag_badge_text {
|
||||
padding: 0.25em 0;
|
||||
}
|
||||
}
|
||||
|
||||
@for $size from 1 through length($o-colors) {
|
||||
&.o_tag_color_#{$size - 1} {
|
||||
@if $size == 1 {
|
||||
& {
|
||||
background-color: $o-view-background-color;
|
||||
color: nth($o-colors, $size);
|
||||
}
|
||||
&:focus-within {
|
||||
color: darken(nth($o-colors, $size), 40%) ;
|
||||
}
|
||||
&::after {
|
||||
background-color: nth($o-colors, $size);
|
||||
}
|
||||
} @else {
|
||||
&, &::after {
|
||||
background-color: nth($o-colors, $size);
|
||||
color: color-contrast(nth($o-colors, $size));
|
||||
}
|
||||
&:focus-within {
|
||||
background-color: darken(nth($o-colors, $size), 20%);
|
||||
}
|
||||
}
|
||||
|
||||
@for $size from 1 through length($o-colors) {
|
||||
&.o_tag_color_#{$size - 1} {
|
||||
@if $size == 1 {
|
||||
& {
|
||||
background-color: $o-view-background-color;
|
||||
color: nth($o-colors, $size);
|
||||
box-shadow: inset 0 0 0 1px;
|
||||
}
|
||||
&:focus-within {
|
||||
color: darken(nth($o-colors, $size), 40%) ;
|
||||
}
|
||||
&::after {
|
||||
background-color: nth($o-colors, $size);
|
||||
}
|
||||
} @else {
|
||||
&, &::after {
|
||||
background-color: nth($o-colors, $size);
|
||||
color: color-contrast(nth($o-colors, $size));
|
||||
}
|
||||
&:focus-within {
|
||||
background-color: darken(nth($o-colors, $size), 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_kanban_view .o_kanban_record .o_tag {
|
||||
display: inline-block;
|
||||
margin-right: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
box-shadow: none;
|
||||
@include o-kanban-tag-color;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
margin-right: 4px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,20 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.TagsList" owl="1">
|
||||
<span t-if="visibleTags.length" t-attf-class="o_tags_list {{props.className}} d-inline-flex flex-wrap" t-att-name="props.name">
|
||||
<t t-if="props.tags" t-foreach="visibleTags" t-as="tag" t-key="tag.id or tag_index">
|
||||
<span t-attf-class="{{`o_tag_color_${ tag.colorIndex or 0 } ${props.displayBadge ? 'badge rounded-pill' : ''}`}} o_tag d-inline-flex align-items-center h-100" tabindex="-1" t-att-data-color="tag.colorIndex" t-att-title="tag.text" t-on-click="(ev) => tag.onClick and tag.onClick(ev)">
|
||||
<img t-if="tag.img" t-att-src="tag.img" class="o_m2m_avatar rounded-circle"/>
|
||||
<div t-if="props.displayBadge" class="o_tag_badge_text" t-esc="tag.text" />
|
||||
<t t-else="">
|
||||
<span />
|
||||
<div t-esc="tag.text" />
|
||||
</t>
|
||||
<a tabIndex="-1" t-if="tag.onDelete" t-on-click.stop.prevent="(ev) => tag.onDelete and tag.onDelete(ev)" href="#" class="fa fa-times o_delete ms-2" title="Delete" aria-label="Delete"></a>
|
||||
</span>
|
||||
</t>
|
||||
<span t-if="props.tags and otherTags.length" class="o_m2m_avatar_empty rounded-circle text-center fw-bold" t-att-data-tooltip="tooltip">
|
||||
<span t-if="otherTags.length > 9" t-esc="'9+'" />
|
||||
<span t-else="" t-esc="'+' + otherTags.length" />
|
||||
<t t-foreach="visibleTags" t-as="tag" t-key="tag.id or tag_index">
|
||||
<span t-attf-class="{{`o_tag_color_${ tag.colorIndex or 0 } ${props.displayBadge ? 'badge rounded-pill' : ''}`}} o_tag d-inline-flex align-items-center h-100" tabindex="-1" t-att-data-color="tag.colorIndex" t-att-title="tag.text" t-on-click="(ev) => tag.onClick and tag.onClick(ev)">
|
||||
<img t-if="tag.img" t-att-src="tag.img" class="o_m2m_avatar rounded-circle"/>
|
||||
<div t-if="props.displayBadge" class="o_tag_badge_text" t-esc="tag.text" />
|
||||
<t t-else="">
|
||||
<span />
|
||||
<div t-esc="tag.text" />
|
||||
</t>
|
||||
<a tabIndex="-1" t-if="tag.onDelete" t-on-click.stop.prevent="(ev) => tag.onDelete and tag.onDelete(ev)" href="#" class="fa fa-times o_delete" title="Delete" aria-label="Delete"></a>
|
||||
</span>
|
||||
</t>
|
||||
<span t-if="props.tags and otherTags.length" class="o_m2m_avatar_empty rounded-circle text-center fw-bold" t-att-data-tooltip="tooltip">
|
||||
<span t-if="otherTags.length > 9" t-esc="'9+'" />
|
||||
<span t-else="" t-esc="'+' + otherTags.length" />
|
||||
</span>
|
||||
</t>
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ export class Many2ManyTagsAvatarField extends Many2ManyTagsField {
|
||||
id: record.id, // datapoint_X
|
||||
text: record.data.display_name,
|
||||
img: `/web/image/${this.props.relation}/${record.resId}/avatar_128`,
|
||||
onDelete: !this.props.readonly ? () => this.onDelete(record.resId) : undefined,
|
||||
onDelete: !this.props.readonly ? () => this.deleteTag(record.id) : undefined,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
+77
-4
@@ -1,9 +1,82 @@
|
||||
.o_field_many2many_tags_avatar {
|
||||
.o_tag {
|
||||
padding: 0;
|
||||
.o_field_widget.o_field_many2many_tags_avatar {
|
||||
flex-flow: row wrap;
|
||||
|
||||
.o_tags_input {
|
||||
padding: 1px 0;
|
||||
}
|
||||
|
||||
.o_field_many2many_selection {
|
||||
flex: 1 0 100px;
|
||||
|
||||
.o_input {
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.o_dropdown_button {
|
||||
top: $o-input-padding-y;
|
||||
}
|
||||
|
||||
.badge {
|
||||
border: 0;
|
||||
flex: 0 0 auto;
|
||||
margin: 1px 2px 1px 0;
|
||||
border: none;
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
padding-right: 0.6rem;
|
||||
box-shadow: inset 0 0 0 1px;
|
||||
|
||||
&.dropdown {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.o_badge_text {
|
||||
@include o-text-overflow(inline-block);
|
||||
max-width: 200px;
|
||||
color: inherit;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.o_delete {
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
padding-left: 4px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 1.4em;
|
||||
width: 1.4em;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.o_badge_text, .o_delete {
|
||||
padding-top: 0.25em;
|
||||
padding-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.o_tag_badge_text {
|
||||
padding-left:2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_list_view .o_field_widget.o_field_many2many_tags_avatar {
|
||||
.o_tags_input {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.o_field_many2many_selection {
|
||||
flex-basis: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
+20
-15
@@ -2,21 +2,26 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.Many2ManyTagsAvatarField" owl="1">
|
||||
<TagsList tags="tags" itemsVisible="itemsVisible" t-on-keydown="onTagsKeydown"/>
|
||||
<div t-if="showM2OSelectionField" class="o_field_many2many_selection d-inline-block mw-100" t-on-keydown="onAutoCompleteKeydown" t-ref="autoComplete">
|
||||
<Many2XAutocomplete
|
||||
id="props.id"
|
||||
placeholder="props.placeholder"
|
||||
resModel="props.relation"
|
||||
autoSelect="true"
|
||||
fieldString="props.string"
|
||||
activeActions="activeActions"
|
||||
update="update"
|
||||
quickCreate="activeActions.canCreate ? quickCreate : null"
|
||||
context="context"
|
||||
getDomain.bind="getDomain"
|
||||
isToMany="true"
|
||||
/>
|
||||
<div
|
||||
class="o_field_tags d-inline-flex flex-wrap mw-100"
|
||||
t-att-class="{'o_tags_input o_input': !props.readonly}"
|
||||
>
|
||||
<TagsList tags="tags" itemsVisible="itemsVisible" t-on-keydown="onTagsKeydown"/>
|
||||
<div t-if="showM2OSelectionField" class="o_field_many2many_selection d-inline-flex w-100" t-on-keydown="onAutoCompleteKeydown" t-ref="autoComplete">
|
||||
<Many2XAutocomplete
|
||||
id="props.id"
|
||||
placeholder="props.placeholder"
|
||||
resModel="props.relation"
|
||||
autoSelect="true"
|
||||
fieldString="props.string"
|
||||
activeActions="activeActions"
|
||||
update="update"
|
||||
quickCreate="activeActions.canCreate ? quickCreate : null"
|
||||
context="context"
|
||||
getDomain.bind="getDomain"
|
||||
isToMany="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { click, getFixture, selectDropdownItem } from "@web/../tests/helpers/utils";
|
||||
import {
|
||||
click,
|
||||
clickEdit,
|
||||
clickSave,
|
||||
getFixture,
|
||||
selectDropdownItem,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
let serverData;
|
||||
@@ -290,7 +296,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
);
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_kanban_record:nth-child(3) .o_field_many2many_tags_avatar .o_tags_list > span:not(.o_m2m_avatar_empty)",
|
||||
".o_kanban_record:nth-child(3) .o_field_many2many_tags_avatar .o_tag",
|
||||
2,
|
||||
"should have 2 records"
|
||||
);
|
||||
@@ -325,7 +331,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_kanban_record:nth-child(4) .o_field_many2many_tags_avatar .o_tags_list > span:not(.o_m2m_avatar_empty)",
|
||||
".o_kanban_record:nth-child(4) .o_field_many2many_tags_avatar .o_tag",
|
||||
2,
|
||||
"should have 2 records"
|
||||
);
|
||||
@@ -358,4 +364,43 @@ QUnit.module("Fields", (hooks) => {
|
||||
target.querySelector(".o_kanban_record .o_field_many2many_tags_avatar img.o_m2m_avatar")
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("widget many2many_tags_avatar delete tag", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "turtle",
|
||||
resId: 2,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="partner_ids" widget="many2many_tags_avatar"/>
|
||||
</sheet>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
await clickEdit(target);
|
||||
assert.containsN(
|
||||
target,
|
||||
".o_field_many2many_tags_avatar.o_field_widget .badge",
|
||||
2,
|
||||
"should have 2 records"
|
||||
);
|
||||
|
||||
await click(
|
||||
target.querySelector(".o_field_many2many_tags_avatar.o_field_widget .badge .o_delete")
|
||||
);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_many2many_tags_avatar.o_field_widget .badge",
|
||||
"should have 1 record"
|
||||
);
|
||||
|
||||
await clickSave(target);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_many2many_tags_avatar.o_field_widget .badge",
|
||||
"should have 1 record"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5947,7 +5947,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_many2many_tags .o_input",
|
||||
".o_field_many2many_tags .o_tags_input",
|
||||
"should display the line in editable mode"
|
||||
);
|
||||
assert.strictEqual(
|
||||
@@ -6000,7 +6000,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o_field_many2many_tags .o_input",
|
||||
".o_field_many2many_tags .o_tags_input",
|
||||
"should display the line in editable mode"
|
||||
);
|
||||
assert.strictEqual(
|
||||
|
||||
Reference in New Issue
Block a user