update Vaadin to 25.2.6 (#95)
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 3s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 3s
Co-authored-by: Thomas Peetz <thomas.peetz@cimt-ag.de> Reviewed-on: #95
This commit was merged in pull request #95.
This commit is contained in:
@@ -2,7 +2,6 @@ plugins {
|
|||||||
id 'java'
|
id 'java'
|
||||||
id 'application'
|
id 'application'
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
//id "com.google.cloud.artifactregistry.gradle-plugin" version "2.2.0"
|
|
||||||
id 'jvm-test-suite'
|
id 'jvm-test-suite'
|
||||||
id 'jacoco'
|
id 'jacoco'
|
||||||
id 'test-report-aggregation'
|
id 'test-report-aggregation'
|
||||||
@@ -11,13 +10,11 @@ plugins {
|
|||||||
alias(libs.plugins.spring.dependencies)
|
alias(libs.plugins.spring.dependencies)
|
||||||
alias(libs.plugins.vaadin)
|
alias(libs.plugins.vaadin)
|
||||||
alias(libs.plugins.lombok)
|
alias(libs.plugins.lombok)
|
||||||
//id 'com.github.ksoichiro.build.info' version '0.2.0'
|
alias(libs.plugins.gradle.git.properties)
|
||||||
//id 'com.pasam.gradle.buildinfo' version '0.1.3'
|
|
||||||
id 'com.gorylenko.gradle-git-properties' version '4.0.1'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven { setUrl("https://nexus.thpeetz.de/repository/maven-central") }
|
maven { setUrl("https://nexus.thpeetz.de/repository/maven-public") }
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { setUrl("https://maven.vaadin.com/vaadin-prereleases") }
|
maven { setUrl("https://maven.vaadin.com/vaadin-prereleases") }
|
||||||
maven { setUrl("https://repo.spring.io/milestone") }
|
maven { setUrl("https://repo.spring.io/milestone") }
|
||||||
@@ -25,7 +22,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
@@ -38,23 +35,23 @@ configurations {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.vaadin:vaadin-core'
|
implementation 'com.vaadin:vaadin-core'
|
||||||
implementation 'com.vaadin:vaadin-spring-boot-starter'
|
implementation 'com.vaadin:vaadin-spring-boot-starter'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-artemis'
|
implementation 'org.springframework.boot:spring-boot-starter-artemis'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||||
implementation 'org.apache.camel.springboot:camel-spring-boot-starter'
|
implementation 'org.springframework.security:spring-security-oauth2-jose'
|
||||||
|
implementation 'org.springframework.security:spring-security-oauth2-resource-server'
|
||||||
implementation 'org.apache.camel.springboot:camel-jms-starter'
|
implementation 'org.apache.camel.springboot:camel-jms-starter'
|
||||||
implementation 'org.apache.camel.springboot:camel-metrics-starter'
|
implementation 'org.apache.camel.springboot:camel-metrics-starter'
|
||||||
implementation 'org.apache.camel.springboot:camel-micrometer-starter'
|
implementation 'org.apache.camel.springboot:camel-micrometer-starter'
|
||||||
|
implementation 'org.apache.camel.springboot:camel-spring-boot-starter'
|
||||||
implementation 'org.apache.activemq:artemis-jakarta-client'
|
implementation 'org.apache.activemq:artemis-jakarta-client'
|
||||||
//implementation libs.artemis
|
//implementation libs.artemis
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
|
||||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||||
implementation 'io.micrometer:micrometer-registry-prometheus'
|
implementation 'io.micrometer:micrometer-registry-prometheus'
|
||||||
implementation libs.jolokia.core
|
implementation libs.jolokia.core
|
||||||
implementation libs.prometheus.collector
|
implementation libs.prometheus.collector
|
||||||
implementation 'org.springframework.security:spring-security-oauth2-jose'
|
|
||||||
implementation 'org.springframework.security:spring-security-oauth2-resource-server'
|
|
||||||
implementation 'com.h2database:h2'
|
implementation 'com.h2database:h2'
|
||||||
implementation libs.hsqldb
|
implementation libs.hsqldb
|
||||||
implementation 'org.postgresql:postgresql'
|
implementation 'org.postgresql:postgresql'
|
||||||
@@ -149,7 +146,6 @@ testing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
test(JvmTestSuite) {
|
test(JvmTestSuite) {
|
||||||
testType = TestSuiteType.UNIT_TEST
|
|
||||||
targets {
|
targets {
|
||||||
all {
|
all {
|
||||||
testTask.configure {
|
testTask.configure {
|
||||||
@@ -165,7 +161,6 @@ testing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
integrationTest(JvmTestSuite) {
|
integrationTest(JvmTestSuite) {
|
||||||
testType = "view-test"
|
|
||||||
targets {
|
targets {
|
||||||
all {
|
all {
|
||||||
testTask.configure {
|
testTask.configure {
|
||||||
@@ -197,13 +192,10 @@ jacocoTestReport {
|
|||||||
reporting {
|
reporting {
|
||||||
reports {
|
reports {
|
||||||
testAggregateTestReport(AggregateTestReport) {
|
testAggregateTestReport(AggregateTestReport) {
|
||||||
testType = TestSuiteType.UNIT_TEST
|
|
||||||
}
|
}
|
||||||
integrationTestAggregateTestReport(AggregateTestReport) {
|
integrationTestAggregateTestReport(AggregateTestReport) {
|
||||||
testType = "view-test"
|
|
||||||
}
|
}
|
||||||
integrationTestCodeCoverageReport(JacocoCoverageReport) {
|
integrationTestCodeCoverageReport(JacocoCoverageReport) {
|
||||||
testType = "view-test"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"lumoImports" : [ "typography", "color", "spacing", "badge", "utility" ]
|
|
||||||
}
|
|
||||||
@@ -1,39 +1,32 @@
|
|||||||
[versions]
|
[versions]
|
||||||
gradle = "8.6"
|
gradle = "9.5.1"
|
||||||
args4j = "2.33"
|
springboot = "4.1.0"
|
||||||
commonscli = "1.5.0"
|
springdependencies = "1.1.7"
|
||||||
junit = "5.8.2"
|
vaadin = "25.2.6"
|
||||||
logback = "1.6.3"
|
|
||||||
mockito = "1.9.5"
|
|
||||||
picoli = "4.7.0"
|
|
||||||
slf4j = "2.0.18"
|
|
||||||
hsqldb = "2.7.1"
|
|
||||||
sqlite = "3.25.2"
|
|
||||||
spotbugs = "6.0.7"
|
|
||||||
sonarqube = "3.3"
|
|
||||||
springboot = "3.2.5"
|
|
||||||
springdependencies = "1.1.4"
|
|
||||||
vaadin = "24.4.23"
|
|
||||||
camel = "4.10.6"
|
camel = "4.10.6"
|
||||||
artemis = "2.41.0"
|
artemis = "2.41.0"
|
||||||
|
logback = "1.6.3"
|
||||||
lombok = "8.11"
|
lombok = "8.11"
|
||||||
gson = "2.9.0"
|
gson = "2.9.0"
|
||||||
jackson = "2.16.1"
|
jackson = "2.16.1"
|
||||||
json_simple = "1.1.1"
|
json_simple = "1.1.1"
|
||||||
jsoup = "1.23.1"
|
jsoup = "1.23.1"
|
||||||
mail = "1.6.2"
|
mail = "1.6.2"
|
||||||
hypersistence = "3.9.10"
|
hypersistence = "3.15.4"
|
||||||
jolokia = "2.6.1"
|
jolokia = "2.6.1"
|
||||||
prometheus = "1.6.0"
|
prometheus = "1.6.0"
|
||||||
|
slf4j = "2.0.18"
|
||||||
|
junit = "5.8.2"
|
||||||
|
hsqldb = "2.7.1"
|
||||||
|
sqlite = "3.25.2"
|
||||||
|
spotbugs = "6.0.7"
|
||||||
|
sonarqube = "3.3"
|
||||||
|
gradleGitProperties = "4.0.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
args4j = { module = "args4j:args4j", version.ref = "args4j" }
|
|
||||||
commonscli = { module = "commons-cli:commons-cli", version.ref = "commonscli" }
|
|
||||||
junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
|
junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
|
||||||
logbackCore = { module = "ch.qos.logback:logback-core", version.ref = "logback" }
|
logbackCore = { module = "ch.qos.logback:logback-core", version.ref = "logback" }
|
||||||
logbackClassic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
logbackClassic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
||||||
mockito = { module = "org.mockito:mockito-all", version.ref = "mockito" }
|
|
||||||
picocli = { module = "info.picocli:picocli", version.ref = "picoli" }
|
|
||||||
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
||||||
hsqldb = { module = "org.hsqldb:hsqldb", version.ref = "hsqldb" }
|
hsqldb = { module = "org.hsqldb:hsqldb", version.ref = "hsqldb" }
|
||||||
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
||||||
@@ -42,7 +35,7 @@ jackson = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref
|
|||||||
json = { module = "com.googlecode.json-simple:json-simple", version.ref ="json_simple" }
|
json = { module = "com.googlecode.json-simple:json-simple", version.ref ="json_simple" }
|
||||||
mail = { module = "com.sun.mail:javax.mail", version.ref ="mail" }
|
mail = { module = "com.sun.mail:javax.mail", version.ref ="mail" }
|
||||||
sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
|
sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
|
||||||
hypersistence = { module = "io.hypersistence:hypersistence-utils-hibernate-63", version.ref = "hypersistence" }
|
hypersistence = { module = "io.hypersistence:hypersistence-utils-hibernate-73", version.ref = "hypersistence" }
|
||||||
vaadin-bom = { module = "com.vaadin:vaadin-bom", version.ref = "vaadin" }
|
vaadin-bom = { module = "com.vaadin:vaadin-bom", version.ref = "vaadin" }
|
||||||
camel-bom = { module = "org.apache.camel.springboot:camel-spring-boot-bom", version.ref = "camel"}
|
camel-bom = { module = "org.apache.camel.springboot:camel-spring-boot-bom", version.ref = "camel"}
|
||||||
artemis = { module = "org.apache.activemq:artemis-jms-server", version.ref = "artemis" }
|
artemis = { module = "org.apache.activemq:artemis-jms-server", version.ref = "artemis" }
|
||||||
@@ -59,3 +52,4 @@ spring-boot = { id = "org.springframework.boot", version.ref = "springboot"}
|
|||||||
spring-dependencies = { id = "io.spring.dependency-management", version.ref = "springdependencies" }
|
spring-dependencies = { id = "io.spring.dependency-management", version.ref = "springdependencies" }
|
||||||
vaadin = { id = "com.vaadin", version.ref = "vaadin" }
|
vaadin = { id = "com.vaadin", version.ref = "vaadin" }
|
||||||
lombok = { id = "io.freefair.lombok", version.ref = "lombok" }
|
lombok = { id = "io.freefair.lombok", version.ref = "lombok" }
|
||||||
|
gradle-git-properties = { id = "com.gorylenko.gradle-git-properties", version.ref = "gradleGitProperties" }
|
||||||
|
|||||||
BIN
Binary file not shown.
+3
-1
@@ -1,7 +1,9 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
|
retries=0
|
||||||
|
retryBackOffMs=500
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
Vendored
+7
-8
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright © 2015-2021 the original authors.
|
# Copyright © 2015 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -15,6 +15,8 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
@@ -55,7 +57,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -84,7 +86,7 @@ done
|
|||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -112,7 +114,6 @@ case "$( uname )" in #(
|
|||||||
NONSTOP* ) nonstop=true ;;
|
NONSTOP* ) nonstop=true ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
@@ -170,7 +171,6 @@ fi
|
|||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if "$cygwin" || "$msys" ; then
|
if "$cygwin" || "$msys" ; then
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
@@ -203,15 +203,14 @@ fi
|
|||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Collect all arguments for the java command:
|
# Collect all arguments for the java command:
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
# and any embedded shellness will be escaped.
|
# and any embedded shellness will be escaped.
|
||||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
# treated as '${Hostname}' itself on the command line.
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
-classpath "$CLASSPATH" \
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
# Stop when "xargs" is not available.
|
||||||
|
|||||||
Vendored
+12
-22
@@ -13,6 +13,8 @@
|
|||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@@ -21,8 +23,8 @@
|
|||||||
@rem
|
@rem
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
setlocal EnableExtensions
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
@@ -49,7 +51,7 @@ echo. 1>&2
|
|||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
:findJavaFromJavaHome
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
@@ -63,30 +65,18 @@ echo. 1>&2
|
|||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||||
|
@rem which allows us to clear the local environment before executing the java command
|
||||||
|
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
||||||
|
|
||||||
:end
|
:exitWithErrorLevel
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||||
|
|
||||||
:fail
|
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
|
||||||
rem the _cmd.exe /c_ return code!
|
|
||||||
set EXIT_CODE=%ERRORLEVEL%
|
|
||||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
|
||||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
|
||||||
exit /b %EXIT_CODE%
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
export {}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export declare const applyCss: (target: Node) => void;
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { injectGlobalCss } from 'Frontend/generated/jar-resources/theme-util.js';
|
||||||
|
import { webcomponentGlobalCssInjector } from 'Frontend/generated/jar-resources/theme-util.js';
|
||||||
|
let needsReloadOnChanges = false;
|
||||||
|
|
||||||
|
let themeRemovers = new WeakMap();
|
||||||
|
let targets = [];
|
||||||
|
const fontFaceRegex = /(@font-face\s*{[\s\S]*?})/g;
|
||||||
|
|
||||||
|
export const applyCss = (target) => {
|
||||||
|
const removers = [];
|
||||||
|
if (target !== document) {
|
||||||
|
|
||||||
|
webcomponentGlobalCssInjector((css) => {
|
||||||
|
removers.push(injectGlobalCss(css, '', target));
|
||||||
|
if(fontFaceRegex.test(css)) {
|
||||||
|
const fontFaces = Array.from(css.match(fontFaceRegex));
|
||||||
|
fontFaces.forEach(fontFace => {
|
||||||
|
removers.push(injectGlobalCss(fontFace, '', document));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
targets.push(new WeakRef(target));
|
||||||
|
themeRemovers.set(target, removers);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept((module) => {
|
||||||
|
|
||||||
|
if (needsReloadOnChanges) {
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
targets.forEach(targetRef => {
|
||||||
|
const target = targetRef.deref();
|
||||||
|
if (target) {
|
||||||
|
themeRemovers.get(target).forEach(remover => remover())
|
||||||
|
module.applyTheme(target);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
import.meta.hot.on('vite:afterUpdate', (update) => {
|
||||||
|
document.dispatchEvent(new CustomEvent('vaadin-theme-updated', { detail: update }));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,707 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
/// <reference lib="es2018" />
|
||||||
|
import { Flow as _Flow } from 'Frontend/generated/jar-resources/Flow.js';
|
||||||
|
import React, { useCallback, useEffect, useReducer, useRef, useState, type ReactNode } from 'react';
|
||||||
|
import { matchRoutes, useBlocker, useLocation, useNavigate, type NavigateOptions, useHref } from 'react-router';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
|
const flow = new _Flow({
|
||||||
|
imports: () => import('Frontend/generated/flow/generated-flow-imports.js')
|
||||||
|
});
|
||||||
|
|
||||||
|
const router = {
|
||||||
|
render() {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const flowReact : { active: boolean } = {
|
||||||
|
active: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClickHandler for vaadin-router-go event is copied from vaadin/router click.js
|
||||||
|
// @ts-ignore
|
||||||
|
function getAnchorOrigin(anchor) {
|
||||||
|
// IE11: on HTTP and HTTPS the default port is not included into
|
||||||
|
// window.location.origin, so won't include it here either.
|
||||||
|
const port = anchor.port;
|
||||||
|
const protocol = anchor.protocol;
|
||||||
|
const defaultHttp = protocol === 'http:' && port === '80';
|
||||||
|
const defaultHttps = protocol === 'https:' && port === '443';
|
||||||
|
const host =
|
||||||
|
defaultHttp || defaultHttps
|
||||||
|
? anchor.hostname // does not include the port number (e.g. www.example.org)
|
||||||
|
: anchor.host; // does include the port number (e.g. www.example.org:80)
|
||||||
|
return `${protocol}//${host}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeURL(url: URL): void | string {
|
||||||
|
// ignore click if baseURI does not match the document (external)
|
||||||
|
if (!url.href.startsWith(document.baseURI)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize path against baseURI
|
||||||
|
return '/' + url.href.slice(document.baseURI.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractURL(event: MouseEvent): void | URL {
|
||||||
|
// ignore the click if the default action is prevented
|
||||||
|
if (event.defaultPrevented) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore the click if not with the primary mouse button
|
||||||
|
if (event.button !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore the click if a modifier key is pressed
|
||||||
|
if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the <a> element that the click is at (or within)
|
||||||
|
let maybeAnchor = event.target;
|
||||||
|
const path = event.composedPath
|
||||||
|
? event.composedPath()
|
||||||
|
: // @ts-ignore
|
||||||
|
event.path || [];
|
||||||
|
|
||||||
|
// example to check: `for...of` loop here throws the "Not yet implemented" error
|
||||||
|
for (let i = 0; i < path.length; i++) {
|
||||||
|
const target = path[i];
|
||||||
|
if (target.nodeName && target.nodeName.toLowerCase() === 'a') {
|
||||||
|
maybeAnchor = target;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
while (maybeAnchor && maybeAnchor.nodeName.toLowerCase() !== 'a') {
|
||||||
|
// @ts-ignore
|
||||||
|
maybeAnchor = maybeAnchor.parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore the click if not at an <a> element
|
||||||
|
// @ts-ignore
|
||||||
|
if (!maybeAnchor || maybeAnchor.nodeName.toLowerCase() !== 'a') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const anchor = maybeAnchor as HTMLAnchorElement;
|
||||||
|
|
||||||
|
// ignore the click if the <a> element has a non-default target
|
||||||
|
if (anchor.target && anchor.target.toLowerCase() !== '_self') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore the click if the <a> element has the 'download' attribute
|
||||||
|
if (anchor.hasAttribute('download')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore the click if the <a> element has the 'router-ignore' attribute
|
||||||
|
if (anchor.hasAttribute('router-ignore')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore the click if the target URL is a fragment on the current page
|
||||||
|
if (anchor.pathname === window.location.pathname && anchor.hash !== '') {
|
||||||
|
// @ts-ignore
|
||||||
|
window.location.hash = anchor.hash;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore the click if the target is external to the app
|
||||||
|
// In IE11 HTMLAnchorElement does not have the `origin` property
|
||||||
|
// @ts-ignore
|
||||||
|
const origin = anchor.origin || getAnchorOrigin(anchor);
|
||||||
|
if (origin !== window.location.origin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new URL(anchor.href, anchor.baseURI);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractPath(event: MouseEvent): void | string {
|
||||||
|
const url = extractURL(event);
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return normalizeURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const registerGlobalClickHandler = () => {
|
||||||
|
window.addEventListener('click', (event: MouseEvent) => {
|
||||||
|
if (flowReact.active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = extractURL(event);
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// ignore click if baseURI does not match the document (external)
|
||||||
|
if (!url.href.startsWith(document.baseURI)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event && event.preventDefault) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize path against baseURI
|
||||||
|
const path = url.pathname + url.search + url.hash;
|
||||||
|
const state = {...window.history.state}
|
||||||
|
if (state.idx !== undefined) {
|
||||||
|
state.idx = state.idx + 1;
|
||||||
|
}
|
||||||
|
window.history.pushState(state, '', path);
|
||||||
|
window.dispatchEvent(new PopStateEvent('popstate'));
|
||||||
|
}, { capture: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fire 'vaadin-navigated' event to inform components of navigation.
|
||||||
|
* @param pathname pathname of navigation
|
||||||
|
* @param search search of navigation
|
||||||
|
*/
|
||||||
|
function fireNavigated(pathname: string, search: string) {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent('vaadin-navigated', {
|
||||||
|
detail: {
|
||||||
|
pathname,
|
||||||
|
search
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
delete window.Vaadin.Flow.navigation;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function postpone() {}
|
||||||
|
|
||||||
|
const prevent = () => postpone;
|
||||||
|
|
||||||
|
type RouterContainer = Awaited<ReturnType<(typeof flow.serverSideRoutes)[0]['action']>>;
|
||||||
|
|
||||||
|
type PortalEntry = {
|
||||||
|
readonly children: ReactNode;
|
||||||
|
readonly domNode: HTMLElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FlowPortalProps = React.PropsWithChildren<
|
||||||
|
Readonly<{
|
||||||
|
domNode: HTMLElement;
|
||||||
|
onRemove(): void;
|
||||||
|
}>
|
||||||
|
>;
|
||||||
|
|
||||||
|
function FlowPortal({ children, domNode, onRemove }: FlowPortalProps) {
|
||||||
|
useEffect(() => {
|
||||||
|
domNode.addEventListener(
|
||||||
|
'flow-portal-remove',
|
||||||
|
(event: Event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
onRemove();
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return createPortal(children, domNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ADD_FLOW_PORTAL = 'ADD_FLOW_PORTAL';
|
||||||
|
|
||||||
|
type AddFlowPortalAction = Readonly<{
|
||||||
|
type: typeof ADD_FLOW_PORTAL;
|
||||||
|
portal: React.ReactElement<FlowPortalProps>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
function addFlowPortal(portal: React.ReactElement<FlowPortalProps>): AddFlowPortalAction {
|
||||||
|
return {
|
||||||
|
type: ADD_FLOW_PORTAL,
|
||||||
|
portal
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const REMOVE_FLOW_PORTAL = 'REMOVE_FLOW_PORTAL';
|
||||||
|
|
||||||
|
type RemoveFlowPortalAction = Readonly<{
|
||||||
|
type: typeof REMOVE_FLOW_PORTAL;
|
||||||
|
key: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
function removeFlowPortal(key: string): RemoveFlowPortalAction {
|
||||||
|
return {
|
||||||
|
type: REMOVE_FLOW_PORTAL,
|
||||||
|
key
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function flowPortalsReducer(
|
||||||
|
portals: readonly React.ReactElement<FlowPortalProps>[],
|
||||||
|
action: AddFlowPortalAction | RemoveFlowPortalAction
|
||||||
|
) {
|
||||||
|
switch (action.type) {
|
||||||
|
case ADD_FLOW_PORTAL:
|
||||||
|
return [...portals, action.portal];
|
||||||
|
case REMOVE_FLOW_PORTAL:
|
||||||
|
return portals.filter(({ key }) => key !== action.key);
|
||||||
|
default:
|
||||||
|
return portals;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type NavigateOpts = {
|
||||||
|
to: string;
|
||||||
|
callback: boolean;
|
||||||
|
opts?: NavigateOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
type NavigateFn = (to: string, callback: boolean, opts?: NavigateOptions) => void;
|
||||||
|
|
||||||
|
let navigateInProgress = false;
|
||||||
|
/**
|
||||||
|
* A hook providing the `navigate(path: string, opts?: NavigateOptions)` function
|
||||||
|
* with React Router API that has more consistent history updates. Uses internal
|
||||||
|
* queue for processing navigate calls.
|
||||||
|
*/
|
||||||
|
function useQueuedNavigate(
|
||||||
|
waitReference: React.MutableRefObject<Promise<void> | undefined>,
|
||||||
|
navigated: React.MutableRefObject<boolean>
|
||||||
|
): NavigateFn {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const navigateQueue = useRef<NavigateOpts[]>([]).current;
|
||||||
|
const [navigateQueueLength, setNavigateQueueLength] = useState(0);
|
||||||
|
|
||||||
|
const dequeueNavigation = useCallback(() => {
|
||||||
|
if (navigateInProgress) {
|
||||||
|
dequeueNavigationAfterCurrentTask();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const navigateArgs = navigateQueue.shift();
|
||||||
|
if (navigateArgs === undefined) {
|
||||||
|
// Empty queue, do nothing.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blockingNavigate = async () => {
|
||||||
|
if (waitReference.current) {
|
||||||
|
await waitReference.current;
|
||||||
|
waitReference.current = undefined;
|
||||||
|
}
|
||||||
|
navigated.current = !navigateArgs.callback;
|
||||||
|
navigateInProgress = true;
|
||||||
|
navigate(navigateArgs.to, navigateArgs.opts);
|
||||||
|
setNavigateQueueLength(navigateQueue.length);
|
||||||
|
};
|
||||||
|
blockingNavigate();
|
||||||
|
}, [navigate, setNavigateQueueLength]);
|
||||||
|
|
||||||
|
const dequeueNavigationAfterCurrentTask = useCallback(() => {
|
||||||
|
setTimeout(dequeueNavigation, 0);
|
||||||
|
}, [dequeueNavigation]);
|
||||||
|
|
||||||
|
const enqueueNavigation = useCallback(
|
||||||
|
(to: string, callback: boolean, opts?: NavigateOptions) => {
|
||||||
|
navigateQueue.push({ to: to, callback: callback, opts: opts });
|
||||||
|
setNavigateQueueLength(navigateQueue.length);
|
||||||
|
if (navigateQueue.length === 1) {
|
||||||
|
// The first navigation can be started right after any pending sync
|
||||||
|
// jobs, which could add more navigations to the queue.
|
||||||
|
dequeueNavigationAfterCurrentTask();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setNavigateQueueLength, dequeueNavigationAfterCurrentTask]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => () => {
|
||||||
|
// The Flow component has rendered, but history might not be
|
||||||
|
// updated yet, as React Router does it asynchronously.
|
||||||
|
// Use microtask callback for history consistency.
|
||||||
|
dequeueNavigationAfterCurrentTask();
|
||||||
|
},
|
||||||
|
[navigateQueueLength, dequeueNavigationAfterCurrentTask]
|
||||||
|
);
|
||||||
|
|
||||||
|
return enqueueNavigation;
|
||||||
|
}
|
||||||
|
|
||||||
|
const flowNavigation = () => {
|
||||||
|
// @ts-ignore
|
||||||
|
window.Vaadin.Flow.navigation = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Flow() {
|
||||||
|
const ref = useRef<HTMLOutputElement>(null);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const blocker = useBlocker(({ currentLocation, nextLocation }) => {
|
||||||
|
navigated.current =
|
||||||
|
navigated.current ||
|
||||||
|
(nextLocation.pathname === currentLocation.pathname &&
|
||||||
|
nextLocation.search === currentLocation.search &&
|
||||||
|
nextLocation.hash === currentLocation.hash);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
const location = useLocation();
|
||||||
|
const navigated = useRef<boolean>(false);
|
||||||
|
const blockerHandled = useRef<boolean>(false);
|
||||||
|
const fromAnchor = useRef<boolean>(false);
|
||||||
|
const containerRef = useRef<RouterContainer | undefined>(undefined);
|
||||||
|
const roundTrip = useRef<Promise<void> | undefined>(undefined);
|
||||||
|
const queuedNavigate = useQueuedNavigate(roundTrip, navigated);
|
||||||
|
const basename = useHref('/');
|
||||||
|
|
||||||
|
// portalsReducer function is used as state outside the Flow component.
|
||||||
|
const [portals, dispatchPortalAction] = useReducer(flowPortalsReducer, []);
|
||||||
|
|
||||||
|
const addPortalEventHandler = useCallback(
|
||||||
|
(event: CustomEvent<PortalEntry>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const key = Math.random().toString(36).slice(2);
|
||||||
|
dispatchPortalAction(
|
||||||
|
addFlowPortal(
|
||||||
|
<FlowPortal
|
||||||
|
key={key}
|
||||||
|
domNode={event.detail.domNode}
|
||||||
|
onRemove={() => dispatchPortalAction(removeFlowPortal(key))}
|
||||||
|
>
|
||||||
|
{event.detail.children}
|
||||||
|
</FlowPortal>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[dispatchPortalAction]
|
||||||
|
);
|
||||||
|
|
||||||
|
const navigateEventHandler = useCallback(
|
||||||
|
(event: MouseEvent) => {
|
||||||
|
const path = extractPath(event);
|
||||||
|
if (!path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event && event.preventDefault) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
navigated.current = false;
|
||||||
|
// When navigation is triggered by click on a link, fromAnchor is set to true
|
||||||
|
// in order to get a server round-trip even when navigating to the same URL again
|
||||||
|
fromAnchor.current = true;
|
||||||
|
// @ts-ignore
|
||||||
|
window.Vaadin.Flow.navigation = true;
|
||||||
|
navigate(path);
|
||||||
|
// Dispatch close event for overlay drawer on click navigation.
|
||||||
|
window.dispatchEvent(new CustomEvent('close-overlay-drawer'));
|
||||||
|
},
|
||||||
|
[navigate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const vaadinRouterGoEventHandler = useCallback(
|
||||||
|
(event: CustomEvent<URL>) => {
|
||||||
|
const url = event.detail;
|
||||||
|
const path = normalizeURL(url);
|
||||||
|
if (!path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
navigate(path);
|
||||||
|
},
|
||||||
|
[navigate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const vaadinNavigateEventHandler = useCallback(
|
||||||
|
(event: CustomEvent<{ state: unknown; url: string; replace?: boolean; callback: boolean }>) => {
|
||||||
|
// @ts-ignore
|
||||||
|
window.Vaadin.Flow.navigation = true;
|
||||||
|
// clean base uri away if for instance redirected to http://localhost/path/user?id=10
|
||||||
|
// else the whole http... will be appended to the url see #19580
|
||||||
|
const path = event.detail.url.startsWith(document.baseURI)
|
||||||
|
? '/' + event.detail.url.slice(document.baseURI.length)
|
||||||
|
: '/' + event.detail.url;
|
||||||
|
fromAnchor.current = false;
|
||||||
|
queuedNavigate(path, event.detail.callback, { state: event.detail.state, replace: event.detail.replace });
|
||||||
|
},
|
||||||
|
[navigate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const redirect = useCallback(
|
||||||
|
(path: string) => {
|
||||||
|
return () => {
|
||||||
|
navigate(path, { replace: true });
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[navigate]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
window.addEventListener('vaadin-router-go', vaadinRouterGoEventHandler);
|
||||||
|
// @ts-ignore
|
||||||
|
window.addEventListener('vaadin-navigate', vaadinNavigateEventHandler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// @ts-ignore
|
||||||
|
window.removeEventListener('vaadin-router-go', vaadinRouterGoEventHandler);
|
||||||
|
// @ts-ignore
|
||||||
|
window.removeEventListener('vaadin-navigate', vaadinNavigateEventHandler);
|
||||||
|
};
|
||||||
|
}, [vaadinRouterGoEventHandler, vaadinNavigateEventHandler]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
window.addEventListener("popstate", flowNavigation);
|
||||||
|
window.addEventListener('click', navigateEventHandler);
|
||||||
|
flowReact.active = true;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
containerRef.current?.parentNode?.removeChild(containerRef.current);
|
||||||
|
containerRef.current?.removeEventListener('flow-portal-add', addPortalEventHandler as EventListener);
|
||||||
|
containerRef.current = undefined;
|
||||||
|
// @ts-ignore
|
||||||
|
window.removeEventListener("popstate", flowNavigation);
|
||||||
|
window.removeEventListener('click', navigateEventHandler);
|
||||||
|
flowReact.active = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (blocker.state === 'blocked') {
|
||||||
|
if (blockerHandled.current) {
|
||||||
|
// Blocker is handled and the new navigation
|
||||||
|
// gets queued to be executed after the current handling ends.
|
||||||
|
const { pathname, state } = blocker.location;
|
||||||
|
// Clear base name to not get /baseName/basename/path
|
||||||
|
const pathNoBase = pathname.substring(basename.length);
|
||||||
|
// path should always start with / else react-router will append to current url
|
||||||
|
queuedNavigate(pathNoBase.startsWith('/') ? pathNoBase : '/' + pathNoBase, true, {
|
||||||
|
state: state,
|
||||||
|
replace: true
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
blockerHandled.current = true;
|
||||||
|
let blockingPromise: any;
|
||||||
|
roundTrip.current = new Promise<void>(
|
||||||
|
(resolve, reject) => (blockingPromise = { resolve: resolve, reject: reject })
|
||||||
|
);
|
||||||
|
// Release blocker handling after promise is fulfilled
|
||||||
|
roundTrip.current.then(
|
||||||
|
() => (blockerHandled.current = false),
|
||||||
|
() => (blockerHandled.current = false)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Proceed to the blocked location, unless the navigation originates from a click on a link.
|
||||||
|
// In that case continue with function execution and perform a server round-trip
|
||||||
|
if (navigated.current && !fromAnchor.current) {
|
||||||
|
blocker.proceed();
|
||||||
|
blockingPromise.resolve();
|
||||||
|
navigateInProgress = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fromAnchor.current = false;
|
||||||
|
const { pathname, search } = blocker.location;
|
||||||
|
const routes = ((window as any)?.Vaadin?.routesConfig || []) as any[];
|
||||||
|
let matched = matchRoutes(Array.from(routes), pathname);
|
||||||
|
|
||||||
|
// Navigation between server routes
|
||||||
|
// @ts-ignore
|
||||||
|
if (matched && matched.filter((path) => path.route?.element?.type?.name === Flow.name).length != 0) {
|
||||||
|
containerRef.current?.onBeforeEnter?.call(
|
||||||
|
containerRef?.current,
|
||||||
|
{ pathname, search },
|
||||||
|
{
|
||||||
|
prevent() {
|
||||||
|
blocker.reset();
|
||||||
|
blockingPromise.resolve();
|
||||||
|
navigateInProgress = false;
|
||||||
|
navigated.current = false;
|
||||||
|
},
|
||||||
|
redirect,
|
||||||
|
continue() {
|
||||||
|
blocker.proceed();
|
||||||
|
blockingPromise.resolve();
|
||||||
|
navigateInProgress = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
router
|
||||||
|
);
|
||||||
|
navigated.current = true;
|
||||||
|
} else {
|
||||||
|
// For covering the 'server -> client' use case
|
||||||
|
Promise.resolve(
|
||||||
|
containerRef.current?.onBeforeLeave?.call(
|
||||||
|
containerRef?.current,
|
||||||
|
{
|
||||||
|
pathname,
|
||||||
|
search
|
||||||
|
},
|
||||||
|
{ prevent },
|
||||||
|
router
|
||||||
|
)
|
||||||
|
).then((cmd: unknown) => {
|
||||||
|
if (cmd === postpone && containerRef.current) {
|
||||||
|
// postponed navigation: expose existing blocker to Flow
|
||||||
|
containerRef.current.serverConnected = (cancel) => {
|
||||||
|
if (cancel) {
|
||||||
|
blocker.reset();
|
||||||
|
} else {
|
||||||
|
blocker.proceed();
|
||||||
|
}
|
||||||
|
blockingPromise.resolve();
|
||||||
|
navigateInProgress = false;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// permitted navigation: proceed with the blocker
|
||||||
|
blocker.proceed();
|
||||||
|
blockingPromise.resolve();
|
||||||
|
navigateInProgress = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [blocker.state, blocker.location]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (blocker.state === 'blocked') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (navigated.current) {
|
||||||
|
navigated.current = false;
|
||||||
|
fireNavigated(location.pathname, location.search);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
flow.serverSideRoutes[0]
|
||||||
|
.action({ pathname: location.pathname, search: location.search })
|
||||||
|
.then((container) => {
|
||||||
|
const outlet = ref.current?.parentNode;
|
||||||
|
if (outlet && outlet !== container.parentNode) {
|
||||||
|
outlet.append(container);
|
||||||
|
container.addEventListener('flow-portal-add', addPortalEventHandler as EventListener);
|
||||||
|
containerRef.current = container;
|
||||||
|
}
|
||||||
|
return container.onBeforeEnter?.call(
|
||||||
|
container,
|
||||||
|
// Always add base to path as it is cleaned in getFlowRoutePath and will break a route starting with basename
|
||||||
|
{ pathname: basename + location.pathname, search: location.search },
|
||||||
|
{
|
||||||
|
prevent,
|
||||||
|
redirect,
|
||||||
|
continue() {
|
||||||
|
fireNavigated(location.pathname, location.search);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
router
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.then((result: unknown) => {
|
||||||
|
if (typeof result === 'function') {
|
||||||
|
result();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<output ref={ref} style={{ display: 'none' }} />
|
||||||
|
{portals}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Flow.type = 'FlowContainer'; // This is for copilot to recognize this
|
||||||
|
|
||||||
|
export const serverSideRoutes = [{ path: '/*', element: <Flow /> }];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the script for an exported WebComponent with the given tag
|
||||||
|
*
|
||||||
|
* @param tag name of the exported web-component to load
|
||||||
|
*
|
||||||
|
* @returns Promise(resolve, reject) that is fulfilled on script load
|
||||||
|
*/
|
||||||
|
export const loadComponentScript = (tag: String): Promise<void> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = `/web-component/${tag}.js`;
|
||||||
|
script.onload = function () {
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
script.onerror = function (err) {
|
||||||
|
reject(err);
|
||||||
|
};
|
||||||
|
document.head.appendChild(script);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.head.removeChild(script);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Properties {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load WebComponent script and create a React element for the WebComponent.
|
||||||
|
*
|
||||||
|
* @param tag custom web-component tag name.
|
||||||
|
* @param props optional Properties object to create element attributes with
|
||||||
|
* @param onload optional callback to be called for script onload
|
||||||
|
* @param onerror optional callback for error loading the script
|
||||||
|
*/
|
||||||
|
export const reactElement = (tag: string, props?: Properties, onload?: () => void, onerror?: (err: any) => void) => {
|
||||||
|
loadComponentScript(tag).then(
|
||||||
|
() => onload?.(),
|
||||||
|
(err) => {
|
||||||
|
if (onerror) {
|
||||||
|
onerror(err);
|
||||||
|
} else {
|
||||||
|
console.error(`Failed to load script for ${tag}.`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (props) {
|
||||||
|
return React.createElement(tag, props);
|
||||||
|
}
|
||||||
|
return React.createElement(tag);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Flow;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
if (import.meta.hot) {
|
||||||
|
// @ts-ignore
|
||||||
|
import.meta.hot.accept((newModule) => {
|
||||||
|
// A hot module replace for Flow.tsx happens when any JS/TS imported through @JsModule
|
||||||
|
// or similar is updated because this updates generated-flow-imports.js and that in turn
|
||||||
|
// is imported by this file. We have no means of hot replacing those files, e.g. some
|
||||||
|
// custom lit element so we need to reload the page. */
|
||||||
|
if (newModule) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
import { createRoot, Root } from 'react-dom/client';
|
||||||
|
import { createElement, type Dispatch, type ReactElement, type ReactNode, useEffect, useReducer } from 'react';
|
||||||
|
|
||||||
|
type FlowStateKeyChangedAction<K extends string, V> = Readonly<{
|
||||||
|
type: 'stateKeyChanged';
|
||||||
|
key: K;
|
||||||
|
value: V;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type FlowStateReducerAction = FlowStateKeyChangedAction<string, unknown>;
|
||||||
|
|
||||||
|
function stateReducer<S extends Readonly<Record<string, unknown>>>(state: S, action: FlowStateReducerAction): S {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'stateKeyChanged':
|
||||||
|
const { value } = action;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
key: value
|
||||||
|
} as S;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DispatchEvent<T> = T extends undefined ? () => boolean : (value: T) => boolean;
|
||||||
|
|
||||||
|
const emptyAction: Dispatch<unknown> = () => {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object with APIs exposed for using in the {@link ReactAdapterElement#render}
|
||||||
|
* implementation.
|
||||||
|
*/
|
||||||
|
export type RenderHooks = {
|
||||||
|
/**
|
||||||
|
* A hook API for using stateful JS properties of the Web Component from
|
||||||
|
* the React `render()`.
|
||||||
|
*
|
||||||
|
* @typeParam T - Type of the state value
|
||||||
|
*
|
||||||
|
* @param key - Web Component property name, which is used for two-way
|
||||||
|
* value propagation from the server and back.
|
||||||
|
* @param initialValue - Fallback initial value (optional). Only applies if
|
||||||
|
* the Java component constructor does not invoke `setState`.
|
||||||
|
* @returns A tuple with two values:
|
||||||
|
* 1. The current state.
|
||||||
|
* 2. The `set` function for changing the state and triggering render
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
readonly useState: ReactAdapterElement['useState'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hook helper to simplify dispatching a `CustomEvent` on the Web
|
||||||
|
* Component from React.
|
||||||
|
*
|
||||||
|
* @typeParam T - The type for `event.detail` value (optional).
|
||||||
|
*
|
||||||
|
* @param type - The `CustomEvent` type string.
|
||||||
|
* @param options - The settings for the `CustomEvent`.
|
||||||
|
* @returns The `dispatch` function. The function parameters change
|
||||||
|
* depending on the `T` generic type:
|
||||||
|
* - For `undefined` type (default), has no parameters.
|
||||||
|
* - For other types, has one parameter for the `event.detail` value of that type.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
readonly useCustomEvent: ReactAdapterElement['useCustomEvent'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hook helper to generate the content element with name attribute to bind
|
||||||
|
* the server-side Flow element for this component.
|
||||||
|
*
|
||||||
|
* This is used together with {@link ReactAdapterComponent::getContentElement}
|
||||||
|
* to have server-side component attach to the correct client element.
|
||||||
|
*
|
||||||
|
* Usage as follows:
|
||||||
|
*
|
||||||
|
* const content = hooks.useContent('content');
|
||||||
|
* return <>
|
||||||
|
* {content}
|
||||||
|
* </>;
|
||||||
|
*
|
||||||
|
* Note! Not adding the 'content' element into the dom will have the
|
||||||
|
* server throw a IllegalStateException for element with tag name not found.
|
||||||
|
*
|
||||||
|
* @param name - The name attribute of the element
|
||||||
|
*/
|
||||||
|
readonly useContent: ReactAdapterElement['useContent'];
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ReadyCallbackFunction {
|
||||||
|
(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A base class for Web Components that render using React. Enables creating
|
||||||
|
* adapters for integrating React components with Flow. Intended for use with
|
||||||
|
* `ReactAdapterComponent` Flow Java class.
|
||||||
|
*/
|
||||||
|
export abstract class ReactAdapterElement extends HTMLElement {
|
||||||
|
#root: Root | undefined = undefined;
|
||||||
|
#rootRendered: boolean = false;
|
||||||
|
#rendering: ReactNode | undefined = undefined;
|
||||||
|
|
||||||
|
#state: Record<string, unknown> = Object.create(null);
|
||||||
|
#stateSetters = new Map<string, Dispatch<unknown>>();
|
||||||
|
#customEvents = new Map<string, DispatchEvent<unknown>>();
|
||||||
|
#dispatchFlowState: Dispatch<FlowStateReducerAction> = emptyAction;
|
||||||
|
|
||||||
|
#readyCallback = new Map<string, ReadyCallbackFunction>();
|
||||||
|
|
||||||
|
readonly #renderHooks: RenderHooks;
|
||||||
|
|
||||||
|
readonly #Wrapper: () => ReactElement | null;
|
||||||
|
|
||||||
|
#unmounting?: Promise<void>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.#renderHooks = {
|
||||||
|
useState: this.useState.bind(this),
|
||||||
|
useCustomEvent: this.useCustomEvent.bind(this),
|
||||||
|
useContent: this.useContent.bind(this)
|
||||||
|
};
|
||||||
|
this.#Wrapper = this.#renderWrapper.bind(this);
|
||||||
|
this.#markAsUsed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async connectedCallback() {
|
||||||
|
this.#rendering = createElement(this.#Wrapper);
|
||||||
|
const createNewRoot = this.dispatchEvent(
|
||||||
|
new CustomEvent('flow-portal-add', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
composed: true,
|
||||||
|
detail: {
|
||||||
|
children: this.#rendering,
|
||||||
|
domNode: this
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!createNewRoot || this.#root) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.#unmounting;
|
||||||
|
|
||||||
|
this.#root = createRoot(this);
|
||||||
|
this.#maybeRenderRoot();
|
||||||
|
this.#root.render(this.#rendering);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a callback for specified element identifier to be called when
|
||||||
|
* react element is ready.
|
||||||
|
* <p>
|
||||||
|
* For internal use only. May be renamed or removed in a future release.
|
||||||
|
*
|
||||||
|
* @param id element identifier that callback is for
|
||||||
|
* @param readyCallback callback method to be informed on element ready state
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public addReadyCallback(id: string, readyCallback: ReadyCallbackFunction) {
|
||||||
|
this.#readyCallback.set(id, readyCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async disconnectedCallback() {
|
||||||
|
if (!this.#root) {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('flow-portal-remove', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
composed: true,
|
||||||
|
detail: {
|
||||||
|
children: this.#rendering,
|
||||||
|
domNode: this
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.#unmounting = Promise.resolve();
|
||||||
|
await this.#unmounting;
|
||||||
|
this.#root.unmount();
|
||||||
|
this.#root = undefined;
|
||||||
|
}
|
||||||
|
this.#rootRendered = false;
|
||||||
|
this.#rendering = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hook API for using stateful JS properties of the Web Component from
|
||||||
|
* the React `render()`.
|
||||||
|
*
|
||||||
|
* @typeParam T - Type of the state value
|
||||||
|
*
|
||||||
|
* @param key - Web Component property name, which is used for two-way
|
||||||
|
* value propagation from the server and back.
|
||||||
|
* @param initialValue - Fallback initial value (optional). Only applies if
|
||||||
|
* the Java component constructor does not invoke `setState`.
|
||||||
|
* @returns A tuple with two values:
|
||||||
|
* 1. The current state.
|
||||||
|
* 2. The `set` function for changing the state and triggering render
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected useState<T>(key: string, initialValue?: T): [value: T, setValue: Dispatch<T>] {
|
||||||
|
if (this.#stateSetters.has(key)) {
|
||||||
|
return [this.#state[key] as T, this.#stateSetters.get(key)!];
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = ((this as Record<string, unknown>)[key] as T) ?? initialValue!;
|
||||||
|
this.#state[key] = value;
|
||||||
|
Object.defineProperty(this, key, {
|
||||||
|
enumerable: true,
|
||||||
|
get(): T {
|
||||||
|
return this.#state[key];
|
||||||
|
},
|
||||||
|
set(nextValue: T) {
|
||||||
|
this.#state[key] = nextValue;
|
||||||
|
this.#dispatchFlowState({ type: 'stateKeyChanged', key, value });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const dispatchChangedEvent = this.useCustomEvent<{ value: T }>(`${key}-changed`, { detail: { value } });
|
||||||
|
const setValue = (value: T) => {
|
||||||
|
this.#state[key] = value;
|
||||||
|
dispatchChangedEvent({ value });
|
||||||
|
this.#dispatchFlowState({ type: 'stateKeyChanged', key, value });
|
||||||
|
};
|
||||||
|
this.#stateSetters.set(key, setValue as Dispatch<unknown>);
|
||||||
|
return [value, setValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hook helper to simplify dispatching a `CustomEvent` on the Web
|
||||||
|
* Component from React.
|
||||||
|
*
|
||||||
|
* @typeParam T - The type for `event.detail` value (optional).
|
||||||
|
*
|
||||||
|
* @param type - The `CustomEvent` type string.
|
||||||
|
* @param options - The settings for the `CustomEvent`.
|
||||||
|
* @returns The `dispatch` function. The function parameters change
|
||||||
|
* depending on the `T` generic type:
|
||||||
|
* - For `undefined` type (default), has no parameters.
|
||||||
|
* - For other types, has one parameter for the `event.detail` value of that type.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected useCustomEvent<T = undefined>(type: string, options: CustomEventInit<T> = {}): DispatchEvent<T> {
|
||||||
|
if (!this.#customEvents.has(type)) {
|
||||||
|
const dispatch = ((detail?: T) => {
|
||||||
|
const eventInitDict =
|
||||||
|
detail === undefined
|
||||||
|
? options
|
||||||
|
: {
|
||||||
|
...options,
|
||||||
|
detail
|
||||||
|
};
|
||||||
|
const event = new CustomEvent(type, eventInitDict);
|
||||||
|
return this.dispatchEvent(event);
|
||||||
|
}) as DispatchEvent<T>;
|
||||||
|
this.#customEvents.set(type, dispatch as DispatchEvent<unknown>);
|
||||||
|
return dispatch;
|
||||||
|
}
|
||||||
|
return this.#customEvents.get(type)! as DispatchEvent<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Web Component render function. To be implemented by users with React.
|
||||||
|
*
|
||||||
|
* @param hooks - the adapter APIs exposed for the implementation.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected abstract render(hooks: RenderHooks): ReactElement | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare content container for Flow to bind server Element to.
|
||||||
|
*
|
||||||
|
* @param name container name attribute matching server name attribute
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected useContent(name: string): ReactElement | null {
|
||||||
|
useEffect(() => {
|
||||||
|
this.#readyCallback.get(name)?.();
|
||||||
|
}, []);
|
||||||
|
return createElement('flow-content-container', { name, style: { display: 'contents' } });
|
||||||
|
}
|
||||||
|
|
||||||
|
#maybeRenderRoot() {
|
||||||
|
if (this.#rootRendered || !this.#root) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#root.render(createElement(this.#Wrapper));
|
||||||
|
this.#rootRendered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#renderWrapper(): ReactElement | null {
|
||||||
|
const [state, dispatchFlowState] = useReducer(stateReducer, this.#state);
|
||||||
|
this.#state = state;
|
||||||
|
this.#dispatchFlowState = dispatchFlowState;
|
||||||
|
return this.render(this.#renderHooks);
|
||||||
|
}
|
||||||
|
|
||||||
|
#markAsUsed(): void {
|
||||||
|
// @ts-ignore
|
||||||
|
let vaadinObject = window.Vaadin || {};
|
||||||
|
// @ts-ignore
|
||||||
|
if (vaadinObject.developmentMode) {
|
||||||
|
vaadinObject.registrations = vaadinObject.registrations || [];
|
||||||
|
vaadinObject.registrations.push({
|
||||||
|
is: 'ReactAdapterElement',
|
||||||
|
version: '25.2.6'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-app-layout.js';
|
||||||
|
import '@vaadin/scroller/src/vaadin-scroller.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
|
||||||
|
import '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-drawer-toggle.js';
|
||||||
|
import '@vaadin/button/src/vaadin-button.js';
|
||||||
|
import 'Frontend/generated/jar-resources/disableOnClickFunctions.js';
|
||||||
|
import '@vaadin/avatar/src/vaadin-avatar.js';
|
||||||
|
import 'Frontend/generated/jar-resources/menubarConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuConnector.js';
|
||||||
|
import '@vaadin/menu-bar/src/vaadin-menu-bar.js';
|
||||||
|
import '@vaadin/context-menu/src/vaadin-context-menu.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-renderer.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-directive.js';
|
||||||
|
import 'lit';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuTargetConnector.js';
|
||||||
|
import '@vaadin/component-base/src/gestures.js';
|
||||||
|
import '@vaadin/horizontal-layout/src/vaadin-horizontal-layout.js';
|
||||||
|
import '@vaadin/icons/vaadin-iconset.js';
|
||||||
|
import '@vaadin/icon/src/vaadin-icon.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-upload-manager-connector.ts';
|
||||||
|
import '@vaadin/upload/vaadin-upload-manager.js';
|
||||||
|
import '@vaadin/notification/src/vaadin-notification.js';
|
||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion-panel.js';
|
||||||
|
import '@vaadin/details/src/vaadin-details.js';
|
||||||
|
import 'Frontend/generated/jar-resources/messageListConnector.js';
|
||||||
|
import '@vaadin/message-list/src/vaadin-message-list.js';
|
||||||
|
import '@vaadin/text-field/src/vaadin-text-field.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-sorter.js';
|
||||||
|
import 'Frontend/generated/jar-resources/gridConnector.ts';
|
||||||
|
import '@vaadin/component-base/src/debounce.js';
|
||||||
|
import '@vaadin/component-base/src/async.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-active-item-mixin.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-grid-flow-selection-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column-group.js';
|
||||||
|
import 'Frontend/generated/jar-resources/lit-renderer.ts';
|
||||||
|
import 'lit/directives/live.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-layout.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-item.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-row.js';
|
||||||
|
import '@vaadin/combo-box/src/vaadin-combo-box.js';
|
||||||
|
import 'Frontend/generated/jar-resources/comboBoxConnector.js';
|
||||||
|
import '@vaadin/combo-box/src/vaadin-combo-box-placeholder.js';
|
||||||
|
import '@vaadin/multi-select-combo-box/src/vaadin-multi-select-combo-box.js';
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-app-layout.js';
|
||||||
|
import '@vaadin/scroller/src/vaadin-scroller.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
|
||||||
|
import '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-drawer-toggle.js';
|
||||||
|
import '@vaadin/button/src/vaadin-button.js';
|
||||||
|
import 'Frontend/generated/jar-resources/disableOnClickFunctions.js';
|
||||||
|
import '@vaadin/horizontal-layout/src/vaadin-horizontal-layout.js';
|
||||||
|
import '@vaadin/icons/vaadin-iconset.js';
|
||||||
|
import '@vaadin/icon/src/vaadin-icon.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-upload-manager-connector.ts';
|
||||||
|
import '@vaadin/upload/vaadin-upload-manager.js';
|
||||||
|
import '@vaadin/notification/src/vaadin-notification.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-renderer.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-directive.js';
|
||||||
|
import 'lit';
|
||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
|
import 'Frontend/generated/jar-resources/menubarConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuConnector.js';
|
||||||
|
import '@vaadin/menu-bar/src/vaadin-menu-bar.js';
|
||||||
|
import '@vaadin/context-menu/src/vaadin-context-menu.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuTargetConnector.js';
|
||||||
|
import '@vaadin/component-base/src/gestures.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion-panel.js';
|
||||||
|
import '@vaadin/details/src/vaadin-details.js';
|
||||||
|
import 'Frontend/generated/jar-resources/messageListConnector.js';
|
||||||
|
import '@vaadin/message-list/src/vaadin-message-list.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-sorter.js';
|
||||||
|
import 'Frontend/generated/jar-resources/gridConnector.ts';
|
||||||
|
import '@vaadin/component-base/src/debounce.js';
|
||||||
|
import '@vaadin/component-base/src/async.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-active-item-mixin.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-grid-flow-selection-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column-group.js';
|
||||||
|
import 'Frontend/generated/jar-resources/lit-renderer.ts';
|
||||||
|
import 'lit/directives/live.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-layout.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-item.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-row.js';
|
||||||
|
import '@vaadin/combo-box/src/vaadin-combo-box.js';
|
||||||
|
import 'Frontend/generated/jar-resources/comboBoxConnector.js';
|
||||||
|
import '@vaadin/combo-box/src/vaadin-combo-box-placeholder.js';
|
||||||
|
import '@vaadin/multi-select-combo-box/src/vaadin-multi-select-combo-box.js';
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
import '@vaadin/app-layout/src/vaadin-app-layout.js';
|
||||||
|
import '@vaadin/scroller/src/vaadin-scroller.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
|
||||||
|
import '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-drawer-toggle.js';
|
||||||
|
import '@vaadin/button/src/vaadin-button.js';
|
||||||
|
import 'Frontend/generated/jar-resources/disableOnClickFunctions.js';
|
||||||
|
import '@vaadin/horizontal-layout/src/vaadin-horizontal-layout.js';
|
||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/icons/vaadin-iconset.js';
|
||||||
|
import '@vaadin/icon/src/vaadin-icon.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-upload-manager-connector.ts';
|
||||||
|
import '@vaadin/upload/vaadin-upload-manager.js';
|
||||||
|
import '@vaadin/notification/src/vaadin-notification.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-renderer.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-directive.js';
|
||||||
|
import 'lit';
|
||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
|
import 'Frontend/generated/jar-resources/menubarConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuConnector.js';
|
||||||
|
import '@vaadin/menu-bar/src/vaadin-menu-bar.js';
|
||||||
|
import '@vaadin/context-menu/src/vaadin-context-menu.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuTargetConnector.js';
|
||||||
|
import '@vaadin/component-base/src/gestures.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion-panel.js';
|
||||||
|
import '@vaadin/details/src/vaadin-details.js';
|
||||||
|
import 'Frontend/generated/jar-resources/messageListConnector.js';
|
||||||
|
import '@vaadin/message-list/src/vaadin-message-list.js';
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-app-layout.js';
|
||||||
|
import '@vaadin/scroller/src/vaadin-scroller.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
|
||||||
|
import '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-drawer-toggle.js';
|
||||||
|
import '@vaadin/button/src/vaadin-button.js';
|
||||||
|
import 'Frontend/generated/jar-resources/disableOnClickFunctions.js';
|
||||||
|
import '@vaadin/horizontal-layout/src/vaadin-horizontal-layout.js';
|
||||||
|
import '@vaadin/icons/vaadin-iconset.js';
|
||||||
|
import '@vaadin/icon/src/vaadin-icon.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-upload-manager-connector.ts';
|
||||||
|
import '@vaadin/upload/vaadin-upload-manager.js';
|
||||||
|
import '@vaadin/notification/src/vaadin-notification.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-renderer.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-directive.js';
|
||||||
|
import 'lit';
|
||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
|
import 'Frontend/generated/jar-resources/menubarConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuConnector.js';
|
||||||
|
import '@vaadin/menu-bar/src/vaadin-menu-bar.js';
|
||||||
|
import '@vaadin/context-menu/src/vaadin-context-menu.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuTargetConnector.js';
|
||||||
|
import '@vaadin/component-base/src/gestures.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion-panel.js';
|
||||||
|
import '@vaadin/details/src/vaadin-details.js';
|
||||||
|
import 'Frontend/generated/jar-resources/messageListConnector.js';
|
||||||
|
import '@vaadin/message-list/src/vaadin-message-list.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-sorter.js';
|
||||||
|
import 'Frontend/generated/jar-resources/gridConnector.ts';
|
||||||
|
import '@vaadin/component-base/src/debounce.js';
|
||||||
|
import '@vaadin/component-base/src/async.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-active-item-mixin.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-grid-flow-selection-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column-group.js';
|
||||||
|
import 'Frontend/generated/jar-resources/lit-renderer.ts';
|
||||||
|
import 'lit/directives/live.js';
|
||||||
|
import '@vaadin/text-field/src/vaadin-text-field.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-layout.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-item.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-row.js';
|
||||||
|
import '@vaadin/list-box/src/vaadin-list-box.js';
|
||||||
|
import '@vaadin/item/src/vaadin-item.js';
|
||||||
|
import '@vaadin/integer-field/src/vaadin-integer-field.js';
|
||||||
|
import '@vaadin/combo-box/src/vaadin-combo-box.js';
|
||||||
|
import 'Frontend/generated/jar-resources/comboBoxConnector.js';
|
||||||
|
import '@vaadin/combo-box/src/vaadin-combo-box-placeholder.js';
|
||||||
|
import '@vaadin/multi-select-combo-box/src/vaadin-multi-select-combo-box.js';
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-app-layout.js';
|
||||||
|
import '@vaadin/scroller/src/vaadin-scroller.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
|
||||||
|
import '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-drawer-toggle.js';
|
||||||
|
import '@vaadin/button/src/vaadin-button.js';
|
||||||
|
import 'Frontend/generated/jar-resources/disableOnClickFunctions.js';
|
||||||
|
import '@vaadin/horizontal-layout/src/vaadin-horizontal-layout.js';
|
||||||
|
import '@vaadin/icons/vaadin-iconset.js';
|
||||||
|
import '@vaadin/icon/src/vaadin-icon.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-upload-manager-connector.ts';
|
||||||
|
import '@vaadin/upload/vaadin-upload-manager.js';
|
||||||
|
import '@vaadin/notification/src/vaadin-notification.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-renderer.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-directive.js';
|
||||||
|
import 'lit';
|
||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
|
import 'Frontend/generated/jar-resources/menubarConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuConnector.js';
|
||||||
|
import '@vaadin/menu-bar/src/vaadin-menu-bar.js';
|
||||||
|
import '@vaadin/context-menu/src/vaadin-context-menu.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuTargetConnector.js';
|
||||||
|
import '@vaadin/component-base/src/gestures.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion-panel.js';
|
||||||
|
import '@vaadin/details/src/vaadin-details.js';
|
||||||
|
import 'Frontend/generated/jar-resources/messageListConnector.js';
|
||||||
|
import '@vaadin/message-list/src/vaadin-message-list.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-sorter.js';
|
||||||
|
import 'Frontend/generated/jar-resources/gridConnector.ts';
|
||||||
|
import '@vaadin/component-base/src/debounce.js';
|
||||||
|
import '@vaadin/component-base/src/async.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-active-item-mixin.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-grid-flow-selection-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column-group.js';
|
||||||
|
import 'Frontend/generated/jar-resources/lit-renderer.ts';
|
||||||
|
import 'lit/directives/live.js';
|
||||||
|
import '@vaadin/text-field/src/vaadin-text-field.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-layout.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-item.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-row.js';
|
||||||
|
import '@vaadin/list-box/src/vaadin-list-box.js';
|
||||||
|
import '@vaadin/item/src/vaadin-item.js';
|
||||||
|
import '@vaadin/integer-field/src/vaadin-integer-field.js';
|
||||||
|
import '@vaadin/combo-box/src/vaadin-combo-box.js';
|
||||||
|
import 'Frontend/generated/jar-resources/comboBoxConnector.js';
|
||||||
|
import '@vaadin/combo-box/src/vaadin-combo-box-placeholder.js';
|
||||||
|
import '@vaadin/multi-select-combo-box/src/vaadin-multi-select-combo-box.js';
|
||||||
|
import '@vaadin/custom-field/src/vaadin-custom-field.js';
|
||||||
|
import '@vaadin/select/src/vaadin-select.js';
|
||||||
|
import 'Frontend/generated/jar-resources/selectConnector.js';
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-app-layout.js';
|
||||||
|
import '@vaadin/scroller/src/vaadin-scroller.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
|
||||||
|
import '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-drawer-toggle.js';
|
||||||
|
import '@vaadin/button/src/vaadin-button.js';
|
||||||
|
import 'Frontend/generated/jar-resources/disableOnClickFunctions.js';
|
||||||
|
import '@vaadin/horizontal-layout/src/vaadin-horizontal-layout.js';
|
||||||
|
import '@vaadin/icons/vaadin-iconset.js';
|
||||||
|
import '@vaadin/icon/src/vaadin-icon.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-upload-manager-connector.ts';
|
||||||
|
import '@vaadin/upload/vaadin-upload-manager.js';
|
||||||
|
import '@vaadin/notification/src/vaadin-notification.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-renderer.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-directive.js';
|
||||||
|
import 'lit';
|
||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
|
import 'Frontend/generated/jar-resources/menubarConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuConnector.js';
|
||||||
|
import '@vaadin/menu-bar/src/vaadin-menu-bar.js';
|
||||||
|
import '@vaadin/context-menu/src/vaadin-context-menu.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuTargetConnector.js';
|
||||||
|
import '@vaadin/component-base/src/gestures.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion-panel.js';
|
||||||
|
import '@vaadin/details/src/vaadin-details.js';
|
||||||
|
import 'Frontend/generated/jar-resources/messageListConnector.js';
|
||||||
|
import '@vaadin/message-list/src/vaadin-message-list.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-sorter.js';
|
||||||
|
import 'Frontend/generated/jar-resources/gridConnector.ts';
|
||||||
|
import '@vaadin/component-base/src/debounce.js';
|
||||||
|
import '@vaadin/component-base/src/async.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-active-item-mixin.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-grid-flow-selection-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column-group.js';
|
||||||
|
import 'Frontend/generated/jar-resources/lit-renderer.ts';
|
||||||
|
import 'lit/directives/live.js';
|
||||||
|
import '@vaadin/text-field/src/vaadin-text-field.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-layout.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-item.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-row.js';
|
||||||
|
import '@vaadin/password-field/src/vaadin-password-field.js';
|
||||||
|
import '@vaadin/email-field/src/vaadin-email-field.js';
|
||||||
|
import '@vaadin/checkbox-group/src/vaadin-checkbox-group.js';
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-app-layout.js';
|
||||||
|
import '@vaadin/scroller/src/vaadin-scroller.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
|
||||||
|
import '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-drawer-toggle.js';
|
||||||
|
import '@vaadin/button/src/vaadin-button.js';
|
||||||
|
import 'Frontend/generated/jar-resources/disableOnClickFunctions.js';
|
||||||
|
import '@vaadin/horizontal-layout/src/vaadin-horizontal-layout.js';
|
||||||
|
import '@vaadin/icons/vaadin-iconset.js';
|
||||||
|
import '@vaadin/icon/src/vaadin-icon.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-upload-manager-connector.ts';
|
||||||
|
import '@vaadin/upload/vaadin-upload-manager.js';
|
||||||
|
import '@vaadin/notification/src/vaadin-notification.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-renderer.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-directive.js';
|
||||||
|
import 'lit';
|
||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
|
import 'Frontend/generated/jar-resources/menubarConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuConnector.js';
|
||||||
|
import '@vaadin/menu-bar/src/vaadin-menu-bar.js';
|
||||||
|
import '@vaadin/context-menu/src/vaadin-context-menu.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuTargetConnector.js';
|
||||||
|
import '@vaadin/component-base/src/gestures.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion-panel.js';
|
||||||
|
import '@vaadin/details/src/vaadin-details.js';
|
||||||
|
import 'Frontend/generated/jar-resources/messageListConnector.js';
|
||||||
|
import '@vaadin/message-list/src/vaadin-message-list.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-sorter.js';
|
||||||
|
import 'Frontend/generated/jar-resources/gridConnector.ts';
|
||||||
|
import '@vaadin/component-base/src/debounce.js';
|
||||||
|
import '@vaadin/component-base/src/async.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-active-item-mixin.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-grid-flow-selection-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column-group.js';
|
||||||
|
import 'Frontend/generated/jar-resources/lit-renderer.ts';
|
||||||
|
import 'lit/directives/live.js';
|
||||||
|
import '@vaadin/text-field/src/vaadin-text-field.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-layout.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-item.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-row.js';
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-app-layout.js';
|
||||||
|
import '@vaadin/scroller/src/vaadin-scroller.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
|
||||||
|
import '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-drawer-toggle.js';
|
||||||
|
import '@vaadin/button/src/vaadin-button.js';
|
||||||
|
import 'Frontend/generated/jar-resources/disableOnClickFunctions.js';
|
||||||
|
import '@vaadin/avatar/src/vaadin-avatar.js';
|
||||||
|
import 'Frontend/generated/jar-resources/menubarConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuConnector.js';
|
||||||
|
import '@vaadin/menu-bar/src/vaadin-menu-bar.js';
|
||||||
|
import '@vaadin/context-menu/src/vaadin-context-menu.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-renderer.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-directive.js';
|
||||||
|
import 'lit';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuTargetConnector.js';
|
||||||
|
import '@vaadin/component-base/src/gestures.js';
|
||||||
|
import '@vaadin/horizontal-layout/src/vaadin-horizontal-layout.js';
|
||||||
|
import '@vaadin/icons/vaadin-iconset.js';
|
||||||
|
import '@vaadin/icon/src/vaadin-icon.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-upload-manager-connector.ts';
|
||||||
|
import '@vaadin/upload/vaadin-upload-manager.js';
|
||||||
|
import '@vaadin/notification/src/vaadin-notification.js';
|
||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion-panel.js';
|
||||||
|
import '@vaadin/details/src/vaadin-details.js';
|
||||||
|
import 'Frontend/generated/jar-resources/messageListConnector.js';
|
||||||
|
import '@vaadin/message-list/src/vaadin-message-list.js';
|
||||||
|
import '@vaadin/text-field/src/vaadin-text-field.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-sorter.js';
|
||||||
|
import 'Frontend/generated/jar-resources/gridConnector.ts';
|
||||||
|
import '@vaadin/component-base/src/debounce.js';
|
||||||
|
import '@vaadin/component-base/src/async.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-active-item-mixin.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-grid-flow-selection-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column-group.js';
|
||||||
|
import 'Frontend/generated/jar-resources/lit-renderer.ts';
|
||||||
|
import 'lit/directives/live.js';
|
||||||
|
import '@vaadin/custom-field/src/vaadin-custom-field.js';
|
||||||
|
import '@vaadin/multi-select-combo-box/src/vaadin-multi-select-combo-box.js';
|
||||||
|
import 'Frontend/generated/jar-resources/comboBoxConnector.js';
|
||||||
|
import '@vaadin/combo-box/src/vaadin-combo-box-placeholder.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-layout.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-item.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-row.js';
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-app-layout.js';
|
||||||
|
import '@vaadin/scroller/src/vaadin-scroller.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
|
||||||
|
import '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-drawer-toggle.js';
|
||||||
|
import '@vaadin/button/src/vaadin-button.js';
|
||||||
|
import 'Frontend/generated/jar-resources/disableOnClickFunctions.js';
|
||||||
|
import '@vaadin/horizontal-layout/src/vaadin-horizontal-layout.js';
|
||||||
|
import '@vaadin/icons/vaadin-iconset.js';
|
||||||
|
import '@vaadin/icon/src/vaadin-icon.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-upload-manager-connector.ts';
|
||||||
|
import '@vaadin/upload/vaadin-upload-manager.js';
|
||||||
|
import '@vaadin/notification/src/vaadin-notification.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-renderer.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-directive.js';
|
||||||
|
import 'lit';
|
||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
|
import 'Frontend/generated/jar-resources/menubarConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuConnector.js';
|
||||||
|
import '@vaadin/menu-bar/src/vaadin-menu-bar.js';
|
||||||
|
import '@vaadin/context-menu/src/vaadin-context-menu.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuTargetConnector.js';
|
||||||
|
import '@vaadin/component-base/src/gestures.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion-panel.js';
|
||||||
|
import '@vaadin/details/src/vaadin-details.js';
|
||||||
|
import 'Frontend/generated/jar-resources/messageListConnector.js';
|
||||||
|
import '@vaadin/message-list/src/vaadin-message-list.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-sorter.js';
|
||||||
|
import 'Frontend/generated/jar-resources/gridConnector.ts';
|
||||||
|
import '@vaadin/component-base/src/debounce.js';
|
||||||
|
import '@vaadin/component-base/src/async.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-active-item-mixin.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-grid-flow-selection-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column-group.js';
|
||||||
|
import 'Frontend/generated/jar-resources/lit-renderer.ts';
|
||||||
|
import 'lit/directives/live.js';
|
||||||
|
import '@vaadin/text-field/src/vaadin-text-field.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-layout.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-item.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-row.js';
|
||||||
|
import '@vaadin/list-box/src/vaadin-list-box.js';
|
||||||
|
import '@vaadin/item/src/vaadin-item.js';
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-app-layout.js';
|
||||||
|
import '@vaadin/scroller/src/vaadin-scroller.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
|
||||||
|
import '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-drawer-toggle.js';
|
||||||
|
import '@vaadin/button/src/vaadin-button.js';
|
||||||
|
import 'Frontend/generated/jar-resources/disableOnClickFunctions.js';
|
||||||
|
import '@vaadin/avatar/src/vaadin-avatar.js';
|
||||||
|
import 'Frontend/generated/jar-resources/menubarConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuConnector.js';
|
||||||
|
import '@vaadin/menu-bar/src/vaadin-menu-bar.js';
|
||||||
|
import '@vaadin/context-menu/src/vaadin-context-menu.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-renderer.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-directive.js';
|
||||||
|
import 'lit';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuTargetConnector.js';
|
||||||
|
import '@vaadin/component-base/src/gestures.js';
|
||||||
|
import '@vaadin/horizontal-layout/src/vaadin-horizontal-layout.js';
|
||||||
|
import '@vaadin/icons/vaadin-iconset.js';
|
||||||
|
import '@vaadin/icon/src/vaadin-icon.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-upload-manager-connector.ts';
|
||||||
|
import '@vaadin/upload/vaadin-upload-manager.js';
|
||||||
|
import '@vaadin/notification/src/vaadin-notification.js';
|
||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion-panel.js';
|
||||||
|
import '@vaadin/details/src/vaadin-details.js';
|
||||||
|
import 'Frontend/generated/jar-resources/messageListConnector.js';
|
||||||
|
import '@vaadin/message-list/src/vaadin-message-list.js';
|
||||||
|
import '@vaadin/text-field/src/vaadin-text-field.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-sorter.js';
|
||||||
|
import 'Frontend/generated/jar-resources/gridConnector.ts';
|
||||||
|
import '@vaadin/component-base/src/debounce.js';
|
||||||
|
import '@vaadin/component-base/src/async.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-active-item-mixin.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-grid-flow-selection-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column-group.js';
|
||||||
|
import 'Frontend/generated/jar-resources/lit-renderer.ts';
|
||||||
|
import 'lit/directives/live.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-layout.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-item.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-row.js';
|
||||||
|
import '@vaadin/integer-field/src/vaadin-integer-field.js';
|
||||||
|
import '@vaadin/combo-box/src/vaadin-combo-box.js';
|
||||||
|
import 'Frontend/generated/jar-resources/comboBoxConnector.js';
|
||||||
|
import '@vaadin/combo-box/src/vaadin-combo-box-placeholder.js';
|
||||||
|
import '@vaadin/multi-select-combo-box/src/vaadin-multi-select-combo-box.js';
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-app-layout.js';
|
||||||
|
import '@vaadin/scroller/src/vaadin-scroller.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
|
||||||
|
import '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-drawer-toggle.js';
|
||||||
|
import '@vaadin/button/src/vaadin-button.js';
|
||||||
|
import 'Frontend/generated/jar-resources/disableOnClickFunctions.js';
|
||||||
|
import '@vaadin/avatar/src/vaadin-avatar.js';
|
||||||
|
import 'Frontend/generated/jar-resources/menubarConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuConnector.js';
|
||||||
|
import '@vaadin/menu-bar/src/vaadin-menu-bar.js';
|
||||||
|
import '@vaadin/context-menu/src/vaadin-context-menu.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-renderer.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-directive.js';
|
||||||
|
import 'lit';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuTargetConnector.js';
|
||||||
|
import '@vaadin/component-base/src/gestures.js';
|
||||||
|
import '@vaadin/horizontal-layout/src/vaadin-horizontal-layout.js';
|
||||||
|
import '@vaadin/icons/vaadin-iconset.js';
|
||||||
|
import '@vaadin/icon/src/vaadin-icon.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-upload-manager-connector.ts';
|
||||||
|
import '@vaadin/upload/vaadin-upload-manager.js';
|
||||||
|
import '@vaadin/notification/src/vaadin-notification.js';
|
||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion-panel.js';
|
||||||
|
import '@vaadin/details/src/vaadin-details.js';
|
||||||
|
import 'Frontend/generated/jar-resources/messageListConnector.js';
|
||||||
|
import '@vaadin/message-list/src/vaadin-message-list.js';
|
||||||
|
import '@vaadin/text-field/src/vaadin-text-field.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-sorter.js';
|
||||||
|
import 'Frontend/generated/jar-resources/gridConnector.ts';
|
||||||
|
import '@vaadin/component-base/src/debounce.js';
|
||||||
|
import '@vaadin/component-base/src/async.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-active-item-mixin.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-grid-flow-selection-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column-group.js';
|
||||||
|
import 'Frontend/generated/jar-resources/lit-renderer.ts';
|
||||||
|
import 'lit/directives/live.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-layout.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-item.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-row.js';
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-app-layout.js';
|
||||||
|
import '@vaadin/scroller/src/vaadin-scroller.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
|
||||||
|
import '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-drawer-toggle.js';
|
||||||
|
import '@vaadin/button/src/vaadin-button.js';
|
||||||
|
import 'Frontend/generated/jar-resources/disableOnClickFunctions.js';
|
||||||
|
import '@vaadin/horizontal-layout/src/vaadin-horizontal-layout.js';
|
||||||
|
import '@vaadin/icons/vaadin-iconset.js';
|
||||||
|
import '@vaadin/icon/src/vaadin-icon.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-upload-manager-connector.ts';
|
||||||
|
import '@vaadin/upload/vaadin-upload-manager.js';
|
||||||
|
import '@vaadin/notification/src/vaadin-notification.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-renderer.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-directive.js';
|
||||||
|
import 'lit';
|
||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
import '@vaadin/avatar/src/vaadin-avatar.js';
|
||||||
|
import 'Frontend/generated/jar-resources/menubarConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuConnector.js';
|
||||||
|
import '@vaadin/menu-bar/src/vaadin-menu-bar.js';
|
||||||
|
import '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
import '@vaadin/context-menu/src/vaadin-context-menu.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-renderer.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-directive.js';
|
||||||
|
import 'lit';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuTargetConnector.js';
|
||||||
|
import '@vaadin/component-base/src/gestures.js';
|
||||||
|
import 'Frontend/generated/jar-resources/disableOnClickFunctions.js';
|
||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-app-layout.js';
|
||||||
|
import '@vaadin/scroller/src/vaadin-scroller.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-drawer-toggle.js';
|
||||||
|
import '@vaadin/button/src/vaadin-button.js';
|
||||||
|
import '@vaadin/horizontal-layout/src/vaadin-horizontal-layout.js';
|
||||||
|
import '@vaadin/icons/vaadin-iconset.js';
|
||||||
|
import '@vaadin/icon/src/vaadin-icon.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-upload-manager-connector.ts';
|
||||||
|
import '@vaadin/upload/vaadin-upload-manager.js';
|
||||||
|
import '@vaadin/notification/src/vaadin-notification.js';
|
||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion-panel.js';
|
||||||
|
import '@vaadin/details/src/vaadin-details.js';
|
||||||
|
import 'Frontend/generated/jar-resources/messageListConnector.js';
|
||||||
|
import '@vaadin/message-list/src/vaadin-message-list.js';
|
||||||
|
import '@vaadin/text-field/src/vaadin-text-field.js';
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-app-layout.js';
|
||||||
|
import '@vaadin/scroller/src/vaadin-scroller.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
|
||||||
|
import '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-drawer-toggle.js';
|
||||||
|
import '@vaadin/button/src/vaadin-button.js';
|
||||||
|
import 'Frontend/generated/jar-resources/disableOnClickFunctions.js';
|
||||||
|
import '@vaadin/horizontal-layout/src/vaadin-horizontal-layout.js';
|
||||||
|
import '@vaadin/icons/vaadin-iconset.js';
|
||||||
|
import '@vaadin/icon/src/vaadin-icon.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-upload-manager-connector.ts';
|
||||||
|
import '@vaadin/upload/vaadin-upload-manager.js';
|
||||||
|
import '@vaadin/notification/src/vaadin-notification.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-renderer.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-directive.js';
|
||||||
|
import 'lit';
|
||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
|
import 'Frontend/generated/jar-resources/menubarConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuConnector.js';
|
||||||
|
import '@vaadin/menu-bar/src/vaadin-menu-bar.js';
|
||||||
|
import '@vaadin/context-menu/src/vaadin-context-menu.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuTargetConnector.js';
|
||||||
|
import '@vaadin/component-base/src/gestures.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion-panel.js';
|
||||||
|
import '@vaadin/details/src/vaadin-details.js';
|
||||||
|
import 'Frontend/generated/jar-resources/messageListConnector.js';
|
||||||
|
import '@vaadin/message-list/src/vaadin-message-list.js';
|
||||||
|
import '@vaadin/text-field/src/vaadin-text-field.js';
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-app-layout.js';
|
||||||
|
import '@vaadin/scroller/src/vaadin-scroller.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav.js';
|
||||||
|
import '@vaadin/side-nav/src/vaadin-side-nav-item.js';
|
||||||
|
import '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
import '@vaadin/app-layout/src/vaadin-drawer-toggle.js';
|
||||||
|
import '@vaadin/button/src/vaadin-button.js';
|
||||||
|
import 'Frontend/generated/jar-resources/disableOnClickFunctions.js';
|
||||||
|
import '@vaadin/horizontal-layout/src/vaadin-horizontal-layout.js';
|
||||||
|
import '@vaadin/icons/vaadin-iconset.js';
|
||||||
|
import '@vaadin/icon/src/vaadin-icon.js';
|
||||||
|
import '@vaadin/upload/src/vaadin-upload.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-upload-manager-connector.ts';
|
||||||
|
import '@vaadin/upload/vaadin-upload-manager.js';
|
||||||
|
import '@vaadin/notification/src/vaadin-notification.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-renderer.js';
|
||||||
|
import 'Frontend/generated/jar-resources/flow-component-directive.js';
|
||||||
|
import 'lit';
|
||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
|
import 'Frontend/generated/jar-resources/menubarConnector.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuConnector.js';
|
||||||
|
import '@vaadin/menu-bar/src/vaadin-menu-bar.js';
|
||||||
|
import '@vaadin/context-menu/src/vaadin-context-menu.js';
|
||||||
|
import 'Frontend/generated/jar-resources/contextMenuTargetConnector.js';
|
||||||
|
import '@vaadin/component-base/src/gestures.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion.js';
|
||||||
|
import '@vaadin/accordion/src/vaadin-accordion-panel.js';
|
||||||
|
import '@vaadin/details/src/vaadin-details.js';
|
||||||
|
import 'Frontend/generated/jar-resources/messageListConnector.js';
|
||||||
|
import '@vaadin/message-list/src/vaadin-message-list.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-sorter.js';
|
||||||
|
import 'Frontend/generated/jar-resources/gridConnector.ts';
|
||||||
|
import '@vaadin/component-base/src/debounce.js';
|
||||||
|
import '@vaadin/component-base/src/async.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-active-item-mixin.js';
|
||||||
|
import 'Frontend/generated/jar-resources/vaadin-grid-flow-selection-column.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column-group.js';
|
||||||
|
import 'Frontend/generated/jar-resources/lit-renderer.ts';
|
||||||
|
import 'lit/directives/live.js';
|
||||||
|
import '@vaadin/text-field/src/vaadin-text-field.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-layout.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-item.js';
|
||||||
|
import '@vaadin/form-layout/src/vaadin-form-row.js';
|
||||||
|
import '@vaadin/list-box/src/vaadin-list-box.js';
|
||||||
|
import '@vaadin/item/src/vaadin-item.js';
|
||||||
|
import '@vaadin/integer-field/src/vaadin-integer-field.js';
|
||||||
|
import '@vaadin/combo-box/src/vaadin-combo-box.js';
|
||||||
|
import 'Frontend/generated/jar-resources/comboBoxConnector.js';
|
||||||
|
import '@vaadin/combo-box/src/vaadin-combo-box-placeholder.js';
|
||||||
|
import '@vaadin/multi-select-combo-box/src/vaadin-multi-select-combo-box.js';
|
||||||
|
import '@vaadin/custom-field/src/vaadin-custom-field.js';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export {}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/login/src/vaadin-login-form.js';
|
||||||
|
import '@vaadin/common-frontend/ConnectionIndicator.js';
|
||||||
|
import 'Frontend/generated/jar-resources/ReactRouterOutletElement.tsx';
|
||||||
|
import 'react-router';
|
||||||
|
import 'react';
|
||||||
|
|
||||||
|
const loadOnDemand = (key) => {
|
||||||
|
const pending = [];
|
||||||
|
if (key === '81bbe2e11b6af26d0d292e2e18bde37546d5b9ec24f4f4f78df2bc9f9c21f431') {
|
||||||
|
pending.push(import('./chunks/chunk-4a1b53ad267588ce5a6c622e0efdd0adeec2e922b2e51061d1fb0d63ad8f7363.js'));
|
||||||
|
}
|
||||||
|
if (key === '8904207b9d538b9eedfaaefd8e82d3415de8513bda9d6bb76f4012f0f336d200') {
|
||||||
|
pending.push(import('./chunks/chunk-050179d38edf3560e7fc3d0177787bb20c55630f7c4b372ebad09b42b287a5dd.js'));
|
||||||
|
}
|
||||||
|
if (key === '96ecac798fbbe91ef146ac4a25f5c31827427ca0f106ba66e63fc1fd6c27b83d') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === 'c0816c2bfaa97d6f3e7eea83c5478186457bdc79cfe3f284c42e192d68607157') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === '58cc18f5518623050c6a99d327dd540140b9b0dca851a76a70e688291f661e11') {
|
||||||
|
pending.push(import('./chunks/chunk-b3e84351087a7066a5317979e35ce71f4f249b80a6f66b5f0c9b0c0790866b93.js'));
|
||||||
|
}
|
||||||
|
if (key === '83045a9760ea76fd534ed71ed553dc7cf2595a5d697e3c7bc55470d219770d05') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === 'e9df10b3be58513bb6452e4dbdce31e0b1dc33868fbba54ee72e82089cf39adf') {
|
||||||
|
pending.push(import('./chunks/chunk-050179d38edf3560e7fc3d0177787bb20c55630f7c4b372ebad09b42b287a5dd.js'));
|
||||||
|
}
|
||||||
|
if (key === '39b4a07b2b07a780f8ac9d2f7cc855e3b78ec60465d9d808690466c1f0bea792') {
|
||||||
|
pending.push(import('./chunks/chunk-b0eb0de7326631053d249cbbdaed9122656362ee32dec3dd21fc23f7db177400.js'));
|
||||||
|
}
|
||||||
|
if (key === '803ed44611ffeb1bcb1f604f267f09af48ef8a5bc8d765e481b67ed59f9b880a') {
|
||||||
|
pending.push(import('./chunks/chunk-36157cc9f15f59ffacee94022392ef651f5128827a02941ad7397c3097927cf6.js'));
|
||||||
|
}
|
||||||
|
if (key === '9f40817b690e11a5b23a3159bb774f15430c23768f26dc0d15efccc3d65223ac') {
|
||||||
|
pending.push(import('./chunks/chunk-17458473c5b0c327e5fdfb8de65e9c031656ad433885b259b9e37650bcf5111d.js'));
|
||||||
|
}
|
||||||
|
if (key === '391ac9fb4bd95bc899c5911c48a6f3b549b118923653444f5ab50acddfb6f291') {
|
||||||
|
pending.push(import('./chunks/chunk-2d26a95ba6c84191291435d6713bc4840a15eaad9dcf9f70fc4a3a70aee09f13.js'));
|
||||||
|
}
|
||||||
|
if (key === '1ac8bd8d734d2603947b1db0616ea4dae4ce691a5264f5c9aeafcaa897e0a872') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === 'e5db1fd1f39047ab2857e9ef118681c3f831f39efbbb2a6f000e9475b77444e5') {
|
||||||
|
pending.push(import('./chunks/chunk-b3e84351087a7066a5317979e35ce71f4f249b80a6f66b5f0c9b0c0790866b93.js'));
|
||||||
|
}
|
||||||
|
if (key === '92a567041312ea4c7faa4dd5bcc74f73cd3b16dd721624d0bc68d1704138abe5') {
|
||||||
|
pending.push(import('./chunks/chunk-6456be19764fead9274c211db67c1fed3129c53422a2de0c4ebc6ea374a4ec5c.js'));
|
||||||
|
}
|
||||||
|
if (key === 'bfac19b5aab9cd5a47ff4a66f2a839ebbb4889867824be809c5dc3a3d47f94b8') {
|
||||||
|
pending.push(import('./chunks/chunk-2d26a95ba6c84191291435d6713bc4840a15eaad9dcf9f70fc4a3a70aee09f13.js'));
|
||||||
|
}
|
||||||
|
if (key === 'a886fd99e05dd2c5ce3e4b643dc8d6d29189dee784fa78678d8ab4fcb89216c0') {
|
||||||
|
pending.push(import('./chunks/chunk-09a91b9550ef322666194480aeb843ea6225d32c6cf3cdd6c06d9751b9b982c4.js'));
|
||||||
|
}
|
||||||
|
if (key === 'becc589ccd4f7ac5e6f13c45eecc05f61d67ce57f585f1655800354ff4b55f3c') {
|
||||||
|
pending.push(import('./chunks/chunk-17458473c5b0c327e5fdfb8de65e9c031656ad433885b259b9e37650bcf5111d.js'));
|
||||||
|
}
|
||||||
|
if (key === '0ca48a0e12f1dbf4945f761a93a7df202b2908327a63c52f2f060559c2bcc7f0') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === '2140ca6af7e9a5ff94d8a66b6e5a23e6d184ef7901ca73d33b3c30f74f94708a') {
|
||||||
|
pending.push(import('./chunks/chunk-b5a3b3b8d39c03fe35c3dda50e5bd1ec25755f18091bcaffc331ab0fd020c6fd.js'));
|
||||||
|
}
|
||||||
|
if (key === '89c2914ed5d19860240c0c1d3dba174616dc452c4ba8ae039f016cf9592e9432') {
|
||||||
|
pending.push(import('./chunks/chunk-2d26a95ba6c84191291435d6713bc4840a15eaad9dcf9f70fc4a3a70aee09f13.js'));
|
||||||
|
}
|
||||||
|
if (key === 'ffd87f879398d3b6942e852eda9b64e28a124c8a61938c867efb360a5cf1d32e') {
|
||||||
|
pending.push(import('./chunks/chunk-2d26a95ba6c84191291435d6713bc4840a15eaad9dcf9f70fc4a3a70aee09f13.js'));
|
||||||
|
}
|
||||||
|
if (key === 'c5f987a4896c791d13aeb6a03a4783ff03df304750a7db930a18d399dba31509') {
|
||||||
|
pending.push(import('./chunks/chunk-2d26a95ba6c84191291435d6713bc4840a15eaad9dcf9f70fc4a3a70aee09f13.js'));
|
||||||
|
}
|
||||||
|
if (key === '3665d7598c9ed4112997a0493fb890edf38d23d3bd9fcb0eb09bf3e045f5228b') {
|
||||||
|
pending.push(import('./chunks/chunk-2d26a95ba6c84191291435d6713bc4840a15eaad9dcf9f70fc4a3a70aee09f13.js'));
|
||||||
|
}
|
||||||
|
if (key === '4e76ad4411fbf58731cdad95d28e6fe0057fb4a97ffd54f193e52b111462552d') {
|
||||||
|
pending.push(import('./chunks/chunk-4a1b53ad267588ce5a6c622e0efdd0adeec2e922b2e51061d1fb0d63ad8f7363.js'));
|
||||||
|
}
|
||||||
|
if (key === '3e87cbb0d40d50da9c3b69d0dada85fe9b7f2f4bae2b79c8f0408a798b120549') {
|
||||||
|
pending.push(import('./chunks/chunk-fc7662d8a7810d7f14f9f989942d4df1753b8cef76009c17999d2ca394bbd8ca.js'));
|
||||||
|
}
|
||||||
|
if (key === 'ac7af13a0805d3ce8a287e9d860521c82aaf55d2c448ce3f62a85d44216a8189') {
|
||||||
|
pending.push(import('./chunks/chunk-2d26a95ba6c84191291435d6713bc4840a15eaad9dcf9f70fc4a3a70aee09f13.js'));
|
||||||
|
}
|
||||||
|
if (key === 'a4eeeab53770801e478b15e6b179e5689afd43fc753f347e0c51ce934bd28a6c') {
|
||||||
|
pending.push(import('./chunks/chunk-5584551b9200a1d2b592bd96c14b3cacc38f4ef0c1fe22978154b3bbc6061f05.js'));
|
||||||
|
}
|
||||||
|
if (key === 'dca37e6961f625182e78a722a61c0eb9b8a2357b5e787fe616fe5993c2db5133') {
|
||||||
|
pending.push(import('./chunks/chunk-1465405c28a197cc689b86bc09d756395cc2b486677ca560b40061d793a87524.js'));
|
||||||
|
}
|
||||||
|
if (key === '5d710852587ce9ebabfac9ed950f6b11bc3a87d96860d4efdfb61dab7ae54091') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === '2b0ca0e51ded6027d3f9e464513210d5503ab2a9bc532de5cfc81d78a58f422f') {
|
||||||
|
pending.push(import('./chunks/chunk-17458473c5b0c327e5fdfb8de65e9c031656ad433885b259b9e37650bcf5111d.js'));
|
||||||
|
}
|
||||||
|
if (key === '28b5e9145505152a4dbd9600977b8177d3a161906581ca5a1a923affc812a761') {
|
||||||
|
pending.push(import('./chunks/chunk-fc7662d8a7810d7f14f9f989942d4df1753b8cef76009c17999d2ca394bbd8ca.js'));
|
||||||
|
}
|
||||||
|
if (key === '68a5f871903ca703f0fac8dd0072e1bfc082ffa14aa445026be6e1eb6cfe1bd0') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === '53ff1588255d7e5486eb67452c8d9c43baea0eecd9bd509fd7f0c55b3cccaa0d') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === '4e356c0939a28ab8766b0375ca6cb478b6163eb9ad80745a9e2f2fc80997b6a2') {
|
||||||
|
pending.push(import('./chunks/chunk-918bb383890df4641c1ca33b36e3fdba718a1ce1f4335789bcf9ca5a32123d48.js'));
|
||||||
|
}
|
||||||
|
if (key === 'b4afd0c0906e26b51836d41720e1b8d91a7cacf0fd42e089f1e0fbc2ee27a5bb') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === '2062a3f35e8fdb051e9362a8a8895d80ef719832e9394babcbaab3aac56d4f0e') {
|
||||||
|
pending.push(import('./chunks/chunk-5584551b9200a1d2b592bd96c14b3cacc38f4ef0c1fe22978154b3bbc6061f05.js'));
|
||||||
|
}
|
||||||
|
return Promise.all(pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Vaadin = window.Vaadin || {};
|
||||||
|
window.Vaadin.Flow = window.Vaadin.Flow || {};
|
||||||
|
window.Vaadin.Flow.loadOnDemand = loadOnDemand;
|
||||||
|
window.Vaadin.Flow.resetFocus = () => {
|
||||||
|
let ae=document.activeElement;
|
||||||
|
while(ae&&ae.shadowRoot) ae = ae.shadowRoot.activeElement;
|
||||||
|
return !ae || ae.blur() || ae.focus() || true;
|
||||||
|
}
|
||||||
+129
@@ -0,0 +1,129 @@
|
|||||||
|
import { injectGlobalWebcomponentCss } from 'Frontend/generated/jar-resources/theme-util.js';
|
||||||
|
|
||||||
|
import '@vaadin/vertical-layout/src/vaadin-vertical-layout.js';
|
||||||
|
import '@vaadin/login/src/vaadin-login-form.js';
|
||||||
|
import '@vaadin/common-frontend/ConnectionIndicator.js';
|
||||||
|
import 'Frontend/generated/jar-resources/ReactRouterOutletElement.tsx';
|
||||||
|
import 'react-router';
|
||||||
|
import 'react';
|
||||||
|
|
||||||
|
const loadOnDemand = (key) => {
|
||||||
|
const pending = [];
|
||||||
|
if (key === '81bbe2e11b6af26d0d292e2e18bde37546d5b9ec24f4f4f78df2bc9f9c21f431') {
|
||||||
|
pending.push(import('./chunks/chunk-4a1b53ad267588ce5a6c622e0efdd0adeec2e922b2e51061d1fb0d63ad8f7363.js'));
|
||||||
|
}
|
||||||
|
if (key === '8904207b9d538b9eedfaaefd8e82d3415de8513bda9d6bb76f4012f0f336d200') {
|
||||||
|
pending.push(import('./chunks/chunk-050179d38edf3560e7fc3d0177787bb20c55630f7c4b372ebad09b42b287a5dd.js'));
|
||||||
|
}
|
||||||
|
if (key === '96ecac798fbbe91ef146ac4a25f5c31827427ca0f106ba66e63fc1fd6c27b83d') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === 'c0816c2bfaa97d6f3e7eea83c5478186457bdc79cfe3f284c42e192d68607157') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === '58cc18f5518623050c6a99d327dd540140b9b0dca851a76a70e688291f661e11') {
|
||||||
|
pending.push(import('./chunks/chunk-b3e84351087a7066a5317979e35ce71f4f249b80a6f66b5f0c9b0c0790866b93.js'));
|
||||||
|
}
|
||||||
|
if (key === '83045a9760ea76fd534ed71ed553dc7cf2595a5d697e3c7bc55470d219770d05') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === 'e9df10b3be58513bb6452e4dbdce31e0b1dc33868fbba54ee72e82089cf39adf') {
|
||||||
|
pending.push(import('./chunks/chunk-050179d38edf3560e7fc3d0177787bb20c55630f7c4b372ebad09b42b287a5dd.js'));
|
||||||
|
}
|
||||||
|
if (key === '39b4a07b2b07a780f8ac9d2f7cc855e3b78ec60465d9d808690466c1f0bea792') {
|
||||||
|
pending.push(import('./chunks/chunk-b0eb0de7326631053d249cbbdaed9122656362ee32dec3dd21fc23f7db177400.js'));
|
||||||
|
}
|
||||||
|
if (key === '803ed44611ffeb1bcb1f604f267f09af48ef8a5bc8d765e481b67ed59f9b880a') {
|
||||||
|
pending.push(import('./chunks/chunk-36157cc9f15f59ffacee94022392ef651f5128827a02941ad7397c3097927cf6.js'));
|
||||||
|
}
|
||||||
|
if (key === '9f40817b690e11a5b23a3159bb774f15430c23768f26dc0d15efccc3d65223ac') {
|
||||||
|
pending.push(import('./chunks/chunk-17458473c5b0c327e5fdfb8de65e9c031656ad433885b259b9e37650bcf5111d.js'));
|
||||||
|
}
|
||||||
|
if (key === '391ac9fb4bd95bc899c5911c48a6f3b549b118923653444f5ab50acddfb6f291') {
|
||||||
|
pending.push(import('./chunks/chunk-2d26a95ba6c84191291435d6713bc4840a15eaad9dcf9f70fc4a3a70aee09f13.js'));
|
||||||
|
}
|
||||||
|
if (key === '1ac8bd8d734d2603947b1db0616ea4dae4ce691a5264f5c9aeafcaa897e0a872') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === 'e5db1fd1f39047ab2857e9ef118681c3f831f39efbbb2a6f000e9475b77444e5') {
|
||||||
|
pending.push(import('./chunks/chunk-b3e84351087a7066a5317979e35ce71f4f249b80a6f66b5f0c9b0c0790866b93.js'));
|
||||||
|
}
|
||||||
|
if (key === '92a567041312ea4c7faa4dd5bcc74f73cd3b16dd721624d0bc68d1704138abe5') {
|
||||||
|
pending.push(import('./chunks/chunk-6456be19764fead9274c211db67c1fed3129c53422a2de0c4ebc6ea374a4ec5c.js'));
|
||||||
|
}
|
||||||
|
if (key === 'bfac19b5aab9cd5a47ff4a66f2a839ebbb4889867824be809c5dc3a3d47f94b8') {
|
||||||
|
pending.push(import('./chunks/chunk-2d26a95ba6c84191291435d6713bc4840a15eaad9dcf9f70fc4a3a70aee09f13.js'));
|
||||||
|
}
|
||||||
|
if (key === 'a886fd99e05dd2c5ce3e4b643dc8d6d29189dee784fa78678d8ab4fcb89216c0') {
|
||||||
|
pending.push(import('./chunks/chunk-09a91b9550ef322666194480aeb843ea6225d32c6cf3cdd6c06d9751b9b982c4.js'));
|
||||||
|
}
|
||||||
|
if (key === 'becc589ccd4f7ac5e6f13c45eecc05f61d67ce57f585f1655800354ff4b55f3c') {
|
||||||
|
pending.push(import('./chunks/chunk-17458473c5b0c327e5fdfb8de65e9c031656ad433885b259b9e37650bcf5111d.js'));
|
||||||
|
}
|
||||||
|
if (key === '0ca48a0e12f1dbf4945f761a93a7df202b2908327a63c52f2f060559c2bcc7f0') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === '2140ca6af7e9a5ff94d8a66b6e5a23e6d184ef7901ca73d33b3c30f74f94708a') {
|
||||||
|
pending.push(import('./chunks/chunk-b5a3b3b8d39c03fe35c3dda50e5bd1ec25755f18091bcaffc331ab0fd020c6fd.js'));
|
||||||
|
}
|
||||||
|
if (key === '89c2914ed5d19860240c0c1d3dba174616dc452c4ba8ae039f016cf9592e9432') {
|
||||||
|
pending.push(import('./chunks/chunk-2d26a95ba6c84191291435d6713bc4840a15eaad9dcf9f70fc4a3a70aee09f13.js'));
|
||||||
|
}
|
||||||
|
if (key === 'ffd87f879398d3b6942e852eda9b64e28a124c8a61938c867efb360a5cf1d32e') {
|
||||||
|
pending.push(import('./chunks/chunk-2d26a95ba6c84191291435d6713bc4840a15eaad9dcf9f70fc4a3a70aee09f13.js'));
|
||||||
|
}
|
||||||
|
if (key === 'c5f987a4896c791d13aeb6a03a4783ff03df304750a7db930a18d399dba31509') {
|
||||||
|
pending.push(import('./chunks/chunk-2d26a95ba6c84191291435d6713bc4840a15eaad9dcf9f70fc4a3a70aee09f13.js'));
|
||||||
|
}
|
||||||
|
if (key === '3665d7598c9ed4112997a0493fb890edf38d23d3bd9fcb0eb09bf3e045f5228b') {
|
||||||
|
pending.push(import('./chunks/chunk-2d26a95ba6c84191291435d6713bc4840a15eaad9dcf9f70fc4a3a70aee09f13.js'));
|
||||||
|
}
|
||||||
|
if (key === '4e76ad4411fbf58731cdad95d28e6fe0057fb4a97ffd54f193e52b111462552d') {
|
||||||
|
pending.push(import('./chunks/chunk-4a1b53ad267588ce5a6c622e0efdd0adeec2e922b2e51061d1fb0d63ad8f7363.js'));
|
||||||
|
}
|
||||||
|
if (key === '3e87cbb0d40d50da9c3b69d0dada85fe9b7f2f4bae2b79c8f0408a798b120549') {
|
||||||
|
pending.push(import('./chunks/chunk-fc7662d8a7810d7f14f9f989942d4df1753b8cef76009c17999d2ca394bbd8ca.js'));
|
||||||
|
}
|
||||||
|
if (key === 'ac7af13a0805d3ce8a287e9d860521c82aaf55d2c448ce3f62a85d44216a8189') {
|
||||||
|
pending.push(import('./chunks/chunk-2d26a95ba6c84191291435d6713bc4840a15eaad9dcf9f70fc4a3a70aee09f13.js'));
|
||||||
|
}
|
||||||
|
if (key === 'a4eeeab53770801e478b15e6b179e5689afd43fc753f347e0c51ce934bd28a6c') {
|
||||||
|
pending.push(import('./chunks/chunk-5584551b9200a1d2b592bd96c14b3cacc38f4ef0c1fe22978154b3bbc6061f05.js'));
|
||||||
|
}
|
||||||
|
if (key === 'dca37e6961f625182e78a722a61c0eb9b8a2357b5e787fe616fe5993c2db5133') {
|
||||||
|
pending.push(import('./chunks/chunk-1465405c28a197cc689b86bc09d756395cc2b486677ca560b40061d793a87524.js'));
|
||||||
|
}
|
||||||
|
if (key === '5d710852587ce9ebabfac9ed950f6b11bc3a87d96860d4efdfb61dab7ae54091') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === '2b0ca0e51ded6027d3f9e464513210d5503ab2a9bc532de5cfc81d78a58f422f') {
|
||||||
|
pending.push(import('./chunks/chunk-17458473c5b0c327e5fdfb8de65e9c031656ad433885b259b9e37650bcf5111d.js'));
|
||||||
|
}
|
||||||
|
if (key === '28b5e9145505152a4dbd9600977b8177d3a161906581ca5a1a923affc812a761') {
|
||||||
|
pending.push(import('./chunks/chunk-fc7662d8a7810d7f14f9f989942d4df1753b8cef76009c17999d2ca394bbd8ca.js'));
|
||||||
|
}
|
||||||
|
if (key === '68a5f871903ca703f0fac8dd0072e1bfc082ffa14aa445026be6e1eb6cfe1bd0') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === '53ff1588255d7e5486eb67452c8d9c43baea0eecd9bd509fd7f0c55b3cccaa0d') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === '4e356c0939a28ab8766b0375ca6cb478b6163eb9ad80745a9e2f2fc80997b6a2') {
|
||||||
|
pending.push(import('./chunks/chunk-918bb383890df4641c1ca33b36e3fdba718a1ce1f4335789bcf9ca5a32123d48.js'));
|
||||||
|
}
|
||||||
|
if (key === 'b4afd0c0906e26b51836d41720e1b8d91a7cacf0fd42e089f1e0fbc2ee27a5bb') {
|
||||||
|
pending.push(import('./chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js'));
|
||||||
|
}
|
||||||
|
if (key === '2062a3f35e8fdb051e9362a8a8895d80ef719832e9394babcbaab3aac56d4f0e') {
|
||||||
|
pending.push(import('./chunks/chunk-5584551b9200a1d2b592bd96c14b3cacc38f4ef0c1fe22978154b3bbc6061f05.js'));
|
||||||
|
}
|
||||||
|
return Promise.all(pending);
|
||||||
|
}
|
||||||
|
window.Vaadin = window.Vaadin || {};
|
||||||
|
window.Vaadin.Flow = window.Vaadin.Flow || {};
|
||||||
|
window.Vaadin.Flow.loadOnDemand = loadOnDemand;
|
||||||
|
window.Vaadin.Flow.resetFocus = () => {
|
||||||
|
let ae=document.activeElement;
|
||||||
|
while(ae&&ae.shadowRoot) ae = ae.shadowRoot.activeElement;
|
||||||
|
return !ae || ae.blur() || ae.focus() || true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
app-shell-imports.d.ts
|
||||||
|
app-shell-imports.js
|
||||||
|
css.generated.d.ts
|
||||||
|
flow/Flow.tsx
|
||||||
|
flow/ReactAdapter.tsx
|
||||||
|
flow/chunks/chunk-050179d38edf3560e7fc3d0177787bb20c55630f7c4b372ebad09b42b287a5dd.js
|
||||||
|
flow/chunks/chunk-09a91b9550ef322666194480aeb843ea6225d32c6cf3cdd6c06d9751b9b982c4.js
|
||||||
|
flow/chunks/chunk-1465405c28a197cc689b86bc09d756395cc2b486677ca560b40061d793a87524.js
|
||||||
|
flow/chunks/chunk-17458473c5b0c327e5fdfb8de65e9c031656ad433885b259b9e37650bcf5111d.js
|
||||||
|
flow/chunks/chunk-2d26a95ba6c84191291435d6713bc4840a15eaad9dcf9f70fc4a3a70aee09f13.js
|
||||||
|
flow/chunks/chunk-36157cc9f15f59ffacee94022392ef651f5128827a02941ad7397c3097927cf6.js
|
||||||
|
flow/chunks/chunk-4a1b53ad267588ce5a6c622e0efdd0adeec2e922b2e51061d1fb0d63ad8f7363.js
|
||||||
|
flow/chunks/chunk-5584551b9200a1d2b592bd96c14b3cacc38f4ef0c1fe22978154b3bbc6061f05.js
|
||||||
|
flow/chunks/chunk-6456be19764fead9274c211db67c1fed3129c53422a2de0c4ebc6ea374a4ec5c.js
|
||||||
|
flow/chunks/chunk-918bb383890df4641c1ca33b36e3fdba718a1ce1f4335789bcf9ca5a32123d48.js
|
||||||
|
flow/chunks/chunk-993657077513d5c8a0171b3eaf9fefcf9af77a45b27ce7cb2f83e54f445b12ea.js
|
||||||
|
flow/chunks/chunk-b0eb0de7326631053d249cbbdaed9122656362ee32dec3dd21fc23f7db177400.js
|
||||||
|
flow/chunks/chunk-b3e84351087a7066a5317979e35ce71f4f249b80a6f66b5f0c9b0c0790866b93.js
|
||||||
|
flow/chunks/chunk-b5a3b3b8d39c03fe35c3dda50e5bd1ec25755f18091bcaffc331ab0fd020c6fd.js
|
||||||
|
flow/chunks/chunk-fc7662d8a7810d7f14f9f989942d4df1753b8cef76009c17999d2ca394bbd8ca.js
|
||||||
|
flow/generated-flow-imports.d.ts
|
||||||
|
flow/generated-flow-imports.js
|
||||||
|
flow/generated-flow-webcomponent-imports.js
|
||||||
|
index.tsx
|
||||||
|
jar-resources/Clipboard.d.ts
|
||||||
|
jar-resources/Clipboard.js
|
||||||
|
jar-resources/Clipboard.js.map
|
||||||
|
jar-resources/Download.d.ts
|
||||||
|
jar-resources/Download.js
|
||||||
|
jar-resources/Download.js.map
|
||||||
|
jar-resources/ElementResize.d.ts
|
||||||
|
jar-resources/ElementResize.js
|
||||||
|
jar-resources/ElementResize.js.map
|
||||||
|
jar-resources/Flow.d.ts
|
||||||
|
jar-resources/Flow.js
|
||||||
|
jar-resources/Flow.js.map
|
||||||
|
jar-resources/FlowBootstrap.d.ts
|
||||||
|
jar-resources/FlowBootstrap.js
|
||||||
|
jar-resources/FlowClient.d.ts
|
||||||
|
jar-resources/FlowClient.js
|
||||||
|
jar-resources/FlowShortcut.js
|
||||||
|
jar-resources/Fullscreen.d.ts
|
||||||
|
jar-resources/Fullscreen.js
|
||||||
|
jar-resources/Fullscreen.js.map
|
||||||
|
jar-resources/Geolocation.d.ts
|
||||||
|
jar-resources/Geolocation.js
|
||||||
|
jar-resources/Geolocation.js.map
|
||||||
|
jar-resources/PageVisibility.d.ts
|
||||||
|
jar-resources/PageVisibility.js
|
||||||
|
jar-resources/PageVisibility.js.map
|
||||||
|
jar-resources/ReactRouterOutletElement.tsx
|
||||||
|
jar-resources/ScreenOrientation.d.ts
|
||||||
|
jar-resources/ScreenOrientation.js
|
||||||
|
jar-resources/ScreenOrientation.js.map
|
||||||
|
jar-resources/WakeLock.d.ts
|
||||||
|
jar-resources/WakeLock.js
|
||||||
|
jar-resources/WakeLock.js.map
|
||||||
|
jar-resources/WebShare.d.ts
|
||||||
|
jar-resources/WebShare.js
|
||||||
|
jar-resources/WebShare.js.map
|
||||||
|
jar-resources/comboBoxConnector.js
|
||||||
|
jar-resources/contextMenuConnector.js
|
||||||
|
jar-resources/contextMenuTargetConnector.js
|
||||||
|
jar-resources/datepickerConnector.js
|
||||||
|
jar-resources/disableOnClickFunctions.js
|
||||||
|
jar-resources/dndConnector.js
|
||||||
|
jar-resources/flow-component-directive.js
|
||||||
|
jar-resources/flow-component-renderer.js
|
||||||
|
jar-resources/gridConnector.ts
|
||||||
|
jar-resources/index.d.ts
|
||||||
|
jar-resources/index.js
|
||||||
|
jar-resources/index.js.map
|
||||||
|
jar-resources/lit-renderer.ts
|
||||||
|
jar-resources/menubarConnector.js
|
||||||
|
jar-resources/messageListConnector.js
|
||||||
|
jar-resources/selectConnector.js
|
||||||
|
jar-resources/theme-util.js
|
||||||
|
jar-resources/tooltip.ts
|
||||||
|
jar-resources/treeGridConnector.ts
|
||||||
|
jar-resources/vaadin-big-decimal-field.js
|
||||||
|
jar-resources/vaadin-grid-flow-selection-column.js
|
||||||
|
jar-resources/vaadin-popover/popover.ts
|
||||||
|
jar-resources/vaadin-time-picker/helpers.js
|
||||||
|
jar-resources/vaadin-time-picker/timepickerConnector.js
|
||||||
|
jar-resources/vaadin-upload-manager-connector.ts
|
||||||
|
jar-resources/virtualListConnector.js
|
||||||
|
jsx-dev-transform/index.ts
|
||||||
|
jsx-dev-transform/jsx-dev-runtime.ts
|
||||||
|
jsx-dev-transform/jsx-runtime.ts
|
||||||
|
layouts.json
|
||||||
|
routes.tsx
|
||||||
|
vaadin-featureflags.js
|
||||||
|
vaadin-react.tsx
|
||||||
|
vaadin.ts
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* This file is auto-generated by Vaadin.
|
||||||
|
* If you want to customize the entry point, you can copy this file or create
|
||||||
|
* your own `index.tsx` in your frontend directory.
|
||||||
|
* By default, the `index.tsx` file should be in `./frontend/` folder.
|
||||||
|
*
|
||||||
|
* NOTE:
|
||||||
|
* - You need to restart the dev-server after adding the new `index.tsx` file.
|
||||||
|
* After that, all modifications to `index.tsx` are recompiled automatically.
|
||||||
|
* - `index.js` is also supported if you don't want to use TypeScript.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
import { createElement } from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import { RouterProvider } from 'react-router';
|
||||||
|
import { router } from 'Frontend/generated/routes.js';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return <RouterProvider router={router} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const outlet = document.getElementById('outlet')!;
|
||||||
|
let root = (outlet as any)._root ?? createRoot(outlet);
|
||||||
|
(outlet as any)._root = root;
|
||||||
|
root.render(createElement(App));
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Reads the first item from the system clipboard and returns its text/plain
|
||||||
|
* and text/html representations. Either field is {@code null} if the
|
||||||
|
* corresponding MIME type is not present.
|
||||||
|
*
|
||||||
|
* The caller is expected to be inside a transient user gesture and to have
|
||||||
|
* been granted the {@code clipboard-read} permission; otherwise
|
||||||
|
* {@code navigator.clipboard.read} rejects and this function propagates the
|
||||||
|
* rejection.
|
||||||
|
*/
|
||||||
|
async function readClipboardPayload() {
|
||||||
|
const items = await navigator.clipboard.read();
|
||||||
|
if (!items.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const item = items[0];
|
||||||
|
const get = async (type) => item.types.includes(type) ? (await item.getType(type)).text() : null;
|
||||||
|
return {
|
||||||
|
text: await get('text/plain'),
|
||||||
|
html: await get('text/html')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Re-encodes the given {@code <img>} as {@code image/png} via a canvas
|
||||||
|
* round-trip. The source can be any rasterisable format the browser already
|
||||||
|
* decodes ({@code image/png}, {@code image/jpeg}, {@code image/svg+xml}, ...);
|
||||||
|
* the output is always a {@code Promise<Blob>} of {@code image/png}, the only
|
||||||
|
* image MIME type every browser's asynchronous Clipboard API accepts on write.
|
||||||
|
*
|
||||||
|
* Cross-origin images need {@code crossorigin="anonymous"} on the {@code <img>}
|
||||||
|
* plus matching CORS headers, otherwise the canvas is tainted and
|
||||||
|
* {@code toBlob} throws.
|
||||||
|
*/
|
||||||
|
function imageToPngBlob(img) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const draw = () => {
|
||||||
|
try {
|
||||||
|
const width = img.naturalWidth || img.width;
|
||||||
|
const height = img.naturalHeight || img.height;
|
||||||
|
if (!width || !height) {
|
||||||
|
reject(new Error('image has no intrinsic size'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) {
|
||||||
|
reject(new Error('2D canvas context not available'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.drawImage(img, 0, 0, width, height);
|
||||||
|
canvas.toBlob((png) => (png ? resolve(png) : reject(new Error('canvas.toBlob returned null'))), 'image/png');
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (img.complete) {
|
||||||
|
// `complete` is also true for an image that already failed to load or has
|
||||||
|
// an empty src; those have naturalWidth === 0 and their load/error events
|
||||||
|
// have already fired and will never fire again, so we must settle here
|
||||||
|
// rather than wait for an event that never comes.
|
||||||
|
if (img.naturalWidth > 0) {
|
||||||
|
draw();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reject(new Error('image failed to load or has empty src'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
img.addEventListener('load', draw, { once: true });
|
||||||
|
img.addEventListener('error', () => reject(new Error('image load failed')), { once: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Writes any combination of text/plain, text/html and image/png to the system
|
||||||
|
* clipboard as a single ClipboardItem. Any argument may be {@code null} to omit
|
||||||
|
* that MIME type; at least one is expected to be non-null (the caller enforces
|
||||||
|
* this). The image argument is the source {@code <img>}; it is re-encoded as
|
||||||
|
* {@code image/png} via {@link imageToPngBlob} and the resulting
|
||||||
|
* {@code Promise<Blob>} is fed directly to {@code ClipboardItem} so the
|
||||||
|
* {@code navigator.clipboard.write} call stays synchronous inside the user
|
||||||
|
* gesture (Safari otherwise loses activation on the first await).
|
||||||
|
*
|
||||||
|
* The caller is expected to be inside a transient user gesture; otherwise
|
||||||
|
* {@code navigator.clipboard.write} rejects and this function propagates the
|
||||||
|
* rejection.
|
||||||
|
*
|
||||||
|
* Resolves with the {@code text/plain} value if present, otherwise with the
|
||||||
|
* {@code text/html} value, otherwise with {@code null} (image-only case).
|
||||||
|
*/
|
||||||
|
async function writeClipboardPayload(text, html, image) {
|
||||||
|
const entries = {};
|
||||||
|
if (text !== null) {
|
||||||
|
entries['text/plain'] = text;
|
||||||
|
}
|
||||||
|
if (html !== null) {
|
||||||
|
entries['text/html'] = html;
|
||||||
|
}
|
||||||
|
if (image !== null) {
|
||||||
|
entries['image/png'] = imageToPngBlob(image);
|
||||||
|
}
|
||||||
|
await navigator.clipboard.write([new ClipboardItem(entries)]);
|
||||||
|
return text !== null ? text : html;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Posts each file from a {@code paste} event's {@code clipboardData.files} as
|
||||||
|
* its own XHR to the URL stored as the named attribute on {@code element}. The
|
||||||
|
* wire format matches vaadin-upload: raw body, percent-encoded {@code X-Filename}
|
||||||
|
* header, MIME type in {@code Content-Type}.
|
||||||
|
*
|
||||||
|
* Each upload is processed in its own HTTP request, so the UI changes the
|
||||||
|
* server-side UploadHandler makes through {@code UI.access} are applied to the
|
||||||
|
* state tree but not sent to the client by the upload response itself. Once
|
||||||
|
* every upload of the paste has settled this helper dispatches a
|
||||||
|
* {@code vaadin-paste-upload-finished} event back on {@code element}; a
|
||||||
|
* server-side listener for that event triggers a normal Flow round trip that
|
||||||
|
* flushes those pending UI changes — so the API works without {@code @Push},
|
||||||
|
* exactly like a regular upload completing through the Upload component.
|
||||||
|
*
|
||||||
|
* Editable targets ({@code <input>}, {@code <textarea>}, {@code contentEditable})
|
||||||
|
* are not given any special treatment here: browsers do not paste files into
|
||||||
|
* those elements, so a paste containing a file in a focused text field is
|
||||||
|
* still a "the user tried to drop a file on the page" event from the
|
||||||
|
* application's point of view.
|
||||||
|
*/
|
||||||
|
// Monotonic counter incremented once per paste gesture so server-side
|
||||||
|
// handlers can correlate the parallel fetch POSTs that belong to the same
|
||||||
|
// paste, and order pastes against each other. Scoped to the browser tab —
|
||||||
|
// a different tab gets its own counter, but no server-side state crosses
|
||||||
|
// tabs in this flow.
|
||||||
|
let pasteSequence = 0;
|
||||||
|
function uploadPastedFiles(event, element, urlAttribute) {
|
||||||
|
const files = event.clipboardData?.files;
|
||||||
|
if (!files || files.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = element.getAttribute(urlAttribute);
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pasteSequence += 1;
|
||||||
|
const pasteId = String(pasteSequence);
|
||||||
|
// Surface the file count too: the batch server handler needs it to know
|
||||||
|
// when the paste has been fully delivered (one fetch per file means the
|
||||||
|
// server only observes arrivals, not the total).
|
||||||
|
const fileCount = String(files.length);
|
||||||
|
const uploads = [];
|
||||||
|
for (const file of files) {
|
||||||
|
const headers = {
|
||||||
|
'X-Filename': encodeURIComponent(file.name),
|
||||||
|
'X-Paste-Id': pasteId,
|
||||||
|
'X-Paste-File-Count': fileCount
|
||||||
|
};
|
||||||
|
if (file.type) {
|
||||||
|
headers['Content-Type'] = file.type;
|
||||||
|
}
|
||||||
|
// The per-file UploadHandler callback runs as each POST is processed;
|
||||||
|
// log network/connectivity failures the server will never see otherwise.
|
||||||
|
uploads.push(fetch(url, { method: 'POST', headers: headers, body: file }).catch((err) => {
|
||||||
|
console.error('Vaadin clipboard file upload failed', err);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
// Tell the server the paste's uploads are done so it can flush the queued
|
||||||
|
// UI updates without requiring @Push. The upload response is written only
|
||||||
|
// after the handler's UI.access task has applied its changes to the state
|
||||||
|
// tree, so by the time a fetch settles those changes are guaranteed to be
|
||||||
|
// picked up by this round trip.
|
||||||
|
Promise.allSettled(uploads).then(() => {
|
||||||
|
element.dispatchEvent(new CustomEvent('vaadin-paste-upload-finished'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const $wnd = window;
|
||||||
|
$wnd.Vaadin ??= {};
|
||||||
|
$wnd.Vaadin.Flow ??= {};
|
||||||
|
$wnd.Vaadin.Flow.clipboard = {
|
||||||
|
readPayload: readClipboardPayload,
|
||||||
|
writePayload: writeClipboardPayload,
|
||||||
|
uploadPastedFiles: uploadPastedFiles
|
||||||
|
};
|
||||||
|
export {};
|
||||||
|
//# sourceMappingURL=Clipboard.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Triggers a file download from the given URL using the standard
|
||||||
|
* <a href download> click pattern.
|
||||||
|
*
|
||||||
|
* The anchor is synthesised, clicked synchronously inside the caller's
|
||||||
|
* gesture context, and removed. The browser then either navigates to the
|
||||||
|
* URL (server responds with Content-Disposition: attachment) or saves the
|
||||||
|
* resource directly when the download attribute applies.
|
||||||
|
*
|
||||||
|
* The {@code download} attribute is honoured only for same-origin URLs;
|
||||||
|
* cross-origin responses must set Content-Disposition themselves for the
|
||||||
|
* filename to take effect.
|
||||||
|
*/
|
||||||
|
function startDownload(url, filename) {
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
// Always set `download` so the browser saves the response rather than
|
||||||
|
// navigating to it. Empty value lets the browser pick the filename from
|
||||||
|
// Content-Disposition or the URL pathname; a non-empty value is the
|
||||||
|
// suggested filename (honoured only same-origin). Cross-origin responses
|
||||||
|
// without Content-Disposition: attachment still navigate — that's a
|
||||||
|
// server-side concern this client helper can't override.
|
||||||
|
a.download = filename ?? '';
|
||||||
|
// Opt out of Vaadin's client-side router so the click reaches the
|
||||||
|
// browser's native download handling instead of being intercepted as an
|
||||||
|
// in-app navigation. Matches Anchor.setHref(DownloadHandler).
|
||||||
|
a.setAttribute('router-ignore', '');
|
||||||
|
// Hidden but in the document — some browsers ignore clicks on detached
|
||||||
|
// anchors.
|
||||||
|
a.style.display = 'none';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
}
|
||||||
|
const $wnd = window;
|
||||||
|
$wnd.Vaadin ??= {};
|
||||||
|
$wnd.Vaadin.Flow ??= {};
|
||||||
|
$wnd.Vaadin.Flow.download = {
|
||||||
|
start: startDownload
|
||||||
|
};
|
||||||
|
export {};
|
||||||
|
//# sourceMappingURL=Download.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"Download.js","sourceRoot":"","sources":["../../../../src/main/frontend/Download.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;;;;GAYG;AACH,SAAS,aAAa,CAAC,GAAW,EAAE,QAAiB;IACnD,MAAM,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACtC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC;IACb,sEAAsE;IACtE,wEAAwE;IACxE,oEAAoE;IACpE,yEAAyE;IACzE,oEAAoE;IACpE,yDAAyD;IACzD,CAAC,CAAC,QAAQ,GAAG,QAAQ,IAAI,EAAE,CAAC;IAC5B,kEAAkE;IAClE,wEAAwE;IACxE,8DAA8D;IAC9D,CAAC,CAAC,YAAY,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;IACpC,uEAAuE;IACvE,WAAW;IACX,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC,CAAC,KAAK,EAAE,CAAC;IACV,CAAC,CAAC,MAAM,EAAE,CAAC;AACb,CAAC;AAED,MAAM,IAAI,GAAG,MAAa,CAAC;AAC3B,IAAI,CAAC,MAAM,KAAK,EAAE,CAAC;AACnB,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;AACxB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,GAAG;IAC1B,KAAK,EAAE,aAAa;CACrB,CAAC","sourcesContent":["/*\n * Copyright 2000-2026 Vaadin Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\n/**\n * Triggers a file download from the given URL using the standard\n * <a href download> click pattern.\n *\n * The anchor is synthesised, clicked synchronously inside the caller's\n * gesture context, and removed. The browser then either navigates to the\n * URL (server responds with Content-Disposition: attachment) or saves the\n * resource directly when the download attribute applies.\n *\n * The {@code download} attribute is honoured only for same-origin URLs;\n * cross-origin responses must set Content-Disposition themselves for the\n * filename to take effect.\n */\nfunction startDownload(url: string, filename?: string): void {\n const a = document.createElement('a');\n a.href = url;\n // Always set `download` so the browser saves the response rather than\n // navigating to it. Empty value lets the browser pick the filename from\n // Content-Disposition or the URL pathname; a non-empty value is the\n // suggested filename (honoured only same-origin). Cross-origin responses\n // without Content-Disposition: attachment still navigate — that's a\n // server-side concern this client helper can't override.\n a.download = filename ?? '';\n // Opt out of Vaadin's client-side router so the click reaches the\n // browser's native download handling instead of being intercepted as an\n // in-app navigation. Matches Anchor.setHref(DownloadHandler).\n a.setAttribute('router-ignore', '');\n // Hidden but in the document — some browsers ignore clicks on detached\n // anchors.\n a.style.display = 'none';\n document.body.appendChild(a);\n a.click();\n a.remove();\n}\n\nconst $wnd = window as any;\n$wnd.Vaadin ??= {};\n$wnd.Vaadin.Flow ??= {};\n$wnd.Vaadin.Flow.download = {\n start: startDownload\n};\n\n// Empty export to ensure TypeScript emits this as an ES module,\n// which is required for Vite to load it via import.\nexport {};\n"]}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
const $wnd = window;
|
||||||
|
$wnd.Vaadin ??= {};
|
||||||
|
$wnd.Vaadin.Flow ??= {};
|
||||||
|
$wnd.Vaadin.Flow.elementResize = {
|
||||||
|
/**
|
||||||
|
* Installs a ResizeObserver on the given element and invokes the callback
|
||||||
|
* with the rounded content-box width and height each time the element
|
||||||
|
* resizes. Returns a function that disconnects the observer.
|
||||||
|
*
|
||||||
|
* Sub-pixel decimals from contentRect are rounded to integers to avoid
|
||||||
|
* spamming equal-after-rounding updates back to the server.
|
||||||
|
*/
|
||||||
|
observe(element, callback) {
|
||||||
|
const observer = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.target.isConnected) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
callback({
|
||||||
|
width: Math.round(entry.contentRect.width),
|
||||||
|
height: Math.round(entry.contentRect.height)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
observer.observe(element);
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export {};
|
||||||
|
//# sourceMappingURL=ElementResize.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"ElementResize.js","sourceRoot":"","sources":["../../../../src/main/frontend/ElementResize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAYH,MAAM,IAAI,GAAG,MAAa,CAAC;AAC3B,IAAI,CAAC,MAAM,KAAK,EAAE,CAAC;AACnB,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;AACxB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,GAAG;IAC/B;;;;;;;OAOG;IACH,OAAO,CAAC,OAAgB,EAAE,QAA8B;QACtD,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,CAAC,OAAO,EAAE,EAAE;YAC9C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;oBAC9B,SAAS;gBACX,CAAC;gBACD,QAAQ,CAAC;oBACP,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC;oBAC1C,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC;iBAC7C,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC1B,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;IACrC,CAAC;CACF,CAAC","sourcesContent":["/*\n * Copyright 2000-2026 Vaadin Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\n/**\n * Size data passed to the observe() callback. Field names match the Java\n * Size record so the value can be Jackson-deserialised on the server when\n * forwarded through a trigger-framework input.\n */\ninterface Size {\n width: number;\n height: number;\n}\n\nconst $wnd = window as any;\n$wnd.Vaadin ??= {};\n$wnd.Vaadin.Flow ??= {};\n$wnd.Vaadin.Flow.elementResize = {\n /**\n * Installs a ResizeObserver on the given element and invokes the callback\n * with the rounded content-box width and height each time the element\n * resizes. Returns a function that disconnects the observer.\n *\n * Sub-pixel decimals from contentRect are rounded to integers to avoid\n * spamming equal-after-rounding updates back to the server.\n */\n observe(element: Element, callback: (size: Size) => void): () => void {\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n if (!entry.target.isConnected) {\n continue;\n }\n callback({\n width: Math.round(entry.contentRect.width),\n height: Math.round(entry.contentRect.height)\n });\n }\n });\n observer.observe(element);\n return () => observer.disconnect();\n }\n};\n\n// Empty export to ensure TypeScript emits this as an ES module,\n// which is required for Vite to load it via import.\nexport {};\n"]}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import './Clipboard';
|
||||||
|
import './Download';
|
||||||
|
import './ElementResize';
|
||||||
|
import './Geolocation';
|
||||||
|
import './WakeLock';
|
||||||
|
export interface FlowConfig {
|
||||||
|
imports?: () => Promise<any>;
|
||||||
|
}
|
||||||
|
interface AppConfig {
|
||||||
|
productionMode: boolean;
|
||||||
|
appId: string;
|
||||||
|
uidl: any;
|
||||||
|
}
|
||||||
|
interface AppInitResponse {
|
||||||
|
appConfig: AppConfig;
|
||||||
|
pushScript?: string;
|
||||||
|
}
|
||||||
|
interface Router {
|
||||||
|
render: (ctx: NavigationParameters, shouldUpdateHistory: boolean) => Promise<void>;
|
||||||
|
}
|
||||||
|
interface HTMLRouterContainer extends HTMLElement {
|
||||||
|
onBeforeEnter?: (ctx: NavigationParameters, cmd: PreventAndRedirectCommands, router: Router) => void | Promise<any>;
|
||||||
|
onBeforeLeave?: (ctx: NavigationParameters, cmd: PreventCommands, router: Router) => void | Promise<any>;
|
||||||
|
serverConnected?: (cancel: boolean, url?: NavigationParameters) => void;
|
||||||
|
serverPaused?: () => void;
|
||||||
|
}
|
||||||
|
interface FlowRoute {
|
||||||
|
action: (params: NavigationParameters) => Promise<HTMLRouterContainer>;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
export interface NavigationParameters {
|
||||||
|
pathname: string;
|
||||||
|
search?: string;
|
||||||
|
}
|
||||||
|
export interface PreventCommands {
|
||||||
|
prevent: () => any;
|
||||||
|
continue?: () => any;
|
||||||
|
}
|
||||||
|
export interface PreventAndRedirectCommands extends PreventCommands {
|
||||||
|
redirect: (route: string) => any;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Client API for flow UI operations.
|
||||||
|
*/
|
||||||
|
export declare class Flow {
|
||||||
|
config: FlowConfig;
|
||||||
|
response?: AppInitResponse;
|
||||||
|
pathname: string;
|
||||||
|
container: HTMLRouterContainer;
|
||||||
|
private isActive;
|
||||||
|
private baseRegex;
|
||||||
|
private appShellTitle;
|
||||||
|
private navigation;
|
||||||
|
constructor(config?: FlowConfig);
|
||||||
|
/**
|
||||||
|
* Return a `route` object for vaadin-router in an one-element array.
|
||||||
|
*
|
||||||
|
* The `FlowRoute` object `path` property handles any route,
|
||||||
|
* and the `action` returns the flow container without updating the content,
|
||||||
|
* delaying the actual Flow server call to the `onBeforeEnter` phase.
|
||||||
|
*
|
||||||
|
* This is a specific API for its use with `vaadin-router`.
|
||||||
|
*/
|
||||||
|
get serverSideRoutes(): [FlowRoute];
|
||||||
|
loadingStarted(): void;
|
||||||
|
loadingFinished(): void;
|
||||||
|
private get action();
|
||||||
|
private flowLeave;
|
||||||
|
private flowNavigate;
|
||||||
|
private getFlowRoutePath;
|
||||||
|
private getFlowRouteQuery;
|
||||||
|
private flowInit;
|
||||||
|
private loadScript;
|
||||||
|
private findNonce;
|
||||||
|
private injectAppIdScript;
|
||||||
|
private flowInitClient;
|
||||||
|
private flowInitUi;
|
||||||
|
private collectBrowserDetails;
|
||||||
|
private addConnectionIndicator;
|
||||||
|
private offlineStubAction;
|
||||||
|
private isFlowClientLoaded;
|
||||||
|
}
|
||||||
|
export {};
|
||||||
@@ -0,0 +1,538 @@
|
|||||||
|
import { ConnectionIndicator, ConnectionState } from '@vaadin/common-frontend';
|
||||||
|
import './Clipboard';
|
||||||
|
import { currentFullscreenState } from './Fullscreen';
|
||||||
|
import './Download';
|
||||||
|
import './ElementResize';
|
||||||
|
import './Geolocation';
|
||||||
|
import { currentVisibility } from './PageVisibility';
|
||||||
|
import { currentScreenOrientationAngle, currentScreenOrientationType } from './ScreenOrientation';
|
||||||
|
import './WakeLock';
|
||||||
|
import { isShareSupported } from './WebShare';
|
||||||
|
class FlowUiInitializationError extends Error {
|
||||||
|
}
|
||||||
|
// flow uses body for keeping references
|
||||||
|
const flowRoot = window.document.body;
|
||||||
|
const $wnd = window;
|
||||||
|
const ROOT_NODE_ID = 1; // See StateTree.java
|
||||||
|
function getClients() {
|
||||||
|
return Object.keys($wnd.Vaadin.Flow.clients)
|
||||||
|
.filter((key) => key !== 'TypeScript')
|
||||||
|
.map((id) => $wnd.Vaadin.Flow.clients[id]);
|
||||||
|
}
|
||||||
|
function sendEvent(eventName, data) {
|
||||||
|
getClients().forEach((client) => client.sendEventMessage(ROOT_NODE_ID, eventName, data));
|
||||||
|
}
|
||||||
|
// In the future could be replaced with RegExp.escape()
|
||||||
|
function escapeRegExp(pattern) {
|
||||||
|
return pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Client API for flow UI operations.
|
||||||
|
*/
|
||||||
|
export class Flow {
|
||||||
|
config;
|
||||||
|
response = undefined;
|
||||||
|
pathname = '';
|
||||||
|
container;
|
||||||
|
// flag used to inform Testbench whether a server route is in progress
|
||||||
|
isActive = false;
|
||||||
|
baseRegex = /^\//;
|
||||||
|
appShellTitle;
|
||||||
|
navigation = '';
|
||||||
|
constructor(config) {
|
||||||
|
// Set window.name early so @PreserveOnRefresh can use it to identify the browser tab
|
||||||
|
// Only set if not already set to preserve any existing value
|
||||||
|
if (!window.name) {
|
||||||
|
window.name = `v-${Math.random()}`;
|
||||||
|
}
|
||||||
|
flowRoot.$ = flowRoot.$ || [];
|
||||||
|
this.config = config || {};
|
||||||
|
// TB checks for the existence of window.Vaadin.Flow in order
|
||||||
|
// to consider that TB needs to wait for `initFlow()`.
|
||||||
|
$wnd.Vaadin = $wnd.Vaadin || {};
|
||||||
|
$wnd.Vaadin.Flow = $wnd.Vaadin.Flow || {};
|
||||||
|
$wnd.Vaadin.Flow.clients = {
|
||||||
|
TypeScript: {
|
||||||
|
isActive: () => this.isActive
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Set browser details collection function as global for use by refresh()
|
||||||
|
$wnd.Vaadin.Flow.getBrowserDetailsParameters = this.collectBrowserDetails.bind(this);
|
||||||
|
// Regular expression used to remove the app-context
|
||||||
|
const elm = document.head.querySelector('base');
|
||||||
|
this.baseRegex = new RegExp(`^${
|
||||||
|
// IE11 does not support document.baseURI
|
||||||
|
escapeRegExp((document.baseURI || (elm && elm.href) || '/').replace(/^https?:\/\/[^/]+/i, ''))}`);
|
||||||
|
this.appShellTitle = document.title;
|
||||||
|
// Put a vaadin-connection-indicator in the dom
|
||||||
|
this.addConnectionIndicator();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return a `route` object for vaadin-router in an one-element array.
|
||||||
|
*
|
||||||
|
* The `FlowRoute` object `path` property handles any route,
|
||||||
|
* and the `action` returns the flow container without updating the content,
|
||||||
|
* delaying the actual Flow server call to the `onBeforeEnter` phase.
|
||||||
|
*
|
||||||
|
* This is a specific API for its use with `vaadin-router`.
|
||||||
|
*/
|
||||||
|
get serverSideRoutes() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
path: '(.*)',
|
||||||
|
action: this.action
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
loadingStarted() {
|
||||||
|
// Make Testbench know that server request is in progress
|
||||||
|
this.isActive = true;
|
||||||
|
$wnd.Vaadin.connectionState.loadingStarted();
|
||||||
|
}
|
||||||
|
loadingFinished() {
|
||||||
|
// Make Testbench know that server request has finished
|
||||||
|
this.isActive = false;
|
||||||
|
$wnd.Vaadin.connectionState.loadingFinished();
|
||||||
|
if ($wnd.Vaadin.listener) {
|
||||||
|
// Listeners registered, do not register again.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$wnd.Vaadin.listener = {};
|
||||||
|
// Listen for click on router-links -> 'link' navigation trigger
|
||||||
|
// and on <a> nodes -> 'client' navigation trigger.
|
||||||
|
// Use capture phase to detect prevented / stopped events.
|
||||||
|
document.addEventListener('click', (_e) => {
|
||||||
|
if (_e.target) {
|
||||||
|
if (_e.composedPath().some((node) => node instanceof HTMLElement && node.hasAttribute('router-link'))) {
|
||||||
|
this.navigation = 'link';
|
||||||
|
}
|
||||||
|
else if (_e.composedPath().some((node) => node.nodeName === 'A')) {
|
||||||
|
this.navigation = 'client';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
capture: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
get action() {
|
||||||
|
// Return a function which is bound to the flow instance, thus we can use
|
||||||
|
// the syntax `...serverSideRoutes` in vaadin-router.
|
||||||
|
return async (params) => {
|
||||||
|
// Store last action pathname so as we can check it in events
|
||||||
|
this.pathname = params.pathname;
|
||||||
|
if ($wnd.Vaadin.connectionState.online) {
|
||||||
|
try {
|
||||||
|
await this.flowInit();
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error instanceof FlowUiInitializationError) {
|
||||||
|
// error initializing Flow: assume connection lost
|
||||||
|
$wnd.Vaadin.connectionState.state = ConnectionState.CONNECTION_LOST;
|
||||||
|
return this.offlineStubAction();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// insert an offline stub
|
||||||
|
return this.offlineStubAction();
|
||||||
|
}
|
||||||
|
// When an action happens, navigation will be resolved `onBeforeEnter`
|
||||||
|
this.container.onBeforeEnter = (ctx, cmd) => this.flowNavigate(ctx, cmd);
|
||||||
|
// For covering the 'server -> client' use case
|
||||||
|
this.container.onBeforeLeave = (ctx, cmd) => this.flowLeave(ctx, cmd);
|
||||||
|
return this.container;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Send a remote call to `JavaScriptBootstrapUI` to check
|
||||||
|
// whether navigation has to be cancelled.
|
||||||
|
async flowLeave(ctx, cmd) {
|
||||||
|
// server -> server, viewing offline stub, or browser is offline
|
||||||
|
const { connectionState } = $wnd.Vaadin;
|
||||||
|
if (this.pathname === ctx.pathname || !this.isFlowClientLoaded() || connectionState.offline) {
|
||||||
|
return Promise.resolve({});
|
||||||
|
}
|
||||||
|
// 'server -> client'
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.loadingStarted();
|
||||||
|
// The callback to run from server side to cancel navigation
|
||||||
|
this.container.serverConnected = (cancel) => {
|
||||||
|
resolve(cmd && cancel ? cmd.prevent() : cmd?.continue?.());
|
||||||
|
this.loadingFinished();
|
||||||
|
};
|
||||||
|
// Call server side to check whether we can leave the view
|
||||||
|
sendEvent('ui-leave-navigation', { route: this.getFlowRoutePath(ctx), query: this.getFlowRouteQuery(ctx) });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Send the remote call to `UI` to render the flow
|
||||||
|
// route specified by the context
|
||||||
|
async flowNavigate(ctx, cmd) {
|
||||||
|
if (this.response) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.loadingStarted();
|
||||||
|
// The callback to run from server side once the view is ready
|
||||||
|
this.container.serverConnected = (cancel, redirectContext) => {
|
||||||
|
if (cmd && cancel) {
|
||||||
|
resolve(cmd.prevent());
|
||||||
|
}
|
||||||
|
else if (cmd && cmd.redirect && redirectContext) {
|
||||||
|
resolve(cmd.redirect(redirectContext.pathname));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cmd?.continue?.();
|
||||||
|
this.container.style.display = '';
|
||||||
|
resolve(this.container);
|
||||||
|
}
|
||||||
|
this.loadingFinished();
|
||||||
|
};
|
||||||
|
this.container.serverPaused = () => {
|
||||||
|
this.loadingFinished();
|
||||||
|
};
|
||||||
|
// Call server side to navigate to the given route
|
||||||
|
sendEvent('ui-navigate', {
|
||||||
|
route: this.getFlowRoutePath(ctx),
|
||||||
|
query: this.getFlowRouteQuery(ctx),
|
||||||
|
appShellTitle: this.appShellTitle,
|
||||||
|
historyState: history.state,
|
||||||
|
trigger: this.navigation
|
||||||
|
});
|
||||||
|
// Default to history navigation trigger.
|
||||||
|
// Link and client cases are handled by click listener in loadingFinished().
|
||||||
|
this.navigation = 'history';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// No server response => offline or erroneous connection
|
||||||
|
return Promise.resolve(this.container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getFlowRoutePath(context) {
|
||||||
|
// Don't decode the pathname here - let the server handle decoding
|
||||||
|
// individual path segments. This preserves the distinction between
|
||||||
|
// literal slashes (path separators) and encoded slashes (%2F, data).
|
||||||
|
return context.pathname.replace(this.baseRegex, '');
|
||||||
|
}
|
||||||
|
getFlowRouteQuery(context) {
|
||||||
|
return (context.search && context.search.substring(1)) || '';
|
||||||
|
}
|
||||||
|
// import flow client modules and initialize UI in server side.
|
||||||
|
async flowInit() {
|
||||||
|
// Do not start flow twice
|
||||||
|
if (!this.isFlowClientLoaded()) {
|
||||||
|
$wnd.Vaadin.Flow.nonce = this.findNonce();
|
||||||
|
// show flow progress indicator
|
||||||
|
this.loadingStarted();
|
||||||
|
// Initialize server side UI
|
||||||
|
this.response = await this.flowInitUi();
|
||||||
|
const { pushScript, appConfig } = this.response;
|
||||||
|
if (typeof pushScript === 'string') {
|
||||||
|
await this.loadScript(pushScript);
|
||||||
|
}
|
||||||
|
const { appId } = appConfig;
|
||||||
|
// we use a custom tag for the flow app container
|
||||||
|
// This must be created before bootstrapMod.init is called as that call
|
||||||
|
// can handle a UIDL from the server, which relies on the container being available
|
||||||
|
const tag = `flow-container-${appId.toLowerCase()}`;
|
||||||
|
const serverCreatedContainer = document.querySelector(tag);
|
||||||
|
if (serverCreatedContainer) {
|
||||||
|
this.container = serverCreatedContainer;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.container = document.createElement(tag);
|
||||||
|
this.container.id = appId;
|
||||||
|
}
|
||||||
|
flowRoot.$[appId] = this.container;
|
||||||
|
// Load bootstrap script with server side parameters
|
||||||
|
const bootstrapMod = await import('./FlowBootstrap');
|
||||||
|
bootstrapMod.init(this.response);
|
||||||
|
// Load custom modules defined by user
|
||||||
|
if (typeof this.config.imports === 'function') {
|
||||||
|
this.injectAppIdScript(appId);
|
||||||
|
await this.config.imports();
|
||||||
|
}
|
||||||
|
// Load flow-client module
|
||||||
|
const clientMod = await import('./FlowClient');
|
||||||
|
await this.flowInitClient(clientMod);
|
||||||
|
// hide flow progress indicator
|
||||||
|
this.loadingFinished();
|
||||||
|
}
|
||||||
|
// It might be that components created from server expect that their content has been rendered.
|
||||||
|
// Appending eagerly the container we avoid these kind of errors.
|
||||||
|
// Note that the client router will move this container to the outlet if the navigation succeed
|
||||||
|
if (this.container && !this.container.isConnected) {
|
||||||
|
this.container.style.display = 'none';
|
||||||
|
document.body.appendChild(this.container);
|
||||||
|
}
|
||||||
|
return this.response;
|
||||||
|
}
|
||||||
|
async loadScript(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.onload = () => resolve();
|
||||||
|
script.onerror = reject;
|
||||||
|
script.src = url;
|
||||||
|
const { nonce } = $wnd.Vaadin.Flow;
|
||||||
|
if (nonce !== undefined) {
|
||||||
|
script.setAttribute('nonce', nonce);
|
||||||
|
}
|
||||||
|
document.body.appendChild(script);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
findNonce() {
|
||||||
|
let nonce;
|
||||||
|
const scriptTags = document.head.getElementsByTagName('script');
|
||||||
|
for (const scriptTag of scriptTags) {
|
||||||
|
if (scriptTag.nonce) {
|
||||||
|
nonce = scriptTag.nonce;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nonce;
|
||||||
|
}
|
||||||
|
injectAppIdScript(appId) {
|
||||||
|
const appIdWithoutHashCode = appId.substring(0, appId.lastIndexOf('-'));
|
||||||
|
const scriptAppId = document.createElement('script');
|
||||||
|
scriptAppId.type = 'module';
|
||||||
|
scriptAppId.setAttribute('data-app-id', appIdWithoutHashCode);
|
||||||
|
const { nonce } = $wnd.Vaadin.Flow;
|
||||||
|
if (nonce !== undefined) {
|
||||||
|
scriptAppId.setAttribute('nonce', nonce);
|
||||||
|
}
|
||||||
|
document.body.append(scriptAppId);
|
||||||
|
}
|
||||||
|
// After the flow-client javascript module has been loaded, this initializes flow UI
|
||||||
|
// in the browser.
|
||||||
|
async flowInitClient(clientMod) {
|
||||||
|
clientMod.init();
|
||||||
|
// client init is async, we need to loop until initialized
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const intervalId = setInterval(() => {
|
||||||
|
// client `isActive() == true` while initializing or processing
|
||||||
|
const initializing = getClients().reduce((prev, client) => prev || client.isActive(), false);
|
||||||
|
if (!initializing) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}, 5);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Returns the `appConfig` object
|
||||||
|
async flowInitUi() {
|
||||||
|
// appConfig was sent in the index.html request
|
||||||
|
const initial = $wnd.Vaadin && $wnd.Vaadin.TypeScript && $wnd.Vaadin.TypeScript.initial;
|
||||||
|
if (initial) {
|
||||||
|
$wnd.Vaadin.TypeScript.initial = undefined;
|
||||||
|
return Promise.resolve(initial);
|
||||||
|
}
|
||||||
|
const browserDetails = await this.collectBrowserDetails();
|
||||||
|
// send a request to the `JavaScriptBootstrapHandler`
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
const httpRequest = xhr;
|
||||||
|
// Browser details are appended as individual query parameters rather
|
||||||
|
// than as a single JSON-encoded value. A JSON payload in the URL
|
||||||
|
// produces many percent-encoded escape sequences (%7B, %22, %3A, ...)
|
||||||
|
// that some firewalls/WAFs (e.g. Sophos) flag and block, which would
|
||||||
|
// fail the bootstrap on the very first page load. Plain key=value pairs
|
||||||
|
// avoid that pattern entirely.
|
||||||
|
const browserDetailsParams = browserDetails
|
||||||
|
? Object.entries(browserDetails)
|
||||||
|
.map(([key, value]) => `&${key}=${encodeURIComponent(value)}`)
|
||||||
|
.join('')
|
||||||
|
: '';
|
||||||
|
const requestPath = `?v-r=init&location=${encodeURIComponent(this.getFlowRoutePath(location))}&query=${encodeURIComponent(this.getFlowRouteQuery(location))}${browserDetailsParams}`;
|
||||||
|
httpRequest.open('GET', requestPath);
|
||||||
|
httpRequest.onerror = () => reject(new FlowUiInitializationError(`Invalid server response when initializing Flow UI.
|
||||||
|
${httpRequest.status}
|
||||||
|
${httpRequest.responseText}`));
|
||||||
|
httpRequest.onload = () => {
|
||||||
|
const contentType = httpRequest.getResponseHeader('content-type');
|
||||||
|
if (contentType && contentType.indexOf('application/json') !== -1) {
|
||||||
|
resolve(JSON.parse(httpRequest.responseText));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
httpRequest.onerror();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
httpRequest.send();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Collects browser details parameters
|
||||||
|
async collectBrowserDetails() {
|
||||||
|
const params = {};
|
||||||
|
/* Screen height and width */
|
||||||
|
params['v-sh'] = $wnd.screen.height;
|
||||||
|
params['v-sw'] = $wnd.screen.width;
|
||||||
|
/* Browser window dimensions */
|
||||||
|
params['v-wh'] = $wnd.innerHeight;
|
||||||
|
params['v-ww'] = $wnd.innerWidth;
|
||||||
|
/* Body element dimensions */
|
||||||
|
params['v-bh'] = $wnd.document.body.clientHeight;
|
||||||
|
params['v-bw'] = $wnd.document.body.clientWidth;
|
||||||
|
/* Current time */
|
||||||
|
const date = new Date();
|
||||||
|
params['v-curdate'] = date.getTime();
|
||||||
|
/* Current timezone offset (including DST shift) */
|
||||||
|
const tzo1 = date.getTimezoneOffset();
|
||||||
|
/* Compare the current tz offset with the first offset from the end
|
||||||
|
of the year that differs --- if less that, we are in DST, otherwise
|
||||||
|
we are in normal time */
|
||||||
|
let dstDiff = 0;
|
||||||
|
let rawTzo = tzo1;
|
||||||
|
for (let m = 12; m > 0; m -= 1) {
|
||||||
|
date.setUTCMonth(m);
|
||||||
|
const tzo2 = date.getTimezoneOffset();
|
||||||
|
if (tzo1 !== tzo2) {
|
||||||
|
dstDiff = tzo1 > tzo2 ? tzo1 - tzo2 : tzo2 - tzo1;
|
||||||
|
rawTzo = tzo1 > tzo2 ? tzo1 : tzo2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Time zone offset */
|
||||||
|
params['v-tzo'] = tzo1;
|
||||||
|
/* DST difference */
|
||||||
|
params['v-dstd'] = dstDiff;
|
||||||
|
/* Time zone offset without DST */
|
||||||
|
params['v-rtzo'] = rawTzo;
|
||||||
|
/* DST in effect? */
|
||||||
|
params['v-dston'] = tzo1 !== rawTzo;
|
||||||
|
/* Time zone id (if available) */
|
||||||
|
try {
|
||||||
|
params['v-tzid'] = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
params['v-tzid'] = '';
|
||||||
|
}
|
||||||
|
/* Window name */
|
||||||
|
if ($wnd.name) {
|
||||||
|
params['v-wn'] = $wnd.name;
|
||||||
|
}
|
||||||
|
/* Detect touch device support */
|
||||||
|
let supportsTouch = false;
|
||||||
|
try {
|
||||||
|
$wnd.document.createEvent('TouchEvent');
|
||||||
|
supportsTouch = true;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
/* Chrome and IE10 touch detection */
|
||||||
|
supportsTouch = 'ontouchstart' in $wnd || typeof $wnd.navigator.msMaxTouchPoints !== 'undefined';
|
||||||
|
}
|
||||||
|
params['v-td'] = supportsTouch;
|
||||||
|
/* Device Pixel Ratio */
|
||||||
|
params['v-pr'] = $wnd.devicePixelRatio;
|
||||||
|
if ($wnd.navigator.platform) {
|
||||||
|
params['v-np'] = $wnd.navigator.platform;
|
||||||
|
}
|
||||||
|
/* Color scheme from CSS color-scheme property */
|
||||||
|
const colorScheme = getComputedStyle(document.documentElement).colorScheme.trim();
|
||||||
|
// "normal" is the default value and means no color scheme is set
|
||||||
|
params['v-cs'] = colorScheme && colorScheme !== 'normal' ? colorScheme : '';
|
||||||
|
/* Page visibility — initial state of document.hidden / document.hasFocus() */
|
||||||
|
params['v-pv'] = currentVisibility();
|
||||||
|
/* Fullscreen state — initial state of document.fullscreenEnabled / .fullscreenElement */
|
||||||
|
params['v-fs'] = currentFullscreenState();
|
||||||
|
/* Screen orientation — initial state of screen.orientation, empty
|
||||||
|
when the Screen Orientation API is unavailable. */
|
||||||
|
params['v-so'] = currentScreenOrientationType();
|
||||||
|
params['v-soa'] = currentScreenOrientationAngle();
|
||||||
|
/* Theme name - detect which theme is in use */
|
||||||
|
const computedStyle = getComputedStyle(document.documentElement);
|
||||||
|
let themeName = '';
|
||||||
|
if (computedStyle.getPropertyValue('--vaadin-lumo-theme').trim()) {
|
||||||
|
themeName = 'lumo';
|
||||||
|
}
|
||||||
|
else if (computedStyle.getPropertyValue('--vaadin-aura-theme').trim()) {
|
||||||
|
themeName = 'aura';
|
||||||
|
}
|
||||||
|
params['v-tn'] = themeName;
|
||||||
|
/* Geolocation availability — guarded because tests may reset
|
||||||
|
window.Vaadin between runs, removing the namespace that
|
||||||
|
Geolocation.ts installs at import time. */
|
||||||
|
const geolocation = $wnd.Vaadin.Flow?.geolocation;
|
||||||
|
if (geolocation) {
|
||||||
|
params['v-ga'] = await geolocation.queryAvailability();
|
||||||
|
}
|
||||||
|
/* Wake-lock availability — same guard rationale as geolocation. */
|
||||||
|
const wakeLock = $wnd.Vaadin.Flow?.wakeLock;
|
||||||
|
if (wakeLock) {
|
||||||
|
params['v-wla'] = wakeLock.queryAvailability();
|
||||||
|
}
|
||||||
|
/* Web Share API support */
|
||||||
|
params['v-ws'] = isShareSupported();
|
||||||
|
/* Stringify each value (they are parsed on the server side) */
|
||||||
|
const stringParams = {};
|
||||||
|
Object.keys(params).forEach((key) => {
|
||||||
|
const value = params[key];
|
||||||
|
if (typeof value !== 'undefined') {
|
||||||
|
stringParams[key] = value.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return stringParams;
|
||||||
|
}
|
||||||
|
// Create shared connection state store and connection indicator
|
||||||
|
addConnectionIndicator() {
|
||||||
|
// add connection indicator to DOM
|
||||||
|
ConnectionIndicator.create();
|
||||||
|
// Listen to browser online/offline events and update the loading indicator accordingly.
|
||||||
|
// Note: if flow-client is loaded, it instead handles the state transitions.
|
||||||
|
$wnd.addEventListener('online', () => {
|
||||||
|
if (!this.isFlowClientLoaded()) {
|
||||||
|
// Send an HTTP HEAD request for sw.js to verify server reachability.
|
||||||
|
// We do not expect sw.js to be cached, so the request goes to the
|
||||||
|
// server rather than being served from local cache.
|
||||||
|
// Require network-level failure to revert the state to CONNECTION_LOST
|
||||||
|
// (HTTP error code is ok since it still verifies server's presence).
|
||||||
|
$wnd.Vaadin.connectionState.state = ConnectionState.RECONNECTING;
|
||||||
|
const http = new XMLHttpRequest();
|
||||||
|
http.open('HEAD', 'sw.js');
|
||||||
|
http.onload = () => {
|
||||||
|
$wnd.Vaadin.connectionState.state = ConnectionState.CONNECTED;
|
||||||
|
};
|
||||||
|
http.onerror = () => {
|
||||||
|
$wnd.Vaadin.connectionState.state = ConnectionState.CONNECTION_LOST;
|
||||||
|
};
|
||||||
|
// Postpone request to reduce potential net::ERR_INTERNET_DISCONNECTED
|
||||||
|
// errors that sometimes occurs even if browser says it is online
|
||||||
|
setTimeout(() => http.send(), 50);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$wnd.addEventListener('offline', () => {
|
||||||
|
if (!this.isFlowClientLoaded()) {
|
||||||
|
$wnd.Vaadin.connectionState.state = ConnectionState.CONNECTION_LOST;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async offlineStubAction() {
|
||||||
|
const offlineStub = document.createElement('iframe');
|
||||||
|
const offlineStubPath = './offline-stub.html';
|
||||||
|
offlineStub.setAttribute('src', offlineStubPath);
|
||||||
|
offlineStub.setAttribute('style', 'width: 100%; height: 100%; border: 0');
|
||||||
|
this.response = undefined;
|
||||||
|
let onlineListener;
|
||||||
|
const removeOfflineStubAndOnlineListener = () => {
|
||||||
|
if (onlineListener !== undefined) {
|
||||||
|
$wnd.Vaadin.connectionState.removeStateChangeListener(onlineListener);
|
||||||
|
onlineListener = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
offlineStub.onBeforeEnter = (ctx, _cmds, router) => {
|
||||||
|
onlineListener = () => {
|
||||||
|
if ($wnd.Vaadin.connectionState.online) {
|
||||||
|
removeOfflineStubAndOnlineListener();
|
||||||
|
router.render(ctx, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$wnd.Vaadin.connectionState.addStateChangeListener(onlineListener);
|
||||||
|
};
|
||||||
|
offlineStub.onBeforeLeave = (_ctx, _cmds, _router) => {
|
||||||
|
removeOfflineStubAndOnlineListener();
|
||||||
|
};
|
||||||
|
return offlineStub;
|
||||||
|
}
|
||||||
|
isFlowClientLoaded() {
|
||||||
|
return this.response !== undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=Flow.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
export const init: (appInitResponse: any) => void;
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
/* This is a copy of the regular `BootstrapHandler.js` in the flow-server
|
||||||
|
module, but with the following modifications:
|
||||||
|
- The main function is exported as an ES module for lazy initialization.
|
||||||
|
- Application configuration is passed as a parameter instead of using
|
||||||
|
replacement placeholders as in the regular bootstrapping.
|
||||||
|
- It reuses `Vaadin.Flow.clients` if exists.
|
||||||
|
- Fixed lint errors.
|
||||||
|
*/
|
||||||
|
const init = function (appInitResponse) {
|
||||||
|
window.Vaadin = window.Vaadin || {};
|
||||||
|
window.Vaadin.Flow = window.Vaadin.Flow || {};
|
||||||
|
|
||||||
|
var apps = {};
|
||||||
|
var widgetsets = {};
|
||||||
|
|
||||||
|
var log;
|
||||||
|
if (typeof window.console === undefined || !window.location.search.match(/[&?]debug(&|$)/)) {
|
||||||
|
/* If no console.log present, just use a no-op */
|
||||||
|
log = function () {};
|
||||||
|
} else if (typeof window.console.log === 'function') {
|
||||||
|
/* If it's a function, use it with apply */
|
||||||
|
log = function () {
|
||||||
|
window.console.log.apply(window.console, arguments);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
/* In IE, its a native function for which apply is not defined, but it works
|
||||||
|
without a proper 'this' reference */
|
||||||
|
log = window.console.log;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isInitializedInDom = function (appId) {
|
||||||
|
var appDiv = document.getElementById(appId);
|
||||||
|
if (!appDiv) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < appDiv.childElementCount; i++) {
|
||||||
|
var className = appDiv.childNodes[i].className;
|
||||||
|
/* If the app div contains a child with the class
|
||||||
|
'v-app-loading' we have only received the HTML
|
||||||
|
but not yet started the widget set
|
||||||
|
(UIConnector removes the v-app-loading div). */
|
||||||
|
if (className && className.indexOf('v-app-loading') != -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Needed for Testbench compatibility, but prevents any Vaadin 7 app from
|
||||||
|
* bootstrapping unless the legacy vaadinBootstrap.js file is loaded before
|
||||||
|
* this script.
|
||||||
|
*/
|
||||||
|
window.Vaadin = window.Vaadin || {};
|
||||||
|
window.Vaadin.Flow = window.Vaadin.Flow || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a CSS animation on an element by adding a class, then
|
||||||
|
* removes the class when the animation ends.
|
||||||
|
*/
|
||||||
|
window.Vaadin.Flow.flashClass = function (element, className) {
|
||||||
|
element.classList.remove(className);
|
||||||
|
void element.offsetWidth;
|
||||||
|
element.classList.add(className);
|
||||||
|
function onAnimationEnd(e) {
|
||||||
|
if (e.target === element) {
|
||||||
|
element.classList.remove(className);
|
||||||
|
element.removeEventListener('animationend', onAnimationEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
element.addEventListener('animationend', onAnimationEnd);
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
var style = getComputedStyle(element);
|
||||||
|
var animName = style.animationName;
|
||||||
|
if (!animName || animName === 'none') {
|
||||||
|
element.classList.remove(className);
|
||||||
|
element.removeEventListener('animationend', onAnimationEnd);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needed for wrapping custom javascript functionality in the components (i.e. connectors)
|
||||||
|
*/
|
||||||
|
window.Vaadin.Flow.tryCatchWrapper = function (originalFunction, component) {
|
||||||
|
return function () {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const result = originalFunction.apply(this, arguments);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`There seems to be an error in ${component}:
|
||||||
|
${error.message}
|
||||||
|
Please submit an issue to https://github.com/vaadin/flow-components/issues/new/choose`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!window.Vaadin.Flow.initApplication) {
|
||||||
|
window.Vaadin.Flow.clients = window.Vaadin.Flow.clients || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a Flow application with the given ID and configuration,
|
||||||
|
* and triggers the widgetset callback to start the client engine.
|
||||||
|
*/
|
||||||
|
window.Vaadin.Flow.initApplication = function (appId, config) {
|
||||||
|
var testbenchId = appId.replace(/-\d+$/, '');
|
||||||
|
|
||||||
|
if (apps[appId]) {
|
||||||
|
if (
|
||||||
|
window.Vaadin &&
|
||||||
|
window.Vaadin.Flow &&
|
||||||
|
window.Vaadin.Flow.clients &&
|
||||||
|
window.Vaadin.Flow.clients[testbenchId] &&
|
||||||
|
window.Vaadin.Flow.clients[testbenchId].initializing
|
||||||
|
) {
|
||||||
|
throw new Error('Application ' + appId + ' is already being initialized');
|
||||||
|
}
|
||||||
|
if (isInitializedInDom(appId)) {
|
||||||
|
if (appInitResponse.appConfig.productionMode) {
|
||||||
|
throw new Error('Application ' + appId + ' already initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove old contents for Flow
|
||||||
|
var appDiv = document.getElementById(appId);
|
||||||
|
for (var i = 0; i < appDiv.childElementCount; i++) {
|
||||||
|
appDiv.childNodes[i].remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// For devMode reset app config and restart widgetset as client
|
||||||
|
// is up and running after hrm update.
|
||||||
|
const getConfig = function (name) {
|
||||||
|
return config[name];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Export public data */
|
||||||
|
const app = {
|
||||||
|
getConfig: getConfig
|
||||||
|
};
|
||||||
|
apps[appId] = app;
|
||||||
|
|
||||||
|
if (widgetsets['client'].callback) {
|
||||||
|
log('Starting from bootstrap', appId);
|
||||||
|
widgetsets['client'].callback(appId);
|
||||||
|
} else {
|
||||||
|
log('Setting pending startup', appId);
|
||||||
|
widgetsets['client'].pendingApps.push(appId);
|
||||||
|
}
|
||||||
|
return apps[appId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log('init application', appId, config);
|
||||||
|
|
||||||
|
window.Vaadin.Flow.clients[testbenchId] = {
|
||||||
|
isActive: function () {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
initializing: true,
|
||||||
|
productionMode: mode
|
||||||
|
};
|
||||||
|
|
||||||
|
var getConfig = function (name) {
|
||||||
|
var value = config[name];
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Export public data */
|
||||||
|
var app = {
|
||||||
|
getConfig: getConfig
|
||||||
|
};
|
||||||
|
apps[appId] = app;
|
||||||
|
|
||||||
|
var widgetset = 'client';
|
||||||
|
widgetsets[widgetset] = {
|
||||||
|
pendingApps: []
|
||||||
|
};
|
||||||
|
if (widgetsets[widgetset].callback) {
|
||||||
|
log('Starting from bootstrap', appId);
|
||||||
|
widgetsets[widgetset].callback(appId);
|
||||||
|
} else {
|
||||||
|
log('Setting pending startup', appId);
|
||||||
|
widgetsets[widgetset].pendingApps.push(appId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return app;
|
||||||
|
};
|
||||||
|
/** Returns an array of all registered application IDs */
|
||||||
|
window.Vaadin.Flow.getAppIds = function () {
|
||||||
|
var ids = [];
|
||||||
|
for (var id in apps) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(apps, id)) {
|
||||||
|
ids.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
};
|
||||||
|
/** Returns the application object for the given ID */
|
||||||
|
window.Vaadin.Flow.getApp = function (appId) {
|
||||||
|
return apps[appId];
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Registers a widgetset callback and starts any applications
|
||||||
|
* that are waiting for it.
|
||||||
|
*/
|
||||||
|
window.Vaadin.Flow.registerWidgetset = function (widgetset, callback) {
|
||||||
|
log('Widgetset registered', widgetset);
|
||||||
|
var ws = widgetsets[widgetset];
|
||||||
|
if (ws && ws.pendingApps) {
|
||||||
|
ws.callback = callback;
|
||||||
|
for (var i = 0; i < ws.pendingApps.length; i++) {
|
||||||
|
var appId = ws.pendingApps[i];
|
||||||
|
log('Starting from register widgetset', appId);
|
||||||
|
callback(appId);
|
||||||
|
}
|
||||||
|
ws.pendingApps = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
log('Flow bootstrap loaded');
|
||||||
|
if (appInitResponse.appConfig.productionMode && typeof window.__gwtStatsEvent != 'function') {
|
||||||
|
window.Vaadin.Flow.gwtStatsEvents = [];
|
||||||
|
window.__gwtStatsEvent = function (event) {
|
||||||
|
window.Vaadin.Flow.gwtStatsEvents.push(event);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var config = appInitResponse.appConfig;
|
||||||
|
var mode = appInitResponse.appConfig.productionMode;
|
||||||
|
window.Vaadin.Flow.initApplication(config.appId, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { init };
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export const init: () => void;
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Client-side helpers for keyboard shortcuts. Loaded on demand by
|
||||||
|
* ShortcutRegistration (see initShortcutClient) the same way FlowWebPush.js is
|
||||||
|
* loaded by WebPush. Provides the popover/modal origin guards (#24974) and the
|
||||||
|
* keydown delegate used when a shortcut listens on a browser-only element.
|
||||||
|
*/
|
||||||
|
window.Vaadin = window.Vaadin || {};
|
||||||
|
window.Vaadin.Flow = window.Vaadin.Flow || {};
|
||||||
|
|
||||||
|
window.Vaadin.Flow.shortcut = window.Vaadin.Flow.shortcut || {
|
||||||
|
// Nearest open popover/modal ancestor of the given node in the flattened
|
||||||
|
// (composed) tree, so slotted light-DOM content resolves to the overlay in a
|
||||||
|
// component's shadow root.
|
||||||
|
_scopeOf: function (node) {
|
||||||
|
while (node) {
|
||||||
|
if (node.nodeType === 1 && node.matches && (node.matches(':popover-open') || node.matches(':modal'))) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
node = node.assignedSlot || node.parentNode || node.host || null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Nearest open popover/modal ancestor of the event target.
|
||||||
|
_eventScope: function (event) {
|
||||||
|
const path = event.composedPath();
|
||||||
|
for (let i = 0; i < path.length; i++) {
|
||||||
|
const node = path[i];
|
||||||
|
if (node && node.nodeType === 1 && node.matches && (node.matches(':popover-open') || node.matches(':modal'))) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Delegate path: suppress when an open popover/modal sits between the event
|
||||||
|
// target and the boundary element the listener is attached to. Returns true
|
||||||
|
// when the shortcut is allowed to fire. Fails open on error.
|
||||||
|
eventWithinBoundary: function (event, boundary) {
|
||||||
|
try {
|
||||||
|
const path = event.composedPath();
|
||||||
|
const boundaryIndex = path.indexOf(boundary);
|
||||||
|
if (boundaryIndex < 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < boundaryIndex; i++) {
|
||||||
|
const node = path[i];
|
||||||
|
if (node && node.nodeType === 1 && node.matches && (node.matches(':popover-open') || node.matches(':modal'))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Normal path: fire only when the event and the shortcut owner (located via
|
||||||
|
// the given attribute selector) share the same popover/modal scope. Returns
|
||||||
|
// true when the shortcut is allowed to fire. Fails open on error.
|
||||||
|
//
|
||||||
|
// A relayed clone (see registerKeydownDelegate) carries the real origin scope
|
||||||
|
// in _vaadinShortcutOriginScope, because its own composedPath points at the
|
||||||
|
// listenOn element and no longer reflects where the keydown happened.
|
||||||
|
eventInOwnerScope: function (event, ownerSelector) {
|
||||||
|
try {
|
||||||
|
const owner = document.querySelector(ownerSelector);
|
||||||
|
if (!owner) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const eventScope =
|
||||||
|
'_vaadinShortcutOriginScope' in event
|
||||||
|
? event._vaadinShortcutOriginScope
|
||||||
|
: window.Vaadin.Flow.shortcut._eventScope(event);
|
||||||
|
return eventScope === window.Vaadin.Flow.shortcut._scopeOf(owner);
|
||||||
|
} catch (e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Relays keydown events from a browser-only element (found by the JS locator)
|
||||||
|
// to the listenOn component. When the given matcher accepts the event a clone
|
||||||
|
// is re-dispatched to listenOn so the server-side shortcut listener fires.
|
||||||
|
// (Previously the inline ELEMENT_LOCATOR_JS in ShortcutRegistration.)
|
||||||
|
registerKeydownDelegate: function (listenOn, delegate, matches, resetFocus, allowDefault) {
|
||||||
|
if (!delegate) {
|
||||||
|
throw 'Shortcut listenOn element not found with the given JS locator';
|
||||||
|
}
|
||||||
|
delegate.addEventListener('keydown', function (event) {
|
||||||
|
if (matches(event, delegate)) {
|
||||||
|
if (resetFocus) {
|
||||||
|
window.Vaadin.Flow.resetFocus();
|
||||||
|
}
|
||||||
|
const clone = new event.constructor(event.type, event);
|
||||||
|
// Remember where the keydown actually originated: the clone is
|
||||||
|
// re-targeted at listenOn, so its composedPath can no longer tell a
|
||||||
|
// downstream owner-scope guard that the event came from this overlay.
|
||||||
|
clone._vaadinShortcutOriginScope = window.Vaadin.Flow.shortcut._eventScope(event);
|
||||||
|
listenOn.dispatchEvent(clone);
|
||||||
|
if (!allowDefault) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
type VaadinFullscreenState = 'UNSUPPORTED' | 'NOT_FULLSCREEN' | 'FULLSCREEN';
|
||||||
|
/**
|
||||||
|
* Returns the current fullscreen state synchronously. Used by the bootstrap
|
||||||
|
* path to seed the server-side signal without waiting for a DOM event.
|
||||||
|
*/
|
||||||
|
export declare function currentFullscreenState(): VaadinFullscreenState;
|
||||||
|
export {};
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Returns the current fullscreen state synchronously. Used by the bootstrap
|
||||||
|
* path to seed the server-side signal without waiting for a DOM event.
|
||||||
|
*/
|
||||||
|
export function currentFullscreenState() {
|
||||||
|
if (document.fullscreenEnabled !== true) {
|
||||||
|
return 'UNSUPPORTED';
|
||||||
|
}
|
||||||
|
return document.fullscreenElement ? 'FULLSCREEN' : 'NOT_FULLSCREEN';
|
||||||
|
}
|
||||||
|
// Dispatch on document.body so the server-side Page facade (listening on
|
||||||
|
// the UI element, which is body) can update its signal.
|
||||||
|
function dispatch(state) {
|
||||||
|
document.body.dispatchEvent(new CustomEvent('vaadin-fullscreen-change', { detail: state }));
|
||||||
|
}
|
||||||
|
// Tracks the most recent component-fullscreen setup so the wrapper can be
|
||||||
|
// torn down when fullscreen exits (programmatically or via Escape) or when
|
||||||
|
// a new fullscreen request supersedes it.
|
||||||
|
let activeComponentReset;
|
||||||
|
function resetComponentIfActive() {
|
||||||
|
if (activeComponentReset) {
|
||||||
|
const fn = activeComponentReset;
|
||||||
|
activeComponentReset = undefined;
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('fullscreenchange', () => {
|
||||||
|
if (!document.fullscreenElement) {
|
||||||
|
resetComponentIfActive();
|
||||||
|
}
|
||||||
|
dispatch(currentFullscreenState());
|
||||||
|
});
|
||||||
|
const $wnd = window;
|
||||||
|
$wnd.Vaadin ??= {};
|
||||||
|
$wnd.Vaadin.Flow ??= {};
|
||||||
|
$wnd.Vaadin.Flow.fullscreen = {
|
||||||
|
/**
|
||||||
|
* Requests fullscreen for the entire page (document.documentElement).
|
||||||
|
* Resolves once the browser has entered fullscreen; rejects with the
|
||||||
|
* browser's error if the request is refused (no user activation,
|
||||||
|
* permissions policy, etc.) or with a custom error if fullscreen is not
|
||||||
|
* supported.
|
||||||
|
*/
|
||||||
|
async requestPageFullscreen() {
|
||||||
|
resetComponentIfActive();
|
||||||
|
if (document.fullscreenEnabled !== true) {
|
||||||
|
throw new Error('Fullscreen is not supported');
|
||||||
|
}
|
||||||
|
await document.documentElement.requestFullscreen();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Requests fullscreen for a specific component by moving it into the
|
||||||
|
* given wrapper element and hiding the rest of the view. Fullscreens
|
||||||
|
* document.documentElement so that Vaadin theming and overlay
|
||||||
|
* components keep working. The component is restored to its original
|
||||||
|
* position on exit (programmatic, Escape, or a superseding request).
|
||||||
|
* If the browser rejects the request, the DOM is rolled back before the
|
||||||
|
* promise rejects with the browser's error.
|
||||||
|
*/
|
||||||
|
async requestComponentFullscreen(element, wrapper) {
|
||||||
|
resetComponentIfActive();
|
||||||
|
if (document.fullscreenEnabled !== true) {
|
||||||
|
throw new Error('Fullscreen is not supported');
|
||||||
|
}
|
||||||
|
const originalParent = element.parentNode;
|
||||||
|
if (!originalParent) {
|
||||||
|
throw new Error('Component is not attached to the DOM');
|
||||||
|
}
|
||||||
|
// The view root is the wrapper's current element child (the route
|
||||||
|
// content). Capture it before touching the DOM, because the steps below
|
||||||
|
// insert a placeholder comment and move the element into the wrapper —
|
||||||
|
// after that, the wrapper's first node may be the placeholder rather than
|
||||||
|
// the view root. Use firstElementChild so comment/text nodes are skipped.
|
||||||
|
const viewRoot = wrapper.firstElementChild;
|
||||||
|
const placeholder = document.createComment('vaadin-fullscreen-placeholder');
|
||||||
|
originalParent.insertBefore(placeholder, element);
|
||||||
|
wrapper.appendChild(element);
|
||||||
|
// When the fullscreened component *is* the view root there is nothing
|
||||||
|
// else to hide; hiding it would blank the fullscreen. Otherwise hide the
|
||||||
|
// view root so only the fullscreened component shows.
|
||||||
|
const hidden = viewRoot === element ? null : viewRoot;
|
||||||
|
const previousDisplay = hidden?.style.display ?? '';
|
||||||
|
if (hidden) {
|
||||||
|
hidden.style.display = 'none';
|
||||||
|
}
|
||||||
|
activeComponentReset = () => {
|
||||||
|
placeholder.parentNode?.insertBefore(element, placeholder);
|
||||||
|
placeholder.remove();
|
||||||
|
if (hidden) {
|
||||||
|
hidden.style.display = previousDisplay;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await document.documentElement.requestFullscreen();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
// Browser rejected the request — undo the DOM changes so the page
|
||||||
|
// does not end up looking fullscreened without actually being so.
|
||||||
|
resetComponentIfActive();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Exits fullscreen mode if the page is currently in fullscreen.
|
||||||
|
*/
|
||||||
|
exitFullscreen() {
|
||||||
|
if (document.fullscreenElement) {
|
||||||
|
document.exitFullscreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=Fullscreen.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
function copyCoords(c) {
|
||||||
|
return {
|
||||||
|
latitude: c.latitude,
|
||||||
|
longitude: c.longitude,
|
||||||
|
accuracy: c.accuracy,
|
||||||
|
altitude: c.altitude,
|
||||||
|
altitudeAccuracy: c.altitudeAccuracy,
|
||||||
|
heading: c.heading,
|
||||||
|
speed: c.speed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const watches = new Map();
|
||||||
|
// The cached availability for the current page. Populated on first
|
||||||
|
// queryAvailability() call, refreshed from each get()/watch() outcome, and
|
||||||
|
// kept current by a permissionchange listener (where supported).
|
||||||
|
let cachedAvailability = null;
|
||||||
|
let permissionChangeListenerInstalled = false;
|
||||||
|
function publishAvailability(next) {
|
||||||
|
if (cachedAvailability === next) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cachedAvailability = next;
|
||||||
|
// Dispatch on document.body so the server-side Geolocation facade (listening
|
||||||
|
// on the UI element, which is body) can update its cached value.
|
||||||
|
document.body.dispatchEvent(new CustomEvent('vaadin-geolocation-availability-change', {
|
||||||
|
detail: { availability: next }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
// Applies a single get()/watch() outcome to the cached availability and
|
||||||
|
// returns the value to report in the response. Never overwrites
|
||||||
|
// UNSUPPORTED, which is session-stable. TIMEOUT and POSITION_UNAVAILABLE
|
||||||
|
// don't reveal the permission state, so the previous cached value is
|
||||||
|
// returned unchanged.
|
||||||
|
function getAndCacheAvailabilityFromResult(position, error) {
|
||||||
|
if (cachedAvailability !== 'UNSUPPORTED') {
|
||||||
|
if (position) {
|
||||||
|
publishAvailability('GRANTED');
|
||||||
|
}
|
||||||
|
else if (error?.code === 1) {
|
||||||
|
publishAvailability('DENIED');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cachedAvailability ?? 'UNKNOWN';
|
||||||
|
}
|
||||||
|
async function resolveAvailability() {
|
||||||
|
if (!window.isSecureContext) {
|
||||||
|
return 'UNSUPPORTED';
|
||||||
|
}
|
||||||
|
// Chromium exposes document.featurePolicy; Firefox and Safari do not
|
||||||
|
// expose any feature-policy introspection API, so the check is only
|
||||||
|
// possible on Chromium. When absent, assume geolocation is allowed.
|
||||||
|
const doc = document;
|
||||||
|
if (doc.featurePolicy && typeof doc.featurePolicy.allowsFeature === 'function') {
|
||||||
|
try {
|
||||||
|
if (!doc.featurePolicy.allowsFeature('geolocation')) {
|
||||||
|
return 'UNSUPPORTED';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (_e) {
|
||||||
|
// Ignore and assume allowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const status = await navigator.permissions.query({ name: 'geolocation' });
|
||||||
|
if (!permissionChangeListenerInstalled) {
|
||||||
|
permissionChangeListenerInstalled = true;
|
||||||
|
status.addEventListener('change', () => {
|
||||||
|
publishAvailability(stateToAvailability(status.state));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return stateToAvailability(status.state);
|
||||||
|
}
|
||||||
|
catch (_e) {
|
||||||
|
// Safari rejects the 'geolocation' permission name with a TypeError
|
||||||
|
return 'UNKNOWN';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function stateToAvailability(state) {
|
||||||
|
switch (state) {
|
||||||
|
case 'granted':
|
||||||
|
return 'GRANTED';
|
||||||
|
case 'denied':
|
||||||
|
return 'DENIED';
|
||||||
|
case 'prompt':
|
||||||
|
return 'PROMPT';
|
||||||
|
default:
|
||||||
|
return 'UNKNOWN';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const $wnd = window;
|
||||||
|
$wnd.Vaadin ??= {};
|
||||||
|
$wnd.Vaadin.Flow ??= {};
|
||||||
|
$wnd.Vaadin.Flow.geolocation = {
|
||||||
|
get(options) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
navigator.geolocation.getCurrentPosition((p) => {
|
||||||
|
const position = { coords: copyCoords(p.coords), timestamp: p.timestamp };
|
||||||
|
resolve({ position, availability: getAndCacheAvailabilityFromResult(position, undefined) });
|
||||||
|
}, (e) => {
|
||||||
|
const error = { code: e.code, message: e.message };
|
||||||
|
resolve({ error, availability: getAndCacheAvailabilityFromResult(undefined, error) });
|
||||||
|
}, options || undefined);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
watch(element, options, watchKey) {
|
||||||
|
if (watches.has(watchKey)) {
|
||||||
|
navigator.geolocation.clearWatch(watches.get(watchKey));
|
||||||
|
}
|
||||||
|
watches.set(watchKey, navigator.geolocation.watchPosition((p) => {
|
||||||
|
const position = { coords: copyCoords(p.coords), timestamp: p.timestamp };
|
||||||
|
getAndCacheAvailabilityFromResult(position, undefined);
|
||||||
|
element.dispatchEvent(new CustomEvent('vaadin-geolocation-position', {
|
||||||
|
detail: position
|
||||||
|
}));
|
||||||
|
}, (e) => {
|
||||||
|
const error = { code: e.code, message: e.message };
|
||||||
|
getAndCacheAvailabilityFromResult(undefined, error);
|
||||||
|
element.dispatchEvent(new CustomEvent('vaadin-geolocation-error', {
|
||||||
|
detail: error
|
||||||
|
}));
|
||||||
|
}, options || undefined));
|
||||||
|
},
|
||||||
|
clearWatch(watchKey) {
|
||||||
|
if (watches.has(watchKey)) {
|
||||||
|
navigator.geolocation.clearWatch(watches.get(watchKey));
|
||||||
|
watches.delete(watchKey);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async queryAvailability() {
|
||||||
|
const value = await resolveAvailability();
|
||||||
|
// publish without dispatching a change event — there is no previous
|
||||||
|
// cached value to compare against when cachedAvailability is null and
|
||||||
|
// the bootstrap consumer just wants the initial answer.
|
||||||
|
cachedAvailability = value;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export {};
|
||||||
|
//# sourceMappingURL=Geolocation.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
|
|||||||
|
type VaadinPageVisibility = 'VISIBLE' | 'VISIBLE_NOT_FOCUSED' | 'HIDDEN';
|
||||||
|
/**
|
||||||
|
* Returns the current visibility state synchronously. Used by the bootstrap
|
||||||
|
* path to seed the server-side signal without waiting for a DOM event.
|
||||||
|
*/
|
||||||
|
export declare function currentVisibility(): VaadinPageVisibility;
|
||||||
|
export {};
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
// Firefox defers the visibilitychange event while the window is blurred, so
|
||||||
|
// a blur handler needs to wait long enough for that delivery to land before
|
||||||
|
// concluding the state is really "visible but not focused".
|
||||||
|
const FIREFOX_BLUR_SETTLE_MS = 500;
|
||||||
|
const DEFAULT_BLUR_SETTLE_MS = 10;
|
||||||
|
/**
|
||||||
|
* Returns the current visibility state synchronously. Used by the bootstrap
|
||||||
|
* path to seed the server-side signal without waiting for a DOM event.
|
||||||
|
*/
|
||||||
|
export function currentVisibility() {
|
||||||
|
if (document.hidden) {
|
||||||
|
return 'HIDDEN';
|
||||||
|
}
|
||||||
|
return document.hasFocus() ? 'VISIBLE' : 'VISIBLE_NOT_FOCUSED';
|
||||||
|
}
|
||||||
|
function isFirefox() {
|
||||||
|
// Firefox is the only supported browser that reorders visibilitychange
|
||||||
|
// relative to blur; UA sniffing is acceptable here because the alternative
|
||||||
|
// is waiting the longer interval on every browser.
|
||||||
|
return navigator.userAgent.indexOf('Firefox') > -1;
|
||||||
|
}
|
||||||
|
let blurTimer;
|
||||||
|
// Dispatch on document.body so the server-side Page facade (listening on
|
||||||
|
// the UI element, which is body) can update its signal.
|
||||||
|
function dispatch(state) {
|
||||||
|
document.body.dispatchEvent(new CustomEvent('vaadin-page-visibility-change', { detail: state }));
|
||||||
|
}
|
||||||
|
function clearBlurTimer() {
|
||||||
|
if (blurTimer !== undefined) {
|
||||||
|
clearTimeout(blurTimer);
|
||||||
|
blurTimer = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
clearBlurTimer();
|
||||||
|
dispatch(document.hidden ? 'HIDDEN' : 'VISIBLE');
|
||||||
|
});
|
||||||
|
window.addEventListener('blur', () => {
|
||||||
|
clearBlurTimer();
|
||||||
|
const delay = isFirefox() ? FIREFOX_BLUR_SETTLE_MS : DEFAULT_BLUR_SETTLE_MS;
|
||||||
|
blurTimer = setTimeout(() => {
|
||||||
|
blurTimer = undefined;
|
||||||
|
if (!document.hidden) {
|
||||||
|
dispatch('VISIBLE_NOT_FOCUSED');
|
||||||
|
}
|
||||||
|
}, delay);
|
||||||
|
});
|
||||||
|
window.addEventListener('focus', () => {
|
||||||
|
clearBlurTimer();
|
||||||
|
if (!document.hidden) {
|
||||||
|
dispatch('VISIBLE');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//# sourceMappingURL=PageVisibility.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"PageVisibility.js","sourceRoot":"","sources":["../../../../src/main/frontend/PageVisibility.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,4EAA4E;AAC5E,4EAA4E;AAC5E,4DAA4D;AAC5D,MAAM,sBAAsB,GAAG,GAAG,CAAC;AACnC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAElC;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpB,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,OAAO,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC;AACjE,CAAC;AAED,SAAS,SAAS;IAChB,uEAAuE;IACvE,2EAA2E;IAC3E,mDAAmD;IACnD,OAAO,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,IAAI,SAAoD,CAAC;AAEzD,yEAAyE;AACzE,wDAAwD;AACxD,SAAS,QAAQ,CAAC,KAA2B;IAC3C,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,+BAA+B,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AACnG,CAAC;AAED,SAAS,cAAc;IACrB,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,SAAS,GAAG,SAAS,CAAC;IACxB,CAAC;AACH,CAAC;AAED,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,GAAG,EAAE;IACjD,cAAc,EAAE,CAAC;IACjB,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE;IACnC,cAAc,EAAE,CAAC;IACjB,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,sBAAsB,CAAC;IAC5E,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;QAC1B,SAAS,GAAG,SAAS,CAAC;QACtB,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YACrB,QAAQ,CAAC,qBAAqB,CAAC,CAAC;QAClC,CAAC;IACH,CAAC,EAAE,KAAK,CAAC,CAAC;AACZ,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;IACpC,cAAc,EAAE,CAAC;IACjB,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QACrB,QAAQ,CAAC,SAAS,CAAC,CAAC;IACtB,CAAC;AACH,CAAC,CAAC,CAAC","sourcesContent":["/*\n * Copyright 2000-2026 Vaadin Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\ntype VaadinPageVisibility = 'VISIBLE' | 'VISIBLE_NOT_FOCUSED' | 'HIDDEN';\n\n// Firefox defers the visibilitychange event while the window is blurred, so\n// a blur handler needs to wait long enough for that delivery to land before\n// concluding the state is really \"visible but not focused\".\nconst FIREFOX_BLUR_SETTLE_MS = 500;\nconst DEFAULT_BLUR_SETTLE_MS = 10;\n\n/**\n * Returns the current visibility state synchronously. Used by the bootstrap\n * path to seed the server-side signal without waiting for a DOM event.\n */\nexport function currentVisibility(): VaadinPageVisibility {\n if (document.hidden) {\n return 'HIDDEN';\n }\n return document.hasFocus() ? 'VISIBLE' : 'VISIBLE_NOT_FOCUSED';\n}\n\nfunction isFirefox(): boolean {\n // Firefox is the only supported browser that reorders visibilitychange\n // relative to blur; UA sniffing is acceptable here because the alternative\n // is waiting the longer interval on every browser.\n return navigator.userAgent.indexOf('Firefox') > -1;\n}\n\nlet blurTimer: ReturnType<typeof setTimeout> | undefined;\n\n// Dispatch on document.body so the server-side Page facade (listening on\n// the UI element, which is body) can update its signal.\nfunction dispatch(state: VaadinPageVisibility): void {\n document.body.dispatchEvent(new CustomEvent('vaadin-page-visibility-change', { detail: state }));\n}\n\nfunction clearBlurTimer(): void {\n if (blurTimer !== undefined) {\n clearTimeout(blurTimer);\n blurTimer = undefined;\n }\n}\n\ndocument.addEventListener('visibilitychange', () => {\n clearBlurTimer();\n dispatch(document.hidden ? 'HIDDEN' : 'VISIBLE');\n});\n\nwindow.addEventListener('blur', () => {\n clearBlurTimer();\n const delay = isFirefox() ? FIREFOX_BLUR_SETTLE_MS : DEFAULT_BLUR_SETTLE_MS;\n blurTimer = setTimeout(() => {\n blurTimer = undefined;\n if (!document.hidden) {\n dispatch('VISIBLE_NOT_FOCUSED');\n }\n }, delay);\n});\n\nwindow.addEventListener('focus', () => {\n clearBlurTimer();\n if (!document.hidden) {\n dispatch('VISIBLE');\n }\n});\n"]}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Outlet } from 'react-router';
|
||||||
|
import { ReactAdapterElement } from "Frontend/generated/flow/ReactAdapter.js";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
class ReactRouterOutletElement extends ReactAdapterElement {
|
||||||
|
public async connectedCallback() {
|
||||||
|
await super.connectedCallback();
|
||||||
|
this.style.display = 'contents';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): React.ReactElement | null {
|
||||||
|
return <Outlet />;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('react-router-outlet', ReactRouterOutletElement);
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Returns the current screen orientation type synchronously, or
|
||||||
|
* {@code 'unsupported'} if the Screen Orientation API is unavailable. Used by
|
||||||
|
* the bootstrap path to seed the server-side signal without waiting for a DOM
|
||||||
|
* event.
|
||||||
|
*/
|
||||||
|
export declare function currentScreenOrientationType(): string;
|
||||||
|
/**
|
||||||
|
* Returns the current screen orientation angle synchronously, or 0 if the
|
||||||
|
* Screen Orientation API is unavailable.
|
||||||
|
*/
|
||||||
|
export declare function currentScreenOrientationAngle(): number;
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Returns the current screen orientation type synchronously, or
|
||||||
|
* {@code 'unsupported'} if the Screen Orientation API is unavailable. Used by
|
||||||
|
* the bootstrap path to seed the server-side signal without waiting for a DOM
|
||||||
|
* event.
|
||||||
|
*/
|
||||||
|
export function currentScreenOrientationType() {
|
||||||
|
return screen.orientation?.type ?? 'unsupported';
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns the current screen orientation angle synchronously, or 0 if the
|
||||||
|
* Screen Orientation API is unavailable.
|
||||||
|
*/
|
||||||
|
export function currentScreenOrientationAngle() {
|
||||||
|
return screen.orientation?.angle ?? 0;
|
||||||
|
}
|
||||||
|
// Dispatch on document.body so the server-side ScreenOrientation facade
|
||||||
|
// (listening on the UI element, which is body) can update its signal.
|
||||||
|
function dispatch(detail) {
|
||||||
|
document.body.dispatchEvent(new CustomEvent('vaadin-screen-orientation-change', { detail }));
|
||||||
|
}
|
||||||
|
if (screen.orientation) {
|
||||||
|
screen.orientation.addEventListener('change', () => {
|
||||||
|
dispatch({
|
||||||
|
type: screen.orientation.type,
|
||||||
|
angle: screen.orientation.angle
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const $wnd = window;
|
||||||
|
$wnd.Vaadin ??= {};
|
||||||
|
$wnd.Vaadin.Flow ??= {};
|
||||||
|
function lockErrorCode(domExceptionName) {
|
||||||
|
switch (domExceptionName) {
|
||||||
|
case 'NotSupportedError':
|
||||||
|
return 'NOT_SUPPORTED';
|
||||||
|
case 'SecurityError':
|
||||||
|
return 'SECURITY';
|
||||||
|
case 'AbortError':
|
||||||
|
return 'ABORT';
|
||||||
|
default:
|
||||||
|
return 'UNKNOWN';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$wnd.Vaadin.Flow.screenOrientation = {
|
||||||
|
// Always resolves so the server-side .then(success, error) chain only
|
||||||
|
// receives the "error" branch on a bridge failure (lost connection, etc.).
|
||||||
|
// Rejected DOMExceptions are folded into the resolved result so the server
|
||||||
|
// can decode them as a record without forfeiting the JS-bridge error arm.
|
||||||
|
lock(type) {
|
||||||
|
if (!screen.orientation || typeof screen.orientation.lock !== 'function') {
|
||||||
|
return Promise.resolve({
|
||||||
|
success: false,
|
||||||
|
code: 'NOT_SUPPORTED',
|
||||||
|
message: 'Screen Orientation API is not supported in this browser.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return screen.orientation
|
||||||
|
.lock(type)
|
||||||
|
.then(() => ({ success: true }))
|
||||||
|
.catch((e) => {
|
||||||
|
const code = lockErrorCode(e.name);
|
||||||
|
const message = e.message ?? '';
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
code,
|
||||||
|
// The DOMException name is dropped once mapped to a typed code;
|
||||||
|
// keep it in the message for diagnostics when no code matches.
|
||||||
|
message: code === 'UNKNOWN' && e.name ? `${e.name}: ${message}` : message
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
unlock() {
|
||||||
|
screen.orientation?.unlock();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=ScreenOrientation.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
// Whether the server-side has asked us to hold the lock. The browser releases
|
||||||
|
// the lock whenever the tab is hidden; this flag is what lets the
|
||||||
|
// visibilitychange handler re-acquire silently when the tab returns.
|
||||||
|
let wanted = false;
|
||||||
|
let sentinel = null;
|
||||||
|
let visibilityListenerInstalled = false;
|
||||||
|
function dispatch(element, state) {
|
||||||
|
element.dispatchEvent(new CustomEvent('vaadin-wake-lock-change', { detail: state }));
|
||||||
|
}
|
||||||
|
async function acquire(element) {
|
||||||
|
if (sentinel) {
|
||||||
|
return { state: 'granted' };
|
||||||
|
}
|
||||||
|
if (!window.isSecureContext || !('wakeLock' in navigator)) {
|
||||||
|
return {
|
||||||
|
state: 'error',
|
||||||
|
errorCode: 'UNSUPPORTED',
|
||||||
|
message: window.isSecureContext
|
||||||
|
? 'Screen Wake Lock API not implemented in this browser'
|
||||||
|
: 'Screen Wake Lock API requires a secure context (HTTPS or localhost)'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const next = await navigator.wakeLock.request('screen');
|
||||||
|
// The user (or the browser) may have released the lock or the tab may have
|
||||||
|
// been hidden again while the request was in flight.
|
||||||
|
if (!wanted || document.visibilityState !== 'visible') {
|
||||||
|
try {
|
||||||
|
await next.release();
|
||||||
|
}
|
||||||
|
catch (_e) {
|
||||||
|
// Ignore; releasing an already-released sentinel throws on some
|
||||||
|
// browsers and there is nothing meaningful to do here.
|
||||||
|
}
|
||||||
|
return { state: 'deferred' };
|
||||||
|
}
|
||||||
|
sentinel = next;
|
||||||
|
next.addEventListener('release', () => {
|
||||||
|
sentinel = null;
|
||||||
|
dispatch(element, 'RELEASED');
|
||||||
|
});
|
||||||
|
dispatch(element, 'ACTIVE');
|
||||||
|
return { state: 'granted' };
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
const name = e?.name;
|
||||||
|
const errorCode = name === 'NotAllowedError' ? 'NOT_ALLOWED' : 'UNKNOWN';
|
||||||
|
return {
|
||||||
|
state: 'error',
|
||||||
|
errorCode,
|
||||||
|
message: e?.message ? String(e.message) : String(e)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function installVisibilityListener(element) {
|
||||||
|
if (visibilityListenerInstalled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visibilityListenerInstalled = true;
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (wanted && !sentinel && document.visibilityState === 'visible') {
|
||||||
|
acquire(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const $wnd = window;
|
||||||
|
$wnd.Vaadin ??= {};
|
||||||
|
$wnd.Vaadin.Flow ??= {};
|
||||||
|
$wnd.Vaadin.Flow.wakeLock = {
|
||||||
|
request(element) {
|
||||||
|
wanted = true;
|
||||||
|
installVisibilityListener(element);
|
||||||
|
if (document.visibilityState !== 'visible') {
|
||||||
|
// The browser will not grant a lock while the page is hidden; the
|
||||||
|
// visibilitychange listener will pick it up on the next 'visible'.
|
||||||
|
return Promise.resolve({ state: 'deferred' });
|
||||||
|
}
|
||||||
|
return acquire(element);
|
||||||
|
},
|
||||||
|
async release(element) {
|
||||||
|
wanted = false;
|
||||||
|
if (!sentinel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const current = sentinel;
|
||||||
|
sentinel = null;
|
||||||
|
try {
|
||||||
|
await current.release();
|
||||||
|
}
|
||||||
|
catch (_e) {
|
||||||
|
// Ignore; the 'release' event listener installed in acquire() also
|
||||||
|
// dispatches RELEASED, so the state still reaches the server even when
|
||||||
|
// the explicit release() call rejects.
|
||||||
|
}
|
||||||
|
dispatch(element, 'RELEASED');
|
||||||
|
},
|
||||||
|
queryAvailability() {
|
||||||
|
if (!window.isSecureContext) {
|
||||||
|
return 'UNSUPPORTED';
|
||||||
|
}
|
||||||
|
return 'wakeLock' in navigator ? 'SUPPORTED' : 'UNSUPPORTED';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export {};
|
||||||
|
//# sourceMappingURL=WakeLock.js.map
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Returns whether the current browser exposes the Web Share API
|
||||||
|
* (`navigator.share`). Used by the bootstrap path to seed the server-side
|
||||||
|
* support signal without waiting for a DOM event.
|
||||||
|
*/
|
||||||
|
export declare function isShareSupported(): boolean;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Returns whether the current browser exposes the Web Share API
|
||||||
|
* (`navigator.share`). Used by the bootstrap path to seed the server-side
|
||||||
|
* support signal without waiting for a DOM event.
|
||||||
|
*/
|
||||||
|
export function isShareSupported() {
|
||||||
|
return typeof navigator.share === 'function';
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=WebShare.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"WebShare.js","sourceRoot":"","sources":["../../../../src/main/frontend/WebShare.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;GAIG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,OAAO,SAAS,CAAC,KAAK,KAAK,UAAU,CAAC;AAC/C,CAAC","sourcesContent":["/*\n * Copyright 2000-2026 Vaadin Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\n/**\n * Returns whether the current browser exposes the Web Share API\n * (`navigator.share`). Used by the bootstrap path to seed the server-side\n * support signal without waiting for a DOM event.\n */\nexport function isShareSupported(): boolean {\n return typeof navigator.share === 'function';\n}\n"]}
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
import { Debouncer } from '@vaadin/component-base/src/debounce.js';
|
||||||
|
import { timeOut } from '@vaadin/component-base/src/async.js';
|
||||||
|
import { ComboBoxPlaceholder } from '@vaadin/combo-box/src/vaadin-combo-box-placeholder.js';
|
||||||
|
|
||||||
|
window.Vaadin.Flow.comboBoxConnector = {};
|
||||||
|
window.Vaadin.Flow.comboBoxConnector.initLazy = (comboBox) => {
|
||||||
|
// Check whether the connector was already initialized for the ComboBox
|
||||||
|
if (comboBox.$connector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
comboBox.$connector = {};
|
||||||
|
|
||||||
|
let cache = {};
|
||||||
|
const placeHolder = new window.Vaadin.ComboBoxPlaceholder();
|
||||||
|
|
||||||
|
let lastTypedFilter = '';
|
||||||
|
let lastRequestedRange = [-1, -1];
|
||||||
|
let lastRequestedFilter = '';
|
||||||
|
let needsDataCommunicatorReset = false;
|
||||||
|
|
||||||
|
const dataProvider = function (params, callback) {
|
||||||
|
if (params.pageSize != comboBox.pageSize) {
|
||||||
|
throw 'Invalid pageSize';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comboBox._clientSideFilter) {
|
||||||
|
if (cache[0]) {
|
||||||
|
performClientSideFilter(cache[0], params.filter, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First fetch: ignore the typed filter so we get the full dataset
|
||||||
|
params = { ...params, filter: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastTypedFilter !== params.filter) {
|
||||||
|
cache = {};
|
||||||
|
lastTypedFilter = params.filter;
|
||||||
|
lastRequestedRange = [-1, -1];
|
||||||
|
|
||||||
|
comboBox._filterDebouncer = Debouncer.debounce(
|
||||||
|
comboBox._filterDebouncer,
|
||||||
|
timeOut.after(comboBox._filterTimeout ?? 500),
|
||||||
|
() => {
|
||||||
|
// Filter cycled back to what server last received — force re-emit.
|
||||||
|
if (params.filter === lastRequestedFilter) {
|
||||||
|
needsDataCommunicatorReset = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
comboBox.clearCache();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comboBox._filterDebouncer?.isActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If buffer-prefetch already cached this page, commit it without a server
|
||||||
|
// round-trip; otherwise ask the server.
|
||||||
|
if (cache[params.page]) {
|
||||||
|
callback(cache[params.page], comboBox.size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
comboBox.$connector.requestPage(params.page, params.filter);
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.getViewportRange = function () {
|
||||||
|
const indices = Array.from(comboBox._scroller?.children ?? [])
|
||||||
|
.map((child) => child.index)
|
||||||
|
.filter((index) => Number.isFinite(index))
|
||||||
|
.sort((a, b) => a - b);
|
||||||
|
if (indices.length === 0) {
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
return [indices[0], indices[indices.length - 1]];
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.requestPage = function (page, filter) {
|
||||||
|
let viewportRange = comboBox.$connector.getViewportRange();
|
||||||
|
const buffer = viewportRange[1] - viewportRange[0];
|
||||||
|
const sizeLimit = Number.isFinite(comboBox.size) ? comboBox.size : Number.POSITIVE_INFINITY;
|
||||||
|
viewportRange[0] = Math.max(viewportRange[0] - buffer, 0);
|
||||||
|
viewportRange[1] = Math.min(viewportRange[1] + buffer, sizeLimit - 1);
|
||||||
|
|
||||||
|
let viewportPageRange = [
|
||||||
|
Math.floor(viewportRange[0] / comboBox.pageSize),
|
||||||
|
Math.floor(viewportRange[1] / comboBox.pageSize)
|
||||||
|
];
|
||||||
|
|
||||||
|
// Collapse to the requested page when it's outside the current viewport,
|
||||||
|
// so confirm() can resolve callbacks left behind by fast scrolling.
|
||||||
|
if (page < viewportPageRange[0] || page > viewportPageRange[1]) {
|
||||||
|
viewportPageRange = [page, page];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastRequestedRange[0] != viewportPageRange[0] || lastRequestedRange[1] != viewportPageRange[1]) {
|
||||||
|
const startIndex = viewportPageRange[0] * comboBox.pageSize;
|
||||||
|
const endIndex = (viewportPageRange[1] + 1) * comboBox.pageSize;
|
||||||
|
comboBox.$server.setViewportRange(startIndex, endIndex - startIndex, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsDataCommunicatorReset) {
|
||||||
|
comboBox.$server.resetDataCommunicator();
|
||||||
|
needsDataCommunicatorReset = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastRequestedRange = viewportPageRange;
|
||||||
|
lastRequestedFilter = filter;
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.clear = (start, length) => {
|
||||||
|
const { pageSize } = comboBox;
|
||||||
|
const firstPage = Math.floor(start / pageSize);
|
||||||
|
const lastPage = firstPage + Math.ceil(length / pageSize);
|
||||||
|
|
||||||
|
for (let page = firstPage; page < lastPage; page++) {
|
||||||
|
delete cache[page];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = firstPage * pageSize; index < lastPage * pageSize; index++) {
|
||||||
|
if (comboBox.filteredItems[index]) {
|
||||||
|
comboBox.filteredItems[index] = placeHolder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.filter = (item, filter) => {
|
||||||
|
filter = filter ? filter.toString().toLowerCase() : '';
|
||||||
|
return comboBox._getItemLabel(item, comboBox.itemLabelPath).toString().toLowerCase().indexOf(filter) > -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.set = (index, items, filter) => {
|
||||||
|
if (filter !== lastTypedFilter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index % comboBox.pageSize != 0) {
|
||||||
|
throw 'Got new data to index ' + index + ' which is not aligned with the page size of ' + comboBox.pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pendingRequests } = comboBox.__dataProviderController.rootCache;
|
||||||
|
if (index === 0 && items.length === 0 && pendingRequests[0]) {
|
||||||
|
// Makes sure that the dataProvider callback is called even when server
|
||||||
|
// returns empty data set (no items match the filter).
|
||||||
|
cache[0] = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstPageToSet = index / comboBox.pageSize;
|
||||||
|
const updatedPageCount = Math.ceil(items.length / comboBox.pageSize);
|
||||||
|
|
||||||
|
for (let i = 0; i < updatedPageCount; i++) {
|
||||||
|
let page = firstPageToSet + i;
|
||||||
|
let slice = items.slice(i * comboBox.pageSize, (i + 1) * comboBox.pageSize);
|
||||||
|
|
||||||
|
cache[page] = slice;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.updateData = (items) => {
|
||||||
|
const itemsMap = new Map(items.map((item) => [item.key, item]));
|
||||||
|
|
||||||
|
comboBox.filteredItems = comboBox.filteredItems.map((item) => {
|
||||||
|
return itemsMap.get(item.key) || item;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.updateSize = function (newSize) {
|
||||||
|
if (!comboBox._clientSideFilter) {
|
||||||
|
// FIXME: It may be that this size set is unnecessary, since when
|
||||||
|
// providing data to combobox via callback we may use data's size.
|
||||||
|
// However, if this size reflect the whole data size, including
|
||||||
|
// data not fetched yet into client side, and combobox expect it
|
||||||
|
// to be set as such, the at least, we don't need it in case the
|
||||||
|
// filter is clientSide only, since it'll increase the height of
|
||||||
|
// the popup at only at first user filter to this size, while the
|
||||||
|
// filtered items count are less.
|
||||||
|
comboBox.size = newSize;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.reset = function () {
|
||||||
|
comboBox._filterDebouncer?.cancel();
|
||||||
|
comboBox._filterDebouncer = null;
|
||||||
|
cache = {};
|
||||||
|
lastRequestedRange = [-1, -1];
|
||||||
|
lastTypedFilter = '';
|
||||||
|
comboBox.clearCache();
|
||||||
|
};
|
||||||
|
|
||||||
|
comboBox.$connector.confirm = function (id, filter) {
|
||||||
|
if (filter !== lastTypedFilter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're done applying changes from this batch, resolve pending
|
||||||
|
// callbacks
|
||||||
|
const { pendingRequests } = comboBox.__dataProviderController.rootCache;
|
||||||
|
Object.entries(pendingRequests).forEach(([page, callback]) => {
|
||||||
|
const items = cache[page];
|
||||||
|
|
||||||
|
if (comboBox._clientSideFilter && items) {
|
||||||
|
performClientSideFilter(items, comboBox.filter, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(items ?? [], comboBox.size);
|
||||||
|
delete cache[page];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Let server know we're done
|
||||||
|
comboBox.$server.confirmUpdate(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Perform filter on client side (here) using the items from specified page
|
||||||
|
// and submitting the filtered items to specified callback.
|
||||||
|
// The filter used is the one from combobox, not the lastFilter stored since
|
||||||
|
// that may not reflect user's input.
|
||||||
|
const performClientSideFilter = function (page, filter, callback) {
|
||||||
|
let filteredItems = page;
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
filteredItems = page.filter((item) => comboBox.$connector.filter(item, filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(filteredItems, filteredItems.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prevent setting the custom value as the 'value'-prop automatically
|
||||||
|
comboBox.addEventListener('custom-value-set', (e) => e.preventDefault());
|
||||||
|
|
||||||
|
comboBox.itemClassNameGenerator = function (item) {
|
||||||
|
return item.className || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assign last, after all `$connector` functions are defined.
|
||||||
|
comboBox.dataProvider = dataProvider;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.Vaadin.ComboBoxPlaceholder = ComboBoxPlaceholder;
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
function getContainer(appId, nodeId) {
|
||||||
|
try {
|
||||||
|
return window.Vaadin.Flow.clients[appId].getByNodeId(nodeId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Could not get node %s from app %s', nodeId, appId);
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the connector for a context menu element.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} contextMenu
|
||||||
|
* @param {string} appId
|
||||||
|
*/
|
||||||
|
function initLazy(contextMenu, appId) {
|
||||||
|
if (contextMenu.$connector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
contextMenu.$connector = {
|
||||||
|
/**
|
||||||
|
* Generates and assigns the items to the context menu.
|
||||||
|
*
|
||||||
|
* @param {number} nodeId
|
||||||
|
*/
|
||||||
|
generateItems(nodeId) {
|
||||||
|
const items = generateItemsTree(appId, nodeId);
|
||||||
|
|
||||||
|
contextMenu.items = items;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an items tree compatible with the context-menu web component
|
||||||
|
* by traversing the given Flow DOM tree of context menu item nodes
|
||||||
|
* whose root node is identified by the `nodeId` argument.
|
||||||
|
*
|
||||||
|
* The app id is required to access the store of Flow DOM nodes.
|
||||||
|
*
|
||||||
|
* @param {string} appId
|
||||||
|
* @param {number} nodeId
|
||||||
|
*/
|
||||||
|
function generateItemsTree(appId, nodeId) {
|
||||||
|
const container = getContainer(appId, nodeId);
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(container.children).map((child) => {
|
||||||
|
const item = {
|
||||||
|
component: child,
|
||||||
|
checked: child._checked,
|
||||||
|
keepOpen: child._keepOpen,
|
||||||
|
className: child.className,
|
||||||
|
theme: child.__theme,
|
||||||
|
tooltip: child.tooltip,
|
||||||
|
tooltipPosition: child.tooltipPosition
|
||||||
|
};
|
||||||
|
// Do not hardcode tag name to allow `vaadin-menu-bar-item`
|
||||||
|
if (child._hasVaadinItemMixin && child._containerNodeId) {
|
||||||
|
item.children = generateItemsTree(appId, child._containerNodeId);
|
||||||
|
}
|
||||||
|
child._item = item;
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the checked state for a context menu item.
|
||||||
|
*
|
||||||
|
* This method is supposed to be called when the context menu item is closed,
|
||||||
|
* so there is no need for triggering a re-render eagarly.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} component
|
||||||
|
* @param {boolean} checked
|
||||||
|
*/
|
||||||
|
function setChecked(component, checked) {
|
||||||
|
if (component._item) {
|
||||||
|
component._item.checked = checked;
|
||||||
|
|
||||||
|
// Set the attribute in the connector to show the checkmark
|
||||||
|
// without having to re-render the whole menu while opened.
|
||||||
|
if (component._item.keepOpen) {
|
||||||
|
component.toggleAttribute('menu-item-checked', checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the keep open state for a context menu item.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} component
|
||||||
|
* @param {boolean} keepOpen
|
||||||
|
*/
|
||||||
|
function setKeepOpen(component, keepOpen) {
|
||||||
|
if (component._item) {
|
||||||
|
component._item.keepOpen = keepOpen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the theme for a context menu item.
|
||||||
|
*
|
||||||
|
* This method is supposed to be called when the context menu item is closed,
|
||||||
|
* so there is no need for triggering a re-render eagarly.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} component
|
||||||
|
* @param {string | undefined | null} theme
|
||||||
|
*/
|
||||||
|
function setTheme(component, theme) {
|
||||||
|
if (component._item) {
|
||||||
|
component._item.theme = theme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Vaadin.Flow.contextMenuConnector = {
|
||||||
|
initLazy,
|
||||||
|
generateItemsTree,
|
||||||
|
setChecked,
|
||||||
|
setKeepOpen,
|
||||||
|
setTheme
|
||||||
|
};
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import * as Gestures from '@vaadin/component-base/src/gestures.js';
|
||||||
|
|
||||||
|
function init(target) {
|
||||||
|
if (target.$contextMenuTargetConnector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.$contextMenuTargetConnector = {
|
||||||
|
openOnHandler(e) {
|
||||||
|
// used by Grid to prevent context menu on selection column click
|
||||||
|
if (target.preventContextMenu && target.preventContextMenu(e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
// The menu is opened later, after a server round-trip, when the event has
|
||||||
|
// finished dispatching and `composedPath()` returns an empty array. Capture
|
||||||
|
// the composed path now so the menu can resolve the target inside a shadow
|
||||||
|
// root (e.g. a grid cell) instead of the retargeted host.
|
||||||
|
e.__composedPath = e.composedPath();
|
||||||
|
this.$contextMenuTargetConnector.openEvent = e;
|
||||||
|
let detail = {};
|
||||||
|
if (target.getContextMenuBeforeOpenDetail) {
|
||||||
|
detail = target.getContextMenuBeforeOpenDetail(e);
|
||||||
|
}
|
||||||
|
target.dispatchEvent(
|
||||||
|
new CustomEvent('vaadin-context-menu-before-open', {
|
||||||
|
detail: detail
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateOpenOn(eventType) {
|
||||||
|
this.removeListener();
|
||||||
|
this.openOnEventType = eventType;
|
||||||
|
|
||||||
|
customElements.whenDefined('vaadin-context-menu').then(() => {
|
||||||
|
if (Gestures.gestures[eventType]) {
|
||||||
|
Gestures.addListener(target, eventType, this.openOnHandler);
|
||||||
|
} else {
|
||||||
|
target.addEventListener(eventType, this.openOnHandler);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
removeListener() {
|
||||||
|
if (this.openOnEventType) {
|
||||||
|
if (Gestures.gestures[this.openOnEventType]) {
|
||||||
|
Gestures.removeListener(target, this.openOnEventType, this.openOnHandler);
|
||||||
|
} else {
|
||||||
|
target.removeEventListener(this.openOnEventType, this.openOnHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
openMenu(contextMenu) {
|
||||||
|
contextMenu.open(this.openEvent);
|
||||||
|
},
|
||||||
|
|
||||||
|
removeConnector() {
|
||||||
|
this.removeListener();
|
||||||
|
target.$contextMenuTargetConnector = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Vaadin.Flow.contextMenuTargetConnector = { init };
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
import dateFnsFormat from 'date-fns/format';
|
||||||
|
import dateFnsParse from 'date-fns/parse';
|
||||||
|
import dateFnsIsValid from 'date-fns/isValid';
|
||||||
|
import { extractDateParts, parseDate as _parseDate } from '@vaadin/date-picker/src/vaadin-date-picker-helper.js';
|
||||||
|
|
||||||
|
window.Vaadin.Flow.datepickerConnector = {};
|
||||||
|
window.Vaadin.Flow.datepickerConnector.initLazy = (datepicker) => {
|
||||||
|
// Check whether the connector was already initialized for the datepicker
|
||||||
|
if (datepicker.$connector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
datepicker.$connector = {};
|
||||||
|
|
||||||
|
const createLocaleBasedDateFormat = function (locale) {
|
||||||
|
try {
|
||||||
|
// Check whether the locale is supported or not
|
||||||
|
new Date().toLocaleDateString(locale);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('The locale is not supported, using default format setting (ISO 8601).');
|
||||||
|
return 'yyyy-MM-dd';
|
||||||
|
}
|
||||||
|
|
||||||
|
// format test date and convert to date-fns pattern
|
||||||
|
const testDate = new Date(Date.UTC(1234, 4, 6));
|
||||||
|
let pattern = testDate.toLocaleDateString(locale, { timeZone: 'UTC' });
|
||||||
|
pattern = pattern
|
||||||
|
// escape date-fns pattern letters by enclosing them in single quotes
|
||||||
|
.replace(/([a-zA-Z]+)/g, "'$1'")
|
||||||
|
// insert date placeholder
|
||||||
|
.replace('06', 'dd')
|
||||||
|
.replace('6', 'd')
|
||||||
|
// insert month placeholder
|
||||||
|
.replace('05', 'MM')
|
||||||
|
.replace('5', 'M')
|
||||||
|
// insert year placeholder
|
||||||
|
.replace('1234', 'yyyy');
|
||||||
|
const isValidPattern = pattern.includes('d') && pattern.includes('M') && pattern.includes('y');
|
||||||
|
if (!isValidPattern) {
|
||||||
|
console.warn('The locale is not supported, using default format setting (ISO 8601).');
|
||||||
|
return 'yyyy-MM-dd';
|
||||||
|
}
|
||||||
|
|
||||||
|
return pattern;
|
||||||
|
};
|
||||||
|
|
||||||
|
function createFormatterAndParser(formats) {
|
||||||
|
if (!formats || formats.length === 0) {
|
||||||
|
throw new Error('Array of custom date formats is null or empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getShortYearFormat(format) {
|
||||||
|
if (format.includes('yyyy') && !format.includes('yyyyy')) {
|
||||||
|
return format.replace('yyyy', 'yy');
|
||||||
|
}
|
||||||
|
if (format.includes('YYYY') && !format.includes('YYYYY')) {
|
||||||
|
return format.replace('YYYY', 'YY');
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFormatWithYear(format) {
|
||||||
|
return format.includes('y') || format.includes('Y');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isShortYearFormat(format) {
|
||||||
|
// Format is long if it includes a four-digit year.
|
||||||
|
return !format.includes('yyyy') && !format.includes('YYYY');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExtendedFormats(formats) {
|
||||||
|
return formats.reduce((acc, format) => {
|
||||||
|
// We first try to match the date with the shorter version,
|
||||||
|
// as short years are supported with the long date format.
|
||||||
|
if (isFormatWithYear(format) && !isShortYearFormat(format)) {
|
||||||
|
acc.push(getShortYearFormat(format));
|
||||||
|
}
|
||||||
|
acc.push(format);
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
function correctFullYear(date) {
|
||||||
|
// The last parsed date check handles the case where a four-digit year is parsed, then formatted
|
||||||
|
// as a two-digit year, and then parsed again. In this case we want to keep the century of the
|
||||||
|
// originally parsed year, instead of using the century of the reference date.
|
||||||
|
|
||||||
|
// Do not apply any correction if the previous parse attempt was failed.
|
||||||
|
if (datepicker.$connector._lastParseStatus === 'error') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update century if the last parsed date is the same except the century.
|
||||||
|
if (datepicker.$connector._lastParseStatus === 'successful') {
|
||||||
|
if (
|
||||||
|
datepicker.$connector._lastParsedDate.day === date.getDate() &&
|
||||||
|
datepicker.$connector._lastParsedDate.month === date.getMonth() &&
|
||||||
|
datepicker.$connector._lastParsedDate.year % 100 === date.getFullYear() % 100
|
||||||
|
) {
|
||||||
|
date.setFullYear(datepicker.$connector._lastParsedDate.year);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update century if this is the first parse after overlay open.
|
||||||
|
const currentValue = _parseDate(datepicker.value);
|
||||||
|
if (
|
||||||
|
dateFnsIsValid(currentValue) &&
|
||||||
|
currentValue.getDate() === date.getDate() &&
|
||||||
|
currentValue.getMonth() === date.getMonth() &&
|
||||||
|
currentValue.getFullYear() % 100 === date.getFullYear() % 100
|
||||||
|
) {
|
||||||
|
date.setFullYear(currentValue.getFullYear());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(dateParts) {
|
||||||
|
const format = formats[0];
|
||||||
|
const date = _parseDate(`${dateParts.year}-${dateParts.month + 1}-${dateParts.day}`);
|
||||||
|
|
||||||
|
return dateFnsFormat(date, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doParseDate(dateString, format, referenceDate) {
|
||||||
|
// When format does not contain a year, then current year should be used.
|
||||||
|
const refDate = isFormatWithYear(format) ? referenceDate : new Date();
|
||||||
|
const date = dateFnsParse(dateString, format, refDate);
|
||||||
|
if (dateFnsIsValid(date)) {
|
||||||
|
if (isFormatWithYear(format) && isShortYearFormat(format)) {
|
||||||
|
correctFullYear(date);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
day: date.getDate(),
|
||||||
|
month: date.getMonth(),
|
||||||
|
year: date.getFullYear()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDate(dateString) {
|
||||||
|
const referenceDate = _getReferenceDate();
|
||||||
|
for (let format of getExtendedFormats(formats)) {
|
||||||
|
const parsedDate = doParseDate(dateString, format, referenceDate);
|
||||||
|
if (parsedDate) {
|
||||||
|
datepicker.$connector._lastParseStatus = 'successful';
|
||||||
|
datepicker.$connector._lastParsedDate = parsedDate;
|
||||||
|
return parsedDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
datepicker.$connector._lastParseStatus = 'error';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
formatDate: formatDate,
|
||||||
|
parseDate: parseDate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getReferenceDate() {
|
||||||
|
const { referenceDate } = datepicker.i18n;
|
||||||
|
return referenceDate ? new Date(referenceDate.year, referenceDate.month, referenceDate.day) : new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
datepicker.$connector.updateI18n = (locale, i18n) => {
|
||||||
|
// Either use custom formats specified in I18N, or create format from locale
|
||||||
|
const hasCustomFormats = i18n && i18n.dateFormats && i18n.dateFormats.length > 0;
|
||||||
|
if (i18n && i18n.referenceDate) {
|
||||||
|
i18n.referenceDate = extractDateParts(new Date(i18n.referenceDate));
|
||||||
|
}
|
||||||
|
const usedFormats = hasCustomFormats ? i18n.dateFormats : [createLocaleBasedDateFormat(locale)];
|
||||||
|
const formatterAndParser = createFormatterAndParser(usedFormats);
|
||||||
|
|
||||||
|
// Merge new I18N settings with formatting and parsing functions
|
||||||
|
datepicker.i18n = Object.assign({}, i18n, formatterAndParser);
|
||||||
|
};
|
||||||
|
|
||||||
|
datepicker.addEventListener('opened-changed', () => (datepicker.$connector._lastParseStatus = undefined));
|
||||||
|
};
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
document.addEventListener('click', (event) => {
|
||||||
|
const target = event.composedPath().find((node) => node.hasAttribute && node.hasAttribute('disableonclick'));
|
||||||
|
if (target) {
|
||||||
|
target.disabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
window.Vaadin = window.Vaadin || {};
|
||||||
|
window.Vaadin.Flow = window.Vaadin.Flow || {};
|
||||||
|
window.Vaadin.Flow.dndConnector = {
|
||||||
|
__ondragenterListener: function (event) {
|
||||||
|
// TODO filter by data type
|
||||||
|
// TODO prevent dropping on itself (by default)
|
||||||
|
const effect = event.currentTarget['__dropEffect'];
|
||||||
|
if (!event.currentTarget.hasAttribute('disabled')) {
|
||||||
|
if (effect) {
|
||||||
|
event.dataTransfer.dropEffect = effect;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effect !== 'none') {
|
||||||
|
/* #7108: if drag moves on top of drop target's children, first another ondragenter event
|
||||||
|
* is fired and then a ondragleave event. This happens again once the drag
|
||||||
|
* moves on top of another children, or back on top of the drop target element.
|
||||||
|
* Thus need to "cancel" the following ondragleave, to not remove class name.
|
||||||
|
* Drop event will happen even when dropped to a child element. */
|
||||||
|
if (event.currentTarget.classList.contains('v-drag-over-target')) {
|
||||||
|
event.currentTarget['__skip-leave'] = true;
|
||||||
|
} else {
|
||||||
|
event.currentTarget.classList.add('v-drag-over-target');
|
||||||
|
}
|
||||||
|
// enables browser specific pseudo classes (at least FF)
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation(); // don't let parents know
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
__ondragoverListener: function (event) {
|
||||||
|
// TODO filter by data type
|
||||||
|
// TODO filter by effectAllowed != dropEffect due to Safari & IE11 ?
|
||||||
|
if (!event.currentTarget.hasAttribute('disabled')) {
|
||||||
|
const effect = event.currentTarget['__dropEffect'];
|
||||||
|
if (effect) {
|
||||||
|
event.dataTransfer.dropEffect = effect;
|
||||||
|
}
|
||||||
|
// allows the drop && don't let parents know
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
__ondragleaveListener: function (event) {
|
||||||
|
if (event.currentTarget['__skip-leave']) {
|
||||||
|
event.currentTarget['__skip-leave'] = false;
|
||||||
|
} else {
|
||||||
|
event.currentTarget.classList.remove('v-drag-over-target');
|
||||||
|
}
|
||||||
|
// #7109 need to stop or any parent drop target might not get highlighted,
|
||||||
|
// as ondragenter for it is fired before the child gets dragleave.
|
||||||
|
event.stopPropagation();
|
||||||
|
},
|
||||||
|
|
||||||
|
__ondropListener: function (event) {
|
||||||
|
const effect = event.currentTarget['__dropEffect'];
|
||||||
|
if (effect) {
|
||||||
|
event.dataTransfer.dropEffect = effect;
|
||||||
|
}
|
||||||
|
event.currentTarget.classList.remove('v-drag-over-target');
|
||||||
|
// prevent browser handling && don't let parents know
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateDropTarget: function (element) {
|
||||||
|
if (element['__active']) {
|
||||||
|
element.addEventListener('dragenter', this.__ondragenterListener, false);
|
||||||
|
element.addEventListener('dragover', this.__ondragoverListener, false);
|
||||||
|
element.addEventListener('dragleave', this.__ondragleaveListener, false);
|
||||||
|
element.addEventListener('drop', this.__ondropListener, false);
|
||||||
|
} else {
|
||||||
|
element.removeEventListener('dragenter', this.__ondragenterListener, false);
|
||||||
|
element.removeEventListener('dragover', this.__ondragoverListener, false);
|
||||||
|
element.removeEventListener('dragleave', this.__ondragleaveListener, false);
|
||||||
|
element.removeEventListener('drop', this.__ondropListener, false);
|
||||||
|
element.classList.remove('v-drag-over-target');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** DRAG SOURCE METHODS: */
|
||||||
|
|
||||||
|
__dragstartListener: function (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.dataTransfer.setData('text/plain', '');
|
||||||
|
if (event.currentTarget.hasAttribute('disabled')) {
|
||||||
|
event.preventDefault();
|
||||||
|
} else {
|
||||||
|
if (event.currentTarget['__effectAllowed']) {
|
||||||
|
event.dataTransfer.effectAllowed = event.currentTarget['__effectAllowed'];
|
||||||
|
}
|
||||||
|
event.currentTarget.classList.add('v-dragged');
|
||||||
|
}
|
||||||
|
if (event.currentTarget.__dragImage) {
|
||||||
|
if (event.currentTarget.__dragImage.style.display === 'none') {
|
||||||
|
event.currentTarget.__dragImage.style.display = 'block';
|
||||||
|
event.currentTarget.classList.add('shown');
|
||||||
|
}
|
||||||
|
event.dataTransfer.setDragImage(
|
||||||
|
event.currentTarget.__dragImage,
|
||||||
|
event.currentTarget.__dragImageOffsetX,
|
||||||
|
event.currentTarget.__dragImageOffsetY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
__dragendListener: function (event) {
|
||||||
|
event.currentTarget.classList.remove('v-dragged');
|
||||||
|
if (event.currentTarget.classList.contains('shown')) {
|
||||||
|
event.currentTarget.classList.remove('shown');
|
||||||
|
event.currentTarget.__dragImage.style.display = 'none';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateDragSource: function (element) {
|
||||||
|
if (element['draggable']) {
|
||||||
|
element.addEventListener('dragstart', this.__dragstartListener, false);
|
||||||
|
element.addEventListener('dragend', this.__dragendListener, false);
|
||||||
|
} else {
|
||||||
|
element.removeEventListener('dragstart', this.__dragstartListener, false);
|
||||||
|
element.removeEventListener('dragend', this.__dragendListener, false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setDragImage: function (dragImage, offsetX, offsetY, dragSource) {
|
||||||
|
dragSource.__dragImage = dragImage;
|
||||||
|
dragSource.__dragImageOffsetX = offsetX;
|
||||||
|
dragSource.__dragImageOffsetY = offsetY;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { noChange } from 'lit';
|
||||||
|
import { directive, PartType } from 'lit/directive.js';
|
||||||
|
import { AsyncDirective } from 'lit/async-directive.js';
|
||||||
|
|
||||||
|
class FlowComponentDirective extends AsyncDirective {
|
||||||
|
constructor(partInfo) {
|
||||||
|
super(partInfo);
|
||||||
|
if (partInfo.type !== PartType.CHILD) {
|
||||||
|
throw new Error(`${this.constructor.directiveName}() can only be used in child bindings`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update(part, [appid, nodeid]) {
|
||||||
|
this.updateContent(part, appid, nodeid);
|
||||||
|
return noChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContent(part, appid, nodeid) {
|
||||||
|
const { parentNode, startNode } = part;
|
||||||
|
this.__parentNode = parentNode;
|
||||||
|
|
||||||
|
const hasNewNodeId = nodeid !== undefined && nodeid !== null;
|
||||||
|
const newNode = hasNewNodeId ? this.getNewNode(appid, nodeid) : null;
|
||||||
|
const oldNode = this.getOldNode(part);
|
||||||
|
|
||||||
|
clearTimeout(this.__parentNode.__nodeRetryTimeout);
|
||||||
|
|
||||||
|
if (hasNewNodeId && !newNode) {
|
||||||
|
// If the node is not found, try again later.
|
||||||
|
this.__parentNode.__nodeRetryTimeout = setTimeout(() => this.updateContent(part, appid, nodeid));
|
||||||
|
} else if (oldNode === newNode) {
|
||||||
|
return;
|
||||||
|
} else if (oldNode && newNode) {
|
||||||
|
parentNode.replaceChild(newNode, oldNode);
|
||||||
|
} else if (oldNode) {
|
||||||
|
parentNode.removeChild(oldNode);
|
||||||
|
} else if (newNode) {
|
||||||
|
startNode.after(newNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getNewNode(appid, nodeid) {
|
||||||
|
return window.Vaadin.Flow.clients[appid].getByNodeId(nodeid);
|
||||||
|
}
|
||||||
|
|
||||||
|
getOldNode(part) {
|
||||||
|
const { startNode, endNode } = part;
|
||||||
|
if (startNode.nextSibling === endNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return startNode.nextSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnected() {
|
||||||
|
clearTimeout(this.__parentNode.__nodeRetryTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the given flow component node.
|
||||||
|
*
|
||||||
|
* WARNING: This directive is not intended for public use.
|
||||||
|
*
|
||||||
|
* @param {string} appid
|
||||||
|
* @param {number} nodeid
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export const flowComponentDirective = directive(FlowComponentDirective);
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { flowComponentDirective } from './flow-component-directive.js';
|
||||||
|
import { render, html as litHtml } from 'lit';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the requested node in a form suitable for Lit template interpolation.
|
||||||
|
* @param {string} appid
|
||||||
|
* @param {number} nodeid
|
||||||
|
* @returns {any} a Lit directive
|
||||||
|
*/
|
||||||
|
function getNode(appid, nodeid) {
|
||||||
|
return flowComponentDirective(appid, nodeid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the nodes defined by the given node ids as the child nodes of the
|
||||||
|
* given root element.
|
||||||
|
* @param {string} appid
|
||||||
|
* @param {number[]} nodeIds
|
||||||
|
* @param {Element} root
|
||||||
|
*/
|
||||||
|
function setChildNodes(appid, nodeIds, root) {
|
||||||
|
render(litHtml`${nodeIds.map((id) => flowComponentDirective(appid, id))}`, root);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SimpleElementBindingStrategy::addChildren uses insertBefore to add child
|
||||||
|
* elements to the container. When the children are manually placed under
|
||||||
|
* another element, the call to insertBefore can occasionally fail due to
|
||||||
|
* an invalid reference node.
|
||||||
|
*
|
||||||
|
* This is a temporary workaround which patches the container's native API
|
||||||
|
* to not fail when called with invalid arguments.
|
||||||
|
*/
|
||||||
|
function patchVirtualContainer(container) {
|
||||||
|
const originalInsertBefore = container.insertBefore;
|
||||||
|
|
||||||
|
container.insertBefore = function (newNode, referenceNode) {
|
||||||
|
if (referenceNode && referenceNode.parentNode === this) {
|
||||||
|
return originalInsertBefore.call(this, newNode, referenceNode);
|
||||||
|
} else {
|
||||||
|
return originalInsertBefore.call(this, newNode, null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Vaadin ||= {};
|
||||||
|
window.Vaadin.FlowComponentHost ||= { patchVirtualContainer, getNode, setChildNodes };
|
||||||
@@ -0,0 +1,725 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { Debouncer } from '@vaadin/component-base/src/debounce.js';
|
||||||
|
import { timeOut, animationFrame } from '@vaadin/component-base/src/async.js';
|
||||||
|
import { Grid } from '@vaadin/grid/src/vaadin-grid.js';
|
||||||
|
import { isFocusable } from '@vaadin/grid/src/vaadin-grid-active-item-mixin.js';
|
||||||
|
import { GridFlowSelectionColumn } from './vaadin-grid-flow-selection-column.js';
|
||||||
|
|
||||||
|
window.Vaadin.Flow.gridConnector = {};
|
||||||
|
window.Vaadin.Flow.gridConnector.initLazy = (grid) => {
|
||||||
|
// Check whether the connector was already initialized for the grid
|
||||||
|
if (grid.$connector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataProviderController = grid._dataProviderController;
|
||||||
|
|
||||||
|
const requestDebouncerDelay = 150;
|
||||||
|
let requestDebouncer;
|
||||||
|
|
||||||
|
let lastRequestedRange = [0, 0];
|
||||||
|
|
||||||
|
const validSelectionModes = ['SINGLE', 'NONE', 'MULTI'];
|
||||||
|
let selectedKeys = {};
|
||||||
|
let selectionMode = 'SINGLE';
|
||||||
|
|
||||||
|
let sorterDirectionsSetFromServer = false;
|
||||||
|
|
||||||
|
grid.size = 0; // To avoid NaN here and there before we get proper data
|
||||||
|
grid.itemIdPath = 'key';
|
||||||
|
|
||||||
|
grid.$connector = {};
|
||||||
|
|
||||||
|
grid.$connector.hasRootRequestQueue = () => {
|
||||||
|
const { pendingRequests } = dataProviderController.rootCache;
|
||||||
|
return Object.keys(pendingRequests).length > 0 || !!requestDebouncer?.isActive();
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.$connector.doSelection = function (items, userOriginated) {
|
||||||
|
if (selectionMode === 'NONE' || !items.length || (userOriginated && grid.hasAttribute('disabled'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectionMode === 'SINGLE') {
|
||||||
|
selectedKeys = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedItemsChanged = false;
|
||||||
|
items.forEach((item) => {
|
||||||
|
const selectable = !userOriginated || grid.isItemSelectable(item);
|
||||||
|
selectedItemsChanged = selectedItemsChanged || selectable;
|
||||||
|
if (item && selectable) {
|
||||||
|
selectedKeys[item.key] = item;
|
||||||
|
item.selected = true;
|
||||||
|
if (userOriginated) {
|
||||||
|
grid.$server.select(item.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FYI: In single selection mode, the server can send items = [null]
|
||||||
|
// which means a "Deselect All" command.
|
||||||
|
const isSelectedItemDifferentOrNull = !grid.activeItem || !item || item.key != grid.activeItem.key;
|
||||||
|
if (!userOriginated && selectionMode === 'SINGLE' && isSelectedItemDifferentOrNull) {
|
||||||
|
grid.activeItem = item;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectedItemsChanged) {
|
||||||
|
grid.selectedItems = Object.values(selectedKeys);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.$connector.doDeselection = function (items, userOriginated) {
|
||||||
|
if (selectionMode === 'NONE' || !items.length || (userOriginated && grid.hasAttribute('disabled'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedSelectedItems = grid.selectedItems.slice();
|
||||||
|
while (items.length) {
|
||||||
|
const itemToDeselect = items.shift();
|
||||||
|
const selectable = !userOriginated || grid.isItemSelectable(itemToDeselect);
|
||||||
|
if (!selectable) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < updatedSelectedItems.length; i++) {
|
||||||
|
const selectedItem = updatedSelectedItems[i];
|
||||||
|
if (itemToDeselect?.key === selectedItem.key) {
|
||||||
|
updatedSelectedItems.splice(i, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (itemToDeselect) {
|
||||||
|
delete selectedKeys[itemToDeselect.key];
|
||||||
|
delete itemToDeselect.selected;
|
||||||
|
if (userOriginated) {
|
||||||
|
grid.$server.deselect(itemToDeselect.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grid.selectedItems = updatedSelectedItems;
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.__activeItemChanged = function (newVal, oldVal) {
|
||||||
|
if (selectionMode != 'SINGLE') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!newVal) {
|
||||||
|
if (oldVal && selectedKeys[oldVal.key]) {
|
||||||
|
if (grid.__deselectDisallowed) {
|
||||||
|
grid.activeItem = oldVal;
|
||||||
|
} else {
|
||||||
|
// The item instance may have changed since the item was stored as active item
|
||||||
|
// and information such as whether the item may be selected or deselected may
|
||||||
|
// be stale. Use data provider controller to get updated instance from grid
|
||||||
|
// cache.
|
||||||
|
oldVal = dataProviderController.getItemContext(oldVal).item;
|
||||||
|
grid.$connector.doDeselection([oldVal], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!selectedKeys[newVal.key]) {
|
||||||
|
grid.$connector.doSelection([newVal], true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
grid._createPropertyObserver('activeItem', '__activeItemChanged', true);
|
||||||
|
|
||||||
|
grid.__activeItemChangedDetails = function (newVal, oldVal) {
|
||||||
|
if (grid.__disallowDetailsOnClick) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// when grid is attached, newVal is not set and oldVal is undefined
|
||||||
|
// do nothing
|
||||||
|
if (newVal == null && oldVal === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newVal && !newVal.detailsOpened) {
|
||||||
|
grid.$server.setDetailsVisible(newVal.key);
|
||||||
|
} else {
|
||||||
|
grid.$server.setDetailsVisible(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
grid._createPropertyObserver('activeItem', '__activeItemChangedDetails', true);
|
||||||
|
|
||||||
|
grid.$connector.getViewportRange = function () {
|
||||||
|
const renderedRows = grid._getRenderedRows();
|
||||||
|
return [renderedRows.at(0)?.index ?? 0, renderedRows.at(-1)?.index ?? 0];
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.$connector.requestPage = function (page) {
|
||||||
|
// Adjust the requested page to be within the valid range in case
|
||||||
|
// the grid size has changed while fetchPage was debounced.
|
||||||
|
page = Math.min(page, Math.floor((grid.size - 1) / grid.pageSize));
|
||||||
|
|
||||||
|
// Determine what to fetch based on scroll position and not only
|
||||||
|
// what grid asked for
|
||||||
|
let viewportRange = grid.$connector.getViewportRange();
|
||||||
|
|
||||||
|
// The buffer size could be multiplied by some constant defined by the user,
|
||||||
|
// if he needs to reduce the number of items sent to the Grid to improve performance
|
||||||
|
// or to increase it to make Grid smoother when scrolling
|
||||||
|
const buffer = viewportRange[1] - viewportRange[0];
|
||||||
|
viewportRange[0] = Math.max(viewportRange[0] - buffer, 0);
|
||||||
|
viewportRange[1] = Math.min(viewportRange[1] + buffer, grid.size);
|
||||||
|
|
||||||
|
let viewportPageRange = [
|
||||||
|
Math.floor(viewportRange[0] / grid.pageSize),
|
||||||
|
Math.floor(viewportRange[1] / grid.pageSize)
|
||||||
|
];
|
||||||
|
|
||||||
|
// When the viewport doesn't contain the requested page or it doesn't contain any items from
|
||||||
|
// the requested level at all, it means that the scroll position has changed while fetchPage
|
||||||
|
// was debounced. For example, it can happen if the user scrolls the grid to the bottom and
|
||||||
|
// then immediately back to the top. In this case, the request for the last page will be left
|
||||||
|
// hanging. To avoid this, as a workaround, we reset the range to only include the requested page
|
||||||
|
// to make sure all hanging requests are resolved. After that, the grid requests the first page
|
||||||
|
// or whatever in the viewport again.
|
||||||
|
if (page < viewportPageRange[0] || page > viewportPageRange[1]) {
|
||||||
|
viewportPageRange = [page, page];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastRequestedRange[0] != viewportPageRange[0] || lastRequestedRange[1] != viewportPageRange[1]) {
|
||||||
|
lastRequestedRange = viewportPageRange;
|
||||||
|
const pageCount = viewportPageRange[1] - viewportPageRange[0] + 1;
|
||||||
|
grid.$server.setViewportRange(viewportPageRange[0] * grid.pageSize, pageCount * grid.pageSize);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.dataProvider = function (params, callback) {
|
||||||
|
if (params.pageSize != grid.pageSize) {
|
||||||
|
throw 'Invalid pageSize';
|
||||||
|
}
|
||||||
|
|
||||||
|
// size is controlled by the server (data communicator), so if the
|
||||||
|
// size is zero, we know that there is no data to fetch.
|
||||||
|
// This also prevents an empty grid getting stuck in a loading state.
|
||||||
|
// The connector does not cache empty pages, so if the grid requests
|
||||||
|
// data again, there would be no cache entry, causing a request to
|
||||||
|
// the server. However, the data communicator will never respond,
|
||||||
|
// as it assumes that the data is already cached.
|
||||||
|
if (grid.size === 0) {
|
||||||
|
callback([], 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestDebouncer = Debouncer.debounce(
|
||||||
|
requestDebouncer,
|
||||||
|
timeOut.after(grid._hasData ? requestDebouncerDelay : 0),
|
||||||
|
() => {
|
||||||
|
grid.$connector.requestPage(params.page);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.$connector.setSorterDirections = function (directions) {
|
||||||
|
sorterDirectionsSetFromServer = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
const sorters = Array.from(grid.querySelectorAll('vaadin-grid-sorter'));
|
||||||
|
|
||||||
|
// Sorters for hidden columns are removed from DOM but stored in the web component.
|
||||||
|
// We need to ensure that all the sorters are reset when using `grid.sort(null)`.
|
||||||
|
grid._sorters.forEach((sorter) => {
|
||||||
|
if (!sorters.includes(sorter)) {
|
||||||
|
sorters.push(sorter);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sorters.forEach((sorter) => {
|
||||||
|
sorter.direction = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply directions in correct order, depending on configured multi-sort priority.
|
||||||
|
// For the default "prepend" mode, directions need to be applied in reverse, in
|
||||||
|
// order for the sort indicators to match the order on the server. For "append"
|
||||||
|
// just keep the order passed from the server.
|
||||||
|
if (grid.multiSortPriority !== 'append') {
|
||||||
|
directions = directions.reverse();
|
||||||
|
}
|
||||||
|
directions.forEach(({ column, direction }) => {
|
||||||
|
sorters.forEach((sorter) => {
|
||||||
|
if (sorter.getAttribute('path') === column) {
|
||||||
|
sorter.direction = direction;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Manually trigger a re-render of the sorter priority indicators
|
||||||
|
// in case some of the sorters were hidden while being updated above
|
||||||
|
// and therefore didn't notify the grid about their direction change.
|
||||||
|
grid.__applySorters();
|
||||||
|
} finally {
|
||||||
|
sorterDirectionsSetFromServer = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let preventUpdateVisibleRowsActive = 0;
|
||||||
|
|
||||||
|
function preventUpdateVisibleRows(callback) {
|
||||||
|
try {
|
||||||
|
preventUpdateVisibleRowsActive++;
|
||||||
|
callback();
|
||||||
|
} finally {
|
||||||
|
preventUpdateVisibleRowsActive--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.__updateVisibleRows = function (...args) {
|
||||||
|
if (preventUpdateVisibleRowsActive === 0) {
|
||||||
|
Object.getPrototypeOf(this).__updateVisibleRows.call(this, ...args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.__updateRow = function (row, ...args) {
|
||||||
|
Object.getPrototypeOf(this).__updateRow.call(this, row, ...args);
|
||||||
|
|
||||||
|
// since no row can be selected when selection mode is NONE
|
||||||
|
// if selectionMode is set to NONE, remove aria-selected attribute from the row
|
||||||
|
if (selectionMode === validSelectionModes[1]) {
|
||||||
|
// selectionMode === NONE
|
||||||
|
row.removeAttribute('aria-selected');
|
||||||
|
Array.from(row.children).forEach((cell) => cell.removeAttribute('aria-selected'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.$connector.set = function (startIndex, items) {
|
||||||
|
const { rootCache } = dataProviderController;
|
||||||
|
items.forEach((item, i) => {
|
||||||
|
rootCache.items[startIndex + i] = item;
|
||||||
|
});
|
||||||
|
|
||||||
|
preventUpdateVisibleRows(() => {
|
||||||
|
grid.$connector.doSelection(items.filter((item) => item.selected));
|
||||||
|
grid.$connector.doDeselection(items.filter((item) => !item.selected && selectedKeys[item.key]));
|
||||||
|
|
||||||
|
items.forEach((item) => {
|
||||||
|
if (item.detailsOpened) {
|
||||||
|
grid.openItemDetails(item);
|
||||||
|
} else {
|
||||||
|
grid.closeItemDetails(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
grid.__updateVisibleRows(startIndex, startIndex + items.length - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the given items for a non-hierarchical grid.
|
||||||
|
*
|
||||||
|
* @param updatedItems the updated items array
|
||||||
|
*/
|
||||||
|
grid.$connector.updateFlatData = function (updatedItems) {
|
||||||
|
const { rootCache } = dataProviderController;
|
||||||
|
|
||||||
|
updatedItems.forEach((item) => {
|
||||||
|
const itemContext = dataProviderController.getItemContext(item);
|
||||||
|
if (!itemContext) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { index } = itemContext;
|
||||||
|
rootCache.items[index] = item;
|
||||||
|
|
||||||
|
preventUpdateVisibleRows(() => {
|
||||||
|
if (item.detailsOpened) {
|
||||||
|
grid.openItemDetails(item);
|
||||||
|
} else {
|
||||||
|
grid.closeItemDetails(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
grid.__updateVisibleRows(index, index);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.$connector.clear = function (index, length) {
|
||||||
|
const { rootCache } = dataProviderController;
|
||||||
|
|
||||||
|
if (index % grid.pageSize != 0) {
|
||||||
|
throw 'Got cleared data for index ' + index + ' which is not aligned with the page size of ' + grid.pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = rootCache.items.slice(index, index + length).filter(Boolean);
|
||||||
|
if (items.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
preventUpdateVisibleRows(() => {
|
||||||
|
grid.$connector.doDeselection(items.filter((item) => selectedKeys[item.key]));
|
||||||
|
items.forEach((item) => grid.closeItemDetails(item));
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = index; i < index + length; i++) {
|
||||||
|
rootCache.items[i] = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.__updateVisibleRows(index, index + length - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.$connector.reset = function () {
|
||||||
|
dataProviderController.clearCache();
|
||||||
|
lastRequestedRange = [-1, -1];
|
||||||
|
requestDebouncer?.cancel();
|
||||||
|
grid.__updateVisibleRows();
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.$connector.updateSize = (newSize) => (grid.size = newSize);
|
||||||
|
|
||||||
|
grid.$connector.updateUniqueItemIdPath = (path) => (grid.itemIdPath = path);
|
||||||
|
|
||||||
|
grid.$connector.confirm = function (id) {
|
||||||
|
// We're done applying changes from this batch, resolve pending
|
||||||
|
// callbacks
|
||||||
|
const { rootCache } = dataProviderController;
|
||||||
|
|
||||||
|
grid._hasData = true;
|
||||||
|
|
||||||
|
Object.entries(rootCache.pendingRequests).forEach(([page, callback]) => {
|
||||||
|
const lastAvailablePage = grid.size ? Math.ceil(grid.size / grid.pageSize) - 1 : 0;
|
||||||
|
// It's possible that the lastRequestedRange includes a page that's beyond lastAvailablePage if the grid's size got reduced during an ongoing data request
|
||||||
|
const lastRequestedRangeEnd = Math.min(lastRequestedRange[1], lastAvailablePage);
|
||||||
|
// Resolve if we have data or if we don't expect to get data
|
||||||
|
const startIndex = page * grid.pageSize;
|
||||||
|
if (rootCache.items[startIndex] !== undefined) {
|
||||||
|
// Cached data is available, resolve the callback
|
||||||
|
callback([]);
|
||||||
|
} else if (page < lastRequestedRange[0] || +page > lastRequestedRangeEnd) {
|
||||||
|
// No cached data, resolve the callback with an empty array
|
||||||
|
callback(new Array(grid.pageSize));
|
||||||
|
// Request grid for content update
|
||||||
|
grid.requestContentUpdate();
|
||||||
|
} else if (callback && grid.size === 0) {
|
||||||
|
// The grid has 0 items => resolve the callback with an empty array
|
||||||
|
callback([]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If all pending requests have already been resolved (which can happen
|
||||||
|
// for example if the server sent preloaded data while the grid had
|
||||||
|
// already made its own requests), cancel the request debouncer to
|
||||||
|
// prevent further unnecessary calls.
|
||||||
|
if (Object.keys(rootCache.pendingRequests).length === 0) {
|
||||||
|
requestDebouncer?.cancel();
|
||||||
|
lastRequestedRange = [-1, -1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let server know we're done
|
||||||
|
grid.$server.confirmUpdate(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.$connector.setSelectionMode = function (mode) {
|
||||||
|
if ((typeof mode === 'string' || mode instanceof String) && validSelectionModes.indexOf(mode) >= 0) {
|
||||||
|
selectionMode = mode;
|
||||||
|
selectedKeys = {};
|
||||||
|
grid.selectedItems = [];
|
||||||
|
grid.$connector.updateMultiSelectable();
|
||||||
|
} else {
|
||||||
|
throw 'Attempted to set an invalid selection mode';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Manage aria-multiselectable attribute depending on the selection mode.
|
||||||
|
* see more: https://github.com/vaadin/web-components/issues/1536
|
||||||
|
* or: https://www.w3.org/TR/wai-aria-1.1/#aria-multiselectable
|
||||||
|
* For selection mode SINGLE, set the aria-multiselectable attribute to false
|
||||||
|
*/
|
||||||
|
grid.$connector.updateMultiSelectable = function () {
|
||||||
|
if (!grid.$) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectionMode === validSelectionModes[0]) {
|
||||||
|
grid.$.table.setAttribute('aria-multiselectable', false);
|
||||||
|
// For selection mode NONE, remove the aria-multiselectable attribute
|
||||||
|
} else if (selectionMode === validSelectionModes[1]) {
|
||||||
|
grid.$.table.removeAttribute('aria-multiselectable');
|
||||||
|
// For selection mode MULTI, set aria-multiselectable to true
|
||||||
|
} else {
|
||||||
|
grid.$.table.setAttribute('aria-multiselectable', true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Have the multi-selectable state updated on attach
|
||||||
|
grid._createPropertyObserver('isAttached', () => grid.$connector.updateMultiSelectable());
|
||||||
|
|
||||||
|
const singleTimeRenderer = (renderer) => {
|
||||||
|
return (root) => {
|
||||||
|
if (renderer) {
|
||||||
|
renderer(root);
|
||||||
|
renderer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.$connector.setHeaderRenderer = function (column, options) {
|
||||||
|
const { content, showSorter, sorterPath } = options;
|
||||||
|
|
||||||
|
if (content === null) {
|
||||||
|
column.headerRenderer = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
column.headerRenderer = singleTimeRenderer((root) => {
|
||||||
|
// Clear previous contents
|
||||||
|
root.innerHTML = '';
|
||||||
|
// Render sorter
|
||||||
|
let contentRoot = root;
|
||||||
|
if (showSorter) {
|
||||||
|
const sorter = document.createElement('vaadin-grid-sorter');
|
||||||
|
sorter.setAttribute('path', sorterPath);
|
||||||
|
const ariaLabel = content instanceof Node ? content.textContent : content;
|
||||||
|
if (ariaLabel) {
|
||||||
|
sorter.setAttribute('aria-label', `Sort by ${ariaLabel}`);
|
||||||
|
}
|
||||||
|
root.appendChild(sorter);
|
||||||
|
|
||||||
|
// Use sorter as content root
|
||||||
|
contentRoot = sorter;
|
||||||
|
}
|
||||||
|
// Add content
|
||||||
|
if (content instanceof Node) {
|
||||||
|
contentRoot.appendChild(content);
|
||||||
|
} else {
|
||||||
|
contentRoot.textContent = content;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// This method is overridden to prevent the grid web component from
|
||||||
|
// automatically excluding columns from sorting when they get hidden.
|
||||||
|
// In Flow, it's the developer's responsibility to remove the column
|
||||||
|
// from the backend sort order when the column gets hidden.
|
||||||
|
grid._getActiveSorters = function () {
|
||||||
|
return this._sorters.filter((sorter) => sorter.direction);
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.__applySorters = function (...args) {
|
||||||
|
const sorters = grid._mapSorters();
|
||||||
|
const sortersChanged = JSON.stringify(grid._previousSorters) !== JSON.stringify(sorters);
|
||||||
|
|
||||||
|
// Update the _previousSorters in vaadin-grid-sort-mixin so that the __applySorters
|
||||||
|
// method in the mixin will skip calling clearCache().
|
||||||
|
//
|
||||||
|
// In Flow Grid's case, we never want to clear the cache eagerly when the sorter elements
|
||||||
|
// change due to one of the following reasons:
|
||||||
|
//
|
||||||
|
// 1. Sorted by user: The items in the new sort order need to be fetched from the server,
|
||||||
|
// and we want to avoid a heavy re-render before the updated items have actually been fetched.
|
||||||
|
//
|
||||||
|
// 2. Sorted programmatically on the server: The items in the new sort order have already
|
||||||
|
// been fetched and applied to the grid. The sorter element states are updated programmatically
|
||||||
|
// to reflect the new sort order, but there's no need to re-render the grid rows.
|
||||||
|
grid._previousSorters = sorters;
|
||||||
|
|
||||||
|
// Call the original __applySorters method in vaadin-grid-sort-mixin
|
||||||
|
Object.getPrototypeOf(this).__applySorters.call(this, ...args);
|
||||||
|
|
||||||
|
if (sortersChanged && !sorterDirectionsSetFromServer) {
|
||||||
|
grid.$server.sortersChanged(sorters);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.$connector.setFooterRenderer = function (column, options) {
|
||||||
|
const { content } = options;
|
||||||
|
|
||||||
|
if (content === null) {
|
||||||
|
column.footerRenderer = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
column.footerRenderer = singleTimeRenderer((root) => {
|
||||||
|
// Clear previous contents
|
||||||
|
root.innerHTML = '';
|
||||||
|
// Add content
|
||||||
|
if (content instanceof Node) {
|
||||||
|
root.appendChild(content);
|
||||||
|
} else {
|
||||||
|
root.textContent = content;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.addEventListener('vaadin-context-menu-before-open', function (e) {
|
||||||
|
const { key, columnId } = e.detail;
|
||||||
|
grid.$server.updateContextMenuTargetItem(key, columnId);
|
||||||
|
});
|
||||||
|
|
||||||
|
grid.getContextMenuBeforeOpenDetail = function (event) {
|
||||||
|
// For `contextmenu` events, we need to access the source event,
|
||||||
|
// when using open on click we just use the click event itself
|
||||||
|
const sourceEvent = event.detail.sourceEvent || event;
|
||||||
|
const eventContext = grid.getEventContext(sourceEvent);
|
||||||
|
const key = eventContext.item?.key || '';
|
||||||
|
const columnId = eventContext.column?.id || '';
|
||||||
|
return { key, columnId };
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.preventContextMenu = function (event) {
|
||||||
|
const isLeftClick = event.type === 'click';
|
||||||
|
const { column } = grid.getEventContext(event);
|
||||||
|
|
||||||
|
return isLeftClick && column instanceof GridFlowSelectionColumn;
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.addEventListener('click', (e) => _fireClickEvent(e, 'item-click'));
|
||||||
|
grid.addEventListener('dblclick', (e) => _fireClickEvent(e, 'item-double-click'));
|
||||||
|
|
||||||
|
grid.addEventListener('column-resize', (e) => {
|
||||||
|
const cols = grid._getColumnsInOrder().filter((col) => !col.hidden);
|
||||||
|
|
||||||
|
cols.forEach((col) => {
|
||||||
|
col.dispatchEvent(new CustomEvent('column-drag-resize'));
|
||||||
|
});
|
||||||
|
|
||||||
|
grid.dispatchEvent(
|
||||||
|
new CustomEvent('column-drag-resize', {
|
||||||
|
detail: {
|
||||||
|
resizedColumnKey: e.detail.resizedColumn._flowId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
grid.addEventListener('column-reorder', (e) => {
|
||||||
|
const columns = grid._columnTree
|
||||||
|
.slice(0)
|
||||||
|
.pop()
|
||||||
|
.filter((c) => c._flowId)
|
||||||
|
.sort((b, a) => b._order - a._order)
|
||||||
|
.map((c) => c._flowId);
|
||||||
|
|
||||||
|
grid.dispatchEvent(
|
||||||
|
new CustomEvent('column-reorder-all-columns', {
|
||||||
|
detail: { columns }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
grid.addEventListener('cell-focus', (e) => {
|
||||||
|
const eventContext = grid.getEventContext(e);
|
||||||
|
const expectedSectionValues = ['header', 'body', 'footer'];
|
||||||
|
|
||||||
|
if (expectedSectionValues.indexOf(eventContext.section) === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.dispatchEvent(
|
||||||
|
new CustomEvent('grid-cell-focus', {
|
||||||
|
detail: {
|
||||||
|
itemKey: eventContext.item ? eventContext.item.key : null,
|
||||||
|
|
||||||
|
internalColumnId: eventContext.column ? eventContext.column._flowId : null,
|
||||||
|
|
||||||
|
section: eventContext.section
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function _fireClickEvent(event, eventName) {
|
||||||
|
// Click event was handled by the component inside grid, do nothing.
|
||||||
|
if (event.defaultPrevented) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = event.composedPath();
|
||||||
|
const idx = path.findIndex((node) => node.localName === 'td' || node.localName === 'th');
|
||||||
|
const cell = path[idx];
|
||||||
|
const content = path.slice(0, idx);
|
||||||
|
|
||||||
|
// Do not fire item click event if the click originated inside a Vaadin overlay
|
||||||
|
// (Select, ComboBox, DatePicker, MenuBar, ContextMenu, ...). The overlay's
|
||||||
|
// menu/list lives in the host component's light DOM, so its clicks bubble
|
||||||
|
// through the cell — but by the time we get here, the overlay has typically
|
||||||
|
// been hidden synchronously, defeating the offsetParent-based isFocusable check.
|
||||||
|
if (content.some((node) => typeof node.localName === 'string' && node.localName.endsWith('-overlay'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not fire item click event if cell content contains focusable elements.
|
||||||
|
// Use this instead of event.target to detect cases like icon inside button.
|
||||||
|
// See https://github.com/vaadin/flow-components/issues/4065
|
||||||
|
if (
|
||||||
|
content.some((node) => {
|
||||||
|
// Ignore focus buttons that the component renders into cells in focus button mode on MacOS
|
||||||
|
const focusable = cell?._focusButton !== node && isFocusable(node);
|
||||||
|
return focusable || node instanceof HTMLLabelElement;
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventContext = grid.getEventContext(event);
|
||||||
|
const section = eventContext.section;
|
||||||
|
|
||||||
|
if (eventContext.item && section !== 'details') {
|
||||||
|
event.itemKey = eventContext.item.key;
|
||||||
|
// if you have a details-renderer, getEventContext().column is undefined
|
||||||
|
if (eventContext.column) {
|
||||||
|
event.internalColumnId = eventContext.column._flowId;
|
||||||
|
}
|
||||||
|
grid.dispatchEvent(new CustomEvent(eventName, { detail: event }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.cellPartNameGenerator = function (column, rowData) {
|
||||||
|
const part = rowData.item.part;
|
||||||
|
if (!part) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return (part.row || '') + ' ' + ((column && part[column._flowId]) || '');
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.dropFilter = (rowData) => rowData.item && !rowData.item.dropDisabled;
|
||||||
|
|
||||||
|
grid.dragFilter = (rowData) => rowData.item && !rowData.item.dragDisabled;
|
||||||
|
|
||||||
|
grid.addEventListener('grid-dragstart', (e) => {
|
||||||
|
if (grid._isSelected(e.detail.draggedItems[0])) {
|
||||||
|
// Dragging selected (possibly multiple) items
|
||||||
|
if (grid.__selectionDragData) {
|
||||||
|
Object.keys(grid.__selectionDragData).forEach((type) => {
|
||||||
|
e.detail.setDragData(type, grid.__selectionDragData[type]);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
(grid.__dragDataTypes || []).forEach((type) => {
|
||||||
|
e.detail.setDragData(type, e.detail.draggedItems.map((item) => item.dragData[type]).join('\n'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grid.__selectionDraggedItemsCount > 1) {
|
||||||
|
e.detail.setDraggedItemsCount(grid.__selectionDraggedItemsCount);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Dragging just one (non-selected) item
|
||||||
|
(grid.__dragDataTypes || []).forEach((type) => {
|
||||||
|
e.detail.setDragData(type, e.detail.draggedItems[0].dragData[type]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
grid.isItemSelectable = (item) => {
|
||||||
|
// If there is no selectable data, assume the item is selectable
|
||||||
|
return item?.selectable === undefined || item.selectable;
|
||||||
|
};
|
||||||
|
|
||||||
|
function isRowFullyInViewport(row) {
|
||||||
|
const rowRect = row.getBoundingClientRect();
|
||||||
|
const tableRect = grid.$.table.getBoundingClientRect();
|
||||||
|
const headerRect = grid.$.header.getBoundingClientRect();
|
||||||
|
const footerRect = grid.$.footer.getBoundingClientRect();
|
||||||
|
return rowRect.top >= tableRect.top + headerRect.height && rowRect.bottom <= tableRect.bottom - footerRect.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.$connector.scrollToItem = function (itemKey, ...args) {
|
||||||
|
const targetRow = grid._getRenderedRows().find((row) => {
|
||||||
|
const { item } = grid.__getRowModel(row);
|
||||||
|
return grid.getItemId(item) === itemKey;
|
||||||
|
});
|
||||||
|
if (targetRow && isRowFullyInViewport(targetRow)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.scrollToIndex(...args);
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './Flow';
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './Flow';
|
||||||
|
//# sourceMappingURL=index.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/main/frontend/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC","sourcesContent":["export * from './Flow';\n"]}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/* eslint-disable no-restricted-syntax */
|
||||||
|
/* eslint-disable max-params */
|
||||||
|
import { html, render } from 'lit';
|
||||||
|
import { live } from 'lit/directives/live.js';
|
||||||
|
|
||||||
|
type RenderRoot = HTMLElement & { __litRenderer?: Renderer; _$litPart$?: any };
|
||||||
|
|
||||||
|
type ItemModel = { item: any; index: number };
|
||||||
|
|
||||||
|
type Renderer = ((root: RenderRoot, rendererOwner: HTMLElement, model: ItemModel) => void) & { __rendererId?: string };
|
||||||
|
|
||||||
|
type Component = HTMLElement & { [key: string]: Renderer | undefined };
|
||||||
|
|
||||||
|
const _window = window as any;
|
||||||
|
_window.Vaadin = _window.Vaadin || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns the component a renderer function which uses Lit to render
|
||||||
|
* the given template expression inside the render root element.
|
||||||
|
*
|
||||||
|
* @param component The host component to which the renderer runction is to be set
|
||||||
|
* @param rendererName The name of the renderer function
|
||||||
|
* @param templateExpression The content of the template literal passed to Lit for rendering.
|
||||||
|
* @param returnChannel A channel to the server.
|
||||||
|
* Calling it will end up invoking a handler in the server-side LitRenderer.
|
||||||
|
* @param clientCallables A list of function names that can be called from within the template literal.
|
||||||
|
* @param propertyNamespace LitRenderer-specific namespace for properties.
|
||||||
|
* Needed to avoid property name collisions between renderers.
|
||||||
|
*/
|
||||||
|
_window.Vaadin.setLitRenderer = (
|
||||||
|
component: Component,
|
||||||
|
rendererName: string,
|
||||||
|
templateExpression: string,
|
||||||
|
returnChannel: (name: string, itemKey: string, args: any[]) => void,
|
||||||
|
clientCallables: string[],
|
||||||
|
propertyNamespace: string,
|
||||||
|
appId: string
|
||||||
|
) => {
|
||||||
|
const callablesCreator = (itemKey: string) => {
|
||||||
|
return clientCallables.map((clientCallable) => (...args: any[]) => {
|
||||||
|
if (itemKey !== undefined) {
|
||||||
|
returnChannel(clientCallable, itemKey, args[0] instanceof Event ? [] : [...args]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const fnArgs = [
|
||||||
|
'html',
|
||||||
|
'root',
|
||||||
|
'live',
|
||||||
|
'appId',
|
||||||
|
'itemKey',
|
||||||
|
'model',
|
||||||
|
'item',
|
||||||
|
'index',
|
||||||
|
...clientCallables,
|
||||||
|
`return html\`${templateExpression}\``
|
||||||
|
];
|
||||||
|
const htmlGenerator = new Function(...fnArgs);
|
||||||
|
const renderFunction = (root: RenderRoot, model: ItemModel, itemKey: string) => {
|
||||||
|
const { item, index } = model;
|
||||||
|
render(htmlGenerator(html, root, live, appId, itemKey, model, item, index, ...callablesCreator(itemKey)), root);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderer: Renderer = (root, _, model) => {
|
||||||
|
const { item } = model;
|
||||||
|
// Clean up the root element of any existing content
|
||||||
|
// (and Lit's _$litPart$ property) from other renderers
|
||||||
|
// TODO: Remove once https://github.com/vaadin/web-components/issues/2235 is done
|
||||||
|
if (root.__litRenderer !== renderer) {
|
||||||
|
root.innerHTML = '';
|
||||||
|
delete root._$litPart$;
|
||||||
|
root.__litRenderer = renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map a new item that only includes the properties defined by
|
||||||
|
// this specific LitRenderer instance. The renderer instance specific
|
||||||
|
// "propertyNamespace" prefix is stripped from the property name at this point:
|
||||||
|
//
|
||||||
|
// item: { key: "2", lr_3769df5394a74ef3_lastName: "Tyler"}
|
||||||
|
// ->
|
||||||
|
// mappedItem: { lastName: "Tyler" }
|
||||||
|
const mappedItem: { [key: string]: any } = {};
|
||||||
|
for (const key in item) {
|
||||||
|
if (key.startsWith(propertyNamespace)) {
|
||||||
|
mappedItem[key.replace(propertyNamespace, '')] = item[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFunction(root, { ...model, item: mappedItem }, item.key);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderer.__rendererId = propertyNamespace;
|
||||||
|
component[rendererName] = renderer;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the renderer function with the given name from the component
|
||||||
|
* if the propertyNamespace matches the renderer's id.
|
||||||
|
*
|
||||||
|
* @param component The host component whose renderer function is to be removed
|
||||||
|
* @param rendererName The name of the renderer function
|
||||||
|
* @param rendererId The rendererId of the function to be removed
|
||||||
|
*/
|
||||||
|
_window.Vaadin.unsetLitRenderer = (component: Component, rendererName: string, rendererId: string) => {
|
||||||
|
// The check for __rendererId property is necessary since the renderer function
|
||||||
|
// may get overridden by another renderer, for example, by one coming from
|
||||||
|
// vaadin-template-renderer. We don't want LitRenderer registration cleanup to
|
||||||
|
// unintentionally remove the new renderer.
|
||||||
|
if (component[rendererName]?.__rendererId === rendererId) {
|
||||||
|
component[rendererName] = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
import './contextMenuConnector.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the connector for a menu bar element.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} menubar
|
||||||
|
* @param {string} appId
|
||||||
|
*/
|
||||||
|
function initLazy(menubar, appId) {
|
||||||
|
if (menubar.$connector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new MutationObserver((records) => {
|
||||||
|
const hasChangedAttributes = records.some((entry) => {
|
||||||
|
const oldValue = entry.oldValue;
|
||||||
|
const newValue = entry.target.getAttribute(entry.attributeName);
|
||||||
|
return oldValue !== newValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasChangedAttributes) {
|
||||||
|
menubar.$connector.generateItems();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
menubar.$connector = {
|
||||||
|
/**
|
||||||
|
* Generates and assigns the items to the menu bar.
|
||||||
|
*
|
||||||
|
* When the method is called without providing a node id,
|
||||||
|
* the previously generated items tree will be used.
|
||||||
|
* That can be useful if you only want to sync the disabled and hidden properties of root items.
|
||||||
|
*
|
||||||
|
* @param {number | undefined} nodeId
|
||||||
|
*/
|
||||||
|
generateItems(nodeId) {
|
||||||
|
if (!menubar.shadowRoot) {
|
||||||
|
// workaround for https://github.com/vaadin/flow/issues/5722
|
||||||
|
setTimeout(() => menubar.$connector.generateItems(nodeId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!menubar._container) {
|
||||||
|
// Menu-bar defers first buttons render to avoid re-layout
|
||||||
|
// See https://github.com/vaadin/web-components/issues/7271
|
||||||
|
queueMicrotask(() => menubar.$connector.generateItems(nodeId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeId) {
|
||||||
|
menubar.__generatedItems = window.Vaadin.Flow.contextMenuConnector.generateItemsTree(appId, nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
let items = menubar.__generatedItems || [];
|
||||||
|
|
||||||
|
items.forEach((item) => {
|
||||||
|
// Propagate disabled state from items to parent buttons
|
||||||
|
item.disabled = item.component.disabled;
|
||||||
|
|
||||||
|
// Saving item to component because `_item` can be reassigned to a new value
|
||||||
|
// when the component goes to the overflow menu
|
||||||
|
item.component._rootItem = item;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Observe for hidden and disabled attributes in case they are changed by Flow.
|
||||||
|
// When a change occurs, the observer will re-generate items on top of the existing tree
|
||||||
|
// to sync the new attribute values with the corresponding properties in the items array.
|
||||||
|
items.forEach((item) => {
|
||||||
|
observer.observe(item.component, {
|
||||||
|
attributeFilter: ['hidden', 'disabled'],
|
||||||
|
attributeOldValue: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove hidden items entirely from the array. Just hiding them
|
||||||
|
// could cause the overflow button to be rendered without items.
|
||||||
|
//
|
||||||
|
// The items-prop needs to be set even when all items are visible
|
||||||
|
// to update the disabled state and re-render buttons.
|
||||||
|
items = items.filter((item) => !item.component.hidden);
|
||||||
|
|
||||||
|
menubar.items = items;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setClassName(component) {
|
||||||
|
const item = component._rootItem || component._item;
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
item.className = component.className;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Vaadin.Flow.menubarConnector = { initLazy, setClassName };
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a DateTimeFormat with the given locale, or throws if invalid.
|
||||||
|
*/
|
||||||
|
function createDateTimeFormatter(locale) {
|
||||||
|
return new Intl.DateTimeFormat(locale, {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFormatter(locale) {
|
||||||
|
// Try creating formatter with progressive fallbacks
|
||||||
|
const localeParts = locale?.split('-');
|
||||||
|
const fallbackLocales = [
|
||||||
|
locale, // Full locale (e.g., "de-DE-hw")
|
||||||
|
localeParts?.slice(0, 2).join('-'), // Base locale without variant (e.g., "de-DE")
|
||||||
|
localeParts?.[0] // Language only (e.g., "de")
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const fallbackLocale of fallbackLocales) {
|
||||||
|
try {
|
||||||
|
return createDateTimeFormatter(fallbackLocale);
|
||||||
|
} catch (e) {
|
||||||
|
// Continue to next fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return createDateTimeFormatter(undefined); // Default locale
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the given items to a new array of items with formatted time.
|
||||||
|
*/
|
||||||
|
function formatItems(items, locale) {
|
||||||
|
const formatter = getFormatter(locale);
|
||||||
|
|
||||||
|
return items.map((item) =>
|
||||||
|
item.time
|
||||||
|
? Object.assign(item, {
|
||||||
|
time: formatter.format(new Date(item.time))
|
||||||
|
})
|
||||||
|
: item
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Vaadin.Flow.messageListConnector = {
|
||||||
|
/**
|
||||||
|
* Initializes the connector for the given message list element.
|
||||||
|
* Sets up event listeners for attachment clicks.
|
||||||
|
*/
|
||||||
|
initLazy(list) {
|
||||||
|
if (list._flowConnectorInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
list._flowConnectorInitialized = true;
|
||||||
|
|
||||||
|
// Listen for attachment-click events and re-dispatch with indexes for Flow
|
||||||
|
list.addEventListener('attachment-click', (e) => {
|
||||||
|
const { item, attachment } = e.detail;
|
||||||
|
|
||||||
|
// Compute indexes from the item and attachment references
|
||||||
|
const itemIndex = list.items.indexOf(item);
|
||||||
|
const attachmentIndex = item?.attachments?.indexOf(attachment) ?? -1;
|
||||||
|
|
||||||
|
// Only dispatch if indexes are valid
|
||||||
|
if (itemIndex < 0 || attachmentIndex < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch a new event with indexes for Flow's @DomEvent handling
|
||||||
|
list.dispatchEvent(
|
||||||
|
new CustomEvent('attachment-click-flow', {
|
||||||
|
detail: { itemIndex, attachmentIndex }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fully replaces the items in the list with the given items.
|
||||||
|
*/
|
||||||
|
setItems(list, items, locale) {
|
||||||
|
this.initLazy(list);
|
||||||
|
list.items = formatItems(items, locale);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the text of the item at the given index to the given text.
|
||||||
|
*/
|
||||||
|
setItemText(list, text, index) {
|
||||||
|
list.items[index].text = text;
|
||||||
|
list.items = [...list.items];
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the given text to the text of the item at the given index.
|
||||||
|
*/
|
||||||
|
appendItemText(list, appendedText, index) {
|
||||||
|
const currentText = list.items[index].text || '';
|
||||||
|
this.setItemText(list, currentText + appendedText, index);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given items to the end of the list.
|
||||||
|
*/
|
||||||
|
addItems(list, newItems, locale) {
|
||||||
|
this.initLazy(list);
|
||||||
|
list.items = [...(list.items || []), ...formatItems(newItems, locale)];
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
window.Vaadin.Flow.selectConnector = {};
|
||||||
|
window.Vaadin.Flow.selectConnector.initLazy = (select) => {
|
||||||
|
// do not init this connector twice for the given select
|
||||||
|
if (select.$connector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
select.$connector = {};
|
||||||
|
|
||||||
|
select.renderer = (root) => {
|
||||||
|
const listBox = select.querySelector('vaadin-select-list-box');
|
||||||
|
if (listBox) {
|
||||||
|
if (root.firstChild) {
|
||||||
|
root.removeChild(root.firstChild);
|
||||||
|
}
|
||||||
|
root.appendChild(listBox);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
import stripCssComments from 'strip-css-comments';
|
||||||
|
|
||||||
|
// Safari 15 - 16.3, polyfilled
|
||||||
|
const polyfilledSafari = CSSStyleSheet.toString().includes('document.createElement');
|
||||||
|
|
||||||
|
const createLinkReferences = (css, target) => {
|
||||||
|
// Unresolved urls are written as '@import url(text);' or '@import "text";' to the css
|
||||||
|
// media query can be present on @media tag or on @import directive after url
|
||||||
|
// Note that with Vite production build there is no space between @import and "text"
|
||||||
|
// [0] is the full match
|
||||||
|
// [1] matches the media query
|
||||||
|
// [2] matches the url
|
||||||
|
// [3] matches the quote char surrounding in '@import "..."'
|
||||||
|
// [4] matches the url in '@import "..."'
|
||||||
|
// [5] matches media query on @import statement
|
||||||
|
const importMatcher =
|
||||||
|
/(?:@media\s(.+?))?(?:\s{)?\@import\s*(?:url\(\s*['"]?(.+?)['"]?\s*\)|(["'])((?:\\.|[^\\])*?)\3)([^;]*);(?:})?/g;
|
||||||
|
|
||||||
|
// Only cleanup if comment exist
|
||||||
|
if (/\/\*(.|[\r\n])*?\*\//gm.exec(css) != null) {
|
||||||
|
// clean up comments
|
||||||
|
css = stripCssComments(css);
|
||||||
|
}
|
||||||
|
|
||||||
|
var match;
|
||||||
|
var styleCss = css;
|
||||||
|
|
||||||
|
// For each external url import add a link reference
|
||||||
|
while ((match = importMatcher.exec(css)) !== null) {
|
||||||
|
styleCss = styleCss.replace(match[0], '');
|
||||||
|
const link = document.createElement('link');
|
||||||
|
link.rel = 'stylesheet';
|
||||||
|
link.href = match[2] || match[4];
|
||||||
|
const media = (match[1] || match[5] || '').trim();
|
||||||
|
if (media) {
|
||||||
|
link.media = media;
|
||||||
|
}
|
||||||
|
// For target document append to head else append to target
|
||||||
|
if (target === document) {
|
||||||
|
document.head.appendChild(link);
|
||||||
|
} else {
|
||||||
|
target.appendChild(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return styleCss;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addAdoptedStyleSafariPolyfill = (sheet, target, first) => {
|
||||||
|
if (first) {
|
||||||
|
target.adoptedStyleSheets = [sheet, ...target.adoptedStyleSheets];
|
||||||
|
} else {
|
||||||
|
target.adoptedStyleSheets = [...target.adoptedStyleSheets, sheet];
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
target.adoptedStyleSheets = target.adoptedStyleSheets.filter((ss) => ss !== sheet);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const addAdoptedStyle = (cssText, target, first) => {
|
||||||
|
const sheet = new CSSStyleSheet();
|
||||||
|
sheet.replaceSync(cssText);
|
||||||
|
if (polyfilledSafari) {
|
||||||
|
return addAdoptedStyleSafariPolyfill(sheet, target, first);
|
||||||
|
}
|
||||||
|
if (first) {
|
||||||
|
target.adoptedStyleSheets.splice(0, 0, sheet);
|
||||||
|
} else {
|
||||||
|
target.adoptedStyleSheets.push(sheet);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
target.adoptedStyleSheets.splice(target.adoptedStyleSheets.indexOf(sheet), 1);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const addStyleTag = (cssText, referenceComment) => {
|
||||||
|
const styleTag = document.createElement('style');
|
||||||
|
styleTag.type = 'text/css';
|
||||||
|
styleTag.textContent = cssText;
|
||||||
|
|
||||||
|
let beforeThis = undefined;
|
||||||
|
if (referenceComment) {
|
||||||
|
const comments = Array.from(document.head.childNodes).filter((elem) => elem.nodeType === Node.COMMENT_NODE);
|
||||||
|
const container = comments.find((comment) => comment.data.trim() === referenceComment);
|
||||||
|
if (container) {
|
||||||
|
beforeThis = container;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.head.insertBefore(styleTag, beforeThis);
|
||||||
|
return () => {
|
||||||
|
styleTag.remove();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// target: Document | ShadowRoot
|
||||||
|
export const injectGlobalCss = (css, referenceComment, target, first) => {
|
||||||
|
if (target === document) {
|
||||||
|
const hash = getHash(css);
|
||||||
|
if (window.Vaadin.theme.injectedGlobalCss.indexOf(hash) !== -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.Vaadin.theme.injectedGlobalCss.push(hash);
|
||||||
|
}
|
||||||
|
const cssText = createLinkReferences(css, target);
|
||||||
|
|
||||||
|
// We avoid mixing style tags and adoptedStyleSheets to make override order clear
|
||||||
|
if (target === document) {
|
||||||
|
return addStyleTag(cssText, referenceComment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return addAdoptedStyle(cssText, target, first);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.Vaadin = window.Vaadin || {};
|
||||||
|
window.Vaadin.theme = window.Vaadin.theme || {};
|
||||||
|
window.Vaadin.theme.injectedGlobalCss = [];
|
||||||
|
|
||||||
|
const webcomponentGlobalCss = {
|
||||||
|
css: [],
|
||||||
|
importers: []
|
||||||
|
};
|
||||||
|
|
||||||
|
export const injectGlobalWebcomponentCss = (css) => {
|
||||||
|
webcomponentGlobalCss.css.push(css);
|
||||||
|
webcomponentGlobalCss.importers.forEach((registrar) => {
|
||||||
|
registrar(css);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const webcomponentGlobalCssInjector = (registrar) => {
|
||||||
|
const registeredCss = [];
|
||||||
|
const wrapper = (css) => {
|
||||||
|
const hash = getHash(css);
|
||||||
|
if (!registeredCss.includes(hash)) {
|
||||||
|
registeredCss.push(hash);
|
||||||
|
registrar(css);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
webcomponentGlobalCss.importers.push(wrapper);
|
||||||
|
webcomponentGlobalCss.css.forEach(wrapper);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate a 32 bit FNV-1a hash
|
||||||
|
* Found here: https://gist.github.com/vaiorabbit/5657561
|
||||||
|
* Ref.: http://isthe.com/chongo/tech/comp/fnv/
|
||||||
|
*
|
||||||
|
* @param {string} str the input value
|
||||||
|
* @returns {string} 32 bit (as 8 byte hex string)
|
||||||
|
*/
|
||||||
|
function hashFnv32a(str) {
|
||||||
|
/*jshint bitwise:false */
|
||||||
|
let i,
|
||||||
|
l,
|
||||||
|
hval = 0x811c9dc5;
|
||||||
|
|
||||||
|
for (i = 0, l = str.length; i < l; i++) {
|
||||||
|
hval ^= str.charCodeAt(i);
|
||||||
|
hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to 8 digit hex string
|
||||||
|
return ('0000000' + (hval >>> 0).toString(16)).substr(-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate a 64 bit hash for the given input.
|
||||||
|
* Double hash is used to significantly lower the collision probability.
|
||||||
|
*
|
||||||
|
* @param {string} input value to get hash for
|
||||||
|
* @returns {string} 64 bit (as 16 byte hex string)
|
||||||
|
*/
|
||||||
|
function getHash(input) {
|
||||||
|
let h1 = hashFnv32a(input); // returns 32 bit (as 8 byte hex string)
|
||||||
|
return h1 + hashFnv32a(h1 + input);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Tooltip } from '@vaadin/tooltip/src/vaadin-tooltip.js';
|
||||||
|
|
||||||
|
const _window = window as any;
|
||||||
|
_window.Vaadin ||= {};
|
||||||
|
_window.Vaadin.Flow ||= {};
|
||||||
|
_window.Vaadin.Flow.tooltip ||= {};
|
||||||
|
|
||||||
|
Object.assign(_window.Vaadin.Flow.tooltip, {
|
||||||
|
setDefaultHideDelay: (hideDelay: number) => Tooltip.setDefaultHideDelay(hideDelay),
|
||||||
|
setDefaultFocusDelay: (focusDelay: number) => Tooltip.setDefaultFocusDelay(focusDelay),
|
||||||
|
setDefaultHoverDelay: (hoverDelay: number) => Tooltip.setDefaultHoverDelay(hoverDelay)
|
||||||
|
});
|
||||||
|
|
||||||
|
const { defaultHideDelay, defaultFocusDelay, defaultHoverDelay } = _window.Vaadin.Flow.tooltip;
|
||||||
|
if (defaultHideDelay) {
|
||||||
|
Tooltip.setDefaultHideDelay(defaultHideDelay);
|
||||||
|
}
|
||||||
|
if (defaultFocusDelay) {
|
||||||
|
Tooltip.setDefaultFocusDelay(defaultFocusDelay);
|
||||||
|
}
|
||||||
|
if (defaultHoverDelay) {
|
||||||
|
Tooltip.setDefaultHoverDelay(defaultHoverDelay);
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import './gridConnector.ts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* treeGridConnector is a communication layer between TreeGrid's flow component
|
||||||
|
* (server-side) and web component (client-side).
|
||||||
|
*
|
||||||
|
* TreeGrid does not rely on the web component's built-in features for handling
|
||||||
|
* hierarchical data. Instead, the hierarchy is fully managed on the server side
|
||||||
|
* and sent to the client as a flattened structure. This approach simplifies the
|
||||||
|
* client-side implementation and improves performance by avoiding recursive
|
||||||
|
* requests to the data provider.
|
||||||
|
*
|
||||||
|
* While the data is transferred as a flat list, the connector makes it appear as
|
||||||
|
* a hierarchy by overriding the web component's methods to add indentation based
|
||||||
|
* on information from server-provided fields `item.level`, `item.expanded`, etc.
|
||||||
|
*
|
||||||
|
* The connector overrides the web component's default `scrollToIndex(...indexes)`
|
||||||
|
* implementation, as it by default assumes that the hierarchy is managed on the
|
||||||
|
* client side. Instead, it uses the server-side method to resolve the hierarchical
|
||||||
|
* path and preload the viewport range, all in a single round-trip. As a result,
|
||||||
|
* required data is already loaded on the client-side by the time the scrolling
|
||||||
|
* begins, which allows the scrollToIndex operation to be executed faster.
|
||||||
|
*
|
||||||
|
* The server estimates the viewport range for `scrollToIndex` based on the `padding`
|
||||||
|
* parameter of $server.setViewportRangeByIndexPath, which defines how many items to
|
||||||
|
* include above and below the target item in the range.
|
||||||
|
*/
|
||||||
|
window.Vaadin.Flow.treeGridConnector = {};
|
||||||
|
window.Vaadin.Flow.treeGridConnector.initLazy = function (grid) {
|
||||||
|
if (grid.$connector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Vaadin.Flow.gridConnector.initLazy(grid);
|
||||||
|
|
||||||
|
grid._dataProviderController._shouldLoadCachePage = function (cache, page) {
|
||||||
|
// `$server.setViewportRangeByIndexPath` sends a preloaded viewport range based on
|
||||||
|
// the provided index path and `padding` parameter. Applying the new range clears
|
||||||
|
// the old range, which is still visible because the actual scroll happens only
|
||||||
|
// after all connector calls in that update are processed. This check prevents
|
||||||
|
// the old range from being unnecessarily re-requested while the new range is
|
||||||
|
// still being processed, which could cause flickering.
|
||||||
|
return !grid.__pendingScrollToIndexes;
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.scrollToIndex = async function (...indexes) {
|
||||||
|
grid.__pendingScrollToIndexes = indexes;
|
||||||
|
|
||||||
|
if (!grid.clientHeight || !grid._columnTree || grid._dataProviderController.isLoading()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [start, end] = grid.$connector.getViewportRange();
|
||||||
|
const padding = Math.floor((end - start) * 1.5);
|
||||||
|
const flatIndex = await grid.$server.setViewportRangeByIndexPath(indexes, padding);
|
||||||
|
grid._scrollToFlatIndex(flatIndex);
|
||||||
|
|
||||||
|
delete grid.__pendingScrollToIndexes;
|
||||||
|
|
||||||
|
return flatIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.__getRowLevel = function (row) {
|
||||||
|
return row._item?.level ?? 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
grid._isExpanded = function (item) {
|
||||||
|
return !!item?.expanded;
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.expandItem = function (item) {
|
||||||
|
if (item !== undefined) {
|
||||||
|
grid.$server.updateExpandedState(grid.getItemId(item), true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
grid.collapseItem = function (item) {
|
||||||
|
if (item !== undefined) {
|
||||||
|
grid.$server.updateExpandedState(grid.getItemId(item), false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2000-2026 Vaadin Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
import { css } from 'lit';
|
||||||
|
import { TextField } from '@vaadin/text-field/src/vaadin-text-field.js';
|
||||||
|
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
|
||||||
|
|
||||||
|
class BigDecimalField extends TextField {
|
||||||
|
static get is() {
|
||||||
|
return 'vaadin-big-decimal-field';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get lumoInjector() {
|
||||||
|
return { ...super.lumoInjector, is: 'vaadin-text-field' };
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return [
|
||||||
|
...super.styles,
|
||||||
|
css`
|
||||||
|
:host([dir='rtl']) [part='input-field'] {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([dir='rtl']) [part='input-field'] ::slotted(input) {
|
||||||
|
--_lumo-text-field-overflow-mask-image: linear-gradient(to left, transparent, #000 1.25em) !important;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
_decimalSeparator: {
|
||||||
|
type: String,
|
||||||
|
value: '.',
|
||||||
|
sync: true,
|
||||||
|
observer: '__decimalSeparatorChanged'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ready() {
|
||||||
|
super.ready();
|
||||||
|
this.inputElement.setAttribute('inputmode', 'decimal');
|
||||||
|
}
|
||||||
|
|
||||||
|
__decimalSeparatorChanged(separator, oldSeparator) {
|
||||||
|
this.allowedCharPattern = '[-+\\d' + separator + ']';
|
||||||
|
|
||||||
|
if (this.value && oldSeparator) {
|
||||||
|
this.value = this.value.split(oldSeparator).join(separator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineCustomElement(BigDecimalField);
|
||||||
+98
@@ -0,0 +1,98 @@
|
|||||||
|
import '@vaadin/checkbox/src/vaadin-checkbox.js';
|
||||||
|
import '@vaadin/grid/src/vaadin-grid-column.js';
|
||||||
|
import { GridColumn } from '@vaadin/grid/src/vaadin-grid-column.js';
|
||||||
|
import { GridSelectionColumnBaseMixin } from '@vaadin/grid/src/vaadin-grid-selection-column-base-mixin.js';
|
||||||
|
|
||||||
|
export class GridFlowSelectionColumn extends GridSelectionColumnBaseMixin(GridColumn) {
|
||||||
|
static get is() {
|
||||||
|
return 'vaadin-grid-flow-selection-column';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Override property to enable auto-width
|
||||||
|
*/
|
||||||
|
autoWidth: {
|
||||||
|
type: Boolean,
|
||||||
|
value: true
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override property to set custom width
|
||||||
|
*/
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
value: '56px'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override method from `GridSelectionColumnBaseMixin` to add ID to select all
|
||||||
|
* checkbox
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
_defaultHeaderRenderer(root, _column) {
|
||||||
|
super._defaultHeaderRenderer(root, _column);
|
||||||
|
const checkbox = root.firstElementChild;
|
||||||
|
if (checkbox) {
|
||||||
|
checkbox.id = 'selectAllCheckbox';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override a method from `GridSelectionColumnBaseMixin` to handle the user
|
||||||
|
* selecting all items.
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
_selectAll() {
|
||||||
|
this.selectAll = true;
|
||||||
|
this.$server.selectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override a method from `GridSelectionColumnBaseMixin` to handle the user
|
||||||
|
* deselecting all items.
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
_deselectAll() {
|
||||||
|
this.selectAll = false;
|
||||||
|
this.$server.deselectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override a method from `GridSelectionColumnBaseMixin` to handle the user
|
||||||
|
* selecting an item.
|
||||||
|
*
|
||||||
|
* @param {Object} item the item to select
|
||||||
|
* @protected
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
_selectItem(item) {
|
||||||
|
this.$server.setShiftKeyDown(this._shiftKeyDown);
|
||||||
|
this._grid.$connector.doSelection([item], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override a method from `GridSelectionColumnBaseMixin` to handle the user
|
||||||
|
* deselecting an item.
|
||||||
|
*
|
||||||
|
* @param {Object} item the item to deselect
|
||||||
|
* @protected
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
_deselectItem(item) {
|
||||||
|
this.$server.setShiftKeyDown(this._shiftKeyDown);
|
||||||
|
this._grid.$connector.doDeselection([item], true);
|
||||||
|
// Optimistically update select all state
|
||||||
|
this.selectAll = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define(GridFlowSelectionColumn.is, GridFlowSelectionColumn);
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Popover } from '@vaadin/popover/src/vaadin-popover.js';
|
||||||
|
|
||||||
|
const _window = window as any;
|
||||||
|
_window.Vaadin ||= {};
|
||||||
|
_window.Vaadin.Flow ||= {};
|
||||||
|
_window.Vaadin.Flow.popover ||= {};
|
||||||
|
|
||||||
|
Object.assign(_window.Vaadin.Flow.popover, {
|
||||||
|
setDefaultHideDelay: (hideDelay: number) => Popover.setDefaultHideDelay(hideDelay),
|
||||||
|
setDefaultFocusDelay: (focusDelay: number) => Popover.setDefaultFocusDelay(focusDelay),
|
||||||
|
setDefaultHoverDelay: (hoverDelay: number) => Popover.setDefaultHoverDelay(hoverDelay)
|
||||||
|
});
|
||||||
|
|
||||||
|
const { defaultHideDelay, defaultFocusDelay, defaultHoverDelay } = _window.Vaadin.Flow.popover;
|
||||||
|
|
||||||
|
if (defaultHideDelay) {
|
||||||
|
Popover.setDefaultHideDelay(defaultHideDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultFocusDelay) {
|
||||||
|
Popover.setDefaultFocusDelay(defaultFocusDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultHoverDelay) {
|
||||||
|
Popover.setDefaultHoverDelay(defaultHoverDelay);
|
||||||
|
}
|
||||||
+183
@@ -0,0 +1,183 @@
|
|||||||
|
// map from unicode eastern arabic number characters to arabic numbers
|
||||||
|
const EASTERN_ARABIC_DIGIT_MAP = {
|
||||||
|
'\\u0660': '0',
|
||||||
|
'\\u0661': '1',
|
||||||
|
'\\u0662': '2',
|
||||||
|
'\\u0663': '3',
|
||||||
|
'\\u0664': '4',
|
||||||
|
'\\u0665': '5',
|
||||||
|
'\\u0666': '6',
|
||||||
|
'\\u0667': '7',
|
||||||
|
'\\u0668': '8',
|
||||||
|
'\\u0669': '9'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes the given string so it can be safely used in a regexp.
|
||||||
|
*
|
||||||
|
* @param {string} string
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
function escapeRegExp(string) {
|
||||||
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses eastern arabic number characters to arabic numbers (0-9)
|
||||||
|
*
|
||||||
|
* @param {string} digits
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
function parseEasternArabicDigits(digits) {
|
||||||
|
return digits.replace(/[\u0660-\u0669]/g, function (char) {
|
||||||
|
const unicode = '\\u0' + char.charCodeAt(0).toString(16);
|
||||||
|
return EASTERN_ARABIC_DIGIT_MAP[unicode];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} locale
|
||||||
|
* @param {Date} testTime
|
||||||
|
* @return {string | null}
|
||||||
|
*/
|
||||||
|
function getAmOrPmString(locale, testTime) {
|
||||||
|
const testTimeString = testTime.toLocaleTimeString(locale);
|
||||||
|
|
||||||
|
// AM/PM string is anything from one letter in eastern arabic to standard two letters,
|
||||||
|
// to having space in between, dots ...
|
||||||
|
// cannot disqualify whitespace since some locales use a. m. / p. m.
|
||||||
|
// TODO when more scripts support is added (than Arabic), need to exclude those numbers too
|
||||||
|
const amOrPmRegExp = /[^\d\u0660-\u0669]/;
|
||||||
|
|
||||||
|
const matches =
|
||||||
|
// In most locales, the time ends with AM/PM:
|
||||||
|
testTimeString.match(new RegExp(`${amOrPmRegExp.source}+$`, 'g')) ||
|
||||||
|
// In some locales, the time starts with AM/PM e.g in Chinese:
|
||||||
|
testTimeString.match(new RegExp(`^${amOrPmRegExp.source}+`, 'g'));
|
||||||
|
|
||||||
|
return matches && matches[0].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} locale
|
||||||
|
* @return {string | null}
|
||||||
|
*/
|
||||||
|
export function getSeparator(locale) {
|
||||||
|
let timeString = TEST_PM_TIME.toLocaleTimeString(locale);
|
||||||
|
|
||||||
|
// Since the next regex picks first non-number-whitespace,
|
||||||
|
// need to discard possible PM from beginning (eg. chinese locale)
|
||||||
|
const pmString = getPmString(locale);
|
||||||
|
if (pmString && timeString.startsWith(pmString)) {
|
||||||
|
timeString = timeString.replace(pmString, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
const matches = timeString.match(/[^\u0660-\u0669\s\d]/);
|
||||||
|
return matches && matches[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for either an AM or PM token in the given time string
|
||||||
|
* depending on what is provided in `amOrPmString`.
|
||||||
|
*
|
||||||
|
* The search is case and space insensitive.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* `searchAmOrPmToken('1 P M', 'PM')` => `'P M'`
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* `searchAmOrPmToken('1 a.m.', 'A. M.')` => `a.m.`
|
||||||
|
*
|
||||||
|
* @param {string} timeString
|
||||||
|
* @param {string} amOrPmString
|
||||||
|
* @return {string | null}
|
||||||
|
*/
|
||||||
|
export function searchAmOrPmToken(timeString, amOrPmString) {
|
||||||
|
if (!amOrPmString) return null;
|
||||||
|
|
||||||
|
// Create a regexp string for searching for AM/PM without space-sensitivity.
|
||||||
|
const tokenRegExpString = amOrPmString.split(/\s*/).map(escapeRegExp).join('\\s*');
|
||||||
|
|
||||||
|
// Create a regexp without case-sensitivity.
|
||||||
|
const tokenRegExp = new RegExp(tokenRegExpString, 'i');
|
||||||
|
|
||||||
|
// Match the regexp against the time string.
|
||||||
|
const tokenMatches = timeString.match(tokenRegExp);
|
||||||
|
if (tokenMatches) {
|
||||||
|
return tokenMatches[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TEST_PM_TIME = new Date('August 19, 1975 23:15:30');
|
||||||
|
|
||||||
|
export const TEST_AM_TIME = new Date('August 19, 1975 05:15:30');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} locale
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
export function getPmString(locale) {
|
||||||
|
return getAmOrPmString(locale, TEST_PM_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} locale
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
export function getAmString(locale) {
|
||||||
|
return getAmOrPmString(locale, TEST_AM_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} digits
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
export function parseDigitsIntoInteger(digits) {
|
||||||
|
return parseInt(parseEasternArabicDigits(digits));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} milliseconds
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
export function parseMillisecondsIntoInteger(milliseconds) {
|
||||||
|
milliseconds = parseEasternArabicDigits(milliseconds);
|
||||||
|
// digits are either .1 .01 or .001 so need to "shift"
|
||||||
|
if (milliseconds.length === 1) {
|
||||||
|
milliseconds += '00';
|
||||||
|
} else if (milliseconds.length === 2) {
|
||||||
|
milliseconds += '0';
|
||||||
|
}
|
||||||
|
return parseInt(milliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} timeString
|
||||||
|
* @param {number} milliseconds
|
||||||
|
* @param {string} amString
|
||||||
|
* @param {string} pmString
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
export function formatMilliseconds(timeString, milliseconds, amString, pmString) {
|
||||||
|
// might need to inject milliseconds between seconds and AM/PM
|
||||||
|
let cleanedTimeString = timeString;
|
||||||
|
if (timeString.endsWith(amString)) {
|
||||||
|
cleanedTimeString = timeString.replace(' ' + amString, '');
|
||||||
|
} else if (timeString.endsWith(pmString)) {
|
||||||
|
cleanedTimeString = timeString.replace(' ' + pmString, '');
|
||||||
|
}
|
||||||
|
if (milliseconds) {
|
||||||
|
let millisecondsString = milliseconds < 10 ? '0' : '';
|
||||||
|
millisecondsString += milliseconds < 100 ? '0' : '';
|
||||||
|
millisecondsString += milliseconds;
|
||||||
|
cleanedTimeString += '.' + millisecondsString;
|
||||||
|
} else {
|
||||||
|
cleanedTimeString += '.000';
|
||||||
|
}
|
||||||
|
if (timeString.endsWith(amString)) {
|
||||||
|
cleanedTimeString = cleanedTimeString + ' ' + amString;
|
||||||
|
} else if (timeString.endsWith(pmString)) {
|
||||||
|
cleanedTimeString = cleanedTimeString + ' ' + pmString;
|
||||||
|
}
|
||||||
|
return cleanedTimeString;
|
||||||
|
}
|
||||||
+181
@@ -0,0 +1,181 @@
|
|||||||
|
import {
|
||||||
|
TEST_PM_TIME,
|
||||||
|
formatMilliseconds,
|
||||||
|
parseMillisecondsIntoInteger,
|
||||||
|
parseDigitsIntoInteger,
|
||||||
|
getAmString,
|
||||||
|
getPmString,
|
||||||
|
getSeparator,
|
||||||
|
searchAmOrPmToken
|
||||||
|
} from './helpers.js';
|
||||||
|
import { parseISOTime } from '@vaadin/time-picker/src/vaadin-time-picker-helper.js';
|
||||||
|
|
||||||
|
// Execute callback when predicate returns true.
|
||||||
|
// Try again later if predicate returns false.
|
||||||
|
function when(predicate, callback, timeout = 0) {
|
||||||
|
if (predicate()) {
|
||||||
|
callback();
|
||||||
|
} else {
|
||||||
|
setTimeout(() => when(predicate, callback, 200), timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseISO(text) {
|
||||||
|
// The default i18n parser of the web component is ISO 8601 compliant.
|
||||||
|
const timeObject = parseISOTime(text);
|
||||||
|
|
||||||
|
// The web component returns an object with string values
|
||||||
|
// while the connector expects number values.
|
||||||
|
return {
|
||||||
|
hours: parseInt(timeObject.hours || 0),
|
||||||
|
minutes: parseInt(timeObject.minutes || 0),
|
||||||
|
seconds: parseInt(timeObject.seconds || 0),
|
||||||
|
milliseconds: parseInt(timeObject.milliseconds || 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Vaadin.Flow.timepickerConnector = {};
|
||||||
|
window.Vaadin.Flow.timepickerConnector.initLazy = (timepicker) => {
|
||||||
|
// Check whether the connector was already initialized for the timepicker
|
||||||
|
if (timepicker.$connector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timepicker.$connector = {};
|
||||||
|
|
||||||
|
timepicker.$connector.setLocale = (locale) => {
|
||||||
|
// capture previous value if any
|
||||||
|
let previousValueObject;
|
||||||
|
if (timepicker.value && timepicker.value !== '') {
|
||||||
|
previousValueObject = parseISO(timepicker.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check whether the locale is supported by the browser or not
|
||||||
|
TEST_PM_TIME.toLocaleTimeString(locale);
|
||||||
|
} catch (e) {
|
||||||
|
locale = 'en-US';
|
||||||
|
// FIXME should do a callback for server to throw an exception ?
|
||||||
|
throw new Error(
|
||||||
|
'vaadin-time-picker: The locale ' + locale + ' is not supported, falling back to default locale setting(en-US).'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 24 or 12 hour clock, if latter then what are the am/pm strings ?
|
||||||
|
const pmString = getPmString(locale);
|
||||||
|
const amString = getAmString(locale);
|
||||||
|
|
||||||
|
// 2. What is the separator ?
|
||||||
|
const separator = getSeparator(locale);
|
||||||
|
|
||||||
|
const includeSeconds = function () {
|
||||||
|
return timepicker.step && timepicker.step < 60;
|
||||||
|
};
|
||||||
|
|
||||||
|
const includeMilliSeconds = function () {
|
||||||
|
return timepicker.step && timepicker.step < 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
let cachedTimeString;
|
||||||
|
let cachedTimeObject;
|
||||||
|
|
||||||
|
timepicker.i18n = {
|
||||||
|
formatTime(timeObject) {
|
||||||
|
if (!timeObject) return;
|
||||||
|
|
||||||
|
const timeToBeFormatted = new Date();
|
||||||
|
timeToBeFormatted.setHours(timeObject.hours);
|
||||||
|
timeToBeFormatted.setMinutes(timeObject.minutes);
|
||||||
|
timeToBeFormatted.setSeconds(timeObject.seconds !== undefined ? timeObject.seconds : 0);
|
||||||
|
|
||||||
|
// the web component expects the correct granularity used for the time string,
|
||||||
|
// thus need to format the time object in correct granularity by passing the format options
|
||||||
|
let localeTimeString = timeToBeFormatted.toLocaleTimeString(locale, {
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
second: includeSeconds() ? 'numeric' : undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
// milliseconds not part of the time format API
|
||||||
|
if (includeMilliSeconds()) {
|
||||||
|
localeTimeString = formatMilliseconds(localeTimeString, timeObject.milliseconds, amString, pmString);
|
||||||
|
}
|
||||||
|
|
||||||
|
return localeTimeString;
|
||||||
|
},
|
||||||
|
|
||||||
|
parseTime(timeString) {
|
||||||
|
if (timeString && timeString === cachedTimeString && cachedTimeObject) {
|
||||||
|
return cachedTimeObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!timeString) {
|
||||||
|
// when nothing is returned, the component shows the invalid state for the input
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amToken = searchAmOrPmToken(timeString, amString);
|
||||||
|
const pmToken = searchAmOrPmToken(timeString, pmString);
|
||||||
|
|
||||||
|
const numbersOnlyTimeString = timeString
|
||||||
|
.replace(amToken || '', '')
|
||||||
|
.replace(pmToken || '', '')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// A regexp that allows to find the numbers with optional separator and continuing searching after it.
|
||||||
|
const numbersRegExp = new RegExp('([\\d\\u0660-\\u0669]){1,2}(?:' + separator + ')?', 'g');
|
||||||
|
|
||||||
|
let hours = numbersRegExp.exec(numbersOnlyTimeString);
|
||||||
|
if (hours) {
|
||||||
|
hours = parseDigitsIntoInteger(hours[0].replace(separator, ''));
|
||||||
|
// handle 12 am -> 0
|
||||||
|
// do not do anything if am & pm are not used or if those are the same,
|
||||||
|
// as with locale bg-BG there is always ч. at the end of the time
|
||||||
|
if (amToken !== pmToken) {
|
||||||
|
if (hours === 12 && amToken) {
|
||||||
|
hours = 0;
|
||||||
|
}
|
||||||
|
if (hours !== 12 && pmToken) {
|
||||||
|
hours += 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const minutes = numbersRegExp.exec(numbersOnlyTimeString);
|
||||||
|
const seconds = minutes && numbersRegExp.exec(numbersOnlyTimeString);
|
||||||
|
// detecting milliseconds from input, expects am/pm removed from end, eg. .0 or .00 or .000
|
||||||
|
const millisecondRegExp = /[[\.][\d\u0660-\u0669]{1,3}$/;
|
||||||
|
// reset to end or things can explode
|
||||||
|
let milliseconds = seconds && includeMilliSeconds() && millisecondRegExp.exec(numbersOnlyTimeString);
|
||||||
|
// handle case where last numbers are seconds and . is the separator (invalid regexp match)
|
||||||
|
if (milliseconds && milliseconds['index'] <= seconds['index']) {
|
||||||
|
milliseconds = undefined;
|
||||||
|
}
|
||||||
|
// hours is a number at this point, others are either arrays or null
|
||||||
|
// the string in [0] from the arrays includes the separator too
|
||||||
|
cachedTimeObject = hours !== undefined && {
|
||||||
|
hours: hours,
|
||||||
|
minutes: minutes ? parseDigitsIntoInteger(minutes[0].replace(separator, '')) : 0,
|
||||||
|
seconds: seconds ? parseDigitsIntoInteger(seconds[0].replace(separator, '')) : 0,
|
||||||
|
milliseconds:
|
||||||
|
minutes && seconds && milliseconds ? parseMillisecondsIntoInteger(milliseconds[0].replace('.', '')) : 0
|
||||||
|
};
|
||||||
|
cachedTimeString = timeString;
|
||||||
|
return cachedTimeObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (previousValueObject) {
|
||||||
|
when(
|
||||||
|
() => timepicker.$,
|
||||||
|
() => {
|
||||||
|
const newValue = timepicker.i18n.formatTime(previousValueObject);
|
||||||
|
// FIXME works but uses private API, needs fixes in web component
|
||||||
|
if (timepicker.inputElement.value !== newValue) {
|
||||||
|
timepicker.inputElement.value = newValue;
|
||||||
|
timepicker.value = newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
+106
@@ -0,0 +1,106 @@
|
|||||||
|
import { UploadManager, type UploadFormat } from '@vaadin/upload/vaadin-upload-manager.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connector element for UploadManager. This element is added as a virtual child
|
||||||
|
* of the owner component and handles initialization of the client-side
|
||||||
|
* UploadManager instance based on properties set from the server.
|
||||||
|
*
|
||||||
|
* Properties read from the element:
|
||||||
|
* - target: Upload URL (from attribute, auto-converted by Flow)
|
||||||
|
* - maxFiles: Maximum number of files (optional)
|
||||||
|
* - maxFileSize: Maximum file size in bytes (optional)
|
||||||
|
* - accept: Accepted file types (optional)
|
||||||
|
* - noAuto: Disable auto-upload (optional)
|
||||||
|
* - uploadFormat: Upload format ('raw' or 'multipart', optional)
|
||||||
|
* - disabled: Whether the manager is disabled (from attribute)
|
||||||
|
*
|
||||||
|
* Events dispatched to the connector element for server-side handling:
|
||||||
|
* - file-remove: When a file is removed
|
||||||
|
* - file-reject: When a file is rejected
|
||||||
|
*/
|
||||||
|
class UploadManagerConnector extends HTMLElement {
|
||||||
|
public manager = new UploadManager();
|
||||||
|
|
||||||
|
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
|
||||||
|
if (name === 'target' && oldValue !== newValue) {
|
||||||
|
this.manager.target = newValue;
|
||||||
|
} else if (name === 'disabled' && oldValue !== newValue) {
|
||||||
|
this.manager.disabled = newValue !== null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get observedAttributes() {
|
||||||
|
return ['target', 'disabled'];
|
||||||
|
}
|
||||||
|
|
||||||
|
set maxFiles(value: number) {
|
||||||
|
this.manager.maxFiles = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
set maxFileSize(value: number) {
|
||||||
|
this.manager.maxFileSize = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
set accept(value: string) {
|
||||||
|
this.manager.accept = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
set noAuto(value: boolean) {
|
||||||
|
this.manager.noAuto = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
set uploadFormat(value: UploadFormat) {
|
||||||
|
this.manager.uploadFormat = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearFileList() {
|
||||||
|
this.manager.files = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private uploading = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
// Forward events to the connector element for server-side handling
|
||||||
|
this.manager.addEventListener('file-remove', (e: CustomEvent) => {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('file-remove', {
|
||||||
|
detail: { fileName: e.detail.file?.name },
|
||||||
|
bubbles: false
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.manager.addEventListener('file-reject', (e: CustomEvent) => {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('file-reject', {
|
||||||
|
detail: {
|
||||||
|
fileName: e.detail.file?.name,
|
||||||
|
errorMessage: e.detail.error
|
||||||
|
},
|
||||||
|
bubbles: false
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Track upload state to detect when all uploads finish
|
||||||
|
this.manager.addEventListener('upload-start', () => {
|
||||||
|
this.uploading = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkAllFinished = () => {
|
||||||
|
const isUploading = this.manager.files.some((file: { uploading?: boolean }) => file.uploading);
|
||||||
|
if (this.uploading && !isUploading) {
|
||||||
|
this.dispatchEvent(new CustomEvent('all-finished', { bubbles: false }));
|
||||||
|
}
|
||||||
|
this.uploading = isUploading;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.manager.addEventListener('upload-success', checkAllFinished);
|
||||||
|
this.manager.addEventListener('upload-error', checkAllFinished);
|
||||||
|
this.manager.addEventListener('upload-abort', checkAllFinished);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('vaadin-upload-manager-connector', UploadManagerConnector);
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import { Debouncer } from '@vaadin/component-base/src/debounce.js';
|
||||||
|
import { timeOut } from '@vaadin/component-base/src/async.js';
|
||||||
|
|
||||||
|
window.Vaadin.Flow.virtualListConnector = {
|
||||||
|
initLazy: function (list) {
|
||||||
|
// Check whether the connector was already initialized for the virtual list
|
||||||
|
if (list.$connector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const extraItemsBuffer = 20;
|
||||||
|
|
||||||
|
let lastRequestedRange = [0, 0];
|
||||||
|
|
||||||
|
list.$connector = {};
|
||||||
|
list.$connector.placeholderItem = { __placeholder: true };
|
||||||
|
|
||||||
|
list.itemAccessibleNameGenerator = (item) => item && item.accessibleName;
|
||||||
|
|
||||||
|
const updateRequestedItem = function () {
|
||||||
|
/*
|
||||||
|
* TODO virtual list seems to do a small index adjustment after scrolling
|
||||||
|
* has stopped. This causes a redundant request to be sent to make a
|
||||||
|
* corresponding minimal change to the buffer. We should avoid these
|
||||||
|
* requests by making the logic skip doing a request if the available
|
||||||
|
* buffer is within some tolerance compared to the requested buffer.
|
||||||
|
*/
|
||||||
|
const visibleIndexes = [...list.children]
|
||||||
|
.filter((el) => '__virtualListIndex' in el)
|
||||||
|
.map((el) => el.__virtualListIndex);
|
||||||
|
const firstNeededItem = Math.min(...visibleIndexes);
|
||||||
|
const lastNeededItem = Math.max(...visibleIndexes);
|
||||||
|
|
||||||
|
let first = Math.max(0, firstNeededItem - extraItemsBuffer);
|
||||||
|
let last = Math.min(lastNeededItem + extraItemsBuffer, list.items.length);
|
||||||
|
|
||||||
|
if (lastRequestedRange[0] != first || lastRequestedRange[1] != last) {
|
||||||
|
lastRequestedRange = [first, last];
|
||||||
|
const count = 1 + last - first;
|
||||||
|
list.$server.setViewportRange(first, count);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const scheduleUpdateRequest = function () {
|
||||||
|
list.__requestDebounce = Debouncer.debounce(list.__requestDebounce, timeOut.after(50), updateRequestedItem);
|
||||||
|
};
|
||||||
|
|
||||||
|
requestAnimationFrame(() => updateRequestedItem);
|
||||||
|
|
||||||
|
// Add an observer function that will invoke on virtualList.renderer property
|
||||||
|
// change and then patches it with a wrapper renderer
|
||||||
|
list.patchVirtualListRenderer = function () {
|
||||||
|
if (!list.renderer || list.renderer.__virtualListConnectorPatched) {
|
||||||
|
// The list either doesn't have a renderer yet or it's already been patched
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalRenderer = list.renderer;
|
||||||
|
|
||||||
|
const renderer = (root, list, model) => {
|
||||||
|
root.__virtualListIndex = model.index;
|
||||||
|
|
||||||
|
if (model.item === undefined) {
|
||||||
|
if (list.$connector.placeholderElement) {
|
||||||
|
// ComponentRenderer
|
||||||
|
if (!root.__hasComponentRendererPlaceholder) {
|
||||||
|
// The root was previously rendered by the ComponentRenderer. Clear and add a placeholder.
|
||||||
|
root.innerHTML = '';
|
||||||
|
delete root._$litPart$;
|
||||||
|
root.appendChild(list.$connector.placeholderElement.cloneNode(true));
|
||||||
|
root.__hasComponentRendererPlaceholder = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// LitRenderer
|
||||||
|
originalRenderer.call(list, root, list, {
|
||||||
|
...model,
|
||||||
|
item: list.$connector.placeholderItem
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (root.__hasComponentRendererPlaceholder) {
|
||||||
|
// The root was previously populated with a placeholder. Clear it.
|
||||||
|
root.innerHTML = '';
|
||||||
|
root.__hasComponentRendererPlaceholder = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
originalRenderer.call(list, root, list, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if we need to do anything once things have settled down.
|
||||||
|
* This method is called multiple times in sequence for the same user
|
||||||
|
* action, but we only want to do the check once.
|
||||||
|
*/
|
||||||
|
scheduleUpdateRequest();
|
||||||
|
};
|
||||||
|
renderer.__virtualListConnectorPatched = true;
|
||||||
|
renderer.__rendererId = originalRenderer.__rendererId;
|
||||||
|
|
||||||
|
list.renderer = renderer;
|
||||||
|
};
|
||||||
|
|
||||||
|
list._createPropertyObserver('renderer', 'patchVirtualListRenderer', true);
|
||||||
|
list.patchVirtualListRenderer();
|
||||||
|
|
||||||
|
list.items = [];
|
||||||
|
|
||||||
|
list.$connector.set = function (index, items) {
|
||||||
|
list.items.splice(index, items.length, ...items);
|
||||||
|
list.items = [...list.items];
|
||||||
|
};
|
||||||
|
|
||||||
|
list.$connector.clear = function (index, length) {
|
||||||
|
// How many items, starting from "index", should be set as undefined
|
||||||
|
const clearCount = Math.min(length, list.items.length - index);
|
||||||
|
list.$connector.set(index, [...Array(clearCount)]);
|
||||||
|
};
|
||||||
|
|
||||||
|
list.$connector.updateData = function (items) {
|
||||||
|
const updatedItemsMap = items.reduce((map, item) => {
|
||||||
|
map[item.key] = item;
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
list.items = list.items.map((item) => {
|
||||||
|
// Items can be undefined if they are outside the viewport
|
||||||
|
if (!item) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
// Replace existing item with updated item,
|
||||||
|
// return existing item as fallback if it was not updated
|
||||||
|
return updatedItemsMap[item.key] || item;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
list.$connector.updateSize = function (newSize) {
|
||||||
|
const delta = newSize - list.items.length;
|
||||||
|
if (delta > 0) {
|
||||||
|
list.items = [...list.items, ...Array(delta)];
|
||||||
|
} else if (delta < 0) {
|
||||||
|
list.items = list.items.slice(0, newSize);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
list.$connector.setPlaceholderItem = function (placeholderItem = {}, appId) {
|
||||||
|
placeholderItem.__placeholder = true;
|
||||||
|
list.$connector.placeholderItem = placeholderItem;
|
||||||
|
const nodeId = Object.entries(placeholderItem).find(([key]) => key.endsWith('_nodeid'));
|
||||||
|
list.$connector.placeholderElement = nodeId ? Vaadin.Flow.clients[appId].getByNodeId(nodeId[1]) : null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { createElement as reactCreateElement } from 'react';
|
||||||
|
|
||||||
|
export const createElement = reactCreateElement;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { JSXSource, Fragment as reactFragment, jsxDEV as reactJsxDEV } from 'react/jsx-dev-runtime';
|
||||||
|
|
||||||
|
export const Fragment = reactFragment;
|
||||||
|
|
||||||
|
export function jsxDEV(
|
||||||
|
type: React.ElementType,
|
||||||
|
props: unknown,
|
||||||
|
key: React.Key | undefined,
|
||||||
|
isStatic: boolean,
|
||||||
|
source?: JSXSource,
|
||||||
|
self?: unknown
|
||||||
|
): React.ReactElement {
|
||||||
|
const realFreeze = Object.freeze;
|
||||||
|
try {
|
||||||
|
(Object as any).freeze = undefined; // prevent React from freezing the element
|
||||||
|
|
||||||
|
const reactElement: any = reactJsxDEV(type, props, key, isStatic, source, self);
|
||||||
|
if (source && !reactElement._source) {
|
||||||
|
// When running with React 19, put the source information on the _debugInfo array,
|
||||||
|
// which will be transferred to the fiber node by React
|
||||||
|
reactElement._debugInfo ??= [];
|
||||||
|
reactElement._debugInfo.source = source;
|
||||||
|
}
|
||||||
|
realFreeze(reactElement);
|
||||||
|
return reactElement;
|
||||||
|
} finally {
|
||||||
|
(Object as any).freeze = realFreeze;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Fragment as reactFragment, jsx as reactJsx, jsxs as reactJsxs } from 'react/jsx-runtime';
|
||||||
|
|
||||||
|
export const Fragment = reactFragment;
|
||||||
|
export const jsx = reactJsx;
|
||||||
|
export const jsxs = reactJsxs;
|
||||||
|
|
||||||
|
throw new Error('Do not use this transform for production builds. It is only meant for development.');
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[]
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user