Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(kfileupload, kselect, kmultiselect): kinput id [KHCP-12336] #2241

Merged
merged 11 commits into from
Jun 19, 2024
47 changes: 37 additions & 10 deletions src/components/KFileUpload/KFileUpload.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
<template>
<div class="k-file-upload">
<div
class="k-file-upload"
v-bind="modifiedAttrs"
>
<KLabel
v-if="label"
v-bind-once="{ for: inputId }"
v-bind="labelAttributes"
ref="labelElement"
:for="$attrs.id ? String($attrs.id) : undefined"
:required="isRequired"
>
{{ strippedLabel }}
Expand All @@ -26,9 +30,9 @@
</span>

<KInput
v-bind="attrs"
Leopoldthecoder marked this conversation as resolved.
Show resolved Hide resolved
:key="fileInputKey"
ref="fileInputElement"
v-bind-once="{ id: inputId }"
:accept="accept"
class="upload-input"
:disabled="disabled"
Expand Down Expand Up @@ -64,14 +68,19 @@
</div>
</template>

<script lang="ts">
export default {
inheritAttrs: false,
}
</script>

Leopoldthecoder marked this conversation as resolved.
Show resolved Hide resolved
<script lang="ts" setup>
import type { PropType } from 'vue'
import { computed, ref, useAttrs, useSlots } from 'vue'
import { computed, ref, useAttrs, useSlots, onMounted } from 'vue'
import KLabel from '@/components/KLabel/KLabel.vue'
import KInput from '@/components/KInput/KInput.vue'
import KButton from '@/components/KButton/KButton.vue'
import useUtilities from '@/composables/useUtilities'
import useUniqueId from '@/composables/useUniqueId'

