
When a user lands at /home, this UI is shown. A couple of components are shareable from /browse, e.g., search-bar, search-results.
Sharing Code with /browse
Components initially created for
the /browse
page
are useful in /home
as well.
The CardsViewingPage
Interface
This functionality can be shared between the two pages:
export class CardsViewingPage extends LitElement {
@provide({ context: searchResultsContext })
@state() protected searchResults: CardSearchResult[] = [];
@state() protected selectedResult: Card | null = null;
@provide({ context: cardsCarouselContext })
@state() protected cardsCarousel = new CardsCarousel([]);
protected cardFetcher: CardFetchEndpoint;
constructor(cardFetcher: CardFetchEndpoint) {
super();
this.cardFetcher = cardFetcher;
this.addEventListeners();
}
render() {
throw new Error('CardsViewingPage must be subclassed and implement render()');
}
// Add event listeners, e.g., search-results, search-result-selected,
private addEventListeners() {...}
// Call `this.cardFetcher` and set `this.selectedResult`.
private updateSelectedCard(cardID: string) {...}
static styles = css`
:host {
display: flex;
flex-direction: column;
gap: 10px;
}
`;
}
@customElement('home-page')
export class HomePage extends CardsViewingPage {
constructor() {
super(trpc.fetchCard.query);
}
render() {...}
}
@customElement('browse-page')
export class BrowsePage extends CardsViewingPage {
constructor() {
super(trpc.fetchPublicCard.query);
}
render() {...}
}
Customizing the <search-bar>
Component
The difference between the <search-bar>
rendered by /browse
and the
one rendered by /home
is the endpoint used by SearchBar.fetchResults
(either trpc.searchPublicCards.query
or trpc.searchCards.query
). We
currently pass a boolean
to distinguish, but is it possible to pass
the endpoint itself so as to be “closer to the metal”? While this code
type-checks:
export type CardSearchEndpoint = typeof trpc.searchCards.query | typeof trpc.searchPublicCards.query;
@customElement('search-bar')
export class SearchBar extends LitElement {
searchEndpoint: CardSearchEndpoint | null = null;
// ...
}
@customElement('browse-page')
export class BrowsePage extends CardsViewingPage {
// ...
render() {
return html`
<search-bar .searchEndpoint=${trpc.searchPublicCards.query}>
</search-bar>
...
`;
}
}
… it fails at runtime with Uncaught (in promise) TypeError: nextDirectiveConstructor is not a constructor
after a Static values 'literal' or 'unsafeStatic' cannot be used as values to non-static templates. Please use the static 'html' tag function. See https://lit.dev/docs/templates/expressions/#static-expressions
warning.
It’s
possible to pass functions via data
attributes
,
so that’s not what’s happening here. The use case does not match the one
described in :
class MyButton extends LitElement {
tag = literal`button`;
render() {
const activeAttribute = getActiveAttribute(); // Must be trusted!
return html`
<${this.tag} ${unsafeStatic(activeAttribute)}=${this.active}>
</${this.tag}>
`;
}
Using .searchEndpoint=${(q: CardSearchQuery) => trpc.searchPublicCards.query(q)}
works though. Huh, I hope this doesn’t
bite back in the future.