Skip to content

Commit

Permalink
feat!: use Lit 2.0.0-rc.1 (#10023)
Browse files Browse the repository at this point in the history
Use the new npm package `lit` (version `2.0.0-rc.1`) instead of `lit-html` and `lit-element`. Update all imports and usage of Lit according to the upgrade guide https://lit.dev/docs/releases/upgrade/

Fix the return type of CSS loader in `types.d.ts` from `CSSResult` to `CSSResultGroup` (this is what the new version of the `css` tagged template literal now returns). Existing projects will need to either delete `types.d.ts` so that it will be replaced with a new version on build (or alternatively manually update its contents) to get the correct type in TS files that import CSS files.

Details: Move main logic of the `field` directive from `render()` to `update()` since it's all about imperative DOM access and didn't actually return/render anything. `render()` is meant for declarative rendering. `update()` is meant for imperative DOM access. See: https://lit.dev/docs/templates/custom-directives/#declarative-rendering:-render() . `update()` now returns `noChange` https://lit.dev/docs/templates/custom-directives/#signaling-no-change
  • Loading branch information
Artur- authored May 4, 2021
1 parent fe6c92f commit fba584d
Show file tree
Hide file tree
Showing 36 changed files with 498 additions and 410 deletions.
3 changes: 2 additions & 1 deletion flow-client/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ module.exports = {
"no-ex-assign": 1,
"no-return-assign": 1,
"no-use-before-define": 1,
"no-useless-constructor": 1,
"no-useless-constructor": 0,
"@typescript-eslint/no-useless-constructor": 1,
"prefer-template": 1,

"@typescript-eslint/explicit-module-boundary-types": 0,
Expand Down
3 changes: 1 addition & 2 deletions flow-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@
},
"dependencies": {
"@types/validator": "13.1.0",
"lit-element": "^2.3.1",
"lit-html": "^1.2.1",
"lit": "2.0.0-rc.1",
"validator": "13.1.17"
}
}
11 changes: 6 additions & 5 deletions flow-client/src/main/frontend/ConnectionIndicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
* the License.
*/

import { css, html, LitElement, property } from 'lit-element';
import { classMap } from 'lit-html/directives/class-map';
import { html, LitElement } from 'lit';
import { property } from 'lit/decorators';
import { classMap } from 'lit/directives/class-map';
import { ConnectionState, ConnectionStateStore } from './ConnectionState';

const DEFAULT_STYLE_ID = 'css-loading-indicator';
Expand Down Expand Up @@ -246,7 +247,7 @@ export class ConnectionIndicator extends LitElement {
if (!document.getElementById(DEFAULT_STYLE_ID)) {
const style = document.createElement('style');
style.id = DEFAULT_STYLE_ID;
style.textContent = this.getDefaultStyle().cssText;
style.textContent = this.getDefaultStyle();
document.head.appendChild(style);
}
} else {
Expand All @@ -257,8 +258,8 @@ export class ConnectionIndicator extends LitElement {
}
}

