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

Use MWC for search input #1422

Open
wants to merge 6 commits into
base: import-page
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/site-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
},
"dependencies": {
"@lit-labs/task": "^2.0.0",
"@material/web": "^1.0.0-pre.1",
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we need = here, because we could get a breaking change at any time while we're on pre, right?

"@webcomponents/catalog-api": "^0.0.0",
"lit": "^2.6.0",
"lit-analyzer": "^1.2.1"
Expand Down
25 changes: 22 additions & 3 deletions packages/site-client/src/pages/catalog/wco-catalog-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@

import {html, css, LitElement} from 'lit';
import {customElement, query, state} from 'lit/decorators.js';

import '@material/web/textfield/outlined-text-field.js';
import type {MdOutlinedTextField} from '@material/web/textfield/outlined-text-field.js';

import '@material/web/icon/icon.js';

import type {CustomElement} from '@webcomponents/catalog-api/lib/schema.js';

import './wco-element-card.js';
Expand All @@ -27,17 +33,30 @@ export class WCOCatalogSearch extends LitElement {
grid-auto-rows: 200px;
gap: 8px;
}

md-outlined-text-field {
width: 40em;
--md-outlined-field-container-shape-start-start: 28px;
--md-outlined-field-container-shape-start-end: 28px;
--md-outlined-field-container-shape-end-start: 28px;
--md-outlined-field-container-shape-end-end: 28px;
}
`;

@query('input')
private _search!: HTMLInputElement;
@query('#search')
private _search!: MdOutlinedTextField;

@state()
private _elements: Array<CustomElement> | undefined;

render() {
return html`
<p>Search: <input id="search" @change=${this._onChange} /></p>
<section id="search-panel">
<h2>Web Components.org Catalog</h2>
<md-outlined-text-field id="search" @change=${this._onChange}
><md-icon slot="leadingicon">search</md-icon></md-outlined-text-field
>
</section>
<div>
${this._elements?.map(
(e) => html`<wco-element-card .element=${e}></wco-element-card>`
Expand Down
115 changes: 110 additions & 5 deletions packages/site-server/src/lib/catalog/routes/catalog-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,105 @@ import {html} from 'lit';
import {Readable} from 'stream';
import Router from '@koa/router';

import {LitElementRenderer} from '@lit-labs/ssr/lib/lit-element-renderer.js';
import {ElementRenderer} from '@lit-labs/ssr/lib/element-renderer.js';

import '@webcomponents/internal-site-client/lib/pages/catalog/wco-catalog-page.js';

type Interface<T> = {
[P in keyof T]: T[P];
};

// TODO (justinfagnani): Update Lit SSR to use this type for
// ElementRendererConstructor
export type ElementRendererConstructor = (new (
tagName: string
) => Interface<ElementRenderer>) &
typeof ElementRenderer;

// Excludes the given tag names from being handled by the given renderer.
// Returns a subclass of the renderer that returns `false` for matches()
// for any element in the list of tag names.
const excludeElements = (
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

cc @kevinpschaaf @augustjk we might want this utility in lit-ssr

Copy link
Contributor

Choose a reason for hiding this comment

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

Could we move this SSR utility stuff to another module?

renderer: ElementRendererConstructor,
excludedTagNames: Array<string>
) => {
return class ExcludeElementRenderer extends renderer {
static matchesClass(
ceClass: typeof HTMLElement,
tagName: string,
attributes: Map<string, string>
) {
console.log('matchesClass', tagName, !excludedTagNames.includes(tagName));
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove log

return excludedTagNames.includes(tagName)
? false
: super.matchesClass(ceClass, tagName, attributes);
}
};
};

const replacements = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
// Note &apos; was not defined in the HTML4 spec, and is not supported by very
// old browsers like IE8, so a codepoint entity is used instead.
"'": '&#39;',
};

/**
* Replaces characters which have special meaning in HTML (&<>"') with escaped
* HTML entities ("&amp;", "&lt;", etc.).
*/
const escapeHtml = (str: string) =>
str.replace(
/[&<>"']/g,
(char) => replacements[char as keyof typeof replacements]
);

// The built-in FallbackRenderer incorrectly causes a
// shadow root to be rendered, which breaks hydration
class FallbackRenderer extends ElementRenderer {
static matchesClass(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_ceClass: typeof HTMLElement,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_tagName: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_attributes: Map<string, string>
) {
return true;
}
private readonly _attributes: {[name: string]: string} = {};

override setAttribute(name: string, value: string) {
this._attributes[name] = value;
}

override *renderAttributes() {
for (const [name, value] of Object.entries(this._attributes)) {
if (value === '' || value === undefined || value === null) {
yield ` ${name}`;
} else {
yield ` ${name}="${escapeHtml(value)}"`;
}
}
}

connectedCallback() {
// do nothing
}
attributeChangedCallback() {
// do nothing
}
*renderLight() {
// do nothing
}

declare renderShadow: ElementRenderer['renderShadow'];
}

export const handleCatalogRoute = async (
context: ParameterizedContext<
DefaultState,
Expand All @@ -25,11 +122,19 @@ export const handleCatalogRoute = async (
globalThis.location = new URL(context.URL.href) as unknown as Location;

context.body = Readable.from(
renderPage({
title: `Web Components Catalog`,
initScript: '/js/catalog/boot.js',
content: html`<wco-catalog-page></wco-catalog-page>`,
})
renderPage(
{
title: `Web Components Catalog`,
initScript: '/js/catalog/boot.js',
content: html`<wco-catalog-page></wco-catalog-page>`,
},
{
elementRenderers: [
excludeElements(LitElementRenderer, ['md-outlined-text-field']),
FallbackRenderer,
],
}
)
);
context.type = 'html';
context.status = 200;
Expand Down
4 changes: 3 additions & 1 deletion packages/site-server/src/lib/dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ startDevServer({
router.routes() as Middleware<DefaultState, DefaultContext, unknown>,
],
watch: true,
nodeResolve: true,
nodeResolve: {
exportConditions: ['development'],
},
},
});
9 changes: 9 additions & 0 deletions packages/site-server/src/test/catalog/search_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,13 @@ test('Finds a button', async () => {
assert.ok(result.elements.length > 2);
});

test('Search page SSRs', async () => {
const response = await request('/catalog');
assert.equal(response.status, 200);
const result = await response.text();

// If the page SSR's, we'll have declarative shadow roots in it.
assert.match(result, '<template shadowroot="open">');
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we do more in the test? Like test some functionality?

});

test.run();
9 changes: 9 additions & 0 deletions packages/site-templates/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ import {escapeHTML} from './escape-html.js';
export {unsafeHTML} from 'lit/directives/unsafe-html.js';
export {html} from 'lit';

// These are needed to load MWC components
// See https://github.com/material-components/material-web/issues/3733
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(globalThis as any).FormData = class FormData {};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(globalThis as any).FormDataEvent = class FormDataEvent extends Event {};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(globalThis as any).HTMLInputElement = class HTMLInputElement {};

export function* renderPage(
data: {
scripts?: Array<string>;
Expand Down