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

Allow some component events bypass strictTemplate #4985

Open
minht11 opened this issue Nov 4, 2024 · 5 comments · May be fixed by #4987
Open

Allow some component events bypass strictTemplate #4985

minht11 opened this issue Nov 4, 2024 · 5 comments · May be fixed by #4987
Labels
feature request Request new features

Comments

@minht11
Copy link

minht11 commented Nov 4, 2024

What problem does this feature solve?

This is sort of like #4879 but even more granular.

For better or for worse props/emits compose in different ways in Vue. For example I can have component but not be sure what attributes it accepts, one example while keeping it type safe could like this:

interface Props {
	type: 'button'
	[k: `data-${string}`]: string
}
const { type, ...restProps } = defineProps<Props>()

If I want to compose props and only pass only state I need with v-bind and so on

<OtherComponent v-bind="restProps" />

The same cannot be said with vue events, while using defineEmits I need explicitely define all possible events or else when I enable strictTemplate things will break.

An actual example I am struggling with

// BaseButton.vue

defineEmits<{
	click: [e: MouseEvent]
	focusin: [e: FocusEvent]
	focusout: [e: FocusEvent]
	keyboard: [e: KeyboardEvent]
	mouse: [e: MouseEvent]
	touch: [e: TouchEvent]
	// ... and so on
}>()

<button
                 @click="$emit('click', $event)"
		@focusin="$emit('focusin', $event)"
		@focusout="$emit('focusout', $event)"
		@keydown="$emit('keyboard', $event)"
		@mousedown="$emit('mouse', $event)"
		@touchstart="$emit('touch', $event)"
>
  <slot />
</button>

I defined events I needed, but now actual event listeners are not lazy, they are actual event listeners even if parent component did not use any of them "mousemove" for example would still fire to the event listener vue registered.

Composability is another issue, lets say I have FancyButton which wraps BaseButton, to declare events I have to the whole:

<BaseButton
                 @click="$emit('click', $event)"
		@focusin="$emit('focusin', $event)"
		@focusout="$emit('focusout', $event)"
		@keydown="$emit('keyboard', $event)"
		@mousedown="$emit('mouse', $event)"
		@touchstart="$emit('touch', $event)"
>
  <slot />
</BaseButton>

Which quickly becomes hard to maintain.

Without enabling strictTemplate this issue is largely masked because component would accept any event, now not so much.

What does the proposed solution look like?

This is largely Vue limitation of how to compose events, but in mean time it would be great to allow disabling strict events on some components. I am not sure how it would look like maybe something with types, comment or tsconfig.json option. It would allow to retain most of benefits of strict templates, while still making it usable with design system components like buttons where you can't and shouldn't realistically write all of the possible events.

@minht11 minht11 added the feature request Request new features label Nov 4, 2024
@KazariEX
Copy link
Collaborator

KazariEX commented Nov 4, 2024

You can enable fallthroughAttributes.

@minht11
Copy link
Author

minht11 commented Nov 4, 2024

fallthroughAttributes will enable it globally while I want that only for few specific components
Edit: enabling fallthroughAttributes introduced bunch of unrelated errors, while keeping previous event listener problem :(

@KazariEX
Copy link
Collaborator

KazariEX commented Nov 4, 2024

If your button is not the single root node of the component (or if v-bind="$attrs" is not set on the button), this option will not take effect.

@KazariEX
Copy link
Collaborator

KazariEX commented Nov 4, 2024

Perhaps we can have a comment syntax or defineCompilerOptions to configure vueCompilerOptions locally.

enabling fallthroughAttributes introduced bunch of unrelated errors

Can you explain this in detail? If it's indeed a bug, please create a minimal reproduction that allows us to investigate it.

@minht11
Copy link
Author

minht11 commented Nov 4, 2024

If your button is not the single root node of the component (or if v-bind="$attrs" is not set on the button), this option will not take effect.

BaseButton is dynamic component which can be either a or button based on prop. Yeah I don't use $attrs, I explicitely define props I need and use interface extensions inside wrapper components and to v-bind=props to pass them from up top.

Whole structure looks something like this:

<MyPage>
  <FancyButton>
     <template>
       <BaseButton>
         <template>
           <Component :is="button | a"><slot /></Component>
         </template>
       </BaseButton>
     </template>
  </FancyButton>
</MyPage>

Can you explain this in detail? If it's indeed a bug, please create a minimal reproduction that allows us to investigate it.

Will try later to make a reproduction later, when I enable fallthroughAttributes it seems to break interface merging. I have design system package, and apps can customize or add additional props to buttons like this:

export interface BaseButtonInitialProps {
	id?: string
	type?: 'button' | 'submit'
	as?: 'a' | 'button'
	disabled?: boolean
	href?: string
	target?: '_blank' | '_self'
	download?: boolean
}

// Interface is intentionally left empty to allow consumers of the component
// to extend and customize its properties through TypeScript's declaration merging.
export interface BaseButtonProps extends BaseButtonInitialProps {}

Perhaps we can have a comment syntax or defineCompilerOptions to configure vueCompilerOptions locally.

Would be great

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request Request new features
Projects
None yet
2 participants