private getDefaultStyle() {
return css`
private getDefaultStyle(): string {
return `
@keyframes v-progress-start {
0% {
width: 0%;
Expand Down
3 changes: 2 additions & 1 deletion flow-client/src/main/frontend/VaadinDevmodeGizmo.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { css, html, LitElement, property } from 'lit-element';
import { css, html, LitElement } from 'lit';
import { property } from 'lit/decorators';

export class VaadinDevmodeGizmo extends LitElement {
static BLUE_HSL = css`206, 100%, 70%`;
Expand Down
174 changes: 98 additions & 76 deletions flow-client/src/main/frontend/form/Field.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { directive, Part, PropertyPart } from 'lit-html';
import { ElementPart, noChange, nothing, PropertyPart } from 'lit';
import { directive, Directive, DirectiveParameters, PartInfo, PartType } from 'lit/directive';
import { _fromString, AbstractModel, getBinderNode } from './Models';

interface Field {
Expand All @@ -11,8 +12,6 @@ interface FieldState extends Field {
name: string;
strategy: FieldStrategy;
}
const fieldStateMap = new WeakMap<PropertyPart, FieldState>();

export interface FieldStrategy extends Field {
element: Element;
}
Expand All @@ -28,7 +27,7 @@ export abstract class AbstractFieldStrategy implements FieldStrategy {
set value(value) {
this.element.value = value;
}
set errorMessage(_: string) {}
set errorMessage(_: string) {} // eslint-disable-line @typescript-eslint/no-empty-function
setAttribute(key: string, val: any) {
if (val) {
this.element.setAttribute(key, '');
Expand Down Expand Up @@ -70,7 +69,7 @@ export class CheckedFieldStrategy extends GenericFieldStrategy {

export class ComboBoxFieldStrategy extends VaadinFieldStrategy {
get value() {
const selectedItem = (this.element as any).selectedItem;
const { selectedItem } = this.element as any;
return selectedItem === null ? undefined : selectedItem;
}
set value(val: any) {
Expand Down Expand Up @@ -98,98 +97,121 @@ export function getDefaultFieldStrategy(elm: any): FieldStrategy {
return new SelectedFieldStrategy(elm);
case 'vaadin-rich-text-editor':
return new GenericFieldStrategy(elm);
case 'input':
if (/^(checkbox|radio)$/.test(elm.type)) {
default:
if (elm.localName === 'input' && /^(checkbox|radio)$/.test(elm.type)) {
return new CheckedFieldStrategy(elm);
}
return elm.constructor.version ? new VaadinFieldStrategy(elm) : new GenericFieldStrategy(elm);
}
return elm.constructor.version ? new VaadinFieldStrategy(elm) : new GenericFieldStrategy(elm);
}

/**
* Binds a form field component into a model.
*
* Exmaple usage:
* Example usage:
*
* ```
* <vaadin-text-field ...="${field(model.name)}">
* </vaadin-text-field>
* ```
*/
export const field = directive(<T>(model: AbstractModel<T>, effect?: (element: Element) => void) => (part: Part) => {
const propertyPart = part as PropertyPart;
if (!(part instanceof PropertyPart) || propertyPart.committer.name !== '..') {
throw new Error('Only supports ...="" syntax');
}
let fieldState: FieldState;
const element = propertyPart.committer.element as HTMLInputElement & Field;

const binderNode = getBinderNode(model);
const fieldStrategy = binderNode.binder.getFieldStrategy(element);

const convertFieldValue = (fieldValue: any) => {
const fromString = (model as any)[_fromString];
return typeof fieldValue === 'string' && fromString ? fromString(fieldValue) : fieldValue;
};

if (fieldStateMap.has(propertyPart)) {
fieldState = fieldStateMap.get(propertyPart)!;
} else {
fieldState = {
name: '',
value: '',
required: false,
invalid: false,
errorMessage: '',
strategy: fieldStrategy
};
fieldStateMap.set(propertyPart, fieldState);

const updateValueFromElement = () => {
fieldState.value = fieldState.strategy.value;
binderNode.value = convertFieldValue(fieldState.value);
if (effect !== undefined) {
effect.call(element, element);
export const field = directive(
class extends Directive {
fieldState?: FieldState;

constructor(partInfo: PartInfo) {
super(partInfo);
if (partInfo.type !== PartType.PROPERTY && partInfo.type !== PartType.ELEMENT) {
throw new Error('Use as "<element {field(...)}" or <element ...={field(...)}"');
}
};
}

element.oninput = () => {
updateValueFromElement();
};
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
render(model: AbstractModel<any>, effect?: (element: Element) => void) {
return nothing;
}

element.onchange = element.onblur = () => {
updateValueFromElement();
binderNode.visited = true;
};
update(part: PropertyPart | ElementPart, [model, effect]: DirectiveParameters<this>) {
const element = part.element as HTMLInputElement & Field;

const binderNode = getBinderNode(model);
const fieldStrategy = binderNode.binder.getFieldStrategy(element);

const convertFieldValue = (fieldValue: any) => {
const fromString = (model as any)[_fromString];
return typeof fieldValue === 'string' && fromString ? fromString(fieldValue) : fieldValue;
};

if (!this.fieldState) {
this.fieldState = {
name: '',
value: '',
required: false,
invalid: false,
errorMessage: '',
strategy: fieldStrategy
};

const { fieldState } = this;

const updateValueFromElement = () => {
fieldState.value = fieldState.strategy.value;
binderNode.value = convertFieldValue(fieldState.value);
if (effect !== undefined) {
effect.call(element, element);
}
};

element.oninput = () => {
updateValueFromElement();
};

const changeBlurHandler = () => {
updateValueFromElement();
binderNode.visited = true;
};
element.onblur = changeBlurHandler;
element.onchange = changeBlurHandler;

element.checkValidity = () => !fieldState.invalid;
}

element.checkValidity = () => !fieldState.invalid;
}
const { fieldState } = this;
const { name } = binderNode;
if (name !== fieldState.name) {
fieldState.name = name;
element.setAttribute('name', name);
}

const name = binderNode.name;
if (name !== fieldState.name) {
fieldState.name = name;
element.setAttribute('name', name);
}
const { value } = binderNode;
const valueFromField = convertFieldValue(fieldState.value);
if (value !== valueFromField && !(Number.isNaN(value) && Number.isNaN(valueFromField))) {
fieldState.value = value;
fieldState.strategy.value = value;
}

const value = binderNode.value;
const valueFromField = convertFieldValue(fieldState.value);
if (value !== valueFromField && !(Number.isNaN(value) && Number.isNaN(valueFromField))) {
fieldState.strategy.value = fieldState.value = value;
}
const { required } = binderNode;
if (required !== fieldState.required) {
fieldState.required = required;
fieldState.strategy.required = required;
}

const required = binderNode.required;
if (required !== fieldState.required) {
fieldState.strategy.required = fieldState.required = required;
}
const firstError = binderNode.ownErrors ? binderNode.ownErrors[0] : undefined;
const errorMessage = (firstError && firstError.message) || '';
if (errorMessage !== fieldState.errorMessage) {
fieldState.errorMessage = errorMessage;
fieldState.strategy.errorMessage = errorMessage;
}

const firstError = binderNode.ownErrors ? binderNode.ownErrors[0] : undefined;
const errorMessage = (firstError && firstError.message) || '';
if (errorMessage !== fieldState.errorMessage) {
fieldState.strategy.errorMessage = fieldState.errorMessage = errorMessage;
}
const { invalid } = binderNode;
if (invalid !== fieldState.invalid) {
fieldState.invalid = invalid;
fieldState.strategy.invalid = invalid;
}

const invalid = binderNode.invalid;
if (invalid !== fieldState.invalid) {
fieldState.strategy.invalid = fieldState.invalid = invalid;
return noChange;
}
}
});
);
3 changes: 2 additions & 1 deletion flow-client/src/test/frontend/form/BinderTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {

import {Employee, EmployeeModel, Order, OrderModel, TestEntity, TestModel} from "./TestModels";

import {customElement, LitElement} from 'lit-element';
import {LitElement} from 'lit';
import {customElement} from 'lit/decorators';

@customElement('lit-order-view')
class LitOrderView extends LitElement {}
Expand Down
Loading

0 comments on commit fba584d

Please sign in to comment.