diff --git a/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.js b/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.js index 180ad4529cb..5251c07a4d8 100644 --- a/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.js +++ b/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.js @@ -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"); diff --git a/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.scss b/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.scss index 6eb7eb4a5f5..8ba997bd7bf 100644 --- a/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.scss +++ b/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.scss @@ -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; } diff --git a/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.xml b/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.xml index 17e89ea33a2..8ffdb031c0a 100644 --- a/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.xml +++ b/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.xml @@ -2,22 +2,27 @@ - -
- +
+ +
+ +
diff --git a/addons/web/static/src/views/fields/many2many_tags/tags_list.scss b/addons/web/static/src/views/fields/many2many_tags/tags_list.scss index 09e804adc44..6d28cb5e12d 100644 --- a/addons/web/static/src/views/fields/many2many_tags/tags_list.scss +++ b/addons/web/static/src/views/fields/many2many_tags/tags_list.scss @@ -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%; + } } diff --git a/addons/web/static/src/views/fields/many2many_tags/tags_list.xml b/addons/web/static/src/views/fields/many2many_tags/tags_list.xml index 3a90e907858..001b41d473b 100644 --- a/addons/web/static/src/views/fields/many2many_tags/tags_list.xml +++ b/addons/web/static/src/views/fields/many2many_tags/tags_list.xml @@ -2,22 +2,20 @@ - - - - -
- - -
- - - - - - - + + + +
+ + +
+ + + + + + diff --git a/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.js b/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.js index e13e4388ae2..568de73a6e1 100644 --- a/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.js +++ b/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.js @@ -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, })); } } diff --git a/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.scss b/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.scss index a1b1feffb8a..490a1cd8307 100644 --- a/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.scss +++ b/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.scss @@ -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; } } diff --git a/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.xml b/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.xml index b72bf8a5f5d..d8b6cf8df0d 100644 --- a/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.xml +++ b/addons/web/static/src/views/fields/many2many_tags_avatar/many2many_tags_avatar_field.xml @@ -2,21 +2,26 @@ - -
- +
+ +
+ +
diff --git a/addons/web/static/tests/views/fields/many2many_tags_avatar_field_tests.js b/addons/web/static/tests/views/fields/many2many_tags_avatar_field_tests.js index ba3527f9b4c..1a47022ec2d 100644 --- a/addons/web/static/tests/views/fields/many2many_tags_avatar_field_tests.js +++ b/addons/web/static/tests/views/fields/many2many_tags_avatar_field_tests.js @@ -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: ` +
+ + + +
`, + }); + + 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" + ); + }); }); diff --git a/addons/web/static/tests/views/fields/one2many_field_tests.js b/addons/web/static/tests/views/fields/one2many_field_tests.js index 27b8fb81456..47c3e604680 100644 --- a/addons/web/static/tests/views/fields/one2many_field_tests.js +++ b/addons/web/static/tests/views/fields/one2many_field_tests.js @@ -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(