Commit d0da616d authored by Benjamin Franzke's avatar Benjamin Franzke
Browse files

[TASK] Update to Lit v2-rc1

Lit is the umbrella term for the next major
lit-html (v2) and lit-element (v3) versions.
Therefore we will refer to these components as
*Lit* in TYPO3 from now on as well.

These two libraries also have been migrated into
a single entry point module named `lit`.
It is officially described as:
> The main module exports the core pieces needed for component
> development: LitElement, html, css, and the most

lit-html v2 and lit-element v3 are mostly compatible
to the previous major versions. Main changes are

 * Deprecation of the `lit-element` entry point in
   favor of the new `lit` module
 * @internalProperty changed to @state
 * shadow css declaration via static property
   instead of static getter method
 * The CSSResult type declaration is gone
 * Directive (currently unused in TYPO3) API has changed

Related testing framework change is:
https://github.com/TYPO3/testing-framework/pull/229

Commands used:

  rm -rf typo3/sysext/core/Resources/Public/JavaScript/Contrib/{@lit,lit-element,lit-html,lit}/
  yarn add lit@^2.0.0-rc.1 lit-html@^2.0.0-rc.2 lit-element@^3.0.0-rc.1
  yarn add --dev rollup@^2.32.0 @rollup/plugin-replace
  grunt build
  git add typo3/sysext/core/Resources/Public/JavaScript/Contrib/{@lit,lit-element,lit-html,lit}/

  composer require --dev typo3/testing-framework:^6.8.1
  composer require --dev typo3/testing-framework:^6.8.1 \
    --no-update --working-dir=typo3/sysext/core

Resolves: #93965
Releases: master
Change-Id: I9b659d851e6ad9dc3cc649bd40aab886b86fb0f8
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/68104

