web-components icon indicating copy to clipboard operation
web-components copied to clipboard

Support renaming elements and using multiple tags on the same page without conflicts

Open Artur- opened this issue 1 year ago • 5 comments
trafficstars

Describe your motivation

In copilot we would like to use the Vaadin components but cannot directly import e.g. button for a couple of reasons:

  1. We do not know if we should import the bare version, the Lumo version or the Material version. If we import the wrong version and the application later imports another version, the theme from the application version will not be applied and a warning printed to the browser console that the element was finalized before a style module was registered.
  2. Application theme rules, both injected into the component shadow root and ::part style rules are applied also to components used for copilot. The themes should be independent.

Describe the solution you'd like

We would like to use e.g. text field and call it copilot-text-field. This would avoid most problems but currently cannot be done because

  1. There is no side effect free import that does not register vaadin-text-field so we will always hit problem 1 above
  2. Even if we had a side effect free import, text field internally registers and uses sub elements like vaadin-input-container which must also be renamed to e.g. copilot-input-container so these internal elements will have application theming applied to them

Describe alternatives you've considered

We could fork the whole component set and do find and replace but this is sounds like the wrong way to go for many reasons

Additional context

No response

Artur- avatar Jan 09 '24 08:01 Artur-

Depends on #6999 (which also suggests introducing side effects free imports, but for other use case).

web-padawan avatar Jan 09 '24 08:01 web-padawan

As discussed in Slack, we could consider a wrapped html function for rendering children with custom tag names. The wrapper would replace strings from the raw template expression before passing it to the original.

const html = htmlReplace(originalHtml, {'vaadin-button': 'copilot-button'});
return html`<vaadin-button>Click me</vaadin-button>`;

Legioth avatar Jan 09 '24 08:01 Legioth

Actually, in case of Polymer we can create a custom html tag function that returns <template> element. Example:

import { PolymerElement } from '@polymer/polymer';

const htmlReplace = function htmlReplace(replacements) {
  // Create a custom html literal function
  return function (strings, ...values) {
    const replaced = strings.map((string) => {
      Object.entries(replacements).forEach(([from, to]) => {
        string = string.replaceAll(from, to);
      });
      return string;
    });

    // Return HTMLTemplateElement to be cloned
    const template = document.createElement('template');
    template.innerHTML = replaced.join('');
    return template;
  };
};

class MyElement extends PolymerElement {
  static get template() {
    const html = htmlReplace({ 'vaadin-button': 'copilot-button' });
    return html`<vaadin-button>Button</vaadin-button>`;
  }

  static get properties() {
    return {
      disabled: {
        type: Boolean,
      },
    };
  }
}

customElements.define('my-test', MyElement);

For Lit based versions, we would need to use unsafeStatic directive to make tag names customizable.

web-padawan avatar Jan 09 '24 09:01 web-padawan

One more thing to address in scope of this issue would be to replace usage of registerStyles in src folders e.g.

https://github.com/vaadin/web-components/blob/08563b9e009eb7f90921c172ab7982d64651aea2/packages/context-menu/src/vaadin-context-menu-overlay.js#L15-L17

These currently have hardcoded HTML tag names. Instead, we should be able to move them into static get template() by using the custom html function (e.g. the one suggested above) that wouldn't prevent interpolating like Polymer does.

web-padawan avatar Jan 09 '24 14:01 web-padawan

I've had some similar interest for some time.

Within my organization, we've vended some components that utilize vaadin (unthemed) as building blocks for my-org-themed components. This looks similar to what was outlined by original commenter: vaadin-button => myorg-button... not dissimilar from the premise of "lion" or "microsoft fast (foundation)"; I've been using vaadin + custom theming long before either of those packages existed and have a lot of respect for you all, so using vaadin for a functional base was my choice.

Here are some lessons I learned along the way.

  • The components inevitably would wind up - either by direct or transitive dependency - in packages that had their own direct or transitive dependency on another version of vaadin
  • Naively changed apps to monkey-patch customElements.define to at least try to render something rather than being broken
  • Had "unwanted styles" show up because some package that depended upon vaadin and myorg-vaadin had imported the vaadin-lumo variant.
    • This caused me to introduce a strict-themable-mixin that blocked "inherited styles" from being attached to myorg-vaadin-*
  • Ultimately, I did vend a hacky workaround for not having sideEffectless-exports here:
    • https://github.com/robrez/scoped-vaadin
    • https://stackblitz.com/edit/typescript-gau6r3?file=index.ts
    • Notes: that this has been working out well internally for some time now.. but I have not done a good job of keeping things up-to-date. This enabled the myorg-button, etc, to be on the same page as vaadin-button (different version). I made a decision to not try to use scoped-registrations and a mixin for polymerelements because I didn't feel confident about some of the details there; Instead, registrations contain major-version. Note also that I generally did not wish for the underlying classes to be registered - I wanted to extend them and register my subclass. I think the readme.md and docs/decisions.md captured some finer points...
    • https://github.com/robrez/scoped-vaadin/blob/main/docs/decisions.md

Kind regards

robrez avatar Feb 26 '24 20:02 robrez