const props = defineProps({
labelAttributes: {
Expand Down Expand Up @@ -127,8 +136,16 @@ const emit = defineEmits<{

const { stripRequiredLabel } = useUtilities()

const inputId = attrs.id ? String(attrs.id) : useUniqueId()
const modifiedAttrs = computed(() => {
const $attrs = { ...attrs }

delete $attrs.id // delete id because we bind id to the input element

return $attrs
})

const fileInputElement = ref<InstanceType<typeof KInput> | null>(null)
const labelElement = ref<InstanceType<typeof KLabel> | null>(null)
const hasLabelTooltip = computed((): boolean => !!(props.labelAttributes?.info || slots['label-tooltip']))
const strippedLabel = computed((): string => stripRequiredLabel(props.label, isRequired.value))
const isRequired = computed((): boolean => attrs?.required !== undefined && String(attrs?.required) !== 'false')
Expand Down Expand Up @@ -235,6 +252,20 @@ const resetInput = (): void => {

emit('file-removed')
}

onMounted(() => {
/**
* Temporary fix for the issue where we can't use v-bind-once to pass id to a custom element (KInput)
* TODO: remove this once useId is released in Vue 3.5
*/
if (!attrs.id) {
const inputElementId = fileInputElement.value?.$el?.querySelector('input')?.id

if (inputElementId) {
labelElement.value?.$el.setAttribute('for', inputElementId)
}
}
})
</script>

<style lang="scss" scoped>
Expand All @@ -260,10 +291,6 @@ $kFileUploadInputPaddingY: var(--kui-space-40, $kui-space-40); // corresponds to
color: transparent !important;
}

:deep(.k-input) {
padding-right: 90px !important; // offset to account for button
}

.file-upload-input-wrapper {
position: relative;

Expand Down
16 changes: 8 additions & 8 deletions src/components/KMultiselect/KMultiselect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
>
<div v-if="collapsedContext">
<KInput
:id="$attrs.id ? String($attrs.id) : undefined"
ref="multiselectInputElement"
v-bind-once="{ id: multiselectId }"
autocapitalize="off"
autocomplete="off"
class="multiselect-input"
Expand Down Expand Up @@ -80,7 +80,7 @@
>
<KBadge
v-for="item, idx in visibleSelectedItems"
:key="`${multiselectId}-${item.key ? item.key : idx}-badge-${key}`"
:key="`${multiselectKey}-${item.key ? item.key : idx}-badge-${key}`"
:appearance="getBadgeAppearance(item)"
class="multiselect-selection-badge"
:icon-before="false"
Expand Down Expand Up @@ -159,8 +159,8 @@
>
<KInput
v-bind="modifiedAttrs"
:id="$attrs.id ? String($attrs.id) : undefined"
ref="multiselectDropdownInputElement"
v-bind-once="{ id: multiselectId }"
autocapitalize="off"
autocomplete="off"
class="multiselect-dropdown-input"
Expand Down Expand Up @@ -254,7 +254,7 @@
>
<KBadge
v-for="item, idx in visibleSelectedItemsStaging"
:key="`${multiselectId}-${item.key ? item.key : idx}-badge`"
:key="`${multiselectKey}-${item.key ? item.key : idx}-badge`"
aria-hidden="true"
class="multiselect-selection-badge"
:icon-before="false"
Expand Down Expand Up @@ -478,11 +478,11 @@ const defaultKPopAttributes = {
const key = ref(0)
const stagingKey = ref(0)

const multiselectWrapperId = useUniqueId() // unique id for the KPop target
const multiselectId = attrs.id ? String(attrs.id) : useUniqueId()
const multiselectWrapperId = useUniqueId() // unique id for the KLabel `for` attribute
const multiselectKey = useUniqueId()

const multiselectElement = ref<HTMLDivElement | null>(null)
const multiselectInputElement = ref<HTMLDivElement | null>(null)
const multiselectInputElement = ref<InstanceType<typeof KInput> | null>(null)
const multiselectDropdownInputElement = ref<HTMLDivElement | null>(null)
const multiselectSelectionsStagingElement = ref<HTMLDivElement>()

Expand Down Expand Up @@ -635,7 +635,7 @@ const handleToggle = async (open: boolean, isToggled: Ref<boolean>, toggle: () =

await nextTick()

const input = document?.getElementById(multiselectId) as HTMLInputElement
const input = multiselectInputElement.value?.$el.querySelector('input') as HTMLInputElement
input?.focus({ preventScroll: true })
}
} else {
Expand Down
21 changes: 18 additions & 3 deletions src/components/KSelect/KSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
>
<KLabel
v-if="label"
v-bind-once="{ for: selectId }"
ref="labelElement"
v-bind="labelAttributes"
data-testid="select-label"
:for="$attrs.id ? String($attrs.id) : undefined"
:required="isRequired"
>
{{ strippedLabel }}
Expand Down Expand Up @@ -39,7 +40,7 @@
@click="onSelectWrapperClick"
>
<KInput
v-bind-once="{ id: selectId }"
ref="fileInputElement"
autocapitalize="off"
autocomplete="off"
class="select-input"
Expand Down Expand Up @@ -363,6 +364,9 @@ const defaultKPopAttributes = {
hideCaret: true,
}

const fileInputElement = ref<InstanceType<typeof KInput> | null>(null)
const labelElement = ref<InstanceType<typeof KLabel> | null>(null)

const strippedLabel = computed((): string => stripRequiredLabel(props.label, isRequired.value))

const filterQuery = ref<string>('')
Expand All @@ -382,7 +386,6 @@ const uniqueFilterQuery = computed((): boolean => {

const selectWrapperId = useUniqueId() // unique id for the KPop target
const selectedItem = ref<SelectItem | null>(null)
const selectId = attrs.id ? String(attrs.id) : useUniqueId()
const selectItems = ref<SelectItem[]>([])
const inputFocused = ref<boolean>(false)

Expand Down Expand Up @@ -672,6 +675,18 @@ onMounted(() => {

resizeObserver.value.observe(selectWrapperElement.value as HTMLDivElement)
}

/**
* Temporary fix for the issue where we can't use v-bind-once to pass id to a custom element (KInput)
* TODO: remove this once useId is released in Vue 3.5
*/
if (!attrs.id) {
const inputElementId = fileInputElement.value?.$el?.querySelector('input')?.id

if (inputElementId) {
labelElement.value?.$el.setAttribute('for', inputElementId)
}
}
})

onUnmounted(() => {
Expand Down