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

querySelector return type could be more specific for compound selectors #29037

Open
5 tasks done
chharvey opened this issue Dec 15, 2018 · 16 comments
Open
5 tasks done

querySelector return type could be more specific for compound selectors #29037

chharvey opened this issue Dec 15, 2018 · 16 comments
Labels
Domain: lib.d.ts The issue relates to the different libraries shipped with TypeScript In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@chharvey
Copy link

Search Terms

querySelector, return, type, selector

Suggestion

This issue closely follows #8114, which applies only to type selectors ("single-element selectors"). Related to #12568.

The return type of ParentNode#querySelector is Element by default, but when the string argument matches exactly a lower-case element name, the return type is the interface of that element.

For example, .querySelector('#hero.wide') returns an Element type, but .querySelector('img') returns an HTMLImageElement type.

This helpful behavior fails beyond simple type selectors (a selector containing only an element name). When the argument becomes a compound selector, such as .querySelector('img#hero.wide'), the return type is the more generic Element. This is unhelpful when the element name, img, remains in the selector.

My suggestion is to improve parsing of the string argument, so that when it is a compound selector that contains a type selector, the return type can still be a specific interface. Obviously, this would not apply to selectors not containing a type selector, e.g. there is no way to know for sure that .querySelector('#hero.wide') is indeed an HTMLImageElement.

Use Cases

document.querySelector('#hero.wide').src = '//example.com/hero.png' // Element
// Error: Property 'src' does not exist on type 'Element'

document.querySelector('img').src = '//example.com/hero.png' // HTMLImageElement
// no error, but cannot specify *which* image

document.querySelector('img#hero.wide').src = '//example.com/hero.png' // Element
// Error: Property 'src' does not exist on type 'Element'

(document.querySelector('img#hero.wide') as HTMLImageElement).src = '//example.com/hero.png'
// no error, and can specify which image, but must assert type manually

Summary: It would be nice for TS to infer that img#hero.wide selects an HTMLImageElement, based on the tag name img in the selector. This would eliminate the need to assert the type manually.

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals. (Specifically: Goals 5, 6, and 9)
@weswigham
Copy link
Member

Ref #21044 which'd enable us to support this.

@weswigham weswigham added Suggestion An idea for TypeScript In Discussion Not yet reached consensus Domain: lib.d.ts The issue relates to the different libraries shipped with TypeScript labels Dec 15, 2018
@fregante
Copy link

fregante commented Feb 2, 2019

Could this also work for complex selectors and lists of them?

document.querySelector('.wrapper div.box');
document.querySelector('.wrapper div.box, .sidebar div.alert');

@chharvey
Copy link
Author

chharvey commented Feb 2, 2019

@bfred-it I could see it working for a single complex selector, since it selects only one element (or an array of the same type of element). But I don’t think it would work for selector lists, since there’s a chance more than one type of element could be selected.

To follow your example, what would .querySelector('.wrapper div.box, .sidebar p.alert') return?HTMLDivElement | HTMLParagraphElement? The idea is to get .querySelector() to have a return type that is a single subclass of Element (or Element[] for .querySelectorAll()).

@fregante
Copy link

fregante commented Feb 2, 2019

return? HTMLDivElement | HTMLParagraphElement?

Yes, actually that's what I meant to write, two different elements.

If that's not possible, at least support for a homogeneous list of selectors would still be useful: 'div.a, div.b'

@bschlenk
Copy link

This is now possible with #40336.

See https://github.com/g-plane/type-gymnastics/blob/master/src/better-querySelector/index.ts

@fregante
Copy link

Wow, I never thought this was going to be possible.

@g-plane will you open a PR to merge it into lib.dom.ts?

@g-plane
Copy link
Contributor

g-plane commented Sep 18, 2020

I'd like to hear opinions from TypeScript team before doing it.

@orta
Copy link
Contributor

orta commented Sep 18, 2020

I made a playground to play about with it (interesting work @g-plane! ) - I'll bring it up in a design meeting (maybe next week will have some time), my bet is that we need to figure out the perf trade-offs for that feature. I'd use it a lot though, so you have me on your side at least!

<edit>Updated the playground - thanks @g-plane</edit>

@Manu1400

This comment has been minimized.

@fregante

This comment has been minimized.

@orta

This comment has been minimized.

@fregante

This comment has been minimized.

@orta
Copy link
Contributor

orta commented Sep 25, 2020

@MikeRyanDev got this even tighter

@g-plane
Copy link
Contributor

g-plane commented Sep 25, 2020

@orta There's something wrong with the playground you made. For Line 56 and Line 57:

- const nestedDiv = document.querySelector('.wrapper div.box');
+ const nestedDiv = querySelector('.wrapper div.box');
- const subNestedDiv = document.querySelectorAll('.wrapper div.box, .sidebar div.alert');
+ const subNestedDiv = querySelectorAll('.wrapper div.box, .sidebar div.alert');

Then, it works.

@Raynos
Copy link

Raynos commented Oct 23, 2020

Would it be possible to open a PR with this change from the playground ?

Having improved support for querySelector in lib.dom.d.ts would be fantastic.

@g-plane
Copy link
Contributor

g-plane commented Nov 13, 2020

For anyone who want this feature now, I've released an npm package for you. Here is the repository: https://github.com/g-plane/typed-query-selector . Feel free to use and star it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: lib.d.ts The issue relates to the different libraries shipped with TypeScript In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

8 participants