diff --git a/.circleci/config.yml b/.circleci/config.yml
index 4e39c6e87c..664ac3dd93 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,123 +1,141 @@
version: 2
-# this block contains anchors to reusable blocks of config.
-references:
- setup_env: &setup_env
- docker:
- - image: circleci/node:8.10.0
- save_cache: &save_cache
- key: v9-dependency-cache-{{ checksum "yarn.lock" }}
+aliases:
+ - &docker-node-lts
+ - image: circleci/node:lts
+
+ - &docker-node-browsers
+ - image: circleci/node:lts-browsers
+ environment:
+ CHROME_BIN: "/usr/bin/google-chrome"
+
+ - &restore-node-modules-cache
+ name: Restore node_modules cache
+ key: v2-yarn-deps-{{ checksum "yarn.lock" }}
+
+ - &restore-yarn-cache
+ name: Restore yarnpkg cache
+ key: v2-yarn-cache
+
+ - &save-node-modules-cache
+ name: Save node_modules cache
paths:
- node_modules
- # explicitly list each package node_modules
- - packages/core/node_modules
- - packages/datetime/node_modules
- - packages/docs-app/node_modules
- - packages/docs-data/node_modules
- - packages/docs-theme/node_modules
- - packages/icons/node_modules
- - packages/karma-build-scripts/node_modules
- - packages/labs/node_modules
- - packages/landing-app/node_modules
- - packages/node-build-scripts/node_modules
- - packages/select/node_modules
- - packages/table/node_modules
- - packages/table-dev-app/node_modules
- - packages/test-commons/node_modules
- - packages/test-react15/node_modules
- - packages/timezone/node_modules
- - packages/tslint-config/node_modules
- - packages/webpack-build-scripts/node_modules
- restore_cache: &restore_cache
- keys:
- - v9-dependency-cache-{{ checksum "yarn.lock" }}
- - v9-dependency-cache-
- persist_to_workspace: &persist_to_workspace
- root: '.'
+ key: v2-yarn-deps-{{ checksum "yarn.lock" }}
+
+ - &save-yarn-cache
+ name: Save yarnpkg cache
paths:
- # directories to persist to workspace
- - packages/*/dist
- - packages/*/lib
- - packages/*/src/generated
+ - ~/.cache/yarn
+ key: v2-yarn-cache
+
+references:
reports_path: &reports_path
path: ./reports
jobs:
- install-dependencies:
- <<: *setup_env
+ checkout-code:
+ docker: *docker-node-lts
steps:
- checkout
- - attach_workspace:
- at: '.'
- - restore_cache: *restore_cache
- - run: yarn --frozen-lockfile
- - run: npm rebuild node-sass
- - save_cache: *save_cache
+ - restore_cache: *restore-yarn-cache
+ - restore_cache: *restore-node-modules-cache
+ - run: yarn install --non-interactive --cache-folder ~/.cache/yarn
+ - run:
+ name: Check if yarn.lock changed during install
+ command: git diff --exit-code
+ - save_cache: *save-node-modules-cache
+ - save_cache: *save-yarn-cache
- persist_to_workspace:
root: '.'
- paths:
- - yarn.lock
+ paths: [packages/*/node_modules]
+
+ clean-lockfile:
+ docker: *docker-node-lts
+ steps:
+ - checkout
+ - restore_cache: *restore-node-modules-cache
+ - run: ./scripts/verifyCleanLockfile.sh
compile:
- <<: *setup_env
+ docker: *docker-node-lts
+ resource_class: large
steps:
- checkout
- - attach_workspace:
- at: '.'
- - restore_cache: *restore_cache
+ - restore_cache: *restore-node-modules-cache
+ - attach_workspace: { at: '.' }
- run: yarn compile
- - persist_to_workspace: *persist_to_workspace
+ - persist_to_workspace:
+ root: '.'
+ paths: [packages/*/lib, packages/*/src/generated]
lint:
- <<: *setup_env
+ docker: *docker-node-lts
environment:
JUNIT_REPORT_PATH: reports
steps:
- checkout
- - attach_workspace:
- at: '.'
- - restore_cache: *restore_cache
- - run: mkdir -p ./reports/tslint ./reports/stylelint
- - run: yarn compile --scope "@blueprintjs/tslint-config"
+ - restore_cache: *restore-node-modules-cache
+ - attach_workspace: { at: '.' }
+ - run: mkdir -p ./reports/eslint ./reports/stylelint
+ # we need to compile the lint rules for blueprint
+ - run: yarn compile --scope "@blueprintjs/{eslint-plugin-blueprint,tslint-config}"
- run: yarn lint
- - store_test_results: *reports_path
- - store_artifacts: *reports_path
+ - store_test_results: { path: ./reports }
+ - store_artifacts: { path: ./reports }
dist:
- <<: *setup_env
+ docker: *docker-node-lts
+ resource_class: large
steps:
- checkout
- - attach_workspace:
- at: '.'
- - restore_cache: *restore_cache
- - run: yarn dist:libs
- - run: yarn dist:apps
- - persist_to_workspace: *persist_to_workspace
+ - restore_cache: *restore-node-modules-cache
+ - attach_workspace: { at: '.' }
+ - run: yarn dist
+ - persist_to_workspace:
+ root: '.'
+ paths: [packages/*/lib, packages/*/dist]
+
+ test-jest:
+ docker: *docker-node-lts
+ environment:
+ JUNIT_REPORT_PATH: reports
+ # JEST_JUNIT_OUTPUT_DIR: "reports/junit/js-test-results.xml"
+ steps:
+ - checkout
+ - restore_cache: *restore-node-modules-cache
+ - attach_workspace: { at: '.' }
+ - run: mkdir ./reports
+ - run: yarn lerna run test:jest-ci
+ - store_test_results: { path: ./reports }
+ - store_artifacts: { path: ./reports }
test-react-16: &test-react
- docker:
- - image: circleci/node:8.10.0-browsers
- environment:
- CHROME_BIN: "/usr/bin/google-chrome"
+ docker: *docker-node-browsers
environment:
JUNIT_REPORT_PATH: reports
- parallelism: 3
+ parallelism: 6
steps:
- checkout
- - attach_workspace:
- at: '.'
- - restore_cache: *restore_cache
+ - restore_cache: *restore-node-modules-cache
+ - attach_workspace: { at: '.' }
- run: mkdir ./reports
- run:
+ # split karma tests into containers because they can take up a lot of memory
+ # running them in one container caused Karma to time out frequently
+ # see https://github.com/palantir/blueprint/issues/3616
command: |
case $CIRCLE_NODE_INDEX in \
- 0) yarn lerna run --parallel test:pre ;; \
- 1) yarn lerna run --parallel test:iso ;; \
- 2) yarn lerna run --parallel test:karma ;; \
+ 0) yarn lerna run --parallel test:typeCheck ;; \
+ 1) yarn lerna run --scope "@blueprintjs/core" test:karma ;; \
+ 2) yarn lerna run --scope "@blueprintjs/datetime" test:karma ;; \
+ 3) yarn lerna run --scope "@blueprintjs/select" test:karma ;; \
+ 4) yarn lerna run --scope "@blueprintjs/table" test:karma ;; \
+ 5) yarn lerna run --scope "@blueprintjs/timezone" test:karma ;; \
esac
when: always
- - store_test_results: *reports_path
- - store_artifacts: *reports_path
+ - store_test_results: { path: ./reports }
+ - store_artifacts: { path: ./reports }
test-react-15:
# copy test-react-16 and override environment
@@ -126,52 +144,74 @@ jobs:
JUNIT_REPORT_PATH: reports
REACT: 15 # use React 15 for this job
+ test-iso-react-16: &test-iso
+ docker: *docker-node-lts
+ environment:
+ JUNIT_REPORT_PATH: reports
+ steps:
+ - checkout
+ - restore_cache: *restore-node-modules-cache
+ - attach_workspace: { at: '.' }
+ - run: mkdir ./reports
+ - run: yarn lerna run --parallel test:iso
+ - store_test_results: { path: ./reports }
+ - store_artifacts: { path: ./reports }
+
+ test-iso-react-15:
+ # copy test-iso-react-16 and override environment
+ <<: *test-iso
+ environment:
+ JUNIT_REPORT_PATH: reports
+ REACT: 15 # use React 15 for this job
+
deploy-preview:
- docker:
- - image: circleci/node:8.10.0
+ docker: *docker-node-lts
steps:
- checkout
- - attach_workspace:
- at: '.'
- - restore_cache: *restore_cache
- - store_artifacts:
- path: packages/docs-app/dist
- - store_artifacts:
- path: packages/landing-app/dist
- - store_artifacts:
- path: packages/table-dev-app/dist
+ - restore_cache: *restore-node-modules-cache
+ - attach_workspace: { at: '.' }
+ - store_artifacts: { path: packages/docs-app/dist }
+ - store_artifacts: { path: packages/landing-app/dist }
+ - store_artifacts: { path: packages/table-dev-app/dist }
- run:
name: Submit Github comment with links to built artifacts
command: node ./scripts/preview.js
deploy-npm:
- <<: *setup_env
+ docker: *docker-node-lts
steps:
- checkout
- - attach_workspace:
- at: '.'
- - restore_cache: *restore_cache
+ - restore_cache: *restore-node-modules-cache
+ - attach_workspace: { at: '.' }
- run: ./scripts/publish-npm-semver-tagged
workflows:
version: 2
compile_lint_test_dist_deploy:
jobs:
- - install-dependencies
+ - checkout-code
+ - clean-lockfile:
+ requires: [checkout-code]
- compile:
- requires: [install-dependencies]
+ requires: [checkout-code]
- lint:
- requires: [install-dependencies]
+ requires: [checkout-code]
- dist:
requires: [compile]
+ - test-jest:
+ requires: [compile]
- test-react-15:
requires: [compile]
- test-react-16:
requires: [compile]
+ - test-iso-react-15:
+ requires: [compile]
+ - test-iso-react-16:
+ requires: [compile]
- deploy-preview:
requires: [dist]
- deploy-npm:
- requires: [dist, lint, test-react-15, test-react-16]
+ requires: [dist, lint, test-react-15, test-react-16, test-iso-react-15, test-iso-react-16]
filters:
branches:
only:
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000000..34e26bba41
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,6 @@
+node_modules
+dist
+fixtures
+coverage
+__snapshots__
+src/generated
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000000..ac1bb60b72
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,6 @@
+{
+ "root": true,
+ "extends": [
+ "./packages/eslint-config"
+ ]
+}
diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md
index f5b40d18a2..54c4e23328 100644
--- a/.github/ISSUE_TEMPLATE/Bug_report.md
+++ b/.github/ISSUE_TEMPLATE/Bug_report.md
@@ -10,7 +10,7 @@ about: Something not working as expected?
- __Package version(s)__:
- __Browser and OS versions__:
-If possible, ink to a minimal repro (fork [this code sandbox](https://codesandbox.io/s/nko3k41y60)):
+If possible, link to a minimal repro (fork [this code sandbox](https://codesandbox.io/s/blueprint-sandbox-et9xy)):
#### Steps to reproduce
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 8a9d619ea4..8c12e6a92c 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -7,11 +7,13 @@
"docs": true
},
"search.exclude": {
- "**/node_modules": true,
- "**/build": true,
- "**/coverage": true,
- "**/dist": true,
- "docs": true
+ "**/build": true,
+ "**/coverage": true,
+ "**/dist": true,
+ "**/lib": true,
+ "**/node_modules": true,
+ "docs": true,
+ "site/docs": true
},
"editor.insertSpaces": true,
"editor.tabSize": 4,
diff --git a/.yarnrc b/.yarnrc
deleted file mode 100644
index c16e54f708..0000000000
--- a/.yarnrc
+++ /dev/null
@@ -1 +0,0 @@
-workspace-experimental true
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3d17f74474..6dced7f956 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -39,7 +39,7 @@ A typical contributor workflow looks like this:
- Linting is best handled by your editor for real-time feedback (see
[Editor integration](https://github.com/palantir/blueprint/wiki/Editor-integration)). Run
`yarn lint` to be 100% safe.
- - TypeScript lint errors can often be automatically fixed by TSLint. Run lint fixes with `yarn lint-fix`.
+ - TypeScript lint errors can often be automatically fixed by ESLint. Run lint fixes with `yarn lint-fix`.
1. Submit a Pull Request on GitHub and fill out the template.
- ⚠️ __DO NOT enable CircleCI for your fork of Blueprint.__ Our build
will run on your fork when you open a PR. You can run NPM scripts locally
diff --git a/README.md b/README.md
index 70bba52824..5abd3d8519 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ It is optimized for building complex, data-dense web interfaces for _desktop app
[**View the full documentation ▸**](http://blueprintjs.com/docs)
-[**Try it out on CodeSandbox ▸**](https://codesandbox.io/s/nko3k41y60)
+[**Try it out on CodeSandbox ▸**](https://codesandbox.io/s/blueprint-sandbox-et9xy)
[**Read frequently asked questions (FAQ) on the wiki ▸**](https://github.com/palantir/blueprint/wiki/Frequently-Asked-Questions)
@@ -39,7 +39,6 @@ These are the component libraries we publish to NPM.
- [](https://www.npmjs.com/package/@blueprintjs/select) – Components for selecting items from a list.
- [](https://www.npmjs.com/package/@blueprintjs/table) – Scalable interactive table component.
- [](https://www.npmjs.com/package/@blueprintjs/timezone) – Components for picking timezones.
-- [](https://www.npmjs.com/package/@blueprintjs/labs) – Incubator and staging area for new components still under initial development.
### Applications
@@ -57,10 +56,12 @@ These are used as development playground environments:
These packages define development dependencies and contain build configuration. They adhere to the standard NPM package layout, which allows us to keep clear API boundaries for build configuration and isolate groups of `devDependencies`. They are published to NPM in order to allow other Blueprint-related projects to use this infrastructure outside this monorepo.
- [](https://www.npmjs.com/package/@blueprintjs/docs-theme) – Documentation theme for [Documentalist](https://github.com/palantir/documentalist) data.
+- [](https://www.npmjs.com/package/@blueprintjs/eslint-config) – ESLint configuration used in this repo and recommended for Blueprint-related projects
+- [](https://www.npmjs.com/package/@blueprintjs/eslint-plugin-blueprint) – implementations for custom ESLint rules which enforce best practices for Blueprint usage
- [](https://www.npmjs.com/package/@blueprintjs/karma-build-scripts)
-- [](https://www.npmjs.com/package/@blueprintjs/node-build-scripts)
-- [](https://www.npmjs.com/package/@blueprintjs/test-commons)
-- [](https://www.npmjs.com/package/@blueprintjs/tslint-config)
+- [](https://www.npmjs.com/package/@blueprintjs/node-build-scripts) – various utility scripts for linting, working with CSS variables, and building icons
+- [](https://www.npmjs.com/package/@blueprintjs/test-commons) – various utility functions used in Blueprint test suites
+- [](https://www.npmjs.com/package/@blueprintjs/tslint-config) – TSLint configuration used in this repo and recommended for Blueprint-related projects (should be installed by `@blueprintjs/eslint-config`, not directly)
- [](https://www.npmjs.com/package/@blueprintjs/webpack-build-scripts)
## Contributing
@@ -71,10 +72,10 @@ then [check out the "help wanted" label](https://github.com/palantir/blueprint/l
## Development
-[Lerna](https://lernajs.io/) manages inter-package dependencies in this monorepo.
+[Lerna](https://lerna.js.org/) manages inter-package dependencies in this monorepo.
Builds are orchestrated via `lerna run` and NPM scripts.
-__Prerequisites__: Node.js v8+, Yarn v1.10+
+__Prerequisites__: Node.js v10+, Yarn v1.18+
### One-time setup
diff --git a/config/tsconfig.base.json b/config/tsconfig.base.json
index 14948c8d2f..f1e6d61a4c 100644
--- a/config/tsconfig.base.json
+++ b/config/tsconfig.base.json
@@ -1,5 +1,5 @@
{
- "version": "2.8.4",
+ "version": "3.5.3",
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"declaration": true,
diff --git a/package.json b/package.json
index f4d388a179..bd4708ce47 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "blueprintjs-monorepo",
- "version": "3.15.0",
+ "version": "3.22.3",
"private": true,
"description": "A React UI toolkit for the web.",
"workspaces": [
@@ -17,12 +17,11 @@
"dev:core": "lerna run dev --parallel --scope \"@blueprintjs/{core,icons,docs-app}\"",
"dev:docs": "lerna run dev --parallel --scope \"@blueprintjs/{core,docs-app,docs-theme}\"",
"dev:datetime": "lerna run dev --parallel --scope \"@blueprintjs/{core,datetime,timezone,docs-app}\"",
- "dev:labs": "lerna run dev --parallel --scope \"@blueprintjs/{core,labs,select,docs-app}\"",
"dev:landing": "lerna run dev --parallel --scope \"@blueprintjs/{core,landing-app}\"",
"dev:select": "lerna run dev --parallel --scope \"@blueprintjs/{core,select,docs-app}\"",
"dev:table": "lerna run dev --parallel --scope \"@blueprintjs/table-dev-app\"",
"dist": "run-s dist:libs dist:apps",
- "dist:libs": "lerna run dist --parallel --scope \"@blueprintjs/{core,datetime,docs-theme,icons,labs,select,table,timezone}\"",
+ "dist:libs": "lerna run dist --parallel --scope \"@blueprintjs/{core,datetime,docs-theme,icons,select,table,timezone}\"",
"dist:apps": "lerna run dist --parallel --scope \"@blueprintjs/{docs-app,landing-app,table-dev-app}\"",
"docs-data": "lerna run compile --scope \"@blueprintjs/docs-data\"",
"lint": "lerna run --parallel lint",
@@ -34,36 +33,36 @@
"verify": "npm-run-all -s compile dist:libs dist:apps -p test lint"
},
"dependencies": {
- "@types/chai": "4.1.7",
- "@types/classnames": "2.2.7",
- "@types/enzyme": "3.1.15",
- "@types/enzyme-adapter-react-16": "1.0.3",
- "@types/mocha": "5.2.6",
- "@types/prop-types": "15.7.0",
- "@types/react": "16.4.18",
- "@types/react-dom": "16.0.11",
- "@types/react-transition-group": "2.0.14",
- "@types/sinon": "7.0.6",
- "@types/webpack": "4.4.24",
+ "@types/chai": "4.2.7",
+ "@types/classnames": "2.2.9",
+ "@types/enzyme": "3.10.4",
+ "@types/enzyme-adapter-react-16": "1.0.5",
+ "@types/mocha": "5.2.7",
+ "@types/prop-types": "15.7.1",
+ "@types/react": "16.9.17",
+ "@types/react-dom": "16.8.5",
+ "@types/react-lifecycles-compat": "^3.0.1",
+ "@types/react-transition-group": "4.2.0",
+ "@types/sinon": "7.5.1",
+ "@types/webpack": "4.41.2",
"chai": "^4.2.0",
"circle-github-bot": "^2.0.1",
"cross-env": "^5.2.0",
"gh-pages": "^2.0.1",
"http-server": "^0.11.1",
"lerna": "^2.11.0",
- "npm-run-all": "^4.1.3",
- "sinon": "^7.2.4",
- "stylelint-config-palantir": "^3.1.1",
- "stylelint-scss": "^3.3.1",
- "typescript": "~2.8.3"
+ "npm-run-all": "^4.1.5",
+ "sinon": "^7.3.2",
+ "stylelint-config-palantir": "^4.0.0",
+ "stylelint-scss": "^3.9.2",
+ "typescript": "~3.7.5",
+ "yarn-deduplicate": "^1.1.1"
},
"resolutions": {
- "@types/enzyme": "3.1.15",
- "@types/react": "16.4.18",
- "node-gyp": "3.8.0"
+ "js-yaml": "3.13.1"
},
"engines": {
- "node": ">=6.1"
+ "node": ">=10"
},
"repository": {
"type": "git",
diff --git a/packages/core/karma.conf.js b/packages/core/karma.conf.js
index b74bf1c71f..abe10e52b8 100644
--- a/packages/core/karma.conf.js
+++ b/packages/core/karma.conf.js
@@ -13,6 +13,7 @@ module.exports = function (config) {
// not worth full coverage
"src/accessibility/*",
"src/common/abstractComponent*",
+ "src/common/abstractPureComponent*",
],
});
config.set(baseConfig);
diff --git a/packages/core/package.json b/packages/core/package.json
index f9fdd60066..ffd3c1aa8e 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@blueprintjs/core",
- "version": "3.15.0-graphext16",
+ "version": "3.24.0",
"description": "Core styles & components",
"main": "lib/cjs/index.js",
"module": "lib/esm/index.js",
@@ -9,7 +9,13 @@
"style": "lib/css/blueprint.css",
"unpkg": "dist/core.bundle.js",
"sideEffects": [
- "*.css"
+ "*.css",
+ "lib/esm/components/index.js",
+ "lib/esm/common/configureDom4.js",
+ "lib/esnext/components/index.js",
+ "lib/esnext/common/configureDom4.js",
+ "lib/cjs/components/index.js",
+ "lib/cjs/common/configureDom4.js"
],
"bin": {
"upgrade-blueprint-2.0.0-rename": "./scripts/upgrade-blueprint-2.0.0-rename.sh",
@@ -21,55 +27,55 @@
"compile:esm": "tsc -p ./src",
"compile:cjs": "tsc -p ./src -m commonjs --outDir lib/cjs",
"compile:esnext": "tsc -p ./src -t esnext --outDir lib/esnext",
- "compile:css": "sass-compile ./src --functions ./scripts/sass-inline-svg.js",
+ "compile:css": "sass-compile ./src --functions ./scripts/sass-custom-functions.js",
"dev": "run-p \"compile:esm -- --watch\" \"compile:css -- --watch\"",
"dist": "run-s \"dist:*\"",
"dist:bundle": "cross-env NODE_ENV=production webpack",
"dist:css": "css-dist lib/css/*.css",
"dist:variables": "generate-css-variables common/_colors.scss common/_color-aliases.scss common/_variables.scss",
"dist:verify": "assert-package-layout",
- "generate-graphext-css": "yarn run compile:css && add-js-version",
- "lint": "run-p lint:scss lint:ts",
+ "lint": "run-p lint:scss lint:es",
"lint:scss": "sass-lint",
- "lint:ts": "ts-lint",
- "lint-fix": "ts-lint --fix",
- "test": "run-s test:pre test:iso test:karma",
- "test:pre": "tsc -p ./test",
+ "lint:es": "es-lint",
+ "lint-fix": "es-lint --fix",
+ "test": "run-s test:typeCheck test:iso test:karma",
+ "test:typeCheck": "tsc -p ./test",
"test:iso": "mocha test/isotest.js",
"test:karma": "karma start",
"test:karma:debug": "karma start --single-run=false --reporters=mocha --debug",
"verify": "npm-run-all compile -p dist test lint"
},
"dependencies": {
- "@blueprintjs/icons": "3.7.0-graphext16",
+ "@blueprintjs/icons": "^3.14.0",
"@types/dom4": "^2.0.1",
"classnames": "^2.2",
- "dom4": "^2.0.1",
- "normalize.css": "^8.0.0",
- "popper.js": "^1.14.1",
- "react-popper": "^1.0.0",
- "react-transition-group": "^2.2.1",
- "resize-observer-polyfill": "^1.5.0",
- "tslib": "^1.9.0"
+ "dom4": "^2.1.5",
+ "normalize.css": "^8.0.1",
+ "popper.js": "^1.15.0",
+ "react-lifecycles-compat": "^3.0.4",
+ "react-popper": "^1.3.7",
+ "react-transition-group": "^2.9.0",
+ "resize-observer-polyfill": "^1.5.1",
+ "tslib": "~1.10.0"
},
"peerDependencies": {
"react": "^15.3.0 || 16",
"react-dom": "^15.3.0 || 16"
},
"devDependencies": {
- "@blueprintjs/karma-build-scripts": "^0.10.0",
- "@blueprintjs/node-build-scripts": "^0.9.0",
- "@blueprintjs/test-commons": "^0.9.0",
- "enzyme": "^3.3.0",
- "karma": "^3.1.4",
- "mocha": "^5.2.0",
- "npm-run-all": "^4.1.2",
- "react": "^16.2.0",
- "react-dom": "^16.2.0",
- "react-test-renderer": "^16.2.0",
+ "@blueprintjs/karma-build-scripts": "^0.12.0",
+ "@blueprintjs/node-build-scripts": "^1.0.0",
+ "@blueprintjs/test-commons": "^0.10.1",
+ "enzyme": "^3.10.0",
+ "karma": "^4.2.0",
+ "mocha": "^6.2.0",
+ "npm-run-all": "^4.1.5",
+ "react": "^16.8.6",
+ "react-dom": "^16.8.6",
+ "react-test-renderer": "^16.8.6",
"sass-inline-svg": "^1.2.0",
- "typescript": "~2.8.3",
- "webpack-cli": "^3.1.2"
+ "typescript": "~3.7.5",
+ "webpack-cli": "^3.3.6"
},
"repository": {
"type": "git",
diff --git a/packages/labs/webpack.config.js b/packages/core/scripts/sass-custom-functions.js
similarity index 52%
rename from packages/labs/webpack.config.js
rename to packages/core/scripts/sass-custom-functions.js
index 0708d456f4..b781efe1de 100644
--- a/packages/labs/webpack.config.js
+++ b/packages/core/scripts/sass-custom-functions.js
@@ -1,5 +1,6 @@
/*
- * Copyright 2017 Palantir Technologies, Inc. All rights reserved.
+ * Copyright 2019 Palantir Technologies, Inc. All rights reserved.
+ *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -13,22 +14,19 @@
* limitations under the License.
*/
-const { baseConfig, COMMON_EXTERNALS } = require("@blueprintjs/webpack-build-scripts");
-const path = require("path");
-
-module.exports = Object.assign({}, baseConfig, {
- entry: {
- labs: [
- "./src/index.ts"
- ],
- },
-
- externals: COMMON_EXTERNALS,
+const inliner = require("sass-inline-svg");
- output: {
- filename: "[name].bundle.js",
- library: ["Blueprint", "Labs"],
- libraryTarget: "umd",
- path: path.resolve(__dirname, "./dist")
- },
-});
+module.exports = {
+ /**
+ * Sass function to inline a UI icon svg and change its path color.
+ *
+ * Usage:
+ * svg-icon("16px/icon-name.svg", (path: (fill: $color)) )
+ */
+ "svg-icon": inliner("../../resources/icons", {
+ // run through SVGO first
+ optimize: true,
+ // minimal "uri" encoding is smaller than base64
+ encodingFormat: "uri"
+ }),
+};
diff --git a/packages/core/scripts/sass-inline-svg.js b/packages/core/scripts/sass-inline-svg.js
deleted file mode 100644
index 790d83b47d..0000000000
--- a/packages/core/scripts/sass-inline-svg.js
+++ /dev/null
@@ -1,12 +0,0 @@
-const inliner = require('sass-inline-svg');
-
-module.exports = {
- // Sass function to inline a UI Icon svg and change its path color:
- // svg-icon("16px/icon-name.svg", (path: (fill: $color)) )
- 'svg-icon': inliner('../../resources/icons', {
- // run through SVGO first
- optimize: true,
- // minimal "uri" encoding is smaller than base64
- encodingFormat: "uri"
- })
-};
diff --git a/packages/core/src/_typography.scss b/packages/core/src/_typography.scss
index 100fcdb826..a7273b2062 100644
--- a/packages/core/src/_typography.scss
+++ b/packages/core/src/_typography.scss
@@ -109,18 +109,20 @@ Running text
Markup:
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
- eiusmod tempor incididunt ut labore et dolore magna aliqua.
+ We build products that make people better at their most important
+ work — the kind of work you read about on the front page of the
+ newspaper, not just the technology section.
- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
- nisi ut aliquip ex ea commodo consequat.
+ A successful data transformation requires the whole organization — users, the IT shop, and
+ leadership — to operate in lockstep. With Foundry, the enterprise comes together to
+ transform the organization and turn data into a competitive advantage.
@@ -356,10 +358,10 @@ Block quotes
Markup:
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
- ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
- ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
- reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
+ Premium Aerotec is a key supplier for Airbus, producing 30 million parts per year,
+ which is huge complexity. Skywise helps us manage all the production steps.
+ It gives Airbus much better visibility into where the product is in the supply chain,
+ and it lets us immediately see our weak points so we can react on the spot.
Styleguide blockquote
diff --git a/packages/core/src/_accessibility.scss b/packages/core/src/accessibility/_focus-states.scss
similarity index 100%
rename from packages/core/src/_accessibility.scss
rename to packages/core/src/accessibility/_focus-states.scss
diff --git a/packages/core/src/blueprint-hi-contrast.scss b/packages/core/src/blueprint-hi-contrast.scss
new file mode 100644
index 0000000000..5b7e739ec6
--- /dev/null
+++ b/packages/core/src/blueprint-hi-contrast.scss
@@ -0,0 +1,22 @@
+/*!
+
+Copyright 2019-present Palantir Technologies, Inc. All rights reserved.
+Licensed under the Apache License, Version 2.0.
+
+*/
+
+// override some intent colors to pass contrast requirements
+$pt-intent-primary: #106ba3 !default; // $blue2
+$pt-intent-success: #0d8050 !default; // $green2;
+$pt-intent-warning: #a66321 !default; // $orange1;
+$pt-intent-danger: #c23030 !default; // $red2;
+
+// Import files in the same order that they are documented in the docs
+@import "common/variables";
+@import "common/colors";
+
+@import "reset";
+@import "typography";
+@import "accessibility/focus-states";
+
+@import "components/index";
diff --git a/packages/core/src/blueprint.scss b/packages/core/src/blueprint.scss
index 53b781ebcc..0186d987c3 100644
--- a/packages/core/src/blueprint.scss
+++ b/packages/core/src/blueprint.scss
@@ -11,7 +11,7 @@ Licensed under the Apache License, Version 2.0.
@import "reset";
@import "typography";
-@import "accessibility";
+@import "accessibility/focus-states";
@import "components/index";
@import "overrides";
diff --git a/packages/core/src/common/_color-aliases.scss b/packages/core/src/common/_color-aliases.scss
index b801efd362..02957118cc 100644
--- a/packages/core/src/common/_color-aliases.scss
+++ b/packages/core/src/common/_color-aliases.scss
@@ -13,12 +13,12 @@ $pt-outline-color: rgba($blue3, 0.6);
$pt-text-color: $dark-gray1 !default;
$pt-text-color-muted: $gray1 !default;
-$pt-text-color-disabled: rgba($pt-text-color-muted, 0.5) !default;
+$pt-text-color-disabled: rgba($pt-text-color-muted, 0.6) !default;
$pt-heading-color: $pt-text-color !default;
$pt-link-color: $blue2 !default;
$pt-dark-text-color: $light-gray5 !default;
-$pt-dark-text-color-muted: $gray5 !default;
-$pt-dark-text-color-disabled: rgba($pt-dark-text-color-muted, 0.5) !default;
+$pt-dark-text-color-muted: $gray4 !default;
+$pt-dark-text-color-disabled: rgba($pt-dark-text-color-muted, 0.6) !default;
$pt-dark-heading-color: $pt-dark-text-color !default;
$pt-dark-link-color: $blue5 !default;
// Default text selection color using #7dbcff
diff --git a/packages/core/src/common/abstractComponent.ts b/packages/core/src/common/abstractComponent.ts
index fbca130b1a..9e8c1e38cf 100644
--- a/packages/core/src/common/abstractComponent.ts
+++ b/packages/core/src/common/abstractComponent.ts
@@ -20,6 +20,7 @@ import { isNodeEnv } from "./utils";
/**
* An abstract component that Blueprint components can extend
* in order to add some common functionality like runtime props validation.
+ * @deprecated componentWillReceiveProps is deprecated in React 16.9; use AbstractComponent2 instead
*/
export abstract class AbstractComponent
extends React.Component
{
/** Component displayName should be `public static`. This property exists to prevent incorrect usage. */
diff --git a/packages/core/src/common/abstractComponent2.ts b/packages/core/src/common/abstractComponent2.ts
new file mode 100644
index 0000000000..2911ab208d
--- /dev/null
+++ b/packages/core/src/common/abstractComponent2.ts
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2019 Palantir Technologies, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as React from "react";
+import { isNodeEnv } from "./utils";
+
+/**
+ * An abstract component that Blueprint components can extend
+ * in order to add some common functionality like runtime props validation.
+ */
+export abstract class AbstractComponent2
extends React.Component
{
+ // unsafe lifecycle methods
+ public componentWillUpdate: never;
+ public componentWillReceiveProps: never;
+ public componentWillMount: never;
+ // this should be static, not an instance method
+ public getDerivedStateFromProps: never;
+
+ /** Component displayName should be `public static`. This property exists to prevent incorrect usage. */
+ protected displayName: never;
+
+ // Not bothering to remove entries when their timeouts finish because clearing invalid ID is a no-op
+ private timeoutIds: number[] = [];
+
+ constructor(props?: P, context?: any) {
+ super(props, context);
+ if (!isNodeEnv("production")) {
+ this.validateProps(this.props);
+ }
+ }
+
+ public componentDidUpdate(_prevProps: P, _prevState: S, _snapshot?: SS) {
+ if (!isNodeEnv("production")) {
+ this.validateProps(this.props);
+ }
+ }
+
+ public componentWillUnmount() {
+ this.clearTimeouts();
+ }
+
+ /**
+ * Set a timeout and remember its ID.
+ * All stored timeouts will be cleared when component unmounts.
+ * @returns a "cancel" function that will clear timeout when invoked.
+ */
+ public setTimeout(callback: () => void, timeout?: number) {
+ const handle = window.setTimeout(callback, timeout);
+ this.timeoutIds.push(handle);
+ return () => window.clearTimeout(handle);
+ }
+
+ /**
+ * Clear all known timeouts.
+ */
+ public clearTimeouts = () => {
+ if (this.timeoutIds.length > 0) {
+ for (const timeoutId of this.timeoutIds) {
+ window.clearTimeout(timeoutId);
+ }
+ this.timeoutIds = [];
+ }
+ };
+
+ /**
+ * Ensures that the props specified for a component are valid.
+ * Implementations should check that props are valid and usually throw an Error if they are not.
+ * Implementations should not duplicate checks that the type system already guarantees.
+ *
+ * This method should be used instead of React's
+ * [propTypes](https://facebook.github.io/react/docs/reusable-components.html#prop-validation) feature.
+ * Like propTypes, these runtime checks run only in development mode.
+ */
+ protected validateProps(_props: P) {
+ // implement in subclass
+ }
+}
diff --git a/packages/core/src/common/abstractPureComponent.ts b/packages/core/src/common/abstractPureComponent.ts
index d8bb9031fa..88efc4b3e9 100644
--- a/packages/core/src/common/abstractPureComponent.ts
+++ b/packages/core/src/common/abstractPureComponent.ts
@@ -20,6 +20,7 @@ import { isNodeEnv } from "./utils";
/**
* An abstract component that Blueprint components can extend
* in order to add some common functionality like runtime props validation.
+ * @deprecated componentWillReceiveProps is deprecated in React 16.9; use AbstractPureComponent2 instead
*/
export abstract class AbstractPureComponent
extends React.PureComponent
{
/** Component displayName should be `public static`. This property exists to prevent incorrect usage. */
@@ -77,7 +78,7 @@ export abstract class AbstractPureComponent
extends React.PureCompone
* [propTypes](https://facebook.github.io/react/docs/reusable-components.html#prop-validation) feature.
* Like propTypes, these runtime checks run only in development mode.
*/
- protected validateProps(_: P & { children?: React.ReactNode }) {
+ protected validateProps(_props: P & { children?: React.ReactNode }) {
// implement in subclass
}
}
diff --git a/packages/core/src/common/abstractPureComponent2.ts b/packages/core/src/common/abstractPureComponent2.ts
new file mode 100644
index 0000000000..514886154e
--- /dev/null
+++ b/packages/core/src/common/abstractPureComponent2.ts
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2019 Palantir Technologies, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as React from "react";
+import { isNodeEnv } from "./utils";
+
+/**
+ * An abstract component that Blueprint components can extend
+ * in order to add some common functionality like runtime props validation.
+ */
+export abstract class AbstractPureComponent2
extends React.PureComponent
{
+ // unsafe lifecycle method
+ public componentWillUpdate: never;
+ public componentWillReceiveProps: never;
+ public componentWillMount: never;
+ // this should be static, not an instance method
+ public getDerivedStateFromProps: never;
+
+ /** Component displayName should be `public static`. This property exists to prevent incorrect usage. */
+ protected displayName: never;
+
+ // Not bothering to remove entries when their timeouts finish because clearing invalid ID is a no-op
+ private timeoutIds: number[] = [];
+
+ constructor(props?: P, context?: any) {
+ super(props, context);
+ if (!isNodeEnv("production")) {
+ this.validateProps(this.props);
+ }
+ }
+
+ public componentDidUpdate(_prevProps: P, _prevState: S, _snapshot?: SS) {
+ if (!isNodeEnv("production")) {
+ this.validateProps(this.props);
+ }
+ }
+
+ public componentWillUnmount() {
+ this.clearTimeouts();
+ }
+
+ /**
+ * Set a timeout and remember its ID.
+ * All stored timeouts will be cleared when component unmounts.
+ * @returns a "cancel" function that will clear timeout when invoked.
+ */
+ public setTimeout(callback: () => void, timeout?: number) {
+ const handle = window.setTimeout(callback, timeout);
+ this.timeoutIds.push(handle);
+ return () => window.clearTimeout(handle);
+ }
+
+ /**
+ * Clear all known timeouts.
+ */
+ public clearTimeouts = () => {
+ if (this.timeoutIds.length > 0) {
+ for (const timeoutId of this.timeoutIds) {
+ window.clearTimeout(timeoutId);
+ }
+ this.timeoutIds = [];
+ }
+ };
+
+ /**
+ * Ensures that the props specified for a component are valid.
+ * Implementations should check that props are valid and usually throw an Error if they are not.
+ * Implementations should not duplicate checks that the type system already guarantees.
+ *
+ * This method should be used instead of React's
+ * [propTypes](https://facebook.github.io/react/docs/reusable-components.html#prop-validation) feature.
+ * Like propTypes, these runtime checks run only in development mode.
+ */
+ protected validateProps(_props: P) {
+ // implement in subclass
+ }
+}
diff --git a/packages/core/src/common/classes.ts b/packages/core/src/common/classes.ts
index ffe0eb7697..b46f668bf0 100644
--- a/packages/core/src/common/classes.ts
+++ b/packages/core/src/common/classes.ts
@@ -19,7 +19,7 @@ import { Elevation } from "./elevation";
import { Intent } from "./intent";
import { Position } from "./position";
-const NS = process.env.BLUEPRINT_NAMESPACE || "bp3";
+const NS = process.env.BLUEPRINT_NAMESPACE || process.env.REACT_APP_BLUEPRINT_NAMESPACE || "bp3";
// modifiers
export const ACTIVE = `${NS}-active`;
@@ -35,6 +35,7 @@ export const INTERACTIVE = `${NS}-interactive`;
export const LARGE = `${NS}-large`;
export const LOADING = `${NS}-loading`;
export const MINIMAL = `${NS}-minimal`;
+export const OUTLINED = `${NS}-outlined`;
export const MULTILINE = `${NS}-multiline`;
export const ROUND = `${NS}-round`;
export const SMALL = `${NS}-small`;
@@ -154,6 +155,7 @@ export const SWITCH_INNER_TEXT = `${SWITCH}-inner-text`;
export const FILE_INPUT = `${NS}-file-input`;
export const FILE_INPUT_HAS_SELECTION = `${NS}-file-input-has-selection`;
export const FILE_UPLOAD_INPUT = `${NS}-file-upload-input`;
+export const FILE_UPLOAD_INPUT_CUSTOM_TEXT = `${NS}-file-upload-input-custom-text`;
export const KEY = `${NS}-key`;
export const KEY_COMBO = `${KEY}-combo`;
diff --git a/packages/core/src/common/configureDom4.ts b/packages/core/src/common/configureDom4.ts
new file mode 100644
index 0000000000..f8f6651951
--- /dev/null
+++ b/packages/core/src/common/configureDom4.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2019 Palantir Technologies, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+if (typeof require !== "undefined" && typeof window !== "undefined" && typeof document !== "undefined") {
+ // we're in browser
+ // tslint:disable-next-line:no-var-requires
+ require("dom4"); // only import actual dom4 if we're in the browser (not server-compatible)
+ // we'll still need dom4 types for the TypeScript to compile, these are included in package.json
+}
diff --git a/packages/core/src/common/constructor.ts b/packages/core/src/common/constructor.ts
index 373a6a9563..12d50c07ea 100644
--- a/packages/core/src/common/constructor.ts
+++ b/packages/core/src/common/constructor.ts
@@ -18,6 +18,4 @@
* Generic interface defining constructor types, such as classes. This is used to type the class
* itself in meta-programming situations such as decorators.
*/
-export interface IConstructor {
- new (...args: any[]): T;
-}
+export type IConstructor = new (...args: any[]) => T;
diff --git a/packages/core/src/common/errors.ts b/packages/core/src/common/errors.ts
index 8b1c0c2310..e3b81a7522 100644
--- a/packages/core/src/common/errors.ts
+++ b/packages/core/src/common/errors.ts
@@ -49,6 +49,10 @@ export const NUMERIC_INPUT_STEP_SIZE_NON_POSITIVE =
ns + ` requires stepSize to be strictly greater than zero.`;
export const NUMERIC_INPUT_STEP_SIZE_NULL = ns + ` requires stepSize to be defined.`;
+export const PANEL_STACK_INITIAL_PANEL_STACK_MUTEX =
+ ns + ` requires exactly one of initialPanel and stack prop`;
+export const PANEL_STACK_REQUIRES_PANEL = ns + ` requires at least one panel in the stack`;
+
export const OVERFLOW_LIST_OBSERVE_PARENTS_CHANGED =
ns + ` does not support changing observeParents after mounting.`;
@@ -94,3 +98,6 @@ export const DIALOG_WARN_NO_HEADER_CLOSE_BUTTON =
export const DRAWER_VERTICAL_IS_IGNORED = ns + ` vertical is ignored if position is defined`;
export const DRAWER_ANGLE_POSITIONS_ARE_CASTED =
ns + ` all angle positions are casted into pure position (TOP, BOTTOM, LEFT or RIGHT)`;
+
+export const TOASTER_MAX_TOASTS_INVALID =
+ ns + ` maxToasts is set to an invalid number, must be greater than 0`;
diff --git a/packages/core/src/common/index.ts b/packages/core/src/common/index.ts
index a69d01e68c..202a7f76ce 100644
--- a/packages/core/src/common/index.ts
+++ b/packages/core/src/common/index.ts
@@ -15,7 +15,9 @@
*/
export * from "./abstractComponent";
+export * from "./abstractComponent2";
export * from "./abstractPureComponent";
+export * from "./abstractPureComponent2";
export * from "./alignment";
export * from "./boundary";
export * from "./colors";
diff --git a/packages/core/src/common/position.ts b/packages/core/src/common/position.ts
index 9b1271cde5..67ddb69185 100644
--- a/packages/core/src/common/position.ts
+++ b/packages/core/src/common/position.ts
@@ -55,11 +55,7 @@ export function isPositionVertical(position: Position) {
}
export function getPositionIgnoreAngles(position: Position) {
- if (
- position === Position.TOP ||
- position === Position.TOP_LEFT ||
- position === Position.TOP_RIGHT
- ) {
+ if (position === Position.TOP || position === Position.TOP_LEFT || position === Position.TOP_RIGHT) {
return Position.TOP;
} else if (
position === Position.BOTTOM ||
@@ -67,11 +63,7 @@ export function getPositionIgnoreAngles(position: Position) {
position === Position.BOTTOM_RIGHT
) {
return Position.BOTTOM;
- } else if (
- position === Position.LEFT ||
- position === Position.LEFT_TOP ||
- position === Position.LEFT_BOTTOM
- ) {
+ } else if (position === Position.LEFT || position === Position.LEFT_TOP || position === Position.LEFT_BOTTOM) {
return Position.LEFT;
} else {
return Position.RIGHT;
diff --git a/packages/core/src/common/props.ts b/packages/core/src/common/props.ts
index 9a7f043932..8fed9af366 100644
--- a/packages/core/src/common/props.ts
+++ b/packages/core/src/common/props.ts
@@ -112,6 +112,7 @@ const INVALID_PROPS = [
"active",
"alignText",
"containerRef",
+ "current",
"elementRef",
"fill",
"icon",
@@ -122,8 +123,10 @@ const INVALID_PROPS = [
"loading",
"leftIcon",
"minimal",
- "onChildrenMount",
- "onRemove",
+ "onRemove", // ITagProps, ITagInputProps
+ "outlined", // IButtonProps
+ "panel", // ITabProps
+ "panelClassName", // ITabProps
"popoverProps",
"rightElement",
"rightIcon",
diff --git a/packages/core/src/common/utils.ts b/packages/core/src/common/utils.ts
index f391d23a25..532f399f95 100644
--- a/packages/core/src/common/utils.ts
+++ b/packages/core/src/common/utils.ts
@@ -209,8 +209,8 @@ export function countDecimalPlaces(num: number) {
if (!isFinite(num)) {
return 0;
}
- let e = 1,
- p = 0;
+ let e = 1;
+ let p = 0;
while (Math.round(num * e) / e !== num) {
e *= 10;
p++;
diff --git a/packages/core/src/common/utils/compareUtils.ts b/packages/core/src/common/utils/compareUtils.ts
index e0aaeca8b8..e56b7f1d2a 100644
--- a/packages/core/src/common/utils/compareUtils.ts
+++ b/packages/core/src/common/utils/compareUtils.ts
@@ -41,6 +41,7 @@ export function arraysEqual(arrA: any[], arrB: any[], compare = (a: any, b: any)
/**
* Shallow comparison between objects. If `keys` is provided, just that subset
* of keys will be compared; otherwise, all keys will be compared.
+ * @returns true if items are equal.
*/
export function shallowCompareKeys(objA: T, objB: T, keys?: IKeyBlacklist | IKeyWhitelist) {
// treat `null` and `undefined` as the same
@@ -65,8 +66,9 @@ export function shallowCompareKeys(objA: T, objB: T, keys?: IK
/**
* Deep comparison between objects. If `keys` is provided, just that subset of
* keys will be compared; otherwise, all keys will be compared.
+ * @returns true if items are equal.
*/
-export function deepCompareKeys(objA: any, objB: any, keys?: string[]): boolean {
+export function deepCompareKeys(objA: any, objB: any, keys?: Array): boolean {
if (objA === objB) {
return true;
} else if (objA == null && objB == null) {
@@ -95,26 +97,6 @@ export function deepCompareKeys(objA: any, objB: any, keys?: string[]): boolean
}
}
-/**
- * Returns a descriptive object for each key whose values are shallowly unequal
- * between two provided objects. Useful for debugging shouldComponentUpdate.
- */
-export function getShallowUnequalKeyValues(
- objA: T,
- objB: T,
- keys?: IKeyBlacklist | IKeyWhitelist,
-) {
- // default param values let null values pass through, so we have to take
- // this more thorough approach
- const definedObjA = objA == null ? {} : objA;
- const definedObjB = objB == null ? {} : objB;
-
- const filteredKeys = _filterKeys(definedObjA, definedObjB, keys == null ? { exclude: [] } : keys);
- return _getUnequalKeyValues(definedObjA, definedObjB, filteredKeys, (a, b, key) => {
- return shallowCompareKeys(a, b, { include: [key] });
- });
-}
-
/**
* Returns a descriptive object for each key whose values are deeply unequal
* between two provided objects. Useful for debugging shouldComponentUpdate.
@@ -145,7 +127,7 @@ function _shallowCompareKeys(objA: T, objB: T, keys: IKeyBlacklist | IKeyW
/**
* Partial deep comparison between objects using the given list of keys.
*/
-function _deepCompareKeys(objA: any, objB: any, keys: string[]): boolean {
+function _deepCompareKeys(objA: any, objB: any, keys: Array): boolean {
return keys.every(key => {
return objA.hasOwnProperty(key) === objB.hasOwnProperty(key) && deepCompareKeys(objA[key], objB[key]);
});
@@ -158,7 +140,7 @@ function _isSimplePrimitiveType(value: any) {
function _filterKeys(objA: T, objB: T, keys: IKeyBlacklist | IKeyWhitelist) {
if (_isWhitelist(keys)) {
return keys.include;
- } else {
+ } else if (_isBlacklist(keys)) {
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
@@ -171,12 +153,18 @@ function _filterKeys(objA: T, objB: T, keys: IKeyBlacklist | IKeyWhitelist
// return the remaining keys as an array
return Object.keys(keySet) as Array;
}
+
+ return [];
}
function _isWhitelist(keys: any): keys is IKeyWhitelist {
return keys != null && (keys as IKeyWhitelist).include != null;
}
+function _isBlacklist(keys: any): keys is IKeyBlacklist {
+ return keys != null && (keys as IKeyBlacklist).exclude != null;
+}
+
function _arrayToObject(arr: any[]) {
return arr.reduce((obj: any, element: any) => {
obj[element] = true;
diff --git a/packages/core/src/common/utils/isDarkTheme.ts b/packages/core/src/common/utils/isDarkTheme.ts
index c819c4fcd6..b4467c35ca 100644
--- a/packages/core/src/common/utils/isDarkTheme.ts
+++ b/packages/core/src/common/utils/isDarkTheme.ts
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+import "../configureDom4";
+
import { Classes } from "../";
export function isDarkTheme(element: Element | Text | null): boolean {
diff --git a/packages/core/src/components/alert/alert.tsx b/packages/core/src/components/alert/alert.tsx
index ee4de62e8d..5ee0e3fd47 100644
--- a/packages/core/src/components/alert/alert.tsx
+++ b/packages/core/src/components/alert/alert.tsx
@@ -16,8 +16,9 @@
import classNames from "classnames";
import * as React from "react";
+import { polyfill } from "react-lifecycles-compat";
-import { AbstractPureComponent, Classes, DISPLAYNAME_PREFIX, Intent, IProps, MaybeElement } from "../../common";
+import { AbstractPureComponent2, Classes, DISPLAYNAME_PREFIX, Intent, IProps, MaybeElement } from "../../common";
import {
ALERT_WARN_CANCEL_ESCAPE_KEY,
ALERT_WARN_CANCEL_OUTSIDE_CLICK,
@@ -61,7 +62,7 @@ export interface IAlertProps extends IOverlayLifecycleProps, IProps {
icon?: IconName | MaybeElement;
/**
- * The intent to be applied to the confirm (right-most) button.
+ * The intent to be applied to the confirm (right-most) button and the icon (if provided).
*/
intent?: Intent;
@@ -117,7 +118,8 @@ export interface IAlertProps extends IOverlayLifecycleProps, IProps {
onClose?(confirmed: boolean, evt?: React.SyntheticEvent): void;
}
-export class Alert extends AbstractPureComponent {
+@polyfill
+export class Alert extends AbstractPureComponent2 {
public static defaultProps: IAlertProps = {
canEscapeKeyCancel: false,
canOutsideClickCancel: false,
diff --git a/packages/core/src/components/breadcrumbs/_breadcrumbs.scss b/packages/core/src/components/breadcrumbs/_breadcrumbs.scss
index fd172ecc00..6c1c07e586 100644
--- a/packages/core/src/components/breadcrumbs/_breadcrumbs.scss
+++ b/packages/core/src/components/breadcrumbs/_breadcrumbs.scss
@@ -53,7 +53,8 @@ Styleguide breadcrumbs
.#{$ns}-breadcrumb,
.#{$ns}-breadcrumb-current,
.#{$ns}-breadcrumbs-collapsed {
- display: inline-block;
+ display: inline-flex;
+ align-items: center;
font-size: $pt-font-size-large;
}
@@ -71,6 +72,10 @@ Styleguide breadcrumbs
cursor: not-allowed;
color: $pt-text-color-disabled;
}
+
+ .#{$ns}-icon {
+ margin-right: $pt-grid-size / 2;
+ }
}
.#{$ns}-breadcrumb-current {
diff --git a/packages/core/src/components/breadcrumbs/breadcrumb.tsx b/packages/core/src/components/breadcrumbs/breadcrumb.tsx
index 1a788baea8..e89b9ddc06 100644
--- a/packages/core/src/components/breadcrumbs/breadcrumb.tsx
+++ b/packages/core/src/components/breadcrumbs/breadcrumb.tsx
@@ -19,6 +19,7 @@ import * as React from "react";
import * as Classes from "../../common/classes";
import { IActionProps, ILinkProps } from "../../common/props";
+import { Icon } from "../icon/icon";
export interface IBreadcrumbProps extends IActionProps, ILinkProps {
/** Whether this breadcrumb is the current breadcrumb. */
@@ -34,9 +35,13 @@ export const Breadcrumb: React.SFC = breadcrumbProps => {
},
breadcrumbProps.className,
);
+
+ const icon = breadcrumbProps.icon != null ? : undefined;
+
if (breadcrumbProps.href == null && breadcrumbProps.onClick == null) {
return (
+ {icon}
{breadcrumbProps.text}
{breadcrumbProps.children}
@@ -50,6 +55,7 @@ export const Breadcrumb: React.SFC = breadcrumbProps => {
tabIndex={breadcrumbProps.disabled ? null : 0}
target={breadcrumbProps.target}
>
+ {icon}
{breadcrumbProps.text}
{breadcrumbProps.children}
diff --git a/packages/core/src/components/breadcrumbs/breadcrumbs.tsx b/packages/core/src/components/breadcrumbs/breadcrumbs.tsx
index 1c21173ef3..cc1641ddd3 100644
--- a/packages/core/src/components/breadcrumbs/breadcrumbs.tsx
+++ b/packages/core/src/components/breadcrumbs/breadcrumbs.tsx
@@ -16,11 +16,9 @@
import classNames from "classnames";
import * as React from "react";
+import { polyfill } from "react-lifecycles-compat";
-import { Boundary } from "../../common/boundary";
-import * as Classes from "../../common/classes";
-import { Position } from "../../common/position";
-import { IProps } from "../../common/props";
+import { AbstractPureComponent2, Boundary, Classes, IProps, Position, removeNonHTMLProps } from "../../common";
import { Menu } from "../menu/menu";
import { MenuItem } from "../menu/menuItem";
import { IOverflowListProps, OverflowList } from "../overflow-list/overflowList";
@@ -76,7 +74,8 @@ export interface IBreadcrumbsProps extends IProps {
popoverProps?: IPopoverProps;
}
-export class Breadcrumbs extends React.PureComponent {
+@polyfill
+export class Breadcrumbs extends AbstractPureComponent2 {
public static defaultProps: Partial = {
collapseFrom: Boundary.START,
};
@@ -120,7 +119,8 @@ export class Breadcrumbs extends React.PureComponent {
private renderOverflowBreadcrumb = (props: IBreadcrumbProps, index: number) => {
const isClickable = props.href != null || props.onClick != null;
- return ;
+ const htmlProps = removeNonHTMLProps(props);
+ return ;
};
private renderBreadcrumbWrapper = (props: IBreadcrumbProps, index: number) => {
@@ -134,7 +134,8 @@ export class Breadcrumbs extends React.PureComponent {
} else if (this.props.breadcrumbRenderer != null) {
return this.props.breadcrumbRenderer(props);
} else {
- return ;
+ // allow user to override 'current' prop
+ return ;
}
}
}
diff --git a/packages/core/src/components/button/_button.scss b/packages/core/src/components/button/_button.scss
index 0af068efae..4f706a6ceb 100644
--- a/packages/core/src/components/button/_button.scss
+++ b/packages/core/src/components/button/_button.scss
@@ -19,6 +19,7 @@ Markup:
.#{$ns}-intent-warning - Warning intent
.#{$ns}-intent-danger - Danger intent
.#{$ns}-minimal - More subtle appearance
+.#{$ns}-outlined - Subtle yet structured appearance
.#{$ns}-large - Larger size
.#{$ns}-small - Smaller size
.#{$ns}-fill - Fill parent container
@@ -46,12 +47,16 @@ Styleguide button
width: 100%;
}
- // default is `text-align: left` so we only need `right` case.
&.#{$ns}-align-right,
.#{$ns}-align-right & {
text-align: right;
}
+ &.#{$ns}-align-left,
+ .#{$ns}-align-left & {
+ text-align: left;
+ }
+
// default styles
&:not([class*="#{$ns}-intent-"]) {
@include pt-button();
@@ -160,6 +165,12 @@ Styleguide button
&.#{$ns}-minimal {
@include pt-button-minimal();
}
+
+ // outline is based on the styles of minimal
+ &.#{$ns}-outlined {
+ @include pt-button-minimal();
+ @include pt-button-outlined();
+ }
}
a.#{$ns}-button {
diff --git a/packages/core/src/components/button/_common.scss b/packages/core/src/components/button/_common.scss
index a6a9ff0e84..f88993b93e 100644
--- a/packages/core/src/components/button/_common.scss
+++ b/packages/core/src/components/button/_common.scss
@@ -85,6 +85,10 @@ $dark-minimal-button-background-color: none !default;
$dark-minimal-button-background-color-hover: rgba($gray3, 0.15) !default;
$dark-minimal-button-background-color-active: rgba($gray3, 0.3) !default;
+$button-outlined-width: 1px !default;
+$button-outlined-border-intent-opacity: 0.6 !default;
+$button-outlined-border-disabled-intent-opacity: 0.2 !default;
+
// "intent": (default, hover, active colors)
$button-intents: (
"primary": ($pt-intent-primary, $blue2, $blue1),
@@ -448,3 +452,56 @@ $button-intents: (
}
}
+@mixin pt-button-outlined() {
+ border: $button-outlined-width solid rgba($pt-text-color, 0.2);
+ box-sizing: border-box;
+
+ &:disabled,
+ &.#{$ns}-disabled,
+ &:disabled:hover,
+ &.#{$ns}-disabled:hover {
+ border-color: rgba($pt-text-color-disabled, 0.1);
+ }
+
+ .#{$ns}-dark & {
+ @include pt-dark-button-outlined();
+ }
+
+ @each $intent, $colors in $button-intents {
+ &.#{$ns}-intent-#{$intent} {
+ @include pt-button-outlined-intent(
+ map-get($pt-intent-text-colors, $intent),
+ map-get($pt-dark-intent-text-colors, $intent)
+ );
+ }
+ }
+}
+
+@mixin pt-dark-button-outlined() {
+ border-color: rgba($white, 0.4);
+
+ &:disabled,
+ &:disabled:hover,
+ &.#{$ns}-disabled,
+ &.#{$ns}-disabled:hover {
+ border-color: rgba($white, 0.2);
+ }
+}
+
+@mixin pt-button-outlined-intent($text-color, $dark-text-color) {
+ border-color: rgba($text-color, $button-outlined-border-intent-opacity);
+
+ &:disabled,
+ &.#{$ns}-disabled {
+ border-color: rgba($text-color, $button-outlined-border-disabled-intent-opacity);
+ }
+
+ .#{$ns}-dark & {
+ border-color: rgba($dark-text-color, $button-outlined-border-intent-opacity);
+
+ &:disabled,
+ &.#{$ns}-disabled {
+ border-color: rgba($dark-text-color, $button-outlined-border-disabled-intent-opacity);
+ }
+ }
+}
diff --git a/packages/core/src/components/button/abstractButton.tsx b/packages/core/src/components/button/abstractButton.tsx
index d4f237e83b..0dc9e3c5d9 100644
--- a/packages/core/src/components/button/abstractButton.tsx
+++ b/packages/core/src/components/button/abstractButton.tsx
@@ -17,11 +17,7 @@
import classNames from "classnames";
import * as React from "react";
-import { Alignment } from "../../common/alignment";
-import * as Classes from "../../common/classes";
-import * as Keys from "../../common/keys";
-import { IActionProps, MaybeElement } from "../../common/props";
-import { isReactNodeEmpty, safeInvoke } from "../../common/utils";
+import { AbstractPureComponent2, Alignment, Classes, IActionProps, Keys, MaybeElement, Utils } from "../../common";
import { Icon, IconName } from "../icon/icon";
import { Spinner } from "../spinner/spinner";
@@ -61,6 +57,9 @@ export interface IButtonProps extends IActionProps {
/** Whether this button should use minimal styles. */
minimal?: boolean;
+ /** Whether this button should use outlined styles. */
+ outlined?: boolean;
+
/** Name of a Blueprint UI icon (or an icon element) to render after the text. */
rightIcon?: IconName | MaybeElement;
@@ -68,18 +67,18 @@ export interface IButtonProps extends IActionProps {
small?: boolean;
/**
- * HTML `type` attribute of button. Common values are `"button"` and `"submit"`.
+ * HTML `type` attribute of button. Accepted values are `"button"`, `"submit"`, and `"reset"`.
* Note that this prop has no effect on `AnchorButton`; it only affects `Button`.
* @default "button"
*/
- type?: string;
+ type?: "submit" | "reset" | "button";
}
export interface IButtonState {
isActive: boolean;
}
-export abstract class AbstractButton> extends React.PureComponent<
+export abstract class AbstractButton> extends AbstractPureComponent2<
IButtonProps & H,
IButtonState
> {
@@ -91,7 +90,7 @@ export abstract class AbstractButton> extend
protected refHandlers = {
button: (ref: HTMLElement) => {
this.buttonRef = ref;
- safeInvoke(this.props.elementRef, ref);
+ Utils.safeInvoke(this.props.elementRef, ref);
},
};
@@ -100,7 +99,7 @@ export abstract class AbstractButton> extend
public abstract render(): JSX.Element;
protected getCommonButtonProps() {
- const { alignText, fill, large, loading, minimal, small, tabIndex } = this.props;
+ const { alignText, fill, large, loading, outlined, minimal, small, tabIndex } = this.props;
const disabled = this.props.disabled || loading;
const className = classNames(
@@ -112,6 +111,7 @@ export abstract class AbstractButton> extend
[Classes.LARGE]: large,
[Classes.LOADING]: loading,
[Classes.MINIMAL]: minimal,
+ [Classes.OUTLINED]: outlined,
[Classes.SMALL]: small,
},
Classes.alignmentClass(alignText),
@@ -142,7 +142,7 @@ export abstract class AbstractButton> extend
}
}
this.currentKeyDown = e.which;
- safeInvoke(this.props.onKeyDown, e);
+ Utils.safeInvoke(this.props.onKeyDown, e);
};
protected handleKeyUp = (e: React.KeyboardEvent) => {
@@ -151,7 +151,7 @@ export abstract class AbstractButton> extend
this.buttonRef.click();
}
this.currentKeyDown = null;
- safeInvoke(this.props.onKeyUp, e);
+ Utils.safeInvoke(this.props.onKeyUp, e);
};
protected renderChildren(): React.ReactNode {
@@ -159,7 +159,7 @@ export abstract class AbstractButton> extend
return [
loading && ,
,
- (!isReactNodeEmpty(text) || !isReactNodeEmpty(children)) && (
+ (!Utils.isReactNodeEmpty(text) || !Utils.isReactNodeEmpty(children)) && (
{text}
{children}
diff --git a/packages/core/src/components/button/button.md b/packages/core/src/components/button/button.md
index 484969917e..124fab0fc0 100644
--- a/packages/core/src/components/button/button.md
+++ b/packages/core/src/components/button/button.md
@@ -21,13 +21,17 @@ Buttons trigger actions when clicked.
```
-
Disabled `Button` prevents all interaction
- Use `AnchorButton` if you need mouse interaction events (such as hovering) on a disabled button.
+
- `Button` uses the native `disabled` attribute on the `
+
+Use `AnchorButton` if you need mouse interaction events (such as hovering) on a disabled button.
+
+`Button` uses the native `disabled` attribute on the `
diff --git a/packages/core/src/components/button/buttonGroup.tsx b/packages/core/src/components/button/buttonGroup.tsx
index a83652bc12..b1fdabc152 100644
--- a/packages/core/src/components/button/buttonGroup.tsx
+++ b/packages/core/src/components/button/buttonGroup.tsx
@@ -16,8 +16,9 @@
import classNames from "classnames";
import * as React from "react";
-import { Alignment } from "../../common/alignment";
-import * as Classes from "../../common/classes";
+import { polyfill } from "react-lifecycles-compat";
+
+import { AbstractPureComponent2, Alignment, Classes } from "../../common";
import { DISPLAYNAME_PREFIX, HTMLDivProps, IProps } from "../../common/props";
export interface IButtonGroupProps extends IProps, HTMLDivProps {
@@ -56,7 +57,8 @@ export interface IButtonGroupProps extends IProps, HTMLDivProps {
// this component is simple enough that tests would be purely tautological.
/* istanbul ignore next */
-export class ButtonGroup extends React.PureComponent {
+@polyfill
+export class ButtonGroup extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.ButtonGroup`;
public render() {
diff --git a/packages/core/src/components/callout/_callout.scss b/packages/core/src/components/callout/_callout.scss
index 1c0a1c99c6..dc82dda3bd 100644
--- a/packages/core/src/components/callout/_callout.scss
+++ b/packages/core/src/components/callout/_callout.scss
@@ -9,7 +9,7 @@ Callout
Markup:
Callout Heading
- Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ex, delectus!
+ It's dangerous to go alone! Take this.
.#{$ns}-intent-primary - Primary intent
diff --git a/packages/core/src/components/callout/callout.tsx b/packages/core/src/components/callout/callout.tsx
index fdd4553d18..495e1065dd 100644
--- a/packages/core/src/components/callout/callout.tsx
+++ b/packages/core/src/components/callout/callout.tsx
@@ -16,11 +16,20 @@
import classNames from "classnames";
import * as React from "react";
+import { polyfill } from "react-lifecycles-compat";
-import { Classes, DISPLAYNAME_PREFIX, HTMLDivProps, IIntentProps, Intent, IProps, MaybeElement } from "../../common";
-import { Icon } from "../../index";
+import {
+ AbstractPureComponent2,
+ Classes,
+ DISPLAYNAME_PREFIX,
+ HTMLDivProps,
+ IIntentProps,
+ Intent,
+ IProps,
+ MaybeElement,
+} from "../../common";
import { H4 } from "../html/html";
-import { IconName } from "../icon/icon";
+import { Icon, IconName } from "../icon/icon";
/** This component also supports the full range of HTML `
` props. */
export interface ICalloutProps extends IIntentProps, IProps, HTMLDivProps {
@@ -50,7 +59,8 @@ export interface ICalloutProps extends IIntentProps, IProps, HTMLDivProps {
title?: string;
}
-export class Callout extends React.PureComponent {
+@polyfill
+export class Callout extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.Callout`;
public render() {
diff --git a/packages/core/src/components/card/card.tsx b/packages/core/src/components/card/card.tsx
index 2925b6cead..6abe9261c5 100644
--- a/packages/core/src/components/card/card.tsx
+++ b/packages/core/src/components/card/card.tsx
@@ -16,8 +16,8 @@
import classNames from "classnames";
import * as React from "react";
-import * as Classes from "../../common/classes";
-import { Elevation } from "../../common/elevation";
+import { polyfill } from "react-lifecycles-compat";
+import { AbstractPureComponent2, Classes, Elevation } from "../../common";
import { DISPLAYNAME_PREFIX, HTMLDivProps, IProps } from "../../common/props";
export interface ICardProps extends IProps, HTMLDivProps {
@@ -48,7 +48,8 @@ export interface ICardProps extends IProps, HTMLDivProps {
onClick?: (e: React.MouseEvent) => void;
}
-export class Card extends React.PureComponent {
+@polyfill
+export class Card extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.Card`;
public static defaultProps: ICardProps = {
elevation: Elevation.ZERO,
diff --git a/packages/core/src/components/collapse/collapse.md b/packages/core/src/components/collapse/collapse.md
index e346c819db..59968d9df8 100644
--- a/packages/core/src/components/collapse/collapse.md
+++ b/packages/core/src/components/collapse/collapse.md
@@ -32,9 +32,9 @@ export class CollapseExample extends React.Component<{}, ICollapseExampleState>
{this.state.isOpen ? "Hide" : "Show"} build logs
-
+
Dummy text.
-
+
);
diff --git a/packages/core/src/components/collapse/collapse.tsx b/packages/core/src/components/collapse/collapse.tsx
index 6292ebf9a4..e9969ce190 100644
--- a/packages/core/src/components/collapse/collapse.tsx
+++ b/packages/core/src/components/collapse/collapse.tsx
@@ -16,9 +16,8 @@
import classNames from "classnames";
import * as React from "react";
-
-import { AbstractPureComponent } from "../../common/abstractPureComponent";
-import * as Classes from "../../common/classes";
+import { polyfill } from "react-lifecycles-compat";
+import { AbstractPureComponent2, Classes } from "../../common";
import { DISPLAYNAME_PREFIX, IProps } from "../../common/props";
export interface ICollapseProps extends IProps {
@@ -53,11 +52,16 @@ export interface ICollapseProps extends IProps {
}
export interface ICollapseState {
- /** The height that should be used for the content animations. This is a CSS value, not just a number. */
- height: string;
-
/** The state the element is currently in. */
animationState: AnimationStates;
+
+ /** The height that should be used for the content animations. This is a CSS value, not just a number. */
+ height: string | undefined;
+
+ /**
+ * The most recent non-zero height (once a height has been measured upon first open; it is undefined until then)
+ */
+ heightWhenOpen: number | undefined;
}
/**
@@ -105,7 +109,8 @@ export enum AnimationStates {
CLOSED,
}
-export class Collapse extends AbstractPureComponent {
+@polyfill
+export class Collapse extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.Collapse`;
public static defaultProps: ICollapseProps = {
@@ -115,31 +120,46 @@ export class Collapse extends AbstractPureComponent this.onDelayedStateChange(), transitionDuration);
+ } else if (animationState === AnimationStates.CLOSING_START) {
this.setTimeout(() =>
this.setState({
animationState: AnimationStates.CLOSING,
height: "0px",
}),
);
- this.setTimeout(() => this.onDelayedStateChange(), this.props.transitionDuration);
- }
- if (this.state.animationState === AnimationStates.OPEN_START) {
- this.setState({
- animationState: AnimationStates.OPENING,
- height: this.height + "px",
- });
- this.setTimeout(() => this.onDelayedStateChange(), this.props.transitionDuration);
+ this.setTimeout(() => this.onDelayedStateChange(), transitionDuration);
}
}
private contentsRefHandler = (el: HTMLElement) => {
this.contents = el;
- if (el != null) {
- this.height = this.contents.clientHeight;
+ if (this.contents != null) {
+ const height = this.contents.clientHeight;
this.setState({
animationState: this.props.isOpen ? AnimationStates.OPEN : AnimationStates.CLOSED,
- height: `${this.height}px`,
+ height: height === 0 ? undefined : `${height}px`,
+ heightWhenOpen: height === 0 ? undefined : height,
});
}
};
diff --git a/packages/core/src/components/collapsible-list/collapsible-list.md b/packages/core/src/components/collapsible-list/collapsible-list.md
index 86d13d1678..4c888bce64 100644
--- a/packages/core/src/components/collapsible-list/collapsible-list.md
+++ b/packages/core/src/components/collapsible-list/collapsible-list.md
@@ -7,18 +7,26 @@ customizing the appearance of visible items, using the props from the `MenuItem`
children.
-
Deprecated: use [Overflow list](#core/components/overflow-list)
- This component is **deprecated since 3.0.0** with the introduction of
- [`OverflowList`](#core/components/overflow-list) which provides a similar
- experience with two distinct advantages:
-
-
Items collapse automatically based on available space in the container.
-
- `OverflowList` accepts a generic array of items (instead of explicit
- `
-
+
+
+Deprecated: use [Overflow list](#core/components/overflow-list)
+
+
+This component is **deprecated since 3.0.0** with the introduction of
+[`OverflowList`](#core/components/overflow-list) which provides a similar
+experience with two distinct advantages:
+
+
+
Items collapse automatically based on available space in the container.
+
+
+`OverflowList` accepts a generic array of items (instead of explicit
+`
+
+
@reactExample CollapsibleListExample
diff --git a/packages/core/src/components/context-menu/contextMenu.tsx b/packages/core/src/components/context-menu/contextMenu.tsx
index 45ba2b9732..47d466dc33 100644
--- a/packages/core/src/components/context-menu/contextMenu.tsx
+++ b/packages/core/src/components/context-menu/contextMenu.tsx
@@ -17,10 +17,8 @@
import classNames from "classnames";
import * as React from "react";
import * as ReactDOM from "react-dom";
-
-import { AbstractPureComponent } from "../../common/abstractPureComponent";
-import * as Classes from "../../common/classes";
-import { Position } from "../../common/position";
+import { polyfill } from "react-lifecycles-compat";
+import { AbstractPureComponent2, Classes, Position } from "../../common";
import { safeInvoke } from "../../common/utils";
import { IOverlayLifecycleProps } from "../overlay/overlay";
import { Popover } from "../popover/popover";
@@ -47,7 +45,8 @@ const TRANSITION_DURATION = 100;
type IContextMenuProps = IOverlayLifecycleProps;
/* istanbul ignore next */
-class ContextMenu extends AbstractPureComponent {
+@polyfill
+class ContextMenu extends AbstractPureComponent2 {
public state: IContextMenuState = {
isDarkTheme: false,
isOpen: false,
diff --git a/packages/core/src/components/dialog/dialog.md b/packages/core/src/components/dialog/dialog.md
index 05e5fb8837..919f6b9fa4 100644
--- a/packages/core/src/components/dialog/dialog.md
+++ b/packages/core/src/components/dialog/dialog.md
@@ -4,11 +4,13 @@ Dialogs present content overlaid over other parts of the UI.
Terminology note
- The term "modal" is sometimes used to mean "dialog," but this is a misnomer.
- _Modal_ is an adjective that describes parts of a UI.
- An element is considered modal if it
- [blocks interaction with the rest of the application](https://en.wikipedia.org/wiki/Modal_window).
- We use the term "dialog" to avoid confusion with the adjective.
+
+The term "modal" is sometimes used to mean "dialog," but this is a misnomer.
+_Modal_ is an adjective that describes parts of a UI.
+An element is considered modal if it
+[blocks interaction with the rest of the application](https://en.wikipedia.org/wiki/Modal_window).
+We use the term "dialog" to avoid confusion with the adjective.
+
@reactExample DialogExample
diff --git a/packages/core/src/components/dialog/dialog.tsx b/packages/core/src/components/dialog/dialog.tsx
index 51fbbbf679..783ed55983 100644
--- a/packages/core/src/components/dialog/dialog.tsx
+++ b/packages/core/src/components/dialog/dialog.tsx
@@ -16,9 +16,8 @@
import classNames from "classnames";
import * as React from "react";
-
-import { AbstractPureComponent } from "../../common/abstractPureComponent";
-import * as Classes from "../../common/classes";
+import { polyfill } from "react-lifecycles-compat";
+import { AbstractPureComponent2, Classes } from "../../common";
import * as Errors from "../../common/errors";
import { DISPLAYNAME_PREFIX, IProps, MaybeElement } from "../../common/props";
import { Button } from "../button/buttons";
@@ -72,7 +71,8 @@ export interface IDialogProps extends IOverlayableProps, IBackdropProps, IProps
transitionName?: string;
}
-export class Dialog extends AbstractPureComponent {
+@polyfill
+export class Dialog extends AbstractPureComponent2 {
public static defaultProps: IDialogProps = {
canOutsideClickClose: true,
isOpen: false,
diff --git a/packages/core/src/components/divider/divider.tsx b/packages/core/src/components/divider/divider.tsx
index 731fb36315..4230d5ddbf 100644
--- a/packages/core/src/components/divider/divider.tsx
+++ b/packages/core/src/components/divider/divider.tsx
@@ -16,7 +16,8 @@
import classNames from "classnames";
import * as React from "react";
-
+import { polyfill } from "react-lifecycles-compat";
+import { AbstractPureComponent2 } from "../../common";
import { DIVIDER } from "../../common/classes";
import { DISPLAYNAME_PREFIX, IProps } from "../../common/props";
@@ -30,12 +31,16 @@ export interface IDividerProps extends IProps, React.HTMLAttributes
// this component is simple enough that tests would be purely tautological.
/* istanbul ignore next */
-export class Divider extends React.PureComponent {
+@polyfill
+export class Divider extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.Divider`;
public render() {
- const { className, tagName: TagName = "div", ...htmlProps } = this.props;
+ const { className, tagName = "div", ...htmlProps } = this.props;
const classes = classNames(DIVIDER, className);
- return ;
+ return React.createElement(tagName, {
+ ...htmlProps,
+ className: classes,
+ });
}
}
diff --git a/packages/core/src/components/drawer/_drawer.scss b/packages/core/src/components/drawer/_drawer.scss
index d98142f502..500c435b32 100644
--- a/packages/core/src/components/drawer/_drawer.scss
+++ b/packages/core/src/components/drawer/_drawer.scss
@@ -11,12 +11,15 @@ $drawer-padding: $pt-grid-size * 2 !default;
$drawer-default-size: 50%;
+$drawer-background-color: $white !default;
+$dark-drawer-background-color: $dark-gray4 !default;
+
.#{$ns}-drawer {
display: flex;
flex-direction: column;
margin: 0;
box-shadow: $pt-elevation-shadow-4;
- background: $white;
+ background: $drawer-background-color;
padding: 0;
&:focus {
@@ -167,7 +170,7 @@ $drawer-default-size: 50%;
&.#{$ns}-dark,
.#{$ns}-dark & {
box-shadow: $pt-dark-dialog-box-shadow;
- background: $dark-gray4;
+ background: $dark-drawer-background-color;
color: $pt-dark-text-color;
}
}
@@ -179,14 +182,14 @@ $drawer-default-size: 50%;
position: relative;
border-radius: 0;
box-shadow: 0 1px 0 $pt-divider-black;
- min-height: $pt-icon-size-large + $dialog-padding;
- padding: $dialog-padding / 4;
- padding-left: $dialog-padding;
+ min-height: $pt-icon-size-large + $drawer-padding;
+ padding: $drawer-padding / 4;
+ padding-left: $drawer-padding;
.#{$ns}-icon-large,
.#{$ns}-icon {
flex: 0 0 auto;
- margin-right: $dialog-padding / 2;
+ margin-right: $drawer-padding / 2;
color: $pt-icon-color;
}
@@ -197,7 +200,7 @@ $drawer-default-size: 50%;
line-height: inherit;
&:last-child {
- margin-right: $dialog-padding;
+ margin-right: $drawer-padding;
}
}
@@ -221,7 +224,7 @@ $drawer-default-size: 50%;
flex: 0 0 auto;
position: relative;
box-shadow: inset 0 1px 0 $pt-divider-black;
- padding: $dialog-padding/2 $dialog-padding;
+ padding: $drawer-padding/2 $drawer-padding;
.#{$ns}-dark & {
box-shadow: inset 0 1px 0 $pt-dark-divider-black;
diff --git a/packages/core/src/components/drawer/drawer.tsx b/packages/core/src/components/drawer/drawer.tsx
index dbdebb43f3..421af8a3c4 100644
--- a/packages/core/src/components/drawer/drawer.tsx
+++ b/packages/core/src/components/drawer/drawer.tsx
@@ -16,9 +16,8 @@
import classNames from "classnames";
import * as React from "react";
-
-import { AbstractPureComponent } from "../../common/abstractPureComponent";
-import * as Classes from "../../common/classes";
+import { polyfill } from "react-lifecycles-compat";
+import { AbstractPureComponent2, Classes } from "../../common";
import * as Errors from "../../common/errors";
import { getPositionIgnoreAngles, isPositionHorizontal, Position } from "../../common/position";
import { DISPLAYNAME_PREFIX, IProps, MaybeElement } from "../../common/props";
@@ -95,7 +94,8 @@ export interface IDrawerProps extends IOverlayableProps, IBackdropProps, IProps
vertical?: boolean;
}
-export class Drawer extends AbstractPureComponent {
+@polyfill
+export class Drawer extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.Drawer`;
public static defaultProps: IDrawerProps = {
canOutsideClickClose: true,
diff --git a/packages/core/src/components/editable-text/editable-text.md b/packages/core/src/components/editable-text/editable-text.md
index 11624b59ee..99301fdeaf 100644
--- a/packages/core/src/components/editable-text/editable-text.md
+++ b/packages/core/src/components/editable-text/editable-text.md
@@ -15,9 +15,11 @@ You should not use `EditableText` when a static always-editable `` or
Centering the component
- **Do not center this component** using `text-align: center`, as it will cause an infinite loop
- in the browser ([more details](https://github.com/JedWatson/react-select/issues/540)). Instead,
- you should center the component via flexbox or with `position` and `transform: translateX(-50%)`.
+
+**Do not center this component** using `text-align: center`, as it will cause an infinite loop
+in the browser ([more details](https://github.com/JedWatson/react-select/issues/540)). Instead,
+you should center the component via flexbox or with `position` and `transform: translateX(-50%)`.
+
diff --git a/packages/core/src/components/editable-text/editableText.tsx b/packages/core/src/components/editable-text/editableText.tsx
index 7f02230f2c..c5db4e0876 100644
--- a/packages/core/src/components/editable-text/editableText.tsx
+++ b/packages/core/src/components/editable-text/editableText.tsx
@@ -16,15 +16,26 @@
import classNames from "classnames";
import * as React from "react";
-
-import { AbstractPureComponent } from "../../common/abstractPureComponent";
-import * as Classes from "../../common/classes";
-import * as Keys from "../../common/keys";
+import { polyfill } from "react-lifecycles-compat";
+import { AbstractPureComponent2, Classes, Keys } from "../../common";
import { DISPLAYNAME_PREFIX, IIntentProps, IProps } from "../../common/props";
import { clamp, safeInvoke } from "../../common/utils";
import { Browser } from "../../compatibility";
export interface IEditableTextProps extends IIntentProps, IProps {
+ /**
+ * EXPERIMENTAL FEATURE.
+ *
+ * When true, this forces the component to _always_ render an editable input (or textarea)
+ * both when the component is focussed and unfocussed, instead of the component's default
+ * behavior of switching between a text span and a text input upon interaction.
+ *
+ * This behavior can help in certain applications where, for example, a custom right-click
+ * context menu is used to supply clipboard copy and paste functionality.
+ * @default false
+ */
+ alwaysRenderInput?: boolean;
+
/**
* If `true` and in multiline mode, the `enter` key will trigger onConfirm and `mod+enter`
* will insert a newline. If `false`, the key bindings are inverted such that `enter`
@@ -78,6 +89,7 @@ export interface IEditableTextProps extends IIntentProps, IProps {
/**
* Whether the entire text field should be selected on focus.
* If `false`, the cursor is placed at the end of the text.
+ * This prop is ignored on inputs with type other then text, search, url, tel and password. See https://html.spec.whatwg.org/multipage/input.html#do-not-apply for details.
* @default false
*/
selectAllOnFocus?: boolean;
@@ -119,10 +131,12 @@ export interface IEditableTextState {
const BUFFER_WIDTH_EDGE = 5;
const BUFFER_WIDTH_IE = 30;
-export class EditableText extends AbstractPureComponent {
+@polyfill
+export class EditableText extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.EditableText`;
public static defaultProps: IEditableTextProps = {
+ alwaysRenderInput: false,
confirmOnEnterKey: false,
defaultValue: "",
disabled: false,
@@ -134,6 +148,7 @@ export class EditableText extends AbstractPureComponent {
@@ -141,11 +156,22 @@ export class EditableText extends AbstractPureComponent {
if (input != null) {
- input.focus();
- const { length } = input.value;
- input.setSelectionRange(this.props.selectAllOnFocus ? 0 : length, length);
- if (!this.props.selectAllOnFocus) {
- input.scrollLeft = input.scrollWidth;
+ this.inputElement = input;
+
+ // temporary fix for #3882
+ if (!this.props.alwaysRenderInput) {
+ this.inputElement.focus();
+ }
+
+ if (this.state != null && this.state.isEditing) {
+ const supportsSelection = inputSupportsSelection(input);
+ if (supportsSelection) {
+ const { length } = input.value;
+ input.setSelectionRange(this.props.selectAllOnFocus ? 0 : length, length);
+ }
+ if (!supportsSelection || !this.props.selectAllOnFocus) {
+ input.scrollLeft = input.scrollWidth;
+ }
}
}
},
@@ -165,7 +191,7 @@ export class EditableText extends AbstractPureComponent
- {this.maybeRenderInput(value)}
-
- {hasValue ? value : this.props.placeholder}
-
+ {alwaysRenderInput || this.state.isEditing ? this.renderInput(value) : undefined}
+ {shouldHideContents ? (
+ undefined
+ ) : (
+
+ {hasValue ? value : this.props.placeholder}
+
+ )}
);
}
@@ -212,25 +248,24 @@ export class EditableText extends AbstractPureComponent {
@@ -253,9 +288,16 @@ export class EditableText extends AbstractPureComponent {
- if (!this.props.disabled) {
+ const { alwaysRenderInput, disabled, selectAllOnFocus } = this.props;
+
+ if (!disabled) {
this.setState({ isEditing: true });
}
+
+ if (alwaysRenderInput && selectAllOnFocus && this.inputElement != null) {
+ const { length } = this.inputElement.value;
+ this.inputElement.setSelectionRange(0, length);
+ }
};
private handleTextChange = (event: React.FormEvent) => {
@@ -295,11 +337,8 @@ export class EditableText extends AbstractPureComponent = {
className: Classes.EDITABLE_TEXT_INPUT,
maxLength,
@@ -307,13 +346,18 @@ export class EditableText extends AbstractPureComponent
) : (
@@ -392,3 +436,21 @@ function insertAtCaret(el: HTMLTextAreaElement, text: string) {
el.selectionEnd = selectionStart + len;
}
}
+
+function inputSupportsSelection(input: HTMLInputElement | HTMLTextAreaElement) {
+ switch (input.type) {
+ // HTMLTextAreaElement
+ case "textarea":
+ return true;
+ // HTMLInputElement
+ // see https://html.spec.whatwg.org/multipage/input.html#do-not-apply
+ case "text":
+ case "search":
+ case "tel":
+ case "url":
+ case "password":
+ return true;
+ default:
+ return false;
+ }
+}
diff --git a/packages/core/src/components/forms/_control-group.scss b/packages/core/src/components/forms/_control-group.scss
index c530ee8e56..2fffba6e90 100644
--- a/packages/core/src/components/forms/_control-group.scss
+++ b/packages/core/src/components/forms/_control-group.scss
@@ -100,13 +100,12 @@ Styleguide control-group
.#{$ns}-button,
.#{$ns}-html-select select,
.#{$ns}-select select {
+ @include new-render-layer();
z-index: index($control-group-stack, "button-default");
// inherit radius since it's most likely to be zero
border-radius: inherit;
&:focus {
- // establish new stacking context so focus state covers neighbors
- position: relative;
z-index: index($control-group-stack, "button-focus");
}
diff --git a/packages/core/src/components/forms/_controls.scss b/packages/core/src/components/forms/_controls.scss
index a55d69d969..af18a7eb6f 100644
--- a/packages/core/src/components/forms/_controls.scss
+++ b/packages/core/src/components/forms/_controls.scss
@@ -305,8 +305,8 @@ $control-indicator-spacing: $pt-grid-size !default;
$dark-switch-background-color-active: rgba($black, 0.9) !default;
$dark-switch-background-color-disabled: $dark-button-background-color-disabled !default;
$dark-switch-checked-background-color: $control-checked-background-color !default;
- $dark-switch-checked-background-color-hover: $blue4 !default;
- $dark-switch-checked-background-color-active: $blue5 !default;
+ $dark-switch-checked-background-color-hover: $control-checked-background-color-hover !default;
+ $dark-switch-checked-background-color-active: $control-checked-background-color-active !default;
$dark-switch-checked-background-color-disabled: rgba($blue1, 0.5) !default;
$switch-indicator-background-color: $white !default;
@@ -315,7 +315,14 @@ $control-indicator-spacing: $pt-grid-size !default;
$dark-switch-indicator-background-color-disabled: rgba($black, 0.4) !default;
&.#{$ns}-switch {
- @mixin indicator-colors($selector, $color, $hover-color, $active-color, $disabled-color) {
+ @mixin indicator-colors(
+ $selector,
+ $color,
+ $hover-color,
+ $active-color,
+ $disabled-color,
+ $disabled-indicator-color
+ ) {
input#{$selector} ~ .#{$ns}-control-indicator {
background: $color;
}
@@ -330,6 +337,10 @@ $control-indicator-spacing: $pt-grid-size !default;
input#{$selector}:disabled ~ .#{$ns}-control-indicator {
background: $disabled-color;
+
+ &::before {
+ background: $disabled-indicator-color;
+ }
}
}
@@ -338,14 +349,16 @@ $control-indicator-spacing: $pt-grid-size !default;
$switch-background-color,
$switch-background-color-hover,
$switch-background-color-active,
- $switch-background-color-disabled
+ $switch-background-color-disabled,
+ $switch-indicator-background-color-disabled
);
@include indicator-colors(
":checked",
$switch-checked-background-color,
$switch-checked-background-color-hover,
$switch-checked-background-color-active,
- $switch-checked-background-color-disabled
+ $switch-checked-background-color-disabled,
+ $switch-indicator-background-color-disabled
);
// convert em variable to px value
@include indicator-position($switch-width / 1em * $control-indicator-size);
@@ -388,14 +401,16 @@ $control-indicator-spacing: $pt-grid-size !default;
$dark-switch-background-color,
$dark-switch-background-color-hover,
$dark-switch-background-color-active,
- $dark-switch-background-color-disabled
+ $dark-switch-background-color-disabled,
+ $dark-switch-indicator-background-color-disabled
);
@include indicator-colors(
":checked",
$dark-switch-checked-background-color,
$dark-switch-checked-background-color-hover,
$dark-switch-checked-background-color-active,
- $dark-switch-checked-background-color-disabled
+ $dark-switch-checked-background-color-disabled,
+ $dark-switch-indicator-background-color-disabled
);
.#{$ns}-control-indicator::before {
diff --git a/packages/core/src/components/forms/_file-input.scss b/packages/core/src/components/forms/_file-input.scss
index 69a677e1a6..32db71f651 100644
--- a/packages/core/src/components/forms/_file-input.scss
+++ b/packages/core/src/components/forms/_file-input.scss
@@ -3,6 +3,7 @@
@import "../../common/variables";
@import "../button/common";
+@import "../../common/mixins";
/*
File input
@@ -78,6 +79,10 @@ $file-input-button-padding-large: ($pt-input-height-large - $pt-button-height) /
.#{$ns}-large & {
height: $pt-input-height-large;
}
+
+ .#{$ns}-file-upload-input-custom-text::after {
+ content: attr(#{$ns}-button-text);
+ }
}
.#{$ns}-file-upload-input {
@@ -94,6 +99,7 @@ $file-input-button-padding-large: ($pt-input-height-large - $pt-button-height) /
&::after {
@include pt-button();
@include pt-button-height($pt-button-height-small);
+ @include overflow-ellipsis();
position: absolute;
top: 0;
right: 0;
diff --git a/packages/core/src/components/forms/_label.scss b/packages/core/src/components/forms/_label.scss
index 4edf8da602..974ea1a4d6 100644
--- a/packages/core/src/components/forms/_label.scss
+++ b/packages/core/src/components/forms/_label.scss
@@ -53,6 +53,10 @@ label.#{$ns}-label {
text-transform: none;
}
+ .#{$ns}-button-group {
+ margin-top: $pt-grid-size / 2;
+ }
+
.#{$ns}-select select,
.#{$ns}-html-select select {
width: 100%;
@@ -80,6 +84,10 @@ label.#{$ns}-label {
vertical-align: top;
}
+ .#{$ns}-button-group {
+ margin: 0 0 0 ($pt-grid-size / 2);
+ }
+
.#{$ns}-input-group .#{$ns}-input {
margin-left: 0;
}
diff --git a/packages/core/src/components/forms/control-group.md b/packages/core/src/components/forms/control-group.md
index bb4a86fdc4..9f22a54d37 100644
--- a/packages/core/src/components/forms/control-group.md
+++ b/packages/core/src/components/forms/control-group.md
@@ -6,13 +6,17 @@ groups, and HTML selects as direct children.
Control group vs. input group
-
Both components group multiple elements into a single unit, but their usage patterns are
- quite different.
-
Think of `ControlGroup` as a parent with multiple children, with each one a separate
- control.
-
Conversely, an `InputGroup` is a single control, and should function like so. A
- button inside of an input group should only affect that input; if its reach is further, then it
- should be promoted to live in a control group.
+
+Both components group multiple elements into a single unit, but their usage patterns are
+quite different.
+
+Think of `ControlGroup` as a parent with multiple children, with each one a separate
+control.
+
+Conversely, an `InputGroup` is a single control, and should function like so. A
+button inside of an input group should only affect that input; if its reach is further, then it
+should be promoted to live in a control group.
+
@reactExample ControlGroupExample
diff --git a/packages/core/src/components/forms/controlGroup.tsx b/packages/core/src/components/forms/controlGroup.tsx
index 31cfa8300b..1fdec49994 100644
--- a/packages/core/src/components/forms/controlGroup.tsx
+++ b/packages/core/src/components/forms/controlGroup.tsx
@@ -16,7 +16,8 @@
import classNames from "classnames";
import * as React from "react";
-import * as Classes from "../../common/classes";
+import { polyfill } from "react-lifecycles-compat";
+import { AbstractPureComponent2, Classes } from "../../common";
import { DISPLAYNAME_PREFIX, HTMLDivProps, IProps } from "../../common/props";
export interface IControlGroupProps extends IProps, HTMLDivProps {
@@ -35,7 +36,8 @@ export interface IControlGroupProps extends IProps, HTMLDivProps {
// this component is simple enough that tests would be purely tautological.
/* istanbul ignore next */
-export class ControlGroup extends React.PureComponent {
+@polyfill
+export class ControlGroup extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.ControlGroup`;
public render() {
diff --git a/packages/core/src/components/forms/controls.tsx b/packages/core/src/components/forms/controls.tsx
index bf3878c617..184e4fa6d1 100644
--- a/packages/core/src/components/forms/controls.tsx
+++ b/packages/core/src/components/forms/controls.tsx
@@ -20,9 +20,9 @@
import classNames from "classnames";
import * as React from "react";
+import { polyfill } from "react-lifecycles-compat";
-import { Alignment } from "../../common/alignment";
-import * as Classes from "../../common/classes";
+import { AbstractPureComponent2, Alignment, Classes } from "../../common";
import { DISPLAYNAME_PREFIX, HTMLInputProps, IProps } from "../../common/props";
import { safeInvoke } from "../../common/utils";
@@ -112,7 +112,7 @@ const Control: React.SFC = ({
style,
type,
typeClassName,
- tagName: TagName = "label",
+ tagName = "label",
...htmlProps
}) => {
const classes = classNames(
@@ -126,14 +126,15 @@ const Control: React.SFC = ({
Classes.alignmentClass(alignIndicator),
className,
);
- return (
-
-
- {indicatorChildren}
- {label}
- {labelElement}
- {children}
-
+
+ return React.createElement(
+ tagName,
+ { className: classes, style },
+ ,
+ {indicatorChildren},
+ label,
+ labelElement,
+ children,
);
};
@@ -156,7 +157,8 @@ export interface ISwitchProps extends IControlProps {
innerLabel?: string;
}
-export class Switch extends React.PureComponent {
+@polyfill
+export class Switch extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.Switch`;
public render() {
@@ -191,7 +193,8 @@ export class Switch extends React.PureComponent {
export interface IRadioProps extends IControlProps {}
-export class Radio extends React.PureComponent {
+@polyfill
+export class Radio extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.Radio`;
public render() {
@@ -223,9 +226,19 @@ export interface ICheckboxState {
indeterminate: boolean;
}
-export class Checkbox extends React.PureComponent {
+@polyfill
+export class Checkbox extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.Checkbox`;
+ public static getDerivedStateFromProps({ indeterminate }: ICheckboxProps): ICheckboxState | null {
+ // put props into state if controlled by props
+ if (indeterminate != null) {
+ return { indeterminate };
+ }
+
+ return null;
+ }
+
public state: ICheckboxState = {
indeterminate: this.props.indeterminate || this.props.defaultIndeterminate || false,
};
@@ -246,13 +259,6 @@ export class Checkbox extends React.PureComponent` element.
Static file name
- File name does not update on file selection. To get this behavior,
- you must implement it separately in JS.
+
+File name does not update on file selection. To get this behavior,
+you must implement it separately in JS.
+
```tsx
@@ -24,4 +25,7 @@ Use `inputProps` to apply props to the `` element.
@## CSS
+Use the standard `input type="file"` along with a `span` with class `@ns-file-upload-input`.
+Wrap that all in a `label` with class `@ns-file-input`.
+
@css file-input
diff --git a/packages/core/src/components/forms/fileInput.tsx b/packages/core/src/components/forms/fileInput.tsx
index 041e0e3111..c9d6bf2e1e 100644
--- a/packages/core/src/components/forms/fileInput.tsx
+++ b/packages/core/src/components/forms/fileInput.tsx
@@ -16,8 +16,8 @@
import classNames from "classnames";
import * as React from "react";
-import { Utils } from "../../common";
-import * as Classes from "../../common/classes";
+import { polyfill } from "react-lifecycles-compat";
+import { AbstractPureComponent2, Classes, Utils } from "../../common";
import { DISPLAYNAME_PREFIX, IProps } from "../../common/props";
export interface IFileInputProps extends React.LabelHTMLAttributes, IProps {
@@ -68,11 +68,18 @@ export interface IFileInputProps extends React.LabelHTMLAttributes {
+@polyfill
+export class FileInput extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.FileInput`;
public static defaultProps: IFileInputProps = {
@@ -83,6 +90,7 @@ export class FileInput extends React.PureComponent {
public render() {
const {
+ buttonText,
className,
disabled,
fill,
@@ -105,10 +113,19 @@ export class FileInput extends React.PureComponent {
className,
);
+ const NS = Classes.getClassNamespace();
+
+ const uploadProps = {
+ [`${NS}-button-text`]: buttonText,
+ className: classNames(Classes.FILE_UPLOAD_INPUT, {
+ [Classes.FILE_UPLOAD_INPUT_CUSTOM_TEXT]: !!buttonText,
+ }),
+ };
+
return (
);
}
diff --git a/packages/core/src/components/forms/formGroup.tsx b/packages/core/src/components/forms/formGroup.tsx
index 9f4eced105..6a7d7f36c3 100644
--- a/packages/core/src/components/forms/formGroup.tsx
+++ b/packages/core/src/components/forms/formGroup.tsx
@@ -16,7 +16,8 @@
import classNames from "classnames";
import * as React from "react";
-import * as Classes from "../../common/classes";
+import { polyfill } from "react-lifecycles-compat";
+import { AbstractPureComponent2, Classes } from "../../common";
import { DISPLAYNAME_PREFIX, IIntentProps, IProps } from "../../common/props";
export interface IFormGroupProps extends IIntentProps, IProps {
@@ -60,7 +61,8 @@ export interface IFormGroupProps extends IIntentProps, IProps {
style?: React.CSSProperties;
}
-export class FormGroup extends React.PureComponent {
+@polyfill
+export class FormGroup extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.FormGroup`;
public render() {
diff --git a/packages/core/src/components/forms/inputGroup.tsx b/packages/core/src/components/forms/inputGroup.tsx
index 1d7672e546..6eb9f9a7fa 100644
--- a/packages/core/src/components/forms/inputGroup.tsx
+++ b/packages/core/src/components/forms/inputGroup.tsx
@@ -16,8 +16,8 @@
import classNames from "classnames";
import * as React from "react";
-
-import * as Classes from "../../common/classes";
+import { polyfill } from "react-lifecycles-compat";
+import { AbstractPureComponent2, Classes } from "../../common";
import {
DISPLAYNAME_PREFIX,
HTMLInputProps,
@@ -41,6 +41,11 @@ export interface IInputGroupProps extends IControlledProps, IIntentProps, IProps
*/
disabled?: boolean;
+ /**
+ * Whether the component should take up the full width of its container.
+ */
+ fill?: boolean;
+
/** Ref handler that receives HTML `` element backing this component. */
inputRef?: (ref: HTMLInputElement | null) => any;
@@ -79,7 +84,8 @@ export interface IInputGroupState {
rightElementWidth: number;
}
-export class InputGroup extends React.PureComponent {
+@polyfill
+export class InputGroup extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.InputGroup`;
public state: IInputGroupState = {
@@ -92,12 +98,13 @@ export class InputGroup extends React.PureComponent`.
Prefer form groups over labels
- The [React `FormGroup` component](#core/components/form-group) provides
- additional functionality such as helper text and modifier props as well as
- full label support. `FormGroup` supports both simple and complex use cases,
- therefore we recommend using it exclusively when constructing forms.
+
+The [React `FormGroup` component](#core/components/form-group) provides
+additional functionality such as helper text and modifier props as well as
+full label support. `FormGroup` supports both simple and complex use cases,
+therefore we recommend using it exclusively when constructing forms.
+
@## Props
diff --git a/packages/core/src/components/forms/numeric-input.md b/packages/core/src/components/forms/numeric-input.md
index 6eb123e486..680c935a67 100644
--- a/packages/core/src/components/forms/numeric-input.md
+++ b/packages/core/src/components/forms/numeric-input.md
@@ -43,9 +43,11 @@ custom `onKeyDown` callback) and when the field loses focus (via a custom
trigged, the field will be cleared.
- This example contains non-core functionality that is meant to demonstrate
- the extensibility of the `NumericInput` component. The correctness of the
- custom evaluation code has not been tested robustly.
+
+This example contains non-core functionality that is meant to demonstrate
+the extensibility of the `NumericInput` component. The correctness of the
+custom evaluation code has not been tested robustly.
+
@reactExample NumericInputExtendedExample
@@ -106,7 +108,7 @@ import * as SomeLibrary from "some-library";
export class NumericInputExample extends React.Component<{}, { value?: number |
string }> {
- public state = { value: null };
+ public state = { value: NumericInput.VALUE_EMPTY };
public render() {
return (
diff --git a/packages/core/src/components/forms/numericInput.tsx b/packages/core/src/components/forms/numericInput.tsx
index ddda8fd0e8..5f85893b3b 100644
--- a/packages/core/src/components/forms/numericInput.tsx
+++ b/packages/core/src/components/forms/numericInput.tsx
@@ -16,10 +16,11 @@
import classNames from "classnames";
import * as React from "react";
+import { polyfill } from "react-lifecycles-compat";
import { IconName } from "@blueprintjs/icons";
import {
- AbstractPureComponent,
+ AbstractPureComponent2,
Classes,
DISPLAYNAME_PREFIX,
HTMLInputProps,
@@ -149,10 +150,13 @@ export interface INumericInputProps extends IIntentProps, IProps {
onButtonClick?(valueAsNumber: number, valueAsString: string): void;
/** The callback invoked when the value changes due to typing, arrow keys, or button clicks. */
- onValueChange?(valueAsNumber: number, valueAsString: string): void;
+ onValueChange?(valueAsNumber: number, valueAsString: string, inputElement: HTMLInputElement | null): void;
}
export interface INumericInputState {
+ prevMinProp?: number;
+ prevMaxProp?: number;
+ prevValueProp?: number | string;
shouldSelectAfterUpdate: boolean;
stepMaxPrecision: number;
value: string;
@@ -179,7 +183,8 @@ const NON_HTML_PROPS: Array = [
type ButtonEventHandlers = Required, "onKeyDown" | "onMouseDown">>;
-export class NumericInput extends AbstractPureComponent {
+@polyfill
+export class NumericInput extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.NumericInput`;
public static VALUE_EMPTY = "";
@@ -198,12 +203,56 @@ export class NumericInput extends AbstractPureComponent {
const delta = this.updateDelta(direction, e);
const nextValue = this.incrementValue(delta);
- this.invokeValueCallback(nextValue, this.props.onButtonClick);
+ this.props.onButtonClick?.(+nextValue, nextValue);
};
private startContinuousChange() {
@@ -373,7 +404,7 @@ export class NumericInput extends AbstractPureComponent {
const nextValue = this.incrementValue(this.delta);
- this.invokeValueCallback(nextValue, this.props.onButtonClick);
+ this.props.onButtonClick?.(+nextValue, nextValue);
};
// Callbacks - Input
@@ -393,9 +424,6 @@ export class NumericInput extends AbstractPureComponent void) {
- Utils.safeInvoke(callback, +value, value);
- }
-
- // Value Helpers
- // =============
-
private incrementValue(delta: number) {
// pretend we're incrementing from 0 if currValue is empty
const currValue = this.state.value || NumericInput.VALUE_ZERO;
const nextValue = this.getSanitizedValue(currValue, delta);
this.setState({ shouldSelectAfterUpdate: this.props.selectAllOnIncrement, value: nextValue });
- this.invokeValueCallback(nextValue, this.props.onValueChange);
return nextValue;
}
@@ -492,20 +511,14 @@ export class NumericInput extends AbstractPureComponent {
+@polyfill
+export class RadioGroup extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.RadioGroup`;
// a unique name for this group, which can be overridden by `name` prop.
diff --git a/packages/core/src/components/forms/text-inputs.md b/packages/core/src/components/forms/text-inputs.md
index 79ce8a2f88..b24b76edb6 100644
--- a/packages/core/src/components/forms/text-inputs.md
+++ b/packages/core/src/components/forms/text-inputs.md
@@ -39,12 +39,13 @@ the parent input.
Icons only
-
You cannot use buttons with text in the CSS API for input groups. The padding for text inputs
- in CSS cannot accommodate buttons whose width varies due to text content. You should use icons on
- buttons instead.
- Conversely, the [`InputGroup`](#core/components/text-inputs.input-group) React
- component _does_ support arbitrary content in its right element.
+You cannot use buttons with text in the CSS API for input groups. The padding for text inputs
+in CSS cannot accommodate buttons whose width varies due to text content. You should use icons on
+buttons instead.
+
+Conversely, the [`InputGroup`](#core/components/text-inputs.input-group) React
+component _does_ support arbitrary content in its right element.
diff --git a/packages/core/src/components/forms/textArea.tsx b/packages/core/src/components/forms/textArea.tsx
index c7fcfda241..d4720eb314 100644
--- a/packages/core/src/components/forms/textArea.tsx
+++ b/packages/core/src/components/forms/textArea.tsx
@@ -16,7 +16,8 @@
import classNames from "classnames";
import * as React from "react";
-import * as Classes from "../../common/classes";
+import { polyfill } from "react-lifecycles-compat";
+import { AbstractPureComponent2, Classes } from "../../common";
import { DISPLAYNAME_PREFIX, IIntentProps, IProps } from "../../common/props";
export interface ITextAreaProps extends IIntentProps, IProps, React.TextareaHTMLAttributes {
@@ -52,11 +53,19 @@ export interface ITextAreaState {
// this component is simple enough that tests would be purely tautological.
/* istanbul ignore next */
-export class TextArea extends React.PureComponent {
+@polyfill
+export class TextArea extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.TextArea`;
-
public state: ITextAreaState = {};
+ private internalTextAreaRef: HTMLTextAreaElement;
+ public componentDidMount() {
+ if (this.props.growVertically) {
+ this.setState({
+ height: this.internalTextAreaRef.scrollHeight,
+ });
+ }
+ }
public render() {
const { className, fill, inputRef, intent, large, small, growVertically, ...htmlProps } = this.props;
@@ -87,7 +96,7 @@ export class TextArea extends React.PureComponent
);
@@ -104,4 +113,12 @@ export class TextArea extends React.PureComponent {
+ this.internalTextAreaRef = ref;
+ if (this.props.inputRef != null) {
+ this.props.inputRef(ref);
+ }
+ };
}
diff --git a/packages/core/src/components/hotkeys/hotkey.tsx b/packages/core/src/components/hotkeys/hotkey.tsx
index a68aedc1c2..ee6de4d5ee 100644
--- a/packages/core/src/components/hotkeys/hotkey.tsx
+++ b/packages/core/src/components/hotkeys/hotkey.tsx
@@ -16,8 +16,8 @@
import classNames from "classnames";
import * as React from "react";
-
-import { AbstractPureComponent, Classes, DISPLAYNAME_PREFIX, IProps } from "../../common";
+import { polyfill } from "react-lifecycles-compat";
+import { AbstractPureComponent2, Classes, DISPLAYNAME_PREFIX, IProps } from "../../common";
import { KeyCombo } from "./keyCombo";
export interface IHotkeyProps extends IProps {
@@ -83,7 +83,8 @@ export interface IHotkeyProps extends IProps {
onKeyUp?(e: KeyboardEvent): any;
}
-export class Hotkey extends AbstractPureComponent {
+@polyfill
+export class Hotkey extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.Hotkey`;
public static defaultProps = {
diff --git a/packages/core/src/components/hotkeys/hotkeys.md b/packages/core/src/components/hotkeys/hotkeys.md
index 71db766017..898019fd44 100644
--- a/packages/core/src/components/hotkeys/hotkeys.md
+++ b/packages/core/src/components/hotkeys/hotkeys.md
@@ -43,6 +43,14 @@ export class MyComponent extends React.Component<{}, {}> {
}
```
+
+
+Your decorated component must return a single DOM element in its `render()` method,
+not a custom React component. This constraint allows `HotkeysTarget` to inject
+event handlers without creating an extra wrapper element.
+
+
+
@### Decorator
The `@HotkeysTarget` decorator allows you to easily add global and local
diff --git a/packages/core/src/components/hotkeys/hotkeys.tsx b/packages/core/src/components/hotkeys/hotkeys.tsx
index 4f39bfb1e7..e04778b559 100644
--- a/packages/core/src/components/hotkeys/hotkeys.tsx
+++ b/packages/core/src/components/hotkeys/hotkeys.tsx
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import * as React from "react";
-
import classNames from "classnames";
-import { AbstractPureComponent, Classes, DISPLAYNAME_PREFIX, IProps } from "../../common";
+import * as React from "react";
+import { polyfill } from "react-lifecycles-compat";
+import { AbstractPureComponent2, Classes, DISPLAYNAME_PREFIX, IProps } from "../../common";
import { HOTKEYS_HOTKEY_CHILDREN } from "../../common/errors";
import { isElementOfType } from "../../common/utils";
import { H4 } from "../html/html";
@@ -41,7 +41,8 @@ export interface IHotkeysProps extends IProps {
tabIndex?: number;
}
-export class Hotkeys extends AbstractPureComponent {
+@polyfill
+export class Hotkeys extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.Hotkeys`;
public static defaultProps = {
diff --git a/packages/core/src/components/hotkeys/hotkeysTarget.tsx b/packages/core/src/components/hotkeys/hotkeysTarget.tsx
index f490d00bc8..f9af2e0bae 100644
--- a/packages/core/src/components/hotkeys/hotkeysTarget.tsx
+++ b/packages/core/src/components/hotkeys/hotkeysTarget.tsx
@@ -42,18 +42,10 @@ export function HotkeysTarget>(W
public static displayName = `HotkeysTarget(${getDisplayName(WrappedComponent)})`;
/** @internal */
- public globalHotkeysEvents?: HotkeysEvents;
+ public globalHotkeysEvents: HotkeysEvents = new HotkeysEvents(HotkeyScope.GLOBAL);
/** @internal */
- public localHotkeysEvents?: HotkeysEvents;
-
- public componentWillMount() {
- if (super.componentWillMount != null) {
- super.componentWillMount();
- }
- this.localHotkeysEvents = new HotkeysEvents(HotkeyScope.LOCAL);
- this.globalHotkeysEvents = new HotkeysEvents(HotkeyScope.GLOBAL);
- }
+ public localHotkeysEvents: HotkeysEvents = new HotkeysEvents(HotkeyScope.LOCAL);
public componentDidMount() {
if (super.componentDidMount != null) {
@@ -91,23 +83,32 @@ export function HotkeysTarget>(W
if (isFunction(this.renderHotkeys)) {
const hotkeys = this.renderHotkeys();
- this.localHotkeysEvents.setHotkeys(hotkeys.props);
- this.globalHotkeysEvents.setHotkeys(hotkeys.props);
+ if (this.localHotkeysEvents) {
+ this.localHotkeysEvents.setHotkeys(hotkeys.props);
+ }
+ if (this.globalHotkeysEvents) {
+ this.globalHotkeysEvents.setHotkeys(hotkeys.props);
+ }
if (this.localHotkeysEvents.count() > 0) {
const tabIndex = hotkeys.props.tabIndex === undefined ? 0 : hotkeys.props.tabIndex;
- const { keyDown: existingKeyDown, keyUp: existingKeyUp } = element.props;
- const onKeyDown = (e: React.KeyboardEvent) => {
+ const { onKeyDown: existingKeyDown, onKeyUp: existingKeyUp } = element.props;
+
+ const handleKeyDownWrapper = (e: React.KeyboardEvent) => {
this.localHotkeysEvents.handleKeyDown(e.nativeEvent as KeyboardEvent);
safeInvoke(existingKeyDown, e);
};
- const onKeyUp = (e: React.KeyboardEvent) => {
+ const handleKeyUpWrapper = (e: React.KeyboardEvent) => {
this.localHotkeysEvents.handleKeyUp(e.nativeEvent as KeyboardEvent);
safeInvoke(existingKeyUp, e);
};
- return React.cloneElement(element, { tabIndex, onKeyDown, onKeyUp });
+ return React.cloneElement(element, {
+ onKeyDown: handleKeyDownWrapper,
+ onKeyUp: handleKeyUpWrapper,
+ tabIndex,
+ });
}
}
return element;
diff --git a/packages/core/src/components/hotkeys/keyCombo.tsx b/packages/core/src/components/hotkeys/keyCombo.tsx
index 560f748014..924a491e31 100644
--- a/packages/core/src/components/hotkeys/keyCombo.tsx
+++ b/packages/core/src/components/hotkeys/keyCombo.tsx
@@ -16,7 +16,8 @@
import classNames from "classnames";
import * as React from "react";
-import { Classes, DISPLAYNAME_PREFIX, IProps } from "../../common";
+import { polyfill } from "react-lifecycles-compat";
+import { AbstractPureComponent2, Classes, DISPLAYNAME_PREFIX, IProps } from "../../common";
import { Icon, IconName } from "../icon/icon";
import { normalizeKeyCombo } from "./hotkeyParser";
@@ -47,7 +48,8 @@ export interface IKeyComboProps extends IProps {
minimal?: boolean;
}
-export class KeyCombo extends React.Component {
+@polyfill
+export class KeyCombo extends AbstractPureComponent2 {
public static displayName = `${DISPLAYNAME_PREFIX}.KeyCombo`;
public render() {
diff --git a/packages/core/src/components/html-select/_html-select.scss b/packages/core/src/components/html-select/_html-select.scss
index 8177da0360..dfb3d60082 100644
--- a/packages/core/src/components/html-select/_html-select.scss
+++ b/packages/core/src/components/html-select/_html-select.scss
@@ -85,6 +85,10 @@ Styleguide select
color: $pt-dark-text-color;
}
+ option:disabled {
+ color: $pt-dark-text-color-disabled;
+ }
+
&::after {
color: $pt-dark-icon-color;
}
diff --git a/packages/core/src/components/html-select/html-select.md b/packages/core/src/components/html-select/html-select.md
index 7a79e8f796..287498e743 100644
--- a/packages/core/src/components/html-select/html-select.md
+++ b/packages/core/src/components/html-select/html-select.md
@@ -5,9 +5,11 @@ dropdown caret, so we provide an `HTMLSelect` component to streamline this
process.
- The [`Select`](#select/multi-select) component in the [**@blueprintjs/select**](#select)
- package provides a React alternative to the native HTML `
@## Props
diff --git a/packages/core/src/components/html-select/htmlSelect.tsx b/packages/core/src/components/html-select/htmlSelect.tsx
index cf15d2a4ca..da9e86b2a6 100644
--- a/packages/core/src/components/html-select/htmlSelect.tsx
+++ b/packages/core/src/components/html-select/htmlSelect.tsx
@@ -16,6 +16,8 @@
import classNames from "classnames";
import * as React from "react";
+import { polyfill } from "react-lifecycles-compat";
+import { AbstractPureComponent2 } from "../../common";
import { DISABLED, FILL, HTML_SELECT, LARGE, MINIMAL } from "../../common/classes";
import { IOptionProps } from "../../common/props";
import { IElementRefProps } from "../html/html";
@@ -58,7 +60,8 @@ export interface IHTMLSelectProps
// this component is simple enough that tests would be purely tautological.
/* istanbul ignore next */
-export class HTMLSelect extends React.PureComponent {
+@polyfill
+export class HTMLSelect extends AbstractPureComponent2 {
public render() {
const {
className,
diff --git a/packages/core/src/components/html-table/html-table.md b/packages/core/src/components/html-table/html-table.md
index 59968128ce..34fad8dcb9 100644
--- a/packages/core/src/components/html-table/html-table.md
+++ b/packages/core/src/components/html-table/html-table.md
@@ -4,9 +4,11 @@ This component provides Blueprint styling to native HTML tables.