Tested-by: Oliver Hader's avatarOliver Hader <oliver.hader@typo3.org>
Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Tested-by: Benjamin Franzke's avatarBenjamin Franzke <bfr@qbus.de>
Reviewed-by: Oliver Hader's avatarOliver Hader <oliver.hader@typo3.org>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Benjamin Franzke's avatarBenjamin Franzke <bfr@qbus.de>
parent 0005bc5b
...@@ -451,37 +451,43 @@ module.exports = function (grunt) { ...@@ -451,37 +451,43 @@ module.exports = function (grunt) {
}, },
rollup: { rollup: {
options: { options: {
format: 'amd', format: 'amd'
onwarn: function(warning) {
if (warning.code === 'THIS_IS_UNDEFINED' && grunt.file.match('*/lit-html/directives/async-*.js')) {
// lit-html's Symbol.asyncIterator polyfill in async-{append/replace}.js contains
// a global check for `this`: `(this && this.__asyncValues) || function (o) {`.
// rollup will rewrite that to `function (o) {` and warn about rewriting `this`.
// The rewrite is perfectly ok, the AMD module will act as a singleton, so no
// global window object is needed here. The warning is therefore silenced.
return;
}
console.warn( warning.message );
}
}, },
'lit-html': { 'lit-html': {
options: { options: {
preserveModules: true, preserveModules: true,
plugins: () => [ plugins: () => [
require('@rollup/plugin-replace')({ values: { 'globalThis': 'window' }, preventAssignment: false }),
{ {
name: 'terser', name: 'terser',
renderChunk: code => require('terser').minify(code, grunt.config.get('terser.options')) renderChunk: code => require('terser').minify(code, {...grunt.config.get('terser.options'), ...{mangle: false}})
} }
] ]
}, },
files: { files: {
'<%= paths.core %>Public/JavaScript/Contrib/lit-html': [ '<%= paths.core %>Public/JavaScript/Contrib/lit-html': [
'node_modules/lit-html/lit-html.js', 'node_modules/lit-html/*.js',
'node_modules/lit-html/directives/*.js', 'node_modules/lit-html/directives/*.js',
'node_modules/lit-html/lib/*.js', ]
}
},
'@lit/reactive-element': {
options: {
preserveModules: true,
plugins: () => [
require('@rollup/plugin-replace')({ values: { 'globalThis': 'window' }, preventAssignment: false }),
{
name: 'terser',
renderChunk: code => require('terser').minify(code, {...grunt.config.get('terser.options'), ...{mangle: false}})
},
]
},
files: {
'<%= paths.core %>Public/JavaScript/Contrib/@lit/reactive-element': [
'node_modules/@lit/reactive-element/*.js',
'node_modules/@lit/reactive-element/decorators/*.js',
// omitted, empty // omitted, empty
'!node_modules/lit-html/lib/render-options.js', '!node_modules/@lit/reactive-element/reactive-controller.js'
'!node_modules/lit-html/lib/template-processor.js',
] ]
} }
}, },
...@@ -489,18 +495,16 @@ module.exports = function (grunt) { ...@@ -489,18 +495,16 @@ module.exports = function (grunt) {
options: { options: {
preserveModules: true, preserveModules: true,
plugins: () => [ plugins: () => [
require('@rollup/plugin-replace')({ values: { 'globalThis': 'window' }, preventAssignment: false }),
{ {
name: 'terser', name: 'terser',
renderChunk: code => require('terser').minify(code, grunt.config.get('terser.options')) renderChunk: code => require('terser').minify(code, {...grunt.config.get('terser.options'), ...{mangle: false}})
}, },
{ {
name: 'externals', name: 'externals',
resolveId: (source) => { resolveId: (source) => {
if (source === 'lit-html/lit-html.js') { if (source.startsWith('lit-html') || source.startsWith('@lit/reactive-element')) {
return {id: 'lit-html', external: true} return {id: source.replace(/\.js$/, ''), external: true}
}
if (source === 'lit-html/lib/shady-render.js') {
return {id: 'lit-html/lib/shady-render', external: true}
} }
return null return null
} }
...@@ -509,8 +513,36 @@ module.exports = function (grunt) { ...@@ -509,8 +513,36 @@ module.exports = function (grunt) {
}, },
files: { files: {
'<%= paths.core %>Public/JavaScript/Contrib/lit-element': [ '<%= paths.core %>Public/JavaScript/Contrib/lit-element': [
'node_modules/lit-element/lit-element.js', 'node_modules/lit-element/*.js',
'node_modules/lit-element/lib/*.js', 'node_modules/lit-element/decorators/*.js',
]
}
},
'lit': {
options: {
preserveModules: true,
plugins: () => [
require('@rollup/plugin-replace')({ values: { 'globalThis': 'window' }, preventAssignment: false }),
{
name: 'terser',
renderChunk: code => require('terser').minify(code, {...grunt.config.get('terser.options'), ...{mangle: false}})
},
{
name: 'externals',
resolveId: (source) => {
if (source.startsWith('lit-html') || source.startsWith('lit-element') || source.startsWith('@lit/reactive-element')) {
return {id: source.replace(/\.js$/, ''), external: true}
}
return null
}
}
]
},
files: {
'<%= paths.core %>Public/JavaScript/Contrib/lit': [
'node_modules/lit/*.js',
'node_modules/lit/decorators/*.js',
'node_modules/lit/directives/*.js',
] ]
} }
}, },
......
...@@ -12,13 +12,22 @@ ...@@ -12,13 +12,22 @@
*/ */
import module = require('module'); import module = require('module');
import {html, css, unsafeCSS, customElement, property, LitElement, TemplateResult, CSSResult} from 'lit-element'; import {html, css, unsafeCSS, LitElement, TemplateResult, CSSResult} from 'lit';
import {unsafeHTML} from 'lit-html/directives/unsafe-html'; import {customElement, property} from 'lit/decorators';
import {until} from 'lit-html/directives/until'; import {unsafeHTML} from 'lit/directives/unsafe-html';
import {until} from 'lit/directives/until';
import {Sizes, States, MarkupIdentifiers} from '../Enum/IconTypes'; import {Sizes, States, MarkupIdentifiers} from '../Enum/IconTypes';
import Icons = require('../Icons'); import Icons = require('../Icons');
import 'TYPO3/CMS/Backend/Element/SpinnerElement'; import 'TYPO3/CMS/Backend/Element/SpinnerElement';
const iconUnifyModifier = 0.86;
const iconSize = (identifier: CSSResult, size: number) => css`
:host([size=${identifier}]),
:host([raw]) .icon-size-${identifier} {
font-size: ${size}px;
}
`;
/** /**
* Module: TYPO3/CMS/Backend/Element/IconElement * Module: TYPO3/CMS/Backend/Element/IconElement
* *
...@@ -45,106 +54,94 @@ export class IconElement extends LitElement { ...@@ -45,106 +54,94 @@ export class IconElement extends LitElement {
*/ */
@property({type: String}) raw?: string = null; @property({type: String}) raw?: string = null;
public static get styles(): CSSResult[] static styles = [
{ css`
const iconUnifyModifier = 0.86; :host {
const iconSize = (identifier: CSSResult, size: number) => css` display: flex;
:host([size=${identifier}]), font-size: 1em;
:host([raw]) .icon-size-${identifier} { width: 1em;
font-size: ${size}px; height: 1em;
line-height: 0;
} }
`;
return [
css`
:host {
display: flex;
font-size: 1em;
width: 1em;
height: 1em;
line-height: 0;
}
typo3-backend-spinner { typo3-backend-spinner {
font-size: 1em; font-size: 1em;
} }
.icon { .icon {
position: relative; position: relative;
display: block; display: block;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
height: 1em; height: 1em;
width: 1em; width: 1em;
line-height: 1; line-height: 1;
} }
.icon svg,
.icon img {
display: block;
height: 1em;
width: 1em;
transform: translate3d(0, 0, 0);
}
.icon * { .icon svg,
display: block; .icon img {
line-height: inherit; display: block;
} height: 1em;
width: 1em;
transform: translate3d(0, 0, 0);
}
.icon-markup { .icon * {
position: absolute; display: block;
display: flex; line-height: inherit;
justify-content: center; }
align-items: center;
text-align: center;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.icon-overlay { .icon-markup {
position: absolute; position: absolute;
bottom: 0; display: flex;
right: 0; justify-content: center;
font-size: 0.6875em; align-items: center;
text-align: center; text-align: center;
} top: 0;
left: 0;
right: 0;
bottom: 0;
}
.icon-color { .icon-overlay {
fill: currentColor; position: absolute;
} bottom: 0;
right: 0;
font-size: 0.6875em;
text-align: center;
}
.icon-state-disabled .icon-markup { .icon-color {
opacity: .5; fill: currentColor;
} }
.icon-unify { .icon-state-disabled .icon-markup {
font-size: ${iconUnifyModifier}em; opacity: .5;
line-height: ${1 / iconUnifyModifier}; }
}
.icon-spin .icon-markup { .icon-unify {
animation: icon-spin 2s infinite linear; font-size: ${iconUnifyModifier}em;
} line-height: ${1 / iconUnifyModifier};
}
@keyframes icon-spin { .icon-spin .icon-markup {
0% { animation: icon-spin 2s infinite linear;
transform: rotate(0deg); }
}
100% { @keyframes icon-spin {
transform: rotate(360deg); 0% {
} transform: rotate(0deg);
} }
`, 100% {
iconSize(unsafeCSS(Sizes.small), 16), transform: rotate(360deg);
iconSize(unsafeCSS(Sizes.default), 32), }
iconSize(unsafeCSS(Sizes.large), 48), }
iconSize(unsafeCSS(Sizes.mega), 64), `,
]; iconSize(unsafeCSS(Sizes.small), 16),
} iconSize(unsafeCSS(Sizes.default), 32),
iconSize(unsafeCSS(Sizes.large), 48),
iconSize(unsafeCSS(Sizes.mega), 64),
];
public render(): TemplateResult { public render(): TemplateResult {
if (this.raw) { if (this.raw) {
......
...@@ -11,7 +11,8 @@ ...@@ -11,7 +11,8 @@
* The TYPO3 project - inspiring people to share! * The TYPO3 project - inspiring people to share!
*/ */
import {html, css, customElement, property, LitElement, TemplateResult, CSSResult} from 'lit-element'; import {html, css, LitElement, TemplateResult} from 'lit';
import {customElement, property} from 'lit/decorators';
import {Sizes} from '../Enum/IconTypes'; import {Sizes} from '../Enum/IconTypes';
/** /**
...@@ -25,42 +26,39 @@ import {Sizes} from '../Enum/IconTypes'; ...@@ -25,42 +26,39 @@ import {Sizes} from '../Enum/IconTypes';
export class SpinnerElement extends LitElement { export class SpinnerElement extends LitElement {
@property({type: String}) size: Sizes = Sizes.default; @property({type: String}) size: Sizes = Sizes.default;
public static get styles(): CSSResult static styles = css`
{ :host {
return css` font-size: 32px;
:host { width: 1em;
font-size: 32px; height: 1em;
width: 1em; display: flex;
height: 1em; justify-content: center;
display: flex; align-items: center;
justify-content: center; }
align-items: center; .spinner {
} display: block;
.spinner { border-style: solid;
display: block; border-color: #212121 #bababa #bababa;
border-style: solid; border-radius: 50%;
border-color: #212121 #bababa #bababa; width: 0.625em;
border-radius: 50%; height: 0.625em;
width: 0.625em; border-width: 0.0625em;
height: 0.625em; animation: spin 1s linear infinite;
border-width: 0.0625em; }
animation: spin 1s linear infinite; :host([size=small]) .spinner {
} font-size: 16px;
:host([size=small]) .spinner { }
font-size: 16px; :host([size=large]) .spinner {
} font-size: 48px;
:host([size=large]) .spinner { }
font-size: 48px; :host([size=mega]) .spinner {
} font-size: 64px;
:host([size=mega]) .spinner { }
font-size: 64px; @keyframes spin {
} 0% { transform: rotate(0deg); }
@keyframes spin { 100% { transform: rotate(360deg); }
0% { transform: rotate(0deg); } }
100% { transform: rotate(360deg); } `;
}
`;
}
public render(): TemplateResult { public render(): TemplateResult {
return html`<div class="spinner"></div>` return html`<div class="spinner"></div>`
......
...@@ -11,7 +11,8 @@ ...@@ -11,7 +11,8 @@
* The TYPO3 project - inspiring people to share! * The TYPO3 project - inspiring people to share!
*/ */
import {html, customElement, property, LitElement, TemplateResult} from 'lit-element'; import {html, LitElement, TemplateResult} from 'lit';
import {customElement, property} from 'lit/decorators';
import {lll} from 'TYPO3/CMS/Core/lit-helper'; import {lll} from 'TYPO3/CMS/Core/lit-helper';
import 'TYPO3/CMS/Backend/Element/IconElement'; import 'TYPO3/CMS/Backend/Element/IconElement';
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
import * as d3selection from 'd3-selection'; import * as d3selection from 'd3-selection';
import {SvgTree, SvgTreeSettings, TreeNodeSelection} from '../../SvgTree'; import {SvgTree, SvgTreeSettings, TreeNodeSelection} from '../../SvgTree';
import {TreeNode} from '../../Tree/TreeNode'; import {TreeNode} from '../../Tree/TreeNode';
import {customElement} from 'lit-element'; import {customElement} from 'lit/decorators';
interface SelectTreeSettings extends SvgTreeSettings { interface SelectTreeSettings extends SvgTreeSettings {
exclusiveNodesIdentifiers: ''; exclusiveNodesIdentifiers: '';
......
...@@ -13,7 +13,8 @@ ...@@ -13,7 +13,8 @@
import type {SelectTree} from './SelectTree'; import type {SelectTree} from './SelectTree';
import {Tooltip} from 'bootstrap'; import {Tooltip} from 'bootstrap';
import {html, customElement, LitElement, TemplateResult} from 'lit-element'; import {html, LitElement, TemplateResult} from 'lit';
import {customElement} from 'lit/decorators';
import {lll} from 'TYPO3/CMS/Core/lit-helper'; import {lll} from 'TYPO3/CMS/Core/lit-helper';
import 'TYPO3/CMS/Backend/Element/IconElement'; import 'TYPO3/CMS/Backend/Element/IconElement';
import './SelectTree'; import './SelectTree';
......
...@@ -11,7 +11,8 @@ ...@@ -11,7 +11,8 @@
* The TYPO3 project - inspiring people to share! * The TYPO3 project - inspiring people to share!
*/ */
import {customElement, property, LitElement} from 'lit-element'; import {LitElement} from 'lit';
import {customElement, property} from 'lit/decorators';
import FormEngine = require('TYPO3/CMS/Backend/FormEngine'); import FormEngine = require('TYPO3/CMS/Backend/FormEngine');
enum UpdateMode { enum UpdateMode {
......
...@@ -16,8 +16,8 @@ import Viewport = require('./Viewport'); ...@@ -16,8 +16,8 @@ import Viewport = require('./Viewport');
import Icons = require('./Icons'); import Icons = require('./Icons');
import 'jquery/autocomplete'; import 'jquery/autocomplete';
import './Input/Clearable'; import './Input/Clearable';
import {html, render} from 'lit-html'; import {html, render} from 'lit';
import {unsafeHTML} from 'lit-html/directives/unsafe-html'; import {unsafeHTML} from 'lit/directives/unsafe-html';
import {renderHTML} from 'TYPO3/CMS/Core/lit-helper'; import {renderHTML} from 'TYPO3/CMS/Core/lit-helper';
enum Identifiers { enum Identifiers {
......
...@@ -11,9 +11,10 @@ ...@@ -11,9 +11,10 @@
* The TYPO3 project - inspiring people to share! * The TYPO3 project - inspiring people to share!
*/ */
import {LitElement, html, customElement, property, internalProperty} from 'lit-element'; import {LitElement, html} from 'lit';
import {classMap} from 'lit-html/directives/class-map'; import {customElement, property, state} from 'lit/decorators';
import {ifDefined} from 'lit-html/directives/if-defined'; import {classMap} from 'lit/directives/class-map';
import {ifDefined} from 'lit/directives/if-defined';
import {AbstractAction} from './ActionButton/AbstractAction'; import {AbstractAction} from './ActionButton/AbstractAction';
import {SeverityEnum} from './Enum/Severity'; import {SeverityEnum} from './Enum/Severity';
import Severity = require('./Severity'); import Severity = require('./Severity');
...@@ -136,8 +137,8 @@ class NotificationMessage extends LitElement { ...@@ -136,8 +137,8 @@ class NotificationMessage extends LitElement {
@property() duration: number = 0; @property() duration: number = 0;
@property({type: Array, attribute: false}) actions: Array<Action> = []; @property({type: Array, attribute: false}) actions: Array<Action> = [];
@internalProperty() visible: boolean = false; @state() visible: boolean = false;
@internalProperty() executingAction: number = -1; @state() executingAction: number = -1;
createRenderRoot(): Element|ShadowRoot { createRenderRoot(): Element|ShadowRoot {
return this; return this;
......