lwc-standard.d.ts Definitions Suboptimal

Issue #1927 resolved
Xander Victory created an issue

The lwc-standard.d.ts file (which I’m assuming is created by you given the copyright) contains module definitions for a bunch of built-in LWC elements, but with functions exported from the module itself.

It would be preferable if they were exported as proper TS classes mimicking the actual API properties and methods documented in the Component Reference.

I’ve made a quick script to generate these from the lwc-standard.json file (attached). It doesn’t do the libraries (like Navigation) properly as it assumes they’re all components.

The declarations are most noticeable when making a custom Lightning Datatable, where one uses extend on the base LightningDatatable class. In this specific example, it would be nice if it could inherit the @api decorated properties so the vanilla props don’t show as unrecognised (in HTML templates).

It can also be useful when using JsDoc comments to aid IDEA’s type resolution:

import {LightningElement} from 'lwc';
import LightningInput from 'lightning/input'

export default class DemoComponent extends LightningElement {
    demo() {
        /** @type {LightningInput} myInput */
        let myInput = this.template.querySelector('lightning-input')
        myInput.value = 'foo' 
    }
}

In this example, myInput.value is recognised as a field on LightningInput for highlighting and autocomplete.

Comments (19)

  1. Scott Wells repo owner

    Thanks, Xander. Coincidentally I was thinking about this just yesterday when I updated the existing lwc-standard.d.ts based on the latest lwc-standard.json and noticed that the properties were missing. I already have some changes in place to enrich it similar to what you've done. That won't be in this week's build as I'm about to release it, but it's looking good for next week's build.

  2. Scott Wells repo owner

    Xander, I've updated my own generator for lwc-standard.d.ts to be much closer to what you've provided. There are a few notable differences, though, and I thought I'd enumerate them here to see if they might cause issues or not:

    1. I provide return type information for methods, e.g., generateUrl(pageReference: object): Promise<String>; vs. yours which is generateUrl(pageReference: object);. I doubt that will cause issues, and in fact I would think the additional return type information is only going to be valuable to the IDE's type inference engine.
    2. I use ? to specify optional formal parameters whereas you use type|undefined, e.g., navigate(pageReference: object, replace?: boolean): void; vs. yours which is navigate(pageReference: object, replace: boolean|undefined);. Any reason to think that would cause an issue? Perhaps some TypeScript(-to-ES6) nuance of which I'm not aware?
    3. You have lightning/slot whereas I have slot. My understanding is that slot doesn't actually exist in the lightning namespace: https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.create_components_slots

    Those are the primary differences. Thoughts?

  3. Xander Victory reporter
    1. Yeah, this was what I meant when I said it doesn’t do the libraries properly. Your current Navigation ones are better than my generated ones. I use a different version in my custom JS library (i.e. my projects end up with your auto-added one and a vanilla IDEA js library on the local filesystem). Pasted below.
    2. Functionally, I’m fairly certain they’re the same thing - I mostly just wasn’t sure how to represent the ? in the AST library I was using. There’s even JsDoc’s {?boolean} for @param
    3. You’re probably right as it seems to be a vanilla element they’ve co-opted (LWC being somewhat based on standard web components and all). My script just slaps lightning/ in front of all the elements in the json. I can’t seem to actually import Slot, though the only reason to do so would probably be the @type {Slot} notation (the input example doesn’t resolve without the import as I don’t think / is valid in JsDoc)

    const Navigate: unique symbol = Symbol('Navigate');
    const GenerateUrl: unique symbol = Symbol('GenerateUrl');
    
    declare module "lightning/navigation" {
        /** use with @wire, but directly assigns the pageref, not {data,error} */
        function CurrentPageReference(config?: any): any;
    
        const NavigationMixin: {
            <T>(Base: T): T & NavClz
            readonly Navigate: typeof Navigate
            readonly GenerateUrl: typeof GenerateUrl
        };
    
        class NavClz {
            [NavigationMixin.Navigate](pageReference: PageReference, replace?: boolean): void;
            [NavigationMixin.GenerateUrl](pageReference: PageReference): Promise<string>;
        }
    
        type PageReference = PageReferenceBase|PageReferenceRecordPage
    
        interface PageReferenceBase {
            type: PageRefType;
            attributes?: object;
            state: {
                [key: string]: string;
            };
        }
    
        interface PageReferenceRecordPage extends PageReferenceBase {
            type: 'standard__recordPage',
            attributes: {
                actionName: 'clone' | 'edit' |'view'
                objectApiName?: string
                recordId: string
            }
        }
    
        type PageRefType = 'standard__app' |
                           'standard__component' |
                           'comm__loginPage' |
                           'standard__knowledgeArticlePage' |
                           'comm__namedPage' |
                           'standard__namedPage' |
                           'standard__navItemPage' |
                           'standard__objectPage' |
                           'standard__recordPage' |
                           'standard__recordRelationshipPage' |
                           'standard__webPage'
    }
    

    Notes:

    1. I’ve made it use the “proper” Symbol types for the navigate/generateUrl calls, but functionally as long as they’re called via NavigationMixin.X it doesn’t matter much
    2. I think I made it a concrete class was something to do with IDEA’s type resolution (but I can’t quite remember)
    3. The PageReference implementation is incomplete (i.e. doesn’t define the other types) - it seems to work without needing to be imported, and is able to help autocomplete (will attach image)

  4. Scott Wells repo owner

    Thanks, Xander. I'm attaching my latest version of lwc-standard.d.ts here if you want to take a look. It doesn't include any changes to NavigationMixin, though I'm familiar with the goal of making it work with a strongly-typed PageReference and have had a few false starts there. I'll play with what you've provided and see where it goes. Let me know if you have any feedback on the rest of it, though, as I'd love to get this nailed so that it's an accurate strongly-typed representation of the standard component types.

  5. Xander Victory reporter

    Looks pretty good, though the question mark syntax is valid for fields too, i.e.

    export default class Accordion extends LightningElement {
            /** The activeSectionName changes the default expanded section. The first section in the accordion is expanded by default. */
            @api
            activeSectionName?: string;
            ...
    }
    

    Technically the event handler attributes (seems to be Aura.Action) should be EventListener rather than Function, though I’m not sure it matters too much since the JS controllers won’t really benefit that I can see (the function definition needs the JsDoc @param {Event}). There are also ~11 variant-specific ones, like MouseEventHandler for onclick (whether LWC uses them or not is another question, I suppose).

    Another cool thing (if you’re not averse to some regex), would be to use the syntax for a fixed set of values:

    export default class Avatar extends LightningElement {
      /** The variant changes the shape of the avatar. Valid values are empty, circle, and square. This value defaults to square. */
      @api
      variant?: 'empty'|'circle'|'square';
    }
    

    There are 17 instances of “valid values are” in the jsdocs

  6. Xander Victory reporter

    Perhaps more relevant for the html autocomplete, but the pattern (valid values are|accepted variants include|possible values are|options include) (('?[-a-z0-9]+'?, )*('?[-a-z0-9]+'? )?(?:and|or) '?[-a-z0-9]+'?). has 47 matches!

  7. Scott Wells repo owner

    Thanks for the feedback. Here's the latest version, and IC2 now uses the same allowed values information for code completion and validation:

    Issue_1927_Completion.png

    Validation is also extended to Boolean and numeric types in template markup. There's more that could be validated, but this should go a LONG way toward better code completion and validation for standard LWC components.

    Let me know if you have any further feedback. I'm going to target getting this out Wednesday or Thursday morning.

  8. Scott Wells repo owner

    Attaching a slightly newer version that includes a previously-missed variant of the enumerated values pattern. I may encounter others as I continue testing/reconciling.

  9. Xander Victory reporter

    Holy balls, that’s awesome!

    I think the only extra thing you could do would be to manually adjust some of the object and any types, depending on how granular you want to go.

    e.g. options: {value:string, label:string}[] for combo boxes and a couple others (or Array<{value:string…}>, same thing)

  10. Scott Wells repo owner

    Another take, this time making those fields with a known schema more strongly-typed. It's not 100% perfect, but it's getting much, much closer. At this point unless you see issues with what I did in this last pass, I'll plan to include this in Thursday morning's build and we'll handle anything else as a one-off correction. Thanks so much for the input on this!

  11. Xander Victory reporter

    Looks good!

    • lightning/buttonStateful has a few \' in the comments
    • lightning-input.min/max seem to beable to be defined as string|number
    • lightning-input.files can be typed FileList (from dom library)
    • I found some more datatable definitions on my side, see below. NB the interfaces are exported to allow use in JSdoc. My sandbox accepted code that contained import {thiscantpossiblyexist} from 'lightning/datatable'`
    • Agreed anything else can be as-needed

    declare module 'lightning/datatable' {
        import { LightningElement, api } from 'lwc';
    
        export interface RowActionEvent<ROWVALUE = any> extends Event {
            detail: RowDetail<ROWVALUE>;
        }
    
        export interface RowDetail<ROWVALUE = any> {
            /** The action that triggered the event */
            action: RowAction;
            /** The row's value from the table's data attribute */
            row: ROWVALUE;
        }
    
        export interface RowAction {
            /** Required. The label that's displayed for the action. */
            label: string;
            /** Required. The name of the action, which identifies the selected action.*/
            name: string;
            /** Specifies whether the action can be selected. If true, the action item is shown as disabled. This value defaults to false. */
            disabled?: boolean;
            /** The name of the icon to be displayed to the right of the action item. */
            iconName?: string
        }
    
        export interface DataTableColumn {
            /** Appends a dropdown menu of actions to a column. You must pass in a list of label-name pairs.*/
            actions?: RowAction[];
    
            /** Provides additional customization, such as appending an icon to the output. For more information, see Appending an Icon to Column Data*/
            cellAttributes?: CellAttributes;
    
            /** Specifies whether a column supports inline editing. The default is false.*/
            editable?: boolean;
    
            /** Required. The name that binds the columns attributes to the associated data. Each columns attribute must correspond to an item in the data array.*/
            fieldName: string;
    
            /** Specifies the width of a column in pixels and makes the column non-resizable.*/
            fixedWidth?: number;
    
            /** The Lightning Design System name of the icon. Names are written in the format standard:opportunity. The icon is appended to the left of the header label.*/
            iconName?: string;
    
            /** The width of the column when it's initialized, which must be within the min-column-width and max-column-width values, or within 50px and 1000px if they are not provided.*/
            initialWidth?: number;
    
            /** Required. The text label displayed in the column header.*/
            label: string;
    
            /** Specifies whether the column can be sorted. The default is false.*/
            sortable?: boolean;
    
            /** Required. The data type to be used for data formatting. For more information, see Formatting with Data Types.*/
            type?: 'action' | 'boolean' | 'button' | 'button-icon' | 'currency' | 'date' | 'date-local' | 'email' | 'location' | 'number' | 'percent' | 'phone' | 'text' | 'url' | string;
    
            /** Provides custom formatting with component attributes for the data type. For example, currency-code for the currency type.*/
            typeAttributes?: TypeAttributes;
        }
    
        export interface CellAttributes {
            /** Required. The Lightning Design System name of the icon, for example, utility:down.*/
            iconName: string;
    
            /** The label for the icon to be displayed on the right of the icon.*/
            iconLabel?: string;
    
            /** The position of the icon relative to the data. Valid options include left and right. This value defaults to left.*/
            iconPosition?: 'left' | 'right';
    
            /** Descriptive text for the icon.*/
            iconAlternativeText?: string;
    
            alignment?: 'left'|'right'|'center';
    
        }
    
        type stringOrFieldRef = string | { fieldName: string }
    
        /** See 'Formatting with Data Types' at https://developer.salesforce.com/docs/component-library/bundle/lightning-datatable/documentation for more details */
        export interface TypeAttributes {
            alternativeText?: string;
            class?: string;
            currencyCode?: string;
            currencyDisplayAs?: string;
            day?: 'numeric' | '2-digit';
            disabled?: boolean;
            era?: 'narrow' | 'short' | 'long';
            hour?: 'numeric' | '2-digit';
            hour12?: boolean;
            iconClass?: string;
            iconName?: string;
            iconPosition?: string;
            label?: stringOrFieldRef;
            latitude?: string;
            longitude?: string;
            maximumFractionDigits?: number;
            maximumSignificantDigits?: number;
            menuAlignment?: string;
            minimumFractionDigits?: number;
            minimumIntegerDigits?: number;
            minimumSignificantDigits?: number;
            minute?: 'numeric' | '2-digit';
            month?: 'narrow' | 'short' | 'long' | '2-digit';
            name?: string;
            rowActions?: RowAction[];
            second?: 'numeric' | '2-digit';
            target?: string;
            timeZone?: string;
            timeZoneName?: string;
            title?: string;
            tooltip?: string;
            value: stringOrFieldRef;
            variant?: string;
            weekday?: 'narrow' | 'short' | 'long';
            year?: 'numeric' | '2-digit';
        }
    
        interface DatatableError {
            title: string;
            messages: Array<string>;
        }
    
        type DatatableRowError = DatatableError & {fieldNames?: Array<string>}
    
        interface DatatableErrorValues {
            /** Row level errors */
            rows?: {[rowKey:string]: DatatableRowError}
    
            /** Table level errors */
            table?: DatatableError
        }
    
        /** A table that displays columns of data, formatted according to type. This component requires API version 41.0 and later. */
        export default class Datatable extends LightningElement {
            //<SNIP>
            @api
            columns?: Array<DataTableColumn>;
    
            /** The array of data to be displayed. */
            @api
            data?: Array<object>;
    
            //<SNIP>
    
            /** Specifies an object containing information about cell level, row level, and table level errors. When it's set, error messages are displayed on the table accordingly. */
            @api
            errors?: DatatableErrorValues;
    
            //<SNIP>
    
            /** The action triggered when a row action is clicked. By default, it also closes the row actions menu. Returns the eventDetails object. */
            @api
            onrowaction?: (ev:RowActionEvent)=>void;
    
            //<SNIP>
        }
    }
    

  12. Scott Wells repo owner

    Xander, I had planned to put this out in a build this morning, and that build is ready to go, but I've had something unexpected come up that will likely delay it until next Tuesday morning. I'm happy to post the build here if you'd like to toy with it, but I don't want to put out a broad release until I'm settled back in early next week.

  13. Scott Wells repo owner

    Delivered in 2.1.8.3. Thanks again for all the input, education, verification, etc., Xander! There are certainly a few other things to be done (likely more than a few), but this should establish a much better new foundation.

  14. Log in to comment