Skip to content

Start refactoring UI modules to typescript#12059

Open
tyrasd wants to merge 3 commits intodevelopfrom
typescript-ui
Open

Start refactoring UI modules to typescript#12059
tyrasd wants to merge 3 commits intodevelopfrom
typescript-ui

Conversation

@tyrasd
Copy link
Copy Markdown
Member

@tyrasd tyrasd commented Mar 20, 2026

In order to avoid oversights such as 0241c76 from slipping through (incomplete) unit tests as well as manual testing, it is probably necessary to port the frontend modules over to typescript.

As a proof of concept here is how this might look like for one of the UI code:

Notably, for d3.select one needs to include the respective type hints, e.g. selection.selectAll<HTMLUListElement, any>(ul.…), and interfacing to existing not-type annotated modules sometimes require an explicit case to any (e.g. in uiSectionMapStyleOptions(context: any), or (uiTooltip() as any).…), at least as long as we don't port them over as well.

Feedback welcome!

@tyrasd tyrasd added the chore Improvements to the iD development experience or codebase label Mar 20, 2026
Copy link
Copy Markdown
Collaborator

@tordans tordans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to push the migration a bit more!

I hope we will find a good way to reduce the any cases. I assume @k-yle will have some ideas here.

.call(drawListItems, ['highlight_edits'], 'checkbox', 'visual_diff', toggleHighlightEdited, function() {
return context.surface().classed('highlight-edited');
});
.call(drawListItems, ['highlight_edits'], 'checkbox', 'visual_diff', toggleHighlightEdited, () =>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to put this into the coding style. Do we use arrow functions or not; and if, when… – Or is it OK, when we mix it up randomly…

In all my other projects I am all in on arrow functions inline and for helper. But for iD I tried to always use the other pattern to keep it the same…

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for recommending arrow functions for anonymous inline functions. Specifically with d3 there are a few cases where regular functions are still needed, e.g. to access this in the callback of .each(function(d) { d3_select(this).… }.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put this into the coding style

This is actually already covered by the linked code style guidelines for Javascript, e.g. https://github.com/airbnb/javascript?tab=readme-ov-file#arrow-functions and similarly for the recommendation for const/let instead of var: https://github.com/airbnb/javascript?tab=readme-ov-file#references

Copy link
Copy Markdown
Collaborator

@k-yle k-yle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's awesome to have an official endorsement of #8651 🎉

I have a (closed source) fork of iD where most of the core has been rewritten in TypeScript, so I'm happy to send more PRs to backport this work

declare var VITEST: true;

declare type Tags = { [key: string]: string };
declare type Tags = { [key: string]: string | undefined };
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also here: //cc @k-yle : I'm not sure this is superbly elegant, but a lot of front end code uses a tags object where setting a particular entry's value to undefined is interpreted as this tag should be deleted.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's tricky because this will make it less convenient for other files where undefined is never used as a value. maybe we need 3 different interfaces for the 3 different cases?

{ [key: string]: string }
{ [key: string]: string | undefined }
{ [key: string]: string | string[] }

e.g. check.js uses the 3rd case right? arrays are used when there's a multi-selection with different values

Copy link
Copy Markdown
Member Author

@tyrasd tyrasd Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, for multi-selections the value can be a string[] too1. E.g. as checked in Array.isArray(_tags[field.key].

But check.ts also uses the explicit undefined version also, see line 48: values = [undefined, 'yes'];, later this is assigned to the updated tags in t[field.key] = values[…];.

But I think the two/three cases are luckily mutually exclusive: undefined as a value is only allowed when updating the tags of objects, and string[] is only occurring as the input of the ui fields. I committed 70d97e5 with a potential way to divide this up.

Footnotes

  1. PS: Honestly, I would consider the way multiselections are encoded as these key=<string | string[]> construct as very suboptimal if not to say hacky. This makes handling them correctly quite cumbersome in places where the tags are not trivially transformable to fields (e.g. here). Maybe after we have this all as typescript, this can be refactored with confidence and without too much pain to Tags[].

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 yeah definitely, i think 70d97e5 is a good solution

* `Tags` - simple/regular tags of a single entity
* `TagsMulti` - tags of a multi-selection: can be either normal key/value tags or value as an array when there are conflicting tags present
* `TagsUpdate` - for the results of an operation, the values here can be set to `undefined` to indicate that the respective tag should be deleted from the respective entities
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

chore Improvements to the iD development experience or codebase

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow using TypeScript in iD's source?

3 participants