From 6f9d93186d24dc0af1ca2204e15b51bb19dd9174 Mon Sep 17 00:00:00 2001 From: Mattias Reichel Date: Fri, 22 Nov 2024 23:30:24 +0100 Subject: [PATCH] Use versions from `grails-bom` in example apps (#920) * fix(deps): Use versions from `grails-bom` in example apps And remove unused versions from `gradle.properties` * build: Remove `webdriver-binaries-gradle-plugin` As `ContainerGebSpec` is used now, `webdriver-binaries-gradle-plugin` can be removed. * chore: Remove outdated comment * chore: Cleanup `examples-grails3-database-per-tenant` - `AnotherBookService`: Make `save()` method use passed book title - `Application`: Remove unused imports and old comment - `application.yml`: Format properly with 2 spaces and group config settings better - `logback.xml`: Correct faulty pattern - `build.gradle`: Use correct dependencies and narrow the scopes. Apply Gradle plugins in subproject instead of having logic and conditions in the root build file apply them - much easier to see what's being applied. * chore: Cleanup `examples-grails3-hibernate5` - `application.yml`: Format properly with 2 spaces and group config settings better. Remove unused config. - `logback.xml`: Correct faulty pattern - `build.gradle`: Use correct dependencies and narrow the scopes. Switch from `tomcat-jdbc` to `HikariCP` as this will be the new default. Apply Gradle plugins in subproject instead of having logic and conditions in the root build file apply them - much easier to see what's being applied. - `BookControllerUnitSpec`: Remove unused imports and bean definition of deprecated `MimeTypesGrailsPlugin`. - `UrlMappings`: Move to package so it is pickup up. * chore: Cleanup `examples-grails3-multiple-datasources` - `application.yml`: Format properly with 2 spaces and group config settings better. Remove unused config. - `logback.xml`: Correct faulty pattern - `Application`: Remove unused imports and old comment - `build.gradle`: Use correct dependencies and narrow the scopes. Switch from `tomcat-jdbc` to `HikariCP` as this will be the new default. Apply Gradle plugins in subproject instead of having logic and conditions in the root build file apply them - much easier to see what's being applied. Use `astTransformation` configuration for dependencies that needs to be both `compileOnly` and `integrationTestCompileOnly`. * chore: Cleanup `examples-grails3-partitioned-multi-tenancy` - `application.yml`: Format properly with 2 spaces and group config settings better. Remove unused config. - `logback.xml`: Correct faulty pattern - `AnotherBookService`: Make `save()` method use passed book title - `PartionedMultiTenancySpec`: Remove unused imports - `Application`: Remove unused imports and old comment - `build.gradle`: Use correct dependencies and narrow the scopes. Switch from `tomcat-jdbc` to `HikariCP` as this will be the new default. Apply Gradle plugins in subproject instead of having logic and conditions in the root build file apply them - much easier to see what's being applied. * chore: Cleanup `examples-grails3-schema-per-tenant` - `application.yml`: Format properly with 2 spaces and group config settings better. Remove unused config. - `logback.xml`: Correct faulty pattern - `AnotherBookService`: Make `save()` method use passed book title - `SchemaPerTenantSpec`: Remove unused imports - `Application`: Remove unused imports and old comment - `build.gradle`: Use correct dependencies and narrow the scopes. Switch from `tomcat-jdbc` to `HikariCP` as this will be the new default. Apply Gradle plugins in subproject instead of having logic and conditions in the root build file apply them - much easier to see what's being applied. * chore: Cleanup `examples-grails-hibernate-groovy-proxy` - `application.yml`: Format properly with 2 spaces and group config settings better. Remove unused config. - `logback.xml`: Correct faulty pattern - `Customer`: Add no-args constructor needed for proxying - `ProxySpec`: Remove unused imports - `Application`: Remove unused imports and old comment - `build.gradle`: Use correct dependencies and narrow the scopes. Switch from `tomcat-jdbc` to `HikariCP` as this will be the new default. Apply Gradle plugins in subproject instead of having logic and conditions in the root build file apply them - much easier to see what's being applied. Change version variable name from `hibernateGroovyProxy` to `yakworksHibernateGroovyProxyVersion`. * chore: Cleanup `examples-spring-boot-hibernate5` - `application.yml`: Format properly with 2 spaces remove unused config. - `Application`: Exclude `HibernateJpaAutoConfiguration` to avoid duplicate transaction managers: java.lang.ClassCastException: class org.springframework.orm.jpa.EntityManagerHolder cannot be cast to class org.springframework.orm.hibernate5.SessionHolder (org.springframework.orm.jpa.EntityManagerHolder and org.springframework.orm.hibernate5.SessionHolder are in unnamed module of loader 'app') - `BookController`: Add `@ReadOnly` to `books()` action to get a session for the `Book.list()` method. - `build.gradle`: Use correct dependencies and narrow the scopes. Switch from `tomcat-jdbc` to `HikariCP` as this will be the new default. Apply Gradle plugins in subproject instead of having logic and conditions in the root build file apply them - much easier to see what's being applied. Add version variable `springBootGradlePluginVersion`. * test: Extract test for issue 450 from `examples-grails-data-service` The fix for grails/gorm-hibernate5#450 did not really fit in `examples-grails-data-service`. This commit adds a new example project, that shows the problem and that it now works (taken from https://github.com/grails-core-issues-forks/multitenantcomposite). * chore: Cleanup `examples-grails-data-service` - `MultitenantBook.groovy`: Remove as extracted to examples-issue-450 - `application.yml`: Format properly with 2 spaces and group config settings better. Remove unused config. - `ClassUsingAService`: Remove dependency on `grails-spring-security-rest`. No need to depend on outside plugins for this test. New test `ServiceInjectionSpec` exercising issue 202. - `TestService`: Remove unused `transactionService` property - `logback.xml`: Correct faulty pattern * chore: Cleanup `examples-standalone-hibernate5` Use correct dependencies and narrow the scopes. Switch from `tomcat-jdbc` to `HikariCP` as this will be the new default. Apply Gradle plugins in subproject instead of having logic and conditions in the root build file apply them - much easier to see what's being applied. --------- Co-authored-by: Scott Murphy --- .github/workflows/gradle.yml | 2 +- build.gradle | 2 - examples/grails-data-service/build.gradle | 50 +- .../grails-app/conf/application.groovy | 14 - .../grails-app/conf/application.yml | 171 +- .../grails-app/conf/logback.xml | 2 +- .../grails-app/conf/spring/resources.groovy | 4 +- .../domain/example/MultitenantBook.groovy | 15 - .../services/example/TestService.groovy | 3 - .../example/ServiceInjectionSpec.groovy | 21 + .../groovy/example/TestServiceSpec.groovy | 4 + .../groovy/example/ClassUsingAService.groovy | 14 + .../LoginAuthenticationSucessHandler.groovy | 23 - .../build.gradle | 34 +- .../grails-app/conf/application.yml | 24 +- .../grails-app/conf/logback.xml | 2 +- .../grails-app/domain/example/Customer.groovy | 7 + .../init/datasources/Application.groovy | 3 - .../src/test/groovy/example/ProxySpec.groovy | 1 - .../grails3-database-per-tenant/build.gradle | 57 +- .../grails-app/conf/application.yml | 137 +- .../grails-app/conf/logback.xml | 2 +- .../init/datasources/Application.groovy | 3 - .../example/AnotherBookService.groovy | 5 +- examples/grails3-hibernate5/build.gradle | 69 +- .../grails-app/conf/application.yml | 180 +- .../grails-app/conf/logback.xml | 2 +- .../{ => functional/tests}/UrlMappings.groovy | 2 + .../domain/functional/tests/Business.groovy | 1 + .../domain/functional/tests/Product.groovy | 3 +- .../tests/BookControllerUnitSpec.groovy | 6 - .../grails3-multiple-datasources/build.gradle | 53 +- .../grails-app/conf/application.yml | 148 +- .../grails-app/conf/logback.xml | 2 +- .../init/datasources/Application.groovy | 3 - .../build.gradle | 45 +- .../grails-app/conf/application.yml | 120 +- .../grails-app/conf/logback.xml | 2 +- .../init/datasources/Application.groovy | 3 - .../example/AnotherBookService.groovy | 4 +- .../PartitionedMultiTenancySpec.groovy | 2 - .../grails3-schema-per-tenant/build.gradle | 47 +- .../grails-app/conf/application.yml | 122 +- .../grails-app/conf/logback.xml | 2 +- .../init/datasources/Application.groovy | 3 - .../example/AnotherBookService.groovy | 4 +- .../groovy/example/SchemaPerTenantSpec.groovy | 5 +- examples/issue450/build.gradle | 36 + .../assets/images/advancedgrails.svg | 27 + .../assets/images/apple-touch-icon-retina.png | Bin 0 -> 7038 bytes .../assets/images/apple-touch-icon.png | Bin 0 -> 3077 bytes .../assets/images/documentation.svg | 19 + .../grails-app/assets/images/favicon.ico | Bin 0 -> 5558 bytes .../images/grails-cupsonly-logo-white.svg | 26 + .../grails-app/assets/images/grails.svg | 13 + .../assets/images/skin/database_add.png | Bin 0 -> 658 bytes .../assets/images/skin/database_delete.png | Bin 0 -> 659 bytes .../assets/images/skin/database_edit.png | Bin 0 -> 767 bytes .../assets/images/skin/database_save.png | Bin 0 -> 755 bytes .../assets/images/skin/database_table.png | Bin 0 -> 726 bytes .../assets/images/skin/exclamation.png | Bin 0 -> 701 bytes .../grails-app/assets/images/skin/house.png | Bin 0 -> 806 bytes .../assets/images/skin/information.png | Bin 0 -> 778 bytes .../grails-app/assets/images/skin/shadow.jpg | Bin 0 -> 300 bytes .../assets/images/skin/sorted_asc.gif | Bin 0 -> 835 bytes .../assets/images/skin/sorted_desc.gif | Bin 0 -> 834 bytes .../grails-app/assets/images/slack.svg | 18 + .../grails-app/assets/images/spinner.gif | Bin 0 -> 2037 bytes .../assets/javascripts/application.js | 11 + .../assets/javascripts/bootstrap.bundle.js | 6461 ++++++++++++ .../javascripts/bootstrap.bundle.js.map | 1 + .../javascripts/bootstrap.bundle.min.js | 7 + .../javascripts/bootstrap.bundle.min.js.map | 1 + .../assets/javascripts/bootstrap.js | 3944 +++++++ .../assets/javascripts/bootstrap.js.map | 1 + .../assets/javascripts/bootstrap.min.js | 7 + .../assets/javascripts/bootstrap.min.js.map | 1 + .../assets/javascripts/jquery-3.3.1.min.js | 2 + .../javascripts/jquery-3.3.1.min.js.map | 1 + .../assets/javascripts/popper.min.js | 5 + .../assets/stylesheets/application.css | 15 + .../assets/stylesheets/bootstrap-grid.css | 1912 ++++ .../assets/stylesheets/bootstrap-reboot.css | 331 + .../assets/stylesheets/bootstrap.css | 9030 +++++++++++++++++ .../assets/stylesheets/bootstrap.css.map | 1 + .../assets/stylesheets/bootstrap.min.css | 7 + .../assets/stylesheets/bootstrap.min.css.map | 1 + .../grails-app/assets/stylesheets/errors.css | 109 + .../grails-app/assets/stylesheets/grails.css | 1078 ++ .../grails-app/assets/stylesheets/main.css | 594 ++ .../grails-app/assets/stylesheets/mobile.css | 82 + .../issue450/grails-app/conf/application.yml | 87 + examples/issue450/grails-app/conf/logback.xml | 17 + .../BookController.groovy | 27 + .../multitenantcomposite/UrlMappings.groovy | 16 + .../domain/multitenantcomposite/Book.groovy | 14 + .../multitenantcomposite/Application.groovy | 13 + .../multitenantcomposite/BootStrap.groovy | 24 + .../multitenantcomposite/BookService.groovy | 11 + .../issue450/grails-app/views/book/books.gsp | 12 + .../issue450/grails-app/views/book/index.gsp | 13 + examples/issue450/grails-app/views/error.gsp | 31 + examples/issue450/grails-app/views/index.gsp | 77 + .../grails-app/views/layouts/main.gsp | 70 + .../issue450/grails-app/views/notFound.gsp | 14 + .../groovy/example/BookControllerSpec.groovy | 29 + examples/spring-boot-hibernate5/build.gradle | 32 +- .../main/groovy/example/Application.groovy | 3 +- .../main/groovy/example/BookController.groovy | 4 +- .../src/main/resources/application.yml | 18 +- examples/standalone-hibernate5/build.gradle | 24 +- gradle.properties | 20 +- grails-datastore-gorm-hibernate5/build.gradle | 2 +- .../tests/proxy/ByteBuddyProxySpec.groovy | 2 +- settings.gradle | 3 + 115 files changed, 24864 insertions(+), 868 deletions(-) delete mode 100644 examples/grails-data-service/grails-app/conf/application.groovy delete mode 100644 examples/grails-data-service/grails-app/domain/example/MultitenantBook.groovy create mode 100644 examples/grails-data-service/src/integration-test/groovy/example/ServiceInjectionSpec.groovy create mode 100644 examples/grails-data-service/src/main/groovy/example/ClassUsingAService.groovy delete mode 100644 examples/grails-data-service/src/main/groovy/example/LoginAuthenticationSucessHandler.groovy rename examples/grails3-hibernate5/grails-app/controllers/{ => functional/tests}/UrlMappings.groovy (91%) create mode 100644 examples/issue450/build.gradle create mode 100644 examples/issue450/grails-app/assets/images/advancedgrails.svg create mode 100644 examples/issue450/grails-app/assets/images/apple-touch-icon-retina.png create mode 100644 examples/issue450/grails-app/assets/images/apple-touch-icon.png create mode 100644 examples/issue450/grails-app/assets/images/documentation.svg create mode 100644 examples/issue450/grails-app/assets/images/favicon.ico create mode 100644 examples/issue450/grails-app/assets/images/grails-cupsonly-logo-white.svg create mode 100644 examples/issue450/grails-app/assets/images/grails.svg create mode 100644 examples/issue450/grails-app/assets/images/skin/database_add.png create mode 100644 examples/issue450/grails-app/assets/images/skin/database_delete.png create mode 100644 examples/issue450/grails-app/assets/images/skin/database_edit.png create mode 100644 examples/issue450/grails-app/assets/images/skin/database_save.png create mode 100644 examples/issue450/grails-app/assets/images/skin/database_table.png create mode 100644 examples/issue450/grails-app/assets/images/skin/exclamation.png create mode 100644 examples/issue450/grails-app/assets/images/skin/house.png create mode 100644 examples/issue450/grails-app/assets/images/skin/information.png create mode 100644 examples/issue450/grails-app/assets/images/skin/shadow.jpg create mode 100644 examples/issue450/grails-app/assets/images/skin/sorted_asc.gif create mode 100644 examples/issue450/grails-app/assets/images/skin/sorted_desc.gif create mode 100644 examples/issue450/grails-app/assets/images/slack.svg create mode 100644 examples/issue450/grails-app/assets/images/spinner.gif create mode 100644 examples/issue450/grails-app/assets/javascripts/application.js create mode 100644 examples/issue450/grails-app/assets/javascripts/bootstrap.bundle.js create mode 100644 examples/issue450/grails-app/assets/javascripts/bootstrap.bundle.js.map create mode 100644 examples/issue450/grails-app/assets/javascripts/bootstrap.bundle.min.js create mode 100644 examples/issue450/grails-app/assets/javascripts/bootstrap.bundle.min.js.map create mode 100644 examples/issue450/grails-app/assets/javascripts/bootstrap.js create mode 100644 examples/issue450/grails-app/assets/javascripts/bootstrap.js.map create mode 100644 examples/issue450/grails-app/assets/javascripts/bootstrap.min.js create mode 100644 examples/issue450/grails-app/assets/javascripts/bootstrap.min.js.map create mode 100644 examples/issue450/grails-app/assets/javascripts/jquery-3.3.1.min.js create mode 100644 examples/issue450/grails-app/assets/javascripts/jquery-3.3.1.min.js.map create mode 100644 examples/issue450/grails-app/assets/javascripts/popper.min.js create mode 100644 examples/issue450/grails-app/assets/stylesheets/application.css create mode 100644 examples/issue450/grails-app/assets/stylesheets/bootstrap-grid.css create mode 100644 examples/issue450/grails-app/assets/stylesheets/bootstrap-reboot.css create mode 100644 examples/issue450/grails-app/assets/stylesheets/bootstrap.css create mode 100644 examples/issue450/grails-app/assets/stylesheets/bootstrap.css.map create mode 100644 examples/issue450/grails-app/assets/stylesheets/bootstrap.min.css create mode 100644 examples/issue450/grails-app/assets/stylesheets/bootstrap.min.css.map create mode 100644 examples/issue450/grails-app/assets/stylesheets/errors.css create mode 100644 examples/issue450/grails-app/assets/stylesheets/grails.css create mode 100644 examples/issue450/grails-app/assets/stylesheets/main.css create mode 100644 examples/issue450/grails-app/assets/stylesheets/mobile.css create mode 100644 examples/issue450/grails-app/conf/application.yml create mode 100644 examples/issue450/grails-app/conf/logback.xml create mode 100644 examples/issue450/grails-app/controllers/multitenantcomposite/BookController.groovy create mode 100644 examples/issue450/grails-app/controllers/multitenantcomposite/UrlMappings.groovy create mode 100644 examples/issue450/grails-app/domain/multitenantcomposite/Book.groovy create mode 100644 examples/issue450/grails-app/init/multitenantcomposite/Application.groovy create mode 100644 examples/issue450/grails-app/init/multitenantcomposite/BootStrap.groovy create mode 100644 examples/issue450/grails-app/services/multitenantcomposite/BookService.groovy create mode 100644 examples/issue450/grails-app/views/book/books.gsp create mode 100644 examples/issue450/grails-app/views/book/index.gsp create mode 100644 examples/issue450/grails-app/views/error.gsp create mode 100644 examples/issue450/grails-app/views/index.gsp create mode 100644 examples/issue450/grails-app/views/layouts/main.gsp create mode 100644 examples/issue450/grails-app/views/notFound.gsp create mode 100644 examples/issue450/src/integration-test/groovy/example/BookControllerSpec.groovy diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 150c95a2..3a27cc11 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -28,7 +28,7 @@ jobs: GRADLE_ENTERPRISE_BUILD_CACHE_NODE_USER: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_USER }} GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY: ${{ secrets.GRADLE_ENTERPRISE_BUILD_CACHE_NODE_KEY }} with: - arguments: build -Dgeb.env=chromeHeadless + arguments: build publish: if: github.event_name == 'push' runs-on: ubuntu-latest diff --git a/build.gradle b/build.gradle index 0552e278..a70d5589 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,6 @@ buildscript { classpath "org.grails:grails-gradle-plugin:$grailsGradlePluginVersion" classpath "org.grails.plugins:views-gradle:$viewsGradleVersion" classpath "org.asciidoctor:asciidoctor-gradle-jvm:$asciidoctorGradleVersion" - classpath "com.github.erdi:webdriver-binaries-gradle-plugin:$webdriverBinariesPluginVersion" } } @@ -90,7 +89,6 @@ subprojects { Project subproject -> if (subproject.name != "examples-grails-data-service") { apply plugin:"org.grails.grails-web" apply plugin:"org.grails.grails-gsp" - apply plugin:"com.github.erdi.webdriver-binaries" } } diff --git a/examples/grails-data-service/build.gradle b/examples/grails-data-service/build.gradle index 6b793a73..80bec7fd 100644 --- a/examples/grails-data-service/build.gradle +++ b/examples/grails-data-service/build.gradle @@ -1,30 +1,28 @@ -group "examples" +plugins { + id 'groovy' + id 'org.grails.grails-web' + id 'org.grails.plugins.views-json' +} + -apply plugin:"org.grails.grails-web" -apply plugin:"org.grails.plugins.views-json" -apply plugin: 'java' +version = rootProject.version +group = 'examples' dependencies { - implementation "org.springframework.boot:spring-boot-starter-logging" - implementation "org.springframework.boot:spring-boot-autoconfigure" - implementation "org.grails:grails-core" - implementation "org.springframework.boot:spring-boot-starter-actuator" - implementation "org.springframework.boot:spring-boot-starter-tomcat" - implementation "org.grails:grails-dependencies" - implementation "org.grails:grails-plugin-codecs" - implementation "org.grails:grails-plugin-services" - implementation "org.grails:grails-plugin-datasource" - implementation "org.grails:grails-web-boot" - implementation "org.grails:grails-logging" - implementation "org.grails.plugins:cache" - implementation "org.grails.plugins:events" - implementation project(":grails-plugin") - implementation "org.hibernate:hibernate-core-jakarta:$hibernateVersion" - implementation "org.grails.plugins:views-json" - implementation "org.grails.plugins:views-json-templates" - implementation "org.grails.plugins:spring-security-rest:$grailsSpringSecurityRestVersion" - profile "org.grails.profiles:rest-api" - runtimeOnly "org.apache.tomcat:tomcat-jdbc" - testImplementation "org.grails:grails-gorm-testing-support:$testingSupportVersion" - testImplementation "org.grails:grails-web-testing-support:$testingSupportVersion" + + implementation project(':grails-plugin') + implementation 'org.grails:grails-core' + implementation 'org.grails.plugins:views-json' + + runtimeOnly 'com.h2database:h2' + runtimeOnly 'com.zaxxer:HikariCP' + runtimeOnly 'org.grails:grails-plugin-databinding' + runtimeOnly 'org.grails:grails-plugin-i18n' + runtimeOnly 'org.grails:grails-plugin-services' + runtimeOnly 'org.grails:grails-plugin-url-mappings' + runtimeOnly 'org.springframework.boot:spring-boot-autoconfigure' + runtimeOnly 'org.springframework.boot:spring-boot-starter-logging' + runtimeOnly 'org.springframework.boot:spring-boot-starter-tomcat' + + integrationTestImplementation 'org.grails:grails-testing-support' } diff --git a/examples/grails-data-service/grails-app/conf/application.groovy b/examples/grails-data-service/grails-app/conf/application.groovy deleted file mode 100644 index 3f36c438..00000000 --- a/examples/grails-data-service/grails-app/conf/application.groovy +++ /dev/null @@ -1,14 +0,0 @@ -grails.plugin.springsecurity.rest.token.storage.jwt.secret = "anystoragejwtsecretwithalargevalue" -grails.plugin.springsecurity.filterChain.chainMap = [ - //Stateless chain - [ - pattern: '/**', - filters: 'JOINED_FILTERS,-anonymousAuthenticationFilter,-exceptionTranslationFilter,-authenticationProcessingFilter,-securityContextPersistenceFilter,-rememberMeAuthenticationFilter' - ], - - //Traditional, stateful chain - [ - pattern: '/stateful/**', - filters: 'JOINED_FILTERS,-restTokenValidationFilter,-restExceptionTranslationFilter' - ] -] diff --git a/examples/grails-data-service/grails-app/conf/application.yml b/examples/grails-data-service/grails-app/conf/application.yml index 1f0743df..c8d02a5e 100644 --- a/examples/grails-data-service/grails-app/conf/application.yml +++ b/examples/grails-data-service/grails-app/conf/application.yml @@ -1,121 +1,66 @@ -grails: - gorm: - multiTenancy: - mode: DISCRIMINATOR - tenantResolverClass: org.grails.datastore.mapping.multitenancy.web.SessionTenantResolver ---- -grails: - profile: rest-api - codegen: - defaultPackage: example - gorm: - reactor: - # Whether to translate GORM events into Reactor events - # Disabled by default for performance reasons - events: false info: - app: - name: '@info.app.name@' - version: '@info.app.version@' - grailsVersion: '@info.app.grailsVersion@' -spring: - jmx: - unique-names: true - main: - banner-mode: "off" - groovy: - template: - check-template-location: false - devtools: - restart: - additional-exclude: - - '*.gsp' - - '**/*.gsp' - - '*.gson' - - '**/*.gson' - - 'logback.groovy' - - '*.properties' -management: - endpoints: - enabled-by-default: false - + app: + name: '@info.app.name@' + version: '@info.app.version@' + grailsVersion: '@info.app.grailsVersion@' --- grails: - mime: - disable: - accept: - header: - userAgents: - - Gecko - - WebKit - - Presto - - Trident - types: - json: - - application/json - - text/json - hal: - - application/hal+json - - application/hal+xml - xml: - - text/xml - - application/xml - atom: application/atom+xml - css: text/css - csv: text/csv - js: text/javascript - rss: application/rss+xml - text: text/plain - all: '*/*' - urlmapping: - cache: - maxsize: 1000 - controllers: - defaultScope: singleton - converters: - encoding: UTF-8 - + profile: rest-api + codegen: + defaultPackage: example + mime: + disable: + accept: + header: + userAgents: + - Gecko + - WebKit + - Presto + - Trident + types: + json: + - application/json + - text/json + hal: + - application/hal+json + - application/hal+xml + xml: + - text/xml + - application/xml + atom: application/atom+xml + css: text/css + csv: text/csv + js: text/javascript + rss: application/rss+xml + text: text/plain + all: '*/*' + urlmapping: + cache: + maxsize: 1000 + converters: + encoding: UTF-8 --- hibernate: - cache: - queries: false - use_second_level_cache: false - use_query_cache: false + cache: + queries: false + use_second_level_cache: false + use_query_cache: false dataSource: - pooled: true - jmxExport: true - driverClassName: org.h2.Driver - username: sa - password: '' + pooled: true + driverClassName: org.h2.Driver + username: sa + password: '' environments: - development: - dataSource: - dbCreate: create-drop - url: jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE - test: - dataSource: - dbCreate: update - url: jdbc:h2:mem:testDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE - production: - dataSource: - dbCreate: none - url: jdbc:h2:./prodDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE - properties: - jmxEnabled: true - initialSize: 5 - maxActive: 50 - minIdle: 5 - maxIdle: 25 - maxWait: 10000 - maxAge: 600000 - timeBetweenEvictionRunsMillis: 5000 - minEvictableIdleTimeMillis: 60000 - validationQuery: SELECT 1 - validationQueryTimeout: 3 - validationInterval: 15000 - testOnBorrow: true - testWhileIdle: true - testOnReturn: false - jdbcInterceptors: ConnectionState - defaultTransactionIsolation: 2 # TRANSACTION_READ_COMMITTED + development: + dataSource: + dbCreate: create-drop + url: jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + test: + dataSource: + dbCreate: update + url: jdbc:h2:mem:testDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + production: + dataSource: + dbCreate: none + url: jdbc:h2:./prodDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE \ No newline at end of file diff --git a/examples/grails-data-service/grails-app/conf/logback.xml b/examples/grails-data-service/grails-app/conf/logback.xml index f4e77836..663389a0 100644 --- a/examples/grails-data-service/grails-app/conf/logback.xml +++ b/examples/grails-data-service/grails-app/conf/logback.xml @@ -7,7 +7,7 @@ UTF-8 - '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex' + %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex diff --git a/examples/grails-data-service/grails-app/conf/spring/resources.groovy b/examples/grails-data-service/grails-app/conf/spring/resources.groovy index fe2abe80..a235f2c1 100644 --- a/examples/grails-data-service/grails-app/conf/spring/resources.groovy +++ b/examples/grails-data-service/grails-app/conf/spring/resources.groovy @@ -1,10 +1,10 @@ -import example.LoginAuthenticationSucessHandler +import example.ClassUsingAService import example.TestBean // Place your Spring DSL code here beans = { - restAuthenticationSuccessHandler(LoginAuthenticationSucessHandler) { + classUsingAService(ClassUsingAService) { testService = ref('testService') } diff --git a/examples/grails-data-service/grails-app/domain/example/MultitenantBook.groovy b/examples/grails-data-service/grails-app/domain/example/MultitenantBook.groovy deleted file mode 100644 index c5629380..00000000 --- a/examples/grails-data-service/grails-app/domain/example/MultitenantBook.groovy +++ /dev/null @@ -1,15 +0,0 @@ -package example - -import grails.gorm.MultiTenant - -// Issue: https://github.com/grails/gorm-hibernate5/issues/450 -// Pr: https://github.com/grails/gorm-hibernate5/pull/451 -class MultitenantBook implements MultiTenant, Serializable { - String id - String tenantId - String title - - static mapping = { - id composite: ['id', 'tenantId'] - } -} \ No newline at end of file diff --git a/examples/grails-data-service/grails-app/services/example/TestService.groovy b/examples/grails-data-service/grails-app/services/example/TestService.groovy index 061d0104..233fa653 100644 --- a/examples/grails-data-service/grails-app/services/example/TestService.groovy +++ b/examples/grails-data-service/grails-app/services/example/TestService.groovy @@ -1,11 +1,8 @@ package example -import grails.gorm.transactions.TransactionService - class TestService { LibraryService libraryService - TransactionService transactionService Boolean testDataService(Serializable id) { libraryService.bookExists(id) diff --git a/examples/grails-data-service/src/integration-test/groovy/example/ServiceInjectionSpec.groovy b/examples/grails-data-service/src/integration-test/groovy/example/ServiceInjectionSpec.groovy new file mode 100644 index 00000000..9c5e2e0a --- /dev/null +++ b/examples/grails-data-service/src/integration-test/groovy/example/ServiceInjectionSpec.groovy @@ -0,0 +1,21 @@ +package example + +import grails.testing.mixin.integration.Integration +import spock.lang.Issue +import spock.lang.Specification + +@Integration +class ServiceInjectionSpec extends Specification { + + ClassUsingAService classUsingAService + + @Issue('https://github.com/grails/gorm-hibernate5/issues/202') + void 'data-service is injected correctly'() { + when: + classUsingAService.doSomethingWithTheService() + + then: + noExceptionThrown() + } + +} diff --git a/examples/grails-data-service/src/integration-test/groovy/example/TestServiceSpec.groovy b/examples/grails-data-service/src/integration-test/groovy/example/TestServiceSpec.groovy index 51a517cb..759725c5 100644 --- a/examples/grails-data-service/src/integration-test/groovy/example/TestServiceSpec.groovy +++ b/examples/grails-data-service/src/integration-test/groovy/example/TestServiceSpec.groovy @@ -11,12 +11,16 @@ class TestServiceSpec extends Specification { TestService testService TestBean testBean + ClassUsingAService classUsingAService @Autowired List bookServiceList void "test data-service is loaded correctly"() { when: + classUsingAService.doSomethingWithTheService() + + and: testService.testDataService() then: diff --git a/examples/grails-data-service/src/main/groovy/example/ClassUsingAService.groovy b/examples/grails-data-service/src/main/groovy/example/ClassUsingAService.groovy new file mode 100644 index 00000000..d549e5b1 --- /dev/null +++ b/examples/grails-data-service/src/main/groovy/example/ClassUsingAService.groovy @@ -0,0 +1,14 @@ +package example + +import groovy.transform.CompileStatic + +@CompileStatic +class ClassUsingAService { + + TestService testService + + void doSomethingWithTheService() { + testService.testDataService(1l) + } + +} diff --git a/examples/grails-data-service/src/main/groovy/example/LoginAuthenticationSucessHandler.groovy b/examples/grails-data-service/src/main/groovy/example/LoginAuthenticationSucessHandler.groovy deleted file mode 100644 index 7b3574b5..00000000 --- a/examples/grails-data-service/src/main/groovy/example/LoginAuthenticationSucessHandler.groovy +++ /dev/null @@ -1,23 +0,0 @@ -package example - -import grails.events.EventPublisher -import grails.plugin.springsecurity.rest.RestAuthenticationSuccessHandler -import groovy.transform.CompileStatic -import org.springframework.security.core.Authentication - -import jakarta.servlet.ServletException -import jakarta.servlet.http.HttpServletRequest -import jakarta.servlet.http.HttpServletResponse - -@CompileStatic -class LoginAuthenticationSucessHandler extends RestAuthenticationSuccessHandler implements EventPublisher { - - TestService testService - - @Override - void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - super.onAuthenticationSuccess(request, response, authentication) - testService.testDataService(1l) - } - -} diff --git a/examples/grails-hibernate-groovy-proxy/build.gradle b/examples/grails-hibernate-groovy-proxy/build.gradle index 63f9a4b7..5afa796a 100644 --- a/examples/grails-hibernate-groovy-proxy/build.gradle +++ b/examples/grails-hibernate-groovy-proxy/build.gradle @@ -1,23 +1,21 @@ -group "examples" - -dependencies { - implementation "org.yakworks:hibernate-groovy-proxy:$hibernateGroovyProxy" +plugins { + id 'groovy' + id 'org.grails.grails-web' +} - implementation "org.springframework.boot:spring-boot-starter-logging" - implementation "org.springframework.boot:spring-boot-autoconfigure" - implementation "org.grails:grails-core:$grailsVersion" - implementation "org.grails:grails-dependencies:$grailsVersion", { - exclude module:'grails-datastore-simple' - } - implementation "org.grails:grails-web-boot:$grailsVersion" - implementation project(":grails-plugin") +version = rootProject.version +group = 'examples' - implementation "org.hibernate:hibernate-core-jakarta:$hibernateVersion" +dependencies { - runtimeOnly "com.h2database:h2" - runtimeOnly "org.yaml:snakeyaml:$snakeYamlVersion" - runtimeOnly "org.apache.tomcat:tomcat-jdbc:$tomcatVersion" + implementation project(':grails-plugin') + implementation 'org.grails:grails-core' + implementation "org.yakworks:hibernate-groovy-proxy:$yakworksHibernateGroovyProxyVersion" - testImplementation "org.grails:grails-gorm-testing-support:$testingSupportVersion" + runtimeOnly 'com.h2database:h2' + runtimeOnly 'com.zaxxer:HikariCP' + runtimeOnly 'org.springframework.boot:spring-boot-autoconfigure' + runtimeOnly 'org.springframework.boot:spring-boot-starter-logging' -} + testImplementation 'org.grails:grails-testing-support' +} \ No newline at end of file diff --git a/examples/grails-hibernate-groovy-proxy/grails-app/conf/application.yml b/examples/grails-hibernate-groovy-proxy/grails-app/conf/application.yml index 5ea30c00..a721bc9f 100644 --- a/examples/grails-hibernate-groovy-proxy/grails-app/conf/application.yml +++ b/examples/grails-hibernate-groovy-proxy/grails-app/conf/application.yml @@ -1,24 +1,16 @@ +info: + app: + name: '@info.app.name@' + version: '@info.app.version@' + grailsVersion: '@info.app.grailsVersion@' --- grails: - profile: web - codegen: - defaultPackage: datasources -info: - app: - name: '@info.app.name@' - version: '@info.app.version@' - grailsVersion: '@info.app.grailsVersion@' -spring: - groovy: - template: - check-template-location: false - main: - allow-circular-references: true - + profile: web + codegen: + defaultPackage: datasources --- dataSource: pooled: true - jmxExport: true driverClassName: org.h2.Driver dbCreate: create-drop url: jdbc:h2:mem:books;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE diff --git a/examples/grails-hibernate-groovy-proxy/grails-app/conf/logback.xml b/examples/grails-hibernate-groovy-proxy/grails-app/conf/logback.xml index f4e77836..663389a0 100644 --- a/examples/grails-hibernate-groovy-proxy/grails-app/conf/logback.xml +++ b/examples/grails-hibernate-groovy-proxy/grails-app/conf/logback.xml @@ -7,7 +7,7 @@ UTF-8 - '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex' + %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex diff --git a/examples/grails-hibernate-groovy-proxy/grails-app/domain/example/Customer.groovy b/examples/grails-hibernate-groovy-proxy/grails-app/domain/example/Customer.groovy index 213e0eb3..d5106864 100644 --- a/examples/grails-hibernate-groovy-proxy/grails-app/domain/example/Customer.groovy +++ b/examples/grails-hibernate-groovy-proxy/grails-app/domain/example/Customer.groovy @@ -9,6 +9,13 @@ class Customer implements Serializable { String name + @SuppressWarnings('unused') + Customer() { + // no-args constructor for proxying. + // Usually added by ControllerDomainTransformer + // from 'org.grails:grails-plugin-controllers' + } + Customer(Long id, String name) { this.id = id this.name = name diff --git a/examples/grails-hibernate-groovy-proxy/grails-app/init/datasources/Application.groovy b/examples/grails-hibernate-groovy-proxy/grails-app/init/datasources/Application.groovy index f9bb4396..249307f2 100644 --- a/examples/grails-hibernate-groovy-proxy/grails-app/init/datasources/Application.groovy +++ b/examples/grails-hibernate-groovy-proxy/grails-app/init/datasources/Application.groovy @@ -3,10 +3,7 @@ package datasources import grails.boot.GrailsApp import grails.boot.config.GrailsAutoConfiguration import groovy.transform.CompileStatic -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration -//@EnableAutoConfiguration(exclude = DataSourceTransactionManagerAutoConfiguration) @CompileStatic class Application extends GrailsAutoConfiguration { static void main(String[] args) { diff --git a/examples/grails-hibernate-groovy-proxy/src/test/groovy/example/ProxySpec.groovy b/examples/grails-hibernate-groovy-proxy/src/test/groovy/example/ProxySpec.groovy index 73c50c9b..084cac28 100644 --- a/examples/grails-hibernate-groovy-proxy/src/test/groovy/example/ProxySpec.groovy +++ b/examples/grails-hibernate-groovy-proxy/src/test/groovy/example/ProxySpec.groovy @@ -4,7 +4,6 @@ import org.hibernate.Hibernate import grails.gorm.transactions.Rollback import grails.test.hibernate.HibernateSpec -import spock.lang.Ignore /** * Tests Proxy with hibernate-groovy-proxy diff --git a/examples/grails3-database-per-tenant/build.gradle b/examples/grails3-database-per-tenant/build.gradle index b671317d..940f6c74 100644 --- a/examples/grails3-database-per-tenant/build.gradle +++ b/examples/grails3-database-per-tenant/build.gradle @@ -1,41 +1,32 @@ -group "examples" - -configurations.all { - resolutionStrategy.eachDependency { DependencyResolveDetails details -> - } +plugins { + id 'com.bertramlabs.asset-pipeline' version '5.0.1' + id 'groovy' + id 'org.grails.grails-gsp' + id 'org.grails.grails-web' } -grails { - agent { - enabled = false - } -} +version = rootProject.version +group = 'examples' dependencies { - implementation "org.springframework.boot:spring-boot-starter-logging" - implementation "org.springframework.boot:spring-boot-autoconfigure" - implementation "org.springframework.boot:spring-boot-starter-actuator" - implementation "org.springframework.boot:spring-boot-starter-tomcat" - implementation "jakarta.servlet:jakarta.servlet-api:$servletApiVersion" - implementation "org.grails:grails-core:$grailsVersion" - implementation "org.grails:grails-dependencies:$grailsVersion", { - exclude module:'grails-datastore-simple' - } - implementation "org.ysb33r.gradle:grolifant80:$grolifantVersion" - implementation "org.grails:grails-web-boot:$grailsVersion" - implementation project(":grails-plugin") - - implementation "org.hibernate:hibernate-core-jakarta:$hibernateVersion" + implementation project(':grails-plugin') + implementation 'org.grails:grails-core' + implementation 'org.grails:grails-plugin-rest' + implementation 'org.grails.plugins:gsp' - runtimeOnly "com.bertramlabs.plugins:asset-pipeline-grails:$assetPipelineVersion" - runtimeOnly "com.h2database:h2" - runtimeOnly "org.yaml:snakeyaml:$snakeYamlVersion" - runtimeOnly "org.apache.tomcat:tomcat-jdbc:$tomcatVersion" - runtimeOnly "org.grails.plugins:fields:$fieldsVersion" - runtimeOnly "org.grails.plugins:scaffolding:$scaffoldingVersion" + runtimeOnly 'com.bertramlabs.plugins:asset-pipeline-grails' + runtimeOnly 'com.h2database:h2' + runtimeOnly 'com.zaxxer:HikariCP' + runtimeOnly 'org.grails:grails-plugin-databinding' + runtimeOnly 'org.grails:grails-plugin-i18n' + runtimeOnly 'org.grails:grails-plugin-services' + runtimeOnly 'org.grails:grails-plugin-url-mappings' + runtimeOnly 'org.grails.plugins:fields' + runtimeOnly 'org.springframework.boot:spring-boot-autoconfigure' + runtimeOnly 'org.springframework.boot:spring-boot-starter-logging' + runtimeOnly 'org.springframework.boot:spring-boot-starter-tomcat' - testImplementation ("org.grails:grails-gorm-testing-support:$testingSupportVersion") { - exclude group: "org.spockframework", module: "spock-core" - } + testImplementation 'org.grails:grails-gorm-testing-support' + testImplementation 'org.spockframework:spock-core' } diff --git a/examples/grails3-database-per-tenant/grails-app/conf/application.yml b/examples/grails3-database-per-tenant/grails-app/conf/application.yml index 21ef8d77..9c7b8043 100644 --- a/examples/grails3-database-per-tenant/grails-app/conf/application.yml +++ b/examples/grails3-database-per-tenant/grails-app/conf/application.yml @@ -1,90 +1,77 @@ ---- -grails: - profile: web - codegen: - defaultPackage: datasources info: - app: - name: '@info.app.name@' - version: '@info.app.version@' - grailsVersion: '@info.app.grailsVersion@' -spring: - groovy: - template: - check-template-location: false - main: - allow-circular-references: true - + app: + name: '@info.app.name@' + version: '@info.app.version@' + grailsVersion: '@info.app.grailsVersion@' --- grails: - mime: - disable: - accept: - header: - userAgents: - - Gecko - - WebKit - - Presto - - Trident - types: - all: '*/*' - atom: application/atom+xml - css: text/css - csv: text/csv - form: application/x-www-form-urlencoded - html: - - text/html - - application/xhtml+xml - js: text/javascript - json: - - application/json - - text/json - multipartForm: multipart/form-data - rss: application/rss+xml - text: text/plain - hal: - - application/hal+json - - application/hal+xml - xml: - - text/xml - - application/xml - urlmapping: - cache: - maxsize: 1000 - controllers: - defaultScope: singleton - converters: - encoding: UTF-8 - hibernate: - cache: - queries: false - views: - default: - codec: html - gsp: - encoding: UTF-8 - htmlcodec: xml - codecs: - expression: html - scriptlets: html - taglib: none - staticparts: none - + profile: web + codegen: + defaultPackage: datasources + mime: + disable: + accept: + header: + userAgents: + - Gecko + - WebKit + - Presto + - Trident + types: + all: '*/*' + atom: application/atom+xml + css: text/css + csv: text/csv + form: application/x-www-form-urlencoded + html: + - text/html + - application/xhtml+xml + js: text/javascript + json: + - application/json + - text/json + multipartForm: multipart/form-data + rss: application/rss+xml + text: text/plain + hal: + - application/hal+json + - application/hal+xml + xml: + - text/xml + - application/xml + urlmapping: + cache: + maxsize: 1000 + converters: + encoding: UTF-8 + hibernate: + cache: + queries: false + views: + default: + codec: html + gsp: + encoding: UTF-8 + htmlcodec: xml + codecs: + expression: html + scriptlets: html + taglib: none + staticparts: none --- grails: gorm: multiTenancy: mode: DATABASE tenantResolverClass: org.grails.datastore.mapping.multitenancy.web.SessionTenantResolver +--- dataSource: pooled: true - jmxExport: true driverClassName: org.h2.Driver dbCreate: create-drop url: jdbc:h2:mem:books;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE - dataSources: - moreBooks: - url: jdbc:h2:mem:moreBooks;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE - evenMoreBooks: - url: jdbc:h2:mem:evenMoreBooks;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + moreBooks: + url: jdbc:h2:mem:moreBooks;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + evenMoreBooks: + url: jdbc:h2:mem:evenMoreBooks;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE \ No newline at end of file diff --git a/examples/grails3-database-per-tenant/grails-app/conf/logback.xml b/examples/grails3-database-per-tenant/grails-app/conf/logback.xml index f4e77836..663389a0 100644 --- a/examples/grails3-database-per-tenant/grails-app/conf/logback.xml +++ b/examples/grails3-database-per-tenant/grails-app/conf/logback.xml @@ -7,7 +7,7 @@ UTF-8 - '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex' + %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex diff --git a/examples/grails3-database-per-tenant/grails-app/init/datasources/Application.groovy b/examples/grails3-database-per-tenant/grails-app/init/datasources/Application.groovy index f9bb4396..249307f2 100644 --- a/examples/grails3-database-per-tenant/grails-app/init/datasources/Application.groovy +++ b/examples/grails3-database-per-tenant/grails-app/init/datasources/Application.groovy @@ -3,10 +3,7 @@ package datasources import grails.boot.GrailsApp import grails.boot.config.GrailsAutoConfiguration import groovy.transform.CompileStatic -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration -//@EnableAutoConfiguration(exclude = DataSourceTransactionManagerAutoConfiguration) @CompileStatic class Application extends GrailsAutoConfiguration { static void main(String[] args) { diff --git a/examples/grails3-database-per-tenant/grails-app/services/example/AnotherBookService.groovy b/examples/grails3-database-per-tenant/grails-app/services/example/AnotherBookService.groovy index a67c0da1..af7144ef 100644 --- a/examples/grails3-database-per-tenant/grails-app/services/example/AnotherBookService.groovy +++ b/examples/grails3-database-per-tenant/grails-app/services/example/AnotherBookService.groovy @@ -10,8 +10,9 @@ import grails.gorm.transactions.Transactional @CurrentTenant @Transactional class AnotherBookService { - Book saveBook(String title) { - new Book(title: "The Stand").save() + + Book saveBook(String title = 'The Stand') { + new Book(title: title).save() } @ReadOnly diff --git a/examples/grails3-hibernate5/build.gradle b/examples/grails3-hibernate5/build.gradle index bcf46bfe..0eb8edbf 100644 --- a/examples/grails3-hibernate5/build.gradle +++ b/examples/grails3-hibernate5/build.gradle @@ -1,39 +1,36 @@ -group "examples" - -dependencies { - implementation "org.springframework.boot:spring-boot-starter-logging" - implementation "org.springframework.boot:spring-boot-autoconfigure" - implementation "org.springframework.boot:spring-boot-starter-actuator" - implementation "org.springframework.boot:spring-boot-starter-tomcat" - implementation "jakarta.servlet:jakarta.servlet-api:$servletApiVersion" - implementation "org.grails:grails-core:$grailsVersion" - implementation "org.grails:grails-dependencies:$grailsVersion", { - exclude module:'grails-datastore-simple' - } - - implementation "org.grails:grails-web-boot:$grailsVersion" - implementation project(":grails-plugin") - - implementation "org.hibernate:hibernate-core-jakarta:$hibernateVersion" - implementation "org.hibernate:hibernate-ehcache:$hibernateVersion" - - runtimeOnly "com.bertramlabs.plugins:asset-pipeline-grails:$assetPipelineVersion" - runtimeOnly "com.h2database:h2" - runtimeOnly "org.yaml:snakeyaml:$snakeYamlVersion" - runtimeOnly "org.apache.tomcat:tomcat-jdbc:$tomcatVersion" - runtimeOnly "org.grails.plugins:fields:$fieldsVersion" - runtimeOnly "org.grails.plugins:scaffolding:$scaffoldingVersion" - - testImplementation("org.grails:grails-gorm-testing-support:$testingSupportVersion") { - exclude group: "org.spockframework", module: "spock-core" - } - testImplementation("org.grails:grails-web-testing-support:$testingSupportVersion") { - exclude group: "org.spockframework", module: "spock-core" - } - - integrationTestImplementation testFixtures("org.grails.plugins:geb:$gebPluginVersion") +plugins { + id 'com.bertramlabs.asset-pipeline' version '5.0.1' + id 'groovy' + id 'org.grails.grails-gsp' + id 'org.grails.grails-web' } +version = rootProject.version +group = 'examples' + +dependencies { -//compileGroovy.groovyOptions.fork = true -//compileGroovy.groovyOptions.forkOptions.jvmArgs = ['-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005'] + implementation project(':grails-plugin') + implementation 'jakarta.persistence:jakarta.persistence-api' + implementation 'jakarta.validation:jakarta.validation-api' + implementation 'org.grails:grails-core' + implementation 'org.grails:grails-plugin-rest' + implementation 'org.grails.plugins:gsp' + + runtimeOnly 'com.bertramlabs.plugins:asset-pipeline-grails' + runtimeOnly 'com.h2database:h2' + runtimeOnly 'com.zaxxer:HikariCP' + runtimeOnly 'org.grails:grails-plugin-databinding' + runtimeOnly 'org.grails:grails-plugin-i18n' + runtimeOnly 'org.grails:grails-plugin-services' + runtimeOnly 'org.grails:grails-plugin-url-mappings' + runtimeOnly 'org.grails.plugins:fields' + runtimeOnly 'org.springframework.boot:spring-boot-autoconfigure' + runtimeOnly 'org.springframework.boot:spring-boot-starter-logging' + runtimeOnly 'org.springframework.boot:spring-boot-starter-tomcat' + + testImplementation 'org.grails:grails-testing-support' + testImplementation 'org.grails:grails-web-testing-support' + + integrationTestImplementation testFixtures('org.grails.plugins:geb') +} \ No newline at end of file diff --git a/examples/grails3-hibernate5/grails-app/conf/application.yml b/examples/grails3-hibernate5/grails-app/conf/application.yml index d84ca03b..41d9d532 100644 --- a/examples/grails3-hibernate5/grails-app/conf/application.yml +++ b/examples/grails3-hibernate5/grails-app/conf/application.yml @@ -1,121 +1,87 @@ ---- -grails: - profile: web - codegen: - defaultPackage: functional.tests info: - app: - name: '@info.app.name@' - version: '@info.app.version@' - grailsVersion: '@info.app.grailsVersion@' -spring: - groovy: - template: - check-template-location: false - main: - allow-circular-references: true - + app: + name: '@info.app.name@' + version: '@info.app.version@' + grailsVersion: '@info.app.grailsVersion@' --- grails: - mime: - disable: - accept: - header: - userAgents: - - Gecko - - WebKit - - Presto - - Trident - types: - all: '*/*' - atom: application/atom+xml - css: text/css - csv: text/csv - form: application/x-www-form-urlencoded - html: - - text/html - - application/xhtml+xml - js: text/javascript - json: - - application/json - - text/json - multipartForm: multipart/form-data - pdf: application/pdf - rss: application/rss+xml - text: text/plain - hal: - - application/hal+json - - application/hal+xml - xml: - - text/xml - - application/xml + profile: web + codegen: + defaultPackage: functional.tests + mime: + disable: + accept: + header: + userAgents: + - Gecko + - WebKit + - Presto + - Trident + types: + all: '*/*' + atom: application/atom+xml + css: text/css + csv: text/csv + form: application/x-www-form-urlencoded + html: + - text/html + - application/xhtml+xml + js: text/javascript + json: + - application/json + - text/json + multipartForm: multipart/form-data + pdf: application/pdf + rss: application/rss+xml + text: text/plain + hal: + - application/hal+json + - application/hal+xml + xml: + - text/xml + - application/xml urlmapping: - cache: - maxsize: 1000 - controllers: - defaultScope: singleton + cache: + maxsize: 1000 converters: - encoding: UTF-8 + encoding: UTF-8 views: - default: - codec: html - gsp: - encoding: UTF-8 - htmlcodec: xml - codecs: - expression: html - scriptlets: html - taglib: none - staticparts: none + default: + codec: html + gsp: + encoding: UTF-8 + htmlcodec: xml + codecs: + expression: html + scriptlets: html + taglib: none + staticparts: none --- hibernate: configClass: functional.tests.CustomHibernateMappingContextConfiguration packagesToScan: - another cache: - queries: false - use_second_level_cache: true - use_query_cache: false - region.factory_class: 'org.hibernate.cache.ehcache.EhCacheRegionFactory' -endpoints: - jmx: - unique-names: true - + queries: false + use_second_level_cache: true + use_query_cache: false + region.factory_class: 'org.hibernate.cache.ehcache.EhCacheRegionFactory' +--- dataSource: - pooled: true - jmxExport: true - driverClassName: org.h2.Driver - username: sa - password: - + pooled: true + driverClassName: org.h2.Driver + username: sa + password: environments: - development: - dataSource: - dbCreate: create-drop - url: jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE - test: - dataSource: - dbCreate: update - url: jdbc:h2:mem:testDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE - production: - dataSource: - dbCreate: update - url: jdbc:h2:./prodDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE - properties: - jmxEnabled: true - initialSize: 5 - maxActive: 50 - minIdle: 5 - maxIdle: 25 - maxWait: 10000 - maxAge: 600000 - timeBetweenEvictionRunsMillis: 5000 - minEvictableIdleTimeMillis: 60000 - validationQuery: SELECT 1 - validationQueryTimeout: 3 - validationInterval: 15000 - testOnBorrow: true - testWhileIdle: true - testOnReturn: false - jdbcInterceptors: ConnectionState - defaultTransactionIsolation: 2 # TRANSACTION_READ_COMMITTED + development: + dataSource: + dbCreate: create-drop + url: jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + test: + dataSource: + dbCreate: update + url: jdbc:h2:mem:testDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + production: + dataSource: + dbCreate: update + url: jdbc:h2:./prodDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE \ No newline at end of file diff --git a/examples/grails3-hibernate5/grails-app/conf/logback.xml b/examples/grails3-hibernate5/grails-app/conf/logback.xml index f4e77836..663389a0 100644 --- a/examples/grails3-hibernate5/grails-app/conf/logback.xml +++ b/examples/grails3-hibernate5/grails-app/conf/logback.xml @@ -7,7 +7,7 @@ UTF-8 - '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex' + %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex diff --git a/examples/grails3-hibernate5/grails-app/controllers/UrlMappings.groovy b/examples/grails3-hibernate5/grails-app/controllers/functional/tests/UrlMappings.groovy similarity index 91% rename from examples/grails3-hibernate5/grails-app/controllers/UrlMappings.groovy rename to examples/grails3-hibernate5/grails-app/controllers/functional/tests/UrlMappings.groovy index 01d7bc91..9cb56da1 100644 --- a/examples/grails3-hibernate5/grails-app/controllers/UrlMappings.groovy +++ b/examples/grails3-hibernate5/grails-app/controllers/functional/tests/UrlMappings.groovy @@ -1,3 +1,5 @@ +package functional.tests + class UrlMappings { static mappings = { diff --git a/examples/grails3-hibernate5/grails-app/domain/functional/tests/Business.groovy b/examples/grails3-hibernate5/grails-app/domain/functional/tests/Business.groovy index 4bbe04ff..2c81d971 100644 --- a/examples/grails3-hibernate5/grails-app/domain/functional/tests/Business.groovy +++ b/examples/grails3-hibernate5/grails-app/domain/functional/tests/Business.groovy @@ -1,6 +1,7 @@ package functional.tests class Business { + String name static hasMany = [ diff --git a/examples/grails3-hibernate5/grails-app/domain/functional/tests/Product.groovy b/examples/grails3-hibernate5/grails-app/domain/functional/tests/Product.groovy index d69ac1c1..573144ae 100644 --- a/examples/grails3-hibernate5/grails-app/domain/functional/tests/Product.groovy +++ b/examples/grails3-hibernate5/grails-app/domain/functional/tests/Product.groovy @@ -1,7 +1,5 @@ package functional.tests -import grails.gorm.annotation.JpaEntity - import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue import jakarta.persistence.Id @@ -12,6 +10,7 @@ import jakarta.validation.constraints.Digits */ @Entity class Product { + @Id @GeneratedValue Long myId diff --git a/examples/grails3-hibernate5/src/test/groovy/functional/tests/BookControllerUnitSpec.groovy b/examples/grails3-hibernate5/src/test/groovy/functional/tests/BookControllerUnitSpec.groovy index 5867e1d6..ef3f205a 100644 --- a/examples/grails3-hibernate5/src/test/groovy/functional/tests/BookControllerUnitSpec.groovy +++ b/examples/grails3-hibernate5/src/test/groovy/functional/tests/BookControllerUnitSpec.groovy @@ -2,18 +2,12 @@ package functional.tests import grails.test.hibernate.HibernateSpec import grails.testing.web.controllers.ControllerUnitTest -import org.grails.plugins.web.mime.MimeTypesGrailsPlugin -import spock.lang.Ignore /** * Created by graemerocher on 24/10/16. */ class BookControllerUnitSpec extends HibernateSpec implements ControllerUnitTest { - def setupSpec() { - defineBeans(new MimeTypesGrailsPlugin()) - } - def setup() { def bookService = Mock(BookService) bookService.getBook(_) >> { args -> diff --git a/examples/grails3-multiple-datasources/build.gradle b/examples/grails3-multiple-datasources/build.gradle index 302e7cee..0ecd983a 100644 --- a/examples/grails3-multiple-datasources/build.gradle +++ b/examples/grails3-multiple-datasources/build.gradle @@ -1,30 +1,41 @@ -group "examples" +plugins { + id 'groovy' +} -dependencies { - implementation "org.springframework.boot:spring-boot-starter-logging" - implementation "org.springframework.boot:spring-boot-autoconfigure" - implementation "org.springframework.boot:spring-boot-starter-actuator" - implementation "org.springframework.boot:spring-boot-starter-tomcat" - implementation "jakarta.servlet:jakarta.servlet-api:$servletApiVersion" - implementation "org.grails:grails-core:$grailsVersion" - implementation "org.grails:grails-dependencies:$grailsVersion", { - exclude module:'grails-datastore-simple' - } +version = rootProject.version +group = 'examples' + +configurations { + astTransformation +} - implementation "org.grails:grails-web-boot:$grailsVersion" - implementation project(":grails-plugin") +dependencies { - implementation "org.hibernate:hibernate-core-jakarta:$hibernateVersion" + astTransformation 'jakarta.servlet:jakarta.servlet-api' + astTransformation 'org.grails:grails-plugin-controllers' - runtimeOnly "com.bertramlabs.plugins:asset-pipeline-grails:$assetPipelineVersion" - runtimeOnly "com.h2database:h2" - runtimeOnly "org.apache.tomcat:tomcat-jdbc:$tomcatVersion" - runtimeOnly "org.grails.plugins:fields:$fieldsVersion" - runtimeOnly "org.grails.plugins:scaffolding:$scaffoldingVersion" + implementation project(':grails-plugin') + implementation 'org.grails:grails-core' + implementation 'org.grails:grails-plugin-domain-class' - testImplementation "org.grails:grails-gorm-testing-support:$testingSupportVersion" + runtimeOnly 'com.h2database:h2' + runtimeOnly 'com.zaxxer:HikariCP' + runtimeOnly 'org.grails:grails-plugin-databinding' + runtimeOnly 'org.grails:grails-plugin-controllers' + runtimeOnly 'org.springframework.boot:spring-boot-autoconfigure' + runtimeOnly 'org.springframework.boot:spring-boot-starter-logging' + runtimeOnly 'org.springframework.boot:spring-boot-starter-tomcat' + testImplementation 'org.grails:grails-testing-support' - integrationTestImplementation testFixtures("org.grails.plugins:geb:$gebPluginVersion") + testRuntimeOnly 'org.grails:grails-web-testing-support' } +sourceSets { + main { + compileClasspath += configurations.astTransformation + } + integrationTest { + compileClasspath += configurations.astTransformation + } +} \ No newline at end of file diff --git a/examples/grails3-multiple-datasources/grails-app/conf/application.yml b/examples/grails3-multiple-datasources/grails-app/conf/application.yml index 7661307b..ea2fbfd9 100644 --- a/examples/grails3-multiple-datasources/grails-app/conf/application.yml +++ b/examples/grails3-multiple-datasources/grails-app/conf/application.yml @@ -1,86 +1,72 @@ ---- -grails: - profile: web - codegen: - defaultPackage: datasources info: - app: - name: '@info.app.name@' - version: '@info.app.version@' - grailsVersion: '@info.app.grailsVersion@' -spring: - groovy: - template: - check-template-location: false - main: - allow-circular-references: true - + app: + name: '@info.app.name@' + version: '@info.app.version@' + grailsVersion: '@info.app.grailsVersion@' --- grails: - mime: - disable: - accept: - header: - userAgents: - - Gecko - - WebKit - - Presto - - Trident - types: - all: '*/*' - atom: application/atom+xml - css: text/css - csv: text/csv - form: application/x-www-form-urlencoded - html: - - text/html - - application/xhtml+xml - js: text/javascript - json: - - application/json - - text/json - multipartForm: multipart/form-data - rss: application/rss+xml - text: text/plain - hal: - - application/hal+json - - application/hal+xml - xml: - - text/xml - - application/xml - urlmapping: - cache: - maxsize: 1000 - controllers: - defaultScope: singleton - converters: - encoding: UTF-8 - hibernate: - cache: - queries: false - views: - default: - codec: html - gsp: - encoding: UTF-8 - htmlcodec: xml - codecs: - expression: html - scriptlets: html - taglib: none - staticparts: none - + profile: web + codegen: + defaultPackage: datasources + mime: + disable: + accept: + header: + userAgents: + - Gecko + - WebKit + - Presto + - Trident + types: + all: '*/*' + atom: application/atom+xml + css: text/css + csv: text/csv + form: application/x-www-form-urlencoded + html: + - text/html + - application/xhtml+xml + js: text/javascript + json: + - application/json + - text/json + multipartForm: multipart/form-data + rss: application/rss+xml + text: text/plain + hal: + - application/hal+json + - application/hal+xml + xml: + - text/xml + - application/xml + urlmapping: + cache: + maxsize: 1000 + converters: + encoding: UTF-8 + hibernate: + cache: + queries: false + views: + default: + codec: html + gsp: + encoding: UTF-8 + htmlcodec: xml + codecs: + expression: html + scriptlets: html + taglib: none + staticparts: none --- dataSources: - dataSource: - pooled: true - jmxExport: true - driverClassName: org.h2.Driver - dbCreate: create-drop - url: jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE - secondary: - pooled: true - jmxExport: true - driverClassName: org.h2.Driver - dbCreate: create-drop - url: jdbc:h2:mem:devDb2;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + dataSource: + pooled: true + driverClassName: org.h2.Driver + dbCreate: create-drop + url: jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + secondary: + pooled: true + driverClassName: org.h2.Driver + dbCreate: create-drop + url: jdbc:h2:mem:devDb2;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE \ No newline at end of file diff --git a/examples/grails3-multiple-datasources/grails-app/conf/logback.xml b/examples/grails3-multiple-datasources/grails-app/conf/logback.xml index f4e77836..663389a0 100644 --- a/examples/grails3-multiple-datasources/grails-app/conf/logback.xml +++ b/examples/grails3-multiple-datasources/grails-app/conf/logback.xml @@ -7,7 +7,7 @@ UTF-8 - '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex' + %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex diff --git a/examples/grails3-multiple-datasources/grails-app/init/datasources/Application.groovy b/examples/grails3-multiple-datasources/grails-app/init/datasources/Application.groovy index cf53b291..f71ba166 100644 --- a/examples/grails3-multiple-datasources/grails-app/init/datasources/Application.groovy +++ b/examples/grails3-multiple-datasources/grails-app/init/datasources/Application.groovy @@ -3,10 +3,7 @@ package datasources import grails.boot.GrailsApp import grails.boot.config.GrailsAutoConfiguration import groovy.transform.CompileStatic -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration -//@EnableAutoConfiguration(exclude = DataSourceTransactionManagerAutoConfiguration) @CompileStatic class Application extends GrailsAutoConfiguration { static void main(String[] args) { diff --git a/examples/grails3-partitioned-multi-tenancy/build.gradle b/examples/grails3-partitioned-multi-tenancy/build.gradle index 31da54b6..c51abfbf 100644 --- a/examples/grails3-partitioned-multi-tenancy/build.gradle +++ b/examples/grails3-partitioned-multi-tenancy/build.gradle @@ -1,28 +1,31 @@ -group "examples" +plugins { + id 'com.bertramlabs.asset-pipeline' version '5.0.1' + id 'groovy' + id 'org.grails.grails-gsp' + id 'org.grails.grails-web' +} +version = rootProject.version +group = 'examples' dependencies { - implementation "org.springframework.boot:spring-boot-starter-logging" - implementation "org.springframework.boot:spring-boot-autoconfigure" - implementation "org.springframework.boot:spring-boot-starter-actuator" - implementation "org.springframework.boot:spring-boot-starter-tomcat" - implementation "jakarta.servlet:jakarta.servlet-api:$servletApiVersion" - implementation "org.grails:grails-core:$grailsVersion" - implementation "org.grails:grails-dependencies:$grailsVersion", { - exclude module:'grails-datastore-simple' - } - implementation "org.grails:grails-web-boot:$grailsVersion" - implementation project(":grails-plugin") - - implementation "org.hibernate:hibernate-core-jakarta:$hibernateVersion" + implementation project(':grails-plugin') + implementation 'org.grails:grails-core' + implementation 'org.grails:grails-plugin-rest' + implementation 'org.grails.plugins:gsp' - runtimeOnly "com.bertramlabs.plugins:asset-pipeline-grails:$assetPipelineVersion" - runtimeOnly "com.h2database:h2" - runtimeOnly "org.yaml:snakeyaml:$snakeYamlVersion" - runtimeOnly "org.apache.tomcat:tomcat-jdbc:$tomcatVersion" - runtimeOnly "org.grails.plugins:fields:$fieldsVersion" - runtimeOnly "org.grails.plugins:scaffolding:$scaffoldingVersion" + runtimeOnly 'com.bertramlabs.plugins:asset-pipeline-grails' + runtimeOnly 'com.h2database:h2' + runtimeOnly 'com.zaxxer:HikariCP' + runtimeOnly 'org.grails:grails-plugin-databinding' + runtimeOnly 'org.grails:grails-plugin-i18n' + runtimeOnly 'org.grails:grails-plugin-services' + runtimeOnly 'org.grails:grails-plugin-url-mappings' + runtimeOnly 'org.grails.plugins:fields' + runtimeOnly 'org.springframework.boot:spring-boot-starter-logging' + runtimeOnly 'org.springframework.boot:spring-boot-autoconfigure' + runtimeOnly 'org.springframework.boot:spring-boot-starter-tomcat' - testImplementation "org.grails:grails-gorm-testing-support:$testingSupportVersion" + testImplementation 'org.grails:grails-testing-support' } diff --git a/examples/grails3-partitioned-multi-tenancy/grails-app/conf/application.yml b/examples/grails3-partitioned-multi-tenancy/grails-app/conf/application.yml index 2f74aaee..0c80bf76 100644 --- a/examples/grails3-partitioned-multi-tenancy/grails-app/conf/application.yml +++ b/examples/grails3-partitioned-multi-tenancy/grails-app/conf/application.yml @@ -1,74 +1,63 @@ ---- -grails: - profile: web - codegen: - defaultPackage: datasources info: - app: - name: '@info.app.name@' - version: '@info.app.version@' - grailsVersion: '@info.app.grailsVersion@' -spring: - groovy: - template: - check-template-location: false - main: - allow-circular-references: true + app: + name: '@info.app.name@' + version: '@info.app.version@' + grailsVersion: '@info.app.grailsVersion@' --- grails: - mime: - disable: - accept: - header: - userAgents: - - Gecko - - WebKit - - Presto - - Trident - types: - all: '*/*' - atom: application/atom+xml - css: text/css - csv: text/csv - form: application/x-www-form-urlencoded - html: - - text/html - - application/xhtml+xml - js: text/javascript - json: - - application/json - - text/json - multipartForm: multipart/form-data - rss: application/rss+xml - text: text/plain - hal: - - application/hal+json - - application/hal+xml - xml: - - text/xml - - application/xml + profile: web + codegen: + defaultPackage: datasources + mime: + disable: + accept: + header: + userAgents: + - Gecko + - WebKit + - Presto + - Trident + types: + all: '*/*' + atom: application/atom+xml + css: text/css + csv: text/csv + form: application/x-www-form-urlencoded + html: + - text/html + - application/xhtml+xml + js: text/javascript + json: + - application/json + - text/json + multipartForm: multipart/form-data + rss: application/rss+xml + text: text/plain + hal: + - application/hal+json + - application/hal+xml + xml: + - text/xml + - application/xml urlmapping: - cache: - maxsize: 1000 - controllers: - defaultScope: singleton + cache: + maxsize: 1000 converters: - encoding: UTF-8 + encoding: UTF-8 hibernate: - cache: - queries: false + cache: + queries: false views: - default: - codec: html - gsp: - encoding: UTF-8 - htmlcodec: xml - codecs: - expression: html - scriptlets: html - taglib: none - staticparts: none - + default: + codec: html + gsp: + encoding: UTF-8 + htmlcodec: xml + codecs: + expression: html + scriptlets: html + taglib: none + staticparts: none --- grails: gorm: @@ -77,7 +66,6 @@ grails: tenantResolverClass: org.grails.datastore.mapping.multitenancy.web.SessionTenantResolver dataSource: pooled: true - jmxExport: true driverClassName: org.h2.Driver dbCreate: create-drop - url: jdbc:h2:mem:books;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + url: jdbc:h2:mem:books;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE \ No newline at end of file diff --git a/examples/grails3-partitioned-multi-tenancy/grails-app/conf/logback.xml b/examples/grails3-partitioned-multi-tenancy/grails-app/conf/logback.xml index f4e77836..663389a0 100644 --- a/examples/grails3-partitioned-multi-tenancy/grails-app/conf/logback.xml +++ b/examples/grails3-partitioned-multi-tenancy/grails-app/conf/logback.xml @@ -7,7 +7,7 @@ UTF-8 - '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex' + %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex diff --git a/examples/grails3-partitioned-multi-tenancy/grails-app/init/datasources/Application.groovy b/examples/grails3-partitioned-multi-tenancy/grails-app/init/datasources/Application.groovy index e98b7d36..00a8e180 100644 --- a/examples/grails3-partitioned-multi-tenancy/grails-app/init/datasources/Application.groovy +++ b/examples/grails3-partitioned-multi-tenancy/grails-app/init/datasources/Application.groovy @@ -2,10 +2,7 @@ package datasources import grails.boot.GrailsApp import grails.boot.config.GrailsAutoConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration -//@EnableAutoConfiguration(exclude = DataSourceTransactionManagerAutoConfiguration) class Application extends GrailsAutoConfiguration { static void main(String[] args) { GrailsApp.run(Application) diff --git a/examples/grails3-partitioned-multi-tenancy/grails-app/services/example/AnotherBookService.groovy b/examples/grails3-partitioned-multi-tenancy/grails-app/services/example/AnotherBookService.groovy index a67c0da1..1bd31156 100644 --- a/examples/grails3-partitioned-multi-tenancy/grails-app/services/example/AnotherBookService.groovy +++ b/examples/grails3-partitioned-multi-tenancy/grails-app/services/example/AnotherBookService.groovy @@ -10,8 +10,8 @@ import grails.gorm.transactions.Transactional @CurrentTenant @Transactional class AnotherBookService { - Book saveBook(String title) { - new Book(title: "The Stand").save() + Book saveBook(String title = "The Stand") { + new Book(title: title).save() } @ReadOnly diff --git a/examples/grails3-partitioned-multi-tenancy/src/test/groovy/example/PartitionedMultiTenancySpec.groovy b/examples/grails3-partitioned-multi-tenancy/src/test/groovy/example/PartitionedMultiTenancySpec.groovy index ac1aee1c..8d8f6b73 100644 --- a/examples/grails3-partitioned-multi-tenancy/src/test/groovy/example/PartitionedMultiTenancySpec.groovy +++ b/examples/grails3-partitioned-multi-tenancy/src/test/groovy/example/PartitionedMultiTenancySpec.groovy @@ -1,11 +1,9 @@ package example -import grails.gorm.transactions.Rollback import grails.test.hibernate.HibernateSpec import org.grails.datastore.mapping.config.Settings import org.grails.datastore.mapping.multitenancy.exceptions.TenantNotFoundException import org.grails.datastore.mapping.multitenancy.resolvers.SystemPropertyTenantResolver -import spock.lang.Ignore /** * Created by graemerocher on 06/04/2017. diff --git a/examples/grails3-schema-per-tenant/build.gradle b/examples/grails3-schema-per-tenant/build.gradle index 97910ad2..f238df0e 100644 --- a/examples/grails3-schema-per-tenant/build.gradle +++ b/examples/grails3-schema-per-tenant/build.gradle @@ -1,28 +1,31 @@ -group "examples" +plugins { + id 'com.bertramlabs.asset-pipeline' version '5.0.1' + id 'groovy' + id 'org.grails.grails-gsp' + id 'org.grails.grails-web' +} -dependencies { - implementation "org.springframework.boot:spring-boot-starter-logging" - implementation "org.springframework.boot:spring-boot-autoconfigure" - implementation "org.springframework.boot:spring-boot-starter-actuator" - implementation "org.springframework.boot:spring-boot-starter-tomcat" - implementation "jakarta.servlet:jakarta.servlet-api:$servletApiVersion" - implementation "org.grails:grails-core:$grailsVersion" - implementation "org.grails:grails-dependencies:$grailsVersion", { - exclude module:'grails-datastore-simple' - } - implementation "org.grails:grails-web-boot:$grailsVersion" - implementation project(":grails-plugin") +version = rootProject.version +group = 'examples' - implementation "org.hibernate:hibernate-core-jakarta:$hibernateVersion" +dependencies { - runtimeOnly "com.bertramlabs.plugins:asset-pipeline-grails:$assetPipelineVersion" - runtimeOnly "com.h2database:h2" - runtimeOnly "org.yaml:snakeyaml:$snakeYamlVersion" - runtimeOnly "org.apache.tomcat:tomcat-jdbc:$tomcatVersion" - runtimeOnly "org.grails.plugins:fields:$fieldsVersion" - runtimeOnly "org.grails.plugins:scaffolding:$scaffoldingVersion" + implementation project(':grails-plugin') + implementation 'org.grails:grails-core' + implementation 'org.grails:grails-plugin-rest' + implementation 'org.grails.plugins:gsp' - testImplementation "org.grails:grails-gorm-testing-support:$testingSupportVersion" + runtimeOnly 'com.bertramlabs.plugins:asset-pipeline-grails' + runtimeOnly 'com.h2database:h2' + runtimeOnly 'com.zaxxer:HikariCP' + runtimeOnly 'org.grails:grails-plugin-databinding' + runtimeOnly 'org.grails:grails-plugin-i18n' + runtimeOnly 'org.grails:grails-plugin-services' + runtimeOnly 'org.grails:grails-plugin-url-mappings' + runtimeOnly 'org.grails.plugins:fields' + runtimeOnly 'org.springframework.boot:spring-boot-starter-logging' + runtimeOnly 'org.springframework.boot:spring-boot-autoconfigure' + runtimeOnly 'org.springframework.boot:spring-boot-starter-tomcat' - integrationTestImplementation testFixtures("org.grails.plugins:geb:$gebPluginVersion") + testImplementation 'org.grails:grails-testing-support' } \ No newline at end of file diff --git a/examples/grails3-schema-per-tenant/grails-app/conf/application.yml b/examples/grails3-schema-per-tenant/grails-app/conf/application.yml index 7904eefd..458ac7e3 100644 --- a/examples/grails3-schema-per-tenant/grails-app/conf/application.yml +++ b/examples/grails3-schema-per-tenant/grails-app/conf/application.yml @@ -1,75 +1,63 @@ ---- -grails: - profile: web - codegen: - defaultPackage: datasources info: app: name: '@info.app.name@' version: '@info.app.version@' grailsVersion: '@info.app.grailsVersion@' -spring: - groovy: - template: - check-template-location: false - main: - allow-circular-references: true - --- grails: - mime: - disable: - accept: - header: - userAgents: - - Gecko - - WebKit - - Presto - - Trident - types: - all: '*/*' - atom: application/atom+xml - css: text/css - csv: text/csv - form: application/x-www-form-urlencoded - html: - - text/html - - application/xhtml+xml - js: text/javascript - json: - - application/json - - text/json - multipartForm: multipart/form-data - rss: application/rss+xml - text: text/plain - hal: - - application/hal+json - - application/hal+xml - xml: - - text/xml - - application/xml - urlmapping: - cache: - maxsize: 1000 - controllers: - defaultScope: singleton - converters: - encoding: UTF-8 - hibernate: - cache: - queries: false - views: - default: - codec: html - gsp: - encoding: UTF-8 - htmlcodec: xml - codecs: - expression: html - scriptlets: html - taglib: none - staticparts: none - + profile: web + codegen: + defaultPackage: datasources + mime: + disable: + accept: + header: + userAgents: + - Gecko + - WebKit + - Presto + - Trident + types: + all: '*/*' + atom: application/atom+xml + css: text/css + csv: text/csv + form: application/x-www-form-urlencoded + html: + - text/html + - application/xhtml+xml + js: text/javascript + json: + - application/json + - text/json + multipartForm: multipart/form-data + rss: application/rss+xml + text: text/plain + hal: + - application/hal+json + - application/hal+xml + xml: + - text/xml + - application/xml + urlmapping: + cache: + maxsize: 1000 + converters: + encoding: UTF-8 + hibernate: + cache: + queries: false + views: + default: + codec: html + gsp: + encoding: UTF-8 + htmlcodec: xml + codecs: + expression: html + scriptlets: html + taglib: none + staticparts: none --- grails: gorm: @@ -78,8 +66,6 @@ grails: tenantResolverClass: org.grails.datastore.mapping.multitenancy.web.SessionTenantResolver dataSource: pooled: true - jmxExport: true driverClassName: org.h2.Driver dbCreate: create-drop - url: jdbc:h2:mem:books;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE - + url: jdbc:h2:mem:books;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE \ No newline at end of file diff --git a/examples/grails3-schema-per-tenant/grails-app/conf/logback.xml b/examples/grails3-schema-per-tenant/grails-app/conf/logback.xml index f4e77836..663389a0 100644 --- a/examples/grails3-schema-per-tenant/grails-app/conf/logback.xml +++ b/examples/grails3-schema-per-tenant/grails-app/conf/logback.xml @@ -7,7 +7,7 @@ UTF-8 - '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex' + %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex diff --git a/examples/grails3-schema-per-tenant/grails-app/init/datasources/Application.groovy b/examples/grails3-schema-per-tenant/grails-app/init/datasources/Application.groovy index f9bb4396..249307f2 100644 --- a/examples/grails3-schema-per-tenant/grails-app/init/datasources/Application.groovy +++ b/examples/grails3-schema-per-tenant/grails-app/init/datasources/Application.groovy @@ -3,10 +3,7 @@ package datasources import grails.boot.GrailsApp import grails.boot.config.GrailsAutoConfiguration import groovy.transform.CompileStatic -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration -//@EnableAutoConfiguration(exclude = DataSourceTransactionManagerAutoConfiguration) @CompileStatic class Application extends GrailsAutoConfiguration { static void main(String[] args) { diff --git a/examples/grails3-schema-per-tenant/grails-app/services/example/AnotherBookService.groovy b/examples/grails3-schema-per-tenant/grails-app/services/example/AnotherBookService.groovy index a67c0da1..fe99877b 100644 --- a/examples/grails3-schema-per-tenant/grails-app/services/example/AnotherBookService.groovy +++ b/examples/grails3-schema-per-tenant/grails-app/services/example/AnotherBookService.groovy @@ -10,8 +10,8 @@ import grails.gorm.transactions.Transactional @CurrentTenant @Transactional class AnotherBookService { - Book saveBook(String title) { - new Book(title: "The Stand").save() + Book saveBook(String title = 'The Stand') { + new Book(title: title).save() } @ReadOnly diff --git a/examples/grails3-schema-per-tenant/src/test/groovy/example/SchemaPerTenantSpec.groovy b/examples/grails3-schema-per-tenant/src/test/groovy/example/SchemaPerTenantSpec.groovy index 58f2c63e..e1bdabcd 100644 --- a/examples/grails3-schema-per-tenant/src/test/groovy/example/SchemaPerTenantSpec.groovy +++ b/examples/grails3-schema-per-tenant/src/test/groovy/example/SchemaPerTenantSpec.groovy @@ -1,19 +1,16 @@ package example - -import grails.core.GrailsApplication import grails.gorm.transactions.Rollback import grails.test.hibernate.HibernateSpec import org.grails.datastore.mapping.config.Settings import org.grails.datastore.mapping.multitenancy.exceptions.TenantNotFoundException import org.grails.datastore.mapping.multitenancy.resolvers.SystemPropertyTenantResolver import org.grails.testing.GrailsUnitTest -import spock.lang.Ignore /** * Created by graemerocher on 06/04/2017. */ -class SchemaPerTenantSpec extends HibernateSpec implements GrailsUnitTest{ +class SchemaPerTenantSpec extends HibernateSpec implements GrailsUnitTest { BookService bookDataService = hibernateDatastore.getService(BookService) diff --git a/examples/issue450/build.gradle b/examples/issue450/build.gradle new file mode 100644 index 00000000..ac09853f --- /dev/null +++ b/examples/issue450/build.gradle @@ -0,0 +1,36 @@ +plugins { + id 'com.bertramlabs.asset-pipeline' version '5.0.1' + id 'groovy' + id 'org.grails.grails-gsp' + id 'org.grails.grails-web' +} + +version = rootProject.version +group = 'multitenantcomposite' + +dependencies { + + implementation project(':grails-plugin') + implementation 'org.grails:grails-core' + implementation 'org.grails:grails-plugin-rest' + implementation 'org.grails.plugins:gsp' + + runtimeOnly 'com.bertramlabs.plugins:asset-pipeline-grails' + runtimeOnly 'com.h2database:h2' + runtimeOnly 'com.zaxxer:HikariCP' + runtimeOnly 'org.grails:grails-plugin-databinding' + runtimeOnly 'org.grails:grails-plugin-i18n' + runtimeOnly 'org.grails:grails-plugin-services' + runtimeOnly 'org.grails:grails-plugin-url-mappings' + runtimeOnly 'org.grails.plugins:fields' + runtimeOnly 'org.springframework.boot:spring-boot-autoconfigure' + runtimeOnly 'org.springframework.boot:spring-boot-starter-logging' + runtimeOnly 'org.springframework.boot:spring-boot-starter-tomcat' + + integrationTestImplementation platform("io.micronaut.platform:micronaut-platform:$micronautPlatformVersion") + integrationTestImplementation 'io.micronaut:micronaut-http-client' + integrationTestImplementation 'org.grails:grails-testing-support' + integrationTestImplementation 'org.spockframework:spock-core' + + integrationTestRuntimeOnly 'io.micronaut.serde:micronaut-serde-jackson' +} diff --git a/examples/issue450/grails-app/assets/images/advancedgrails.svg b/examples/issue450/grails-app/assets/images/advancedgrails.svg new file mode 100644 index 00000000..8b63ec8b --- /dev/null +++ b/examples/issue450/grails-app/assets/images/advancedgrails.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/examples/issue450/grails-app/assets/images/apple-touch-icon-retina.png b/examples/issue450/grails-app/assets/images/apple-touch-icon-retina.png new file mode 100644 index 0000000000000000000000000000000000000000..d5bc4c0da0bf34d32c86eee86f14845f1e5bf412 GIT binary patch literal 7038 zcmV-^8-e7BP)Py5I7vi7RCodHT??FD)4Bh?edZBlroEM*RKi6)f?Kb8q)LcnCT_hiqMrTTqHe32 znM_IBd#Arvq{$@JYJ*ZjdP!V@y3I@?1QkUil%j){#G{B_%!It=>~;U&x6e7V&zb!^ zXP=WZk@cH1d+oKp^{wx*-s@XyGYw*Ay4pWOX3d^NHh#P(88d>I8^DtDGkCHQKat^H zV(Nv@`5A+EUWJHmBCDHtXg4>t|DVDR!hbwiXf_LfUQhJ;VPx6IKw(EgF{2EbVM)@@ z#P31z&qKivlWjdnwDaRU;nMX<=20$ORjo*~sAVrQbP9fBpuEwBXsdDw!V52ivldEy z3^n;d{1))|nSb$utA?0niGK|*qnNZxh#+XB@K}cB|nE8)T?5$L9=JA-B?`6ZQ z;aN3`MD1%Pn=VG5)ocvaR)cW-Xkey|=#x9iqU%sgJN@7)8NNvsi5kbWs0~eLdJ2?x zU?mw(DlaH?1#xOBji0^%H5RS^rg57@k;dGSqOK<=lCh_N^Sw6>>&fN{LB2?AO{JzU z-p+h_I$7-D*eb3_vv~4oO53wh8}|<k=L}V_7Asub3ir5VMx1V|af) zn$e2oTS;MN^FnirnGw$+s{1ZaxN3DayqwR92-I}8TnN^tfxCYyCo|QC8}rilG0MGy znr8f}-2G=+73r+J^v*#u`ju)EawX}367M5!IKP}LM`cr_>1;h5gPD04%#5g{(5f{r zDD!npSZGUlJO;q&^+H>M=N`8 zMPGPEGURQZSkQOQr;!ck@~HL=NpYB%BF)nFp|q~|Zj`pMNfEa><3UrqgoZSn%wyWu zB*AAQiZlza7*4#Q6G}WN38FqJIH1g@$ka6_F-0j^k>-Jxk+h!fM{~NzCq)5C@`+~l zWvZv+_}G~%OTk-`BDE&chGkonSc(cTmK9}E-RM$kX3>f?kG3^XZ+dCm<`m7zt6=+Y z7{SR7!Y=Ud0vto|+$9fZwM8L|@smzD4yzuhW-xJx^qJVzAWKPD2{6 z%!Gqq8t0W>!c>85{TMV~<5q@Q)VdAmZ3To(JGSvluQ+4?G@{}7Vn(wyL5RKLKnzo7 zt4uK>DG|-a25>o%n`bWY!UqLox=BxA)H{*a_UwrlC`p{1hCuopf9}7$OO2M_Z_UD% z4$#q(i*sg32mHtB6E9|K`jJ} z+Ri}HT|i{{g~juq6^dLzV_QDQ#X5aKTo@@c@YC)+IJ3A1ieh~|bi?&|qE zYD!~%3Vpi)N<=f4al?ns<<&h?0sEP3P;yUxFk4W~(#bo}x^z1h&0z>;o*(u8;Zpo$ z4%*}|+kx3Xqi<=+|MBb`yxYTcEe1aw7?fNI_!D{jtpAm0^s5-{^1*IU^`{v9A>u_4 zY|bKk&AIZ9bXd4HcZ)w+XO9OhTEMw!#$D7(|^X#O=a!KJko$PTJ7qZ ze4#XjvB{~SeoHgMSTM+|Sg&yNtXXcHFu?jU8pTW8VdCeeSwAWa)Ax02t^~u|x#J+b zXJ8QZh=!RpXQM9HV?6bBykD(h7M^MPH+Py+{m5e!uj{!J$~+%~qYwDVY3~E)ujFSs z+xBEi-wFBc8#f=3IYahHcYRpe&>E?eeq*i*L*yB6*YpoUnBMe+i4bTOx9zAF1{qkh zu@zVxVE_RLFjet3ZMB(rc*pZ=Cy zv8_99fIr>09HzoAp~Qb$`#@9^TGa$kM|kRHh$@k*15&GzEz-lW7h5y-Og%N1ejPck;ODPK{5!c9Kbx3=;Aj z8~X?Z+^d0h{dLUizlXH>>!8RaFHz2-6L7g;#>OVZgN=nGMgIu9mj!5WNtGMy=>VN_EhQ>c#_)C(*KUf)$uMJ>W|Uf{flm30?w=>{o#Z{fYT3o;xAz zU@$tujU2Guh?il!xPwqnY8@|>pgSn$9p{}JQCs&y)AnRG9?wIaTIvePc#-xE;Kfhf zFu?6zfNwttOV>mTU^7Wv4?~}l!XY8jN8%p}7QKKcM(F%G6ilH*2RyD+C3)FOou+fu z0xaH&>}**9W)3KT-SS!m5%~KD2yJUNGxK1 zqY!Azd$mcxmMyV7m)ErF>d89umb*X7gWmE|6%x>?))3y&eb_d?My{oaTa;K8x(Xyh zbp&0xfePl4Mt2F^0LA)IYA`D0S1}@$$+y_&$ON$u@7P0x9}^sm9x>>{OwhouTUCsA zgkd()|Bh5Ti^Nk-IrTfYzdtnJm)GJTDlAPte0DJeLsBGl;$(4x#gC`Kwz$S=yi|(J zk1^H2_LU*Xmqo@kfWY#d*)^ZMQr=Wo*JLp`#No}{-?q2e*!My7T+Mpws`Vz)W*6*xp3)B6{(jI86)eE$vH!xPY&5;fXOz=%L6)d zE|SVNt-R`Ri~_bwQe42deUuH+B=f9lT*j2X0a3u9>I}M*V5eziGo}D0nb*|%Lz6+Y z8DL>9k1R5}a}+6eyOqPG`RH<>9h5i?l}8pC-DMQX{U#YnN~v5mJxLlXfD4=9yJDa- zvMgEQaz(*wiOC=`8CH%=FttAr`*FLK1NWf9Y13)tqE~b$I>hRO+;>_=$LX`nh<$LN zHugDXB@{YD(<+zNmeCo$K#vQQF^g(OEw`IsbeZ^wHJ@i&6p5RM%jmRkc<<%&%a}!# zqqf`2Pay9p3%ZdyVXQA<(VOK0+6`El#rX+c1joWR7QCS?gyV@`F?U0vEQr%i_&)}x zuf7j^ow=~WURQpmswW4r4>D;pPAJ0KI%H?wa;TF9cP!aqL0Y4nX`l=|@iQr`&!+%e z2dN$lAx5$F8Ce711_ycQ|F?Oy!s1B1zF`j9k5$s>qHNxr zGy9K&W01kZIC?x zgD$^_i(@ds>rps_{VT0OL92+K%thJAn(Pc!(U(rrHB6J>@luVpm)G$NW zV7I(%s~vgyR_H~K(XH#b8C{4D2$fg4Rip^#$T-H;s4$a}rZsm=IxJ6g-VL;RO;M{9 za=Qi2UY!62-v%BTWZj10xgPKd%eUI&Q3G+we(w!XqOM{IkIJj?T#SQ^foo{wq|l)l z4@Fj_B9;r~ADdOw-lcQ&n~p`3FM(n(xI*$Yza9K-a`cgm!oYeelNgIBDFln2WxE3Mt3WUPGy?r8M7$`BG6$kQ?u& z@M{y;_4r+=k=jZLPgGlbWY!?6D4ITea|ZD395XFyWQ?XYhn(p@&@3L8dynbi2{Zmm zmUSpf?*r1zqHxSMU6Xzqcb*zE+eE?jMA+)yhfo-uGne2+c^W(IZ^dThoZNDzEwOME z)wCi95HAaIbIb$Z3tx5cp$L8EyIAcQ2W9>keTofh*UM;a@1r=GyN6GbstMMCnoV*g z+95)_@Swa1PenSFCEGWvY~$iU_lRq&Tdo2@m&Z*j@Mub&CsRMBlv@o24`?_9kLy9l z0a!Ko7{~6~Y4{#j>&apPs1hfaQcybmQ_y+3kOT!jjmJf>-~D4LB&ux_Z$Rz#zAG;b zH|UrqePnkSpkF*n&nV*cOHJ35&-%ec30FZh9eM~Z0lW-#{UaEoPp8%0Ph)5Om?-JX z4I`FnC~V)MzVu&2i73FUab*76Bs7<*M3J4yut+63yqj*hNd9mL?y*vYp`Cn9?FqPT z>bX+zNLuGMMY(Io#%+o;9HkAknX7S2vXC_QXqRAN4M_a6Fu4x3>2ez1a829=Vz0b)7~b>d zY3PvU(!e9puPEaOroX9<}NKDB>pqxA>)FQ9R^$PVM;gL zQDo;N8AcyfGFBG?AYO(o(QBx#@vdT9jP{nrSwPVHSktr=t;8-t z*#P$uls3N(f(eT=OQviO7}P{u>>J>e7NIF~S8M=iE=qYeat{z>P%f9zouf$ky0?57 zx=aowy$-c!fmt%?^I=(l`Gat|(0?OMhPL1ofI9;h7aboA6%e~|{ZAlh-F6Cq(kP`# zXcHPkoZJejJSTku%ayOAvz;H8AQhIj{W{EdK_%=(i5?2Gt*N1C(=;8h^`UTX0Nci* zMtda1yOWu&?~cOAG1hL5(gqsccs%{44_A=J*un5b5)ufVW&7=Z=H(OZ<##8cPs_Vs z)t#j*&!N;G;|`1BN3*yeb$&E93Tz&Xz46;oYrZ?3&}h0Y-`ySN_Gg;w@^8_(bmms% z0yi<`@I&A*?F%@EX`3$^_E^QM#ua0L5LcY;(*cz0Uy2hnP%ILm;-JObbOR(Ev1BDW%Z zdR?wkDWhY1wLf+ai1SY+F)|3j=o1AsqDym$OJf1FrU+}Y<#Vbd=*pc#o+6!_l(^Fk zPzJw0lcsn9WcCHoHAQr+M;;;qdw|*2J079gAb%dS5G6gi(|zlepvqIE;h zjFPmTw*CBHeL;o{ebF~AECO~bVIX<&K}};G;J_E~0V5UmrxiRZuLF}N6XKzx0g7~N zP<6vJnM~bS3_57JhT6wrP4)u}xQe^9$Z-)|G{TiM+FQZ#W(k0mcIh^D2)}|Df9Xc<^pahQn!$#H$-=M8A)2 zycIcq`T~p^{F{!5dAh;0gC4?j-C>^oRO)7Jx;!HT16Kyd7DgU><+~`|>k5Oz-jz11 z_U14+04neBAfKL3td=;r@8&lRLMK*JJ`2ojL)U;>v z0s~&SY5GzSE*5H$oHk*#))SF{-9T3AQTJ<@Uuc5m!$V2JT z;qX=^H;V;%5VcLPCn-y#Ob4 zpt>jscE1KHQUSN3HZc3XJ5d}zO7UAXzTjKQQ4ru8S15@+Q9!Y-b7wTTe5lN%`l0+# z?>F%Ap_qGk#|DIgca=c^NKlj``?~K2!AM-FB9&R@E5Z=@#vAi#y8om7edGNic%>L8 zg&r2dNbX~tx46v@O=!4S$3=2xYaBj~Z!jgiiwuf&kbM*baL6It=^AENE9@_0oY`}s zMTz_frjq|fcHOOEL}jeGzlK`&eci-$;qmfUVen`n z)}zDuF}}fdQ||LkK8bYSRjlz0{Sb27#s@4g7;?LhnmSIzGj!=Ok|HHHH5Igbo(9Gd zfw>1nYNu^jhVB^l<7+sU^`U|r{j4FrTx0eH{(lZYkr$Q0x38Y?%8QBj0&~#+#Afji z-tc)@?nUA1^ z)f8sDDc+&Tg)jv?zSO+cqI|7ci9)n>sko9zj~WPzdj4`8+u+bX(oL9yn5VeIHmyI# zoGyUS%s60907!+ScuE{43URR%soXTp=s<~yKYB}86rds2oGvu`ly$W>)uDjp1;8kE zK0(HDbBV1;?RZBtoQLOmn6=(xKySi8_rc3$4nesk%FJ) zGq*z;PDTg2JZ650fTQNDCVS`ki6C<$Nm2Ag2zR;!{7~LgDpU6-gk#_>N|AyedxzM_ zt)GCVKo+?x>)85L)}AxbjK_5>fk1NxO^M zTIDfE(IyQC4Flwrc$CUcWH{lfqV1K0sFD;Z2>95UD~akxSG1W;r(1N9LI_CE={bIz z&rA?yAS!K6MRqE~K+w?0So6prY&beL6eq9lhJeQg!6g-kl6((s)o_8Bha`bBbC1D% z(7$UE_`+~NiA%Ik)GjIvIy^oxMM_q>+_kFr76_vvr5eJ(H=Fs_RD<1kXG1!iV;6*8 zcC`4(qj1>fe5}Zvfu)%`S9ICDD1`G#MyEv%Y`h3dRFRT_HhEo>Tj84uf5ldSz{opT zGFy(NFEc(g{{wLh2 z3`7>q9e*g7D9TnNvjjU$ZL3^yf-{UI9Gb}@97Ip_h41T4CruqUB;Ax2ilOX^%x389 zFyMeFoAXQA-pS&t=4WPmZ4S1@+X_wl%SCs^6q#iJhgnBc+MbP?8sBOli=>L5sCmzl z7)pt~Qw3ec6)C(qcbq;s5hKVc7!2)QL4M1Y6$JUB+5)Q?slT;bxd_-Kip(;j{pX^# zQ*imi6g0O7X2S>56V2!f;?z_U>->bL6mjIRNfqfP6gOowq{HzV|RadLGJqnDj;|TqHH&QkXzizZ*1)^b)0Wo1=Wl zRK9_U&nFs#Px=zez+vRA>d=TWgF})fHar+;0*7Ms%?-`0~9EOJm$_ltKZt^p2NB4 zoO|xM!$ZSP?(DtST6;bAUVH7w*~4@OlDs?nJUW`biEO!whzF6#<-~j*L{$<=7r>69 z+zR+Xz;^=HLKc5aT}t2PQS%O-Zk70SF_gyH1Buf2Lg5J{JWNQeVtF&MO)MXw6J94O zeVME0{i|3WQS_&5q%_T}BFW4G^U)%7%KTgX?o-WvQI35J5F+$oUI)0{3==H4|3Jq{}dy$ zC?jRv%#oC_U&HA2E=I`dq{XQ2rIa<1Z(Zju%CuNR0)C4VvYPS);jIHKEO@HvU0vF1AzkzKd&OPYq36H zQ-B)NKd_{(#1g5VvLEG_(Cz{7)6IiP4h=0Qwy;r)ak7VFV@E%4b zB}+Z}LItMneDk9Hl#!=|>@{L%8ZnAx;by$d(L0O@87b>#^)<^x6eEi_XIYqDV~a2< z)_7)XqQ|5cjoSr)OGnZMc7%gK>+j!Ur(T_lt(h0@>GQSm2L z$c4esPQV%Ck$3geX&F4!bxi?(9Xg?M0NgePZAf&@6uQv^Bq@mf5Xj6C&5WdT;BET? zg1wPV!spve15d#alwG0+Z)YUAZL+{|3Mma2Tmh=BplgR)WiT9QWnlo3W9TsVoUzri zN!hx`*zAO|H9yV=hY&+x1EyuHPl(D;SyJGyfwBbZ-v$vs_Si-gbimkZn+((wj*&ul zM9_sn%*{Bsz7j^9T?nFQe;6HWq|wN+>j}$TI^j+xk(z~~S|TQ=fk-dLk|e!r!ljy} zw`%aWob@tqd*N&I;?5>QD3KnBFqtdp#NjCk$VHnm*BJ;>a-b6_3Vncj&cf{iJs8XJHM(#gz7CCU2}knAhH(X zT=$ey&i#vH*89|WSP!TRb1`S3)|y@ftv|+bZ)B7-X{>t~`{TI*-1!!UEn<$@g9C)V zD%U~Klk(<6Lw&$AUc~|*t&%iS1Z$rh zCD^BdXJ-^nVF<@nw6En2SzFgNAC1||mUX%w#6zj6wn-Xh-r7-PRbfQ$duUhd2jJI( zN>W$F?UjvQs-NnaFTpBeC1x zLWg`9yg%{Nk==d+wuyIQB(KEjYb8cyc@93Jf^A|a7`P5?^a8CfI_Rf{PXw)k%so^? z?IzJFQoumN0$;G0e@QJrbnN zsUL4$0lFW#FqD62_<)}ab8{^AcN_U6i8koQF8xHW3@}1lVZd;gS)m**un$**{mjjo zw*O#pXF9Zd4-D)PY>RX6*Tj_e0V1-%xW*!6v}$ zYxy_yOuRNj&uzHvzgm|NB>hI8JQ+a;KflatacK9IQ3fg=Bh!_n^(P)}PJ!>vsro90 zANYD8$ScM`r=4S0)<`@;Fn1yz%ff|>mG+2AOK}|%lY}nHgUttb$WuNUetCJA`N57o zr7tVY7@2)#vC&TySB5KPsAs1=9!*K-FXKCjPk6LB1<3=4Z)vBORqXc2AN64q3B8FcoS0vXAc}&yWO@b+C+1yYpa%)_ien;rq29c6x{FuzYq>`jr@rIVPn)~S-%p8(&i|7Lp84JN01lZJA;qkRb7sT59GV`s9g0|JN&pDBMm~| z$>=kZyvZId_z)9B%leutu%W&&2*+gqLm1{j%oPfgiGVpY;A8mZ0I?i3%)AsQ|Iq=k zS2cP=e-Dr?VD!Vue6iJMBvBt?-GxaspzEkeEL5@`b{xx}p(n`0Ey#O2Q>YG?6p64B)$Qa$q- zs7>-3Y519kKzJw9vDA?C4IGbK^bXuD`m1vR*)UMw@yv`Pi#5_*H<*V&9T-vJk>;gG zob)UXr<&VP9GHMKhs1bVfrm=kAn%;a;f9LNQJfWj8yAd%ZA>s=`3oEd7g78fuBrD> zdD(GlOIMI(_cON%BID6+EaqflT?}7~afpwegJC^_keIrOy65sMNwwXCXD%Oxo{Zlx z#B|iDpp0CVU&J#|qjyJznro1A_2qJ~Q_kr(=pkdz<^LuK8mo14qx~)VzmWd}oqpOb TOgI^o00000NkvXXu0mjfLl)`& literal 0 HcmV?d00001 diff --git a/examples/issue450/grails-app/assets/images/documentation.svg b/examples/issue450/grails-app/assets/images/documentation.svg new file mode 100644 index 00000000..29bc9d57 --- /dev/null +++ b/examples/issue450/grails-app/assets/images/documentation.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/examples/issue450/grails-app/assets/images/favicon.ico b/examples/issue450/grails-app/assets/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..76e4b11feda94c87ef50f4350eceeb1506e31311 GIT binary patch literal 5558 zcmds5TWB0r7@oAD-jLoD5kymO6!D=TzFEzK#d}{Q^hKedq)oD$o!QjZ7iB>ZDuT5Z zQK*!*+QLGbCUe=S#R?S^iw{!UT3ecmm$p`8F14H8xg5XmOinU8o!!i8KrnDJXU{qR z_xt7+PjCE@Rlt(vwLV=FO+C0aWg*UiUYXHhK9 zn3Z+9<$b8z{)wCwjIy!MG0t(#d~iiDT6zyu?$2577Q^;F&)edh;RIub9e91iLa?!~ z+updG!y2xo3@}K`6Fsg z9&0H>Sz~U8^;XC|F);Ud`BFoqbL@^Hv6TmA~KYha=;`uIpVVF zcXrTw3i|(+gdz2yErx~LYx+G(Mm=+;`;cx27wXA!oY>@0GSR0#P;P@y9ZlMIjSW7= znOfd(G|r^mO!u`pWu*?@QF2AODKGteSOoH%WA=;kb04}DT-abv-J>w*f7EF%eu)hZ zaz)R9v8p@X$lUUQU_3yd%RPhc!-~JNU)*X~{_%RYkLphja!uz43Bi0Zja%byQ90G; zI-KJb!*(B6{MmkS8^+G2;U^9;$VvIm;^<=Lk~DjlvOGg4_`Q3ex=Qi)9GF-R-~S>l z2G^03+!@nb2i?!5(VcorS z`5OJsTJCDt>N9EAkgvwK$(gf!+~3LkJZI>4jCrjQPop_~f4L1gaX{+8`y*mo#ZMd1 z7E$|$XC3^QG2Qk?_-W=gGOs$`;hNY`_Q%;@<$rQcRI+BZHw}K8HJO2Od)^AphyD=8 zzw*C${B4r?JvfprPTrgbJIxy0jSF?t+kv|a`5pW+{=@jI%|D1UNBF2^UP*)7%o@XT zA7vcT7oz!x7%Klte|P-J+~CxjX0X$&eQ{9Sg1mW}9MnPOU&{7cynp-=+Mef4- zLUDXq&+dukT_24tUBBzkCA@EGt8@+bA!H~^90QH_CH(Nqmp-}r{^=+d7pnUgdBdYF zsDZvm?8uh4{geNsDS2Y$9si7Bi@)dt@%xuHn5jQt1KNmZG@ngk(zj9L#On|8^H0`a zPJ{X@l0TxkBu$_EIRCo!7x`r!RjOZqrr}9iBmJHAC$OV+4>{w1jMe1&JNYh6;+q+p zjy2Zu#3Xi{FX=bm6|Q>!Xwvp7&YFGyi0YH{{-toE_XYcmY3XCr@%?Y!zoI&zU-SNn z95#aceH1n>^A2N^eT>Pud3^tj>494DKY9O-tMR}3&F>`mbRxfyH2a;TdvNM*)KBjq zUysXX`#U%`mw{Nn>Lvd@`0XX+`|nU&z6<&7x+A_u{d$RWNi{BUyN9X|!naRu{!|Fw zB{{pgLGksPQ%^DmHXlN*g*9BO=J+Hgz7L$D9CHNkyS(Ea>L?yx#CgP23aUl?og~B!g%=2 zVLI1vo-&k$ZrhX`pUkP8$M_!B#xi%C?i(0$!+p)>OUfcnFDiLCZ;NC3A@A8Iwx7O+ z`tgtXv2V~_gEKf1#V~9i-+O#1PhRZvn~kV#rBK2@zK`i8F|07iO&vmei7VNm@*@di zHA9_dWhL~G-y$e0?PmuQ$fsY`ENt)t?>7C|^JU6%8TPOf_W1?xu5X9U-$v|sQ*-do zQm-|ufmy$AH<51n|(Hp aAB7=xL9F5|-{N;0+0aiDzfp{|{l5XfK*m4- literal 0 HcmV?d00001 diff --git a/examples/issue450/grails-app/assets/images/grails-cupsonly-logo-white.svg b/examples/issue450/grails-app/assets/images/grails-cupsonly-logo-white.svg new file mode 100644 index 00000000..d3fe882c --- /dev/null +++ b/examples/issue450/grails-app/assets/images/grails-cupsonly-logo-white.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/issue450/grails-app/assets/images/grails.svg b/examples/issue450/grails-app/assets/images/grails.svg new file mode 100644 index 00000000..79f698b6 --- /dev/null +++ b/examples/issue450/grails-app/assets/images/grails.svg @@ -0,0 +1,13 @@ + + + + grails + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/examples/issue450/grails-app/assets/images/skin/database_add.png b/examples/issue450/grails-app/assets/images/skin/database_add.png new file mode 100644 index 0000000000000000000000000000000000000000..802bd6cde02d442288490c5f278b225e192927b5 GIT binary patch literal 658 zcmV;D0&V??P)$< zN?sD2BV4j9Yl`*+fsWQD?H_4>L?~r48B=l;Spkuc)A?yA6iP)R5d;DO`2BwH_g=3D z!!XcjG|+Ch%jCP5&1Rc|$N`LEvA9yN*EyX%X^loByIQT_h>#+l_9wi@{(Z?D2RE zUDq)j4#hY2{Z&BrR!Yn$4g z^!3C0RHpz#W@n--_jThHPEAe2R834DnuV#1kUlxXv}=C^n7~&>f1RO6h@8fUuT`wRE91*{Z%LW-oO8KckjTdf s77ewwyuEar+*b)ffn+a07*qoM6N<$f>LlT?EnA( literal 0 HcmV?d00001 diff --git a/examples/issue450/grails-app/assets/images/skin/database_delete.png b/examples/issue450/grails-app/assets/images/skin/database_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..cce652e845cde732ac3ce9a4132b597301ad660e GIT binary patch literal 659 zcmV;E0&M+>P)ps_J1 zHhTmGl@tMUh=_=a3Fb%)BqZVTW3s!xH?UsxE?7A5@n+t<@6Gq#%m~l(@IOPFT;%il z03|#}Sa4nU2-$-Kn!4}FekQv@$S0FY$L9!N0g;c={9!m8K4zLG48uSu6aw$J+ii5a zT~sO+G#ZW9!I0fqFSvY9*@h|sR=YqL#x%oU@(yD@pz0* zr-R{eDEHX6V*RaK<|4rWl@GKt@8COeKZT>%ICBq4+h_I-+?Y*V28oxmqBc+Oz5 zc>4@kzJgDe<1lkKXYEtktsNC`FoTI)4qLaF!_1E&4qv>EKx`iUcee83)!MzalltZ# z3K)~8`*H_w9^uf5^9X)<39-6>(AOuJvu0IKc#FRkFoCa%ULtC>8v6bIR-H|{S~CWm zy|LB2yL+L!Vs5g8ONBz=aUzj0EX$JbfSV}a#vT*B_2)32Uc<0oLyzLS9V$=7hM4?~ znM@`|iEa~8)bZW?7q}d~l*7K(I`+@}grT|_=WaH**u tj~DMRZZpzVy^OjT85Vq(G=8XD@S91FH}kp`d7hyPh1 z5CCa%X>*RH50W(1GMNlqE*ChCgTvu4bCM)M5Co)BDTG2HvvyYjmSvI4<)A2v`CcxU zQ79BpEEdf*P56K_c;lUWy=bic9s#4IZm`yWps?HR<^;5uf_%3rLf1Uy7}ym7{u3SG zgQxL#;V@<+y-&7GK#MIB!!Xb^&5SuEhPoOV9{wDJpWonQN~qlHho`!h-y&cUDCjiw zot3{JSR;b3Z$U9V2&bDtVsaL$Qu?FFtIb;kXm<)qqyl<=9Kq@&_)r^^)N|OJWjH)_ za7i;6X_aj`duRB^h5&`toyKA^3V-E1_yb`=eg>PPj8Y+p^vFkpk)_tg?(s>=HO~R< zNVkfdL~{$}rBQI|9QHR{MrpYhcBg@2p$?h%pAnUsbBDUeKUv#oTc6-&EEZdf$Kwze zM&QwZLDd6D&pd?=1#1F{N2f6?Hf8gwvt`>|pf)ft5F|nm_AI~XywcT&?}K--v^WN? z_7vo-s3)9F{TXfF{hpqll^q2vdwBb}datvKg-yfc+gC^|#8-K5)%lB$rlxi}-rEGO xUZ|2A>wRp~(I5;*aZFyx-fDe3J-^%i_y?+(!m^mX(n$aS002ovPDHLkV1gwlT*CkW literal 0 HcmV?d00001 diff --git a/examples/issue450/grails-app/assets/images/skin/database_save.png b/examples/issue450/grails-app/assets/images/skin/database_save.png new file mode 100644 index 0000000000000000000000000000000000000000..44c06dddf19fbda14efe428b9b1793c13f46b2cf GIT binary patch literal 755 zcmV3^_07cLZBR}_>&jXObH zw2it@svr%qE?kJ(Xuudu+DSW|WWK!jNvbU^UO02#+Tt zYOko4%Vx8c4Gh!M(=Qem7g;XcE?n0Qi^XD?&*vX7@xPFCIh;%;@xMr?(;$(vo9j9i z6;riZMJyIWG#Z6r7^-I5HtO{{DwPWQ`}>&y+Y;!yjz*&a$8prX=XtO!3$0d5J>%Mz z1f8>Jnx-7^X2#7Yb#zC2VYfZ>c17@L{s)8{OuWBa3WHFfVXfhLv2t?V0V~q5R2D*D z&315l_#iF}b>Zoo?-;+7*`WOJWsMw(x3WXv`@U*s@Y-&edFEYpz0skP)dFfu zZ4wIp&Vbb!+|0+3Qa}p<*AH-eY>3q8s6?RA)zqP8W39IT5HLFG9m1F);gE|P`L7@@ zctjKsn1rA6!ZZR%R^(SjU!r=2o$yGp<$KViK~{B;AIcgvN+J+&Nvur+W(Sw&=H?z} zGMRW^U!Nl3AvWzQ3~C%Z*G*(?qLfNCq;tpg2yRW4@yl9;p3CK)O-@c8Sy))OUMiKc zQp#QYFZe-*@LZDInR^#F=Bm=!vA2i6tkEJ#i0aggzp2D%3!>h~r~3uLt(-IMoyFAT&uF!>{(iS?1OX-eX zKw9bunxR5FrF6QaYs~9>A4#zW^dwIvCpq(+cfR?U`T6-{9LHUqo16RKcDwUVr?cX4 zIN~hJDs48~aRAJ}U_2g=KAB9SP$;0;Y@*$6Ly{z<(`i^NmbL#1W@l#$wOS3;YPBOE zJ;7`?L*?Ga6XzC292wl75}>gDz`(>h?is$JPxm#0jGnotoK|nAVM5$DQ z!C*kO-aeF@+Ejy?nVHEp8V&F~k7BWicsx!aH9kHLRpcQ?L&JFBAB4i&kAaVUxVvzh z3a-EY0%m%8nhI7|SE(QpiBL#sG#VUMM9}*(0mg2(Q$Zq;z|PJNd_Euiem@;jtJN@i za|c2MmsL?PR;yKNwOUA}QuO=7;V@#c7!{~gs?J7hAlsE7U#g?$aRkhSTqLq6iuCu9 z10_j_=;?Dc?4cZ386qH0HkgHTDT|HmGR`W4V2noNQJqfLJEot)q{V_UtsW+m31cP~ zDwWEi3HYBSoF4M;T?VaIdqinn1HZ9}32qs-PdwPbCf+WI6n9jl0-8cjV3%1FB%B&r z+`mzSliyLSH0dxYE}rk&=!uCa*V>()2znj`_XYjtbt>@4FLHnJE|G`xv)Ba@oLBny z1%3K7c4fiB^4{k6E8Pif0kNy62}b@9+N#0$9Ug7g~-`rQ^qx~m@y2OU8A z#zh~=7n#Z$Z*fx-GOtDf07cgx0suCz_W(2~Y(0tf@FX@P6EPuM_dgn$vj9LucO)%W zw%HgMW>=#oL>nZ>M&NEf08>)#)k<{$fCT_r>rPi=BV=hFh6WS^qqze>C6Ek}o{M5% za|@JGowu0t{&hgNzySHZxy@LTNh);YzZ2zSp_ zl$^T&Dnc|NLb&RD_!4>pt@VHdP)ZGER%5ZmWEe$lryR&y;2u^3cOkO4#6c%-(EY6a{600000NkvXXu0mjfxS2AI literal 0 HcmV?d00001 diff --git a/examples/issue450/grails-app/assets/images/skin/house.png b/examples/issue450/grails-app/assets/images/skin/house.png new file mode 100644 index 0000000000000000000000000000000000000000..fed62219f57cdfb854782dbadf5123c44d056bd4 GIT binary patch literal 806 zcmV+>1KIqEP)v;U&v3%|^C`Ga3?LtY&4dQB4Oz;1v;J%z!D&%WRH@BZ?x; z3)8@IUIv@hG|@IwyHLC`l{1<4BK>wam95g|i|?Cfzt876&-Zx_0f5*l-9`IJI&mHu zE6$@xB)6N}7VeR;!X8D!TAw;;&0Bsj?A071cO>X3K0wl7WZ1;Tg!4LHyNcnzoeQ7t zNW`aSlm8WXYkek&ir$13=ngczvf zV0vnjNpCF&K8px}dunv+`LIb-sOC$_jD(;IBI$xC|7`(+9cA>Vir_V#z{?k7SX^Ah z^71m~W@q439Ycqfhi7+gp#A14n1n1!e>$EdeATG|f798Y=ggzwEKH2Q!qU2QA(Se?dwqG69%>n$6rtE z%F(845Az8c{w(XgimJg96!jLMz?zS6I1HUm2baqQx7&@nx;lhHA!r6vs2|fqJETOu zLxeu2OQ(3(au%dg>AcZsWI(zXn9XJg1cLe8k~0h0wOL=&HK}7X k{AKr*U4z7Szv)i%9gTgghwgU$Q~&?~07*qoM6N<$g31kYk^lez literal 0 HcmV?d00001 diff --git a/examples/issue450/grails-app/assets/images/skin/information.png b/examples/issue450/grails-app/assets/images/skin/information.png new file mode 100644 index 0000000000000000000000000000000000000000..12cd1aef900803abba99b26920337ec01ad5c267 GIT binary patch literal 778 zcmV+l1NHogP)BVme|mWaqy4$_pJm?y9KM{-*hp?1+Ey3e-CEDooTa!B;e(Q>TSF?bj>5At13y1p zriN3w3x~5SfZj{@J4M{kp{?=M_Lh2bV+5LH)Q)5W!-ePA$RgE1@5f1cyHki0Y}JyVEYZF(LD$xXlt$7A5CgE@ zpV-&l%vf;=5kZ2-2gi@Y6J&=cuwt>!vJ^#(&n|LcZyUzi6Duj$$hJ1s*HD-#;k-w@ zpdrwAuoDG_N2bvb07G$Zk*?Hc)JLtW4yqOnic_$zO7NZ#l>Fm){;fE?b$IbOaX2fe z0la4g0Dfw2xk7Wi7NapVD8YMPCZu?A1QCK*67dgsvRKBLFtrM>?$%&_lD1882mzdO zWPdw5KWw6IT`m1b_8=lS5jt8D3=RDa=&jWzR-)S@56WMslZ~mKu1)-wpXB>rNBQ>N zU#K`#1B&v|_AQK;7I~B}OdGiUT9LX>f0xm6<;LeP!=vFjPsUQF*wCJ*dO)4YBypgdiuF!=i@6Zyi7F|q#K zz?tlSZULa@t1D?$e;f@b36&N!V2mjOHw|*FMR=dr@6o0ZXGBB_+=zx3%$`cG63Jm-*84Da1I50Ew7%?y?G#+5$ UVU>wEFhP-tNtBU=gM+~u00(^_>i_@% literal 0 HcmV?d00001 diff --git a/examples/issue450/grails-app/assets/images/skin/sorted_desc.gif b/examples/issue450/grails-app/assets/images/skin/sorted_desc.gif new file mode 100644 index 0000000000000000000000000000000000000000..38b3a01d078418d3afcdb2765251a9f21b7995be GIT binary patch literal 834 zcmZ?wbhEHbWMg1s_|Cu}C@83_tE;D{=jG+)?d|RKCt}{bc?%XSShQ%-?%lih?%lh8 z|9*y1Fd72GGz1iXvM@3*urla?{0GVt3>@+doD32i4hNW;7z9Kl3=A9^nYh^GDt25@ NJj%i*$Hu~74FL5|8=3$B literal 0 HcmV?d00001 diff --git a/examples/issue450/grails-app/assets/images/slack.svg b/examples/issue450/grails-app/assets/images/slack.svg new file mode 100644 index 00000000..34fcf4ce --- /dev/null +++ b/examples/issue450/grails-app/assets/images/slack.svg @@ -0,0 +1,18 @@ + + + + slack_orange + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/issue450/grails-app/assets/images/spinner.gif b/examples/issue450/grails-app/assets/images/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..1ed786f2ece49ec5db07dee13a56ef38025b628c GIT binary patch literal 2037 zcmY*ZcTf|19{+AO8xjIZfItFCFrkL3BodGwflvety+|>T03uy{D35o<4X`9Q3=bSU zojZMs3Qw_)j=f_!)B!!mUKqwc>ezd^4c;H{$Ifr|uTTHRC8&buXgI)uM*u&6{`~Rd zM}L3+_wV1IK7G1x-@ebEKY#i1<^B8j-@bj@wr$&s7cWknII(;8?sMnPJ$(4^-Me>t z_wN1p@#D#pCr3v|A3b`6qUeJM5ANT;KRi7A^5x65YuBDSb?WNXs}mCwVPRo|gM+(v z?RxX(&Gzlv_w3no`}XavTesf4dGq@9>xT~?_VDnybm`KK8#fLfJh*P%x(gRB?AWp6 z>({T(pFdAaOMCY0SzBA%hYueT6BF;;xnpHzb@}q;O`A4(d3im4{J5Z?;ONn#0|NsW zFJ9cgfB);(ugAv5a&vP_OG`&aMz(C(^6J&A@$vBk2M!!Re*DRkCvV@rJ%9duQ&ZFC z&6|%MJC>4?a{Bb?vuDqqIdg`~z*4Ws1>(;I2=4ORL zQCC-|R4OYgD_5;rwQSk44I4IW+_=%t&(GD>Rj=1yxpHM_Xh`ytnG&0k9<5Zz%KT@c z2mnYvQ>hsF`jQ_R5(mKIydH2saldkg!Gzlvi9nFeu<gyfnCs8|SGO1R0M3N~Sp zx@MoynP5Rh(303ldPHFemMT%$+lXy}A1JR?IwL6=E`apW=@Z-0R!QO^@j_&{6#;i|5R1o$ zJ1J~xCDQ$1cs9`DW9Z!)D)^+%&Ug81908UkMXX;jkp>#b+SE}d{`0S>>Ef(`Ns6oa zB~H-LX25y1!BDgW%?nC0$RettYz8zsuz>9Z!g8TH$kU;rwYWrSTkjuYCH9utgFU6b z*wzBKaG6IjEXYSpAo5j4&c(t zwi-c(Q6YX2N-v2q(mgN`>wuCTV&XSRUA4h-f8g+uV2k_=o;2wb`7N)&+7T-C+CT_Bu4KrjWmK>x0AbH2rmR&7+q6fX+R0o&}V!t?TP+g0{x`8PSP{7CvnEPL^Hzx^T2Eus2 zi$U6ZM7}+l%$}afWn#%|+MPTsC236GKi9-ad*Z9;Ymg?jSO$L1s$w4BvDzu_kH9>z zTWC{K?jx4~H3oGGt3}I}LWNw@H)C>9GF4^|x(5n#+Va}kr_cb>{a+K%>B+@JNrC8? zJOUkD3fe*NdA-rJupqo?FfRK}k zOm!l7Dj$dsL|kS`3FL3^@^7axrU9d*@79yf!+bu3kXbziU!v&TO3p#w{y_lYG5ZPzDh;giB_lkGp((nqZ12&%LTrgm4vY= z{ffd0B$DiUKReJ9>?{#_KVrMyhiu_A8fNd!&DWTZ4+9S|1lJHkLv-^}a0IC{WI9N! zQTY-}db!%OH^;l2ZeajnA&Q6Uv=#0KZB9;^^lzMOi^0~bS~m!&K#e6hia79(ItV9M SXP~*+AU!zMlD%~Wg#Hic*k=_0 literal 0 HcmV?d00001 diff --git a/examples/issue450/grails-app/assets/javascripts/application.js b/examples/issue450/grails-app/assets/javascripts/application.js new file mode 100644 index 00000000..658aeb3c --- /dev/null +++ b/examples/issue450/grails-app/assets/javascripts/application.js @@ -0,0 +1,11 @@ +// This is a manifest file that'll be compiled into application.js. +// +// Any JavaScript file within this directory can be referenced here using a relative path. +// +// You're free to add application-wide JavaScript to this file, but it's generally better +// to create separate JavaScript files as needed. +// +//= require jquery-3.3.1.min +//= require bootstrap +//= require popper.min +//= require_self diff --git a/examples/issue450/grails-app/assets/javascripts/bootstrap.bundle.js b/examples/issue450/grails-app/assets/javascripts/bootstrap.bundle.js new file mode 100644 index 00000000..e8b832da --- /dev/null +++ b/examples/issue450/grails-app/assets/javascripts/bootstrap.bundle.js @@ -0,0 +1,6461 @@ +/*! + * Bootstrap v4.1.3 (https://getbootstrap.com/) + * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('jquery')) : + typeof define === 'function' && define.amd ? define(['exports', 'jquery'], factory) : + (factory((global.bootstrap = {}),global.jQuery)); +}(this, (function (exports,$) { 'use strict'; + + $ = $ && $.hasOwnProperty('default') ? $['default'] : $; + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + function _defineProperty(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + + return obj; + } + + function _objectSpread(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i] != null ? arguments[i] : {}; + var ownKeys = Object.keys(source); + + if (typeof Object.getOwnPropertySymbols === 'function') { + ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { + return Object.getOwnPropertyDescriptor(source, sym).enumerable; + })); + } + + ownKeys.forEach(function (key) { + _defineProperty(target, key, source[key]); + }); + } + + return target; + } + + function _inheritsLoose(subClass, superClass) { + subClass.prototype = Object.create(superClass.prototype); + subClass.prototype.constructor = subClass; + subClass.__proto__ = superClass; + } + + /** + * -------------------------------------------------------------------------- + * Bootstrap (v4.1.3): util.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + + var Util = function ($$$1) { + /** + * ------------------------------------------------------------------------ + * Private TransitionEnd Helpers + * ------------------------------------------------------------------------ + */ + var TRANSITION_END = 'transitionend'; + var MAX_UID = 1000000; + var MILLISECONDS_MULTIPLIER = 1000; // Shoutout AngusCroll (https://goo.gl/pxwQGp) + + function toType(obj) { + return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase(); + } + + function getSpecialTransitionEndEvent() { + return { + bindType: TRANSITION_END, + delegateType: TRANSITION_END, + handle: function handle(event) { + if ($$$1(event.target).is(this)) { + return event.handleObj.handler.apply(this, arguments); // eslint-disable-line prefer-rest-params + } + + return undefined; // eslint-disable-line no-undefined + } + }; + } + + function transitionEndEmulator(duration) { + var _this = this; + + var called = false; + $$$1(this).one(Util.TRANSITION_END, function () { + called = true; + }); + setTimeout(function () { + if (!called) { + Util.triggerTransitionEnd(_this); + } + }, duration); + return this; + } + + function setTransitionEndSupport() { + $$$1.fn.emulateTransitionEnd = transitionEndEmulator; + $$$1.event.special[Util.TRANSITION_END] = getSpecialTransitionEndEvent(); + } + /** + * -------------------------------------------------------------------------- + * Public Util Api + * -------------------------------------------------------------------------- + */ + + + var Util = { + TRANSITION_END: 'bsTransitionEnd', + getUID: function getUID(prefix) { + do { + // eslint-disable-next-line no-bitwise + prefix += ~~(Math.random() * MAX_UID); // "~~" acts like a faster Math.floor() here + } while (document.getElementById(prefix)); + + return prefix; + }, + getSelectorFromElement: function getSelectorFromElement(element) { + var selector = element.getAttribute('data-target'); + + if (!selector || selector === '#') { + selector = element.getAttribute('href') || ''; + } + + try { + return document.querySelector(selector) ? selector : null; + } catch (err) { + return null; + } + }, + getTransitionDurationFromElement: function getTransitionDurationFromElement(element) { + if (!element) { + return 0; + } // Get transition-duration of the element + + + var transitionDuration = $$$1(element).css('transition-duration'); + var floatTransitionDuration = parseFloat(transitionDuration); // Return 0 if element or transition duration is not found + + if (!floatTransitionDuration) { + return 0; + } // If multiple durations are defined, take the first + + + transitionDuration = transitionDuration.split(',')[0]; + return parseFloat(transitionDuration) * MILLISECONDS_MULTIPLIER; + }, + reflow: function reflow(element) { + return element.offsetHeight; + }, + triggerTransitionEnd: function triggerTransitionEnd(element) { + $$$1(element).trigger(TRANSITION_END); + }, + // TODO: Remove in v5 + supportsTransitionEnd: function supportsTransitionEnd() { + return Boolean(TRANSITION_END); + }, + isElement: function isElement(obj) { + return (obj[0] || obj).nodeType; + }, + typeCheckConfig: function typeCheckConfig(componentName, config, configTypes) { + for (var property in configTypes) { + if (Object.prototype.hasOwnProperty.call(configTypes, property)) { + var expectedTypes = configTypes[property]; + var value = config[property]; + var valueType = value && Util.isElement(value) ? 'element' : toType(value); + + if (!new RegExp(expectedTypes).test(valueType)) { + throw new Error(componentName.toUpperCase() + ": " + ("Option \"" + property + "\" provided type \"" + valueType + "\" ") + ("but expected type \"" + expectedTypes + "\".")); + } + } + } + } + }; + setTransitionEndSupport(); + return Util; + }($); + + /** + * -------------------------------------------------------------------------- + * Bootstrap (v4.1.3): alert.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + + var Alert = function ($$$1) { + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + var NAME = 'alert'; + var VERSION = '4.1.3'; + var DATA_KEY = 'bs.alert'; + var EVENT_KEY = "." + DATA_KEY; + var DATA_API_KEY = '.data-api'; + var JQUERY_NO_CONFLICT = $$$1.fn[NAME]; + var Selector = { + DISMISS: '[data-dismiss="alert"]' + }; + var Event = { + CLOSE: "close" + EVENT_KEY, + CLOSED: "closed" + EVENT_KEY, + CLICK_DATA_API: "click" + EVENT_KEY + DATA_API_KEY + }; + var ClassName = { + ALERT: 'alert', + FADE: 'fade', + SHOW: 'show' + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + }; + + var Alert = + /*#__PURE__*/ + function () { + function Alert(element) { + this._element = element; + } // Getters + + + var _proto = Alert.prototype; + + // Public + _proto.close = function close(element) { + var rootElement = this._element; + + if (element) { + rootElement = this._getRootElement(element); + } + + var customEvent = this._triggerCloseEvent(rootElement); + + if (customEvent.isDefaultPrevented()) { + return; + } + + this._removeElement(rootElement); + }; + + _proto.dispose = function dispose() { + $$$1.removeData(this._element, DATA_KEY); + this._element = null; + }; // Private + + + _proto._getRootElement = function _getRootElement(element) { + var selector = Util.getSelectorFromElement(element); + var parent = false; + + if (selector) { + parent = document.querySelector(selector); + } + + if (!parent) { + parent = $$$1(element).closest("." + ClassName.ALERT)[0]; + } + + return parent; + }; + + _proto._triggerCloseEvent = function _triggerCloseEvent(element) { + var closeEvent = $$$1.Event(Event.CLOSE); + $$$1(element).trigger(closeEvent); + return closeEvent; + }; + + _proto._removeElement = function _removeElement(element) { + var _this = this; + + $$$1(element).removeClass(ClassName.SHOW); + + if (!$$$1(element).hasClass(ClassName.FADE)) { + this._destroyElement(element); + + return; + } + + var transitionDuration = Util.getTransitionDurationFromElement(element); + $$$1(element).one(Util.TRANSITION_END, function (event) { + return _this._destroyElement(element, event); + }).emulateTransitionEnd(transitionDuration); + }; + + _proto._destroyElement = function _destroyElement(element) { + $$$1(element).detach().trigger(Event.CLOSED).remove(); + }; // Static + + + Alert._jQueryInterface = function _jQueryInterface(config) { + return this.each(function () { + var $element = $$$1(this); + var data = $element.data(DATA_KEY); + + if (!data) { + data = new Alert(this); + $element.data(DATA_KEY, data); + } + + if (config === 'close') { + data[config](this); + } + }); + }; + + Alert._handleDismiss = function _handleDismiss(alertInstance) { + return function (event) { + if (event) { + event.preventDefault(); + } + + alertInstance.close(this); + }; + }; + + _createClass(Alert, null, [{ + key: "VERSION", + get: function get() { + return VERSION; + } + }]); + + return Alert; + }(); + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + + $$$1(document).on(Event.CLICK_DATA_API, Selector.DISMISS, Alert._handleDismiss(new Alert())); + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $$$1.fn[NAME] = Alert._jQueryInterface; + $$$1.fn[NAME].Constructor = Alert; + + $$$1.fn[NAME].noConflict = function () { + $$$1.fn[NAME] = JQUERY_NO_CONFLICT; + return Alert._jQueryInterface; + }; + + return Alert; + }($); + + /** + * -------------------------------------------------------------------------- + * Bootstrap (v4.1.3): button.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + + var Button = function ($$$1) { + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + var NAME = 'button'; + var VERSION = '4.1.3'; + var DATA_KEY = 'bs.button'; + var EVENT_KEY = "." + DATA_KEY; + var DATA_API_KEY = '.data-api'; + var JQUERY_NO_CONFLICT = $$$1.fn[NAME]; + var ClassName = { + ACTIVE: 'active', + BUTTON: 'btn', + FOCUS: 'focus' + }; + var Selector = { + DATA_TOGGLE_CARROT: '[data-toggle^="button"]', + DATA_TOGGLE: '[data-toggle="buttons"]', + INPUT: 'input', + ACTIVE: '.active', + BUTTON: '.btn' + }; + var Event = { + CLICK_DATA_API: "click" + EVENT_KEY + DATA_API_KEY, + FOCUS_BLUR_DATA_API: "focus" + EVENT_KEY + DATA_API_KEY + " " + ("blur" + EVENT_KEY + DATA_API_KEY) + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + }; + + var Button = + /*#__PURE__*/ + function () { + function Button(element) { + this._element = element; + } // Getters + + + var _proto = Button.prototype; + + // Public + _proto.toggle = function toggle() { + var triggerChangeEvent = true; + var addAriaPressed = true; + var rootElement = $$$1(this._element).closest(Selector.DATA_TOGGLE)[0]; + + if (rootElement) { + var input = this._element.querySelector(Selector.INPUT); + + if (input) { + if (input.type === 'radio') { + if (input.checked && this._element.classList.contains(ClassName.ACTIVE)) { + triggerChangeEvent = false; + } else { + var activeElement = rootElement.querySelector(Selector.ACTIVE); + + if (activeElement) { + $$$1(activeElement).removeClass(ClassName.ACTIVE); + } + } + } + + if (triggerChangeEvent) { + if (input.hasAttribute('disabled') || rootElement.hasAttribute('disabled') || input.classList.contains('disabled') || rootElement.classList.contains('disabled')) { + return; + } + + input.checked = !this._element.classList.contains(ClassName.ACTIVE); + $$$1(input).trigger('change'); + } + + input.focus(); + addAriaPressed = false; + } + } + + if (addAriaPressed) { + this._element.setAttribute('aria-pressed', !this._element.classList.contains(ClassName.ACTIVE)); + } + + if (triggerChangeEvent) { + $$$1(this._element).toggleClass(ClassName.ACTIVE); + } + }; + + _proto.dispose = function dispose() { + $$$1.removeData(this._element, DATA_KEY); + this._element = null; + }; // Static + + + Button._jQueryInterface = function _jQueryInterface(config) { + return this.each(function () { + var data = $$$1(this).data(DATA_KEY); + + if (!data) { + data = new Button(this); + $$$1(this).data(DATA_KEY, data); + } + + if (config === 'toggle') { + data[config](); + } + }); + }; + + _createClass(Button, null, [{ + key: "VERSION", + get: function get() { + return VERSION; + } + }]); + + return Button; + }(); + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + + $$$1(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE_CARROT, function (event) { + event.preventDefault(); + var button = event.target; + + if (!$$$1(button).hasClass(ClassName.BUTTON)) { + button = $$$1(button).closest(Selector.BUTTON); + } + + Button._jQueryInterface.call($$$1(button), 'toggle'); + }).on(Event.FOCUS_BLUR_DATA_API, Selector.DATA_TOGGLE_CARROT, function (event) { + var button = $$$1(event.target).closest(Selector.BUTTON)[0]; + $$$1(button).toggleClass(ClassName.FOCUS, /^focus(in)?$/.test(event.type)); + }); + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $$$1.fn[NAME] = Button._jQueryInterface; + $$$1.fn[NAME].Constructor = Button; + + $$$1.fn[NAME].noConflict = function () { + $$$1.fn[NAME] = JQUERY_NO_CONFLICT; + return Button._jQueryInterface; + }; + + return Button; + }($); + + /** + * -------------------------------------------------------------------------- + * Bootstrap (v4.1.3): carousel.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + + var Carousel = function ($$$1) { + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + var NAME = 'carousel'; + var VERSION = '4.1.3'; + var DATA_KEY = 'bs.carousel'; + var EVENT_KEY = "." + DATA_KEY; + var DATA_API_KEY = '.data-api'; + var JQUERY_NO_CONFLICT = $$$1.fn[NAME]; + var ARROW_LEFT_KEYCODE = 37; // KeyboardEvent.which value for left arrow key + + var ARROW_RIGHT_KEYCODE = 39; // KeyboardEvent.which value for right arrow key + + var TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch + + var Default = { + interval: 5000, + keyboard: true, + slide: false, + pause: 'hover', + wrap: true + }; + var DefaultType = { + interval: '(number|boolean)', + keyboard: 'boolean', + slide: '(boolean|string)', + pause: '(string|boolean)', + wrap: 'boolean' + }; + var Direction = { + NEXT: 'next', + PREV: 'prev', + LEFT: 'left', + RIGHT: 'right' + }; + var Event = { + SLIDE: "slide" + EVENT_KEY, + SLID: "slid" + EVENT_KEY, + KEYDOWN: "keydown" + EVENT_KEY, + MOUSEENTER: "mouseenter" + EVENT_KEY, + MOUSELEAVE: "mouseleave" + EVENT_KEY, + TOUCHEND: "touchend" + EVENT_KEY, + LOAD_DATA_API: "load" + EVENT_KEY + DATA_API_KEY, + CLICK_DATA_API: "click" + EVENT_KEY + DATA_API_KEY + }; + var ClassName = { + CAROUSEL: 'carousel', + ACTIVE: 'active', + SLIDE: 'slide', + RIGHT: 'carousel-item-right', + LEFT: 'carousel-item-left', + NEXT: 'carousel-item-next', + PREV: 'carousel-item-prev', + ITEM: 'carousel-item' + }; + var Selector = { + ACTIVE: '.active', + ACTIVE_ITEM: '.active.carousel-item', + ITEM: '.carousel-item', + NEXT_PREV: '.carousel-item-next, .carousel-item-prev', + INDICATORS: '.carousel-indicators', + DATA_SLIDE: '[data-slide], [data-slide-to]', + DATA_RIDE: '[data-ride="carousel"]' + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + }; + + var Carousel = + /*#__PURE__*/ + function () { + function Carousel(element, config) { + this._items = null; + this._interval = null; + this._activeElement = null; + this._isPaused = false; + this._isSliding = false; + this.touchTimeout = null; + this._config = this._getConfig(config); + this._element = $$$1(element)[0]; + this._indicatorsElement = this._element.querySelector(Selector.INDICATORS); + + this._addEventListeners(); + } // Getters + + + var _proto = Carousel.prototype; + + // Public + _proto.next = function next() { + if (!this._isSliding) { + this._slide(Direction.NEXT); + } + }; + + _proto.nextWhenVisible = function nextWhenVisible() { + // Don't call next when the page isn't visible + // or the carousel or its parent isn't visible + if (!document.hidden && $$$1(this._element).is(':visible') && $$$1(this._element).css('visibility') !== 'hidden') { + this.next(); + } + }; + + _proto.prev = function prev() { + if (!this._isSliding) { + this._slide(Direction.PREV); + } + }; + + _proto.pause = function pause(event) { + if (!event) { + this._isPaused = true; + } + + if (this._element.querySelector(Selector.NEXT_PREV)) { + Util.triggerTransitionEnd(this._element); + this.cycle(true); + } + + clearInterval(this._interval); + this._interval = null; + }; + + _proto.cycle = function cycle(event) { + if (!event) { + this._isPaused = false; + } + + if (this._interval) { + clearInterval(this._interval); + this._interval = null; + } + + if (this._config.interval && !this._isPaused) { + this._interval = setInterval((document.visibilityState ? this.nextWhenVisible : this.next).bind(this), this._config.interval); + } + }; + + _proto.to = function to(index) { + var _this = this; + + this._activeElement = this._element.querySelector(Selector.ACTIVE_ITEM); + + var activeIndex = this._getItemIndex(this._activeElement); + + if (index > this._items.length - 1 || index < 0) { + return; + } + + if (this._isSliding) { + $$$1(this._element).one(Event.SLID, function () { + return _this.to(index); + }); + return; + } + + if (activeIndex === index) { + this.pause(); + this.cycle(); + return; + } + + var direction = index > activeIndex ? Direction.NEXT : Direction.PREV; + + this._slide(direction, this._items[index]); + }; + + _proto.dispose = function dispose() { + $$$1(this._element).off(EVENT_KEY); + $$$1.removeData(this._element, DATA_KEY); + this._items = null; + this._config = null; + this._element = null; + this._interval = null; + this._isPaused = null; + this._isSliding = null; + this._activeElement = null; + this._indicatorsElement = null; + }; // Private + + + _proto._getConfig = function _getConfig(config) { + config = _objectSpread({}, Default, config); + Util.typeCheckConfig(NAME, config, DefaultType); + return config; + }; + + _proto._addEventListeners = function _addEventListeners() { + var _this2 = this; + + if (this._config.keyboard) { + $$$1(this._element).on(Event.KEYDOWN, function (event) { + return _this2._keydown(event); + }); + } + + if (this._config.pause === 'hover') { + $$$1(this._element).on(Event.MOUSEENTER, function (event) { + return _this2.pause(event); + }).on(Event.MOUSELEAVE, function (event) { + return _this2.cycle(event); + }); + + if ('ontouchstart' in document.documentElement) { + // If it's a touch-enabled device, mouseenter/leave are fired as + // part of the mouse compatibility events on first tap - the carousel + // would stop cycling until user tapped out of it; + // here, we listen for touchend, explicitly pause the carousel + // (as if it's the second time we tap on it, mouseenter compat event + // is NOT fired) and after a timeout (to allow for mouse compatibility + // events to fire) we explicitly restart cycling + $$$1(this._element).on(Event.TOUCHEND, function () { + _this2.pause(); + + if (_this2.touchTimeout) { + clearTimeout(_this2.touchTimeout); + } + + _this2.touchTimeout = setTimeout(function (event) { + return _this2.cycle(event); + }, TOUCHEVENT_COMPAT_WAIT + _this2._config.interval); + }); + } + } + }; + + _proto._keydown = function _keydown(event) { + if (/input|textarea/i.test(event.target.tagName)) { + return; + } + + switch (event.which) { + case ARROW_LEFT_KEYCODE: + event.preventDefault(); + this.prev(); + break; + + case ARROW_RIGHT_KEYCODE: + event.preventDefault(); + this.next(); + break; + + default: + } + }; + + _proto._getItemIndex = function _getItemIndex(element) { + this._items = element && element.parentNode ? [].slice.call(element.parentNode.querySelectorAll(Selector.ITEM)) : []; + return this._items.indexOf(element); + }; + + _proto._getItemByDirection = function _getItemByDirection(direction, activeElement) { + var isNextDirection = direction === Direction.NEXT; + var isPrevDirection = direction === Direction.PREV; + + var activeIndex = this._getItemIndex(activeElement); + + var lastItemIndex = this._items.length - 1; + var isGoingToWrap = isPrevDirection && activeIndex === 0 || isNextDirection && activeIndex === lastItemIndex; + + if (isGoingToWrap && !this._config.wrap) { + return activeElement; + } + + var delta = direction === Direction.PREV ? -1 : 1; + var itemIndex = (activeIndex + delta) % this._items.length; + return itemIndex === -1 ? this._items[this._items.length - 1] : this._items[itemIndex]; + }; + + _proto._triggerSlideEvent = function _triggerSlideEvent(relatedTarget, eventDirectionName) { + var targetIndex = this._getItemIndex(relatedTarget); + + var fromIndex = this._getItemIndex(this._element.querySelector(Selector.ACTIVE_ITEM)); + + var slideEvent = $$$1.Event(Event.SLIDE, { + relatedTarget: relatedTarget, + direction: eventDirectionName, + from: fromIndex, + to: targetIndex + }); + $$$1(this._element).trigger(slideEvent); + return slideEvent; + }; + + _proto._setActiveIndicatorElement = function _setActiveIndicatorElement(element) { + if (this._indicatorsElement) { + var indicators = [].slice.call(this._indicatorsElement.querySelectorAll(Selector.ACTIVE)); + $$$1(indicators).removeClass(ClassName.ACTIVE); + + var nextIndicator = this._indicatorsElement.children[this._getItemIndex(element)]; + + if (nextIndicator) { + $$$1(nextIndicator).addClass(ClassName.ACTIVE); + } + } + }; + + _proto._slide = function _slide(direction, element) { + var _this3 = this; + + var activeElement = this._element.querySelector(Selector.ACTIVE_ITEM); + + var activeElementIndex = this._getItemIndex(activeElement); + + var nextElement = element || activeElement && this._getItemByDirection(direction, activeElement); + + var nextElementIndex = this._getItemIndex(nextElement); + + var isCycling = Boolean(this._interval); + var directionalClassName; + var orderClassName; + var eventDirectionName; + + if (direction === Direction.NEXT) { + directionalClassName = ClassName.LEFT; + orderClassName = ClassName.NEXT; + eventDirectionName = Direction.LEFT; + } else { + directionalClassName = ClassName.RIGHT; + orderClassName = ClassName.PREV; + eventDirectionName = Direction.RIGHT; + } + + if (nextElement && $$$1(nextElement).hasClass(ClassName.ACTIVE)) { + this._isSliding = false; + return; + } + + var slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName); + + if (slideEvent.isDefaultPrevented()) { + return; + } + + if (!activeElement || !nextElement) { + // Some weirdness is happening, so we bail + return; + } + + this._isSliding = true; + + if (isCycling) { + this.pause(); + } + + this._setActiveIndicatorElement(nextElement); + + var slidEvent = $$$1.Event(Event.SLID, { + relatedTarget: nextElement, + direction: eventDirectionName, + from: activeElementIndex, + to: nextElementIndex + }); + + if ($$$1(this._element).hasClass(ClassName.SLIDE)) { + $$$1(nextElement).addClass(orderClassName); + Util.reflow(nextElement); + $$$1(activeElement).addClass(directionalClassName); + $$$1(nextElement).addClass(directionalClassName); + var transitionDuration = Util.getTransitionDurationFromElement(activeElement); + $$$1(activeElement).one(Util.TRANSITION_END, function () { + $$$1(nextElement).removeClass(directionalClassName + " " + orderClassName).addClass(ClassName.ACTIVE); + $$$1(activeElement).removeClass(ClassName.ACTIVE + " " + orderClassName + " " + directionalClassName); + _this3._isSliding = false; + setTimeout(function () { + return $$$1(_this3._element).trigger(slidEvent); + }, 0); + }).emulateTransitionEnd(transitionDuration); + } else { + $$$1(activeElement).removeClass(ClassName.ACTIVE); + $$$1(nextElement).addClass(ClassName.ACTIVE); + this._isSliding = false; + $$$1(this._element).trigger(slidEvent); + } + + if (isCycling) { + this.cycle(); + } + }; // Static + + + Carousel._jQueryInterface = function _jQueryInterface(config) { + return this.each(function () { + var data = $$$1(this).data(DATA_KEY); + + var _config = _objectSpread({}, Default, $$$1(this).data()); + + if (typeof config === 'object') { + _config = _objectSpread({}, _config, config); + } + + var action = typeof config === 'string' ? config : _config.slide; + + if (!data) { + data = new Carousel(this, _config); + $$$1(this).data(DATA_KEY, data); + } + + if (typeof config === 'number') { + data.to(config); + } else if (typeof action === 'string') { + if (typeof data[action] === 'undefined') { + throw new TypeError("No method named \"" + action + "\""); + } + + data[action](); + } else if (_config.interval) { + data.pause(); + data.cycle(); + } + }); + }; + + Carousel._dataApiClickHandler = function _dataApiClickHandler(event) { + var selector = Util.getSelectorFromElement(this); + + if (!selector) { + return; + } + + var target = $$$1(selector)[0]; + + if (!target || !$$$1(target).hasClass(ClassName.CAROUSEL)) { + return; + } + + var config = _objectSpread({}, $$$1(target).data(), $$$1(this).data()); + + var slideIndex = this.getAttribute('data-slide-to'); + + if (slideIndex) { + config.interval = false; + } + + Carousel._jQueryInterface.call($$$1(target), config); + + if (slideIndex) { + $$$1(target).data(DATA_KEY).to(slideIndex); + } + + event.preventDefault(); + }; + + _createClass(Carousel, null, [{ + key: "VERSION", + get: function get() { + return VERSION; + } + }, { + key: "Default", + get: function get() { + return Default; + } + }]); + + return Carousel; + }(); + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + + $$$1(document).on(Event.CLICK_DATA_API, Selector.DATA_SLIDE, Carousel._dataApiClickHandler); + $$$1(window).on(Event.LOAD_DATA_API, function () { + var carousels = [].slice.call(document.querySelectorAll(Selector.DATA_RIDE)); + + for (var i = 0, len = carousels.length; i < len; i++) { + var $carousel = $$$1(carousels[i]); + + Carousel._jQueryInterface.call($carousel, $carousel.data()); + } + }); + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $$$1.fn[NAME] = Carousel._jQueryInterface; + $$$1.fn[NAME].Constructor = Carousel; + + $$$1.fn[NAME].noConflict = function () { + $$$1.fn[NAME] = JQUERY_NO_CONFLICT; + return Carousel._jQueryInterface; + }; + + return Carousel; + }($); + + /** + * -------------------------------------------------------------------------- + * Bootstrap (v4.1.3): collapse.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + + var Collapse = function ($$$1) { + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + var NAME = 'collapse'; + var VERSION = '4.1.3'; + var DATA_KEY = 'bs.collapse'; + var EVENT_KEY = "." + DATA_KEY; + var DATA_API_KEY = '.data-api'; + var JQUERY_NO_CONFLICT = $$$1.fn[NAME]; + var Default = { + toggle: true, + parent: '' + }; + var DefaultType = { + toggle: 'boolean', + parent: '(string|element)' + }; + var Event = { + SHOW: "show" + EVENT_KEY, + SHOWN: "shown" + EVENT_KEY, + HIDE: "hide" + EVENT_KEY, + HIDDEN: "hidden" + EVENT_KEY, + CLICK_DATA_API: "click" + EVENT_KEY + DATA_API_KEY + }; + var ClassName = { + SHOW: 'show', + COLLAPSE: 'collapse', + COLLAPSING: 'collapsing', + COLLAPSED: 'collapsed' + }; + var Dimension = { + WIDTH: 'width', + HEIGHT: 'height' + }; + var Selector = { + ACTIVES: '.show, .collapsing', + DATA_TOGGLE: '[data-toggle="collapse"]' + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + }; + + var Collapse = + /*#__PURE__*/ + function () { + function Collapse(element, config) { + this._isTransitioning = false; + this._element = element; + this._config = this._getConfig(config); + this._triggerArray = $$$1.makeArray(document.querySelectorAll("[data-toggle=\"collapse\"][href=\"#" + element.id + "\"]," + ("[data-toggle=\"collapse\"][data-target=\"#" + element.id + "\"]"))); + var toggleList = [].slice.call(document.querySelectorAll(Selector.DATA_TOGGLE)); + + for (var i = 0, len = toggleList.length; i < len; i++) { + var elem = toggleList[i]; + var selector = Util.getSelectorFromElement(elem); + var filterElement = [].slice.call(document.querySelectorAll(selector)).filter(function (foundElem) { + return foundElem === element; + }); + + if (selector !== null && filterElement.length > 0) { + this._selector = selector; + + this._triggerArray.push(elem); + } + } + + this._parent = this._config.parent ? this._getParent() : null; + + if (!this._config.parent) { + this._addAriaAndCollapsedClass(this._element, this._triggerArray); + } + + if (this._config.toggle) { + this.toggle(); + } + } // Getters + + + var _proto = Collapse.prototype; + + // Public + _proto.toggle = function toggle() { + if ($$$1(this._element).hasClass(ClassName.SHOW)) { + this.hide(); + } else { + this.show(); + } + }; + + _proto.show = function show() { + var _this = this; + + if (this._isTransitioning || $$$1(this._element).hasClass(ClassName.SHOW)) { + return; + } + + var actives; + var activesData; + + if (this._parent) { + actives = [].slice.call(this._parent.querySelectorAll(Selector.ACTIVES)).filter(function (elem) { + return elem.getAttribute('data-parent') === _this._config.parent; + }); + + if (actives.length === 0) { + actives = null; + } + } + + if (actives) { + activesData = $$$1(actives).not(this._selector).data(DATA_KEY); + + if (activesData && activesData._isTransitioning) { + return; + } + } + + var startEvent = $$$1.Event(Event.SHOW); + $$$1(this._element).trigger(startEvent); + + if (startEvent.isDefaultPrevented()) { + return; + } + + if (actives) { + Collapse._jQueryInterface.call($$$1(actives).not(this._selector), 'hide'); + + if (!activesData) { + $$$1(actives).data(DATA_KEY, null); + } + } + + var dimension = this._getDimension(); + + $$$1(this._element).removeClass(ClassName.COLLAPSE).addClass(ClassName.COLLAPSING); + this._element.style[dimension] = 0; + + if (this._triggerArray.length) { + $$$1(this._triggerArray).removeClass(ClassName.COLLAPSED).attr('aria-expanded', true); + } + + this.setTransitioning(true); + + var complete = function complete() { + $$$1(_this._element).removeClass(ClassName.COLLAPSING).addClass(ClassName.COLLAPSE).addClass(ClassName.SHOW); + _this._element.style[dimension] = ''; + + _this.setTransitioning(false); + + $$$1(_this._element).trigger(Event.SHOWN); + }; + + var capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1); + var scrollSize = "scroll" + capitalizedDimension; + var transitionDuration = Util.getTransitionDurationFromElement(this._element); + $$$1(this._element).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration); + this._element.style[dimension] = this._element[scrollSize] + "px"; + }; + + _proto.hide = function hide() { + var _this2 = this; + + if (this._isTransitioning || !$$$1(this._element).hasClass(ClassName.SHOW)) { + return; + } + + var startEvent = $$$1.Event(Event.HIDE); + $$$1(this._element).trigger(startEvent); + + if (startEvent.isDefaultPrevented()) { + return; + } + + var dimension = this._getDimension(); + + this._element.style[dimension] = this._element.getBoundingClientRect()[dimension] + "px"; + Util.reflow(this._element); + $$$1(this._element).addClass(ClassName.COLLAPSING).removeClass(ClassName.COLLAPSE).removeClass(ClassName.SHOW); + var triggerArrayLength = this._triggerArray.length; + + if (triggerArrayLength > 0) { + for (var i = 0; i < triggerArrayLength; i++) { + var trigger = this._triggerArray[i]; + var selector = Util.getSelectorFromElement(trigger); + + if (selector !== null) { + var $elem = $$$1([].slice.call(document.querySelectorAll(selector))); + + if (!$elem.hasClass(ClassName.SHOW)) { + $$$1(trigger).addClass(ClassName.COLLAPSED).attr('aria-expanded', false); + } + } + } + } + + this.setTransitioning(true); + + var complete = function complete() { + _this2.setTransitioning(false); + + $$$1(_this2._element).removeClass(ClassName.COLLAPSING).addClass(ClassName.COLLAPSE).trigger(Event.HIDDEN); + }; + + this._element.style[dimension] = ''; + var transitionDuration = Util.getTransitionDurationFromElement(this._element); + $$$1(this._element).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration); + }; + + _proto.setTransitioning = function setTransitioning(isTransitioning) { + this._isTransitioning = isTransitioning; + }; + + _proto.dispose = function dispose() { + $$$1.removeData(this._element, DATA_KEY); + this._config = null; + this._parent = null; + this._element = null; + this._triggerArray = null; + this._isTransitioning = null; + }; // Private + + + _proto._getConfig = function _getConfig(config) { + config = _objectSpread({}, Default, config); + config.toggle = Boolean(config.toggle); // Coerce string values + + Util.typeCheckConfig(NAME, config, DefaultType); + return config; + }; + + _proto._getDimension = function _getDimension() { + var hasWidth = $$$1(this._element).hasClass(Dimension.WIDTH); + return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT; + }; + + _proto._getParent = function _getParent() { + var _this3 = this; + + var parent = null; + + if (Util.isElement(this._config.parent)) { + parent = this._config.parent; // It's a jQuery object + + if (typeof this._config.parent.jquery !== 'undefined') { + parent = this._config.parent[0]; + } + } else { + parent = document.querySelector(this._config.parent); + } + + var selector = "[data-toggle=\"collapse\"][data-parent=\"" + this._config.parent + "\"]"; + var children = [].slice.call(parent.querySelectorAll(selector)); + $$$1(children).each(function (i, element) { + _this3._addAriaAndCollapsedClass(Collapse._getTargetFromElement(element), [element]); + }); + return parent; + }; + + _proto._addAriaAndCollapsedClass = function _addAriaAndCollapsedClass(element, triggerArray) { + if (element) { + var isOpen = $$$1(element).hasClass(ClassName.SHOW); + + if (triggerArray.length) { + $$$1(triggerArray).toggleClass(ClassName.COLLAPSED, !isOpen).attr('aria-expanded', isOpen); + } + } + }; // Static + + + Collapse._getTargetFromElement = function _getTargetFromElement(element) { + var selector = Util.getSelectorFromElement(element); + return selector ? document.querySelector(selector) : null; + }; + + Collapse._jQueryInterface = function _jQueryInterface(config) { + return this.each(function () { + var $this = $$$1(this); + var data = $this.data(DATA_KEY); + + var _config = _objectSpread({}, Default, $this.data(), typeof config === 'object' && config ? config : {}); + + if (!data && _config.toggle && /show|hide/.test(config)) { + _config.toggle = false; + } + + if (!data) { + data = new Collapse(this, _config); + $this.data(DATA_KEY, data); + } + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError("No method named \"" + config + "\""); + } + + data[config](); + } + }); + }; + + _createClass(Collapse, null, [{ + key: "VERSION", + get: function get() { + return VERSION; + } + }, { + key: "Default", + get: function get() { + return Default; + } + }]); + + return Collapse; + }(); + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + + $$$1(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) { + // preventDefault only for elements (which change the URL) not inside the collapsible element + if (event.currentTarget.tagName === 'A') { + event.preventDefault(); + } + + var $trigger = $$$1(this); + var selector = Util.getSelectorFromElement(this); + var selectors = [].slice.call(document.querySelectorAll(selector)); + $$$1(selectors).each(function () { + var $target = $$$1(this); + var data = $target.data(DATA_KEY); + var config = data ? 'toggle' : $trigger.data(); + + Collapse._jQueryInterface.call($target, config); + }); + }); + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $$$1.fn[NAME] = Collapse._jQueryInterface; + $$$1.fn[NAME].Constructor = Collapse; + + $$$1.fn[NAME].noConflict = function () { + $$$1.fn[NAME] = JQUERY_NO_CONFLICT; + return Collapse._jQueryInterface; + }; + + return Collapse; + }($); + + /**! + * @fileOverview Kickass library to create and place poppers near their reference elements. + * @version 1.14.3 + * @license + * Copyright (c) 2016 Federico Zivolo and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'; + + var longerTimeoutBrowsers = ['Edge', 'Trident', 'Firefox']; + var timeoutDuration = 0; + for (var i = 0; i < longerTimeoutBrowsers.length; i += 1) { + if (isBrowser && navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) { + timeoutDuration = 1; + break; + } + } + + function microtaskDebounce(fn) { + var called = false; + return function () { + if (called) { + return; + } + called = true; + window.Promise.resolve().then(function () { + called = false; + fn(); + }); + }; + } + + function taskDebounce(fn) { + var scheduled = false; + return function () { + if (!scheduled) { + scheduled = true; + setTimeout(function () { + scheduled = false; + fn(); + }, timeoutDuration); + } + }; + } + + var supportsMicroTasks = isBrowser && window.Promise; + + /** + * Create a debounced version of a method, that's asynchronously deferred + * but called in the minimum time possible. + * + * @method + * @memberof Popper.Utils + * @argument {Function} fn + * @returns {Function} + */ + var debounce = supportsMicroTasks ? microtaskDebounce : taskDebounce; + + /** + * Check if the given variable is a function + * @method + * @memberof Popper.Utils + * @argument {Any} functionToCheck - variable to check + * @returns {Boolean} answer to: is a function? + */ + function isFunction(functionToCheck) { + var getType = {}; + return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; + } + + /** + * Get CSS computed property of the given element + * @method + * @memberof Popper.Utils + * @argument {Eement} element + * @argument {String} property + */ + function getStyleComputedProperty(element, property) { + if (element.nodeType !== 1) { + return []; + } + // NOTE: 1 DOM access here + var css = getComputedStyle(element, null); + return property ? css[property] : css; + } + + /** + * Returns the parentNode or the host of the element + * @method + * @memberof Popper.Utils + * @argument {Element} element + * @returns {Element} parent + */ + function getParentNode(element) { + if (element.nodeName === 'HTML') { + return element; + } + return element.parentNode || element.host; + } + + /** + * Returns the scrolling parent of the given element + * @method + * @memberof Popper.Utils + * @argument {Element} element + * @returns {Element} scroll parent + */ + function getScrollParent(element) { + // Return body, `getScroll` will take care to get the correct `scrollTop` from it + if (!element) { + return document.body; + } + + switch (element.nodeName) { + case 'HTML': + case 'BODY': + return element.ownerDocument.body; + case '#document': + return element.body; + } + + // Firefox want us to check `-x` and `-y` variations as well + + var _getStyleComputedProp = getStyleComputedProperty(element), + overflow = _getStyleComputedProp.overflow, + overflowX = _getStyleComputedProp.overflowX, + overflowY = _getStyleComputedProp.overflowY; + + if (/(auto|scroll|overlay)/.test(overflow + overflowY + overflowX)) { + return element; + } + + return getScrollParent(getParentNode(element)); + } + + var isIE11 = isBrowser && !!(window.MSInputMethodContext && document.documentMode); + var isIE10 = isBrowser && /MSIE 10/.test(navigator.userAgent); + + /** + * Determines if the browser is Internet Explorer + * @method + * @memberof Popper.Utils + * @param {Number} version to check + * @returns {Boolean} isIE + */ + function isIE(version) { + if (version === 11) { + return isIE11; + } + if (version === 10) { + return isIE10; + } + return isIE11 || isIE10; + } + + /** + * Returns the offset parent of the given element + * @method + * @memberof Popper.Utils + * @argument {Element} element + * @returns {Element} offset parent + */ + function getOffsetParent(element) { + if (!element) { + return document.documentElement; + } + + var noOffsetParent = isIE(10) ? document.body : null; + + // NOTE: 1 DOM access here + var offsetParent = element.offsetParent; + // Skip hidden elements which don't have an offsetParent + while (offsetParent === noOffsetParent && element.nextElementSibling) { + offsetParent = (element = element.nextElementSibling).offsetParent; + } + + var nodeName = offsetParent && offsetParent.nodeName; + + if (!nodeName || nodeName === 'BODY' || nodeName === 'HTML') { + return element ? element.ownerDocument.documentElement : document.documentElement; + } + + // .offsetParent will return the closest TD or TABLE in case + // no offsetParent is present, I hate this job... + if (['TD', 'TABLE'].indexOf(offsetParent.nodeName) !== -1 && getStyleComputedProperty(offsetParent, 'position') === 'static') { + return getOffsetParent(offsetParent); + } + + return offsetParent; + } + + function isOffsetContainer(element) { + var nodeName = element.nodeName; + + if (nodeName === 'BODY') { + return false; + } + return nodeName === 'HTML' || getOffsetParent(element.firstElementChild) === element; + } + + /** + * Finds the root node (document, shadowDOM root) of the given element + * @method + * @memberof Popper.Utils + * @argument {Element} node + * @returns {Element} root node + */ + function getRoot(node) { + if (node.parentNode !== null) { + return getRoot(node.parentNode); + } + + return node; + } + + /** + * Finds the offset parent common to the two provided nodes + * @method + * @memberof Popper.Utils + * @argument {Element} element1 + * @argument {Element} element2 + * @returns {Element} common offset parent + */ + function findCommonOffsetParent(element1, element2) { + // This check is needed to avoid errors in case one of the elements isn't defined for any reason + if (!element1 || !element1.nodeType || !element2 || !element2.nodeType) { + return document.documentElement; + } + + // Here we make sure to give as "start" the element that comes first in the DOM + var order = element1.compareDocumentPosition(element2) & Node.DOCUMENT_POSITION_FOLLOWING; + var start = order ? element1 : element2; + var end = order ? element2 : element1; + + // Get common ancestor container + var range = document.createRange(); + range.setStart(start, 0); + range.setEnd(end, 0); + var commonAncestorContainer = range.commonAncestorContainer; + + // Both nodes are inside #document + + if (element1 !== commonAncestorContainer && element2 !== commonAncestorContainer || start.contains(end)) { + if (isOffsetContainer(commonAncestorContainer)) { + return commonAncestorContainer; + } + + return getOffsetParent(commonAncestorContainer); + } + + // one of the nodes is inside shadowDOM, find which one + var element1root = getRoot(element1); + if (element1root.host) { + return findCommonOffsetParent(element1root.host, element2); + } else { + return findCommonOffsetParent(element1, getRoot(element2).host); + } + } + + /** + * Gets the scroll value of the given element in the given side (top and left) + * @method + * @memberof Popper.Utils + * @argument {Element} element + * @argument {String} side `top` or `left` + * @returns {number} amount of scrolled pixels + */ + function getScroll(element) { + var side = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'top'; + + var upperSide = side === 'top' ? 'scrollTop' : 'scrollLeft'; + var nodeName = element.nodeName; + + if (nodeName === 'BODY' || nodeName === 'HTML') { + var html = element.ownerDocument.documentElement; + var scrollingElement = element.ownerDocument.scrollingElement || html; + return scrollingElement[upperSide]; + } + + return element[upperSide]; + } + + /* + * Sum or subtract the element scroll values (left and top) from a given rect object + * @method + * @memberof Popper.Utils + * @param {Object} rect - Rect object you want to change + * @param {HTMLElement} element - The element from the function reads the scroll values + * @param {Boolean} subtract - set to true if you want to subtract the scroll values + * @return {Object} rect - The modifier rect object + */ + function includeScroll(rect, element) { + var subtract = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + + var scrollTop = getScroll(element, 'top'); + var scrollLeft = getScroll(element, 'left'); + var modifier = subtract ? -1 : 1; + rect.top += scrollTop * modifier; + rect.bottom += scrollTop * modifier; + rect.left += scrollLeft * modifier; + rect.right += scrollLeft * modifier; + return rect; + } + + /* + * Helper to detect borders of a given element + * @method + * @memberof Popper.Utils + * @param {CSSStyleDeclaration} styles + * Result of `getStyleComputedProperty` on the given element + * @param {String} axis - `x` or `y` + * @return {number} borders - The borders size of the given axis + */ + + function getBordersSize(styles, axis) { + var sideA = axis === 'x' ? 'Left' : 'Top'; + var sideB = sideA === 'Left' ? 'Right' : 'Bottom'; + + return parseFloat(styles['border' + sideA + 'Width'], 10) + parseFloat(styles['border' + sideB + 'Width'], 10); + } + + function getSize(axis, body, html, computedStyle) { + return Math.max(body['offset' + axis], body['scroll' + axis], html['client' + axis], html['offset' + axis], html['scroll' + axis], isIE(10) ? html['offset' + axis] + computedStyle['margin' + (axis === 'Height' ? 'Top' : 'Left')] + computedStyle['margin' + (axis === 'Height' ? 'Bottom' : 'Right')] : 0); + } + + function getWindowSizes() { + var body = document.body; + var html = document.documentElement; + var computedStyle = isIE(10) && getComputedStyle(html); + + return { + height: getSize('Height', body, html, computedStyle), + width: getSize('Width', body, html, computedStyle) + }; + } + + var classCallCheck = function (instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + }; + + var createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; + }(); + + + + + + var defineProperty = function (obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + + return obj; + }; + + var _extends = Object.assign || function (target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + + return target; + }; + + /** + * Given element offsets, generate an output similar to getBoundingClientRect + * @method + * @memberof Popper.Utils + * @argument {Object} offsets + * @returns {Object} ClientRect like output + */ + function getClientRect(offsets) { + return _extends({}, offsets, { + right: offsets.left + offsets.width, + bottom: offsets.top + offsets.height + }); + } + + /** + * Get bounding client rect of given element + * @method + * @memberof Popper.Utils + * @param {HTMLElement} element + * @return {Object} client rect + */ + function getBoundingClientRect(element) { + var rect = {}; + + // IE10 10 FIX: Please, don't ask, the element isn't + // considered in DOM in some circumstances... + // This isn't reproducible in IE10 compatibility mode of IE11 + try { + if (isIE(10)) { + rect = element.getBoundingClientRect(); + var scrollTop = getScroll(element, 'top'); + var scrollLeft = getScroll(element, 'left'); + rect.top += scrollTop; + rect.left += scrollLeft; + rect.bottom += scrollTop; + rect.right += scrollLeft; + } else { + rect = element.getBoundingClientRect(); + } + } catch (e) {} + + var result = { + left: rect.left, + top: rect.top, + width: rect.right - rect.left, + height: rect.bottom - rect.top + }; + + // subtract scrollbar size from sizes + var sizes = element.nodeName === 'HTML' ? getWindowSizes() : {}; + var width = sizes.width || element.clientWidth || result.right - result.left; + var height = sizes.height || element.clientHeight || result.bottom - result.top; + + var horizScrollbar = element.offsetWidth - width; + var vertScrollbar = element.offsetHeight - height; + + // if an hypothetical scrollbar is detected, we must be sure it's not a `border` + // we make this check conditional for performance reasons + if (horizScrollbar || vertScrollbar) { + var styles = getStyleComputedProperty(element); + horizScrollbar -= getBordersSize(styles, 'x'); + vertScrollbar -= getBordersSize(styles, 'y'); + + result.width -= horizScrollbar; + result.height -= vertScrollbar; + } + + return getClientRect(result); + } + + function getOffsetRectRelativeToArbitraryNode(children, parent) { + var fixedPosition = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + + var isIE10 = isIE(10); + var isHTML = parent.nodeName === 'HTML'; + var childrenRect = getBoundingClientRect(children); + var parentRect = getBoundingClientRect(parent); + var scrollParent = getScrollParent(children); + + var styles = getStyleComputedProperty(parent); + var borderTopWidth = parseFloat(styles.borderTopWidth, 10); + var borderLeftWidth = parseFloat(styles.borderLeftWidth, 10); + + // In cases where the parent is fixed, we must ignore negative scroll in offset calc + if (fixedPosition && parent.nodeName === 'HTML') { + parentRect.top = Math.max(parentRect.top, 0); + parentRect.left = Math.max(parentRect.left, 0); + } + var offsets = getClientRect({ + top: childrenRect.top - parentRect.top - borderTopWidth, + left: childrenRect.left - parentRect.left - borderLeftWidth, + width: childrenRect.width, + height: childrenRect.height + }); + offsets.marginTop = 0; + offsets.marginLeft = 0; + + // Subtract margins of documentElement in case it's being used as parent + // we do this only on HTML because it's the only element that behaves + // differently when margins are applied to it. The margins are included in + // the box of the documentElement, in the other cases not. + if (!isIE10 && isHTML) { + var marginTop = parseFloat(styles.marginTop, 10); + var marginLeft = parseFloat(styles.marginLeft, 10); + + offsets.top -= borderTopWidth - marginTop; + offsets.bottom -= borderTopWidth - marginTop; + offsets.left -= borderLeftWidth - marginLeft; + offsets.right -= borderLeftWidth - marginLeft; + + // Attach marginTop and marginLeft because in some circumstances we may need them + offsets.marginTop = marginTop; + offsets.marginLeft = marginLeft; + } + + if (isIE10 && !fixedPosition ? parent.contains(scrollParent) : parent === scrollParent && scrollParent.nodeName !== 'BODY') { + offsets = includeScroll(offsets, parent); + } + + return offsets; + } + + function getViewportOffsetRectRelativeToArtbitraryNode(element) { + var excludeScroll = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + var html = element.ownerDocument.documentElement; + var relativeOffset = getOffsetRectRelativeToArbitraryNode(element, html); + var width = Math.max(html.clientWidth, window.innerWidth || 0); + var height = Math.max(html.clientHeight, window.innerHeight || 0); + + var scrollTop = !excludeScroll ? getScroll(html) : 0; + var scrollLeft = !excludeScroll ? getScroll(html, 'left') : 0; + + var offset = { + top: scrollTop - relativeOffset.top + relativeOffset.marginTop, + left: scrollLeft - relativeOffset.left + relativeOffset.marginLeft, + width: width, + height: height + }; + + return getClientRect(offset); + } + + /** + * Check if the given element is fixed or is inside a fixed parent + * @method + * @memberof Popper.Utils + * @argument {Element} element + * @argument {Element} customContainer + * @returns {Boolean} answer to "isFixed?" + */ + function isFixed(element) { + var nodeName = element.nodeName; + if (nodeName === 'BODY' || nodeName === 'HTML') { + return false; + } + if (getStyleComputedProperty(element, 'position') === 'fixed') { + return true; + } + return isFixed(getParentNode(element)); + } + + /** + * Finds the first parent of an element that has a transformed property defined + * @method + * @memberof Popper.Utils + * @argument {Element} element + * @returns {Element} first transformed parent or documentElement + */ + + function getFixedPositionOffsetParent(element) { + // This check is needed to avoid errors in case one of the elements isn't defined for any reason + if (!element || !element.parentElement || isIE()) { + return document.documentElement; + } + var el = element.parentElement; + while (el && getStyleComputedProperty(el, 'transform') === 'none') { + el = el.parentElement; + } + return el || document.documentElement; + } + + /** + * Computed the boundaries limits and return them + * @method + * @memberof Popper.Utils + * @param {HTMLElement} popper + * @param {HTMLElement} reference + * @param {number} padding + * @param {HTMLElement} boundariesElement - Element used to define the boundaries + * @param {Boolean} fixedPosition - Is in fixed position mode + * @returns {Object} Coordinates of the boundaries + */ + function getBoundaries(popper, reference, padding, boundariesElement) { + var fixedPosition = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; + + // NOTE: 1 DOM access here + + var boundaries = { top: 0, left: 0 }; + var offsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, reference); + + // Handle viewport case + if (boundariesElement === 'viewport') { + boundaries = getViewportOffsetRectRelativeToArtbitraryNode(offsetParent, fixedPosition); + } else { + // Handle other cases based on DOM element used as boundaries + var boundariesNode = void 0; + if (boundariesElement === 'scrollParent') { + boundariesNode = getScrollParent(getParentNode(reference)); + if (boundariesNode.nodeName === 'BODY') { + boundariesNode = popper.ownerDocument.documentElement; + } + } else if (boundariesElement === 'window') { + boundariesNode = popper.ownerDocument.documentElement; + } else { + boundariesNode = boundariesElement; + } + + var offsets = getOffsetRectRelativeToArbitraryNode(boundariesNode, offsetParent, fixedPosition); + + // In case of HTML, we need a different computation + if (boundariesNode.nodeName === 'HTML' && !isFixed(offsetParent)) { + var _getWindowSizes = getWindowSizes(), + height = _getWindowSizes.height, + width = _getWindowSizes.width; + + boundaries.top += offsets.top - offsets.marginTop; + boundaries.bottom = height + offsets.top; + boundaries.left += offsets.left - offsets.marginLeft; + boundaries.right = width + offsets.left; + } else { + // for all the other DOM elements, this one is good + boundaries = offsets; + } + } + + // Add paddings + boundaries.left += padding; + boundaries.top += padding; + boundaries.right -= padding; + boundaries.bottom -= padding; + + return boundaries; + } + + function getArea(_ref) { + var width = _ref.width, + height = _ref.height; + + return width * height; + } + + /** + * Utility used to transform the `auto` placement to the placement with more + * available space. + * @method + * @memberof Popper.Utils + * @argument {Object} data - The data object generated by update method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function computeAutoPlacement(placement, refRect, popper, reference, boundariesElement) { + var padding = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0; + + if (placement.indexOf('auto') === -1) { + return placement; + } + + var boundaries = getBoundaries(popper, reference, padding, boundariesElement); + + var rects = { + top: { + width: boundaries.width, + height: refRect.top - boundaries.top + }, + right: { + width: boundaries.right - refRect.right, + height: boundaries.height + }, + bottom: { + width: boundaries.width, + height: boundaries.bottom - refRect.bottom + }, + left: { + width: refRect.left - boundaries.left, + height: boundaries.height + } + }; + + var sortedAreas = Object.keys(rects).map(function (key) { + return _extends({ + key: key + }, rects[key], { + area: getArea(rects[key]) + }); + }).sort(function (a, b) { + return b.area - a.area; + }); + + var filteredAreas = sortedAreas.filter(function (_ref2) { + var width = _ref2.width, + height = _ref2.height; + return width >= popper.clientWidth && height >= popper.clientHeight; + }); + + var computedPlacement = filteredAreas.length > 0 ? filteredAreas[0].key : sortedAreas[0].key; + + var variation = placement.split('-')[1]; + + return computedPlacement + (variation ? '-' + variation : ''); + } + + /** + * Get offsets to the reference element + * @method + * @memberof Popper.Utils + * @param {Object} state + * @param {Element} popper - the popper element + * @param {Element} reference - the reference element (the popper will be relative to this) + * @param {Element} fixedPosition - is in fixed position mode + * @returns {Object} An object containing the offsets which will be applied to the popper + */ + function getReferenceOffsets(state, popper, reference) { + var fixedPosition = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; + + var commonOffsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, reference); + return getOffsetRectRelativeToArbitraryNode(reference, commonOffsetParent, fixedPosition); + } + + /** + * Get the outer sizes of the given element (offset size + margins) + * @method + * @memberof Popper.Utils + * @argument {Element} element + * @returns {Object} object containing width and height properties + */ + function getOuterSizes(element) { + var styles = getComputedStyle(element); + var x = parseFloat(styles.marginTop) + parseFloat(styles.marginBottom); + var y = parseFloat(styles.marginLeft) + parseFloat(styles.marginRight); + var result = { + width: element.offsetWidth + y, + height: element.offsetHeight + x + }; + return result; + } + + /** + * Get the opposite placement of the given one + * @method + * @memberof Popper.Utils + * @argument {String} placement + * @returns {String} flipped placement + */ + function getOppositePlacement(placement) { + var hash = { left: 'right', right: 'left', bottom: 'top', top: 'bottom' }; + return placement.replace(/left|right|bottom|top/g, function (matched) { + return hash[matched]; + }); + } + + /** + * Get offsets to the popper + * @method + * @memberof Popper.Utils + * @param {Object} position - CSS position the Popper will get applied + * @param {HTMLElement} popper - the popper element + * @param {Object} referenceOffsets - the reference offsets (the popper will be relative to this) + * @param {String} placement - one of the valid placement options + * @returns {Object} popperOffsets - An object containing the offsets which will be applied to the popper + */ + function getPopperOffsets(popper, referenceOffsets, placement) { + placement = placement.split('-')[0]; + + // Get popper node sizes + var popperRect = getOuterSizes(popper); + + // Add position, width and height to our offsets object + var popperOffsets = { + width: popperRect.width, + height: popperRect.height + }; + + // depending by the popper placement we have to compute its offsets slightly differently + var isHoriz = ['right', 'left'].indexOf(placement) !== -1; + var mainSide = isHoriz ? 'top' : 'left'; + var secondarySide = isHoriz ? 'left' : 'top'; + var measurement = isHoriz ? 'height' : 'width'; + var secondaryMeasurement = !isHoriz ? 'height' : 'width'; + + popperOffsets[mainSide] = referenceOffsets[mainSide] + referenceOffsets[measurement] / 2 - popperRect[measurement] / 2; + if (placement === secondarySide) { + popperOffsets[secondarySide] = referenceOffsets[secondarySide] - popperRect[secondaryMeasurement]; + } else { + popperOffsets[secondarySide] = referenceOffsets[getOppositePlacement(secondarySide)]; + } + + return popperOffsets; + } + + /** + * Mimics the `find` method of Array + * @method + * @memberof Popper.Utils + * @argument {Array} arr + * @argument prop + * @argument value + * @returns index or -1 + */ + function find(arr, check) { + // use native find if supported + if (Array.prototype.find) { + return arr.find(check); + } + + // use `filter` to obtain the same behavior of `find` + return arr.filter(check)[0]; + } + + /** + * Return the index of the matching object + * @method + * @memberof Popper.Utils + * @argument {Array} arr + * @argument prop + * @argument value + * @returns index or -1 + */ + function findIndex(arr, prop, value) { + // use native findIndex if supported + if (Array.prototype.findIndex) { + return arr.findIndex(function (cur) { + return cur[prop] === value; + }); + } + + // use `find` + `indexOf` if `findIndex` isn't supported + var match = find(arr, function (obj) { + return obj[prop] === value; + }); + return arr.indexOf(match); + } + + /** + * Loop trough the list of modifiers and run them in order, + * each of them will then edit the data object. + * @method + * @memberof Popper.Utils + * @param {dataObject} data + * @param {Array} modifiers + * @param {String} ends - Optional modifier name used as stopper + * @returns {dataObject} + */ + function runModifiers(modifiers, data, ends) { + var modifiersToRun = ends === undefined ? modifiers : modifiers.slice(0, findIndex(modifiers, 'name', ends)); + + modifiersToRun.forEach(function (modifier) { + if (modifier['function']) { + // eslint-disable-line dot-notation + console.warn('`modifier.function` is deprecated, use `modifier.fn`!'); + } + var fn = modifier['function'] || modifier.fn; // eslint-disable-line dot-notation + if (modifier.enabled && isFunction(fn)) { + // Add properties to offsets to make them a complete clientRect object + // we do this before each modifier to make sure the previous one doesn't + // mess with these values + data.offsets.popper = getClientRect(data.offsets.popper); + data.offsets.reference = getClientRect(data.offsets.reference); + + data = fn(data, modifier); + } + }); + + return data; + } + + /** + * Updates the position of the popper, computing the new offsets and applying + * the new style.
+ * Prefer `scheduleUpdate` over `update` because of performance reasons. + * @method + * @memberof Popper + */ + function update() { + // if popper is destroyed, don't perform any further update + if (this.state.isDestroyed) { + return; + } + + var data = { + instance: this, + styles: {}, + arrowStyles: {}, + attributes: {}, + flipped: false, + offsets: {} + }; + + // compute reference element offsets + data.offsets.reference = getReferenceOffsets(this.state, this.popper, this.reference, this.options.positionFixed); + + // compute auto placement, store placement inside the data object, + // modifiers will be able to edit `placement` if needed + // and refer to originalPlacement to know the original value + data.placement = computeAutoPlacement(this.options.placement, data.offsets.reference, this.popper, this.reference, this.options.modifiers.flip.boundariesElement, this.options.modifiers.flip.padding); + + // store the computed placement inside `originalPlacement` + data.originalPlacement = data.placement; + + data.positionFixed = this.options.positionFixed; + + // compute the popper offsets + data.offsets.popper = getPopperOffsets(this.popper, data.offsets.reference, data.placement); + + data.offsets.popper.position = this.options.positionFixed ? 'fixed' : 'absolute'; + + // run the modifiers + data = runModifiers(this.modifiers, data); + + // the first `update` will call `onCreate` callback + // the other ones will call `onUpdate` callback + if (!this.state.isCreated) { + this.state.isCreated = true; + this.options.onCreate(data); + } else { + this.options.onUpdate(data); + } + } + + /** + * Helper used to know if the given modifier is enabled. + * @method + * @memberof Popper.Utils + * @returns {Boolean} + */ + function isModifierEnabled(modifiers, modifierName) { + return modifiers.some(function (_ref) { + var name = _ref.name, + enabled = _ref.enabled; + return enabled && name === modifierName; + }); + } + + /** + * Get the prefixed supported property name + * @method + * @memberof Popper.Utils + * @argument {String} property (camelCase) + * @returns {String} prefixed property (camelCase or PascalCase, depending on the vendor prefix) + */ + function getSupportedPropertyName(property) { + var prefixes = [false, 'ms', 'Webkit', 'Moz', 'O']; + var upperProp = property.charAt(0).toUpperCase() + property.slice(1); + + for (var i = 0; i < prefixes.length; i++) { + var prefix = prefixes[i]; + var toCheck = prefix ? '' + prefix + upperProp : property; + if (typeof document.body.style[toCheck] !== 'undefined') { + return toCheck; + } + } + return null; + } + + /** + * Destroy the popper + * @method + * @memberof Popper + */ + function destroy() { + this.state.isDestroyed = true; + + // touch DOM only if `applyStyle` modifier is enabled + if (isModifierEnabled(this.modifiers, 'applyStyle')) { + this.popper.removeAttribute('x-placement'); + this.popper.style.position = ''; + this.popper.style.top = ''; + this.popper.style.left = ''; + this.popper.style.right = ''; + this.popper.style.bottom = ''; + this.popper.style.willChange = ''; + this.popper.style[getSupportedPropertyName('transform')] = ''; + } + + this.disableEventListeners(); + + // remove the popper if user explicity asked for the deletion on destroy + // do not use `remove` because IE11 doesn't support it + if (this.options.removeOnDestroy) { + this.popper.parentNode.removeChild(this.popper); + } + return this; + } + + /** + * Get the window associated with the element + * @argument {Element} element + * @returns {Window} + */ + function getWindow(element) { + var ownerDocument = element.ownerDocument; + return ownerDocument ? ownerDocument.defaultView : window; + } + + function attachToScrollParents(scrollParent, event, callback, scrollParents) { + var isBody = scrollParent.nodeName === 'BODY'; + var target = isBody ? scrollParent.ownerDocument.defaultView : scrollParent; + target.addEventListener(event, callback, { passive: true }); + + if (!isBody) { + attachToScrollParents(getScrollParent(target.parentNode), event, callback, scrollParents); + } + scrollParents.push(target); + } + + /** + * Setup needed event listeners used to update the popper position + * @method + * @memberof Popper.Utils + * @private + */ + function setupEventListeners(reference, options, state, updateBound) { + // Resize event listener on window + state.updateBound = updateBound; + getWindow(reference).addEventListener('resize', state.updateBound, { passive: true }); + + // Scroll event listener on scroll parents + var scrollElement = getScrollParent(reference); + attachToScrollParents(scrollElement, 'scroll', state.updateBound, state.scrollParents); + state.scrollElement = scrollElement; + state.eventsEnabled = true; + + return state; + } + + /** + * It will add resize/scroll events and start recalculating + * position of the popper element when they are triggered. + * @method + * @memberof Popper + */ + function enableEventListeners() { + if (!this.state.eventsEnabled) { + this.state = setupEventListeners(this.reference, this.options, this.state, this.scheduleUpdate); + } + } + + /** + * Remove event listeners used to update the popper position + * @method + * @memberof Popper.Utils + * @private + */ + function removeEventListeners(reference, state) { + // Remove resize event listener on window + getWindow(reference).removeEventListener('resize', state.updateBound); + + // Remove scroll event listener on scroll parents + state.scrollParents.forEach(function (target) { + target.removeEventListener('scroll', state.updateBound); + }); + + // Reset state + state.updateBound = null; + state.scrollParents = []; + state.scrollElement = null; + state.eventsEnabled = false; + return state; + } + + /** + * It will remove resize/scroll events and won't recalculate popper position + * when they are triggered. It also won't trigger onUpdate callback anymore, + * unless you call `update` method manually. + * @method + * @memberof Popper + */ + function disableEventListeners() { + if (this.state.eventsEnabled) { + cancelAnimationFrame(this.scheduleUpdate); + this.state = removeEventListeners(this.reference, this.state); + } + } + + /** + * Tells if a given input is a number + * @method + * @memberof Popper.Utils + * @param {*} input to check + * @return {Boolean} + */ + function isNumeric(n) { + return n !== '' && !isNaN(parseFloat(n)) && isFinite(n); + } + + /** + * Set the style to the given popper + * @method + * @memberof Popper.Utils + * @argument {Element} element - Element to apply the style to + * @argument {Object} styles + * Object with a list of properties and values which will be applied to the element + */ + function setStyles(element, styles) { + Object.keys(styles).forEach(function (prop) { + var unit = ''; + // add unit if the value is numeric and is one of the following + if (['width', 'height', 'top', 'right', 'bottom', 'left'].indexOf(prop) !== -1 && isNumeric(styles[prop])) { + unit = 'px'; + } + element.style[prop] = styles[prop] + unit; + }); + } + + /** + * Set the attributes to the given popper + * @method + * @memberof Popper.Utils + * @argument {Element} element - Element to apply the attributes to + * @argument {Object} styles + * Object with a list of properties and values which will be applied to the element + */ + function setAttributes(element, attributes) { + Object.keys(attributes).forEach(function (prop) { + var value = attributes[prop]; + if (value !== false) { + element.setAttribute(prop, attributes[prop]); + } else { + element.removeAttribute(prop); + } + }); + } + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by `update` method + * @argument {Object} data.styles - List of style properties - values to apply to popper element + * @argument {Object} data.attributes - List of attribute properties - values to apply to popper element + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The same data object + */ + function applyStyle(data) { + // any property present in `data.styles` will be applied to the popper, + // in this way we can make the 3rd party modifiers add custom styles to it + // Be aware, modifiers could override the properties defined in the previous + // lines of this modifier! + setStyles(data.instance.popper, data.styles); + + // any property present in `data.attributes` will be applied to the popper, + // they will be set as HTML attributes of the element + setAttributes(data.instance.popper, data.attributes); + + // if arrowElement is defined and arrowStyles has some properties + if (data.arrowElement && Object.keys(data.arrowStyles).length) { + setStyles(data.arrowElement, data.arrowStyles); + } + + return data; + } + + /** + * Set the x-placement attribute before everything else because it could be used + * to add margins to the popper margins needs to be calculated to get the + * correct popper offsets. + * @method + * @memberof Popper.modifiers + * @param {HTMLElement} reference - The reference element used to position the popper + * @param {HTMLElement} popper - The HTML element used as popper + * @param {Object} options - Popper.js options + */ + function applyStyleOnLoad(reference, popper, options, modifierOptions, state) { + // compute reference element offsets + var referenceOffsets = getReferenceOffsets(state, popper, reference, options.positionFixed); + + // compute auto placement, store placement inside the data object, + // modifiers will be able to edit `placement` if needed + // and refer to originalPlacement to know the original value + var placement = computeAutoPlacement(options.placement, referenceOffsets, popper, reference, options.modifiers.flip.boundariesElement, options.modifiers.flip.padding); + + popper.setAttribute('x-placement', placement); + + // Apply `position` to popper before anything else because + // without the position applied we can't guarantee correct computations + setStyles(popper, { position: options.positionFixed ? 'fixed' : 'absolute' }); + + return options; + } + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by `update` method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function computeStyle(data, options) { + var x = options.x, + y = options.y; + var popper = data.offsets.popper; + + // Remove this legacy support in Popper.js v2 + + var legacyGpuAccelerationOption = find(data.instance.modifiers, function (modifier) { + return modifier.name === 'applyStyle'; + }).gpuAcceleration; + if (legacyGpuAccelerationOption !== undefined) { + console.warn('WARNING: `gpuAcceleration` option moved to `computeStyle` modifier and will not be supported in future versions of Popper.js!'); + } + var gpuAcceleration = legacyGpuAccelerationOption !== undefined ? legacyGpuAccelerationOption : options.gpuAcceleration; + + var offsetParent = getOffsetParent(data.instance.popper); + var offsetParentRect = getBoundingClientRect(offsetParent); + + // Styles + var styles = { + position: popper.position + }; + + // Avoid blurry text by using full pixel integers. + // For pixel-perfect positioning, top/bottom prefers rounded + // values, while left/right prefers floored values. + var offsets = { + left: Math.floor(popper.left), + top: Math.round(popper.top), + bottom: Math.round(popper.bottom), + right: Math.floor(popper.right) + }; + + var sideA = x === 'bottom' ? 'top' : 'bottom'; + var sideB = y === 'right' ? 'left' : 'right'; + + // if gpuAcceleration is set to `true` and transform is supported, + // we use `translate3d` to apply the position to the popper we + // automatically use the supported prefixed version if needed + var prefixedProperty = getSupportedPropertyName('transform'); + + // now, let's make a step back and look at this code closely (wtf?) + // If the content of the popper grows once it's been positioned, it + // may happen that the popper gets misplaced because of the new content + // overflowing its reference element + // To avoid this problem, we provide two options (x and y), which allow + // the consumer to define the offset origin. + // If we position a popper on top of a reference element, we can set + // `x` to `top` to make the popper grow towards its top instead of + // its bottom. + var left = void 0, + top = void 0; + if (sideA === 'bottom') { + top = -offsetParentRect.height + offsets.bottom; + } else { + top = offsets.top; + } + if (sideB === 'right') { + left = -offsetParentRect.width + offsets.right; + } else { + left = offsets.left; + } + if (gpuAcceleration && prefixedProperty) { + styles[prefixedProperty] = 'translate3d(' + left + 'px, ' + top + 'px, 0)'; + styles[sideA] = 0; + styles[sideB] = 0; + styles.willChange = 'transform'; + } else { + // othwerise, we use the standard `top`, `left`, `bottom` and `right` properties + var invertTop = sideA === 'bottom' ? -1 : 1; + var invertLeft = sideB === 'right' ? -1 : 1; + styles[sideA] = top * invertTop; + styles[sideB] = left * invertLeft; + styles.willChange = sideA + ', ' + sideB; + } + + // Attributes + var attributes = { + 'x-placement': data.placement + }; + + // Update `data` attributes, styles and arrowStyles + data.attributes = _extends({}, attributes, data.attributes); + data.styles = _extends({}, styles, data.styles); + data.arrowStyles = _extends({}, data.offsets.arrow, data.arrowStyles); + + return data; + } + + /** + * Helper used to know if the given modifier depends from another one.
+ * It checks if the needed modifier is listed and enabled. + * @method + * @memberof Popper.Utils + * @param {Array} modifiers - list of modifiers + * @param {String} requestingName - name of requesting modifier + * @param {String} requestedName - name of requested modifier + * @returns {Boolean} + */ + function isModifierRequired(modifiers, requestingName, requestedName) { + var requesting = find(modifiers, function (_ref) { + var name = _ref.name; + return name === requestingName; + }); + + var isRequired = !!requesting && modifiers.some(function (modifier) { + return modifier.name === requestedName && modifier.enabled && modifier.order < requesting.order; + }); + + if (!isRequired) { + var _requesting = '`' + requestingName + '`'; + var requested = '`' + requestedName + '`'; + console.warn(requested + ' modifier is required by ' + _requesting + ' modifier in order to work, be sure to include it before ' + _requesting + '!'); + } + return isRequired; + } + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by update method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function arrow(data, options) { + var _data$offsets$arrow; + + // arrow depends on keepTogether in order to work + if (!isModifierRequired(data.instance.modifiers, 'arrow', 'keepTogether')) { + return data; + } + + var arrowElement = options.element; + + // if arrowElement is a string, suppose it's a CSS selector + if (typeof arrowElement === 'string') { + arrowElement = data.instance.popper.querySelector(arrowElement); + + // if arrowElement is not found, don't run the modifier + if (!arrowElement) { + return data; + } + } else { + // if the arrowElement isn't a query selector we must check that the + // provided DOM node is child of its popper node + if (!data.instance.popper.contains(arrowElement)) { + console.warn('WARNING: `arrow.element` must be child of its popper element!'); + return data; + } + } + + var placement = data.placement.split('-')[0]; + var _data$offsets = data.offsets, + popper = _data$offsets.popper, + reference = _data$offsets.reference; + + var isVertical = ['left', 'right'].indexOf(placement) !== -1; + + var len = isVertical ? 'height' : 'width'; + var sideCapitalized = isVertical ? 'Top' : 'Left'; + var side = sideCapitalized.toLowerCase(); + var altSide = isVertical ? 'left' : 'top'; + var opSide = isVertical ? 'bottom' : 'right'; + var arrowElementSize = getOuterSizes(arrowElement)[len]; + + // + // extends keepTogether behavior making sure the popper and its + // reference have enough pixels in conjuction + // + + // top/left side + if (reference[opSide] - arrowElementSize < popper[side]) { + data.offsets.popper[side] -= popper[side] - (reference[opSide] - arrowElementSize); + } + // bottom/right side + if (reference[side] + arrowElementSize > popper[opSide]) { + data.offsets.popper[side] += reference[side] + arrowElementSize - popper[opSide]; + } + data.offsets.popper = getClientRect(data.offsets.popper); + + // compute center of the popper + var center = reference[side] + reference[len] / 2 - arrowElementSize / 2; + + // Compute the sideValue using the updated popper offsets + // take popper margin in account because we don't have this info available + var css = getStyleComputedProperty(data.instance.popper); + var popperMarginSide = parseFloat(css['margin' + sideCapitalized], 10); + var popperBorderSide = parseFloat(css['border' + sideCapitalized + 'Width'], 10); + var sideValue = center - data.offsets.popper[side] - popperMarginSide - popperBorderSide; + + // prevent arrowElement from being placed not contiguously to its popper + sideValue = Math.max(Math.min(popper[len] - arrowElementSize, sideValue), 0); + + data.arrowElement = arrowElement; + data.offsets.arrow = (_data$offsets$arrow = {}, defineProperty(_data$offsets$arrow, side, Math.round(sideValue)), defineProperty(_data$offsets$arrow, altSide, ''), _data$offsets$arrow); + + return data; + } + + /** + * Get the opposite placement variation of the given one + * @method + * @memberof Popper.Utils + * @argument {String} placement variation + * @returns {String} flipped placement variation + */ + function getOppositeVariation(variation) { + if (variation === 'end') { + return 'start'; + } else if (variation === 'start') { + return 'end'; + } + return variation; + } + + /** + * List of accepted placements to use as values of the `placement` option.
+ * Valid placements are: + * - `auto` + * - `top` + * - `right` + * - `bottom` + * - `left` + * + * Each placement can have a variation from this list: + * - `-start` + * - `-end` + * + * Variations are interpreted easily if you think of them as the left to right + * written languages. Horizontally (`top` and `bottom`), `start` is left and `end` + * is right.
+ * Vertically (`left` and `right`), `start` is top and `end` is bottom. + * + * Some valid examples are: + * - `top-end` (on top of reference, right aligned) + * - `right-start` (on right of reference, top aligned) + * - `bottom` (on bottom, centered) + * - `auto-right` (on the side with more space available, alignment depends by placement) + * + * @static + * @type {Array} + * @enum {String} + * @readonly + * @method placements + * @memberof Popper + */ + var placements = ['auto-start', 'auto', 'auto-end', 'top-start', 'top', 'top-end', 'right-start', 'right', 'right-end', 'bottom-end', 'bottom', 'bottom-start', 'left-end', 'left', 'left-start']; + + // Get rid of `auto` `auto-start` and `auto-end` + var validPlacements = placements.slice(3); + + /** + * Given an initial placement, returns all the subsequent placements + * clockwise (or counter-clockwise). + * + * @method + * @memberof Popper.Utils + * @argument {String} placement - A valid placement (it accepts variations) + * @argument {Boolean} counter - Set to true to walk the placements counterclockwise + * @returns {Array} placements including their variations + */ + function clockwise(placement) { + var counter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + var index = validPlacements.indexOf(placement); + var arr = validPlacements.slice(index + 1).concat(validPlacements.slice(0, index)); + return counter ? arr.reverse() : arr; + } + + var BEHAVIORS = { + FLIP: 'flip', + CLOCKWISE: 'clockwise', + COUNTERCLOCKWISE: 'counterclockwise' + }; + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by update method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function flip(data, options) { + // if `inner` modifier is enabled, we can't use the `flip` modifier + if (isModifierEnabled(data.instance.modifiers, 'inner')) { + return data; + } + + if (data.flipped && data.placement === data.originalPlacement) { + // seems like flip is trying to loop, probably there's not enough space on any of the flippable sides + return data; + } + + var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, options.boundariesElement, data.positionFixed); + + var placement = data.placement.split('-')[0]; + var placementOpposite = getOppositePlacement(placement); + var variation = data.placement.split('-')[1] || ''; + + var flipOrder = []; + + switch (options.behavior) { + case BEHAVIORS.FLIP: + flipOrder = [placement, placementOpposite]; + break; + case BEHAVIORS.CLOCKWISE: + flipOrder = clockwise(placement); + break; + case BEHAVIORS.COUNTERCLOCKWISE: + flipOrder = clockwise(placement, true); + break; + default: + flipOrder = options.behavior; + } + + flipOrder.forEach(function (step, index) { + if (placement !== step || flipOrder.length === index + 1) { + return data; + } + + placement = data.placement.split('-')[0]; + placementOpposite = getOppositePlacement(placement); + + var popperOffsets = data.offsets.popper; + var refOffsets = data.offsets.reference; + + // using floor because the reference offsets may contain decimals we are not going to consider here + var floor = Math.floor; + var overlapsRef = placement === 'left' && floor(popperOffsets.right) > floor(refOffsets.left) || placement === 'right' && floor(popperOffsets.left) < floor(refOffsets.right) || placement === 'top' && floor(popperOffsets.bottom) > floor(refOffsets.top) || placement === 'bottom' && floor(popperOffsets.top) < floor(refOffsets.bottom); + + var overflowsLeft = floor(popperOffsets.left) < floor(boundaries.left); + var overflowsRight = floor(popperOffsets.right) > floor(boundaries.right); + var overflowsTop = floor(popperOffsets.top) < floor(boundaries.top); + var overflowsBottom = floor(popperOffsets.bottom) > floor(boundaries.bottom); + + var overflowsBoundaries = placement === 'left' && overflowsLeft || placement === 'right' && overflowsRight || placement === 'top' && overflowsTop || placement === 'bottom' && overflowsBottom; + + // flip the variation if required + var isVertical = ['top', 'bottom'].indexOf(placement) !== -1; + var flippedVariation = !!options.flipVariations && (isVertical && variation === 'start' && overflowsLeft || isVertical && variation === 'end' && overflowsRight || !isVertical && variation === 'start' && overflowsTop || !isVertical && variation === 'end' && overflowsBottom); + + if (overlapsRef || overflowsBoundaries || flippedVariation) { + // this boolean to detect any flip loop + data.flipped = true; + + if (overlapsRef || overflowsBoundaries) { + placement = flipOrder[index + 1]; + } + + if (flippedVariation) { + variation = getOppositeVariation(variation); + } + + data.placement = placement + (variation ? '-' + variation : ''); + + // this object contains `position`, we want to preserve it along with + // any additional property we may add in the future + data.offsets.popper = _extends({}, data.offsets.popper, getPopperOffsets(data.instance.popper, data.offsets.reference, data.placement)); + + data = runModifiers(data.instance.modifiers, data, 'flip'); + } + }); + return data; + } + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by update method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function keepTogether(data) { + var _data$offsets = data.offsets, + popper = _data$offsets.popper, + reference = _data$offsets.reference; + + var placement = data.placement.split('-')[0]; + var floor = Math.floor; + var isVertical = ['top', 'bottom'].indexOf(placement) !== -1; + var side = isVertical ? 'right' : 'bottom'; + var opSide = isVertical ? 'left' : 'top'; + var measurement = isVertical ? 'width' : 'height'; + + if (popper[side] < floor(reference[opSide])) { + data.offsets.popper[opSide] = floor(reference[opSide]) - popper[measurement]; + } + if (popper[opSide] > floor(reference[side])) { + data.offsets.popper[opSide] = floor(reference[side]); + } + + return data; + } + + /** + * Converts a string containing value + unit into a px value number + * @function + * @memberof {modifiers~offset} + * @private + * @argument {String} str - Value + unit string + * @argument {String} measurement - `height` or `width` + * @argument {Object} popperOffsets + * @argument {Object} referenceOffsets + * @returns {Number|String} + * Value in pixels, or original string if no values were extracted + */ + function toValue(str, measurement, popperOffsets, referenceOffsets) { + // separate value from unit + var split = str.match(/((?:\-|\+)?\d*\.?\d*)(.*)/); + var value = +split[1]; + var unit = split[2]; + + // If it's not a number it's an operator, I guess + if (!value) { + return str; + } + + if (unit.indexOf('%') === 0) { + var element = void 0; + switch (unit) { + case '%p': + element = popperOffsets; + break; + case '%': + case '%r': + default: + element = referenceOffsets; + } + + var rect = getClientRect(element); + return rect[measurement] / 100 * value; + } else if (unit === 'vh' || unit === 'vw') { + // if is a vh or vw, we calculate the size based on the viewport + var size = void 0; + if (unit === 'vh') { + size = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); + } else { + size = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); + } + return size / 100 * value; + } else { + // if is an explicit pixel unit, we get rid of the unit and keep the value + // if is an implicit unit, it's px, and we return just the value + return value; + } + } + + /** + * Parse an `offset` string to extrapolate `x` and `y` numeric offsets. + * @function + * @memberof {modifiers~offset} + * @private + * @argument {String} offset + * @argument {Object} popperOffsets + * @argument {Object} referenceOffsets + * @argument {String} basePlacement + * @returns {Array} a two cells array with x and y offsets in numbers + */ + function parseOffset(offset, popperOffsets, referenceOffsets, basePlacement) { + var offsets = [0, 0]; + + // Use height if placement is left or right and index is 0 otherwise use width + // in this way the first offset will use an axis and the second one + // will use the other one + var useHeight = ['right', 'left'].indexOf(basePlacement) !== -1; + + // Split the offset string to obtain a list of values and operands + // The regex addresses values with the plus or minus sign in front (+10, -20, etc) + var fragments = offset.split(/(\+|\-)/).map(function (frag) { + return frag.trim(); + }); + + // Detect if the offset string contains a pair of values or a single one + // they could be separated by comma or space + var divider = fragments.indexOf(find(fragments, function (frag) { + return frag.search(/,|\s/) !== -1; + })); + + if (fragments[divider] && fragments[divider].indexOf(',') === -1) { + console.warn('Offsets separated by white space(s) are deprecated, use a comma (,) instead.'); + } + + // If divider is found, we divide the list of values and operands to divide + // them by ofset X and Y. + var splitRegex = /\s*,\s*|\s+/; + var ops = divider !== -1 ? [fragments.slice(0, divider).concat([fragments[divider].split(splitRegex)[0]]), [fragments[divider].split(splitRegex)[1]].concat(fragments.slice(divider + 1))] : [fragments]; + + // Convert the values with units to absolute pixels to allow our computations + ops = ops.map(function (op, index) { + // Most of the units rely on the orientation of the popper + var measurement = (index === 1 ? !useHeight : useHeight) ? 'height' : 'width'; + var mergeWithPrevious = false; + return op + // This aggregates any `+` or `-` sign that aren't considered operators + // e.g.: 10 + +5 => [10, +, +5] + .reduce(function (a, b) { + if (a[a.length - 1] === '' && ['+', '-'].indexOf(b) !== -1) { + a[a.length - 1] = b; + mergeWithPrevious = true; + return a; + } else if (mergeWithPrevious) { + a[a.length - 1] += b; + mergeWithPrevious = false; + return a; + } else { + return a.concat(b); + } + }, []) + // Here we convert the string values into number values (in px) + .map(function (str) { + return toValue(str, measurement, popperOffsets, referenceOffsets); + }); + }); + + // Loop trough the offsets arrays and execute the operations + ops.forEach(function (op, index) { + op.forEach(function (frag, index2) { + if (isNumeric(frag)) { + offsets[index] += frag * (op[index2 - 1] === '-' ? -1 : 1); + } + }); + }); + return offsets; + } + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by update method + * @argument {Object} options - Modifiers configuration and options + * @argument {Number|String} options.offset=0 + * The offset value as described in the modifier description + * @returns {Object} The data object, properly modified + */ + function offset(data, _ref) { + var offset = _ref.offset; + var placement = data.placement, + _data$offsets = data.offsets, + popper = _data$offsets.popper, + reference = _data$offsets.reference; + + var basePlacement = placement.split('-')[0]; + + var offsets = void 0; + if (isNumeric(+offset)) { + offsets = [+offset, 0]; + } else { + offsets = parseOffset(offset, popper, reference, basePlacement); + } + + if (basePlacement === 'left') { + popper.top += offsets[0]; + popper.left -= offsets[1]; + } else if (basePlacement === 'right') { + popper.top += offsets[0]; + popper.left += offsets[1]; + } else if (basePlacement === 'top') { + popper.left += offsets[0]; + popper.top -= offsets[1]; + } else if (basePlacement === 'bottom') { + popper.left += offsets[0]; + popper.top += offsets[1]; + } + + data.popper = popper; + return data; + } + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by `update` method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function preventOverflow(data, options) { + var boundariesElement = options.boundariesElement || getOffsetParent(data.instance.popper); + + // If offsetParent is the reference element, we really want to + // go one step up and use the next offsetParent as reference to + // avoid to make this modifier completely useless and look like broken + if (data.instance.reference === boundariesElement) { + boundariesElement = getOffsetParent(boundariesElement); + } + + // NOTE: DOM access here + // resets the popper's position so that the document size can be calculated excluding + // the size of the popper element itself + var transformProp = getSupportedPropertyName('transform'); + var popperStyles = data.instance.popper.style; // assignment to help minification + var top = popperStyles.top, + left = popperStyles.left, + transform = popperStyles[transformProp]; + + popperStyles.top = ''; + popperStyles.left = ''; + popperStyles[transformProp] = ''; + + var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, boundariesElement, data.positionFixed); + + // NOTE: DOM access here + // restores the original style properties after the offsets have been computed + popperStyles.top = top; + popperStyles.left = left; + popperStyles[transformProp] = transform; + + options.boundaries = boundaries; + + var order = options.priority; + var popper = data.offsets.popper; + + var check = { + primary: function primary(placement) { + var value = popper[placement]; + if (popper[placement] < boundaries[placement] && !options.escapeWithReference) { + value = Math.max(popper[placement], boundaries[placement]); + } + return defineProperty({}, placement, value); + }, + secondary: function secondary(placement) { + var mainSide = placement === 'right' ? 'left' : 'top'; + var value = popper[mainSide]; + if (popper[placement] > boundaries[placement] && !options.escapeWithReference) { + value = Math.min(popper[mainSide], boundaries[placement] - (placement === 'right' ? popper.width : popper.height)); + } + return defineProperty({}, mainSide, value); + } + }; + + order.forEach(function (placement) { + var side = ['left', 'top'].indexOf(placement) !== -1 ? 'primary' : 'secondary'; + popper = _extends({}, popper, check[side](placement)); + }); + + data.offsets.popper = popper; + + return data; + } + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by `update` method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function shift(data) { + var placement = data.placement; + var basePlacement = placement.split('-')[0]; + var shiftvariation = placement.split('-')[1]; + + // if shift shiftvariation is specified, run the modifier + if (shiftvariation) { + var _data$offsets = data.offsets, + reference = _data$offsets.reference, + popper = _data$offsets.popper; + + var isVertical = ['bottom', 'top'].indexOf(basePlacement) !== -1; + var side = isVertical ? 'left' : 'top'; + var measurement = isVertical ? 'width' : 'height'; + + var shiftOffsets = { + start: defineProperty({}, side, reference[side]), + end: defineProperty({}, side, reference[side] + reference[measurement] - popper[measurement]) + }; + + data.offsets.popper = _extends({}, popper, shiftOffsets[shiftvariation]); + } + + return data; + } + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by update method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function hide(data) { + if (!isModifierRequired(data.instance.modifiers, 'hide', 'preventOverflow')) { + return data; + } + + var refRect = data.offsets.reference; + var bound = find(data.instance.modifiers, function (modifier) { + return modifier.name === 'preventOverflow'; + }).boundaries; + + if (refRect.bottom < bound.top || refRect.left > bound.right || refRect.top > bound.bottom || refRect.right < bound.left) { + // Avoid unnecessary DOM access if visibility hasn't changed + if (data.hide === true) { + return data; + } + + data.hide = true; + data.attributes['x-out-of-boundaries'] = ''; + } else { + // Avoid unnecessary DOM access if visibility hasn't changed + if (data.hide === false) { + return data; + } + + data.hide = false; + data.attributes['x-out-of-boundaries'] = false; + } + + return data; + } + + /** + * @function + * @memberof Modifiers + * @argument {Object} data - The data object generated by `update` method + * @argument {Object} options - Modifiers configuration and options + * @returns {Object} The data object, properly modified + */ + function inner(data) { + var placement = data.placement; + var basePlacement = placement.split('-')[0]; + var _data$offsets = data.offsets, + popper = _data$offsets.popper, + reference = _data$offsets.reference; + + var isHoriz = ['left', 'right'].indexOf(basePlacement) !== -1; + + var subtractLength = ['top', 'left'].indexOf(basePlacement) === -1; + + popper[isHoriz ? 'left' : 'top'] = reference[basePlacement] - (subtractLength ? popper[isHoriz ? 'width' : 'height'] : 0); + + data.placement = getOppositePlacement(placement); + data.offsets.popper = getClientRect(popper); + + return data; + } + + /** + * Modifier function, each modifier can have a function of this type assigned + * to its `fn` property.
+ * These functions will be called on each update, this means that you must + * make sure they are performant enough to avoid performance bottlenecks. + * + * @function ModifierFn + * @argument {dataObject} data - The data object generated by `update` method + * @argument {Object} options - Modifiers configuration and options + * @returns {dataObject} The data object, properly modified + */ + + /** + * Modifiers are plugins used to alter the behavior of your poppers.
+ * Popper.js uses a set of 9 modifiers to provide all the basic functionalities + * needed by the library. + * + * Usually you don't want to override the `order`, `fn` and `onLoad` props. + * All the other properties are configurations that could be tweaked. + * @namespace modifiers + */ + var modifiers = { + /** + * Modifier used to shift the popper on the start or end of its reference + * element.
+ * It will read the variation of the `placement` property.
+ * It can be one either `-end` or `-start`. + * @memberof modifiers + * @inner + */ + shift: { + /** @prop {number} order=100 - Index used to define the order of execution */ + order: 100, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: shift + }, + + /** + * The `offset` modifier can shift your popper on both its axis. + * + * It accepts the following units: + * - `px` or unitless, interpreted as pixels + * - `%` or `%r`, percentage relative to the length of the reference element + * - `%p`, percentage relative to the length of the popper element + * - `vw`, CSS viewport width unit + * - `vh`, CSS viewport height unit + * + * For length is intended the main axis relative to the placement of the popper.
+ * This means that if the placement is `top` or `bottom`, the length will be the + * `width`. In case of `left` or `right`, it will be the height. + * + * You can provide a single value (as `Number` or `String`), or a pair of values + * as `String` divided by a comma or one (or more) white spaces.
+ * The latter is a deprecated method because it leads to confusion and will be + * removed in v2.
+ * Additionally, it accepts additions and subtractions between different units. + * Note that multiplications and divisions aren't supported. + * + * Valid examples are: + * ``` + * 10 + * '10%' + * '10, 10' + * '10%, 10' + * '10 + 10%' + * '10 - 5vh + 3%' + * '-10px + 5vh, 5px - 6%' + * ``` + * > **NB**: If you desire to apply offsets to your poppers in a way that may make them overlap + * > with their reference element, unfortunately, you will have to disable the `flip` modifier. + * > More on this [reading this issue](https://github.com/FezVrasta/popper.js/issues/373) + * + * @memberof modifiers + * @inner + */ + offset: { + /** @prop {number} order=200 - Index used to define the order of execution */ + order: 200, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: offset, + /** @prop {Number|String} offset=0 + * The offset value as described in the modifier description + */ + offset: 0 + }, + + /** + * Modifier used to prevent the popper from being positioned outside the boundary. + * + * An scenario exists where the reference itself is not within the boundaries.
+ * We can say it has "escaped the boundaries" — or just "escaped".
+ * In this case we need to decide whether the popper should either: + * + * - detach from the reference and remain "trapped" in the boundaries, or + * - if it should ignore the boundary and "escape with its reference" + * + * When `escapeWithReference` is set to`true` and reference is completely + * outside its boundaries, the popper will overflow (or completely leave) + * the boundaries in order to remain attached to the edge of the reference. + * + * @memberof modifiers + * @inner + */ + preventOverflow: { + /** @prop {number} order=300 - Index used to define the order of execution */ + order: 300, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: preventOverflow, + /** + * @prop {Array} [priority=['left','right','top','bottom']] + * Popper will try to prevent overflow following these priorities by default, + * then, it could overflow on the left and on top of the `boundariesElement` + */ + priority: ['left', 'right', 'top', 'bottom'], + /** + * @prop {number} padding=5 + * Amount of pixel used to define a minimum distance between the boundaries + * and the popper this makes sure the popper has always a little padding + * between the edges of its container + */ + padding: 5, + /** + * @prop {String|HTMLElement} boundariesElement='scrollParent' + * Boundaries used by the modifier, can be `scrollParent`, `window`, + * `viewport` or any DOM element. + */ + boundariesElement: 'scrollParent' + }, + + /** + * Modifier used to make sure the reference and its popper stay near eachothers + * without leaving any gap between the two. Expecially useful when the arrow is + * enabled and you want to assure it to point to its reference element. + * It cares only about the first axis, you can still have poppers with margin + * between the popper and its reference element. + * @memberof modifiers + * @inner + */ + keepTogether: { + /** @prop {number} order=400 - Index used to define the order of execution */ + order: 400, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: keepTogether + }, + + /** + * This modifier is used to move the `arrowElement` of the popper to make + * sure it is positioned between the reference element and its popper element. + * It will read the outer size of the `arrowElement` node to detect how many + * pixels of conjuction are needed. + * + * It has no effect if no `arrowElement` is provided. + * @memberof modifiers + * @inner + */ + arrow: { + /** @prop {number} order=500 - Index used to define the order of execution */ + order: 500, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: arrow, + /** @prop {String|HTMLElement} element='[x-arrow]' - Selector or node used as arrow */ + element: '[x-arrow]' + }, + + /** + * Modifier used to flip the popper's placement when it starts to overlap its + * reference element. + * + * Requires the `preventOverflow` modifier before it in order to work. + * + * **NOTE:** this modifier will interrupt the current update cycle and will + * restart it if it detects the need to flip the placement. + * @memberof modifiers + * @inner + */ + flip: { + /** @prop {number} order=600 - Index used to define the order of execution */ + order: 600, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: flip, + /** + * @prop {String|Array} behavior='flip' + * The behavior used to change the popper's placement. It can be one of + * `flip`, `clockwise`, `counterclockwise` or an array with a list of valid + * placements (with optional variations). + */ + behavior: 'flip', + /** + * @prop {number} padding=5 + * The popper will flip if it hits the edges of the `boundariesElement` + */ + padding: 5, + /** + * @prop {String|HTMLElement} boundariesElement='viewport' + * The element which will define the boundaries of the popper position, + * the popper will never be placed outside of the defined boundaries + * (except if keepTogether is enabled) + */ + boundariesElement: 'viewport' + }, + + /** + * Modifier used to make the popper flow toward the inner of the reference element. + * By default, when this modifier is disabled, the popper will be placed outside + * the reference element. + * @memberof modifiers + * @inner + */ + inner: { + /** @prop {number} order=700 - Index used to define the order of execution */ + order: 700, + /** @prop {Boolean} enabled=false - Whether the modifier is enabled or not */ + enabled: false, + /** @prop {ModifierFn} */ + fn: inner + }, + + /** + * Modifier used to hide the popper when its reference element is outside of the + * popper boundaries. It will set a `x-out-of-boundaries` attribute which can + * be used to hide with a CSS selector the popper when its reference is + * out of boundaries. + * + * Requires the `preventOverflow` modifier before it in order to work. + * @memberof modifiers + * @inner + */ + hide: { + /** @prop {number} order=800 - Index used to define the order of execution */ + order: 800, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: hide + }, + + /** + * Computes the style that will be applied to the popper element to gets + * properly positioned. + * + * Note that this modifier will not touch the DOM, it just prepares the styles + * so that `applyStyle` modifier can apply it. This separation is useful + * in case you need to replace `applyStyle` with a custom implementation. + * + * This modifier has `850` as `order` value to maintain backward compatibility + * with previous versions of Popper.js. Expect the modifiers ordering method + * to change in future major versions of the library. + * + * @memberof modifiers + * @inner + */ + computeStyle: { + /** @prop {number} order=850 - Index used to define the order of execution */ + order: 850, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: computeStyle, + /** + * @prop {Boolean} gpuAcceleration=true + * If true, it uses the CSS 3d transformation to position the popper. + * Otherwise, it will use the `top` and `left` properties. + */ + gpuAcceleration: true, + /** + * @prop {string} [x='bottom'] + * Where to anchor the X axis (`bottom` or `top`). AKA X offset origin. + * Change this if your popper should grow in a direction different from `bottom` + */ + x: 'bottom', + /** + * @prop {string} [x='left'] + * Where to anchor the Y axis (`left` or `right`). AKA Y offset origin. + * Change this if your popper should grow in a direction different from `right` + */ + y: 'right' + }, + + /** + * Applies the computed styles to the popper element. + * + * All the DOM manipulations are limited to this modifier. This is useful in case + * you want to integrate Popper.js inside a framework or view library and you + * want to delegate all the DOM manipulations to it. + * + * Note that if you disable this modifier, you must make sure the popper element + * has its position set to `absolute` before Popper.js can do its work! + * + * Just disable this modifier and define you own to achieve the desired effect. + * + * @memberof modifiers + * @inner + */ + applyStyle: { + /** @prop {number} order=900 - Index used to define the order of execution */ + order: 900, + /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ + enabled: true, + /** @prop {ModifierFn} */ + fn: applyStyle, + /** @prop {Function} */ + onLoad: applyStyleOnLoad, + /** + * @deprecated since version 1.10.0, the property moved to `computeStyle` modifier + * @prop {Boolean} gpuAcceleration=true + * If true, it uses the CSS 3d transformation to position the popper. + * Otherwise, it will use the `top` and `left` properties. + */ + gpuAcceleration: undefined + } + }; + + /** + * The `dataObject` is an object containing all the informations used by Popper.js + * this object get passed to modifiers and to the `onCreate` and `onUpdate` callbacks. + * @name dataObject + * @property {Object} data.instance The Popper.js instance + * @property {String} data.placement Placement applied to popper + * @property {String} data.originalPlacement Placement originally defined on init + * @property {Boolean} data.flipped True if popper has been flipped by flip modifier + * @property {Boolean} data.hide True if the reference element is out of boundaries, useful to know when to hide the popper. + * @property {HTMLElement} data.arrowElement Node used as arrow by arrow modifier + * @property {Object} data.styles Any CSS property defined here will be applied to the popper, it expects the JavaScript nomenclature (eg. `marginBottom`) + * @property {Object} data.arrowStyles Any CSS property defined here will be applied to the popper arrow, it expects the JavaScript nomenclature (eg. `marginBottom`) + * @property {Object} data.boundaries Offsets of the popper boundaries + * @property {Object} data.offsets The measurements of popper, reference and arrow elements. + * @property {Object} data.offsets.popper `top`, `left`, `width`, `height` values + * @property {Object} data.offsets.reference `top`, `left`, `width`, `height` values + * @property {Object} data.offsets.arrow] `top` and `left` offsets, only one of them will be different from 0 + */ + + /** + * Default options provided to Popper.js constructor.
+ * These can be overriden using the `options` argument of Popper.js.
+ * To override an option, simply pass as 3rd argument an object with the same + * structure of this object, example: + * ``` + * new Popper(ref, pop, { + * modifiers: { + * preventOverflow: { enabled: false } + * } + * }) + * ``` + * @type {Object} + * @static + * @memberof Popper + */ + var Defaults = { + /** + * Popper's placement + * @prop {Popper.placements} placement='bottom' + */ + placement: 'bottom', + + /** + * Set this to true if you want popper to position it self in 'fixed' mode + * @prop {Boolean} positionFixed=false + */ + positionFixed: false, + + /** + * Whether events (resize, scroll) are initially enabled + * @prop {Boolean} eventsEnabled=true + */ + eventsEnabled: true, + + /** + * Set to true if you want to automatically remove the popper when + * you call the `destroy` method. + * @prop {Boolean} removeOnDestroy=false + */ + removeOnDestroy: false, + + /** + * Callback called when the popper is created.
+ * By default, is set to no-op.
+ * Access Popper.js instance with `data.instance`. + * @prop {onCreate} + */ + onCreate: function onCreate() {}, + + /** + * Callback called when the popper is updated, this callback is not called + * on the initialization/creation of the popper, but only on subsequent + * updates.
+ * By default, is set to no-op.
+ * Access Popper.js instance with `data.instance`. + * @prop {onUpdate} + */ + onUpdate: function onUpdate() {}, + + /** + * List of modifiers used to modify the offsets before they are applied to the popper. + * They provide most of the functionalities of Popper.js + * @prop {modifiers} + */ + modifiers: modifiers + }; + + /** + * @callback onCreate + * @param {dataObject} data + */ + + /** + * @callback onUpdate + * @param {dataObject} data + */ + + // Utils + // Methods + var Popper = function () { + /** + * Create a new Popper.js instance + * @class Popper + * @param {HTMLElement|referenceObject} reference - The reference element used to position the popper + * @param {HTMLElement} popper - The HTML element used as popper. + * @param {Object} options - Your custom options to override the ones defined in [Defaults](#defaults) + * @return {Object} instance - The generated Popper.js instance + */ + function Popper(reference, popper) { + var _this = this; + + var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + classCallCheck(this, Popper); + + this.scheduleUpdate = function () { + return requestAnimationFrame(_this.update); + }; + + // make update() debounced, so that it only runs at most once-per-tick + this.update = debounce(this.update.bind(this)); + + // with {} we create a new object with the options inside it + this.options = _extends({}, Popper.Defaults, options); + + // init state + this.state = { + isDestroyed: false, + isCreated: false, + scrollParents: [] + }; + + // get reference and popper elements (allow jQuery wrappers) + this.reference = reference && reference.jquery ? reference[0] : reference; + this.popper = popper && popper.jquery ? popper[0] : popper; + + // Deep merge modifiers options + this.options.modifiers = {}; + Object.keys(_extends({}, Popper.Defaults.modifiers, options.modifiers)).forEach(function (name) { + _this.options.modifiers[name] = _extends({}, Popper.Defaults.modifiers[name] || {}, options.modifiers ? options.modifiers[name] : {}); + }); + + // Refactoring modifiers' list (Object => Array) + this.modifiers = Object.keys(this.options.modifiers).map(function (name) { + return _extends({ + name: name + }, _this.options.modifiers[name]); + }) + // sort the modifiers by order + .sort(function (a, b) { + return a.order - b.order; + }); + + // modifiers have the ability to execute arbitrary code when Popper.js get inited + // such code is executed in the same order of its modifier + // they could add new properties to their options configuration + // BE AWARE: don't add options to `options.modifiers.name` but to `modifierOptions`! + this.modifiers.forEach(function (modifierOptions) { + if (modifierOptions.enabled && isFunction(modifierOptions.onLoad)) { + modifierOptions.onLoad(_this.reference, _this.popper, _this.options, modifierOptions, _this.state); + } + }); + + // fire the first update to position the popper in the right place + this.update(); + + var eventsEnabled = this.options.eventsEnabled; + if (eventsEnabled) { + // setup event listeners, they will take care of update the position in specific situations + this.enableEventListeners(); + } + + this.state.eventsEnabled = eventsEnabled; + } + + // We can't use class properties because they don't get listed in the + // class prototype and break stuff like Sinon stubs + + + createClass(Popper, [{ + key: 'update', + value: function update$$1() { + return update.call(this); + } + }, { + key: 'destroy', + value: function destroy$$1() { + return destroy.call(this); + } + }, { + key: 'enableEventListeners', + value: function enableEventListeners$$1() { + return enableEventListeners.call(this); + } + }, { + key: 'disableEventListeners', + value: function disableEventListeners$$1() { + return disableEventListeners.call(this); + } + + /** + * Schedule an update, it will run on the next UI update available + * @method scheduleUpdate + * @memberof Popper + */ + + + /** + * Collection of utilities useful when writing custom modifiers. + * Starting from version 1.7, this method is available only if you + * include `popper-utils.js` before `popper.js`. + * + * **DEPRECATION**: This way to access PopperUtils is deprecated + * and will be removed in v2! Use the PopperUtils module directly instead. + * Due to the high instability of the methods contained in Utils, we can't + * guarantee them to follow semver. Use them at your own risk! + * @static + * @private + * @type {Object} + * @deprecated since version 1.8 + * @member Utils + * @memberof Popper + */ + + }]); + return Popper; + }(); + + /** + * The `referenceObject` is an object that provides an interface compatible with Popper.js + * and lets you use it as replacement of a real DOM node.
+ * You can use this method to position a popper relatively to a set of coordinates + * in case you don't have a DOM node to use as reference. + * + * ``` + * new Popper(referenceObject, popperNode); + * ``` + * + * NB: This feature isn't supported in Internet Explorer 10 + * @name referenceObject + * @property {Function} data.getBoundingClientRect + * A function that returns a set of coordinates compatible with the native `getBoundingClientRect` method. + * @property {number} data.clientWidth + * An ES6 getter that will return the width of the virtual reference element. + * @property {number} data.clientHeight + * An ES6 getter that will return the height of the virtual reference element. + */ + + + Popper.Utils = (typeof window !== 'undefined' ? window : global).PopperUtils; + Popper.placements = placements; + Popper.Defaults = Defaults; + + /** + * -------------------------------------------------------------------------- + * Bootstrap (v4.1.3): dropdown.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + + var Dropdown = function ($$$1) { + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + var NAME = 'dropdown'; + var VERSION = '4.1.3'; + var DATA_KEY = 'bs.dropdown'; + var EVENT_KEY = "." + DATA_KEY; + var DATA_API_KEY = '.data-api'; + var JQUERY_NO_CONFLICT = $$$1.fn[NAME]; + var ESCAPE_KEYCODE = 27; // KeyboardEvent.which value for Escape (Esc) key + + var SPACE_KEYCODE = 32; // KeyboardEvent.which value for space key + + var TAB_KEYCODE = 9; // KeyboardEvent.which value for tab key + + var ARROW_UP_KEYCODE = 38; // KeyboardEvent.which value for up arrow key + + var ARROW_DOWN_KEYCODE = 40; // KeyboardEvent.which value for down arrow key + + var RIGHT_MOUSE_BUTTON_WHICH = 3; // MouseEvent.which value for the right button (assuming a right-handed mouse) + + var REGEXP_KEYDOWN = new RegExp(ARROW_UP_KEYCODE + "|" + ARROW_DOWN_KEYCODE + "|" + ESCAPE_KEYCODE); + var Event = { + HIDE: "hide" + EVENT_KEY, + HIDDEN: "hidden" + EVENT_KEY, + SHOW: "show" + EVENT_KEY, + SHOWN: "shown" + EVENT_KEY, + CLICK: "click" + EVENT_KEY, + CLICK_DATA_API: "click" + EVENT_KEY + DATA_API_KEY, + KEYDOWN_DATA_API: "keydown" + EVENT_KEY + DATA_API_KEY, + KEYUP_DATA_API: "keyup" + EVENT_KEY + DATA_API_KEY + }; + var ClassName = { + DISABLED: 'disabled', + SHOW: 'show', + DROPUP: 'dropup', + DROPRIGHT: 'dropright', + DROPLEFT: 'dropleft', + MENURIGHT: 'dropdown-menu-right', + MENULEFT: 'dropdown-menu-left', + POSITION_STATIC: 'position-static' + }; + var Selector = { + DATA_TOGGLE: '[data-toggle="dropdown"]', + FORM_CHILD: '.dropdown form', + MENU: '.dropdown-menu', + NAVBAR_NAV: '.navbar-nav', + VISIBLE_ITEMS: '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)' + }; + var AttachmentMap = { + TOP: 'top-start', + TOPEND: 'top-end', + BOTTOM: 'bottom-start', + BOTTOMEND: 'bottom-end', + RIGHT: 'right-start', + RIGHTEND: 'right-end', + LEFT: 'left-start', + LEFTEND: 'left-end' + }; + var Default = { + offset: 0, + flip: true, + boundary: 'scrollParent', + reference: 'toggle', + display: 'dynamic' + }; + var DefaultType = { + offset: '(number|string|function)', + flip: 'boolean', + boundary: '(string|element)', + reference: '(string|element)', + display: 'string' + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + }; + + var Dropdown = + /*#__PURE__*/ + function () { + function Dropdown(element, config) { + this._element = element; + this._popper = null; + this._config = this._getConfig(config); + this._menu = this._getMenuElement(); + this._inNavbar = this._detectNavbar(); + + this._addEventListeners(); + } // Getters + + + var _proto = Dropdown.prototype; + + // Public + _proto.toggle = function toggle() { + if (this._element.disabled || $$$1(this._element).hasClass(ClassName.DISABLED)) { + return; + } + + var parent = Dropdown._getParentFromElement(this._element); + + var isActive = $$$1(this._menu).hasClass(ClassName.SHOW); + + Dropdown._clearMenus(); + + if (isActive) { + return; + } + + var relatedTarget = { + relatedTarget: this._element + }; + var showEvent = $$$1.Event(Event.SHOW, relatedTarget); + $$$1(parent).trigger(showEvent); + + if (showEvent.isDefaultPrevented()) { + return; + } // Disable totally Popper.js for Dropdown in Navbar + + + if (!this._inNavbar) { + /** + * Check for Popper dependency + * Popper - https://popper.js.org + */ + if (typeof Popper === 'undefined') { + throw new TypeError('Bootstrap dropdown require Popper.js (https://popper.js.org)'); + } + + var referenceElement = this._element; + + if (this._config.reference === 'parent') { + referenceElement = parent; + } else if (Util.isElement(this._config.reference)) { + referenceElement = this._config.reference; // Check if it's jQuery element + + if (typeof this._config.reference.jquery !== 'undefined') { + referenceElement = this._config.reference[0]; + } + } // If boundary is not `scrollParent`, then set position to `static` + // to allow the menu to "escape" the scroll parent's boundaries + // https://github.com/twbs/bootstrap/issues/24251 + + + if (this._config.boundary !== 'scrollParent') { + $$$1(parent).addClass(ClassName.POSITION_STATIC); + } + + this._popper = new Popper(referenceElement, this._menu, this._getPopperConfig()); + } // If this is a touch-enabled device we add extra + // empty mouseover listeners to the body's immediate children; + // only needed because of broken event delegation on iOS + // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html + + + if ('ontouchstart' in document.documentElement && $$$1(parent).closest(Selector.NAVBAR_NAV).length === 0) { + $$$1(document.body).children().on('mouseover', null, $$$1.noop); + } + + this._element.focus(); + + this._element.setAttribute('aria-expanded', true); + + $$$1(this._menu).toggleClass(ClassName.SHOW); + $$$1(parent).toggleClass(ClassName.SHOW).trigger($$$1.Event(Event.SHOWN, relatedTarget)); + }; + + _proto.dispose = function dispose() { + $$$1.removeData(this._element, DATA_KEY); + $$$1(this._element).off(EVENT_KEY); + this._element = null; + this._menu = null; + + if (this._popper !== null) { + this._popper.destroy(); + + this._popper = null; + } + }; + + _proto.update = function update() { + this._inNavbar = this._detectNavbar(); + + if (this._popper !== null) { + this._popper.scheduleUpdate(); + } + }; // Private + + + _proto._addEventListeners = function _addEventListeners() { + var _this = this; + + $$$1(this._element).on(Event.CLICK, function (event) { + event.preventDefault(); + event.stopPropagation(); + + _this.toggle(); + }); + }; + + _proto._getConfig = function _getConfig(config) { + config = _objectSpread({}, this.constructor.Default, $$$1(this._element).data(), config); + Util.typeCheckConfig(NAME, config, this.constructor.DefaultType); + return config; + }; + + _proto._getMenuElement = function _getMenuElement() { + if (!this._menu) { + var parent = Dropdown._getParentFromElement(this._element); + + if (parent) { + this._menu = parent.querySelector(Selector.MENU); + } + } + + return this._menu; + }; + + _proto._getPlacement = function _getPlacement() { + var $parentDropdown = $$$1(this._element.parentNode); + var placement = AttachmentMap.BOTTOM; // Handle dropup + + if ($parentDropdown.hasClass(ClassName.DROPUP)) { + placement = AttachmentMap.TOP; + + if ($$$1(this._menu).hasClass(ClassName.MENURIGHT)) { + placement = AttachmentMap.TOPEND; + } + } else if ($parentDropdown.hasClass(ClassName.DROPRIGHT)) { + placement = AttachmentMap.RIGHT; + } else if ($parentDropdown.hasClass(ClassName.DROPLEFT)) { + placement = AttachmentMap.LEFT; + } else if ($$$1(this._menu).hasClass(ClassName.MENURIGHT)) { + placement = AttachmentMap.BOTTOMEND; + } + + return placement; + }; + + _proto._detectNavbar = function _detectNavbar() { + return $$$1(this._element).closest('.navbar').length > 0; + }; + + _proto._getPopperConfig = function _getPopperConfig() { + var _this2 = this; + + var offsetConf = {}; + + if (typeof this._config.offset === 'function') { + offsetConf.fn = function (data) { + data.offsets = _objectSpread({}, data.offsets, _this2._config.offset(data.offsets) || {}); + return data; + }; + } else { + offsetConf.offset = this._config.offset; + } + + var popperConfig = { + placement: this._getPlacement(), + modifiers: { + offset: offsetConf, + flip: { + enabled: this._config.flip + }, + preventOverflow: { + boundariesElement: this._config.boundary + } + } // Disable Popper.js if we have a static display + + }; + + if (this._config.display === 'static') { + popperConfig.modifiers.applyStyle = { + enabled: false + }; + } + + return popperConfig; + }; // Static + + + Dropdown._jQueryInterface = function _jQueryInterface(config) { + return this.each(function () { + var data = $$$1(this).data(DATA_KEY); + + var _config = typeof config === 'object' ? config : null; + + if (!data) { + data = new Dropdown(this, _config); + $$$1(this).data(DATA_KEY, data); + } + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError("No method named \"" + config + "\""); + } + + data[config](); + } + }); + }; + + Dropdown._clearMenus = function _clearMenus(event) { + if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH || event.type === 'keyup' && event.which !== TAB_KEYCODE)) { + return; + } + + var toggles = [].slice.call(document.querySelectorAll(Selector.DATA_TOGGLE)); + + for (var i = 0, len = toggles.length; i < len; i++) { + var parent = Dropdown._getParentFromElement(toggles[i]); + + var context = $$$1(toggles[i]).data(DATA_KEY); + var relatedTarget = { + relatedTarget: toggles[i] + }; + + if (event && event.type === 'click') { + relatedTarget.clickEvent = event; + } + + if (!context) { + continue; + } + + var dropdownMenu = context._menu; + + if (!$$$1(parent).hasClass(ClassName.SHOW)) { + continue; + } + + if (event && (event.type === 'click' && /input|textarea/i.test(event.target.tagName) || event.type === 'keyup' && event.which === TAB_KEYCODE) && $$$1.contains(parent, event.target)) { + continue; + } + + var hideEvent = $$$1.Event(Event.HIDE, relatedTarget); + $$$1(parent).trigger(hideEvent); + + if (hideEvent.isDefaultPrevented()) { + continue; + } // If this is a touch-enabled device we remove the extra + // empty mouseover listeners we added for iOS support + + + if ('ontouchstart' in document.documentElement) { + $$$1(document.body).children().off('mouseover', null, $$$1.noop); + } + + toggles[i].setAttribute('aria-expanded', 'false'); + $$$1(dropdownMenu).removeClass(ClassName.SHOW); + $$$1(parent).removeClass(ClassName.SHOW).trigger($$$1.Event(Event.HIDDEN, relatedTarget)); + } + }; + + Dropdown._getParentFromElement = function _getParentFromElement(element) { + var parent; + var selector = Util.getSelectorFromElement(element); + + if (selector) { + parent = document.querySelector(selector); + } + + return parent || element.parentNode; + }; // eslint-disable-next-line complexity + + + Dropdown._dataApiKeydownHandler = function _dataApiKeydownHandler(event) { + // If not input/textarea: + // - And not a key in REGEXP_KEYDOWN => not a dropdown command + // If input/textarea: + // - If space key => not a dropdown command + // - If key is other than escape + // - If key is not up or down => not a dropdown command + // - If trigger inside the menu => not a dropdown command + if (/input|textarea/i.test(event.target.tagName) ? event.which === SPACE_KEYCODE || event.which !== ESCAPE_KEYCODE && (event.which !== ARROW_DOWN_KEYCODE && event.which !== ARROW_UP_KEYCODE || $$$1(event.target).closest(Selector.MENU).length) : !REGEXP_KEYDOWN.test(event.which)) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + if (this.disabled || $$$1(this).hasClass(ClassName.DISABLED)) { + return; + } + + var parent = Dropdown._getParentFromElement(this); + + var isActive = $$$1(parent).hasClass(ClassName.SHOW); + + if (!isActive && (event.which !== ESCAPE_KEYCODE || event.which !== SPACE_KEYCODE) || isActive && (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE)) { + if (event.which === ESCAPE_KEYCODE) { + var toggle = parent.querySelector(Selector.DATA_TOGGLE); + $$$1(toggle).trigger('focus'); + } + + $$$1(this).trigger('click'); + return; + } + + var items = [].slice.call(parent.querySelectorAll(Selector.VISIBLE_ITEMS)); + + if (items.length === 0) { + return; + } + + var index = items.indexOf(event.target); + + if (event.which === ARROW_UP_KEYCODE && index > 0) { + // Up + index--; + } + + if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) { + // Down + index++; + } + + if (index < 0) { + index = 0; + } + + items[index].focus(); + }; + + _createClass(Dropdown, null, [{ + key: "VERSION", + get: function get() { + return VERSION; + } + }, { + key: "Default", + get: function get() { + return Default; + } + }, { + key: "DefaultType", + get: function get() { + return DefaultType; + } + }]); + + return Dropdown; + }(); + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + + $$$1(document).on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler).on(Event.KEYDOWN_DATA_API, Selector.MENU, Dropdown._dataApiKeydownHandler).on(Event.CLICK_DATA_API + " " + Event.KEYUP_DATA_API, Dropdown._clearMenus).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) { + event.preventDefault(); + event.stopPropagation(); + + Dropdown._jQueryInterface.call($$$1(this), 'toggle'); + }).on(Event.CLICK_DATA_API, Selector.FORM_CHILD, function (e) { + e.stopPropagation(); + }); + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $$$1.fn[NAME] = Dropdown._jQueryInterface; + $$$1.fn[NAME].Constructor = Dropdown; + + $$$1.fn[NAME].noConflict = function () { + $$$1.fn[NAME] = JQUERY_NO_CONFLICT; + return Dropdown._jQueryInterface; + }; + + return Dropdown; + }($, Popper); + + /** + * -------------------------------------------------------------------------- + * Bootstrap (v4.1.3): modal.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + + var Modal = function ($$$1) { + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + var NAME = 'modal'; + var VERSION = '4.1.3'; + var DATA_KEY = 'bs.modal'; + var EVENT_KEY = "." + DATA_KEY; + var DATA_API_KEY = '.data-api'; + var JQUERY_NO_CONFLICT = $$$1.fn[NAME]; + var ESCAPE_KEYCODE = 27; // KeyboardEvent.which value for Escape (Esc) key + + var Default = { + backdrop: true, + keyboard: true, + focus: true, + show: true + }; + var DefaultType = { + backdrop: '(boolean|string)', + keyboard: 'boolean', + focus: 'boolean', + show: 'boolean' + }; + var Event = { + HIDE: "hide" + EVENT_KEY, + HIDDEN: "hidden" + EVENT_KEY, + SHOW: "show" + EVENT_KEY, + SHOWN: "shown" + EVENT_KEY, + FOCUSIN: "focusin" + EVENT_KEY, + RESIZE: "resize" + EVENT_KEY, + CLICK_DISMISS: "click.dismiss" + EVENT_KEY, + KEYDOWN_DISMISS: "keydown.dismiss" + EVENT_KEY, + MOUSEUP_DISMISS: "mouseup.dismiss" + EVENT_KEY, + MOUSEDOWN_DISMISS: "mousedown.dismiss" + EVENT_KEY, + CLICK_DATA_API: "click" + EVENT_KEY + DATA_API_KEY + }; + var ClassName = { + SCROLLBAR_MEASURER: 'modal-scrollbar-measure', + BACKDROP: 'modal-backdrop', + OPEN: 'modal-open', + FADE: 'fade', + SHOW: 'show' + }; + var Selector = { + DIALOG: '.modal-dialog', + DATA_TOGGLE: '[data-toggle="modal"]', + DATA_DISMISS: '[data-dismiss="modal"]', + FIXED_CONTENT: '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top', + STICKY_CONTENT: '.sticky-top' + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + }; + + var Modal = + /*#__PURE__*/ + function () { + function Modal(element, config) { + this._config = this._getConfig(config); + this._element = element; + this._dialog = element.querySelector(Selector.DIALOG); + this._backdrop = null; + this._isShown = false; + this._isBodyOverflowing = false; + this._ignoreBackdropClick = false; + this._scrollbarWidth = 0; + } // Getters + + + var _proto = Modal.prototype; + + // Public + _proto.toggle = function toggle(relatedTarget) { + return this._isShown ? this.hide() : this.show(relatedTarget); + }; + + _proto.show = function show(relatedTarget) { + var _this = this; + + if (this._isTransitioning || this._isShown) { + return; + } + + if ($$$1(this._element).hasClass(ClassName.FADE)) { + this._isTransitioning = true; + } + + var showEvent = $$$1.Event(Event.SHOW, { + relatedTarget: relatedTarget + }); + $$$1(this._element).trigger(showEvent); + + if (this._isShown || showEvent.isDefaultPrevented()) { + return; + } + + this._isShown = true; + + this._checkScrollbar(); + + this._setScrollbar(); + + this._adjustDialog(); + + $$$1(document.body).addClass(ClassName.OPEN); + + this._setEscapeEvent(); + + this._setResizeEvent(); + + $$$1(this._element).on(Event.CLICK_DISMISS, Selector.DATA_DISMISS, function (event) { + return _this.hide(event); + }); + $$$1(this._dialog).on(Event.MOUSEDOWN_DISMISS, function () { + $$$1(_this._element).one(Event.MOUSEUP_DISMISS, function (event) { + if ($$$1(event.target).is(_this._element)) { + _this._ignoreBackdropClick = true; + } + }); + }); + + this._showBackdrop(function () { + return _this._showElement(relatedTarget); + }); + }; + + _proto.hide = function hide(event) { + var _this2 = this; + + if (event) { + event.preventDefault(); + } + + if (this._isTransitioning || !this._isShown) { + return; + } + + var hideEvent = $$$1.Event(Event.HIDE); + $$$1(this._element).trigger(hideEvent); + + if (!this._isShown || hideEvent.isDefaultPrevented()) { + return; + } + + this._isShown = false; + var transition = $$$1(this._element).hasClass(ClassName.FADE); + + if (transition) { + this._isTransitioning = true; + } + + this._setEscapeEvent(); + + this._setResizeEvent(); + + $$$1(document).off(Event.FOCUSIN); + $$$1(this._element).removeClass(ClassName.SHOW); + $$$1(this._element).off(Event.CLICK_DISMISS); + $$$1(this._dialog).off(Event.MOUSEDOWN_DISMISS); + + if (transition) { + var transitionDuration = Util.getTransitionDurationFromElement(this._element); + $$$1(this._element).one(Util.TRANSITION_END, function (event) { + return _this2._hideModal(event); + }).emulateTransitionEnd(transitionDuration); + } else { + this._hideModal(); + } + }; + + _proto.dispose = function dispose() { + $$$1.removeData(this._element, DATA_KEY); + $$$1(window, document, this._element, this._backdrop).off(EVENT_KEY); + this._config = null; + this._element = null; + this._dialog = null; + this._backdrop = null; + this._isShown = null; + this._isBodyOverflowing = null; + this._ignoreBackdropClick = null; + this._scrollbarWidth = null; + }; + + _proto.handleUpdate = function handleUpdate() { + this._adjustDialog(); + }; // Private + + + _proto._getConfig = function _getConfig(config) { + config = _objectSpread({}, Default, config); + Util.typeCheckConfig(NAME, config, DefaultType); + return config; + }; + + _proto._showElement = function _showElement(relatedTarget) { + var _this3 = this; + + var transition = $$$1(this._element).hasClass(ClassName.FADE); + + if (!this._element.parentNode || this._element.parentNode.nodeType !== Node.ELEMENT_NODE) { + // Don't move modal's DOM position + document.body.appendChild(this._element); + } + + this._element.style.display = 'block'; + + this._element.removeAttribute('aria-hidden'); + + this._element.scrollTop = 0; + + if (transition) { + Util.reflow(this._element); + } + + $$$1(this._element).addClass(ClassName.SHOW); + + if (this._config.focus) { + this._enforceFocus(); + } + + var shownEvent = $$$1.Event(Event.SHOWN, { + relatedTarget: relatedTarget + }); + + var transitionComplete = function transitionComplete() { + if (_this3._config.focus) { + _this3._element.focus(); + } + + _this3._isTransitioning = false; + $$$1(_this3._element).trigger(shownEvent); + }; + + if (transition) { + var transitionDuration = Util.getTransitionDurationFromElement(this._element); + $$$1(this._dialog).one(Util.TRANSITION_END, transitionComplete).emulateTransitionEnd(transitionDuration); + } else { + transitionComplete(); + } + }; + + _proto._enforceFocus = function _enforceFocus() { + var _this4 = this; + + $$$1(document).off(Event.FOCUSIN) // Guard against infinite focus loop + .on(Event.FOCUSIN, function (event) { + if (document !== event.target && _this4._element !== event.target && $$$1(_this4._element).has(event.target).length === 0) { + _this4._element.focus(); + } + }); + }; + + _proto._setEscapeEvent = function _setEscapeEvent() { + var _this5 = this; + + if (this._isShown && this._config.keyboard) { + $$$1(this._element).on(Event.KEYDOWN_DISMISS, function (event) { + if (event.which === ESCAPE_KEYCODE) { + event.preventDefault(); + + _this5.hide(); + } + }); + } else if (!this._isShown) { + $$$1(this._element).off(Event.KEYDOWN_DISMISS); + } + }; + + _proto._setResizeEvent = function _setResizeEvent() { + var _this6 = this; + + if (this._isShown) { + $$$1(window).on(Event.RESIZE, function (event) { + return _this6.handleUpdate(event); + }); + } else { + $$$1(window).off(Event.RESIZE); + } + }; + + _proto._hideModal = function _hideModal() { + var _this7 = this; + + this._element.style.display = 'none'; + + this._element.setAttribute('aria-hidden', true); + + this._isTransitioning = false; + + this._showBackdrop(function () { + $$$1(document.body).removeClass(ClassName.OPEN); + + _this7._resetAdjustments(); + + _this7._resetScrollbar(); + + $$$1(_this7._element).trigger(Event.HIDDEN); + }); + }; + + _proto._removeBackdrop = function _removeBackdrop() { + if (this._backdrop) { + $$$1(this._backdrop).remove(); + this._backdrop = null; + } + }; + + _proto._showBackdrop = function _showBackdrop(callback) { + var _this8 = this; + + var animate = $$$1(this._element).hasClass(ClassName.FADE) ? ClassName.FADE : ''; + + if (this._isShown && this._config.backdrop) { + this._backdrop = document.createElement('div'); + this._backdrop.className = ClassName.BACKDROP; + + if (animate) { + this._backdrop.classList.add(animate); + } + + $$$1(this._backdrop).appendTo(document.body); + $$$1(this._element).on(Event.CLICK_DISMISS, function (event) { + if (_this8._ignoreBackdropClick) { + _this8._ignoreBackdropClick = false; + return; + } + + if (event.target !== event.currentTarget) { + return; + } + + if (_this8._config.backdrop === 'static') { + _this8._element.focus(); + } else { + _this8.hide(); + } + }); + + if (animate) { + Util.reflow(this._backdrop); + } + + $$$1(this._backdrop).addClass(ClassName.SHOW); + + if (!callback) { + return; + } + + if (!animate) { + callback(); + return; + } + + var backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop); + $$$1(this._backdrop).one(Util.TRANSITION_END, callback).emulateTransitionEnd(backdropTransitionDuration); + } else if (!this._isShown && this._backdrop) { + $$$1(this._backdrop).removeClass(ClassName.SHOW); + + var callbackRemove = function callbackRemove() { + _this8._removeBackdrop(); + + if (callback) { + callback(); + } + }; + + if ($$$1(this._element).hasClass(ClassName.FADE)) { + var _backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop); + + $$$1(this._backdrop).one(Util.TRANSITION_END, callbackRemove).emulateTransitionEnd(_backdropTransitionDuration); + } else { + callbackRemove(); + } + } else if (callback) { + callback(); + } + }; // ---------------------------------------------------------------------- + // the following methods are used to handle overflowing modals + // todo (fat): these should probably be refactored out of modal.js + // ---------------------------------------------------------------------- + + + _proto._adjustDialog = function _adjustDialog() { + var isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; + + if (!this._isBodyOverflowing && isModalOverflowing) { + this._element.style.paddingLeft = this._scrollbarWidth + "px"; + } + + if (this._isBodyOverflowing && !isModalOverflowing) { + this._element.style.paddingRight = this._scrollbarWidth + "px"; + } + }; + + _proto._resetAdjustments = function _resetAdjustments() { + this._element.style.paddingLeft = ''; + this._element.style.paddingRight = ''; + }; + + _proto._checkScrollbar = function _checkScrollbar() { + var rect = document.body.getBoundingClientRect(); + this._isBodyOverflowing = rect.left + rect.right < window.innerWidth; + this._scrollbarWidth = this._getScrollbarWidth(); + }; + + _proto._setScrollbar = function _setScrollbar() { + var _this9 = this; + + if (this._isBodyOverflowing) { + // Note: DOMNode.style.paddingRight returns the actual value or '' if not set + // while $(DOMNode).css('padding-right') returns the calculated value or 0 if not set + var fixedContent = [].slice.call(document.querySelectorAll(Selector.FIXED_CONTENT)); + var stickyContent = [].slice.call(document.querySelectorAll(Selector.STICKY_CONTENT)); // Adjust fixed content padding + + $$$1(fixedContent).each(function (index, element) { + var actualPadding = element.style.paddingRight; + var calculatedPadding = $$$1(element).css('padding-right'); + $$$1(element).data('padding-right', actualPadding).css('padding-right', parseFloat(calculatedPadding) + _this9._scrollbarWidth + "px"); + }); // Adjust sticky content margin + + $$$1(stickyContent).each(function (index, element) { + var actualMargin = element.style.marginRight; + var calculatedMargin = $$$1(element).css('margin-right'); + $$$1(element).data('margin-right', actualMargin).css('margin-right', parseFloat(calculatedMargin) - _this9._scrollbarWidth + "px"); + }); // Adjust body padding + + var actualPadding = document.body.style.paddingRight; + var calculatedPadding = $$$1(document.body).css('padding-right'); + $$$1(document.body).data('padding-right', actualPadding).css('padding-right', parseFloat(calculatedPadding) + this._scrollbarWidth + "px"); + } + }; + + _proto._resetScrollbar = function _resetScrollbar() { + // Restore fixed content padding + var fixedContent = [].slice.call(document.querySelectorAll(Selector.FIXED_CONTENT)); + $$$1(fixedContent).each(function (index, element) { + var padding = $$$1(element).data('padding-right'); + $$$1(element).removeData('padding-right'); + element.style.paddingRight = padding ? padding : ''; + }); // Restore sticky content + + var elements = [].slice.call(document.querySelectorAll("" + Selector.STICKY_CONTENT)); + $$$1(elements).each(function (index, element) { + var margin = $$$1(element).data('margin-right'); + + if (typeof margin !== 'undefined') { + $$$1(element).css('margin-right', margin).removeData('margin-right'); + } + }); // Restore body padding + + var padding = $$$1(document.body).data('padding-right'); + $$$1(document.body).removeData('padding-right'); + document.body.style.paddingRight = padding ? padding : ''; + }; + + _proto._getScrollbarWidth = function _getScrollbarWidth() { + // thx d.walsh + var scrollDiv = document.createElement('div'); + scrollDiv.className = ClassName.SCROLLBAR_MEASURER; + document.body.appendChild(scrollDiv); + var scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth; + document.body.removeChild(scrollDiv); + return scrollbarWidth; + }; // Static + + + Modal._jQueryInterface = function _jQueryInterface(config, relatedTarget) { + return this.each(function () { + var data = $$$1(this).data(DATA_KEY); + + var _config = _objectSpread({}, Default, $$$1(this).data(), typeof config === 'object' && config ? config : {}); + + if (!data) { + data = new Modal(this, _config); + $$$1(this).data(DATA_KEY, data); + } + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError("No method named \"" + config + "\""); + } + + data[config](relatedTarget); + } else if (_config.show) { + data.show(relatedTarget); + } + }); + }; + + _createClass(Modal, null, [{ + key: "VERSION", + get: function get() { + return VERSION; + } + }, { + key: "Default", + get: function get() { + return Default; + } + }]); + + return Modal; + }(); + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + + $$$1(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) { + var _this10 = this; + + var target; + var selector = Util.getSelectorFromElement(this); + + if (selector) { + target = document.querySelector(selector); + } + + var config = $$$1(target).data(DATA_KEY) ? 'toggle' : _objectSpread({}, $$$1(target).data(), $$$1(this).data()); + + if (this.tagName === 'A' || this.tagName === 'AREA') { + event.preventDefault(); + } + + var $target = $$$1(target).one(Event.SHOW, function (showEvent) { + if (showEvent.isDefaultPrevented()) { + // Only register focus restorer if modal will actually get shown + return; + } + + $target.one(Event.HIDDEN, function () { + if ($$$1(_this10).is(':visible')) { + _this10.focus(); + } + }); + }); + + Modal._jQueryInterface.call($$$1(target), config, this); + }); + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $$$1.fn[NAME] = Modal._jQueryInterface; + $$$1.fn[NAME].Constructor = Modal; + + $$$1.fn[NAME].noConflict = function () { + $$$1.fn[NAME] = JQUERY_NO_CONFLICT; + return Modal._jQueryInterface; + }; + + return Modal; + }($); + + /** + * -------------------------------------------------------------------------- + * Bootstrap (v4.1.3): tooltip.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + + var Tooltip = function ($$$1) { + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + var NAME = 'tooltip'; + var VERSION = '4.1.3'; + var DATA_KEY = 'bs.tooltip'; + var EVENT_KEY = "." + DATA_KEY; + var JQUERY_NO_CONFLICT = $$$1.fn[NAME]; + var CLASS_PREFIX = 'bs-tooltip'; + var BSCLS_PREFIX_REGEX = new RegExp("(^|\\s)" + CLASS_PREFIX + "\\S+", 'g'); + var DefaultType = { + animation: 'boolean', + template: 'string', + title: '(string|element|function)', + trigger: 'string', + delay: '(number|object)', + html: 'boolean', + selector: '(string|boolean)', + placement: '(string|function)', + offset: '(number|string)', + container: '(string|element|boolean)', + fallbackPlacement: '(string|array)', + boundary: '(string|element)' + }; + var AttachmentMap = { + AUTO: 'auto', + TOP: 'top', + RIGHT: 'right', + BOTTOM: 'bottom', + LEFT: 'left' + }; + var Default = { + animation: true, + template: '', + trigger: 'hover focus', + title: '', + delay: 0, + html: false, + selector: false, + placement: 'top', + offset: 0, + container: false, + fallbackPlacement: 'flip', + boundary: 'scrollParent' + }; + var HoverState = { + SHOW: 'show', + OUT: 'out' + }; + var Event = { + HIDE: "hide" + EVENT_KEY, + HIDDEN: "hidden" + EVENT_KEY, + SHOW: "show" + EVENT_KEY, + SHOWN: "shown" + EVENT_KEY, + INSERTED: "inserted" + EVENT_KEY, + CLICK: "click" + EVENT_KEY, + FOCUSIN: "focusin" + EVENT_KEY, + FOCUSOUT: "focusout" + EVENT_KEY, + MOUSEENTER: "mouseenter" + EVENT_KEY, + MOUSELEAVE: "mouseleave" + EVENT_KEY + }; + var ClassName = { + FADE: 'fade', + SHOW: 'show' + }; + var Selector = { + TOOLTIP: '.tooltip', + TOOLTIP_INNER: '.tooltip-inner', + ARROW: '.arrow' + }; + var Trigger = { + HOVER: 'hover', + FOCUS: 'focus', + CLICK: 'click', + MANUAL: 'manual' + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + }; + + var Tooltip = + /*#__PURE__*/ + function () { + function Tooltip(element, config) { + /** + * Check for Popper dependency + * Popper - https://popper.js.org + */ + if (typeof Popper === 'undefined') { + throw new TypeError('Bootstrap tooltips require Popper.js (https://popper.js.org)'); + } // private + + + this._isEnabled = true; + this._timeout = 0; + this._hoverState = ''; + this._activeTrigger = {}; + this._popper = null; // Protected + + this.element = element; + this.config = this._getConfig(config); + this.tip = null; + + this._setListeners(); + } // Getters + + + var _proto = Tooltip.prototype; + + // Public + _proto.enable = function enable() { + this._isEnabled = true; + }; + + _proto.disable = function disable() { + this._isEnabled = false; + }; + + _proto.toggleEnabled = function toggleEnabled() { + this._isEnabled = !this._isEnabled; + }; + + _proto.toggle = function toggle(event) { + if (!this._isEnabled) { + return; + } + + if (event) { + var dataKey = this.constructor.DATA_KEY; + var context = $$$1(event.currentTarget).data(dataKey); + + if (!context) { + context = new this.constructor(event.currentTarget, this._getDelegateConfig()); + $$$1(event.currentTarget).data(dataKey, context); + } + + context._activeTrigger.click = !context._activeTrigger.click; + + if (context._isWithActiveTrigger()) { + context._enter(null, context); + } else { + context._leave(null, context); + } + } else { + if ($$$1(this.getTipElement()).hasClass(ClassName.SHOW)) { + this._leave(null, this); + + return; + } + + this._enter(null, this); + } + }; + + _proto.dispose = function dispose() { + clearTimeout(this._timeout); + $$$1.removeData(this.element, this.constructor.DATA_KEY); + $$$1(this.element).off(this.constructor.EVENT_KEY); + $$$1(this.element).closest('.modal').off('hide.bs.modal'); + + if (this.tip) { + $$$1(this.tip).remove(); + } + + this._isEnabled = null; + this._timeout = null; + this._hoverState = null; + this._activeTrigger = null; + + if (this._popper !== null) { + this._popper.destroy(); + } + + this._popper = null; + this.element = null; + this.config = null; + this.tip = null; + }; + + _proto.show = function show() { + var _this = this; + + if ($$$1(this.element).css('display') === 'none') { + throw new Error('Please use show on visible elements'); + } + + var showEvent = $$$1.Event(this.constructor.Event.SHOW); + + if (this.isWithContent() && this._isEnabled) { + $$$1(this.element).trigger(showEvent); + var isInTheDom = $$$1.contains(this.element.ownerDocument.documentElement, this.element); + + if (showEvent.isDefaultPrevented() || !isInTheDom) { + return; + } + + var tip = this.getTipElement(); + var tipId = Util.getUID(this.constructor.NAME); + tip.setAttribute('id', tipId); + this.element.setAttribute('aria-describedby', tipId); + this.setContent(); + + if (this.config.animation) { + $$$1(tip).addClass(ClassName.FADE); + } + + var placement = typeof this.config.placement === 'function' ? this.config.placement.call(this, tip, this.element) : this.config.placement; + + var attachment = this._getAttachment(placement); + + this.addAttachmentClass(attachment); + var container = this.config.container === false ? document.body : $$$1(document).find(this.config.container); + $$$1(tip).data(this.constructor.DATA_KEY, this); + + if (!$$$1.contains(this.element.ownerDocument.documentElement, this.tip)) { + $$$1(tip).appendTo(container); + } + + $$$1(this.element).trigger(this.constructor.Event.INSERTED); + this._popper = new Popper(this.element, tip, { + placement: attachment, + modifiers: { + offset: { + offset: this.config.offset + }, + flip: { + behavior: this.config.fallbackPlacement + }, + arrow: { + element: Selector.ARROW + }, + preventOverflow: { + boundariesElement: this.config.boundary + } + }, + onCreate: function onCreate(data) { + if (data.originalPlacement !== data.placement) { + _this._handlePopperPlacementChange(data); + } + }, + onUpdate: function onUpdate(data) { + _this._handlePopperPlacementChange(data); + } + }); + $$$1(tip).addClass(ClassName.SHOW); // If this is a touch-enabled device we add extra + // empty mouseover listeners to the body's immediate children; + // only needed because of broken event delegation on iOS + // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html + + if ('ontouchstart' in document.documentElement) { + $$$1(document.body).children().on('mouseover', null, $$$1.noop); + } + + var complete = function complete() { + if (_this.config.animation) { + _this._fixTransition(); + } + + var prevHoverState = _this._hoverState; + _this._hoverState = null; + $$$1(_this.element).trigger(_this.constructor.Event.SHOWN); + + if (prevHoverState === HoverState.OUT) { + _this._leave(null, _this); + } + }; + + if ($$$1(this.tip).hasClass(ClassName.FADE)) { + var transitionDuration = Util.getTransitionDurationFromElement(this.tip); + $$$1(this.tip).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration); + } else { + complete(); + } + } + }; + + _proto.hide = function hide(callback) { + var _this2 = this; + + var tip = this.getTipElement(); + var hideEvent = $$$1.Event(this.constructor.Event.HIDE); + + var complete = function complete() { + if (_this2._hoverState !== HoverState.SHOW && tip.parentNode) { + tip.parentNode.removeChild(tip); + } + + _this2._cleanTipClass(); + + _this2.element.removeAttribute('aria-describedby'); + + $$$1(_this2.element).trigger(_this2.constructor.Event.HIDDEN); + + if (_this2._popper !== null) { + _this2._popper.destroy(); + } + + if (callback) { + callback(); + } + }; + + $$$1(this.element).trigger(hideEvent); + + if (hideEvent.isDefaultPrevented()) { + return; + } + + $$$1(tip).removeClass(ClassName.SHOW); // If this is a touch-enabled device we remove the extra + // empty mouseover listeners we added for iOS support + + if ('ontouchstart' in document.documentElement) { + $$$1(document.body).children().off('mouseover', null, $$$1.noop); + } + + this._activeTrigger[Trigger.CLICK] = false; + this._activeTrigger[Trigger.FOCUS] = false; + this._activeTrigger[Trigger.HOVER] = false; + + if ($$$1(this.tip).hasClass(ClassName.FADE)) { + var transitionDuration = Util.getTransitionDurationFromElement(tip); + $$$1(tip).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration); + } else { + complete(); + } + + this._hoverState = ''; + }; + + _proto.update = function update() { + if (this._popper !== null) { + this._popper.scheduleUpdate(); + } + }; // Protected + + + _proto.isWithContent = function isWithContent() { + return Boolean(this.getTitle()); + }; + + _proto.addAttachmentClass = function addAttachmentClass(attachment) { + $$$1(this.getTipElement()).addClass(CLASS_PREFIX + "-" + attachment); + }; + + _proto.getTipElement = function getTipElement() { + this.tip = this.tip || $$$1(this.config.template)[0]; + return this.tip; + }; + + _proto.setContent = function setContent() { + var tip = this.getTipElement(); + this.setElementContent($$$1(tip.querySelectorAll(Selector.TOOLTIP_INNER)), this.getTitle()); + $$$1(tip).removeClass(ClassName.FADE + " " + ClassName.SHOW); + }; + + _proto.setElementContent = function setElementContent($element, content) { + var html = this.config.html; + + if (typeof content === 'object' && (content.nodeType || content.jquery)) { + // Content is a DOM node or a jQuery + if (html) { + if (!$$$1(content).parent().is($element)) { + $element.empty().append(content); + } + } else { + $element.text($$$1(content).text()); + } + } else { + $element[html ? 'html' : 'text'](content); + } + }; + + _proto.getTitle = function getTitle() { + var title = this.element.getAttribute('data-original-title'); + + if (!title) { + title = typeof this.config.title === 'function' ? this.config.title.call(this.element) : this.config.title; + } + + return title; + }; // Private + + + _proto._getAttachment = function _getAttachment(placement) { + return AttachmentMap[placement.toUpperCase()]; + }; + + _proto._setListeners = function _setListeners() { + var _this3 = this; + + var triggers = this.config.trigger.split(' '); + triggers.forEach(function (trigger) { + if (trigger === 'click') { + $$$1(_this3.element).on(_this3.constructor.Event.CLICK, _this3.config.selector, function (event) { + return _this3.toggle(event); + }); + } else if (trigger !== Trigger.MANUAL) { + var eventIn = trigger === Trigger.HOVER ? _this3.constructor.Event.MOUSEENTER : _this3.constructor.Event.FOCUSIN; + var eventOut = trigger === Trigger.HOVER ? _this3.constructor.Event.MOUSELEAVE : _this3.constructor.Event.FOCUSOUT; + $$$1(_this3.element).on(eventIn, _this3.config.selector, function (event) { + return _this3._enter(event); + }).on(eventOut, _this3.config.selector, function (event) { + return _this3._leave(event); + }); + } + + $$$1(_this3.element).closest('.modal').on('hide.bs.modal', function () { + return _this3.hide(); + }); + }); + + if (this.config.selector) { + this.config = _objectSpread({}, this.config, { + trigger: 'manual', + selector: '' + }); + } else { + this._fixTitle(); + } + }; + + _proto._fixTitle = function _fixTitle() { + var titleType = typeof this.element.getAttribute('data-original-title'); + + if (this.element.getAttribute('title') || titleType !== 'string') { + this.element.setAttribute('data-original-title', this.element.getAttribute('title') || ''); + this.element.setAttribute('title', ''); + } + }; + + _proto._enter = function _enter(event, context) { + var dataKey = this.constructor.DATA_KEY; + context = context || $$$1(event.currentTarget).data(dataKey); + + if (!context) { + context = new this.constructor(event.currentTarget, this._getDelegateConfig()); + $$$1(event.currentTarget).data(dataKey, context); + } + + if (event) { + context._activeTrigger[event.type === 'focusin' ? Trigger.FOCUS : Trigger.HOVER] = true; + } + + if ($$$1(context.getTipElement()).hasClass(ClassName.SHOW) || context._hoverState === HoverState.SHOW) { + context._hoverState = HoverState.SHOW; + return; + } + + clearTimeout(context._timeout); + context._hoverState = HoverState.SHOW; + + if (!context.config.delay || !context.config.delay.show) { + context.show(); + return; + } + + context._timeout = setTimeout(function () { + if (context._hoverState === HoverState.SHOW) { + context.show(); + } + }, context.config.delay.show); + }; + + _proto._leave = function _leave(event, context) { + var dataKey = this.constructor.DATA_KEY; + context = context || $$$1(event.currentTarget).data(dataKey); + + if (!context) { + context = new this.constructor(event.currentTarget, this._getDelegateConfig()); + $$$1(event.currentTarget).data(dataKey, context); + } + + if (event) { + context._activeTrigger[event.type === 'focusout' ? Trigger.FOCUS : Trigger.HOVER] = false; + } + + if (context._isWithActiveTrigger()) { + return; + } + + clearTimeout(context._timeout); + context._hoverState = HoverState.OUT; + + if (!context.config.delay || !context.config.delay.hide) { + context.hide(); + return; + } + + context._timeout = setTimeout(function () { + if (context._hoverState === HoverState.OUT) { + context.hide(); + } + }, context.config.delay.hide); + }; + + _proto._isWithActiveTrigger = function _isWithActiveTrigger() { + for (var trigger in this._activeTrigger) { + if (this._activeTrigger[trigger]) { + return true; + } + } + + return false; + }; + + _proto._getConfig = function _getConfig(config) { + config = _objectSpread({}, this.constructor.Default, $$$1(this.element).data(), typeof config === 'object' && config ? config : {}); + + if (typeof config.delay === 'number') { + config.delay = { + show: config.delay, + hide: config.delay + }; + } + + if (typeof config.title === 'number') { + config.title = config.title.toString(); + } + + if (typeof config.content === 'number') { + config.content = config.content.toString(); + } + + Util.typeCheckConfig(NAME, config, this.constructor.DefaultType); + return config; + }; + + _proto._getDelegateConfig = function _getDelegateConfig() { + var config = {}; + + if (this.config) { + for (var key in this.config) { + if (this.constructor.Default[key] !== this.config[key]) { + config[key] = this.config[key]; + } + } + } + + return config; + }; + + _proto._cleanTipClass = function _cleanTipClass() { + var $tip = $$$1(this.getTipElement()); + var tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX); + + if (tabClass !== null && tabClass.length) { + $tip.removeClass(tabClass.join('')); + } + }; + + _proto._handlePopperPlacementChange = function _handlePopperPlacementChange(popperData) { + var popperInstance = popperData.instance; + this.tip = popperInstance.popper; + + this._cleanTipClass(); + + this.addAttachmentClass(this._getAttachment(popperData.placement)); + }; + + _proto._fixTransition = function _fixTransition() { + var tip = this.getTipElement(); + var initConfigAnimation = this.config.animation; + + if (tip.getAttribute('x-placement') !== null) { + return; + } + + $$$1(tip).removeClass(ClassName.FADE); + this.config.animation = false; + this.hide(); + this.show(); + this.config.animation = initConfigAnimation; + }; // Static + + + Tooltip._jQueryInterface = function _jQueryInterface(config) { + return this.each(function () { + var data = $$$1(this).data(DATA_KEY); + + var _config = typeof config === 'object' && config; + + if (!data && /dispose|hide/.test(config)) { + return; + } + + if (!data) { + data = new Tooltip(this, _config); + $$$1(this).data(DATA_KEY, data); + } + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError("No method named \"" + config + "\""); + } + + data[config](); + } + }); + }; + + _createClass(Tooltip, null, [{ + key: "VERSION", + get: function get() { + return VERSION; + } + }, { + key: "Default", + get: function get() { + return Default; + } + }, { + key: "NAME", + get: function get() { + return NAME; + } + }, { + key: "DATA_KEY", + get: function get() { + return DATA_KEY; + } + }, { + key: "Event", + get: function get() { + return Event; + } + }, { + key: "EVENT_KEY", + get: function get() { + return EVENT_KEY; + } + }, { + key: "DefaultType", + get: function get() { + return DefaultType; + } + }]); + + return Tooltip; + }(); + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + + $$$1.fn[NAME] = Tooltip._jQueryInterface; + $$$1.fn[NAME].Constructor = Tooltip; + + $$$1.fn[NAME].noConflict = function () { + $$$1.fn[NAME] = JQUERY_NO_CONFLICT; + return Tooltip._jQueryInterface; + }; + + return Tooltip; + }($, Popper); + + /** + * -------------------------------------------------------------------------- + * Bootstrap (v4.1.3): popover.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + + var Popover = function ($$$1) { + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + var NAME = 'popover'; + var VERSION = '4.1.3'; + var DATA_KEY = 'bs.popover'; + var EVENT_KEY = "." + DATA_KEY; + var JQUERY_NO_CONFLICT = $$$1.fn[NAME]; + var CLASS_PREFIX = 'bs-popover'; + var BSCLS_PREFIX_REGEX = new RegExp("(^|\\s)" + CLASS_PREFIX + "\\S+", 'g'); + + var Default = _objectSpread({}, Tooltip.Default, { + placement: 'right', + trigger: 'click', + content: '', + template: '' + }); + + var DefaultType = _objectSpread({}, Tooltip.DefaultType, { + content: '(string|element|function)' + }); + + var ClassName = { + FADE: 'fade', + SHOW: 'show' + }; + var Selector = { + TITLE: '.popover-header', + CONTENT: '.popover-body' + }; + var Event = { + HIDE: "hide" + EVENT_KEY, + HIDDEN: "hidden" + EVENT_KEY, + SHOW: "show" + EVENT_KEY, + SHOWN: "shown" + EVENT_KEY, + INSERTED: "inserted" + EVENT_KEY, + CLICK: "click" + EVENT_KEY, + FOCUSIN: "focusin" + EVENT_KEY, + FOCUSOUT: "focusout" + EVENT_KEY, + MOUSEENTER: "mouseenter" + EVENT_KEY, + MOUSELEAVE: "mouseleave" + EVENT_KEY + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + }; + + var Popover = + /*#__PURE__*/ + function (_Tooltip) { + _inheritsLoose(Popover, _Tooltip); + + function Popover() { + return _Tooltip.apply(this, arguments) || this; + } + + var _proto = Popover.prototype; + + // Overrides + _proto.isWithContent = function isWithContent() { + return this.getTitle() || this._getContent(); + }; + + _proto.addAttachmentClass = function addAttachmentClass(attachment) { + $$$1(this.getTipElement()).addClass(CLASS_PREFIX + "-" + attachment); + }; + + _proto.getTipElement = function getTipElement() { + this.tip = this.tip || $$$1(this.config.template)[0]; + return this.tip; + }; + + _proto.setContent = function setContent() { + var $tip = $$$1(this.getTipElement()); // We use append for html objects to maintain js events + + this.setElementContent($tip.find(Selector.TITLE), this.getTitle()); + + var content = this._getContent(); + + if (typeof content === 'function') { + content = content.call(this.element); + } + + this.setElementContent($tip.find(Selector.CONTENT), content); + $tip.removeClass(ClassName.FADE + " " + ClassName.SHOW); + }; // Private + + + _proto._getContent = function _getContent() { + return this.element.getAttribute('data-content') || this.config.content; + }; + + _proto._cleanTipClass = function _cleanTipClass() { + var $tip = $$$1(this.getTipElement()); + var tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX); + + if (tabClass !== null && tabClass.length > 0) { + $tip.removeClass(tabClass.join('')); + } + }; // Static + + + Popover._jQueryInterface = function _jQueryInterface(config) { + return this.each(function () { + var data = $$$1(this).data(DATA_KEY); + + var _config = typeof config === 'object' ? config : null; + + if (!data && /destroy|hide/.test(config)) { + return; + } + + if (!data) { + data = new Popover(this, _config); + $$$1(this).data(DATA_KEY, data); + } + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError("No method named \"" + config + "\""); + } + + data[config](); + } + }); + }; + + _createClass(Popover, null, [{ + key: "VERSION", + // Getters + get: function get() { + return VERSION; + } + }, { + key: "Default", + get: function get() { + return Default; + } + }, { + key: "NAME", + get: function get() { + return NAME; + } + }, { + key: "DATA_KEY", + get: function get() { + return DATA_KEY; + } + }, { + key: "Event", + get: function get() { + return Event; + } + }, { + key: "EVENT_KEY", + get: function get() { + return EVENT_KEY; + } + }, { + key: "DefaultType", + get: function get() { + return DefaultType; + } + }]); + + return Popover; + }(Tooltip); + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + + $$$1.fn[NAME] = Popover._jQueryInterface; + $$$1.fn[NAME].Constructor = Popover; + + $$$1.fn[NAME].noConflict = function () { + $$$1.fn[NAME] = JQUERY_NO_CONFLICT; + return Popover._jQueryInterface; + }; + + return Popover; + }($); + + /** + * -------------------------------------------------------------------------- + * Bootstrap (v4.1.3): scrollspy.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + + var ScrollSpy = function ($$$1) { + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + var NAME = 'scrollspy'; + var VERSION = '4.1.3'; + var DATA_KEY = 'bs.scrollspy'; + var EVENT_KEY = "." + DATA_KEY; + var DATA_API_KEY = '.data-api'; + var JQUERY_NO_CONFLICT = $$$1.fn[NAME]; + var Default = { + offset: 10, + method: 'auto', + target: '' + }; + var DefaultType = { + offset: 'number', + method: 'string', + target: '(string|element)' + }; + var Event = { + ACTIVATE: "activate" + EVENT_KEY, + SCROLL: "scroll" + EVENT_KEY, + LOAD_DATA_API: "load" + EVENT_KEY + DATA_API_KEY + }; + var ClassName = { + DROPDOWN_ITEM: 'dropdown-item', + DROPDOWN_MENU: 'dropdown-menu', + ACTIVE: 'active' + }; + var Selector = { + DATA_SPY: '[data-spy="scroll"]', + ACTIVE: '.active', + NAV_LIST_GROUP: '.nav, .list-group', + NAV_LINKS: '.nav-link', + NAV_ITEMS: '.nav-item', + LIST_ITEMS: '.list-group-item', + DROPDOWN: '.dropdown', + DROPDOWN_ITEMS: '.dropdown-item', + DROPDOWN_TOGGLE: '.dropdown-toggle' + }; + var OffsetMethod = { + OFFSET: 'offset', + POSITION: 'position' + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + }; + + var ScrollSpy = + /*#__PURE__*/ + function () { + function ScrollSpy(element, config) { + var _this = this; + + this._element = element; + this._scrollElement = element.tagName === 'BODY' ? window : element; + this._config = this._getConfig(config); + this._selector = this._config.target + " " + Selector.NAV_LINKS + "," + (this._config.target + " " + Selector.LIST_ITEMS + ",") + (this._config.target + " " + Selector.DROPDOWN_ITEMS); + this._offsets = []; + this._targets = []; + this._activeTarget = null; + this._scrollHeight = 0; + $$$1(this._scrollElement).on(Event.SCROLL, function (event) { + return _this._process(event); + }); + this.refresh(); + + this._process(); + } // Getters + + + var _proto = ScrollSpy.prototype; + + // Public + _proto.refresh = function refresh() { + var _this2 = this; + + var autoMethod = this._scrollElement === this._scrollElement.window ? OffsetMethod.OFFSET : OffsetMethod.POSITION; + var offsetMethod = this._config.method === 'auto' ? autoMethod : this._config.method; + var offsetBase = offsetMethod === OffsetMethod.POSITION ? this._getScrollTop() : 0; + this._offsets = []; + this._targets = []; + this._scrollHeight = this._getScrollHeight(); + var targets = [].slice.call(document.querySelectorAll(this._selector)); + targets.map(function (element) { + var target; + var targetSelector = Util.getSelectorFromElement(element); + + if (targetSelector) { + target = document.querySelector(targetSelector); + } + + if (target) { + var targetBCR = target.getBoundingClientRect(); + + if (targetBCR.width || targetBCR.height) { + // TODO (fat): remove sketch reliance on jQuery position/offset + return [$$$1(target)[offsetMethod]().top + offsetBase, targetSelector]; + } + } + + return null; + }).filter(function (item) { + return item; + }).sort(function (a, b) { + return a[0] - b[0]; + }).forEach(function (item) { + _this2._offsets.push(item[0]); + + _this2._targets.push(item[1]); + }); + }; + + _proto.dispose = function dispose() { + $$$1.removeData(this._element, DATA_KEY); + $$$1(this._scrollElement).off(EVENT_KEY); + this._element = null; + this._scrollElement = null; + this._config = null; + this._selector = null; + this._offsets = null; + this._targets = null; + this._activeTarget = null; + this._scrollHeight = null; + }; // Private + + + _proto._getConfig = function _getConfig(config) { + config = _objectSpread({}, Default, typeof config === 'object' && config ? config : {}); + + if (typeof config.target !== 'string') { + var id = $$$1(config.target).attr('id'); + + if (!id) { + id = Util.getUID(NAME); + $$$1(config.target).attr('id', id); + } + + config.target = "#" + id; + } + + Util.typeCheckConfig(NAME, config, DefaultType); + return config; + }; + + _proto._getScrollTop = function _getScrollTop() { + return this._scrollElement === window ? this._scrollElement.pageYOffset : this._scrollElement.scrollTop; + }; + + _proto._getScrollHeight = function _getScrollHeight() { + return this._scrollElement.scrollHeight || Math.max(document.body.scrollHeight, document.documentElement.scrollHeight); + }; + + _proto._getOffsetHeight = function _getOffsetHeight() { + return this._scrollElement === window ? window.innerHeight : this._scrollElement.getBoundingClientRect().height; + }; + + _proto._process = function _process() { + var scrollTop = this._getScrollTop() + this._config.offset; + + var scrollHeight = this._getScrollHeight(); + + var maxScroll = this._config.offset + scrollHeight - this._getOffsetHeight(); + + if (this._scrollHeight !== scrollHeight) { + this.refresh(); + } + + if (scrollTop >= maxScroll) { + var target = this._targets[this._targets.length - 1]; + + if (this._activeTarget !== target) { + this._activate(target); + } + + return; + } + + if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) { + this._activeTarget = null; + + this._clear(); + + return; + } + + var offsetLength = this._offsets.length; + + for (var i = offsetLength; i--;) { + var isActiveTarget = this._activeTarget !== this._targets[i] && scrollTop >= this._offsets[i] && (typeof this._offsets[i + 1] === 'undefined' || scrollTop < this._offsets[i + 1]); + + if (isActiveTarget) { + this._activate(this._targets[i]); + } + } + }; + + _proto._activate = function _activate(target) { + this._activeTarget = target; + + this._clear(); + + var queries = this._selector.split(','); // eslint-disable-next-line arrow-body-style + + + queries = queries.map(function (selector) { + return selector + "[data-target=\"" + target + "\"]," + (selector + "[href=\"" + target + "\"]"); + }); + var $link = $$$1([].slice.call(document.querySelectorAll(queries.join(',')))); + + if ($link.hasClass(ClassName.DROPDOWN_ITEM)) { + $link.closest(Selector.DROPDOWN).find(Selector.DROPDOWN_TOGGLE).addClass(ClassName.ACTIVE); + $link.addClass(ClassName.ACTIVE); + } else { + // Set triggered link as active + $link.addClass(ClassName.ACTIVE); // Set triggered links parents as active + // With both