diff --git a/client/package.json b/client/package.json index d3d06ff51b73..a3a45a7fc868 100644 --- a/client/package.json +++ b/client/package.json @@ -105,6 +105,7 @@ "vue-router": "^3.6.5", "vue-rx": "^6.2.0", "vue-virtual-scroll-list": "^2.3.5", + "vue2-teleport": "^1.0.1", "vuedraggable": "^2.24.3", "vuex": "^3.6.2", "vuex-cache": "^3.4.0", diff --git a/client/src/components/History/Content/ContentItem.vue b/client/src/components/History/Content/ContentItem.vue index aeae2c6bff83..ddc2b2491a71 100644 --- a/client/src/components/History/Content/ContentItem.vue +++ b/client/src/components/History/Content/ContentItem.vue @@ -304,6 +304,7 @@ export default { .content-item { cursor: default; + container-type: inline-size; .name { word-break: break-all; diff --git a/client/src/components/TagsMultiselect/HeadlessMultiselect.vue b/client/src/components/TagsMultiselect/HeadlessMultiselect.vue new file mode 100644 index 000000000000..da8652f42fbe --- /dev/null +++ b/client/src/components/TagsMultiselect/HeadlessMultiselect.vue @@ -0,0 +1,371 @@ + + + + + + + + + + + + + + onOptionSelected(option)" + @keydown="(e) => onOptionKey(e, i)" + @mouseover="() => onOptionHover(i)" + @focusin="() => onOptionHover(i)" + @keydown.tab="(e) => onOptionTab(e, i)"> + + {{ option }} + + + + + + remove tag + + + + + + + + add tag + + + + + + + + diff --git a/client/src/components/TagsMultiselect/StatelessTags.vue b/client/src/components/TagsMultiselect/StatelessTags.vue index 0e1419e9258a..61bb6f5ebe18 100644 --- a/client/src/components/TagsMultiselect/StatelessTags.vue +++ b/client/src/components/TagsMultiselect/StatelessTags.vue @@ -3,15 +3,16 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faCheck, faPlus, faTags, faTimes } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; import { BButton } from "bootstrap-vue"; +import { storeToRefs } from "pinia"; import type { Ref } from "vue"; import { computed, nextTick, ref } from "vue"; import Multiselect from "vue-multiselect"; import { useToast } from "@/composables/toast"; -import { useMultiselect } from "@/composables/useMultiselect"; import { useUid } from "@/composables/utils/uid"; import { useUserTagsStore } from "@/stores/userTagsStore"; +import HeadlessMultiselect from "./HeadlessMultiselect.vue"; import Tag from "./Tag.vue"; interface StatelessTagsProps { @@ -39,14 +40,15 @@ const emit = defineEmits<{ library.add(faTags, faCheck, faTimes, faPlus); -const { userTags, addLocalTag } = useUserTagsStore(); +const userTagsStore = useUserTagsStore(); +const { userTags } = storeToRefs(userTagsStore); const { warning } = useToast(); function onAddTag(tag: string) { const newTag = tag.trim(); if (isValid(newTag)) { - addLocalTag(newTag); + userTagsStore.addLocalTag(newTag); emit("input", [...props.value, newTag]); } else { warning(`"${newTag}" is not a valid tag.`, "Invalid Tag"); @@ -64,7 +66,7 @@ function onDelete(tag: string) { emit("input", val); } -const { editing, ariaExpanded, onOpen, onClose } = useMultiselect(); +const editing = ref(false); const multiselectElement: Ref = ref(null); @@ -97,12 +99,8 @@ const slicedTags = computed(() => { const invalidTagRegex = /([.:\s][.:\s])|(^[.:])|([.:]$)|(^[\s]*$)/; -function isValid(tag: string | { label: string }) { - if (typeof tag === "string") { - return !tag.match(invalidTagRegex); - } else { - return !tag.label.match(invalidTagRegex); - } +function isValid(tag: string) { + return !tag.match(invalidTagRegex); } function onTagClicked(tag: string) { @@ -112,78 +110,31 @@ function onTagClicked(tag: string) { - - - - - - - {{ props.placeholder }} - - + + + - - - - - - - Type to add new tag - - - - - - - - - {{ option.label ?? option }} - - - - - - - - remove tag - - - - - - add tag - - - - - + :selected="tags" + :placeholder="props.placeholder" + :validator="isValid" + @close="editing = false" + @addOption="onAddTag" + @input="onInput" /> + + {{ props.placeholder }} + + @@ -242,7 +193,6 @@ function onTagClicked(tag: string) { } } - &:deep(.multiselect) .multiselect__input, .toggle-button { font-size: $font-size-base; color: $text-color; @@ -254,6 +204,7 @@ function onTagClicked(tag: string) { margin: 0; border: none; width: 100%; + height: 1.75rem; } &:deep(.multiselect) { diff --git a/client/src/stores/userTagsStore.ts b/client/src/stores/userTagsStore.ts index 47f717ddea0c..f2bb4047df58 100644 --- a/client/src/stores/userTagsStore.ts +++ b/client/src/stores/userTagsStore.ts @@ -23,5 +23,5 @@ export const useUserTagsStore = defineStore("userTagsStore", () => { localTags.value.push(tag); }; - return { userTags, addLocalTag }; + return { localTags, userTags, addLocalTag }; }); diff --git a/client/yarn.lock b/client/yarn.lock index 3081e69cb3cd..022d497b96ce 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -11675,6 +11675,11 @@ vue-virtual-scroll-list@^2.3.5: resolved "https://registry.yarnpkg.com/vue-virtual-scroll-list/-/vue-virtual-scroll-list-2.3.5.tgz#b589ac6245faf857c35090f854e59d653e90626c" integrity sha512-YFK6u5yltqtAOfTBcij/KGAS2SoZvzbNIAf9qTULauPObEp53xj22tDuohrrM2vNkgoD5kejXICIUBt2Q4ZDqQ== +vue2-teleport@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vue2-teleport/-/vue2-teleport-1.0.1.tgz#1b7f9f69c1223f522cf6cd81c39b8d6019e1cf66" + integrity sha512-hbY/Q0x8qXGFxo6h4KU4YYesUcN+uUjliqqC0PoNSgpcbS2QRb3qXi+7XMTgLYs0a8i7o1H6Mu43UV4Vbgkhgw== + vue@2.7.14, vue@^2.6.10, vue@^2.7.14: version "2.7.14" resolved "https://registry.yarnpkg.com/vue/-/vue-2.7.14.tgz#3743dcd248fd3a34d421ae456b864a0246bafb17" diff --git a/package.json b/package.json index 8f48deaef7e5..a32e81c9d212 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,6 @@ "version": "23.1.0", "description": ".. figure:: https://galaxyproject.org/images/galaxy-logos/galaxy_project_logo.jpg :alt: Galaxy Logo", "main": "index.js", - "devDependencies": {}, "scripts": { "stage": "cpy './node_modules/@galaxyproject/galaxy-client/dist/*' './static/dist'" },