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

Allow lit 3 #2723

Open
wants to merge 8 commits into
base: master
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lemon-monkeys-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@open-wc/lit-helpers': minor
---

Bump lit-helpers support Lit@3
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ stats.html
/packages/dev-server-hmr/src/patches/**/*
/packages/testing/plugins/**/*
/packages/lit-helpers/src/spread.js
/packages/lit-helpers/src/spread-lit2.js
_site/
_site-dev
docs/_merged_assets/
Expand Down
3 changes: 3 additions & 0 deletions packages/dev-server-hmr/src/hmrPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,12 @@ function hmrPlugin(pluginConfig) {
rootDir,
});
} catch (error) {
// @ts-ignore
if (error.name === 'SyntaxError') {
// forward babel error to dev server
// @ts-ignore
const strippedMsg = error.message.replace(new RegExp(`${filePath} ?:? ?`, 'g'), '');
// @ts-ignore
throw new PluginSyntaxError(strippedMsg, filePath, error.code, error.loc, error.pos);
}
throw error;
Expand Down
9 changes: 5 additions & 4 deletions packages/lit-helpers/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
spread.js
spread.js.map
spread.d.ts.map
spread.d.ts
spread*.js
spread*.js.map
spread*.d.ts.map
spread*.d.ts

11 changes: 8 additions & 3 deletions packages/lit-helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"*.d.ts",
"*.js",
"src",
"!src/spread.ts"
"!src/spread.ts",
"!src/spread-lit2.*"
],
"keywords": [
"lit",
Expand All @@ -34,7 +35,11 @@
"helpers"
],
"peerDependencies": {
"lit": "^2.0.0"
"lit": "^2.0.0 || ^3.0.0"
},
"sideEffects": false
"sideEffects": false,
"devDependencies": {
"lit": "^3.0.0",
"lit2": "npm:lit@2"
}
}
253 changes: 253 additions & 0 deletions packages/lit-helpers/src/spread-lit2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
import { ElementPart, Part } from 'lit2';
import { nothing } from 'lit2/html.js';
import { directive } from 'lit2/directive.js';
import { AsyncDirective } from 'lit2/async-directive.js';

type EventListenerWithOptions = EventListenerOrEventListenerObject &
Partial<AddEventListenerOptions>;


/**
* Usage:
* import { html, render } from 'lit';
* import { spreadProps } from '@open-wc/lit-helpers';
*
* render(
* html`
* <div
* ${spreadProps({
* prop1: 'prop1',
* prop2: ['Prop', '2'],
* prop3: {
* prop: 3,
* }
* })}
* ></div>
* `,
* document.body,
* );
*/
export class SpreadPropsDirective extends AsyncDirective {
host!: EventTarget | object | Element;
element!: Element;
prevData: { [key: string]: unknown } = {};

render(_spreadData: { [key: string]: unknown }) {
return nothing;
}
update(part: Part, [spreadData]: Parameters<this['render']>) {
if (this.element !== (part as ElementPart).element) {
this.element = (part as ElementPart).element;
}
this.host = part.options?.host || this.element;
this.apply(spreadData);
this.groom(spreadData);
this.prevData = { ...spreadData };
}

apply(data: { [key: string]: unknown }) {
if (!data) return;
const { prevData, element } = this;
for (const key in data) {
const value = data[key];
if (value === prevData[key]) {
continue;
}
// @ts-ignore
element[key] = value;
}
}

groom(data: { [key: string]: unknown }) {
const { prevData, element } = this;
if (!prevData) return;
for (const key in prevData) {
if (!data || (!(key in data) && element[key] === prevData[key])) {
// @ts-ignore
element[key] = undefined;
}
}
}
}

export const spreadProps = directive(SpreadPropsDirective);

/**
* Usage:
* import { html, render } from 'lit';
* import { spreadEvents } from '@open-wc/lit-helpers';
*
* render(
* html`
* <div
* ${spreadEvents({
* '@my-event': () => console.log('my-event fired'),
* '@my-other-event': () => console.log('my-other-event fired'),
* '@my-additional-event':
* () => console.log('my-additional-event fired'),
* })}
* ></div>
* `,
* document.body,
* );
*/
export class SpreadEventsDirective extends SpreadPropsDirective {
eventData: { [key: string]: unknown } = {};

apply(data: { [key: string]: unknown }) {
if (!data) return;
for (const key in data) {
const value = data[key];
if (value === this.eventData[key]) {
// do nothing if the same value is being applied again.
continue;
}
this.applyEvent(key, value as EventListenerWithOptions);
}
}

applyEvent(eventName: string, eventValue: EventListenerWithOptions) {
const { prevData, element } = this;
this.eventData[eventName] = eventValue;
const prevHandler = prevData[eventName];
if (prevHandler) {
element.removeEventListener(eventName, this, eventValue);
}
element.addEventListener(eventName, this, eventValue);
}

groom(data: { [key: string]: unknown }) {
const { prevData, element } = this;
if (!prevData) return;
for (const key in prevData) {
if (!data || (!(key in data) && element[key] === prevData[key])) {
this.groomEvent(key, prevData[key] as EventListenerWithOptions)
}
}
}

groomEvent(eventName: string, eventValue: EventListenerWithOptions) {
const { element } = this;
delete this.eventData[eventName];
element.removeEventListener(eventName, this, eventValue);
}

handleEvent(event: Event) {
const value: Function | EventListenerObject = this.eventData[
event.type
] as Function | EventListenerObject;
if (typeof value === 'function') {
(value as Function).call(this.host, event);
} else {
(value as EventListenerObject).handleEvent(event);
}
}

disconnected() {
const { eventData, element } = this;
for (const key in eventData) {
// event listener
const name = key.slice(1);
const value = eventData[key] as EventListenerWithOptions;
element.removeEventListener(name, this, value);
}
}

reconnected() {
const { eventData, element } = this;
for (const key in eventData) {
// event listener
const name = key.slice(1);
const value = eventData[key] as EventListenerWithOptions;
element.addEventListener(name, this, value);
}
}
}

export const spreadEvents = directive(SpreadEventsDirective);

/**
* Usage:
* import { html, render } from 'lit';
* import { spread } from '@open-wc/lit-helpers';
*
* render(
* html`
* <div
* ${spread({
* 'my-attribute': 'foo',
* '?my-boolean-attribute': true,
* '.myProperty': { foo: 'bar' },
* '@my-event': () => console.log('my-event fired'),
* })}
* ></div>
* `,
* document.body,
* );
*/
export class SpreadDirective extends SpreadEventsDirective {
apply(data: { [key: string]: unknown }) {
if (!data) return;
const { prevData, element } = this;
for (const key in data) {
const value = data[key];
if (value === prevData[key]) {
continue;
}
const name = key.slice(1);
switch (key[0]) {
case '@': // event listener
this.eventData[name] = value;
this.applyEvent(name, value as EventListenerWithOptions);
break;
case '.': // property
// @ts-ignore
element[name] = value;
break;
case '?': // boolean attribute
if (value) {
element.setAttribute(name, '');
} else {
element.removeAttribute(name);
}
break;
default:
// standard attribute
if (value != null) {
element.setAttribute(key, String(value));
} else {
element.removeAttribute(key);
}
break;
}
}
}

groom(data: { [key: string]: unknown }) {
const { prevData, element } = this;
if (!prevData) return;
for (const key in prevData) {
const name = key.slice(1);
if (!data || (!(key in data) && element[name] === prevData[key])) {
switch (key[0]) {
case '@': // event listener
this.groomEvent(name, prevData[key] as EventListenerWithOptions)
break;
case '.': // property
// @ts-ignore
element[name] = undefined;
break;
case '?': // boolean attribute
element.removeAttribute(name);
break;
default:
// standard attribute
element.removeAttribute(key);
break;
}
}
}
}
}

export const spread = directive(SpreadDirective);